diff --git a/.github/workflows/build-python-client.yml b/.github/workflows/build-python-client.yml index 31385626..f41fd875 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/ @@ -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/ diff --git a/api/openapi.json b/api/openapi.json index d70ef50e..78309bd3 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/UserFileToProgramJob" + }, + { + "$ref": "#/components/schemas/UserFile" + } + ], + "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,158 @@ } } }, + "/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 +2174,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 +3512,9 @@ "required": false, "schema": { "type": "integer", - "maximum": 100, + "maximum": 50, "minimum": 1, - "default": 50, + "default": 20, "title": "Limit" } }, @@ -4164,9 +4324,9 @@ "required": false, "schema": { "type": "integer", - "maximum": 100, + "maximum": 50, "minimum": 1, - "default": 50, + "default": 20, "title": "Limit" } }, @@ -5322,106 +5482,593 @@ } } }, - "/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": [ - "file" - ], - "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" + "404": { + "description": "Wallet not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorGet" + } + } + } }, - "sha256_checksum": { - "type": "string", - "pattern": "^[a-fA-F0-9]{64}$", - "title": "Sha256 Checksum", - "description": "SHA256 checksum" + "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": { + "anyOf": [ + { + "$ref": "#/components/schemas/UserFileToProgramJob" + }, + { + "$ref": "#/components/schemas/UserFile" + } + ], + "title": "Client File" } }, "type": "object", "required": [ - "filename", - "filesize", - "sha256_checksum" + "client_file" ], - "title": "ClientFile", - "description": "Represents a file stored on the client side" + "title": "Body_abort_multipart_upload_v0_files__file_id__abort_post" + }, + "Body_complete_multipart_upload_v0_files__file_id__complete_post": { + "properties": { + "client_file": { + "anyOf": [ + { + "$ref": "#/components/schemas/UserFileToProgramJob" + }, + { + "$ref": "#/components/schemas/UserFile" + } + ], + "title": "Client File" + }, + "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" }, "ClientFileUploadData": { "properties": { @@ -6044,47 +6691,376 @@ "title": "Submitted At", "description": "Last modification timestamp of the solver job" }, - "started_at": { + "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": "LicensedItemGet" + }, + "LicensedResource": { + "properties": { + "source": { + "$ref": "#/components/schemas/LicensedResourceSource" + }, + "category_id": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "title": "Category Id" + }, + "category_display": { + "type": "string", + "title": "Category Display" + }, + "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", + "title": "Id" + }, + "description": { + "type": "string", + "title": "Description" + }, + "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 +7385,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 +7524,7 @@ "title": "Unitname" }, "unitExtraInfo": { - "$ref": "#/components/schemas/UnitExtraInfo" + "$ref": "#/components/schemas/UnitExtraInfoTier" }, "currentCostPerUnit": { "type": "number", @@ -6634,6 +7669,72 @@ "type": "object", "title": "ProfileUpdate" }, + "Program": { + "properties": { + "id": { + "type": "string", + "title": "Id", + "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 resource" + }, + "title": { + "type": "string", + "maxLength": 100, + "title": "Title", + "description": "Human readable name" + }, + "description": { + "anyOf": [ + { + "type": "string", + "maxLength": 500 + }, + { + "type": "null" + } + ], + "title": "Description", + "description": "Description of the resource" + }, + "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", + "url" + ], + "title": "Program", + "description": "A released program 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": [ @@ -6703,35 +7804,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": [ @@ -6747,6 +7846,11 @@ ], "title": "Url", "description": "Link to get this resource" + }, + "maintainer": { + "type": "string", + "title": "Maintainer", + "description": "Maintainer of the solver" } }, "type": "object", @@ -6754,8 +7858,8 @@ "id", "version", "title", - "maintainer", - "url" + "url", + "maintainer" ], "title": "Solver", "description": "A released solver with a specific version", @@ -6898,7 +8002,7 @@ "kind": "input" } }, - "UnitExtraInfo": { + "UnitExtraInfoTier": { "properties": { "CPU": { "type": "integer", @@ -6923,7 +8027,7 @@ "RAM", "VRAM" ], - "title": "UnitExtraInfo", + "title": "UnitExtraInfoTier", "description": "Custom information that is propagated to the frontend. Defined fields are mandatory." }, "UploadLinks": { @@ -6964,6 +8068,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": [ diff --git a/clients/python/src/osparc/_api_files_api.py b/clients/python/src/osparc/_api_files_api.py index 8931cd5a..fa7f72ef 100644 --- a/clients/python/src/osparc/_api_files_api.py +++ b/clients/python/src/osparc/_api_files_api.py @@ -25,6 +25,8 @@ FileUploadCompletionBody, FileUploadData, UploadedPart, + UserFileToProgramJob, + UserFile, ) from urllib.parse import urljoin import aiofiles @@ -115,13 +117,75 @@ 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_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_to_program_job_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, + ) -> 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 = ClientFile( + UserFileToProgramJob( + 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, + ) + ) + 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, @@ -150,11 +214,26 @@ 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, + user_file = ClientFile( + UserFile( + 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, + client_file: ClientFile, + 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 ) @@ -164,7 +243,7 @@ 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(client_file.actual_instance.filesize / chunk_size): raise RuntimeError( "Did not receive sufficient number of upload URLs from the server." ) 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/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..3691da21 100644 --- a/clients/python/src/osparc/models.py +++ b/clients/python/src/osparc/models.py @@ -13,6 +13,10 @@ BodyCompleteMultipartUploadV0FilesFileIdCompletePost as BodyCompleteMultipartUploadV0FilesFileIdCompletePost, ) from osparc_client.models.client_file import ClientFile as ClientFile +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 ( ClientFileUploadData as ClientFileUploadData, ) 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 ), diff --git a/programs_example.py b/programs_example.py new file mode 100644 index 00000000..d9e9e175 --- /dev/null +++ b/programs_example.py @@ -0,0 +1,62 @@ +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 + ) + return osparc.ApiClient(config) + + +def check_credentials(): + with osparc_client() as api_client: + user_api = osparc.UsersApi(api_client) + profile = user_api.get_my_profile() + print(profile.model_dump_json(indent=2)) + + +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) + # 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 + ) + print(program_job.model_dump_json(indent=2)) + + with TemporaryDirectory() as tmp_dir: + # Upload the input file + tmp_file = Path(tmp_dir) / "tmpfile" + tmp_file.write_text("Hi oSPARC 👋") + await files_api.upload_file_to_program_job_async( + file=tmp_file, + program_key=program.id, + program_version=program.version, + job_id=program_job.id, + workspace_path="workspace/tmpdirectory/tmpfile", + ) + + +if __name__ == "__main__": + check_credentials() + asyncio.run( + run(program_key="simcore/services/dynamic/jupyter-math", version="3.0.2") + )