From 7f6a5ae8451d68d6fd2b0ca33553e2594e9bf5e8 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Mon, 31 Mar 2025 10:48:51 +0200 Subject: [PATCH 01/11] update openapi.json --- api/openapi.json | 1442 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 1324 insertions(+), 118 deletions(-) diff --git a/api/openapi.json b/api/openapi.json index d70ef50e..1e0030a3 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -449,7 +449,15 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ClientFile" + "anyOf": [ + { + "$ref": "#/components/schemas/ClientFileInProgramJob" + }, + { + "$ref": "#/components/schemas/ClientFile" + } + ], + "title": "Client File" } } } @@ -805,9 +813,9 @@ "required": false, "schema": { "type": "integer", - "maximum": 100, + "maximum": 50, "minimum": 1, - "default": 50, + "default": 20, "title": "Limit" } }, @@ -1248,6 +1256,189 @@ } } }, + "/v0/programs": { + "get": { + "tags": [ + "programs" + ], + "summary": "List Programs", + "description": "Lists all available solvers (latest version)\n\nSEE get_solvers_page for paginated version of this function", + "operationId": "list_programs", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Program" + }, + "type": "array", + "title": "Response List Programs V0 Programs Get" + } + } + } + } + }, + "security": [ + { + "HTTPBasic": [] + } + ] + } + }, + "/v0/programs/{program_key}/releases/{version}": { + "get": { + "tags": [ + "programs" + ], + "summary": "Get Program Release", + "description": "Gets a specific release of a solver", + "operationId": "get_program_release", + "security": [ + { + "HTTPBasic": [] + } + ], + "parameters": [ + { + "name": "program_key", + "in": "path", + "required": true, + "schema": { + "type": "string", + "pattern": "^simcore/services/dynamic/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", + "title": "Program Key" + } + }, + { + "name": "version", + "in": "path", + "required": true, + "schema": { + "type": "string", + "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-]+)*)?$", + "title": "Version" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Program" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v0/programs/{program_key}/releases/{version}/jobs": { + "post": { + "tags": [ + "programs" + ], + "summary": "Create Program Job", + "description": "Creates a job in a specific release with given inputs.\n\nNOTE: This operation does **not** start the job", + "operationId": "create_program_job", + "security": [ + { + "HTTPBasic": [] + } + ], + "parameters": [ + { + "name": "program_key", + "in": "path", + "required": true, + "schema": { + "type": "string", + "pattern": "^simcore/services/dynamic/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", + "title": "Program Key" + } + }, + { + "name": "version", + "in": "path", + "required": true, + "schema": { + "type": "string", + "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-]+)*)?$", + "title": "Version" + } + }, + { + "name": "x-simcore-parent-project-uuid", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "title": "X-Simcore-Parent-Project-Uuid" + } + }, + { + "name": "x-simcore-parent-node-id", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "title": "X-Simcore-Parent-Node-Id" + } + } + ], + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, "/v0/solvers": { "get": { "tags": [ @@ -2014,9 +2205,9 @@ "tags": [ "solvers" ], - "summary": "Create Job", + "summary": "Create Solver Job", "description": "Creates a job in a specific release with given inputs.\n\nNOTE: This operation does **not** start the job", - "operationId": "create_job", + "operationId": "create_solver_job", "security": [ { "HTTPBasic": [] @@ -3352,9 +3543,9 @@ "required": false, "schema": { "type": "integer", - "maximum": 100, + "maximum": 50, "minimum": 1, - "default": 50, + "default": 20, "title": "Limit" } }, @@ -4164,9 +4355,9 @@ "required": false, "schema": { "type": "integer", - "maximum": 100, + "maximum": 50, "minimum": 1, - "default": 50, + "default": 20, "title": "Limit" } }, @@ -5322,74 +5513,574 @@ } } }, - "/v0/credits/price": { + "/v0/wallets/{wallet_id}/licensed-items": { "get": { "tags": [ - "credits" + "wallets" + ], + "summary": "Get Available Licensed Items For Wallet", + "description": "Get all available licensed items for a given wallet", + "operationId": "get_available_licensed_items_for_wallet", + "security": [ + { + "HTTPBasic": [] + } + ], + "parameters": [ + { + "name": "wallet_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Wallet Id" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 50, + "minimum": 1, + "default": 20, + "title": "Limit" + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "default": 0, + "title": "Offset" + } + } ], - "summary": "Get Credits Price", - "description": "New in *version 0.6.0*", - "operationId": "get_credits_price", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetCreditPriceLegacy" + "$ref": "#/components/schemas/Page_LicensedItemGet_" } } } - } - }, - "security": [ - { - "HTTPBasic": [] - } - ] - } - } - }, - "components": { - "schemas": { - "Body_abort_multipart_upload_v0_files__file_id__abort_post": { - "properties": { - "client_file": { - "$ref": "#/components/schemas/ClientFile" - } - }, - "type": "object", - "required": [ - "client_file" - ], - "title": "Body_abort_multipart_upload_v0_files__file_id__abort_post" - }, - "Body_complete_multipart_upload_v0_files__file_id__complete_post": { - "properties": { - "client_file": { - "$ref": "#/components/schemas/ClientFile" }, - "uploaded_parts": { - "$ref": "#/components/schemas/FileUploadCompletionBody" - } - }, - "type": "object", - "required": [ - "client_file", - "uploaded_parts" - ], - "title": "Body_complete_multipart_upload_v0_files__file_id__complete_post" - }, - "Body_upload_file_v0_files_content_put": { - "properties": { - "file": { - "type": "string", - "format": "binary", - "title": "File" - } - }, - "type": "object", - "required": [ + "404": { + "description": "Wallet not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "403": { + "description": "Access to wallet is not allowed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "429": { + "description": "Too many requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "502": { + "description": "Unexpected error when communicating with backend service", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "503": { + "description": "Service unavailable", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "504": { + "description": "Request to a backend service timed out.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v0/wallets/{wallet_id}/licensed-items/{licensed_item_id}/checkout": { + "post": { + "tags": [ + "wallets" + ], + "summary": "Checkout Licensed Item", + "description": "Checkout licensed item", + "operationId": "checkout_licensed_item", + "security": [ + { + "HTTPBasic": [] + } + ], + "parameters": [ + { + "name": "wallet_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Wallet Id" + } + }, + { + "name": "licensed_item_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Licensed Item Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LicensedItemCheckoutData" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LicensedItemCheckoutGet" + } + } + } + }, + "404": { + "description": "Wallet not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "403": { + "description": "Access to wallet is not allowed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "429": { + "description": "Too many requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "502": { + "description": "Unexpected error when communicating with backend service", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "503": { + "description": "Service unavailable", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "504": { + "description": "Request to a backend service timed out.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v0/credits/price": { + "get": { + "tags": [ + "credits" + ], + "summary": "Get Credits Price", + "description": "New in *version 0.6.0*", + "operationId": "get_credits_price", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetCreditPriceLegacy" + } + } + } + } + }, + "security": [ + { + "HTTPBasic": [] + } + ] + } + }, + "/v0/licensed-items": { + "get": { + "tags": [ + "licensed-items" + ], + "summary": "Get Licensed Items", + "description": "Get all licensed items", + "operationId": "get_licensed_items", + "security": [ + { + "HTTPBasic": [] + } + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "maximum": 50, + "minimum": 1, + "default": 20, + "title": "Limit" + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "default": 0, + "title": "Offset" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Page_LicensedItemGet_" + } + } + } + }, + "429": { + "description": "Too many requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "502": { + "description": "Unexpected error when communicating with backend service", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "503": { + "description": "Service unavailable", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "504": { + "description": "Request to a backend service timed out.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/v0/licensed-items/{licensed_item_id}/checked-out-items/{licensed_item_checkout_id}/release": { + "post": { + "tags": [ + "licensed-items" + ], + "summary": "Release Licensed Item", + "description": "Release previously checked out licensed item", + "operationId": "release_licensed_item", + "security": [ + { + "HTTPBasic": [] + } + ], + "parameters": [ + { + "name": "licensed_item_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Licensed Item Id" + } + }, + { + "name": "licensed_item_checkout_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Licensed Item Checkout Id" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LicensedItemCheckoutGet" + } + } + } + }, + "429": { + "description": "Too many requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "502": { + "description": "Unexpected error when communicating with backend service", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "503": { + "description": "Service unavailable", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "504": { + "description": "Request to a backend service timed out.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Body_abort_multipart_upload_v0_files__file_id__abort_post": { + "properties": { + "client_file": { + "$ref": "#/components/schemas/ClientFile" + } + }, + "type": "object", + "required": [ + "client_file" + ], + "title": "Body_abort_multipart_upload_v0_files__file_id__abort_post" + }, + "Body_complete_multipart_upload_v0_files__file_id__complete_post": { + "properties": { + "client_file": { + "$ref": "#/components/schemas/ClientFile" + }, + "uploaded_parts": { + "$ref": "#/components/schemas/FileUploadCompletionBody" + } + }, + "type": "object", + "required": [ + "client_file", + "uploaded_parts" + ], + "title": "Body_complete_multipart_upload_v0_files__file_id__complete_post" + }, + "Body_upload_file_v0_files_content_put": { + "properties": { + "file": { + "type": "string", + "format": "binary", + "title": "File" + } + }, + "type": "object", + "required": [ "file" ], "title": "Body_upload_file_v0_files_content_put" @@ -5423,6 +6114,64 @@ "title": "ClientFile", "description": "Represents a file stored on the client side" }, + "ClientFileInProgramJob": { + "properties": { + "filename": { + "type": "string", + "pattern": ".+", + "title": "Filename", + "description": "File name" + }, + "filesize": { + "type": "integer", + "minimum": 0, + "title": "Filesize", + "description": "File size in bytes" + }, + "sha256_checksum": { + "type": "string", + "pattern": "^[a-fA-F0-9]{64}$", + "title": "Sha256 Checksum", + "description": "SHA256 checksum" + }, + "program_key": { + "type": "string", + "pattern": "^simcore/services/dynamic/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", + "title": "Program Key", + "description": "Program identifier" + }, + "program_version": { + "type": "string", + "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-]+)*)?$", + "title": "Program Version", + "description": "Program version" + }, + "job_id": { + "type": "string", + "format": "uuid", + "title": "Job Id", + "description": "Job identifier" + }, + "workspace_path": { + "type": "string", + "pattern": "^workspace/.*", + "format": "path", + "title": "Workspace Path", + "description": "The file's relative path within the job's workspace directory. E.g. 'workspace/myfile.txt'" + } + }, + "type": "object", + "required": [ + "filename", + "filesize", + "sha256_checksum", + "program_key", + "program_version", + "job_id", + "workspace_path" + ], + "title": "ClientFileInProgramJob" + }, "ClientFileUploadData": { "properties": { "file_id": { @@ -6003,88 +6752,417 @@ }, "type": "object", "required": [ - "job_id", - "results" + "job_id", + "results" + ], + "title": "JobOutputs", + "example": { + "job_id": "99d9ac65-9f10-4e2f-a433-b5e412bb037b", + "results": { + "enabled": false, + "maxSAR": 4.33, + "n": 55, + "output_file": { + "filename": "sar_matrix.txt", + "id": "0a3b2c56-dbcd-4871-b93b-d454b7883f9f" + }, + "title": "Specific Absorption Rate" + } + } + }, + "JobStatus": { + "properties": { + "job_id": { + "type": "string", + "format": "uuid", + "title": "Job Id" + }, + "state": { + "$ref": "#/components/schemas/RunningState" + }, + "progress": { + "type": "integer", + "maximum": 100, + "minimum": 0, + "title": "Progress", + "default": 0 + }, + "submitted_at": { + "type": "string", + "format": "date-time", + "title": "Submitted At", + "description": "Last modification timestamp of the solver job" + }, + "started_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Started At", + "description": "Timestamp that indicate the moment the solver starts execution or None if the event did not occur" + }, + "stopped_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Stopped At", + "description": "Timestamp at which the solver finished or killed execution or None if the event did not occur" + } + }, + "type": "object", + "required": [ + "job_id", + "state", + "submitted_at" + ], + "title": "JobStatus", + "example": { + "job_id": "145beae4-a3a8-4fde-adbb-4e8257c2c083", + "progress": 3, + "started_at": "2021-04-01 07:16:43.670610", + "state": "STARTED", + "submitted_at": "2021-04-01 07:15:54.631007" + } + }, + "LicensedItemCheckoutData": { + "properties": { + "number_of_seats": { + "type": "integer", + "exclusiveMinimum": true, + "title": "Number Of Seats", + "minimum": 0 + }, + "service_run_id": { + "type": "string", + "title": "Service Run Id" + } + }, + "type": "object", + "required": [ + "number_of_seats", + "service_run_id" + ], + "title": "LicensedItemCheckoutData" + }, + "LicensedItemCheckoutGet": { + "properties": { + "licensed_item_checkout_id": { + "type": "string", + "format": "uuid", + "title": "Licensed Item Checkout Id" + }, + "licensed_item_id": { + "type": "string", + "format": "uuid", + "title": "Licensed Item Id" + }, + "key": { + "type": "string", + "title": "Key" + }, + "version": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "title": "Version" + }, + "wallet_id": { + "type": "integer", + "exclusiveMinimum": true, + "title": "Wallet Id", + "minimum": 0 + }, + "user_id": { + "type": "integer", + "exclusiveMinimum": true, + "title": "User Id", + "minimum": 0 + }, + "product_name": { + "type": "string", + "title": "Product Name" + }, + "started_at": { + "type": "string", + "format": "date-time", + "title": "Started At" + }, + "stopped_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Stopped At" + }, + "num_of_seats": { + "type": "integer", + "title": "Num Of Seats" + } + }, + "type": "object", + "required": [ + "licensed_item_checkout_id", + "licensed_item_id", + "key", + "version", + "wallet_id", + "user_id", + "product_name", + "started_at", + "stopped_at", + "num_of_seats" + ], + "title": "LicensedItemCheckoutGet" + }, + "LicensedItemGet": { + "properties": { + "licensed_item_id": { + "type": "string", + "format": "uuid", + "title": "Licensed Item Id" + }, + "key": { + "type": "string", + "title": "Key" + }, + "version": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "title": "Version" + }, + "display_name": { + "type": "string", + "title": "Display Name" + }, + "licensed_resource_type": { + "$ref": "#/components/schemas/LicensedResourceType" + }, + "licensed_resources": { + "items": { + "$ref": "#/components/schemas/LicensedResource" + }, + "type": "array", + "title": "Licensed Resources" + }, + "pricing_plan_id": { + "type": "integer", + "exclusiveMinimum": true, + "title": "Pricing Plan Id", + "minimum": 0 + }, + "is_hidden_on_market": { + "type": "boolean", + "title": "Is Hidden On Market" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, + "modified_at": { + "type": "string", + "format": "date-time", + "title": "Modified At" + } + }, + "type": "object", + "required": [ + "licensed_item_id", + "key", + "version", + "display_name", + "licensed_resource_type", + "licensed_resources", + "pricing_plan_id", + "is_hidden_on_market", + "created_at", + "modified_at" ], - "title": "JobOutputs", - "example": { - "job_id": "99d9ac65-9f10-4e2f-a433-b5e412bb037b", - "results": { - "enabled": false, - "maxSAR": 4.33, - "n": 55, - "output_file": { - "filename": "sar_matrix.txt", - "id": "0a3b2c56-dbcd-4871-b93b-d454b7883f9f" - }, - "title": "Specific Absorption Rate" - } - } + "title": "LicensedItemGet" }, - "JobStatus": { + "LicensedResource": { "properties": { - "job_id": { + "source": { + "$ref": "#/components/schemas/LicensedResourceSource" + }, + "category_id": { "type": "string", - "format": "uuid", - "title": "Job Id" + "maxLength": 100, + "minLength": 1, + "title": "Category Id" }, - "state": { - "$ref": "#/components/schemas/RunningState" + "category_display": { + "type": "string", + "title": "Category Display" }, - "progress": { + "terms_of_use_url": { + "anyOf": [ + { + "type": "string", + "maxLength": 2083, + "minLength": 1, + "format": "uri" + }, + { + "type": "null" + } + ], + "title": "Terms Of Use Url" + } + }, + "type": "object", + "required": [ + "source", + "category_id", + "category_display", + "terms_of_use_url" + ], + "title": "LicensedResource" + }, + "LicensedResourceSource": { + "properties": { + "id": { "type": "integer", - "maximum": 100, - "minimum": 0, - "title": "Progress", - "default": 0 + "title": "Id" }, - "submitted_at": { + "description": { "type": "string", - "format": "date-time", - "title": "Submitted At", - "description": "Last modification timestamp of the solver job" + "title": "Description" }, - "started_at": { + "thumbnail": { + "type": "string", + "title": "Thumbnail" + }, + "features": { + "$ref": "#/components/schemas/LicensedResourceSourceFeaturesDict" + }, + "doi": { "anyOf": [ { - "type": "string", - "format": "date-time" + "type": "string" }, { "type": "null" } ], - "title": "Started At", - "description": "Timestamp that indicate the moment the solver starts execution or None if the event did not occur" + "title": "Doi" }, - "stopped_at": { + "license_key": { + "type": "string", + "title": "License Key" + }, + "license_version": { + "type": "string", + "title": "License Version" + }, + "protection": { + "type": "string", + "enum": [ + "Code", + "PayPal" + ], + "title": "Protection" + }, + "available_from_url": { "anyOf": [ { "type": "string", - "format": "date-time" + "maxLength": 2083, + "minLength": 1, + "format": "uri" }, { "type": "null" } ], - "title": "Stopped At", - "description": "Timestamp at which the solver finished or killed execution or None if the event did not occur" + "title": "Available From Url" } }, "type": "object", "required": [ - "job_id", - "state", - "submitted_at" + "id", + "description", + "thumbnail", + "features", + "doi", + "license_key", + "license_version", + "protection", + "available_from_url" + ], + "title": "LicensedResourceSource" + }, + "LicensedResourceSourceFeaturesDict": { + "properties": { + "age": { + "type": "string", + "title": "Age" + }, + "date": { + "type": "string", + "format": "date", + "title": "Date" + }, + "ethnicity": { + "type": "string", + "title": "Ethnicity" + }, + "functionality": { + "type": "string", + "title": "Functionality" + }, + "height": { + "type": "string", + "title": "Height" + }, + "name": { + "type": "string", + "title": "Name" + }, + "sex": { + "type": "string", + "title": "Sex" + }, + "species": { + "type": "string", + "title": "Species" + }, + "version": { + "type": "string", + "title": "Version" + }, + "weight": { + "type": "string", + "title": "Weight" + } + }, + "type": "object", + "required": [ + "date" ], - "title": "JobStatus", - "example": { - "job_id": "145beae4-a3a8-4fde-adbb-4e8257c2c083", - "progress": 3, - "started_at": "2021-04-01 07:16:43.670610", - "state": "STARTED", - "submitted_at": "2021-04-01 07:15:54.631007" - } + "title": "LicensedResourceSourceFeaturesDict" + }, + "LicensedResourceType": { + "type": "string", + "enum": [ + "VIP_MODEL" + ], + "title": "LicensedResourceType" }, "Links": { "properties": { @@ -6409,6 +7487,65 @@ ], "title": "Page[Job]" }, + "Page_LicensedItemGet_": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/LicensedItemGet" + }, + "type": "array", + "title": "Items" + }, + "total": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "null" + } + ], + "title": "Total" + }, + "limit": { + "anyOf": [ + { + "type": "integer", + "minimum": 1 + }, + { + "type": "null" + } + ], + "title": "Limit" + }, + "offset": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "null" + } + ], + "title": "Offset" + }, + "links": { + "$ref": "#/components/schemas/Links" + } + }, + "type": "object", + "required": [ + "items", + "total", + "limit", + "offset", + "links" + ], + "title": "Page[LicensedItemGet]" + }, "Page_Study_": { "properties": { "items": { @@ -6489,7 +7626,7 @@ "title": "Unitname" }, "unitExtraInfo": { - "$ref": "#/components/schemas/UnitExtraInfo" + "$ref": "#/components/schemas/UnitExtraInfoTier" }, "currentCostPerUnit": { "type": "number", @@ -6634,6 +7771,75 @@ "type": "object", "title": "ProfileUpdate" }, + "Program": { + "properties": { + "id": { + "type": "string", + "pattern": "^simcore/services/dynamic/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", + "title": "Id", + "description": "Program identifier" + }, + "version": { + "type": "string", + "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-]+)*)?$", + "title": "Version", + "description": "semantic version number of the node" + }, + "title": { + "type": "string", + "title": "Title", + "description": "Human readable name" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" + }, + "maintainer": { + "type": "string", + "title": "Maintainer" + }, + "url": { + "anyOf": [ + { + "type": "string", + "maxLength": 2083, + "minLength": 1, + "format": "uri" + }, + { + "type": "null" + } + ], + "title": "Url", + "description": "Link to get this resource" + } + }, + "type": "object", + "required": [ + "id", + "version", + "title", + "maintainer", + "url" + ], + "title": "Program", + "description": "A released solver with a specific version", + "example": { + "description": "Simulation framework", + "id": "simcore/services/dynamic/sim4life", + "maintainer": "info@itis.swiss", + "title": "Sim4life", + "url": "https://api.osparc.io/v0/solvers/simcore%2Fservices%2Fdynamic%2Fsim4life/releases/8.0.0", + "version": "8.0.0" + } + }, "RunningState": { "type": "string", "enum": [ @@ -6898,7 +8104,7 @@ "kind": "input" } }, - "UnitExtraInfo": { + "UnitExtraInfoTier": { "properties": { "CPU": { "type": "integer", @@ -6923,7 +8129,7 @@ "RAM", "VRAM" ], - "title": "UnitExtraInfo", + "title": "UnitExtraInfoTier", "description": "Custom information that is propagated to the frontend. Defined fields are mandatory." }, "UploadLinks": { From 0e86bd0280dde845bd866219aa5dec1898408dff Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Mon, 31 Mar 2025 11:46:47 +0200 Subject: [PATCH 02/11] start adding programs example --- clients/python/src/osparc/_api_files_api.py | 24 ++++++-- clients/python/src/osparc/api.py | 1 + clients/python/src/osparc/models.py | 3 + programs_example.py | 63 +++++++++++++++++++++ 4 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 programs_example.py diff --git a/clients/python/src/osparc/_api_files_api.py b/clients/python/src/osparc/_api_files_api.py index 8931cd5a..2dcced41 100644 --- a/clients/python/src/osparc/_api_files_api.py +++ b/clients/python/src/osparc/_api_files_api.py @@ -25,6 +25,7 @@ FileUploadCompletionBody, FileUploadData, UploadedPart, + ClientFileInProgramJob, ) from urllib.parse import urljoin import aiofiles @@ -134,6 +135,10 @@ def upload_file( async def upload_file_async( self, file: Union[str, Path], + program_key: str, + program_version: str, + job_id: str, + workspace_path: str, timeout_seconds: int = DEFAULT_TIMEOUT_SECONDS, max_concurrent_uploads: int = _MAX_CONCURRENT_UPLOADS, **kwargs, @@ -150,10 +155,21 @@ async def upload_file_async( # if a file has the same sha256 checksum # and name they are considered equal return file_result - client_file: ClientFile = ClientFile( - filename=file.name, - filesize=file.stat().st_size, - sha256_checksum=checksum, + # ClientFileInProgramJob( + # filename=file.name, + # filesize=file.stat().st_size, + # sha256_checksum=checksum, + # ) + client_file = ClientFile( + ClientFileInProgramJob( + filename=file.name, + filesize=file.stat().st_size, + sha256_checksum=checksum, + program_key=program_key, + program_version=program_version, + job_id=job_id, + workspace_path=workspace_path, + ) ) client_upload_schema: ClientFileUploadData = super().get_upload_links( client_file=client_file, _request_timeout=timeout_seconds, **kwargs diff --git a/clients/python/src/osparc/api.py b/clients/python/src/osparc/api.py index b3d751da..fd9ee87a 100644 --- a/clients/python/src/osparc/api.py +++ b/clients/python/src/osparc/api.py @@ -12,6 +12,7 @@ from osparc_client.api.meta_api import MetaApi as MetaApi from osparc_client.api.users_api import UsersApi as UsersApi from osparc_client.api.wallets_api import WalletsApi as WalletsApi +from osparc_client.api.programs_api import ProgramsApi as ProgramsApi from ._api_solvers_api import SolversApi as SolversApi diff --git a/clients/python/src/osparc/models.py b/clients/python/src/osparc/models.py index 195d3ca0..d3fedced 100644 --- a/clients/python/src/osparc/models.py +++ b/clients/python/src/osparc/models.py @@ -13,6 +13,9 @@ BodyCompleteMultipartUploadV0FilesFileIdCompletePost as BodyCompleteMultipartUploadV0FilesFileIdCompletePost, ) from osparc_client.models.client_file import ClientFile as ClientFile +from osparc_client.models.client_file_in_program_job import ( + ClientFileInProgramJob as ClientFileInProgramJob, +) from osparc_client.models.client_file_upload_data import ( ClientFileUploadData as ClientFileUploadData, ) diff --git a/programs_example.py b/programs_example.py new file mode 100644 index 00000000..7c386980 --- /dev/null +++ b/programs_example.py @@ -0,0 +1,63 @@ +from pathlib import Path +from pydantic_settings import BaseSettings, SettingsConfigDict +from pydantic import Field +import osparc +import asyncio +from tempfile import TemporaryDirectory + + +class OsparcUser(BaseSettings): + host: str = Field(default=...) + key: str = Field(default=...) + secret: str = Field(default=...) + model_config = SettingsConfigDict(env_prefix="osparc_api_") + + +def osparc_client(): + user = OsparcUser() + config = osparc.Configuration( + host=user.host, username=user.key, password=user.secret + ) + print(config.host) + print(config.username) + print(config.password) + return osparc.ApiClient(config) + + +def check_credentials(): + with osparc_client() as api_client: + user_api = osparc.UsersApi(api_client) + print(user_api.get_my_profile()) + + +async def run(program_key: str, version: str) -> osparc.JobStatus: + with osparc_client() as api_client: + files_api = osparc.FilesApi(api_client) + programs_api = osparc.ProgramsApi(api_client) + # check if the program exists + program = programs_api.get_program_release( + program_key=program_key, version=version + ) + + program_job = programs_api.create_program_job( + program_key=program.id, version=program.version + ) + + with TemporaryDirectory() as tmp_dir: + # Upload the input file + tmp_file = Path(tmp_dir) / "tmpfile" + tmp_file.write_text("this is a test") + await files_api.upload_file_async( + file=tmp_file, + program_key=program.id, + program_version=program.version, + job_id=program_job.id, + workspace_path="workspace/tmpfile", + ) + + +if __name__ == "__main__": + check_credentials() + asyncio.run( + run(program_key="simcore/services/dynamic/jupyter-math", version="3.0.2") + ) From c5d806e132aa04f4af0a8a19472b09c9558ea4bb Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Mon, 31 Mar 2025 16:56:50 +0200 Subject: [PATCH 03/11] correct file to upload --- programs_example.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/programs_example.py b/programs_example.py index 7c386980..7175d765 100644 --- a/programs_example.py +++ b/programs_example.py @@ -42,17 +42,18 @@ async def run(program_key: str, version: str) -> osparc.JobStatus: program_job = programs_api.create_program_job( program_key=program.id, version=program.version ) + print(f"{program_job=}") with TemporaryDirectory() as tmp_dir: # Upload the input file tmp_file = Path(tmp_dir) / "tmpfile" - tmp_file.write_text("this is a test") + tmp_file.write_text("Hi oSPARC 👋") await files_api.upload_file_async( file=tmp_file, program_key=program.id, program_version=program.version, job_id=program_job.id, - workspace_path="workspace/tmpfile", + workspace_path="workspace/tmpdirectory/tmpfile", ) From 3741ce17c6601d8142657d3918b0bdf9be373a5f Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Mon, 31 Mar 2025 17:34:40 +0200 Subject: [PATCH 04/11] allow to also upload files to programs --- api/openapi.json | 36 +++++--- clients/python/src/osparc/_api_files_api.py | 99 +++++++++++++++++---- clients/python/src/osparc/models.py | 5 +- programs_example.py | 2 +- 4 files changed, 110 insertions(+), 32 deletions(-) diff --git a/api/openapi.json b/api/openapi.json index 1e0030a3..02faee48 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -451,13 +451,13 @@ "schema": { "anyOf": [ { - "$ref": "#/components/schemas/ClientFileInProgramJob" + "$ref": "#/components/schemas/ClientFileToProgramJob" }, { "$ref": "#/components/schemas/ClientFile" } ], - "title": "Client File" + "title": "User File" } } } @@ -6045,20 +6045,36 @@ "schemas": { "Body_abort_multipart_upload_v0_files__file_id__abort_post": { "properties": { - "client_file": { - "$ref": "#/components/schemas/ClientFile" + "user_file": { + "anyOf": [ + { + "$ref": "#/components/schemas/ClientFileToProgramJob" + }, + { + "$ref": "#/components/schemas/ClientFile" + } + ], + "title": "User File" } }, "type": "object", "required": [ - "client_file" + "user_file" ], "title": "Body_abort_multipart_upload_v0_files__file_id__abort_post" }, "Body_complete_multipart_upload_v0_files__file_id__complete_post": { "properties": { - "client_file": { - "$ref": "#/components/schemas/ClientFile" + "user_file": { + "anyOf": [ + { + "$ref": "#/components/schemas/ClientFileToProgramJob" + }, + { + "$ref": "#/components/schemas/ClientFile" + } + ], + "title": "User File" }, "uploaded_parts": { "$ref": "#/components/schemas/FileUploadCompletionBody" @@ -6066,7 +6082,7 @@ }, "type": "object", "required": [ - "client_file", + "user_file", "uploaded_parts" ], "title": "Body_complete_multipart_upload_v0_files__file_id__complete_post" @@ -6114,7 +6130,7 @@ "title": "ClientFile", "description": "Represents a file stored on the client side" }, - "ClientFileInProgramJob": { + "ClientFileToProgramJob": { "properties": { "filename": { "type": "string", @@ -6170,7 +6186,7 @@ "job_id", "workspace_path" ], - "title": "ClientFileInProgramJob" + "title": "ClientFileToProgramJob" }, "ClientFileUploadData": { "properties": { diff --git a/clients/python/src/osparc/_api_files_api.py b/clients/python/src/osparc/_api_files_api.py index 2dcced41..4391a99b 100644 --- a/clients/python/src/osparc/_api_files_api.py +++ b/clients/python/src/osparc/_api_files_api.py @@ -25,7 +25,8 @@ FileUploadCompletionBody, FileUploadData, UploadedPart, - ClientFileInProgramJob, + ClientFileToProgramJob, + UserFile, ) from urllib.parse import urljoin import aiofiles @@ -116,23 +117,31 @@ async def download_file_async( ) # aiofiles doesnt seem to have an async variant of this return dest_file - def upload_file( + def upload_file_to_program_job( self, file: Union[str, Path], + program_key: str, + program_version: str, + job_id: str, + workspace_path: str, timeout_seconds: int = DEFAULT_TIMEOUT_SECONDS, max_concurrent_uploads: int = _MAX_CONCURRENT_UPLOADS, **kwargs, ): return asyncio.run( - self.upload_file_async( + self.upload_file_to_program_job_async( file=file, + program_key=program_key, + program_version=program_version, + job_id=job_id, + workspace_path=workspace_path, timeout_seconds=timeout_seconds, max_concurrent_uploads=max_concurrent_uploads, **kwargs, ) ) - async def upload_file_async( + async def upload_file_to_program_job_async( self, file: Union[str, Path], program_key: str, @@ -155,13 +164,8 @@ async def upload_file_async( # if a file has the same sha256 checksum # and name they are considered equal return file_result - # ClientFileInProgramJob( - # filename=file.name, - # filesize=file.stat().st_size, - # sha256_checksum=checksum, - # ) - client_file = ClientFile( - ClientFileInProgramJob( + user_file = UserFile( + ClientFileToProgramJob( filename=file.name, filesize=file.stat().st_size, sha256_checksum=checksum, @@ -171,8 +175,67 @@ async def upload_file_async( workspace_path=workspace_path, ) ) + return await self._upload_user_file( + user_file, file, timeout_seconds, max_concurrent_uploads, **kwargs + ) + + def upload_file( + self, + file: Union[str, Path], + timeout_seconds: int = DEFAULT_TIMEOUT_SECONDS, + max_concurrent_uploads: int = _MAX_CONCURRENT_UPLOADS, + **kwargs, + ) -> File: + return asyncio.run( + self.upload_file_async( + file=file, + timeout_seconds=timeout_seconds, + max_concurrent_uploads=max_concurrent_uploads, + **kwargs, + ) + ) + + async def upload_file_async( + self, + file: Union[str, Path], + timeout_seconds: int = DEFAULT_TIMEOUT_SECONDS, + max_concurrent_uploads: int = _MAX_CONCURRENT_UPLOADS, + **kwargs, + ) -> File: + if isinstance(file, str): + file = Path(file) + if not file.is_file(): + raise RuntimeError(f"{file} is not a file") + checksum: str = await compute_sha256(file) + for file_result in self._search_files( + sha256_checksum=checksum, timeout_seconds=timeout_seconds + ): + if file_result.filename == file.name: + # if a file has the same sha256 checksum + # and name they are considered equal + return file_result + user_file = UserFile( + ClientFile( + filename=file.name, + filesize=file.stat().st_size, + sha256_checksum=checksum, + ) + ) + return await self._upload_user_file( + user_file, file, timeout_seconds, max_concurrent_uploads, **kwargs + ) + + async def _upload_user_file( + self, + user_file: UserFile, + file: Path, + timeout_seconds: int = DEFAULT_TIMEOUT_SECONDS, + max_concurrent_uploads: int = _MAX_CONCURRENT_UPLOADS, + **kwargs, + ) -> File: + assert file.is_file() # nosec client_upload_schema: ClientFileUploadData = super().get_upload_links( - client_file=client_file, _request_timeout=timeout_seconds, **kwargs + user_file=user_file, _request_timeout=timeout_seconds, **kwargs ) chunk_size: int = client_upload_schema.upload_schema.chunk_size links: FileUploadData = client_upload_schema.upload_schema.links @@ -180,14 +243,12 @@ async def upload_file_async( iter(client_upload_schema.upload_schema.urls), start=1 ) n_urls: int = len(client_upload_schema.upload_schema.urls) - if n_urls < math.ceil(file.stat().st_size / chunk_size): + if n_urls < math.ceil(user_file.actual_instance.filesize / chunk_size): raise RuntimeError( "Did not receive sufficient number of upload URLs from the server." ) - abort_body = BodyAbortMultipartUploadV0FilesFileIdAbortPost( - client_file=client_file - ) + abort_body = BodyAbortMultipartUploadV0FilesFileIdAbortPost(user_file=user_file) upload_tasks: Set[asyncio.Task] = set() uploaded_parts: List[UploadedPart] = [] @@ -240,7 +301,7 @@ async def upload_file_async( server_file: File = await self._complete_multipart_upload( api_server_session, links.complete_upload, # type: ignore - client_file, + user_file, uploaded_parts, ) _logger.debug("File upload complete: %s", file.name) @@ -250,11 +311,11 @@ async def _complete_multipart_upload( self, http_client: AsyncHttpClient, complete_link: str, - client_file: ClientFile, + user_file: UserFile, uploaded_parts: List[UploadedPart], ) -> File: complete_payload = BodyCompleteMultipartUploadV0FilesFileIdCompletePost( - client_file=client_file, + user_file=user_file, uploaded_parts=FileUploadCompletionBody(parts=uploaded_parts), ) response: Response = await http_client.post( diff --git a/clients/python/src/osparc/models.py b/clients/python/src/osparc/models.py index d3fedced..62d4f0d8 100644 --- a/clients/python/src/osparc/models.py +++ b/clients/python/src/osparc/models.py @@ -13,9 +13,10 @@ BodyCompleteMultipartUploadV0FilesFileIdCompletePost as BodyCompleteMultipartUploadV0FilesFileIdCompletePost, ) from osparc_client.models.client_file import ClientFile as ClientFile -from osparc_client.models.client_file_in_program_job import ( - ClientFileInProgramJob as ClientFileInProgramJob, +from osparc_client.models.client_file_to_program_job import ( + ClientFileToProgramJob as ClientFileToProgramJob, ) +from osparc_client.models.user_file import UserFile as UserFile from osparc_client.models.client_file_upload_data import ( ClientFileUploadData as ClientFileUploadData, ) diff --git a/programs_example.py b/programs_example.py index 7175d765..7ddf934a 100644 --- a/programs_example.py +++ b/programs_example.py @@ -48,7 +48,7 @@ async def run(program_key: str, version: str) -> osparc.JobStatus: # Upload the input file tmp_file = Path(tmp_dir) / "tmpfile" tmp_file.write_text("Hi oSPARC 👋") - await files_api.upload_file_async( + await files_api.upload_file_to_program_job_async( file=tmp_file, program_key=program.id, program_version=program.version, From 8b66361f470739877d8397b4c91c0768fcdfef6d Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Tue, 1 Apr 2025 09:20:28 +0200 Subject: [PATCH 05/11] update openapi.json --- api/openapi.json | 200 +++++++++++++++++++++++------------------------ 1 file changed, 100 insertions(+), 100 deletions(-) diff --git a/api/openapi.json b/api/openapi.json index 02faee48..726c45d6 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -451,13 +451,13 @@ "schema": { "anyOf": [ { - "$ref": "#/components/schemas/ClientFileToProgramJob" + "$ref": "#/components/schemas/UserFileToProgramJob" }, { - "$ref": "#/components/schemas/ClientFile" + "$ref": "#/components/schemas/UserFile" } ], - "title": "User File" + "title": "Client File" } } } @@ -6045,36 +6045,36 @@ "schemas": { "Body_abort_multipart_upload_v0_files__file_id__abort_post": { "properties": { - "user_file": { + "client_file": { "anyOf": [ { - "$ref": "#/components/schemas/ClientFileToProgramJob" + "$ref": "#/components/schemas/UserFileToProgramJob" }, { - "$ref": "#/components/schemas/ClientFile" + "$ref": "#/components/schemas/UserFile" } ], - "title": "User File" + "title": "Client File" } }, "type": "object", "required": [ - "user_file" + "client_file" ], "title": "Body_abort_multipart_upload_v0_files__file_id__abort_post" }, "Body_complete_multipart_upload_v0_files__file_id__complete_post": { "properties": { - "user_file": { + "client_file": { "anyOf": [ { - "$ref": "#/components/schemas/ClientFileToProgramJob" + "$ref": "#/components/schemas/UserFileToProgramJob" }, { - "$ref": "#/components/schemas/ClientFile" + "$ref": "#/components/schemas/UserFile" } ], - "title": "User File" + "title": "Client File" }, "uploaded_parts": { "$ref": "#/components/schemas/FileUploadCompletionBody" @@ -6082,7 +6082,7 @@ }, "type": "object", "required": [ - "user_file", + "client_file", "uploaded_parts" ], "title": "Body_complete_multipart_upload_v0_files__file_id__complete_post" @@ -6101,93 +6101,6 @@ ], "title": "Body_upload_file_v0_files_content_put" }, - "ClientFile": { - "properties": { - "filename": { - "type": "string", - "title": "Filename", - "description": "File name" - }, - "filesize": { - "type": "integer", - "minimum": 0, - "title": "Filesize", - "description": "File size in bytes" - }, - "sha256_checksum": { - "type": "string", - "pattern": "^[a-fA-F0-9]{64}$", - "title": "Sha256 Checksum", - "description": "SHA256 checksum" - } - }, - "type": "object", - "required": [ - "filename", - "filesize", - "sha256_checksum" - ], - "title": "ClientFile", - "description": "Represents a file stored on the client side" - }, - "ClientFileToProgramJob": { - "properties": { - "filename": { - "type": "string", - "pattern": ".+", - "title": "Filename", - "description": "File name" - }, - "filesize": { - "type": "integer", - "minimum": 0, - "title": "Filesize", - "description": "File size in bytes" - }, - "sha256_checksum": { - "type": "string", - "pattern": "^[a-fA-F0-9]{64}$", - "title": "Sha256 Checksum", - "description": "SHA256 checksum" - }, - "program_key": { - "type": "string", - "pattern": "^simcore/services/dynamic/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", - "title": "Program Key", - "description": "Program identifier" - }, - "program_version": { - "type": "string", - "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-]+)*)?$", - "title": "Program Version", - "description": "Program version" - }, - "job_id": { - "type": "string", - "format": "uuid", - "title": "Job Id", - "description": "Job identifier" - }, - "workspace_path": { - "type": "string", - "pattern": "^workspace/.*", - "format": "path", - "title": "Workspace Path", - "description": "The file's relative path within the job's workspace directory. E.g. 'workspace/myfile.txt'" - } - }, - "type": "object", - "required": [ - "filename", - "filesize", - "sha256_checksum", - "program_key", - "program_version", - "job_id", - "workspace_path" - ], - "title": "ClientFileToProgramJob" - }, "ClientFileUploadData": { "properties": { "file_id": { @@ -8186,6 +8099,93 @@ ], "title": "UploadedPart" }, + "UserFile": { + "properties": { + "filename": { + "type": "string", + "title": "Filename", + "description": "File name" + }, + "filesize": { + "type": "integer", + "minimum": 0, + "title": "Filesize", + "description": "File size in bytes" + }, + "sha256_checksum": { + "type": "string", + "pattern": "^[a-fA-F0-9]{64}$", + "title": "Sha256 Checksum", + "description": "SHA256 checksum" + } + }, + "type": "object", + "required": [ + "filename", + "filesize", + "sha256_checksum" + ], + "title": "UserFile", + "description": "Represents a file stored on the client side" + }, + "UserFileToProgramJob": { + "properties": { + "filename": { + "type": "string", + "pattern": ".+", + "title": "Filename", + "description": "File name" + }, + "filesize": { + "type": "integer", + "minimum": 0, + "title": "Filesize", + "description": "File size in bytes" + }, + "sha256_checksum": { + "type": "string", + "pattern": "^[a-fA-F0-9]{64}$", + "title": "Sha256 Checksum", + "description": "SHA256 checksum" + }, + "program_key": { + "type": "string", + "pattern": "^simcore/services/dynamic/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", + "title": "Program Key", + "description": "Program identifier" + }, + "program_version": { + "type": "string", + "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-]+)*)?$", + "title": "Program Version", + "description": "Program version" + }, + "job_id": { + "type": "string", + "format": "uuid", + "title": "Job Id", + "description": "Job identifier" + }, + "workspace_path": { + "type": "string", + "pattern": "^workspace/.*", + "format": "path", + "title": "Workspace Path", + "description": "The file's relative path within the job's workspace directory. E.g. 'workspace/myfile.txt'" + } + }, + "type": "object", + "required": [ + "filename", + "filesize", + "sha256_checksum", + "program_key", + "program_version", + "job_id", + "workspace_path" + ], + "title": "UserFileToProgramJob" + }, "UserRoleEnum": { "type": "string", "enum": [ From eef3eef277b53be0339670102b28645cf3807ed7 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Tue, 1 Apr 2025 09:45:32 +0200 Subject: [PATCH 06/11] fixes --- clients/python/src/osparc/_api_files_api.py | 26 +++++++++++---------- clients/python/src/osparc/models.py | 4 ++-- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/clients/python/src/osparc/_api_files_api.py b/clients/python/src/osparc/_api_files_api.py index 4391a99b..fa7f72ef 100644 --- a/clients/python/src/osparc/_api_files_api.py +++ b/clients/python/src/osparc/_api_files_api.py @@ -25,7 +25,7 @@ FileUploadCompletionBody, FileUploadData, UploadedPart, - ClientFileToProgramJob, + UserFileToProgramJob, UserFile, ) from urllib.parse import urljoin @@ -164,8 +164,8 @@ async def upload_file_to_program_job_async( # if a file has the same sha256 checksum # and name they are considered equal return file_result - user_file = UserFile( - ClientFileToProgramJob( + user_file = ClientFile( + UserFileToProgramJob( filename=file.name, filesize=file.stat().st_size, sha256_checksum=checksum, @@ -214,8 +214,8 @@ async def upload_file_async( # if a file has the same sha256 checksum # and name they are considered equal return file_result - user_file = UserFile( - ClientFile( + user_file = ClientFile( + UserFile( filename=file.name, filesize=file.stat().st_size, sha256_checksum=checksum, @@ -227,7 +227,7 @@ async def upload_file_async( async def _upload_user_file( self, - user_file: UserFile, + client_file: ClientFile, file: Path, timeout_seconds: int = DEFAULT_TIMEOUT_SECONDS, max_concurrent_uploads: int = _MAX_CONCURRENT_UPLOADS, @@ -235,7 +235,7 @@ async def _upload_user_file( ) -> File: assert file.is_file() # nosec client_upload_schema: ClientFileUploadData = super().get_upload_links( - user_file=user_file, _request_timeout=timeout_seconds, **kwargs + client_file=client_file, _request_timeout=timeout_seconds, **kwargs ) chunk_size: int = client_upload_schema.upload_schema.chunk_size links: FileUploadData = client_upload_schema.upload_schema.links @@ -243,12 +243,14 @@ async def _upload_user_file( iter(client_upload_schema.upload_schema.urls), start=1 ) n_urls: int = len(client_upload_schema.upload_schema.urls) - if n_urls < math.ceil(user_file.actual_instance.filesize / chunk_size): + if n_urls < math.ceil(client_file.actual_instance.filesize / chunk_size): raise RuntimeError( "Did not receive sufficient number of upload URLs from the server." ) - abort_body = BodyAbortMultipartUploadV0FilesFileIdAbortPost(user_file=user_file) + abort_body = BodyAbortMultipartUploadV0FilesFileIdAbortPost( + client_file=client_file + ) upload_tasks: Set[asyncio.Task] = set() uploaded_parts: List[UploadedPart] = [] @@ -301,7 +303,7 @@ async def _upload_user_file( server_file: File = await self._complete_multipart_upload( api_server_session, links.complete_upload, # type: ignore - user_file, + client_file, uploaded_parts, ) _logger.debug("File upload complete: %s", file.name) @@ -311,11 +313,11 @@ async def _complete_multipart_upload( self, http_client: AsyncHttpClient, complete_link: str, - user_file: UserFile, + client_file: ClientFile, uploaded_parts: List[UploadedPart], ) -> File: complete_payload = BodyCompleteMultipartUploadV0FilesFileIdCompletePost( - user_file=user_file, + client_file=client_file, uploaded_parts=FileUploadCompletionBody(parts=uploaded_parts), ) response: Response = await http_client.post( diff --git a/clients/python/src/osparc/models.py b/clients/python/src/osparc/models.py index 62d4f0d8..3691da21 100644 --- a/clients/python/src/osparc/models.py +++ b/clients/python/src/osparc/models.py @@ -13,8 +13,8 @@ BodyCompleteMultipartUploadV0FilesFileIdCompletePost as BodyCompleteMultipartUploadV0FilesFileIdCompletePost, ) from osparc_client.models.client_file import ClientFile as ClientFile -from osparc_client.models.client_file_to_program_job import ( - ClientFileToProgramJob as ClientFileToProgramJob, +from osparc_client.models.user_file_to_program_job import ( + UserFileToProgramJob as UserFileToProgramJob, ) from osparc_client.models.user_file import UserFile as UserFile from osparc_client.models.client_file_upload_data import ( From d580d991f813f3105a41cf97a86f1f4fda1f94e9 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 9 Apr 2025 10:28:49 +0200 Subject: [PATCH 07/11] update openapi.json --- api/openapi.json | 44 ++++++++++++++++++++++---------------------- programs_example.py | 10 ++++------ 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/api/openapi.json b/api/openapi.json index 726c45d6..dfafb2a2 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -7704,35 +7704,33 @@ "properties": { "id": { "type": "string", - "pattern": "^simcore/services/dynamic/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", "title": "Id", - "description": "Program identifier" + "description": "Resource identifier" }, "version": { "type": "string", "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-]+)*)?$", "title": "Version", - "description": "semantic version number of the node" + "description": "Semantic version number of the resource" }, "title": { "type": "string", + "maxLength": 100, "title": "Title", "description": "Human readable name" }, "description": { "anyOf": [ { - "type": "string" + "type": "string", + "maxLength": 500 }, { "type": "null" } ], - "title": "Description" - }, - "maintainer": { - "type": "string", - "title": "Maintainer" + "title": "Description", + "description": "Description of the resource" }, "url": { "anyOf": [ @@ -7755,11 +7753,10 @@ "id", "version", "title", - "maintainer", "url" ], "title": "Program", - "description": "A released solver with a specific version", + "description": "A released program with a specific version", "example": { "description": "Simulation framework", "id": "simcore/services/dynamic/sim4life", @@ -7838,35 +7835,33 @@ "properties": { "id": { "type": "string", - "pattern": "^simcore/services/comp/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", "title": "Id", - "description": "Solver identifier" + "description": "Resource identifier" }, "version": { "type": "string", "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-]+)*)?$", "title": "Version", - "description": "semantic version number of the node" + "description": "Semantic version number of the resource" }, "title": { "type": "string", + "maxLength": 100, "title": "Title", "description": "Human readable name" }, "description": { "anyOf": [ { - "type": "string" + "type": "string", + "maxLength": 500 }, { "type": "null" } ], - "title": "Description" - }, - "maintainer": { - "type": "string", - "title": "Maintainer" + "title": "Description", + "description": "Description of the resource" }, "url": { "anyOf": [ @@ -7882,6 +7877,11 @@ ], "title": "Url", "description": "Link to get this resource" + }, + "maintainer": { + "type": "string", + "title": "Maintainer", + "description": "Maintainer of the solver" } }, "type": "object", @@ -7889,8 +7889,8 @@ "id", "version", "title", - "maintainer", - "url" + "url", + "maintainer" ], "title": "Solver", "description": "A released solver with a specific version", diff --git a/programs_example.py b/programs_example.py index 7ddf934a..d9e9e175 100644 --- a/programs_example.py +++ b/programs_example.py @@ -18,19 +18,17 @@ def osparc_client(): config = osparc.Configuration( host=user.host, username=user.key, password=user.secret ) - print(config.host) - print(config.username) - print(config.password) return osparc.ApiClient(config) def check_credentials(): with osparc_client() as api_client: user_api = osparc.UsersApi(api_client) - print(user_api.get_my_profile()) + profile = user_api.get_my_profile() + print(profile.model_dump_json(indent=2)) -async def run(program_key: str, version: str) -> osparc.JobStatus: +async def run(program_key: str, version: str): with osparc_client() as api_client: files_api = osparc.FilesApi(api_client) programs_api = osparc.ProgramsApi(api_client) @@ -42,7 +40,7 @@ async def run(program_key: str, version: str) -> osparc.JobStatus: program_job = programs_api.create_program_job( program_key=program.id, version=program.version ) - print(f"{program_job=}") + print(program_job.model_dump_json(indent=2)) with TemporaryDirectory() as tmp_dir: # Upload the input file From 073b8d52d634ab8c9ca127bade55dcdb6182a6c7 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Thu, 17 Apr 2025 07:50:55 +0200 Subject: [PATCH 08/11] update openapi.json to latest osparc-simcore/master version --- api/openapi.json | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/api/openapi.json b/api/openapi.json index dfafb2a2..78309bd3 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -1256,37 +1256,6 @@ } } }, - "/v0/programs": { - "get": { - "tags": [ - "programs" - ], - "summary": "List Programs", - "description": "Lists all available solvers (latest version)\n\nSEE get_solvers_page for paginated version of this function", - "operationId": "list_programs", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/Program" - }, - "type": "array", - "title": "Response List Programs V0 Programs Get" - } - } - } - } - }, - "security": [ - { - "HTTPBasic": [] - } - ] - } - }, "/v0/programs/{program_key}/releases/{version}": { "get": { "tags": [ From 9a231b1ab5de342106f0b16efb6bdde2831ae5af Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Thu, 17 Apr 2025 07:52:31 +0200 Subject: [PATCH 09/11] use v4 upload-artifact workflow --- .github/workflows/build-python-client.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-python-client.yml b/.github/workflows/build-python-client.yml index 31385626..cbec533f 100644 --- a/.github/workflows/build-python-client.yml +++ b/.github/workflows/build-python-client.yml @@ -52,7 +52,7 @@ jobs: echo "github.workflow.id: ${{github.workflow.id}}" echo "github.run_id: ${{github.run_id}}" - name: Upload wheels - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: osparc_python_wheels path: clients/python/artifacts/dist/ From 0031d26d7b83becd97562976694d3bce3ff3fe08 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Thu, 17 Apr 2025 07:54:26 +0200 Subject: [PATCH 10/11] upgrade download-artifact workflow --- .github/workflows/build-python-client.yml | 4 ++-- .github/workflows/publish-python-client.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-python-client.yml b/.github/workflows/build-python-client.yml index cbec533f..f41fd875 100644 --- a/.github/workflows/build-python-client.yml +++ b/.github/workflows/build-python-client.yml @@ -83,7 +83,7 @@ jobs: restore-keys: | ${{ runner.os }}-pip - name: Download wheels - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: osparc_python_wheels path: clients/python/artifacts/dist/ @@ -122,7 +122,7 @@ jobs: restore-keys: | ${{ runner.os }}-pip - name: Download wheels - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: osparc_python_wheels path: clients/python/artifacts/dist/ diff --git a/.github/workflows/publish-python-client.yml b/.github/workflows/publish-python-client.yml index 7cf25768..8cc492ff 100644 --- a/.github/workflows/publish-python-client.yml +++ b/.github/workflows/publish-python-client.yml @@ -25,7 +25,7 @@ jobs: run: | make devenv - name: Download wheels - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: osparc_python_wheels path: osparc_python_wheels/ From 12901d69e667e45fac4cc33a62799f5515107216 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Thu, 17 Apr 2025 09:28:59 +0200 Subject: [PATCH 11/11] fix tests --- clients/python/src/osparc/_api_solvers_api.py | 2 +- clients/python/test/test_osparc/test_apis.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/python/src/osparc/_api_solvers_api.py b/clients/python/src/osparc/_api_solvers_api.py index 3c9ca36d..0ffcfad0 100644 --- a/clients/python/src/osparc/_api_solvers_api.py +++ b/clients/python/src/osparc/_api_solvers_api.py @@ -108,7 +108,7 @@ def create_job( _job_inputs = _JobInputs.from_json(job_inputs.model_dump_json()) assert _job_inputs is not None kwargs = {**kwargs, **ParentProjectInfo().model_dump(exclude_none=True)} - return super().create_job(solver_key, version, _job_inputs, **kwargs) + return super().create_solver_job(solver_key, version, _job_inputs, **kwargs) def get_job_output_logfile( self, diff --git a/clients/python/test/test_osparc/test_apis.py b/clients/python/test/test_osparc/test_apis.py index ba43906a..138f8f81 100644 --- a/clients/python/test/test_osparc/test_apis.py +++ b/clients/python/test/test_osparc/test_apis.py @@ -44,7 +44,7 @@ def check_headers(**kwargs): ) mocker.patch( - "osparc_client.SolversApi.create_job", + "osparc_client.SolversApi.create_solver_job", side_effect=lambda solver_key, version, job_inpus, **kwargs: check_headers( **kwargs ),