From 9a5e7e6d0583a01b0aaff606dd5b7e465e47b552 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Tue, 4 Jul 2023 16:31:05 +0200 Subject: [PATCH 01/27] add newly generated openapi.json --- api/openapi.json | 2134 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 1942 insertions(+), 192 deletions(-) diff --git a/api/openapi.json b/api/openapi.json index 862b62a4..a9a81d29 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -1,13 +1,9 @@ { "openapi": "3.0.2", "info": { - "title": "osparc.io web API", - "description": "osparc-simcore public web API specifications", - "version": "0.4.0", - "x-logo": { - "url": "https://raw.githubusercontent.com/ITISFoundation/osparc-manual/b809d93619512eb60c827b7e769c6145758378d0/_media/osparc-logo.svg", - "altText": "osparc-simcore logo" - } + "title": "osparc.io web API (dev)", + "description": "osparc-simcore public API specifications", + "version": "0.4.5-dev" }, "paths": { "/v0/meta": { @@ -107,7 +103,7 @@ "files" ], "summary": "List Files", - "description": "Lists all files stored in the system", + "description": "Lists all files stored in the system\n\nSEE get_files_page for a paginated version of this function", "operationId": "list_files", "responses": { "200": { @@ -132,6 +128,67 @@ ] } }, + "/v0/files/page": { + "get": { + "tags": [ + "files" + ], + "summary": "Get Files Page", + "operationId": "get_files_page", + "parameters": [ + { + "required": false, + "schema": { + "title": "Limit", + "maximum": 100, + "minimum": 1, + "type": "integer", + "default": 50 + }, + "name": "limit", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Offset", + "minimum": 0, + "type": "integer", + "default": 0 + }, + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LimitOffsetPage_File_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "HTTPBasic": [] + } + ] + } + }, "/v0/files/content": { "put": { "tags": [ @@ -222,7 +279,63 @@ } }, "404": { - "description": "File not found" + "description": "File not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "HTTPBasic": [] + } + ] + }, + "delete": { + "tags": [ + "files" + ], + "summary": "Delete File", + "operationId": "delete_file", + "parameters": [ + { + "required": true, + "schema": { + "title": "File Id", + "type": "string", + "format": "uuid" + }, + "name": "file_id", + "in": "path" + } + ], + "responses": { + "204": { + "description": "Successful Response" + }, + "404": { + "description": "File not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } }, "422": { "description": "Validation Error", @@ -266,7 +379,14 @@ "description": "Successful Response" }, "404": { - "description": "File not found" + "description": "File not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } }, "200": { "description": "Returns a arbitrary binary data", @@ -308,7 +428,7 @@ "solvers" ], "summary": "List Solvers", - "description": "Lists all available solvers (latest version)", + "description": "Lists all available solvers (latest version)\n\nSEE get_solvers_page for paginated version of this function", "operationId": "list_solvers", "responses": { "200": { @@ -333,13 +453,69 @@ ] } }, + "/v0/solvers/page": { + "get": { + "tags": [ + "solvers" + ], + "summary": "Get Solvers Page", + "operationId": "get_solvers_page", + "parameters": [ + { + "required": false, + "schema": { + "title": "Limit", + "maximum": 100, + "minimum": 1, + "type": "integer", + "default": 50 + }, + "name": "limit", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Offset", + "minimum": 0, + "type": "integer", + "default": 0 + }, + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LimitOffsetPage_Solver_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, "/v0/solvers/releases": { "get": { "tags": [ "solvers" ], "summary": "Lists All Releases", - "description": "Lists all released solvers (all released versions)", + "description": "Lists all released solvers i.e. all released versions\n\nSEE get_solvers_releases_page for a paginated version of this function", "operationId": "list_solvers_releases", "responses": { "200": { @@ -364,6 +540,62 @@ ] } }, + "/v0/solversreleases/page": { + "get": { + "tags": [ + "solvers" + ], + "summary": "Get Solvers Releases Page", + "operationId": "get_solvers_releases_page", + "parameters": [ + { + "required": false, + "schema": { + "title": "Limit", + "maximum": 100, + "minimum": 1, + "type": "integer", + "default": 50 + }, + "name": "limit", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Offset", + "minimum": 0, + "type": "integer", + "default": 0 + }, + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LimitOffsetPage_Solver_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, "/v0/solvers/{solver_key}/latest": { "get": { "tags": [ @@ -377,7 +609,7 @@ "required": true, "schema": { "title": "Solver Key", - "pattern": "^(simcore)/(services)/comp(/[\\w/-]+)+$", + "pattern": "^simcore/services/comp/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", "type": "string" }, "name": "solver_key", @@ -419,14 +651,14 @@ "solvers" ], "summary": "List Solver Releases", - "description": "Lists all releases of a given solver", + "description": "Lists all releases of a given (one) solver\n\nSEE get_solver_releases_page for a paginated version of this function", "operationId": "list_solver_releases", "parameters": [ { "required": true, "schema": { "title": "Solver Key", - "pattern": "^(simcore)/(services)/comp(/[\\w/-]+)+$", + "pattern": "^simcore/services/comp/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", "type": "string" }, "name": "solver_key", @@ -466,34 +698,46 @@ ] } }, - "/v0/solvers/{solver_key}/releases/{version}": { + "/v0/solvers/{solver_key}/releases/page": { "get": { "tags": [ "solvers" ], - "summary": "Get Solver Release", - "description": "Gets a specific release of a solver", - "operationId": "get_solver_release", + "summary": "Get Solver Releases Page", + "operationId": "get_solver_releases_page", "parameters": [ { "required": true, "schema": { "title": "Solver Key", - "pattern": "^(simcore)/(services)/comp(/[\\w/-]+)+$", + "pattern": "^simcore/services/comp/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", "type": "string" }, "name": "solver_key", "in": "path" }, { - "required": true, + "required": false, "schema": { - "title": "Version", - "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-]+)*)?$", - "type": "string" + "title": "Limit", + "maximum": 100, + "minimum": 1, + "type": "integer", + "default": 50 }, - "name": "version", - "in": "path" + "name": "limit", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Offset", + "minimum": 0, + "type": "integer", + "default": 0 + }, + "name": "offset", + "in": "query" } ], "responses": { @@ -502,7 +746,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Solver" + "$ref": "#/components/schemas/LimitOffsetPage_Solver_" } } } @@ -517,28 +761,23 @@ } } } - }, - "security": [ - { - "HTTPBasic": [] - } - ] + } } }, - "/v0/solvers/{solver_key}/releases/{version}/jobs": { + "/v0/solvers/{solver_key}/releases/{version}": { "get": { "tags": [ "solvers" ], - "summary": "List Jobs", - "description": "List of all jobs in a specific released solver", - "operationId": "list_jobs", + "summary": "Get Solver Release", + "description": "Gets a specific release of a solver", + "operationId": "get_solver_release", "parameters": [ { "required": true, "schema": { "title": "Solver Key", - "pattern": "^(simcore)/(services)/comp(/[\\w/-]+)+$", + "pattern": "^simcore/services/comp/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", "type": "string" }, "name": "solver_key", @@ -548,6 +787,7 @@ "required": true, "schema": { "title": "Version", + "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-]+)*)?$", "type": "string" }, "name": "version", @@ -560,11 +800,7 @@ "content": { "application/json": { "schema": { - "title": "Response List Jobs V0 Solvers Solver Key Releases Version Jobs Get", - "type": "array", - "items": { - "$ref": "#/components/schemas/Job" - } + "$ref": "#/components/schemas/Solver" } } } @@ -585,20 +821,22 @@ "HTTPBasic": [] } ] - }, - "post": { + } + }, + "/v0/solvers/{solver_key}/releases/{version}/ports": { + "get": { "tags": [ "solvers" ], - "summary": "Create Job", - "description": "Creates a job in a specific release with given inputs.\n\nNOTE: This operation does **not** start the job", - "operationId": "create_job", + "summary": "List Solver Ports", + "description": "Lists inputs and outputs of a given solver\n\nNew in *version 0.5.0* (only with API_SERVER_DEV_FEATURES_ENABLED=1)", + "operationId": "list_solver_ports", "parameters": [ { "required": true, "schema": { "title": "Solver Key", - "pattern": "^(simcore)/(services)/comp(/[\\w/-]+)+$", + "pattern": "^simcore/services/comp/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", "type": "string" }, "name": "solver_key", @@ -608,29 +846,20 @@ "required": true, "schema": { "title": "Version", + "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-]+)*)?$", "type": "string" }, "name": "version", "in": "path" } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/JobInputs" - } - } - }, - "required": true - }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Job" + "$ref": "#/components/schemas/OnePage_SolverPort_" } } } @@ -653,20 +882,20 @@ ] } }, - "/v0/solvers/{solver_key}/releases/{version}/jobs/{job_id}": { + "/v0/solvers/{solver_key}/releases/{version}/jobs": { "get": { "tags": [ "solvers" ], - "summary": "Get Job", - "description": "Gets job of a given solver", - "operationId": "get_job", + "summary": "List Jobs", + "description": "List of jobs in a specific released solver (limited to 20 jobs)\n\nSEE get_jobs_page for paginated version of this function", + "operationId": "list_jobs", "parameters": [ { "required": true, "schema": { "title": "Solver Key", - "pattern": "^(simcore)/(services)/comp(/[\\w/-]+)+$", + "pattern": "^simcore/services/comp/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", "type": "string" }, "name": "solver_key", @@ -681,16 +910,6 @@ }, "name": "version", "in": "path" - }, - { - "required": true, - "schema": { - "title": "Job Id", - "type": "string", - "format": "uuid" - }, - "name": "job_id", - "in": "path" } ], "responses": { @@ -699,9 +918,80 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Job" - } - } + "title": "Response List Jobs V0 Solvers Solver Key Releases Version Jobs Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/Job" + } + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "HTTPBasic": [] + } + ] + }, + "post": { + "tags": [ + "solvers" + ], + "summary": "Create Job", + "description": "Creates a job in a specific release with given inputs.\n\nNOTE: This operation does **not** start the job", + "operationId": "create_job", + "parameters": [ + { + "required": true, + "schema": { + "title": "Solver Key", + "pattern": "^simcore/services/comp/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", + "type": "string" + }, + "name": "solver_key", + "in": "path" + }, + { + "required": true, + "schema": { + "title": "Version", + "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-]+)*)?$", + "type": "string" + }, + "name": "version", + "in": "path" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JobInputs" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" + } + } } }, "422": { @@ -722,19 +1012,102 @@ ] } }, - "/v0/solvers/{solver_key}/releases/{version}/jobs/{job_id}:start": { - "post": { + "/v0/solvers/{solver_key}/releases/{version}/jobs/page": { + "get": { "tags": [ "solvers" ], - "summary": "Start Job", - "operationId": "start_job", + "summary": "Get Jobs Page", + "description": "List of jobs on a specific released solver (includes pagination)\n\n\nBreaking change in *version 0.5*: response model changed from list[Job] to pagination Page[Job].", + "operationId": "get_jobs_page", + "parameters": [ + { + "required": true, + "schema": { + "title": "Solver Key", + "pattern": "^simcore/services/comp/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", + "type": "string" + }, + "name": "solver_key", + "in": "path" + }, + { + "required": true, + "schema": { + "title": "Version", + "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-]+)*)?$", + "type": "string" + }, + "name": "version", + "in": "path" + }, + { + "required": false, + "schema": { + "title": "Limit", + "maximum": 100, + "minimum": 1, + "type": "integer", + "default": 50 + }, + "name": "limit", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Offset", + "minimum": 0, + "type": "integer", + "default": 0 + }, + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LimitOffsetPage_Job_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "HTTPBasic": [] + } + ] + } + }, + "/v0/solvers/{solver_key}/releases/{version}/jobs/{job_id}": { + "get": { + "tags": [ + "solvers" + ], + "summary": "Get Job", + "description": "Gets job of a given solver", + "operationId": "get_job", "parameters": [ { "required": true, "schema": { "title": "Solver Key", - "pattern": "^(simcore)/(services)/comp(/[\\w/-]+)+$", + "pattern": "^simcore/services/comp/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", "type": "string" }, "name": "solver_key", @@ -767,7 +1140,77 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/JobStatus" + "$ref": "#/components/schemas/Job" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "HTTPBasic": [] + } + ] + }, + "delete": { + "tags": [ + "solvers" + ], + "summary": "Delete Job", + "description": "Deletes an existing solver job\n\nNew in *version 0.5*", + "operationId": "delete_job", + "parameters": [ + { + "required": true, + "schema": { + "title": "Solver Key", + "pattern": "^simcore/services/comp/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", + "type": "string" + }, + "name": "solver_key", + "in": "path" + }, + { + "required": true, + "schema": { + "title": "Version", + "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-]+)*)?$", + "type": "string" + }, + "name": "version", + "in": "path" + }, + { + "required": true, + "schema": { + "title": "Job Id", + "type": "string", + "format": "uuid" + }, + "name": "job_id", + "in": "path" + } + ], + "responses": { + "204": { + "description": "Successful Response" + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" } } } @@ -790,19 +1233,20 @@ ] } }, - "/v0/solvers/{solver_key}/releases/{version}/jobs/{job_id}:stop": { + "/v0/solvers/{solver_key}/releases/{version}/jobs/{job_id}:start": { "post": { "tags": [ "solvers" ], - "summary": "Stop Job", - "operationId": "stop_job", + "summary": "Start Job", + "description": "Starts job job_id created with the solver solver_key:version\n\nNew in *version 0.4.3*: cluster_id", + "operationId": "start_job", "parameters": [ { "required": true, "schema": { "title": "Solver Key", - "pattern": "^(simcore)/(services)/comp(/[\\w/-]+)+$", + "pattern": "^simcore/services/comp/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", "type": "string" }, "name": "solver_key", @@ -827,6 +1271,16 @@ }, "name": "job_id", "in": "path" + }, + { + "required": false, + "schema": { + "title": "Cluster Id", + "minimum": 0, + "type": "integer" + }, + "name": "cluster_id", + "in": "query" } ], "responses": { @@ -835,7 +1289,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Job" + "$ref": "#/components/schemas/JobStatus" } } } @@ -858,19 +1312,19 @@ ] } }, - "/v0/solvers/{solver_key}/releases/{version}/jobs/{job_id}:inspect": { + "/v0/solvers/{solver_key}/releases/{version}/jobs/{job_id}:stop": { "post": { "tags": [ "solvers" ], - "summary": "Inspect Job", - "operationId": "inspect_job", + "summary": "Stop Job", + "operationId": "stop_job", "parameters": [ { "required": true, "schema": { "title": "Solver Key", - "pattern": "^(simcore)/(services)/comp(/[\\w/-]+)+$", + "pattern": "^simcore/services/comp/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", "type": "string" }, "name": "solver_key", @@ -903,7 +1357,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/JobStatus" + "$ref": "#/components/schemas/Job" } } } @@ -926,19 +1380,19 @@ ] } }, - "/v0/solvers/{solver_key}/releases/{version}/jobs/{job_id}/outputs": { - "get": { + "/v0/solvers/{solver_key}/releases/{version}/jobs/{job_id}:inspect": { + "post": { "tags": [ "solvers" ], - "summary": "Get Job Outputs", - "operationId": "get_job_outputs", + "summary": "Inspect Job", + "operationId": "inspect_job", "parameters": [ { "required": true, "schema": { "title": "Solver Key", - "pattern": "^(simcore)/(services)/comp(/[\\w/-]+)+$", + "pattern": "^simcore/services/comp/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", "type": "string" }, "name": "solver_key", @@ -971,7 +1425,993 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/JobOutputs" + "$ref": "#/components/schemas/JobStatus" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "HTTPBasic": [] + } + ] + } + }, + "/v0/solvers/{solver_key}/releases/{version}/jobs/{job_id}/outputs": { + "get": { + "tags": [ + "solvers" + ], + "summary": "Get Job Outputs", + "operationId": "get_job_outputs", + "parameters": [ + { + "required": true, + "schema": { + "title": "Solver Key", + "pattern": "^simcore/services/comp/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", + "type": "string" + }, + "name": "solver_key", + "in": "path" + }, + { + "required": true, + "schema": { + "title": "Version", + "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-]+)*)?$", + "type": "string" + }, + "name": "version", + "in": "path" + }, + { + "required": true, + "schema": { + "title": "Job Id", + "type": "string", + "format": "uuid" + }, + "name": "job_id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JobOutputs" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "HTTPBasic": [] + } + ] + } + }, + "/v0/solvers/{solver_key}/releases/{version}/jobs/{job_id}/outputs/logfile": { + "get": { + "tags": [ + "solvers" + ], + "summary": "Get Job Output Logfile", + "description": "Special extra output with persistent logs file for the solver run.\n\nNOTE: this is not a log stream but a predefined output that is only\navailable after the job is done.\n\nNew in *version 0.4.0*", + "operationId": "get_job_output_logfile", + "parameters": [ + { + "required": true, + "schema": { + "title": "Solver Key", + "pattern": "^simcore/services/comp/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", + "type": "string" + }, + "name": "solver_key", + "in": "path" + }, + { + "required": true, + "schema": { + "title": "Version", + "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-]+)*)?$", + "type": "string" + }, + "name": "version", + "in": "path" + }, + { + "required": true, + "schema": { + "title": "Job Id", + "type": "string", + "format": "uuid" + }, + "name": "job_id", + "in": "path" + } + ], + "responses": { + "307": { + "description": "Successful Response" + }, + "200": { + "description": "Returns a log file", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + }, + "application/zip": { + "schema": { + "type": "string", + "format": "binary" + } + }, + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "Log not found" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "HTTPBasic": [] + } + ] + } + }, + "/v0/solvers/{solver_key}/releases/{version}/jobs/{job_id}/metadata": { + "get": { + "tags": [ + "solvers" + ], + "summary": "Get Job Custom Metadata", + "description": "Gets custom metadata from a job", + "operationId": "get_job_custom_metadata", + "parameters": [ + { + "required": true, + "schema": { + "title": "Solver Key", + "pattern": "^simcore/services/comp/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", + "type": "string" + }, + "name": "solver_key", + "in": "path" + }, + { + "required": true, + "schema": { + "title": "Version", + "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-]+)*)?$", + "type": "string" + }, + "name": "version", + "in": "path" + }, + { + "required": true, + "schema": { + "title": "Job Id", + "type": "string", + "format": "uuid" + }, + "name": "job_id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Get Job Custom Metadata V0 Solvers Solver Key Releases Version Jobs Job Id Metadata Get", + "type": "object" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "solvers" + ], + "summary": "Create Job Custom Metadata", + "description": "Attaches custom metadata to a job.", + "operationId": "create_job_custom_metadata", + "parameters": [ + { + "required": true, + "schema": { + "title": "Solver Key", + "pattern": "^simcore/services/comp/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", + "type": "string" + }, + "name": "solver_key", + "in": "path" + }, + { + "required": true, + "schema": { + "title": "Version", + "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-]+)*)?$", + "type": "string" + }, + "name": "version", + "in": "path" + }, + { + "required": true, + "schema": { + "title": "Job Id", + "type": "string", + "format": "uuid" + }, + "name": "job_id", + "in": "path" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "Metadata", + "type": "object" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v0/studies/": { + "get": { + "tags": [ + "studies" + ], + "summary": "List Studies", + "operationId": "list_studies", + "parameters": [ + { + "required": false, + "schema": { + "title": "Limit", + "maximum": 100, + "minimum": 1, + "type": "integer", + "default": 50 + }, + "name": "limit", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Offset", + "minimum": 0, + "type": "integer", + "default": 0 + }, + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LimitOffsetPage_Study_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v0/studies/{study_id}": { + "get": { + "tags": [ + "studies" + ], + "summary": "Get Study", + "operationId": "get_study", + "parameters": [ + { + "required": true, + "schema": { + "title": "Study Id", + "type": "string", + "format": "uuid" + }, + "name": "study_id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Study" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v0/studies/{study_id}/ports": { + "get": { + "tags": [ + "studies" + ], + "summary": "List Study Ports", + "description": "Lists metadata on ports of a given study\n\nNew in *version 0.5.0* (only with API_SERVER_DEV_FEATURES_ENABLED=1)", + "operationId": "list_study_ports", + "parameters": [ + { + "required": true, + "schema": { + "title": "Study Id", + "type": "string", + "format": "uuid" + }, + "name": "study_id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OnePage_StudyPort_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "HTTPBasic": [] + } + ] + } + }, + "/v0/studies/{study_id}/jobs": { + "get": { + "tags": [ + "studies" + ], + "summary": "List Study Jobs", + "operationId": "list_study_jobs", + "parameters": [ + { + "required": true, + "schema": { + "title": "Study Id", + "type": "string", + "format": "uuid" + }, + "name": "study_id", + "in": "path" + }, + { + "required": false, + "schema": { + "title": "Limit", + "maximum": 100, + "minimum": 1, + "type": "integer", + "default": 50 + }, + "name": "limit", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Offset", + "minimum": 0, + "type": "integer", + "default": 0 + }, + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LimitOffsetPage_Job_" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "studies" + ], + "summary": "Create Study Job", + "operationId": "create_study_job", + "parameters": [ + { + "required": true, + "schema": { + "title": "Study Id", + "type": "string", + "format": "uuid" + }, + "name": "study_id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v0/studies/{study_id}/jobs/{job_id}": { + "get": { + "tags": [ + "studies" + ], + "summary": "Get Study Job", + "operationId": "get_study_job", + "parameters": [ + { + "required": true, + "schema": { + "title": "Study Id", + "type": "string", + "format": "uuid" + }, + "name": "study_id", + "in": "path" + }, + { + "required": true, + "schema": { + "title": "Job Id", + "type": "string", + "format": "uuid" + }, + "name": "job_id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "studies" + ], + "summary": "Delete Study Job", + "operationId": "delete_study_job", + "parameters": [ + { + "required": true, + "schema": { + "title": "Study Id", + "type": "string", + "format": "uuid" + }, + "name": "study_id", + "in": "path" + }, + { + "required": true, + "schema": { + "title": "Job Id", + "type": "string", + "format": "uuid" + }, + "name": "job_id", + "in": "path" + } + ], + "responses": { + "204": { + "description": "Successful Response" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v0/studies/{study_id}/jobs/{job_id}:start": { + "post": { + "tags": [ + "studies" + ], + "summary": "Start Study Job", + "operationId": "start_study_job", + "parameters": [ + { + "required": true, + "schema": { + "title": "Study Id", + "type": "string", + "format": "uuid" + }, + "name": "study_id", + "in": "path" + }, + { + "required": true, + "schema": { + "title": "Job Id", + "type": "string", + "format": "uuid" + }, + "name": "job_id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JobStatus" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v0/studies/{study_id}/jobs/{job_id}:stop": { + "post": { + "tags": [ + "studies" + ], + "summary": "Stop Study Job", + "operationId": "stop_study_job", + "parameters": [ + { + "required": true, + "schema": { + "title": "Study Id", + "type": "string", + "format": "uuid" + }, + "name": "study_id", + "in": "path" + }, + { + "required": true, + "schema": { + "title": "Job Id", + "type": "string", + "format": "uuid" + }, + "name": "job_id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v0/studies/{study_id}/jobs/{job_id}:inspect": { + "post": { + "tags": [ + "studies" + ], + "summary": "Inspect Study Job", + "operationId": "inspect_study_job", + "parameters": [ + { + "required": true, + "schema": { + "title": "Study Id", + "type": "string", + "format": "uuid" + }, + "name": "study_id", + "in": "path" + }, + { + "required": true, + "schema": { + "title": "Job Id", + "type": "string", + "format": "uuid" + }, + "name": "job_id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JobStatus" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v0/studies/{study_id}/jobs/{job_id}/outputs": { + "post": { + "tags": [ + "studies" + ], + "summary": "Get Study Job Outputs", + "operationId": "get_study_job_outputs", + "parameters": [ + { + "required": true, + "schema": { + "title": "Study Id", + "type": "string", + "format": "uuid" + }, + "name": "study_id", + "in": "path" + }, + { + "required": true, + "schema": { + "title": "Job Id", + "type": "string", + "format": "uuid" + }, + "name": "job_id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JobOutputs" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v0/studies/{study_id}/jobs/{job_id}/outputs/logfile": { + "post": { + "tags": [ + "studies" + ], + "summary": "Get Study Job Output Logfile", + "operationId": "get_study_job_output_logfile", + "parameters": [ + { + "required": true, + "schema": { + "title": "Study Id", + "type": "string", + "format": "uuid" + }, + "name": "study_id", + "in": "path" + }, + { + "required": true, + "schema": { + "title": "Job Id", + "type": "string", + "format": "uuid" + }, + "name": "job_id", + "in": "path" + } + ], + "responses": { + "307": { + "description": "Successful Response" + }, + "200": { + "description": "Returns a log file", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + }, + "application/zip": { + "schema": { + "type": "string", + "format": "binary" + } + }, + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "Log not found" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v0/studies/{study_id}/jobs/{job_id}/metadata": { + "get": { + "tags": [ + "studies" + ], + "summary": "Get Study Job Custom Metadata", + "description": "Gets custom metadata from a job", + "operationId": "get_study_job_custom_metadata", + "parameters": [ + { + "required": true, + "schema": { + "title": "Study Id", + "type": "string", + "format": "uuid" + }, + "name": "study_id", + "in": "path" + }, + { + "required": true, + "schema": { + "title": "Job Id", + "type": "string", + "format": "uuid" + }, + "name": "job_id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Get Study Job Custom Metadata V0 Studies Study Id Jobs Job Id Metadata Get", + "type": "object" } } } @@ -986,41 +2426,24 @@ } } } - }, - "security": [ - { - "HTTPBasic": [] - } - ] - } - }, - "/v0/solvers/{solver_key}/releases/{version}/jobs/{job_id}/outputs/logfile": { - "get": { + } + }, + "post": { "tags": [ - "solvers" + "studies" ], - "summary": "Get Job Output Logfile", - "description": "Special extra output with persistent logs file for the solver run.\n\nNOTE: this is not a log stream but a predefined output that is only\navailable after the job is done.", - "operationId": "get_job_output_logfile", + "summary": "Create Study Job Custom Metadata", + "description": "Attaches custom metadata to a job", + "operationId": "create_study_job_custom_metadata", "parameters": [ { "required": true, "schema": { - "title": "Solver Key", - "pattern": "^(simcore)/(services)/comp(/[\\w/-]+)+$", - "type": "string" - }, - "name": "solver_key", - "in": "path" - }, - { - "required": true, - "schema": { - "title": "Version", - "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-]+)*)?$", - "type": "string" + "title": "Study Id", + "type": "string", + "format": "uuid" }, - "name": "version", + "name": "study_id", "in": "path" }, { @@ -1034,35 +2457,26 @@ "in": "path" } ], - "responses": { - "307": { - "description": "Successful Response" + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "Metadata", + "type": "object" + } + } }, + "required": true + }, + "responses": { "200": { - "description": "Returns a log file", + "description": "Successful Response", "content": { - "application/octet-stream": { - "schema": { - "type": "string", - "format": "binary" - } - }, - "application/zip": { - "schema": { - "type": "string", - "format": "binary" - } - }, - "text/plain": { - "schema": { - "type": "string" - } + "application/json": { + "schema": {} } } }, - "404": { - "description": "Log not found" - }, "422": { "description": "Validation Error", "content": { @@ -1073,12 +2487,7 @@ } } } - }, - "security": [ - { - "HTTPBasic": [] - } - ] + } } } }, @@ -1098,6 +2507,20 @@ } } }, + "ErrorGet": { + "title": "ErrorGet", + "required": [ + "errors" + ], + "type": "object", + "properties": { + "errors": { + "title": "Errors", + "type": "array", + "items": {} + } + } + }, "File": { "title": "File", "required": [ @@ -1188,7 +2611,7 @@ }, "name": { "title": "Name", - "pattern": "^([^\\s/]+/?)+$", + "pattern": "^([^\\s/]+/?){1,10}$", "type": "string" }, "inputs_checksum": { @@ -1204,7 +2627,7 @@ }, "runner_name": { "title": "Runner Name", - "pattern": "^([^\\s/]+/?)+$", + "pattern": "^([^\\s/]+/?){1,10}$", "type": "string", "description": "Runner that executes job" }, @@ -1229,7 +2652,7 @@ "maxLength": 2083, "minLength": 1, "type": "string", - "description": "Link to the job outputs (sub-collection", + "description": "Link to the job outputs (sub-collection)", "format": "uri" } }, @@ -1239,9 +2662,9 @@ "runner_name": "solvers/isolve/releases/1.3.4", "inputs_checksum": "12345", "created_at": "2021-01-22T23:59:52.322176", - "url": "https://api.osparc.io/v0/jobs/f622946d-fd29-35b9-a193-abdd1095167c", + "url": "https://api.osparc.io/v0/solvers/isolve/releases/1.3.4/jobs/f622946d-fd29-35b9-a193-abdd1095167c", "runner_url": "https://api.osparc.io/v0/solvers/isolve/releases/1.3.4", - "outputs_url": "https://api.osparc.io/v0/jobs/f622946d-fd29-35b9-a193-abdd1095167c/outputs" + "outputs_url": "https://api.osparc.io/v0/solvers/isolve/releases/1.3.4/jobs/f622946d-fd29-35b9-a193-abdd1095167c/outputs" } }, "JobInputs": { @@ -1270,6 +2693,10 @@ }, { "type": "string" + }, + { + "type": "array", + "items": {} } ] } @@ -1282,9 +2709,8 @@ "title": "Temperature", "enabled": true, "input_file": { - "id": "0a3b2c56-dbcd-4871-b93b-d454b7883f9f", "filename": "input.txt", - "content_type": "text/plain" + "id": "0a3b2c56-dbcd-4871-b93b-d454b7883f9f" } } } @@ -1322,6 +2748,10 @@ }, { "type": "string" + }, + { + "type": "array", + "items": {} } ] } @@ -1335,9 +2765,8 @@ "title": "Specific Absorption Rate", "enabled": false, "output_file": { - "id": "0a3b2c56-dbcd-4871-b93b-d454b7883f9f", "filename": "sar_matrix.txt", - "content_type": "text/plain" + "id": "0a3b2c56-dbcd-4871-b93b-d454b7883f9f" } } } @@ -1357,18 +2786,19 @@ "format": "uuid" }, "state": { - "$ref": "#/components/schemas/TaskStates" + "$ref": "#/components/schemas/RunningState" }, "progress": { "title": "Progress", - "maximum": 100.0, - "minimum": 0.0, + "maximum": 100, + "minimum": 0, "type": "integer", "default": 0 }, "submitted_at": { "title": "Submitted At", "type": "string", + "description": "Last modification timestamp of the solver job", "format": "date-time" }, "started_at": { @@ -1392,11 +2822,188 @@ "started_at": "2021-04-01 07:16:43.670610" } }, + "LimitOffsetPage_File_": { + "title": "LimitOffsetPage[File]", + "required": [ + "items", + "total", + "links" + ], + "type": "object", + "properties": { + "items": { + "title": "Items", + "type": "array", + "items": { + "$ref": "#/components/schemas/File" + } + }, + "total": { + "title": "Total", + "minimum": 0, + "type": "integer" + }, + "limit": { + "title": "Limit", + "minimum": 1, + "type": "integer" + }, + "offset": { + "title": "Offset", + "minimum": 0, + "type": "integer" + }, + "links": { + "$ref": "#/components/schemas/Links" + } + } + }, + "LimitOffsetPage_Job_": { + "title": "LimitOffsetPage[Job]", + "required": [ + "items", + "total", + "links" + ], + "type": "object", + "properties": { + "items": { + "title": "Items", + "type": "array", + "items": { + "$ref": "#/components/schemas/Job" + } + }, + "total": { + "title": "Total", + "minimum": 0, + "type": "integer" + }, + "limit": { + "title": "Limit", + "minimum": 1, + "type": "integer" + }, + "offset": { + "title": "Offset", + "minimum": 0, + "type": "integer" + }, + "links": { + "$ref": "#/components/schemas/Links" + } + } + }, + "LimitOffsetPage_Solver_": { + "title": "LimitOffsetPage[Solver]", + "required": [ + "items", + "total", + "links" + ], + "type": "object", + "properties": { + "items": { + "title": "Items", + "type": "array", + "items": { + "$ref": "#/components/schemas/Solver" + } + }, + "total": { + "title": "Total", + "minimum": 0, + "type": "integer" + }, + "limit": { + "title": "Limit", + "minimum": 1, + "type": "integer" + }, + "offset": { + "title": "Offset", + "minimum": 0, + "type": "integer" + }, + "links": { + "$ref": "#/components/schemas/Links" + } + } + }, + "LimitOffsetPage_Study_": { + "title": "LimitOffsetPage[Study]", + "required": [ + "items", + "total", + "links" + ], + "type": "object", + "properties": { + "items": { + "title": "Items", + "type": "array", + "items": { + "$ref": "#/components/schemas/Study" + } + }, + "total": { + "title": "Total", + "minimum": 0, + "type": "integer" + }, + "limit": { + "title": "Limit", + "minimum": 1, + "type": "integer" + }, + "offset": { + "title": "Offset", + "minimum": 0, + "type": "integer" + }, + "links": { + "$ref": "#/components/schemas/Links" + } + } + }, + "Links": { + "title": "Links", + "type": "object", + "properties": { + "first": { + "title": "First", + "type": "string", + "example": "/api/v1/users?limit=1&offset1" + }, + "last": { + "title": "Last", + "type": "string", + "example": "/api/v1/users?limit=1&offset1" + }, + "self": { + "title": "Self", + "type": "string", + "example": "/api/v1/users?limit=1&offset1" + }, + "next": { + "title": "Next", + "type": "string", + "example": "/api/v1/users?limit=1&offset1" + }, + "prev": { + "title": "Prev", + "type": "string", + "example": "/api/v1/users?limit=1&offset1" + } + } + }, "Meta": { "title": "Meta", "required": [ "name", - "version" + "version", + "docs_url", + "docs_dev_url" ], "type": "object", "properties": { @@ -1423,16 +3030,14 @@ "maxLength": 65536, "minLength": 1, "type": "string", - "format": "uri", - "default": "https://docs.osparc.io" + "format": "uri" }, "docs_dev_url": { "title": "Docs Dev Url", "maxLength": 65536, "minLength": 1, "type": "string", - "format": "uri", - "default": "https://api.osparc.io/dev/docs" + "format": "uri" } }, "example": { @@ -1442,10 +3047,54 @@ "v1": "1.3.4", "v2": "2.4.45" }, - "doc_url": "https://api.osparc.io/doc", - "doc_dev_url": "https://api.osparc.io/dev/doc" + "docs_url": "https://api.osparc.io/dev/doc", + "docs_dev_url": "https://api.osparc.io/dev/doc" } }, + "OnePage_SolverPort_": { + "title": "OnePage[SolverPort]", + "required": [ + "items" + ], + "type": "object", + "properties": { + "items": { + "title": "Items", + "type": "array", + "items": { + "$ref": "#/components/schemas/SolverPort" + } + }, + "total": { + "title": "Total", + "minimum": 0, + "type": "integer" + } + }, + "description": "A single page is used to envelope a small sequence that does not require\npagination\n\nIf total > MAXIMUM_NUMBER_OF_ITEMS_PER_PAGE, we should consider extending this\nentrypoint to proper pagination" + }, + "OnePage_StudyPort_": { + "title": "OnePage[StudyPort]", + "required": [ + "items" + ], + "type": "object", + "properties": { + "items": { + "title": "Items", + "type": "array", + "items": { + "$ref": "#/components/schemas/StudyPort" + } + }, + "total": { + "title": "Total", + "minimum": 0, + "type": "integer" + } + }, + "description": "A single page is used to envelope a small sequence that does not require\npagination\n\nIf total > MAXIMUM_NUMBER_OF_ITEMS_PER_PAGE, we should consider extending this\nentrypoint to proper pagination" + }, "Profile": { "title": "Profile", "required": [ @@ -1519,6 +3168,22 @@ } } }, + "RunningState": { + "title": "RunningState", + "enum": [ + "UNKNOWN", + "PUBLISHED", + "NOT_STARTED", + "PENDING", + "STARTED", + "RETRY", + "SUCCESS", + "FAILED", + "ABORTED" + ], + "type": "string", + "description": "State of execution of a project's computational workflow\n\nSEE StateType for task state" + }, "Solver": { "title": "Solver", "required": [ @@ -1532,7 +3197,7 @@ "properties": { "id": { "title": "Id", - "pattern": "^(simcore)/(services)/comp(/[\\w/-]+)+$", + "pattern": "^simcore/services/comp/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", "type": "string", "description": "Solver identifier" }, @@ -1574,21 +3239,99 @@ "url": "https://api.osparc.io/v0/solvers/simcore%2Fservices%2Fcomp%2Fisolve/releases/2.1.1" } }, - "TaskStates": { - "title": "TaskStates", - "enum": [ - "UNKNOWN", - "PUBLISHED", - "NOT_STARTED", - "PENDING", - "STARTED", - "RETRY", - "SUCCESS", - "FAILED", - "ABORTED" + "SolverPort": { + "title": "SolverPort", + "required": [ + "key", + "kind" ], - "type": "string", - "description": "An enumeration." + "type": "object", + "properties": { + "key": { + "title": "Key name", + "pattern": "^[^_\\W0-9]\\w*$", + "type": "string", + "description": "port identifier name" + }, + "kind": { + "title": "Kind", + "enum": [ + "input", + "output" + ], + "type": "string" + }, + "content_schema": { + "title": "Content Schema", + "type": "object", + "description": "jsonschema for the port's value. SEE https://json-schema.org" + } + }, + "example": { + "key": "input_2", + "kind": "input", + "content_schema": { + "title": "Sleep interval", + "type": "integer", + "x_unit": "second", + "minimum": 0, + "maximum": 5 + } + } + }, + "Study": { + "title": "Study", + "required": [ + "uid" + ], + "type": "object", + "properties": { + "uid": { + "title": "Uid", + "type": "string", + "format": "uuid" + } + } + }, + "StudyPort": { + "title": "StudyPort", + "required": [ + "key", + "kind" + ], + "type": "object", + "properties": { + "key": { + "title": "Key name", + "type": "string", + "description": "port identifier name.Correponds to the UUID of the parameter/probe node in the study", + "format": "uuid" + }, + "kind": { + "title": "Kind", + "enum": [ + "input", + "output" + ], + "type": "string" + }, + "content_schema": { + "title": "Content Schema", + "type": "object", + "description": "jsonschema for the port's value. SEE https://json-schema.org" + } + }, + "example": { + "key": "input_2", + "kind": "input", + "content_schema": { + "title": "Sleep interval", + "type": "integer", + "x_unit": "second", + "minimum": 0, + "maximum": 5 + } + } }, "UserRoleEnum": { "title": "UserRoleEnum", @@ -1637,7 +3380,14 @@ "title": "Location", "type": "array", "items": { - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] } }, "msg": { From c9ae76de2515bb550196959481d0878bc5a3b647 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Thu, 6 Jul 2023 14:53:36 +0200 Subject: [PATCH 02/27] adapt tests so they pass --- clients/python/client/osparc/__init__.py | 3 ++- clients/python/test_osparc_client/test_meta.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/clients/python/client/osparc/__init__.py b/clients/python/client/osparc/__init__.py index 2efc72bc..415b63b5 100644 --- a/clients/python/client/osparc/__init__.py +++ b/clients/python/client/osparc/__init__.py @@ -24,7 +24,6 @@ Profile, ProfileUpdate, Solver, - TaskStates, UserRoleEnum, UsersGroup, ValidationError, @@ -35,6 +34,8 @@ UsersApi ) +from osparc_client import RunningState as TaskStates + __all__ = [ # imports from osparc_client "__version__", diff --git a/clients/python/test_osparc_client/test_meta.py b/clients/python/test_osparc_client/test_meta.py index 3a0983de..a1838093 100644 --- a/clients/python/test_osparc_client/test_meta.py +++ b/clients/python/test_osparc_client/test_meta.py @@ -48,6 +48,8 @@ def make_instance(self, include_optional): return Meta( name = '0', version = '0.5.0', + docs_url = 'https://docs.osparc.io', + docs_dev_url = 'https://api.osparc.io/dev/docs' ) def testMeta(self): From f51fa3130b6d7242d4e221dbddf542bcbecaaf9a Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Mon, 10 Jul 2023 12:19:53 +0200 Subject: [PATCH 03/27] update openapi.json --- api/openapi.json | 103 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 86 insertions(+), 17 deletions(-) diff --git a/api/openapi.json b/api/openapi.json index a9a81d29..f7d8dad5 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -540,7 +540,7 @@ ] } }, - "/v0/solversreleases/page": { + "/v0/solvers/releases/page": { "get": { "tags": [ "solvers" @@ -1649,8 +1649,7 @@ "content": { "application/json": { "schema": { - "title": "Response Get Job Custom Metadata V0 Solvers Solver Key Releases Version Jobs Job Id Metadata Get", - "type": "object" + "$ref": "#/components/schemas/JobMetadata" } } } @@ -1667,13 +1666,13 @@ } } }, - "post": { + "put": { "tags": [ "solvers" ], - "summary": "Create Job Custom Metadata", - "description": "Attaches custom metadata to a job.", - "operationId": "create_job_custom_metadata", + "summary": "Replace Job Custom Metadata", + "description": "Changes job's custom metadata", + "operationId": "replace_job_custom_metadata", "parameters": [ { "required": true, @@ -1710,8 +1709,7 @@ "content": { "application/json": { "schema": { - "title": "Metadata", - "type": "object" + "$ref": "#/components/schemas/JobMetadataReplace" } } }, @@ -2410,8 +2408,7 @@ "content": { "application/json": { "schema": { - "title": "Response Get Study Job Custom Metadata V0 Studies Study Id Jobs Job Id Metadata Get", - "type": "object" + "$ref": "#/components/schemas/JobMetadata" } } } @@ -2428,13 +2425,13 @@ } } }, - "post": { + "put": { "tags": [ "studies" ], - "summary": "Create Study Job Custom Metadata", - "description": "Attaches custom metadata to a job", - "operationId": "create_study_job_custom_metadata", + "summary": "Replace Study Job Custom Metadata", + "description": "Changes job's custom metadata", + "operationId": "replace_study_job_custom_metadata", "parameters": [ { "required": true, @@ -2461,8 +2458,7 @@ "content": { "application/json": { "schema": { - "title": "Metadata", - "type": "object" + "$ref": "#/components/schemas/JobMetadataReplace" } } }, @@ -2715,6 +2711,79 @@ } } }, + "JobMetadata": { + "title": "JobMetadata", + "required": [ + "job_id", + "metadata", + "url" + ], + "type": "object", + "properties": { + "job_id": { + "title": "Job Id", + "type": "string", + "description": "Parent Job", + "format": "uuid" + }, + "metadata": { + "title": "Metadata", + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "integer" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "description": "Custom key-value map" + }, + "url": { + "title": "Url", + "maxLength": 2083, + "minLength": 1, + "type": "string", + "description": "Link to get this resource (self)", + "format": "uri" + } + } + }, + "JobMetadataReplace": { + "title": "JobMetadataReplace", + "type": "object", + "properties": { + "metadata": { + "title": "Metadata", + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "integer" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "description": "Custom key-value map" + } + } + }, "JobOutputs": { "title": "JobOutputs", "required": [ From 5fea52a5c7dff3c4cb6ad9a492d3be04fc0036c0 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Mon, 10 Jul 2023 13:35:22 +0200 Subject: [PATCH 04/27] minor fix --- clients/python/Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/clients/python/Makefile b/clients/python/Makefile index c0016f1b..bda7d2b1 100644 --- a/clients/python/Makefile +++ b/clients/python/Makefile @@ -115,8 +115,9 @@ dist: artifacts_dir ## builds distribution wheel dist-ci: python-client dist ## build wheel and tar ball in a single command .PHONY: build-n-install-osparc-dev -build-n-install-osparc-dev: dist-ci ## install the built osparc package in editable mode - python -m pip install -e client/ --find-links=artifacts/dist +build-n-install-osparc-dev: python-client ## install the built osparc package in editable mode + python -m pip install -e artifacts/client + python -m pip install -e client/ ## DOCKER ------------------------------------------------------------------------------- From 1229409d1691363896d93c2ec3e8cde45d5e1b11 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Mon, 10 Jul 2023 14:02:26 +0200 Subject: [PATCH 05/27] minor changes --- clients/python/Makefile | 8 +++++--- clients/python/client/osparc/__init__.py | 3 +-- clients/python/client/osparc/models.py | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/clients/python/Makefile b/clients/python/Makefile index bda7d2b1..dbcf126e 100644 --- a/clients/python/Makefile +++ b/clients/python/Makefile @@ -26,6 +26,8 @@ python-client: validate-api-specification artifacts_dir ## generate python clien --package-name=osparc_client \ --release-note="Updated to $(APP_VERSION)" black $(PYTHON_DIR)/artifacts/client/*.py + @-mkdir $(PYTHON_DIR)/client/osparc/data/ + @cp $(REPO_ROOT)/api/openapi.json $(PYTHON_DIR)/client/osparc/data/ .PHONY: python-client-from-templates python-client-from-templates: validate-api-specification artifacts_dir ## generate python client using templates in a specified directory (usage: 'make python-client-from-templates -e TEMPLATE_DIR=path/to/templates') @@ -47,6 +49,8 @@ python-client-from-templates: validate-api-specification artifacts_dir ## genera --release-note="Updated to $(APP_VERSION)" \ --template-dir=/tmp/python_templates black $(PYTHON_DIR)/artifacts/client/*.py + @-mkdir $(PYTHON_DIR)/client/osparc/data/ + @cp $(REPO_ROOT)/api/openapi.json $(PYTHON_DIR)/client/osparc/data/ .PHONY: generator-help generator-help: ## help on client-api generator @@ -94,7 +98,7 @@ pylint: _check_venv_active ## runs linter (only to check errors. SEE .pylintrc e .PHONY: test-dev test-dev: _check_venv_active ## runs tests during development # runs tests for development (e.g w/ pdb) - pytest -vv --exitfirst --failed-first --durations=10 --pdb --ignore=$(ARTIFACTS_DIR)/client $(PYTHON_DIR) + python -m pytest -vv --exitfirst --failed-first --durations=10 --pdb $(PYTHON_DIR)/test/test_osparc .PHONY: dist dist: artifacts_dir ## builds distribution wheel @@ -102,8 +106,6 @@ dist: artifacts_dir ## builds distribution wheel python -m pip install build # Build a binary wheel and a source tarball python -m build --sdist --wheel $(ARTIFACTS_DIR)/client - @-mkdir $(PYTHON_DIR)/client/osparc/data/ - @cp $(REPO_ROOT)/api/openapi.json $(PYTHON_DIR)/client/osparc/data/ python -m build --sdist --wheel $(PYTHON_DIR)/client @-rm -rf $(ARTIFACTS_DIR)/dist @mkdir $(ARTIFACTS_DIR)/dist diff --git a/clients/python/client/osparc/__init__.py b/clients/python/client/osparc/__init__.py index 0a231449..855009d8 100644 --- a/clients/python/client/osparc/__init__.py +++ b/clients/python/client/osparc/__init__.py @@ -2,6 +2,7 @@ 0.5.0 osparc client """ from ._info import openapi + from osparc_client import ( __version__, ApiClient, @@ -39,8 +40,6 @@ __all__ = [ # imports from osparc_client "__version__", - "api", - "models", "FilesApi", "MetaApi", "SolversApi", diff --git a/clients/python/client/osparc/models.py b/clients/python/client/osparc/models.py index 53fcd952..10dff6b7 100644 --- a/clients/python/client/osparc/models.py +++ b/clients/python/client/osparc/models.py @@ -22,11 +22,11 @@ Profile, ProfileUpdate, Solver, - TaskStates, UserRoleEnum, UsersGroup, ValidationError, ) +from osparc_client.models import RunningState as TaskStates __all__ = [ "BodyUploadFileV0FilesContentPut", From bc8ee1fa47d2a90f0bcced44682b1408629ad311 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Mon, 10 Jul 2023 14:07:33 +0200 Subject: [PATCH 06/27] import new names directly in osparc --- clients/python/client/osparc/__init__.py | 30 ++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/clients/python/client/osparc/__init__.py b/clients/python/client/osparc/__init__.py index 855009d8..7dfc25cf 100644 --- a/clients/python/client/osparc/__init__.py +++ b/clients/python/client/osparc/__init__.py @@ -33,6 +33,22 @@ MetaApi, SolversApi, UsersApi, + + + StudiesApi, + OnePageSolverPort, + StudyPort, + Study, + LimitOffsetPageStudy, + LimitOffsetPageFile, + JobMetadataReplace, + LimitOffsetPageJob, + Links, + SolverPort, + JobMetadata, + LimitOffsetPageSolver, + ErrorGet, + OnePageStudyPort, ) from osparc_client import RunningState as TaskStates @@ -67,6 +83,20 @@ "ApiValueError", "ApiKeyError", "ApiException", + "StudiesApi", + "OnePageSolverPort", + "StudyPort", + "Study", + "LimitOffsetPageStudy", + "LimitOffsetPageFile", + "JobMetadataReplace", + "LimitOffsetPageJob", + "Links", + "SolverPort", + "JobMetadata", + "LimitOffsetPageSolver", + "ErrorGet", + "OnePageStudyPort", # imports from osparc "openapi", ] From 19d30c18aed3a8d6c7e40cd029ef56137c61bf4e Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Tue, 18 Jul 2023 08:40:36 +0200 Subject: [PATCH 07/27] upgrade openapi.json to latest from master on osparc-simcore --- api/openapi.json | 52 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/api/openapi.json b/api/openapi.json index f7d8dad5..19a780a8 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -1609,7 +1609,7 @@ "solvers" ], "summary": "Get Job Custom Metadata", - "description": "Gets custom metadata from a job", + "description": "Gets custom metadata from a job\n\nNew in *version 0.5*", "operationId": "get_job_custom_metadata", "parameters": [ { @@ -1654,6 +1654,16 @@ } } }, + "404": { + "description": "Job not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, "422": { "description": "Validation Error", "content": { @@ -1664,14 +1674,19 @@ } } } - } + }, + "security": [ + { + "HTTPBasic": [] + } + ] }, - "put": { + "patch": { "tags": [ "solvers" ], "summary": "Replace Job Custom Metadata", - "description": "Changes job's custom metadata", + "description": "Updates custom metadata from a job\n\nNew in *version 0.5*", "operationId": "replace_job_custom_metadata", "parameters": [ { @@ -1709,7 +1724,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/JobMetadataReplace" + "$ref": "#/components/schemas/JobMetadataUpdate" } } }, @@ -1720,7 +1735,19 @@ "description": "Successful Response", "content": { "application/json": { - "schema": {} + "schema": { + "$ref": "#/components/schemas/JobMetadata" + } + } + } + }, + "404": { + "description": "Job not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } } } }, @@ -1734,7 +1761,12 @@ } } } - } + }, + "security": [ + { + "HTTPBasic": [] + } + ] } }, "/v0/studies/": { @@ -2458,7 +2490,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/JobMetadataReplace" + "$ref": "#/components/schemas/JobMetadataUpdate" } } }, @@ -2757,8 +2789,8 @@ } } }, - "JobMetadataReplace": { - "title": "JobMetadataReplace", + "JobMetadataUpdate": { + "title": "JobMetadataUpdate", "type": "object", "properties": { "metadata": { From 30879a3d664489b7f95481f0e27d2ca9e2197338 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Tue, 18 Jul 2023 08:58:13 +0200 Subject: [PATCH 08/27] fix import --- clients/python/client/osparc/__init__.py | 48 ++++++++++++------------ 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/clients/python/client/osparc/__init__.py b/clients/python/client/osparc/__init__.py index 7dfc25cf..873c7594 100644 --- a/clients/python/client/osparc/__init__.py +++ b/clients/python/client/osparc/__init__.py @@ -5,6 +5,15 @@ from osparc_client import ( __version__, + + # APIs + FilesApi, + MetaApi, + SolversApi, + StudiesApi, + UsersApi, + + # API client ApiClient, Configuration, OpenApiException, @@ -12,43 +21,36 @@ ApiValueError, ApiKeyError, ApiException, - # model imports + + # models BodyUploadFileV0FilesContentPut, + ErrorGet, File, Groups, HTTPValidationError, Job, JobInputs, + JobMetadata, + JobMetadataUpdate, JobOutputs, JobStatus, + LimitOffsetPageFile, + LimitOffsetPageJob, + LimitOffsetPageSolver, + LimitOffsetPageStudy, + Links, Meta, + OnePageSolverPort, + OnePageStudyPort, Profile, ProfileUpdate, Solver, + SolverPort, + Study, + StudyPort, UserRoleEnum, UsersGroup, ValidationError, - # api imports - FilesApi, - MetaApi, - SolversApi, - UsersApi, - - - StudiesApi, - OnePageSolverPort, - StudyPort, - Study, - LimitOffsetPageStudy, - LimitOffsetPageFile, - JobMetadataReplace, - LimitOffsetPageJob, - Links, - SolverPort, - JobMetadata, - LimitOffsetPageSolver, - ErrorGet, - OnePageStudyPort, ) from osparc_client import RunningState as TaskStates @@ -89,7 +91,7 @@ "Study", "LimitOffsetPageStudy", "LimitOffsetPageFile", - "JobMetadataReplace", + "JobMetadataUpdate", "LimitOffsetPageJob", "Links", "SolverPort", From 539f05d3d8afdbf9b56e394cd510fc724ec50ed6 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Jul 2023 08:08:37 +0200 Subject: [PATCH 09/27] bugfix in BasicTutorial and add StudyTutorial --- clients/python/docs/BasicTutorial.ipynb | 4 +- clients/python/docs/StudyTutorial.ipynb | 497 ++++++++++++++++++++++++ 2 files changed, 499 insertions(+), 2 deletions(-) create mode 100755 clients/python/docs/StudyTutorial.ipynb diff --git a/clients/python/docs/BasicTutorial.ipynb b/clients/python/docs/BasicTutorial.ipynb index 56e5afa5..0342dabc 100755 --- a/clients/python/docs/BasicTutorial.ipynb +++ b/clients/python/docs/BasicTutorial.ipynb @@ -28,8 +28,8 @@ }, "outputs": [], "source": [ - "import importlib\n", - "if importlib.util.find_spec('osparc') is not None:\n", + "import importlib.util\n", + "if importlib.util.find_spec('osparc') is None:\n", " ! pip install osparc\n", "! python -c \"import osparc; print(osparc.__version__)\"" ] diff --git a/clients/python/docs/StudyTutorial.ipynb b/clients/python/docs/StudyTutorial.ipynb new file mode 100755 index 00000000..1e04e631 --- /dev/null +++ b/clients/python/docs/StudyTutorial.ipynb @@ -0,0 +1,497 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "id": "f15de720", + "metadata": {}, + "source": [ + "# Study Tutorial\n", + "\n", + "\n", + "\n", + "## Installation and configuration\n", + "The installation and configuration process is discussed in detail in the `BasicTutorial`. Of course, if we don't already have `osparc` installed we need to do so and we need to setup the `osparc.Configuration`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "992916f5", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import importlib.util\n", + "if importlib.util.find_spec('osparc') is None:\n", + " ! pip install osparc\n", + "from osparc import Configuration\n", + "cfg: Configuration = Configuration(\n", + " host=os.environ[\"OSPARC_API_HOST\"],\n", + " username=os.environ[\"OSPARC_API_KEY\"],\n", + " password=os.environ[\"OSPARC_API_SECRET\"]\n", + ")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "630c5926", + "metadata": {}, + "source": [ + "The configuration can now be used to create an instance of the API client. The API client is responsible of the communication with the osparc platform\n", + "\n", + "\n", + "The functions in the [osparc API] are grouped into sections such as *meta*, *users*, *files* or *solvers*. Each section address a different resource of the platform.\n", + "\n", + "\n", + "\n", + "For example, the *users* section includes functions about the user (i.e. you) and can be accessed initializing a ``UsersApi``:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29337833", + "metadata": {}, + "outputs": [], + "source": [ + "from osparc import ApiClient, UsersApi\n", + "\n", + "with ApiClient(cfg) as api_client:\n", + "\n", + " users_api = UsersApi(api_client)\n", + "\n", + " profile = users_api.get_my_profile()\n", + " print(profile)\n", + "\n", + " #\n", + " # {'first_name': 'foo',\n", + " # 'gravatar_id': 'aa33fssec77ea434c2ea4fb92d0fd379e',\n", + " # 'groups': {'all': {'description': 'all users',\n", + " # 'gid': '1',\n", + " # 'label': 'Everyone'},\n", + " # 'me': {'description': 'primary group',\n", + " # 'gid': '2',\n", + " # 'label': 'foo'},\n", + " # 'organizations': []},\n", + " # 'last_name': '',\n", + " # 'login': 'foo@itis.swiss',\n", + " # 'role': 'USER'}\n", + " #" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "6912889e", + "metadata": {}, + "source": [ + "## Solvers Workflow\n", + "\n", + "The osparc API can be used to execute any computational service published in the platform. This means that any computational service listed in the UI under the [Discover Tab](http://docs.osparc.io/#/docs/platform_introduction/core_elements/Discover?id=discover-tab) is accessible from the API. Note that computational services are denoted as *solvers* in the API for convenience, but they refer to the same concept.\n", + "\n", + "\n", + "Let's use the sleepers computational service to illustrate a typical workflow. The sleepers computational service is a very basic service that simply waits (i.e. *sleeps*) a given time before producing some outputs. It takes as input one natural number, an optional text file input that contains another natural number and a boolean in the form of a checkbox. It also provides two outputs: one natural number and a file containing a single natural number." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "398bcd09", + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "from pathlib import Path\n", + "from zipfile import ZipFile\n", + "from tempfile import TemporaryDirectory\n", + "\n", + "import osparc\n", + "\n", + "CLIENT_VERSION = tuple(map(int, osparc.__version__.split(\".\")))\n", + "assert CLIENT_VERSION >= (0, 4, 3)\n", + "\n", + "Path(\"file_with_number.txt\").write_text(\"3\")\n", + "\n", + "with osparc.ApiClient(cfg) as api_client:\n", + "\n", + " files_api = osparc.FilesApi(api_client)\n", + " input_file: osparc.File = files_api.upload_file(file=\"file_with_number.txt\")\n", + "\n", + " solvers_api = osparc.SolversApi(api_client)\n", + " solver: osparc.Solver = solvers_api.get_solver_release(\n", + " \"simcore/services/comp/itis/sleeper\", \"2.0.2\"\n", + " )\n", + "\n", + " job: osparc.Job = solvers_api.create_job(\n", + " solver.id,\n", + " solver.version,\n", + " osparc.JobInputs(\n", + " {\n", + " \"input_3\": 0,\n", + " \"input_2\": 3.0,\n", + " \"input_1\": input_file,\n", + " }\n", + " ),\n", + " )\n", + "\n", + " status: osparc.JobStatus = solvers_api.start_job(solver.id, solver.version, job.id)\n", + " while not status.stopped_at:\n", + " time.sleep(3)\n", + " status = solvers_api.inspect_job(solver.id, solver.version, job.id)\n", + " print(\"Solver progress\", f\"{status.progress}/100\", flush=True)\n", + " #\n", + " # Solver progress 0/100\n", + " # Solver progress 100/100\n", + "\n", + " outputs: osparc.JobOutputs = solvers_api.get_job_outputs(solver.id, solver.version, job.id)\n", + "\n", + " print(f\"Job {outputs.job_id} got these results:\")\n", + " for output_name, result in outputs.results.items():\n", + " print(output_name, \"=\", result)\n", + "\n", + " #\n", + " # Job 19fc28f7-46fb-4e96-9129-5e924801f088 got these results:\n", + " #\n", + " # output_1 = {'checksum': '859fda0cb82fc4acb4686510a172d9a9-1',\n", + " # 'content_type': 'text/plain',\n", + " # 'filename': 'single_number.txt',\n", + " # 'id': '9fb4f70e-3589-3e9e-991e-3059086c3aae'}\n", + " # output_2 = 4.0\n", + "\n", + " if CLIENT_VERSION >= (0, 5, 0):\n", + " logfile_path: str = solvers_api.get_job_output_logfile(\n", + " solver.id, solver.version, job.id\n", + " )\n", + " zip_path = Path(logfile_path)\n", + "\n", + " with TemporaryDirectory() as tmp_dir:\n", + " with ZipFile(f\"{zip_path}\") as fzip:\n", + " fzip.extractall(tmp_dir)\n", + " logfiles = list(Path(tmp_dir).glob(\"*.log*\"))\n", + " print(\"Unzipped\", logfiles[0], \"contains:\\n\", logfiles[0].read_text())\n", + " #\n", + " # Unzipped extracted/sleeper_2.0.2.logs contains:\n", + " # 2022-06-01T18:15:00.405035847+02:00 Entrypoint for stage production ...\n", + " # 2022-06-01T18:15:00.421279969+02:00 User : uid=0(root) gid=0(root) groups=0(root)\n", + " # 2022-06-01T18:15:00.421560331+02:00 Workdir : /home/scu\n", + " # ...\n", + " # 2022-06-01T18:15:00.864550043+02:00 \n", + " # 2022-06-01T18:15:03.923876794+02:00 Will sleep for 3 seconds\n", + " # 2022-06-01T18:15:03.924473521+02:00 [PROGRESS] 1/3...\n", + " # 2022-06-01T18:15:03.925021846+02:00 Remaining sleep time 0.9999995231628418\n", + " # 2022-06-01T18:15:03.925558026+02:00 [PROGRESS] 2/3...\n", + " # 2022-06-01T18:15:03.926103062+02:00 Remaining sleep time 0.9999985694885254\n", + " # 2022-06-01T18:15:03.926643184+02:00 [PROGRESS] 3/3...\n", + " # 2022-06-01T18:15:03.933544384+02:00 Remaining sleep time 0.9999983310699463\n", + "\n", + " download_path: str = files_api.download_file(file_id=outputs.results[\"output_1\"].id)\n", + " print(Path(download_path).read_text())\n", + " #\n", + " # 7\n" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "c0092d84", + "metadata": {}, + "source": [ + "The script above\n", + "\n", + "1. Uploads a file ``file_with_number.txt``\n", + "2. Selects version ``2.0.2`` of the ``sleeper``\n", + "3. Runs the ``sleeper`` and provides a reference to the uploaded file and other values as input parameters\n", + "4. Monitors the status of the solver while it is running in the platform\n", + "5. When the execution completes, it checks the outputs\n", + "6. The logs are downloaded, unzipped and saved to a new ```extracted``` directory\n", + "7. One of the outputs is a file and it is downloaded\n", + "\n", + "\n", + "#### Files\n", + "\n", + "Files used as input to solvers or produced by solvers in the platform are accessible in the **files** section and specifically with the ``FilesApi`` class.\n", + "In order to use a file as input, it has to be uploaded first and the reference used in the corresponding solver's input." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "d6d2dfdb", + "metadata": {}, + "source": [ + "```python\n", + "files_api = FilesApi(api_client)\n", + "input_file: File = files_api.upload_file(file=\"file_with_number.txt\")\n", + "\n", + "\n", + "# ...\n", + "\n", + "\n", + "outputs: JobOutputs = solvers_api.get_job_outputs(solver.id, solver.version, job.id)\n", + "results_file: File = outputs.results[\"output_1\"]\n", + "download_path: str = files_api.download_file(file_id=results_file.id)\n", + "```" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "e40ad2a5", + "metadata": {}, + "source": [ + "In the snippet above, ``input_file`` is a ``File`` reference to the uploaded file and that is passed as input to the solver. Analogously, ``results_file`` is a ``File`` produced by the solver and that can also be downloaded.\n", + "\n", + "\n", + "#### Solvers, Inputs and Outputs\n", + "\n", + "The inputs and outputs are specific for every solver. Every input/output has a name and an associated type that can be as simple as booleans, numbers, strings ... or more complex as files. You can find this information in the UI under Discover Tab, selecting the service card > More Info > raw metadata. For instance, the ``sleeper`` version ``2.0.2`` has the following ``raw-metadata``:" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "f5262250", + "metadata": { + "attributes": { + "classes": [ + "json" + ], + "id": "" + } + }, + "source": [ + "```json\n", + "{\n", + " inputs: {\n", + " 'input_1': {'description': 'Pick a file containing only one '\n", + " 'integer',\n", + " 'displayOrder': 1,\n", + " 'fileToKeyMap': {'single_number.txt': 'input_1'},\n", + " 'label': 'File with int number',\n", + " 'type': 'data:text/plain'},\n", + " 'input_2': {'defaultValue': 2,\n", + " 'description': 'Choose an amount of time to sleep',\n", + " 'displayOrder': 2,\n", + " 'label': 'Sleep interval',\n", + " 'type': 'integer',\n", + " 'unit': 'second'},\n", + " 'input_3': {'defaultValue': False,\n", + " 'description': 'If set to true will cause service to '\n", + " 'fail after it sleeps',\n", + " 'displayOrder': 3,\n", + " 'label': 'Fail after sleep',\n", + " 'type': 'boolean'},\n", + " }\n", + "}\n", + "```" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "1584bf07", + "metadata": {}, + "source": [ + "So, the inputs can be set as follows" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "5337fcae", + "metadata": {}, + "source": [ + "```python\n", + "# ...\n", + "job = solvers_api.create_job(\n", + " solver.id,\n", + " solver.version,\n", + " job_inputs=JobInputs(\n", + " {\n", + " \"input_1\": uploaded_input_file,\n", + " \"input_2\": 3 * n, # sleep time in secs\n", + " \"input_3\": bool(n % 2), # fail after sleep?\n", + " }\n", + " ),\n", + " )\n", + "```" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "8dfa35ad", + "metadata": {}, + "source": [ + "And the metadata for the outputs are" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "d4222e74", + "metadata": { + "attributes": { + "classes": [ + "json" + ], + "id": "" + } + }, + "source": [ + "```json\n", + "{\n", + " 'outputs': {'output_1': {'description': 'Integer is generated in range [1-9]',\n", + " 'displayOrder': 1,\n", + " 'fileToKeyMap': {'single_number.txt': 'output_1'},\n", + " 'label': 'File containing one random integer',\n", + " 'type': 'data:text/plain'},\n", + " 'output_2': {'description': 'Interval is generated in range '\n", + " '[1-9]',\n", + " 'displayOrder': 2,\n", + " 'label': 'Random sleep interval',\n", + " 'type': 'integer',\n", + " 'unit': 'second'}},\n", + "}\n", + "```" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "97e73630", + "metadata": {}, + "source": [ + "so this information determines which output corresponds to a number or a file in the following snippet" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "36e8f03b", + "metadata": {}, + "source": [ + "```python\n", + "# ...\n", + "\n", + "outputs: JobOutputs = solvers_api.get_job_outputs(solver.id, solver.version, job.id)\n", + "\n", + "output_file = outputs.results[\"output_1\"]\n", + "number = outputs.results[\"output_2\"]\n", + "\n", + "assert status.state == \"SUCCESS\"\n", + "\n", + "\n", + "assert isinstance(output_file, File)\n", + "assert isinstance(number, float)\n", + "\n", + "# output file exists\n", + "assert files_api.get_file(output_file.id) == output_file\n", + "\n", + "# can download and open\n", + "download_path: str = files_api.download_file(file_id=output_file.id)\n", + "assert float(Path(download_path).read_text()), \"contains a random number\"\n", + "\n", + "```" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "a58035b0", + "metadata": {}, + "source": [ + "#### Job Status\n", + "\n", + "Once the client script triggers the solver, the solver runs in the platform and the script is freed. Sometimes, it is convenient to monitor the status of the run to see e.g. the progress of the execution or if the run was completed.\n", + "\n", + "A solver runs in a plaforma starts a ``Job``. Using the ``solvers_api``, allows us to inspect the ``Job`` and get a ``JobStatus`` with information about its status. For instance" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "93817d1e", + "metadata": {}, + "source": [ + "```python \n", + " status: JobStatus = solvers_api.start_job(solver.id, solver.version, job.id)\n", + " while not status.stopped_at:\n", + " time.sleep(3)\n", + " status = solvers_api.inspect_job(solver.id, solver.version, job.id)\n", + " print(\"Solver progress\", f\"{status.progress}/100\", flush=True)\n", + "``` " + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "5180b589", + "metadata": {}, + "source": [ + "#### Logs\n", + "\n", + "When a solver runs, it will generate logs during execution which are then saved as .log files. Starting from the osparc Python Client version 0.5.0, The ``solvers_api`` also allows us to obtain the ``logfile_path`` associated with a particular ``Job``. This is a zip file that can then be extracted and saved. For instance" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "417d4663", + "metadata": {}, + "source": [ + "```python\n", + "logfile_path: str = solvers_api.get_job_output_logfile(\n", + " solver.id, solver.version, job.id\n", + ")\n", + "zip_path = Path(logfile_path)\n", + "\n", + "extract_dir = Path(\"./extracted\")\n", + "extract_dir.mkdir()\n", + "\n", + "with ZipFile(f\"{zip_path}\") as fzip:\n", + " fzip.extractall(f\"{extract_dir}\")\n", + "```" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "72d60050", + "metadata": {}, + "source": [ + "## References\n", + "\n", + "- [osparc API python client] documentation\n", + "- [osparc API] documentation\n", + "- A full script with this tutorial: [``sleeper.py``](https://github.com/ITISFoundation/osparc-simcore/blob/master/tests/public-api/examples/sleeper.py)\n", + "\n", + "[osparc API python client]:https://itisfoundation.github.io/osparc-simcore-python-client\n", + "[osparc API]:https://api.osparc.io/doc" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "", + "language": "python", + "name": "" + }, + "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.9.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From adfa75b48942087b4f4ba28e5a3f75aa5f953598 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Jul 2023 09:28:09 +0200 Subject: [PATCH 10/27] finish proto version of study tutorial --- clients/python/docs/BasicTutorial.ipynb | 28 +- clients/python/docs/StudyTutorial.ipynb | 400 ++---------------------- 2 files changed, 41 insertions(+), 387 deletions(-) diff --git a/clients/python/docs/BasicTutorial.ipynb b/clients/python/docs/BasicTutorial.ipynb index 0342dabc..b3d337be 100755 --- a/clients/python/docs/BasicTutorial.ipynb +++ b/clients/python/docs/BasicTutorial.ipynb @@ -31,7 +31,11 @@ "import importlib.util\n", "if importlib.util.find_spec('osparc') is None:\n", " ! pip install osparc\n", - "! python -c \"import osparc; print(osparc.__version__)\"" + "! python -c \"import osparc; print(osparc.__version__)\"\n", + "\n", + "# this tutorial is compatible with version >= 0.5.0\n", + "CLIENT_VERSION = tuple(map(int, osparc.__version__.split(\".\")))\n", + "assert tuple(map(int, osparc.__version__.split(\".\"))) >= (0, 5, 0)" ] }, { @@ -140,9 +144,6 @@ "\n", "import osparc\n", "\n", - "CLIENT_VERSION = tuple(map(int, osparc.__version__.split(\".\")))\n", - "assert CLIENT_VERSION >= (0, 4, 3)\n", - "\n", "Path(\"file_with_number.txt\").write_text(\"3\")\n", "\n", "with osparc.ApiClient(cfg) as api_client:\n", @@ -191,17 +192,16 @@ " # 'id': '9fb4f70e-3589-3e9e-991e-3059086c3aae'}\n", " # output_2 = 4.0\n", "\n", - " if CLIENT_VERSION >= (0, 5, 0):\n", - " logfile_path: str = solvers_api.get_job_output_logfile(\n", - " solver.id, solver.version, job.id\n", - " )\n", - " zip_path = Path(logfile_path)\n", + " logfile_path: str = solvers_api.get_job_output_logfile(\n", + " solver.id, solver.version, job.id\n", + " )\n", + " zip_path = Path(logfile_path)\n", "\n", - " with TemporaryDirectory() as tmp_dir:\n", - " with ZipFile(f\"{zip_path}\") as fzip:\n", - " fzip.extractall(tmp_dir)\n", - " logfiles = list(Path(tmp_dir).glob(\"*.log*\"))\n", - " print(\"Unzipped\", logfiles[0], \"contains:\\n\", logfiles[0].read_text())\n", + " with TemporaryDirectory() as tmp_dir:\n", + " with ZipFile(f\"{zip_path}\") as fzip:\n", + " fzip.extractall(tmp_dir)\n", + " logfiles = list(Path(tmp_dir).glob(\"*.log*\"))\n", + " print(\"Unzipped\", logfiles[0], \"contains:\\n\", logfiles[0].read_text())\n", " #\n", " # Unzipped extracted/sleeper_2.0.2.logs contains:\n", " # 2022-06-01T18:15:00.405035847+02:00 Entrypoint for stage production ...\n", diff --git a/clients/python/docs/StudyTutorial.ipynb b/clients/python/docs/StudyTutorial.ipynb index 1e04e631..e518fce8 100755 --- a/clients/python/docs/StudyTutorial.ipynb +++ b/clients/python/docs/StudyTutorial.ipynb @@ -30,55 +30,10 @@ " host=os.environ[\"OSPARC_API_HOST\"],\n", " username=os.environ[\"OSPARC_API_KEY\"],\n", " password=os.environ[\"OSPARC_API_SECRET\"]\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "630c5926", - "metadata": {}, - "source": [ - "The configuration can now be used to create an instance of the API client. The API client is responsible of the communication with the osparc platform\n", - "\n", - "\n", - "The functions in the [osparc API] are grouped into sections such as *meta*, *users*, *files* or *solvers*. Each section address a different resource of the platform.\n", - "\n", - "\n", - "\n", - "For example, the *users* section includes functions about the user (i.e. you) and can be accessed initializing a ``UsersApi``:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "29337833", - "metadata": {}, - "outputs": [], - "source": [ - "from osparc import ApiClient, UsersApi\n", - "\n", - "with ApiClient(cfg) as api_client:\n", - "\n", - " users_api = UsersApi(api_client)\n", - "\n", - " profile = users_api.get_my_profile()\n", - " print(profile)\n", + ")\n", "\n", - " #\n", - " # {'first_name': 'foo',\n", - " # 'gravatar_id': 'aa33fssec77ea434c2ea4fb92d0fd379e',\n", - " # 'groups': {'all': {'description': 'all users',\n", - " # 'gid': '1',\n", - " # 'label': 'Everyone'},\n", - " # 'me': {'description': 'primary group',\n", - " # 'gid': '2',\n", - " # 'label': 'foo'},\n", - " # 'organizations': []},\n", - " # 'last_name': '',\n", - " # 'login': 'foo@itis.swiss',\n", - " # 'role': 'USER'}\n", - " #" + "# this tutorial is compatible with version > 0.5.0\n", + "assert tuple(map(int, osparc.__version__.split(\".\"))) > (0, 5, 0)" ] }, { @@ -87,12 +42,9 @@ "id": "6912889e", "metadata": {}, "source": [ - "## Solvers Workflow\n", - "\n", - "The osparc API can be used to execute any computational service published in the platform. This means that any computational service listed in the UI under the [Discover Tab](http://docs.osparc.io/#/docs/platform_introduction/core_elements/Discover?id=discover-tab) is accessible from the API. Note that computational services are denoted as *solvers* in the API for convenience, but they refer to the same concept.\n", + "## Study Workflow\n", "\n", - "\n", - "Let's use the sleepers computational service to illustrate a typical workflow. The sleepers computational service is a very basic service that simply waits (i.e. *sleeps*) a given time before producing some outputs. It takes as input one natural number, an optional text file input that contains another natural number and a boolean in the form of a checkbox. It also provides two outputs: one natural number and a file containing a single natural number." + "Studies can connect computational resources as well as pre- and postprocess data. Studies mmust be created through the UI (where you generated the `OSPARC_API_KEY` and the `OSPARC_API_SECRET`). Once a study has been created via the UI one can interact with it (modify, run, read and write data to it etc.) using the `osparc` python package. Here we will demonstrate the typical workflow. As an illustration we consider a simple study containing a single sleeper solver.\n" ] }, { @@ -109,87 +61,40 @@ "\n", "import osparc\n", "\n", - "CLIENT_VERSION = tuple(map(int, osparc.__version__.split(\".\")))\n", - "assert CLIENT_VERSION >= (0, 4, 3)\n", - "\n", - "Path(\"file_with_number.txt\").write_text(\"3\")\n", + "# Get the ID of a study under the study information\n", + "SLEEPER_STUDY_ID: str = \"fcad50ac-253e-11ee-9414-02420a0f071c\"\n", "\n", "with osparc.ApiClient(cfg) as api_client:\n", "\n", - " files_api = osparc.FilesApi(api_client)\n", - " input_file: osparc.File = files_api.upload_file(file=\"file_with_number.txt\")\n", + " studies_api: osparc.StudiesApi = osparc.StudiesApi(api_client)\n", + " study: osparc.Study = studies_api.get_study(SLEEPER_STUDY_ID)\n", "\n", - " solvers_api = osparc.SolversApi(api_client)\n", - " solver: osparc.Solver = solvers_api.get_solver_release(\n", - " \"simcore/services/comp/itis/sleeper\", \"2.0.2\"\n", - " )\n", "\n", - " job: osparc.Job = solvers_api.create_job(\n", - " solver.id,\n", - " solver.version,\n", - " osparc.JobInputs(\n", - " {\n", - " \"input_3\": 0,\n", - " \"input_2\": 3.0,\n", - " \"input_1\": input_file,\n", - " }\n", - " ),\n", - " )\n", + " job: osparc.Job = studies_api.create_study_job(study_id=study.id, \n", + " job_inputs=osparc.JobInputs({\n", + " \"X\": 1\n", + " }))\n", "\n", - " status: osparc.JobStatus = solvers_api.start_job(solver.id, solver.version, job.id)\n", + " status: osparc.JobStatus = studies_api.start_study_job(study.id, job_id=job.id)\n", " while not status.stopped_at:\n", " time.sleep(3)\n", - " status = solvers_api.inspect_job(solver.id, solver.version, job.id)\n", + " status = studies_api.inspect_study_job(study.id, job.id)\n", " print(\"Solver progress\", f\"{status.progress}/100\", flush=True)\n", - " #\n", - " # Solver progress 0/100\n", - " # Solver progress 100/100\n", "\n", - " outputs: osparc.JobOutputs = solvers_api.get_job_outputs(solver.id, solver.version, job.id)\n", + " outputs: osparc.JobOutputs = studies_api.get_study_job_outputs(study_id=study.id, job_id=job.id)\n", "\n", " print(f\"Job {outputs.job_id} got these results:\")\n", " for output_name, result in outputs.results.items():\n", " print(output_name, \"=\", result)\n", "\n", - " #\n", - " # Job 19fc28f7-46fb-4e96-9129-5e924801f088 got these results:\n", - " #\n", - " # output_1 = {'checksum': '859fda0cb82fc4acb4686510a172d9a9-1',\n", - " # 'content_type': 'text/plain',\n", - " # 'filename': 'single_number.txt',\n", - " # 'id': '9fb4f70e-3589-3e9e-991e-3059086c3aae'}\n", - " # output_2 = 4.0\n", + " logfile_path: str = studies_api.get_study_job_output_logfile(study.id, job.id)\n", + " zip_path = Path(logfile_path)\n", "\n", - " if CLIENT_VERSION >= (0, 5, 0):\n", - " logfile_path: str = solvers_api.get_job_output_logfile(\n", - " solver.id, solver.version, job.id\n", - " )\n", - " zip_path = Path(logfile_path)\n", - "\n", - " with TemporaryDirectory() as tmp_dir:\n", - " with ZipFile(f\"{zip_path}\") as fzip:\n", - " fzip.extractall(tmp_dir)\n", - " logfiles = list(Path(tmp_dir).glob(\"*.log*\"))\n", - " print(\"Unzipped\", logfiles[0], \"contains:\\n\", logfiles[0].read_text())\n", - " #\n", - " # Unzipped extracted/sleeper_2.0.2.logs contains:\n", - " # 2022-06-01T18:15:00.405035847+02:00 Entrypoint for stage production ...\n", - " # 2022-06-01T18:15:00.421279969+02:00 User : uid=0(root) gid=0(root) groups=0(root)\n", - " # 2022-06-01T18:15:00.421560331+02:00 Workdir : /home/scu\n", - " # ...\n", - " # 2022-06-01T18:15:00.864550043+02:00 \n", - " # 2022-06-01T18:15:03.923876794+02:00 Will sleep for 3 seconds\n", - " # 2022-06-01T18:15:03.924473521+02:00 [PROGRESS] 1/3...\n", - " # 2022-06-01T18:15:03.925021846+02:00 Remaining sleep time 0.9999995231628418\n", - " # 2022-06-01T18:15:03.925558026+02:00 [PROGRESS] 2/3...\n", - " # 2022-06-01T18:15:03.926103062+02:00 Remaining sleep time 0.9999985694885254\n", - " # 2022-06-01T18:15:03.926643184+02:00 [PROGRESS] 3/3...\n", - " # 2022-06-01T18:15:03.933544384+02:00 Remaining sleep time 0.9999983310699463\n", - "\n", - " download_path: str = files_api.download_file(file_id=outputs.results[\"output_1\"].id)\n", - " print(Path(download_path).read_text())\n", - " #\n", - " # 7\n" + " with TemporaryDirectory() as tmp_dir:\n", + " with ZipFile(f\"{zip_path}\") as fzip:\n", + " fzip.extractall(tmp_dir)\n", + " logfiles = list(Path(tmp_dir).glob(\"*.log*\"))\n", + " print(\"Unzipped\", logfiles[0], \"contains:\\n\", logfiles[0].read_text())\n" ] }, { @@ -200,260 +105,10 @@ "source": [ "The script above\n", "\n", - "1. Uploads a file ``file_with_number.txt``\n", - "2. Selects version ``2.0.2`` of the ``sleeper``\n", - "3. Runs the ``sleeper`` and provides a reference to the uploaded file and other values as input parameters\n", - "4. Monitors the status of the solver while it is running in the platform\n", - "5. When the execution completes, it checks the outputs\n", - "6. The logs are downloaded, unzipped and saved to a new ```extracted``` directory\n", - "7. One of the outputs is a file and it is downloaded\n", - "\n", - "\n", - "#### Files\n", - "\n", - "Files used as input to solvers or produced by solvers in the platform are accessible in the **files** section and specifically with the ``FilesApi`` class.\n", - "In order to use a file as input, it has to be uploaded first and the reference used in the corresponding solver's input." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "d6d2dfdb", - "metadata": {}, - "source": [ - "```python\n", - "files_api = FilesApi(api_client)\n", - "input_file: File = files_api.upload_file(file=\"file_with_number.txt\")\n", - "\n", - "\n", - "# ...\n", - "\n", - "\n", - "outputs: JobOutputs = solvers_api.get_job_outputs(solver.id, solver.version, job.id)\n", - "results_file: File = outputs.results[\"output_1\"]\n", - "download_path: str = files_api.download_file(file_id=results_file.id)\n", - "```" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "e40ad2a5", - "metadata": {}, - "source": [ - "In the snippet above, ``input_file`` is a ``File`` reference to the uploaded file and that is passed as input to the solver. Analogously, ``results_file`` is a ``File`` produced by the solver and that can also be downloaded.\n", - "\n", - "\n", - "#### Solvers, Inputs and Outputs\n", - "\n", - "The inputs and outputs are specific for every solver. Every input/output has a name and an associated type that can be as simple as booleans, numbers, strings ... or more complex as files. You can find this information in the UI under Discover Tab, selecting the service card > More Info > raw metadata. For instance, the ``sleeper`` version ``2.0.2`` has the following ``raw-metadata``:" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "f5262250", - "metadata": { - "attributes": { - "classes": [ - "json" - ], - "id": "" - } - }, - "source": [ - "```json\n", - "{\n", - " inputs: {\n", - " 'input_1': {'description': 'Pick a file containing only one '\n", - " 'integer',\n", - " 'displayOrder': 1,\n", - " 'fileToKeyMap': {'single_number.txt': 'input_1'},\n", - " 'label': 'File with int number',\n", - " 'type': 'data:text/plain'},\n", - " 'input_2': {'defaultValue': 2,\n", - " 'description': 'Choose an amount of time to sleep',\n", - " 'displayOrder': 2,\n", - " 'label': 'Sleep interval',\n", - " 'type': 'integer',\n", - " 'unit': 'second'},\n", - " 'input_3': {'defaultValue': False,\n", - " 'description': 'If set to true will cause service to '\n", - " 'fail after it sleeps',\n", - " 'displayOrder': 3,\n", - " 'label': 'Fail after sleep',\n", - " 'type': 'boolean'},\n", - " }\n", - "}\n", - "```" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "1584bf07", - "metadata": {}, - "source": [ - "So, the inputs can be set as follows" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "5337fcae", - "metadata": {}, - "source": [ - "```python\n", - "# ...\n", - "job = solvers_api.create_job(\n", - " solver.id,\n", - " solver.version,\n", - " job_inputs=JobInputs(\n", - " {\n", - " \"input_1\": uploaded_input_file,\n", - " \"input_2\": 3 * n, # sleep time in secs\n", - " \"input_3\": bool(n % 2), # fail after sleep?\n", - " }\n", - " ),\n", - " )\n", - "```" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "8dfa35ad", - "metadata": {}, - "source": [ - "And the metadata for the outputs are" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "d4222e74", - "metadata": { - "attributes": { - "classes": [ - "json" - ], - "id": "" - } - }, - "source": [ - "```json\n", - "{\n", - " 'outputs': {'output_1': {'description': 'Integer is generated in range [1-9]',\n", - " 'displayOrder': 1,\n", - " 'fileToKeyMap': {'single_number.txt': 'output_1'},\n", - " 'label': 'File containing one random integer',\n", - " 'type': 'data:text/plain'},\n", - " 'output_2': {'description': 'Interval is generated in range '\n", - " '[1-9]',\n", - " 'displayOrder': 2,\n", - " 'label': 'Random sleep interval',\n", - " 'type': 'integer',\n", - " 'unit': 'second'}},\n", - "}\n", - "```" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "97e73630", - "metadata": {}, - "source": [ - "so this information determines which output corresponds to a number or a file in the following snippet" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "36e8f03b", - "metadata": {}, - "source": [ - "```python\n", - "# ...\n", - "\n", - "outputs: JobOutputs = solvers_api.get_job_outputs(solver.id, solver.version, job.id)\n", - "\n", - "output_file = outputs.results[\"output_1\"]\n", - "number = outputs.results[\"output_2\"]\n", - "\n", - "assert status.state == \"SUCCESS\"\n", - "\n", - "\n", - "assert isinstance(output_file, File)\n", - "assert isinstance(number, float)\n", - "\n", - "# output file exists\n", - "assert files_api.get_file(output_file.id) == output_file\n", - "\n", - "# can download and open\n", - "download_path: str = files_api.download_file(file_id=output_file.id)\n", - "assert float(Path(download_path).read_text()), \"contains a random number\"\n", - "\n", - "```" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "a58035b0", - "metadata": {}, - "source": [ - "#### Job Status\n", - "\n", - "Once the client script triggers the solver, the solver runs in the platform and the script is freed. Sometimes, it is convenient to monitor the status of the run to see e.g. the progress of the execution or if the run was completed.\n", - "\n", - "A solver runs in a plaforma starts a ``Job``. Using the ``solvers_api``, allows us to inspect the ``Job`` and get a ``JobStatus`` with information about its status. For instance" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "93817d1e", - "metadata": {}, - "source": [ - "```python \n", - " status: JobStatus = solvers_api.start_job(solver.id, solver.version, job.id)\n", - " while not status.stopped_at:\n", - " time.sleep(3)\n", - " status = solvers_api.inspect_job(solver.id, solver.version, job.id)\n", - " print(\"Solver progress\", f\"{status.progress}/100\", flush=True)\n", - "``` " - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "5180b589", - "metadata": {}, - "source": [ - "#### Logs\n", - "\n", - "When a solver runs, it will generate logs during execution which are then saved as .log files. Starting from the osparc Python Client version 0.5.0, The ``solvers_api`` also allows us to obtain the ``logfile_path`` associated with a particular ``Job``. This is a zip file that can then be extracted and saved. For instance" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "417d4663", - "metadata": {}, - "source": [ - "```python\n", - "logfile_path: str = solvers_api.get_job_output_logfile(\n", - " solver.id, solver.version, job.id\n", - ")\n", - "zip_path = Path(logfile_path)\n", - "\n", - "extract_dir = Path(\"./extracted\")\n", - "extract_dir.mkdir()\n", - "\n", - "with ZipFile(f\"{zip_path}\") as fzip:\n", - " fzip.extractall(f\"{extract_dir}\")\n", - "```" + "1. Gets the sleeper study\n", + "2. Submits a job to the sleeper study (asking it to sleep for `X=1` second)\n", + "3. When the execution completes, it checks the outputs\n", + "4. The logs are downloaded and extracted\n" ] }, { @@ -466,7 +121,6 @@ "\n", "- [osparc API python client] documentation\n", "- [osparc API] documentation\n", - "- A full script with this tutorial: [``sleeper.py``](https://github.com/ITISFoundation/osparc-simcore/blob/master/tests/public-api/examples/sleeper.py)\n", "\n", "[osparc API python client]:https://itisfoundation.github.io/osparc-simcore-python-client\n", "[osparc API]:https://api.osparc.io/doc" From cc742815844047c1bafcc28f9241009f39c08629 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Jul 2023 09:49:58 +0200 Subject: [PATCH 11/27] increment version - to be changed --- api/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/config.json b/api/config.json index 16f5253c..82e61fc2 100644 --- a/api/config.json +++ b/api/config.json @@ -2,6 +2,6 @@ "homepage": "https://itisfoundation.github.io/osparc-simcore-clients/", "python": { - "version": "0.5.0" + "version": "0.6.0" } } From 0863b6988fed6031ed2951523f78d996e0f4afbe Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Jul 2023 10:56:21 +0200 Subject: [PATCH 12/27] configure tutorial tests to only run with compatible oosparc version --- clients/python/test/e2e/test_notebooks.py | 110 ++++++++++++++++++---- 1 file changed, 90 insertions(+), 20 deletions(-) diff --git a/clients/python/test/e2e/test_notebooks.py b/clients/python/test/e2e/test_notebooks.py index 8d9af796..b6a9ad8a 100644 --- a/clients/python/test/e2e/test_notebooks.py +++ b/clients/python/test/e2e/test_notebooks.py @@ -1,36 +1,35 @@ +import json import shutil import sys from pathlib import Path from tempfile import TemporaryDirectory -from typing import Any, List +from typing import Any, Dict, List, Optional, Set import osparc import papermill as pm import pytest -docs_dir: Path = Path(__file__).parent.parent.parent / "docs" -all_notebooks: List[Path] = list(docs_dir.glob("*.ipynb")) +# utilities -------------------------------------------------------------------------- +DOCS_DIR: Path = Path(__file__).parent.parent.parent / "docs" +DATA_DIR: Path = Path(__file__).parent / "data" +TUTORIAL_CLIENT_COMPATIBILITY_JSON: Path = ( + DATA_DIR / "tutorial_client_compatibility.json" +) + +assert DOCS_DIR.is_dir() +assert DATA_DIR.is_dir() +assert TUTORIAL_CLIENT_COMPATIBILITY_JSON.is_file() -def test_notebook_config(tmp_path: Path): - """Checks the jupyter environment is configured correctly""" - config_test_nb: Path = Path(__file__).parent / "data" / "config_test.ipynb" - assert config_test_nb.is_file() - test_run_notebooks( - tmp_path, - config_test_nb, - { - "expected_python_bin": sys.executable, - "expected_osparc_version": str(osparc.__version__), - "expected_osparc_file": osparc.__file__, - }, - ) - assert len(all_notebooks) > 0, f"Did not find any notebooks in {docs_dir}" +def run_notebook(tmp_path: Path, notebook: Path, params: dict[str, Any] = {}): + """Run a jupyter notebook using papermill -@pytest.mark.parametrize("notebook", all_notebooks) -def test_run_notebooks(tmp_path: Path, notebook: Path, params: dict[str, Any] = {}): - """Run all notebooks in the documentation""" + Args: + tmp_path (Path): temporary directory + notebook (Path): path to notebook to run + params (dict[str, Any], optional): parameters to pass to notebook. Defaults to {}. + """ print(f"Running {notebook.name} with parameters {params}") assert ( notebook.is_file() @@ -45,3 +44,74 @@ def test_run_notebooks(tmp_path: Path, notebook: Path, params: dict[str, Any] = kernel_name="python3", parameters=params, ) + + +def get_tutorials(osparc_version: Optional[str] = None) -> List[Path]: + """Returns the tutorial notebooks compatible with a given osparc client version + + Args: + osparc_version (str): osparc.__version__ + + Returns: + List[Path]: A list of *Path*s to the tutorial notebooks + """ + compatibility_dict: Dict[str, Any] = json.loads( + TUTORIAL_CLIENT_COMPATIBILITY_JSON.read_text() + ) + tutorial_names: List[str] = [] + if osparc_version is not None: + assert ( + osparc_version in compatibility_dict["versions"] + ), f"{osparc_version} does not exist in {TUTORIAL_CLIENT_COMPATIBILITY_JSON}" + tutorial_names = compatibility_dict["versions"][osparc_version] + else: + for v in compatibility_dict["versions"]: + tutorial_names += compatibility_dict["versions"][v] + result: List[Path] = [] + for name in tutorial_names: + result += list(DOCS_DIR.rglob(f"*{name}")) + return result + + +# Tests ------------------------------------------------------------------------------- + + +def test_notebook_config(tmp_path: Path): + """Test configuration of test setup. + Make sanity checks (ensure all files are discovered, correct installations are on path etc) + + Args: + tmp_path (Path): Temporary path pytest fixture + """ + # sanity check configuration of jupyter environment + config_test_nb: Path = DATA_DIR / "config_test.ipynb" + assert config_test_nb.is_file() + run_notebook( + tmp_path, + config_test_nb, + { + "expected_python_bin": sys.executable, + "expected_osparc_version": str(osparc.__version__), + "expected_osparc_file": osparc.__file__, + }, + ) + # sanity check paths and jsons: are we collecting all notebooks? + tutorials: Set[Path] = set(DOCS_DIR.glob("*.ipynb")) + json_notebooks: Set[Path] = set(get_tutorials()) + assert len(tutorials) > 0, f"Did not find any tutorial notebooks in {DOCS_DIR}" + assert ( + len(tutorials.intersection(json_notebooks)) == 0 + ), f"Some tutorial notebooks are not present in {TUTORIAL_CLIENT_COMPATIBILITY_JSON}" + + +@pytest.mark.parametrize("tutorials", get_tutorials(osparc_version=osparc.__version__)) +def test_run_tutorials(tmp_path: Path, tutorials: List[Path]): + """Run all tutorials compatible with the installed version of osparc + + Args: + tmp_path (Path): pytest tmp_path fixture + tutorials (List[Path]): list of tutorials + """ + for pth in tutorials: + print(f"Running {pth}") + run_notebook(tmp_path, pth) From 25de66c252b6edae0841681f4c929842ad71738a Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Jul 2023 10:56:49 +0200 Subject: [PATCH 13/27] cosmetic change --- clients/python/test/e2e/test_notebooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/python/test/e2e/test_notebooks.py b/clients/python/test/e2e/test_notebooks.py index b6a9ad8a..038609ff 100644 --- a/clients/python/test/e2e/test_notebooks.py +++ b/clients/python/test/e2e/test_notebooks.py @@ -113,5 +113,5 @@ def test_run_tutorials(tmp_path: Path, tutorials: List[Path]): tutorials (List[Path]): list of tutorials """ for pth in tutorials: - print(f"Running {pth}") + print(f"Running {pth.relative_to(DOCS_DIR)}") run_notebook(tmp_path, pth) From 34316edc252203fc4b740208d4718475d6e3644d Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Jul 2023 11:00:12 +0200 Subject: [PATCH 14/27] add tutorial_client_compatibility.json --- .../test/e2e/data/tutorial_client_compatibility.json | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 clients/python/test/e2e/data/tutorial_client_compatibility.json diff --git a/clients/python/test/e2e/data/tutorial_client_compatibility.json b/clients/python/test/e2e/data/tutorial_client_compatibility.json new file mode 100644 index 00000000..228a1db2 --- /dev/null +++ b/clients/python/test/e2e/data/tutorial_client_compatibility.json @@ -0,0 +1,9 @@ +{ + "__comment": "This document describes which tutorial notebooks are compatible with which version of the osparc python package", + "versions": + { + "0.5.0": ["BasicTutorial.ipynb"], + "0.6.0": ["BasicTutorial.ipynb", + "StudyTutorial.ipynb"] + } +} From d6ed3a512f6de3e52725407e21b0867b64516de1 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Jul 2023 11:31:54 +0200 Subject: [PATCH 15/27] parametrize tests and make them pass --- clients/python/docs/BasicTutorial.ipynb | 1 + clients/python/test/e2e/test_notebooks.py | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/clients/python/docs/BasicTutorial.ipynb b/clients/python/docs/BasicTutorial.ipynb index b3d337be..4c4470a2 100755 --- a/clients/python/docs/BasicTutorial.ipynb +++ b/clients/python/docs/BasicTutorial.ipynb @@ -33,6 +33,7 @@ " ! pip install osparc\n", "! python -c \"import osparc; print(osparc.__version__)\"\n", "\n", + "import osparc\n", "# this tutorial is compatible with version >= 0.5.0\n", "CLIENT_VERSION = tuple(map(int, osparc.__version__.split(\".\")))\n", "assert tuple(map(int, osparc.__version__.split(\".\"))) >= (0, 5, 0)" diff --git a/clients/python/test/e2e/test_notebooks.py b/clients/python/test/e2e/test_notebooks.py index 038609ff..df645bb7 100644 --- a/clients/python/test/e2e/test_notebooks.py +++ b/clients/python/test/e2e/test_notebooks.py @@ -70,6 +70,7 @@ def get_tutorials(osparc_version: Optional[str] = None) -> List[Path]: result: List[Path] = [] for name in tutorial_names: result += list(DOCS_DIR.rglob(f"*{name}")) + return result @@ -100,18 +101,17 @@ def test_notebook_config(tmp_path: Path): json_notebooks: Set[Path] = set(get_tutorials()) assert len(tutorials) > 0, f"Did not find any tutorial notebooks in {DOCS_DIR}" assert ( - len(tutorials.intersection(json_notebooks)) == 0 + len(tutorials.difference(json_notebooks)) == 0 ), f"Some tutorial notebooks are not present in {TUTORIAL_CLIENT_COMPATIBILITY_JSON}" -@pytest.mark.parametrize("tutorials", get_tutorials(osparc_version=osparc.__version__)) -def test_run_tutorials(tmp_path: Path, tutorials: List[Path]): +@pytest.mark.parametrize("tutorial", get_tutorials(osparc_version=osparc.__version__)) +def test_run_tutorials(tmp_path: Path, tutorial: Path): """Run all tutorials compatible with the installed version of osparc Args: tmp_path (Path): pytest tmp_path fixture tutorials (List[Path]): list of tutorials """ - for pth in tutorials: - print(f"Running {pth.relative_to(DOCS_DIR)}") - run_notebook(tmp_path, pth) + print(f"Running {tutorial.relative_to(DOCS_DIR)}") + run_notebook(tmp_path, tutorial) From 00c930bb8d253b1e455ec5e50fbbaa66e5e6efc9 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Jul 2023 13:29:22 +0200 Subject: [PATCH 16/27] improve tests --- clients/python/test/e2e/test_notebooks.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/clients/python/test/e2e/test_notebooks.py b/clients/python/test/e2e/test_notebooks.py index df645bb7..86a33b89 100644 --- a/clients/python/test/e2e/test_notebooks.py +++ b/clients/python/test/e2e/test_notebooks.py @@ -16,10 +16,12 @@ TUTORIAL_CLIENT_COMPATIBILITY_JSON: Path = ( DATA_DIR / "tutorial_client_compatibility.json" ) +API_DIR: Path = DOCS_DIR.parent.parent.parent / "api" assert DOCS_DIR.is_dir() assert DATA_DIR.is_dir() assert TUTORIAL_CLIENT_COMPATIBILITY_JSON.is_file() +assert API_DIR.is_dir() def run_notebook(tmp_path: Path, notebook: Path, params: dict[str, Any] = {}): @@ -96,6 +98,7 @@ def test_notebook_config(tmp_path: Path): "expected_osparc_file": osparc.__file__, }, ) + # sanity check paths and jsons: are we collecting all notebooks? tutorials: Set[Path] = set(DOCS_DIR.glob("*.ipynb")) json_notebooks: Set[Path] = set(get_tutorials()) @@ -104,8 +107,17 @@ def test_notebook_config(tmp_path: Path): len(tutorials.difference(json_notebooks)) == 0 ), f"Some tutorial notebooks are not present in {TUTORIAL_CLIENT_COMPATIBILITY_JSON}" + # check that version of this repo is present in compatibility json + cur_version: str = json.loads((API_DIR / "config.json").read_text())["python"][ + "version" + ] + assert ( + cur_version + in json.loads(TUTORIAL_CLIENT_COMPATIBILITY_JSON.read_text())["versions"].keys() + ), f"The version defined in {API_DIR/'config.json'} is not present in {TUTORIAL_CLIENT_COMPATIBILITY_JSON}" + -@pytest.mark.parametrize("tutorial", get_tutorials(osparc_version=osparc.__version__)) +@pytest.mark.parametrize("tutorial", get_tutorials()) def test_run_tutorials(tmp_path: Path, tutorial: Path): """Run all tutorials compatible with the installed version of osparc @@ -113,5 +125,9 @@ def test_run_tutorials(tmp_path: Path, tutorial: Path): tmp_path (Path): pytest tmp_path fixture tutorials (List[Path]): list of tutorials """ + if not tutorial in get_tutorials(osparc.__version__): + pytest.skip( + f"{tutorial.relative_to(DOCS_DIR)} is not compatible with osparc.__version__=={osparc.__version__}. See {TUTORIAL_CLIENT_COMPATIBILITY_JSON.name}" + ) print(f"Running {tutorial.relative_to(DOCS_DIR)}") run_notebook(tmp_path, tutorial) From 39a1f838f39de925ca84fa0089fdffbb177f78f1 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Jul 2023 13:46:33 +0200 Subject: [PATCH 17/27] small fix to not run tests multiple times --- clients/python/test/e2e/test_notebooks.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/clients/python/test/e2e/test_notebooks.py b/clients/python/test/e2e/test_notebooks.py index 86a33b89..43c225e4 100644 --- a/clients/python/test/e2e/test_notebooks.py +++ b/clients/python/test/e2e/test_notebooks.py @@ -60,15 +60,17 @@ def get_tutorials(osparc_version: Optional[str] = None) -> List[Path]: compatibility_dict: Dict[str, Any] = json.loads( TUTORIAL_CLIENT_COMPATIBILITY_JSON.read_text() ) - tutorial_names: List[str] = [] + tutorial_names: Set[str] = set() if osparc_version is not None: assert ( osparc_version in compatibility_dict["versions"] ), f"{osparc_version} does not exist in {TUTORIAL_CLIENT_COMPATIBILITY_JSON}" - tutorial_names = compatibility_dict["versions"][osparc_version] + tutorial_names = set(compatibility_dict["versions"][osparc_version]) else: for v in compatibility_dict["versions"]: - tutorial_names += compatibility_dict["versions"][v] + tutorial_names = tutorial_names.union( + set(compatibility_dict["versions"][v]) + ) result: List[Path] = [] for name in tutorial_names: result += list(DOCS_DIR.rglob(f"*{name}")) From b80d9aec43720829d456dd047d1b4a0b891e6795 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Jul 2023 14:04:25 +0200 Subject: [PATCH 18/27] small change --- clients/python/test/e2e/test_notebooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/python/test/e2e/test_notebooks.py b/clients/python/test/e2e/test_notebooks.py index 43c225e4..a1a94e11 100644 --- a/clients/python/test/e2e/test_notebooks.py +++ b/clients/python/test/e2e/test_notebooks.py @@ -3,7 +3,7 @@ import sys from pathlib import Path from tempfile import TemporaryDirectory -from typing import Any, Dict, List, Optional, Set +from typing import Any, Dict, List, Optional, Set, Union import osparc import papermill as pm From ea7983cd346693270750131ecb1eca6e605b25c1 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Jul 2023 15:20:02 +0200 Subject: [PATCH 19/27] minor fixes according to PR feedback --- clients/python/test/e2e/test_notebooks.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/clients/python/test/e2e/test_notebooks.py b/clients/python/test/e2e/test_notebooks.py index a1a94e11..1563481f 100644 --- a/clients/python/test/e2e/test_notebooks.py +++ b/clients/python/test/e2e/test_notebooks.py @@ -24,7 +24,7 @@ assert API_DIR.is_dir() -def run_notebook(tmp_path: Path, notebook: Path, params: dict[str, Any] = {}): +def _run_notebook(tmp_path: Path, notebook: Path, params: dict[str, Any] = {}): """Run a jupyter notebook using papermill Args: @@ -48,7 +48,7 @@ def run_notebook(tmp_path: Path, notebook: Path, params: dict[str, Any] = {}): ) -def get_tutorials(osparc_version: Optional[str] = None) -> List[Path]: +def _get_tutorials(osparc_version: Optional[str] = None) -> List[Path]: """Returns the tutorial notebooks compatible with a given osparc client version Args: @@ -91,7 +91,7 @@ def test_notebook_config(tmp_path: Path): # sanity check configuration of jupyter environment config_test_nb: Path = DATA_DIR / "config_test.ipynb" assert config_test_nb.is_file() - run_notebook( + _run_notebook( tmp_path, config_test_nb, { @@ -103,23 +103,25 @@ def test_notebook_config(tmp_path: Path): # sanity check paths and jsons: are we collecting all notebooks? tutorials: Set[Path] = set(DOCS_DIR.glob("*.ipynb")) - json_notebooks: Set[Path] = set(get_tutorials()) + json_notebooks: Set[Path] = set(_get_tutorials()) assert len(tutorials) > 0, f"Did not find any tutorial notebooks in {DOCS_DIR}" assert ( len(tutorials.difference(json_notebooks)) == 0 ), f"Some tutorial notebooks are not present in {TUTORIAL_CLIENT_COMPATIBILITY_JSON}" # check that version of this repo is present in compatibility json - cur_version: str = json.loads((API_DIR / "config.json").read_text())["python"][ + current_version: str = json.loads((API_DIR / "config.json").read_text())["python"][ "version" ] + compatible_versions: Set[str] = json.loads( + TUTORIAL_CLIENT_COMPATIBILITY_JSON.read_text() + )["versions"].keys() assert ( - cur_version - in json.loads(TUTORIAL_CLIENT_COMPATIBILITY_JSON.read_text())["versions"].keys() + current_version in compatible_versions ), f"The version defined in {API_DIR/'config.json'} is not present in {TUTORIAL_CLIENT_COMPATIBILITY_JSON}" -@pytest.mark.parametrize("tutorial", get_tutorials()) +@pytest.mark.parametrize("tutorial", _get_tutorials(), ids=lambda p: p.name) def test_run_tutorials(tmp_path: Path, tutorial: Path): """Run all tutorials compatible with the installed version of osparc @@ -127,9 +129,8 @@ def test_run_tutorials(tmp_path: Path, tutorial: Path): tmp_path (Path): pytest tmp_path fixture tutorials (List[Path]): list of tutorials """ - if not tutorial in get_tutorials(osparc.__version__): + if not tutorial in _get_tutorials(osparc.__version__): pytest.skip( f"{tutorial.relative_to(DOCS_DIR)} is not compatible with osparc.__version__=={osparc.__version__}. See {TUTORIAL_CLIENT_COMPATIBILITY_JSON.name}" ) - print(f"Running {tutorial.relative_to(DOCS_DIR)}") - run_notebook(tmp_path, tutorial) + _run_notebook(tmp_path, tutorial) From b0b7fabc2e7a6e5b7c323f5cf9c4848c2e99c352 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Jul 2023 15:23:32 +0200 Subject: [PATCH 20/27] use tuple instead of list for __all__ --- clients/python/client/osparc/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/python/client/osparc/__init__.py b/clients/python/client/osparc/__init__.py index 2f9e5ec2..83929c65 100644 --- a/clients/python/client/osparc/__init__.py +++ b/clients/python/client/osparc/__init__.py @@ -50,7 +50,7 @@ from ._info import openapi -__all__ = [ +__all__: tuple[str, ...] = ( # imports from osparc_client "__version__", "FilesApi", @@ -96,4 +96,4 @@ "OnePageStudyPort", # imports from osparc "openapi", -] +) From 94ab77634c205f16f793ef6c86f1ee1c174dcdc5 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Jul 2023 16:50:50 +0200 Subject: [PATCH 21/27] fix tests against older python versiosn --- clients/python/client/osparc/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clients/python/client/osparc/__init__.py b/clients/python/client/osparc/__init__.py index 83929c65..501af224 100644 --- a/clients/python/client/osparc/__init__.py +++ b/clients/python/client/osparc/__init__.py @@ -1,6 +1,8 @@ """ 0.5.0 osparc client """ +from typing import Tuple + from osparc_client import ( # APIs; API client; models ApiClient, ApiException, @@ -50,7 +52,7 @@ from ._info import openapi -__all__: tuple[str, ...] = ( +__all__: Tuple[str, ...] = ( # imports from osparc_client "__version__", "FilesApi", From 23ffd7a8ca747b4c311fa70b067e3ebfb55dbcb6 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Thu, 20 Jul 2023 09:21:02 +0200 Subject: [PATCH 22/27] fix __all__ in api.py and models.py --- clients/python/client/osparc/api.py | 4 ++-- clients/python/client/osparc/models.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/clients/python/client/osparc/api.py b/clients/python/client/osparc/api.py index 2c654b57..84c25941 100644 --- a/clients/python/client/osparc/api.py +++ b/clients/python/client/osparc/api.py @@ -1,5 +1,5 @@ import warnings -from typing import Final +from typing import Final, Tuple from ._warnings_and_errors import VisibleDeprecationWarning @@ -11,4 +11,4 @@ from osparc_client.api import FilesApi, MetaApi, SolversApi, UsersApi -__all__ = ["FilesApi", "MetaApi", "SolversApi", "UsersApi"] +__all__: Tuple[str, ...] = ("FilesApi", "MetaApi", "SolversApi", "UsersApi") diff --git a/clients/python/client/osparc/models.py b/clients/python/client/osparc/models.py index 8e54bf31..888da09c 100644 --- a/clients/python/client/osparc/models.py +++ b/clients/python/client/osparc/models.py @@ -1,5 +1,5 @@ import warnings -from typing import Final +from typing import Final, Tuple from ._warnings_and_errors import VisibleDeprecationWarning @@ -26,7 +26,7 @@ from osparc_client.models import RunningState as TaskStates from osparc_client.models import Solver, UserRoleEnum, UsersGroup, ValidationError -__all__ = [ +__all__: Tuple[str, ...] = ( "BodyUploadFileV0FilesContentPut", "File", "Groups", @@ -43,4 +43,4 @@ "UserRoleEnum", "UsersGroup", "ValidationError", -] +) From 569b49b86c1b92b30a35cd02f0c564baad3e7b85 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Thu, 20 Jul 2023 09:31:43 +0200 Subject: [PATCH 23/27] separate options on different lines --- clients/python/Makefile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/clients/python/Makefile b/clients/python/Makefile index 4aef4680..010da551 100644 --- a/clients/python/Makefile +++ b/clients/python/Makefile @@ -102,7 +102,13 @@ pylint: _check_venv_active ## runs linter (only to check errors. SEE .pylintrc e .PHONY: test-dev test-dev: _check_venv_active ## runs tests during development # runs tests for development (e.g w/ pdb) - python -m pytest -vv --exitfirst --failed-first --durations=10 --pdb $(PYTHON_DIR)/test/test_osparc + python -m pytest \ + -vv \ + --exitfirst \ + --failed-first \ + --durations=10 \ + --pdb \ + $(PYTHON_DIR)/test/test_osparc .PHONY: dist dist: artifacts_dir ## builds distribution wheel From 5995dcfd8464561dc9cdeac38054f6dc8058a22c Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Thu, 20 Jul 2023 12:04:09 +0200 Subject: [PATCH 24/27] make the sleeper study id a parameter --- clients/python/docs/StudyTutorial.ipynb | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/clients/python/docs/StudyTutorial.ipynb b/clients/python/docs/StudyTutorial.ipynb index e518fce8..8c070a03 100755 --- a/clients/python/docs/StudyTutorial.ipynb +++ b/clients/python/docs/StudyTutorial.ipynb @@ -44,7 +44,22 @@ "source": [ "## Study Workflow\n", "\n", - "Studies can connect computational resources as well as pre- and postprocess data. Studies mmust be created through the UI (where you generated the `OSPARC_API_KEY` and the `OSPARC_API_SECRET`). Once a study has been created via the UI one can interact with it (modify, run, read and write data to it etc.) using the `osparc` python package. Here we will demonstrate the typical workflow. As an illustration we consider a simple study containing a single sleeper solver.\n" + "Studies can connect computational resources as well as pre- and postprocess data. Studies must be created through the UI (where you generated the `OSPARC_API_KEY` and the `OSPARC_API_SECRET`). Once a study has been created via the UI one can interact with it (modify, run, read and write data to it etc.) using the `osparc` python package. Here we will demonstrate the typical workflow. As an illustration we will use the `Single Sleeper` study. This study is available as a template through the UI. Depending on which version of OSPARC you are us\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8707f005", + "metadata": { + "tags": [ + "parameters" + ] + }, + "outputs": [], + "source": [ + "from typing import Optional\n", + "SINGLE_SLEEPER_STUDY_ID: Optional[str] = None" ] }, { @@ -62,12 +77,12 @@ "import osparc\n", "\n", "# Get the ID of a study under the study information\n", - "SLEEPER_STUDY_ID: str = \"fcad50ac-253e-11ee-9414-02420a0f071c\"\n", + "assert SINGLE_SLEEPER_STUDY_ID is not None, \"The SINGLE_SLEEPER_STUDY_ID must be set before running the tutorial\"\n", "\n", "with osparc.ApiClient(cfg) as api_client:\n", "\n", " studies_api: osparc.StudiesApi = osparc.StudiesApi(api_client)\n", - " study: osparc.Study = studies_api.get_study(SLEEPER_STUDY_ID)\n", + " study: osparc.Study = studies_api.get_study(SINGLE_SLEEPER_STUDY_ID)\n", "\n", "\n", " job: osparc.Job = studies_api.create_study_job(study_id=study.id, \n", From 579a87a27cf46ce592ea5b541da5bbf5fcec0888 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Tue, 5 Sep 2023 13:17:00 +0200 Subject: [PATCH 25/27] update to lates openapi.json --- api/openapi.json | 192 ++++++++++++++++++++++++++++++----------------- 1 file changed, 124 insertions(+), 68 deletions(-) diff --git a/api/openapi.json b/api/openapi.json index dcb7d219..3a4e8c74 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -251,7 +251,7 @@ "files" ], "summary": "Get Upload Links", - "description": "Get upload links for uploading a file to storage\n\nArguments:\n request -- Request object\n client_file -- ClientFile object\n user_id -- User Id\n\nReturns:\n FileUploadSchema", + "description": "Get upload links for uploading a file to storage", "operationId": "get_upload_links", "requestBody": { "content": { @@ -292,14 +292,14 @@ ] } }, - "/v0/files/{file_id}:complete": { - "post": { + "/v0/files/{file_id}": { + "get": { "tags": [ "files" ], - "summary": "Complete Multipart Upload", - "description": "Complete multipart upload\n\nArguments:\n request: The Request object\n file_id: The Storage id\n file: The File object which is to be completed\n uploaded_parts: The uploaded parts\n completion_link: The completion link\n user_id: The user id\n\nReturns:\n The completed File object", - "operationId": "complete_multipart_upload", + "summary": "Get File", + "description": "Gets metadata for a given file resource", + "operationId": "get_file", "parameters": [ { "required": true, @@ -312,16 +312,6 @@ "in": "path" } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_complete_multipart_upload_v0_files__file_id__complete_post" - } - } - }, - "required": true - }, "responses": { "200": { "description": "Successful Response", @@ -333,6 +323,16 @@ } } }, + "404": { + "description": "File not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, "422": { "description": "Validation Error", "content": { @@ -349,16 +349,13 @@ "HTTPBasic": [] } ] - } - }, - "/v0/files/{file_id}:abort": { - "post": { + }, + "delete": { "tags": [ "files" ], - "summary": "Abort Multipart Upload", - "description": "Abort a multipart upload\n\nArguments:\n request: The Request\n file_id: The StorageFileID\n upload_links: The FileUploadSchema\n user_id: The user id", - "operationId": "abort_multipart_upload", + "summary": "Delete File", + "operationId": "delete_file", "parameters": [ { "required": true, @@ -371,22 +368,17 @@ "in": "path" } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_abort_multipart_upload_v0_files__file_id__abort_post" - } - } - }, - "required": true - }, "responses": { - "200": { - "description": "Successful Response", + "204": { + "description": "Successful Response" + }, + "404": { + "description": "File not found", "content": { "application/json": { - "schema": {} + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } } } }, @@ -408,14 +400,13 @@ ] } }, - "/v0/files/{file_id}": { - "get": { + "/v0/files/{file_id}:abort": { + "post": { "tags": [ "files" ], - "summary": "Get File", - "description": "Gets metadata for a given file resource", - "operationId": "get_file", + "summary": "Abort Multipart Upload", + "operationId": "abort_multipart_upload", "parameters": [ { "required": true, @@ -428,24 +419,22 @@ "in": "path" } ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/File" - } + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_abort_multipart_upload_v0_files__file_id__abort_post" } } }, - "404": { - "description": "File not found", + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorGet" - } + "schema": {} } } }, @@ -465,13 +454,15 @@ "HTTPBasic": [] } ] - }, - "delete": { + } + }, + "/v0/files/{file_id}:complete": { + "post": { "tags": [ "files" ], - "summary": "Delete File", - "operationId": "delete_file", + "summary": "Complete Multipart Upload", + "operationId": "complete_multipart_upload", "parameters": [ { "required": true, @@ -484,16 +475,23 @@ "in": "path" } ], - "responses": { - "204": { - "description": "Successful Response" + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_complete_multipart_upload_v0_files__file_id__complete_post" + } + } }, - "404": { - "description": "File not found", + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ErrorGet" + "$ref": "#/components/schemas/File" } } } @@ -1145,7 +1143,7 @@ "required": true }, "responses": { - "200": { + "201": { "description": "Successful Response", "content": { "application/json": { @@ -1179,7 +1177,7 @@ "solvers" ], "summary": "Get Jobs Page", - "description": "List of jobs on a specific released solver (includes pagination)\n\n\nBreaking change in *version 0.5*: response model changed from list[Job] to pagination Page[Job].", + "description": "List of jobs on a specific released solver (includes pagination)", "operationId": "get_jobs_page", "parameters": [ { @@ -2051,6 +2049,64 @@ ] } }, + "/v0/studies/{study_id}:clone": { + "post": { + "tags": [ + "studies" + ], + "summary": "Clone Study", + "operationId": "clone_study", + "parameters": [ + { + "required": true, + "schema": { + "title": "Study Id", + "type": "string", + "format": "uuid" + }, + "name": "study_id", + "in": "path" + } + ], + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Study" + } + } + } + }, + "404": { + "description": "Study not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "security": [ + { + "HTTPBasic": [] + } + ] + } + }, "/v0/studies/{study_id}/ports": { "get": { "tags": [ @@ -2194,7 +2250,7 @@ } ], "responses": { - "200": { + "201": { "description": "Successful Response", "content": { "application/json": { @@ -2788,7 +2844,7 @@ "file_id": { "title": "File Id", "type": "string", - "description": "The file id", + "description": "The file resource id", "format": "uuid" }, "upload_schema": { From 845fb38b4a80ac74a304d219b9589eb647afbc56 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Tue, 5 Sep 2023 13:37:29 +0200 Subject: [PATCH 26/27] update study tutorial --- clients/python/docs/StudyTutorial.ipynb | 27 ++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/clients/python/docs/StudyTutorial.ipynb b/clients/python/docs/StudyTutorial.ipynb index 8c070a03..2334dcef 100755 --- a/clients/python/docs/StudyTutorial.ipynb +++ b/clients/python/docs/StudyTutorial.ipynb @@ -14,6 +14,24 @@ "The installation and configuration process is discussed in detail in the `BasicTutorial`. Of course, if we don't already have `osparc` installed we need to do so and we need to setup the `osparc.Configuration`:" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "dbeace4c", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "import osparc\n", + "print(sys.executable)" + ] + }, + { + "cell_type": "markdown", + "id": "27ed012e", + "metadata": {}, + "source": [] + }, { "cell_type": "code", "execution_count": null, @@ -59,7 +77,7 @@ "outputs": [], "source": [ "from typing import Optional\n", - "SINGLE_SLEEPER_STUDY_ID: Optional[str] = None" + "SINGLE_SLEEPER_STUDY_ID: Optional[str] = \"6dda87ae-26e3-11ee-a43a-02420a0f0717\" # https://osparc-master.speag.com/study/6dda87ae-26e3-11ee-a43a-02420a0f0717" ] }, { @@ -82,10 +100,9 @@ "with osparc.ApiClient(cfg) as api_client:\n", "\n", " studies_api: osparc.StudiesApi = osparc.StudiesApi(api_client)\n", - " study: osparc.Study = studies_api.get_study(SINGLE_SLEEPER_STUDY_ID)\n", - "\n", + " study: osparc.Study = studies_api.clone_study(SINGLE_SLEEPER_STUDY_ID)\n", "\n", - " job: osparc.Job = studies_api.create_study_job(study_id=study.id, \n", + " job: osparc.Job = studies_api.create_study_job(study_id=study.uid, \n", " job_inputs=osparc.JobInputs({\n", " \"X\": 1\n", " }))\n", @@ -158,7 +175,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.0" + "version": "3.10.10" } }, "nbformat": 4, From 361329521026468ea684fa2252ff215dfeb431f4 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Tue, 5 Sep 2023 13:39:29 +0200 Subject: [PATCH 27/27] remove parameter tag --- clients/python/docs/StudyTutorial.ipynb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/clients/python/docs/StudyTutorial.ipynb b/clients/python/docs/StudyTutorial.ipynb index 2334dcef..4c434d8a 100755 --- a/clients/python/docs/StudyTutorial.ipynb +++ b/clients/python/docs/StudyTutorial.ipynb @@ -70,9 +70,7 @@ "execution_count": null, "id": "8707f005", "metadata": { - "tags": [ - "parameters" - ] + "tags": [] }, "outputs": [], "source": [