From 35f11dfc84076d12a75fe7292a07726eb918f6c9 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard <126242332+bisgaard-itis@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:22:36 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20ensure=20backwards=20compatibili?= =?UTF-8?q?ty=20of=20api=20server=20(#6866)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci-testing-deploy.yml | 77 ++ Makefile | 20 +- api/specs/director/openapi.yaml | 492 -------- api/specs/web-server/_projects_states.py | 18 +- api/specs/web-server/openapi.py | 3 + ci/github/helpers/openapi-specs-diff.bash | 47 + services/api-server/openapi.json | 40 +- .../api/routes/credits.py | 4 +- .../api/routes/solvers.py | 4 +- .../api/routes/solvers_jobs_getters.py | 12 +- .../api/routes/wallets.py | 6 +- .../models/schemas/model_adapter.py | 123 ++ .../services/webserver.py | 34 +- .../tests/unit/_with_db/test_product.py | 8 +- .../tests/unit/test_api_solver_jobs.py | 12 +- .../api-server/tests/unit/test_api_wallets.py | 10 +- services/catalog/openapi.json | 16 +- services/director-v2/openapi.json | 37 +- .../src/simcore_service_director/api/Makefile | 6 - services/dynamic-scheduler/openapi.json | 32 +- services/payments/gateway/openapi.json | 1039 ----------------- services/resource-usage-tracker/openapi.json | 2 +- .../api/v0/openapi.yaml | 193 +-- 23 files changed, 452 insertions(+), 1783 deletions(-) delete mode 100644 api/specs/director/openapi.yaml create mode 100755 ci/github/helpers/openapi-specs-diff.bash create mode 100644 services/api-server/src/simcore_service_api_server/models/schemas/model_adapter.py delete mode 100644 services/payments/gateway/openapi.json diff --git a/.github/workflows/ci-testing-deploy.yml b/.github/workflows/ci-testing-deploy.yml index 516a401f9e3..63623a2c521 100644 --- a/.github/workflows/ci-testing-deploy.yml +++ b/.github/workflows/ci-testing-deploy.yml @@ -2655,6 +2655,8 @@ jobs: system-test-environment-setup, system-test-public-api, system-test-swarm-deploy, + system-api-specs, + system-backwards-compatibility ] runs-on: ubuntu-latest steps: @@ -2719,3 +2721,78 @@ jobs: env: TAG_PREFIX: hotfix-staging-github run: ./ci/deploy/dockerhub-deploy.bash -n + + system-api-specs: + needs: [changes] + if: ${{ needs.changes.outputs.anything-py == 'true' || github.event_name == 'push' }} + timeout-minutes: 10 + name: "[sys] check api-specs are up to date" + runs-on: ubuntu-latest + steps: + - name: setup python environment + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: install uv + uses: astral-sh/setup-uv@v4 + with: + version: "0.4.x" + enable-cache: false + - name: checkout source branch + uses: actions/checkout@v4 + - name: Regenerate specs and check + run: | + uv venv .venv && source .venv/bin/activate + make openapi-specs + ./ci/github/helpers/openapi-specs-diff.bash diff \ + https://raw.githubusercontent.com/${{ github.event.pull_request.head.repo.full_name }}/refs/heads/${{ github.event.pull_request.head.ref }} \ + . + + system-backwards-compatibility: + needs: [changes, system-api-specs] + if: ${{ needs.changes.outputs.anything-py == 'true' || github.event_name == 'push' }} + timeout-minutes: 10 + name: "[sys] api-server backwards compatibility" + runs-on: ubuntu-latest + steps: + - name: setup python environment + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: install uv + uses: astral-sh/setup-uv@v4 + with: + version: "0.4.x" + enable-cache: false + - name: checkout + uses: actions/checkout@v4 + - name: check api-server backwards compatibility + run: | + ./scripts/openapi-diff.bash breaking --fail-on ERR\ + https://raw.githubusercontent.com/${{ github.event.pull_request.base.repo.full_name }}/refs/heads/${{ github.event.pull_request.base.ref }}/services/api-server/openapi.json \ + /specs/services/api-server/openapi.json + + api-spec-backwards-compatibility: + needs: [changes, system-api-specs] + if: ${{ needs.changes.outputs.anything-py == 'true' || github.event_name == 'push' }} + continue-on-error: true + timeout-minutes: 10 + name: "api-specs-backwards-compatibility" + runs-on: ubuntu-latest + steps: + - name: setup python environment + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: install uv + uses: astral-sh/setup-uv@v4 + with: + version: "0.4.x" + enable-cache: false + - name: checkout + uses: actions/checkout@v4 + - name: Check openapi-specs backwards compatibility + run: | + ./ci/github/helpers/openapi-specs-diff.bash breaking \ + https://raw.githubusercontent.com/${{ github.event.pull_request.base.repo.full_name }}/refs/heads/${{ github.event.pull_request.base.ref }} \ + . diff --git a/Makefile b/Makefile index 564e353ee58..1ecccd09739 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ .DEFAULT_GOAL := help SHELL := /bin/bash - +.SHELLFLAGS := -o errexit -o pipefail -c MAKE_C := $(MAKE) --no-print-directory --directory # Operating system @@ -84,7 +84,7 @@ export SWARM_STACK_NAME_NO_HYPHEN = $(subst -,_,$(SWARM_STACK_NAME)) export DOCKER_IMAGE_TAG ?= latest export DOCKER_REGISTRY ?= itisfoundation - +MAKEFILES_WITH_OPENAPI_SPECS := $(shell find . -mindepth 2 -type f -name 'Makefile' -not -path '*/.*' -exec grep -l '^openapi-specs:' {} \; | xargs realpath) get_my_ip := $(shell (hostname --all-ip-addresses || hostname -i) 2>/dev/null | cut --delimiter=" " --fields=1) @@ -131,6 +131,12 @@ test_python_version: ## Check Python version, throw error if compilation would f @.venv/bin/python ./scripts/test_python_version.py +.PHONY: _check_venv_active +_check_venv_active: + # Checking whether virtual environment was activated + @python3 -c "import sys; assert sys.base_prefix!=sys.prefix" + + ## DOCKER BUILD ------------------------------- # # - all builds are immediatly tagged as 'local/{service}:${BUILD_TARGET}' where BUILD_TARGET='development', 'production', 'cache' @@ -573,9 +579,13 @@ new-service: .venv ## Bakes a new project from cookiecutter-simcore-pyservice an .PHONY: openapi-specs -openapi-specs: ## bundles and validates openapi specifications and schemas of ALL service's API - @$(MAKE_C) services/web/server $@ - @$(MAKE_C) services/storage $@ +openapi-specs: .env _check_venv_active ## generates and validates openapi specifications and schemas of ALL service's API + @for makefile in $(MAKEFILES_WITH_OPENAPI_SPECS); do \ + echo "Generating openapi-specs using $${makefile}"; \ + $(MAKE_C) $$(dirname $${makefile}) install-dev; \ + $(MAKE_C) $$(dirname $${makefile}) $@; \ + printf "%0.s=" {1..100} && printf "\n"; \ + done .PHONY: settings-schema.json diff --git a/api/specs/director/openapi.yaml b/api/specs/director/openapi.yaml deleted file mode 100644 index fe95950536e..00000000000 --- a/api/specs/director/openapi.yaml +++ /dev/null @@ -1,492 +0,0 @@ -openapi: "3.0.0" -info: - description: This is the oSparc's director API - version: 0.1.0 - title: Director API - contact: - name: IT'IS Foundation - email: support@simcore.com - license: - name: MIT - url: https://github.com/ITISFoundation/osparc-simcore/blob/master/LICENSE - -servers: - - description: Development server - url: http://{host}:{port}/{version} - variables: - host: - default: "localhost" - port: - default: "8080" - version: - default: "v0" - enum: - - "v0" - - description: Production server - url: http://director:{port}/{version} - variables: - port: - default: "8080" - version: - default: "v0" - enum: - - "v0" - -# tags are used for organizing operations -tags: - - name: admins - description: Secured Admin-only calls - - name: developers - description: Operations available to regular developers - - name: users - description: Operations available to regular users - -paths: - /: - get: - tags: - - users - summary: Service health-check endpoint - description: Some general information on the API and state of the service behind - operationId: root_get - responses: - "200": - description: Service information - content: - application/json: - schema: - $ref: "#/components/schemas/HealthCheckEnveloped" - default: - description: Unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - - /services: - get: - tags: - - users - summary: Lists available services in the oSparc platform - description: Lists available services in the oSparc platform - operationId: services_get - parameters: - - $ref: "#/components/parameters/ServiceType" - responses: - "200": - description: Success, returns the list of available services - content: - application/json: - schema: - $ref: "#/components/schemas/ServicesEnveloped" - "401": - description: Unauthorized access - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - default: - description: Unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - - /services/{service_key}/{service_version}: - get: - tags: - - users - summary: Returns details of the selected service if available in the oSparc platform - description: Returns details of the selected service if available in the oSparc platform - operationId: services_by_key_version_get - parameters: - - $ref: "#/components/parameters/ServiceKeyPath" - - $ref: "#/components/parameters/ServiceVersionPath" - responses: - "200": - description: Success, returns the details of the service - content: - application/json: - schema: - $ref: "#/components/schemas/ServicesEnveloped" - "401": - description: Unauthorized access - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - "404": - description: Service not found - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - default: - description: Unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - - /services/{service_key}/{service_version}/labels: - get: - tags: - - users - summary: Returns the list of tags attached to a service - operationId: get_service_labels - parameters: - - $ref: "#/components/parameters/ServiceKeyPath" - - $ref: "#/components/parameters/ServiceVersionPath" - responses: - "200": - description: Success, returns the details of the service - content: - application/json: - schema: - type: object - additionalProperties: - type: string - "401": - description: Unauthorized access - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - "404": - description: Service not found - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - default: - description: Unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - - /service_extras/{service_key}/{service_version}: - get: - tags: - - users - summary: Returns the service's details which should be hidden from the user defined as extras. - description: Currently returns the node_requirements an array of resoruces needed for scheduling. - operationId: service_extras_by_key_version_get - parameters: - - $ref: "#/components/parameters/ServiceKeyPath" - - $ref: "#/components/parameters/ServiceVersionPath" - responses: - "200": - description: Success, returns an object containing details hidden from the user - content: - application/json: - schema: - $ref: "#/components/schemas/ServiceExtrasEnveloped" - "401": - description: Unauthorized access - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - "404": - description: Service not found - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - default: - description: Unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - - /running_interactive_services: - get: - tags: - - users - summary: Returns a list of interactive services - operationId: running_interactive_services_list_get - parameters: - - in: query - name: user_id - required: false - schema: - type: string - - in: query - name: project_id - required: false - schema: - type: string - responses: - "200": - description: Returns the running services instances - content: - application/json: - schema: - $ref: "#/components/schemas/RunningServicesEnveloped" - default: - description: Unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - post: - tags: - - users - summary: Starts an interactive service in the oSparc platform - operationId: running_interactive_services_post - parameters: - - $ref: "#/components/parameters/UserId" - - $ref: "#/components/parameters/ProjectId" - - $ref: "#/components/parameters/ServiceKey" - - $ref: "#/components/parameters/ServiceVersion" - - $ref: "#/components/parameters/AssignmentUuid" - - $ref: "#/components/parameters/ServiceBasePath" - responses: - "201": - description: Succesfully created the service in the oSparc platform. Returns the location where the service runs. - content: - application/json: - schema: - $ref: "#/components/schemas/RunningServiceEnveloped" - "400": - description: Malformed function call, missing field - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - "401": - description: Unauthorized access - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - "404": - description: Service not found - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - "409": - description: A service with the same uuid already exists - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - default: - description: Unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - - /running_interactive_services/{service_uuid}: - get: - tags: - - users - summary: Succesfully returns if a service with the defined uuid is up and running - description: Succesfully returns if a service with the defined uuid is up and running - operationId: running_interactive_services_get - parameters: - - $ref: "#/components/parameters/ServiceUuid" - responses: - "200": - description: OK service exists and runs. Returns service location. - content: - application/json: - schema: - $ref: "#/components/schemas/RunningServiceEnveloped" - "400": - description: Malformed function call, missing field - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - "404": - description: Service not found - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - default: - description: Unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - delete: - tags: - - users - summary: Stops and removes an interactive service from the oSparc platform - description: Stops and removes an interactive service from the oSparc platform - operationId: running_interactive_services_delete - parameters: - - $ref: "#/components/parameters/ServiceUuid" - - $ref: "#/components/parameters/SaveState" - responses: - "204": - description: Succesfully stopped and removed the service from the oSparc platform - "400": - description: Malformed function call, missing field - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - "404": - description: Service not found - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - default: - description: Unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnveloped" - -components: - parameters: - UserId: - in: query - name: user_id - description: The ID of the user that starts the service - required: true - schema: - type: string - example: asdfgj233 - ProjectId: - in: query - name: project_id - description: The ID of the project in which the service starts - required: true - schema: - type: string - example: asdfgj233 - AssignmentUuid: - in: query - name: service_uuid - description: The uuid to assign the service with - required: true - schema: - type: string - # format: uuid - example: 123e4567-e89b-12d3-a456-426655440000 - - ServiceKeyPath: - in: path - name: service_key - description: The key (url) of the service - required: true - schema: - type: string - description: distinctive name for the node based on the docker registry path - pattern: '^(simcore)/(services)/(comp|dynamic)(/[\w/-]+)+$' - example: - - simcore/services/comp/itis/sleeper - - simcore/services/dynamic/3dviewer - - ServiceKey: - in: query - name: service_key - description: The key (url) of the service - required: true - schema: - type: string - description: distinctive name for the node based on the docker registry path - pattern: '^(simcore)/(services)/(comp|dynamic)(/[\w/-]+)+$' - example: - - simcore/services/comp/itis/sleeper - - simcore/services/dynamic/3dviewer - - ServiceType: - in: query - name: service_type - description: | - The service type: - * computational - a computational service - * interactive - an interactive service - required: false - schema: - type: string - enum: - - computational - - interactive - example: computational - - ServiceBasePath: - in: query - name: service_basepath - description: predefined basepath for the backend service otherwise uses root - required: false - schema: - type: string - example: "/x/EycCXbU0H/" - default: "" - - ServiceUuid: - in: path - name: service_uuid - description: The uuid of the service - required: true - schema: - type: string - # format: uuid - example: 123e4567-e89b-12d3-a456-426655440000 - - ServiceVersionPath: - in: path - name: service_version - description: The tag/version of the service - required: true - schema: - type: string - description: semantic version number - pattern: >- - ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ - example: - - 1.0.0 - - 0.0.1 - - ServiceVersion: - in: query - name: service_tag - description: The tag/version of the service - required: false - schema: - type: string - description: semantic version number - pattern: >- - ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ - example: - - 1.0.0 - - 0.0.1 - - SaveState: - in: query - name: save_state - description: Save the state prior to removing the service - required: false - schema: - type: boolean - default: true - - schemas: - ErrorEnveloped: - $ref: "./schemas/error.yaml#/components/schemas/ErrorEnveloped" - - RunningServiceEnveloped: - $ref: "./schemas/running_service.yaml#/components/schemas/RunningServiceEnveloped" - - RunningServicesEnveloped: - $ref: "./schemas/running_service.yaml#/components/schemas/RunningServicesEnveloped" - - ServicesEnveloped: - $ref: "./schemas/services.yaml#/components/schemas/ServicesEnveloped" - - ServiceExtrasEnveloped: - $ref: "./schemas/services.yaml#/components/schemas/ServiceExtrasEnveloped" - - HealthCheckEnveloped: - $ref: "./schemas/health_check.yaml#/components/schemas/HealthCheckEnveloped" diff --git a/api/specs/web-server/_projects_states.py b/api/specs/web-server/_projects_states.py index 0c737062285..1547e8f3b35 100644 --- a/api/specs/web-server/_projects_states.py +++ b/api/specs/web-server/_projects_states.py @@ -34,8 +34,8 @@ ) -def to_desc(exceptions: set[type[Exception]] | type[Exception]): - exc_classes = {exceptions} if not isinstance(exceptions, set) else exceptions +def to_desc(exceptions: list[type[Exception]] | type[Exception]): + exc_classes = [exceptions] if not isinstance(exceptions, list) else exceptions return ", ".join(f"{cls.__name__}" for cls in exc_classes) @@ -43,26 +43,26 @@ def to_desc(exceptions: set[type[Exception]] | type[Exception]): "/projects/{project_id}:open", response_model=Envelope[ProjectGet], responses={ - status.HTTP_400_BAD_REQUEST: {"description": to_desc({ValidationError})}, + status.HTTP_400_BAD_REQUEST: {"description": to_desc([ValidationError])}, status.HTTP_402_PAYMENT_REQUIRED: { - "description": to_desc({WalletNotEnoughCreditsError}) + "description": to_desc([WalletNotEnoughCreditsError]) }, status.HTTP_403_FORBIDDEN: { - "description": to_desc({ProjectInvalidRightsError}) + "description": to_desc([ProjectInvalidRightsError]) }, status.HTTP_404_NOT_FOUND: { "description": to_desc( - {ProjectNotFoundError, UserDefaultWalletNotFoundError} + [ProjectNotFoundError, UserDefaultWalletNotFoundError] ) }, status.HTTP_409_CONFLICT: { - "description": to_desc({ProjectTooManyProjectOpenedError}), + "description": to_desc([ProjectTooManyProjectOpenedError]), }, status.HTTP_422_UNPROCESSABLE_ENTITY: { - "description": to_desc({ValidationError}) + "description": to_desc([ValidationError]) }, status.HTTP_503_SERVICE_UNAVAILABLE: { - "description": to_desc({DirectorServiceError}) + "description": to_desc([DirectorServiceError]) }, }, ) diff --git a/api/specs/web-server/openapi.py b/api/specs/web-server/openapi.py index 5a679b75713..77e656efdaa 100644 --- a/api/specs/web-server/openapi.py +++ b/api/specs/web-server/openapi.py @@ -98,6 +98,9 @@ def main(): # .yaml oas_path = webserver_resources.get_path("api/v0/openapi.yaml").resolve() + if not oas_path.exists(): + oas_path.parent.mkdir(parents=True) + oas_path.write_text("") print(f"Writing {oas_path}...", end=None) with oas_path.open("wt") as fh: yaml.safe_dump(openapi, stream=fh, sort_keys=False) diff --git a/ci/github/helpers/openapi-specs-diff.bash b/ci/github/helpers/openapi-specs-diff.bash new file mode 100755 index 00000000000..b4409c174a8 --- /dev/null +++ b/ci/github/helpers/openapi-specs-diff.bash @@ -0,0 +1,47 @@ +#!/bin/bash + +# Recursively checks if all openapi specs within a local osparc-simcore revision are different/backwards compatible with a remote base +# Example: +# bash osparc-simcore/ci/github/helpers/openapi-specs-diff.bash diff \ +# https://raw.githubusercontent.com/ITISFoundation/osparc-simcore/refs/heads/master \ +# ./osparc-simcore/ +# or +# bash osparc-simcore/ci/github/helpers/openapi-specs-diff.bash breaking \ +# https://raw.githubusercontent.com/ITISFoundation/osparc-simcore/refs/heads/master \ +# ./osparc-simcore/ +# +# The script generates github error annotations for better being able to locate issues. + +operation=$1 +base_remote=$2 +revision_local=$3 + +repo_base_dir=$(realpath "$(dirname "${BASH_SOURCE[0]}")/../../..") +openapi_specs=$(find "${revision_local}" -type f \( -name 'openapi.json' -o -name 'openapi.yaml' \) -not -path '*/.*' -exec realpath --relative-to="${revision_local}" {} \;) + +cd "${revision_local}" || exit 1 # required to mount correct dir for diff tool + + +function run_diff_tool() { + exit_status=0 + for spec in ${openapi_specs}; do + echo "Comparing ${spec}" + if ! "${repo_base_dir}/scripts/openapi-diff.bash" "$@" "${base_remote}/${spec}" "/specs/${spec}"; then + echo "::error file=${spec}:: Error when checking ${spec}" + exit_status=$(("${exit_status}" + "1")) + fi + printf "%0.s=" {1..100} && printf "\n" + done + + exit "${exit_status}" +} + + +if [[ "${operation}" == "diff" ]]; then + run_diff_tool "diff" "--fail-on-diff" +elif [[ "${operation}" == "breaking" ]]; then + run_diff_tool "breaking" "--fail-on" "ERR" +else + echo "the operation '${operation}' is not supported" + exit 1 +fi diff --git a/services/api-server/openapi.json b/services/api-server/openapi.json index 883b7364473..e04d4b41d32 100644 --- a/services/api-server/openapi.json +++ b/services/api-server/openapi.json @@ -1931,7 +1931,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ServicePricingPlanGet" + "$ref": "#/components/schemas/ServicePricingPlanGetLegacy" } } } @@ -2650,6 +2650,7 @@ "type": "null" } ], + "deprecated": true, "title": "Cluster Id" }, "deprecated": true @@ -3789,7 +3790,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/WalletGetWithAvailableCredits" + "$ref": "#/components/schemas/WalletGetWithAvailableCreditsLegacy" } } } @@ -3928,7 +3929,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PricingUnitGet" + "$ref": "#/components/schemas/PricingUnitGetLegacy" } } } @@ -4628,6 +4629,7 @@ "type": "null" } ], + "deprecated": true, "title": "Cluster Id" }, "deprecated": true @@ -5119,7 +5121,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/WalletGetWithAvailableCredits" + "$ref": "#/components/schemas/WalletGetWithAvailableCreditsLegacy" } } } @@ -5232,7 +5234,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/WalletGetWithAvailableCredits" + "$ref": "#/components/schemas/WalletGetWithAvailableCreditsLegacy" } } } @@ -5334,7 +5336,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetCreditPrice" + "$ref": "#/components/schemas/GetCreditPriceLegacy" } } } @@ -5563,7 +5565,7 @@ ], "title": "FileUploadData" }, - "GetCreditPrice": { + "GetCreditPriceLegacy": { "properties": { "productName": { "type": "string", @@ -5602,7 +5604,7 @@ "usdPerCredit", "minPaymentAmountUsd" ], - "title": "GetCreditPrice" + "title": "GetCreditPriceLegacy" }, "Groups": { "properties": { @@ -6471,7 +6473,7 @@ ], "title": "PricingPlanClassification" }, - "PricingUnitGet": { + "PricingUnitGetLegacy": { "properties": { "pricingUnitId": { "type": "integer", @@ -6487,7 +6489,8 @@ "$ref": "#/components/schemas/UnitExtraInfo" }, "currentCostPerUnit": { - "type": "string", + "type": "number", + "minimum": 0.0, "title": "Currentcostperunit" }, "default": { @@ -6503,7 +6506,7 @@ "currentCostPerUnit", "default" ], - "title": "PricingUnitGet" + "title": "PricingUnitGetLegacy" }, "Profile": { "properties": { @@ -6645,7 +6648,7 @@ "title": "RunningState", "description": "State of execution of a project's computational workflow\n\nSEE StateType for task state" }, - "ServicePricingPlanGet": { + "ServicePricingPlanGetLegacy": { "properties": { "pricingPlanId": { "type": "integer", @@ -6675,7 +6678,7 @@ }, "pricingUnits": { "items": { - "$ref": "#/components/schemas/PricingUnitGet" + "$ref": "#/components/schemas/PricingUnitGetLegacy" }, "type": "array", "title": "Pricingunits" @@ -6691,7 +6694,7 @@ "pricingPlanKey", "pricingUnits" ], - "title": "ServicePricingPlanGet" + "title": "ServicePricingPlanGetLegacy" }, "Solver": { "properties": { @@ -7032,7 +7035,7 @@ ], "title": "ValidationError" }, - "WalletGetWithAvailableCredits": { + "WalletGetWithAvailableCreditsLegacy": { "properties": { "walletId": { "type": "integer", @@ -7088,7 +7091,8 @@ "title": "Modified" }, "availableCredits": { - "type": "string", + "type": "number", + "minimum": 0.0, "title": "Availablecredits" } }, @@ -7096,15 +7100,13 @@ "required": [ "walletId", "name", - "description", "owner", - "thumbnail", "status", "created", "modified", "availableCredits" ], - "title": "WalletGetWithAvailableCredits" + "title": "WalletGetWithAvailableCreditsLegacy" }, "WalletStatus": { "type": "string", diff --git a/services/api-server/src/simcore_service_api_server/api/routes/credits.py b/services/api-server/src/simcore_service_api_server/api/routes/credits.py index 5370e6dd72d..5b5258cfb01 100644 --- a/services/api-server/src/simcore_service_api_server/api/routes/credits.py +++ b/services/api-server/src/simcore_service_api_server/api/routes/credits.py @@ -1,8 +1,8 @@ from typing import Annotated from fastapi import APIRouter, Depends, status -from models_library.api_schemas_webserver.product import GetCreditPrice +from ...models.schemas.model_adapter import GetCreditPriceLegacy from ..dependencies.webserver import AuthSession, get_webserver_session from ._constants import FMSG_CHANGELOG_NEW_IN_VERSION @@ -12,7 +12,7 @@ @router.get( "/price", status_code=status.HTTP_200_OK, - response_model=GetCreditPrice, + response_model=GetCreditPriceLegacy, description=FMSG_CHANGELOG_NEW_IN_VERSION.format("0.6.0"), ) async def get_credits_price( diff --git a/services/api-server/src/simcore_service_api_server/api/routes/solvers.py b/services/api-server/src/simcore_service_api_server/api/routes/solvers.py index fca71667030..c85b8b39baf 100644 --- a/services/api-server/src/simcore_service_api_server/api/routes/solvers.py +++ b/services/api-server/src/simcore_service_api_server/api/routes/solvers.py @@ -5,13 +5,13 @@ from fastapi import APIRouter, Depends, HTTPException, status from httpx import HTTPStatusError -from models_library.api_schemas_api_server.pricing_plans import ServicePricingPlanGet from pydantic import ValidationError from ...exceptions.service_errors_utils import DEFAULT_BACKEND_SERVICE_STATUS_CODES from ...models.basic_types import VersionStr from ...models.pagination import OnePage, Page, PaginationParams from ...models.schemas.errors import ErrorGet +from ...models.schemas.model_adapter import ServicePricingPlanGetLegacy from ...models.schemas.solvers import Solver, SolverKeyId, SolverPort from ...services.catalog import CatalogApi from ..dependencies.application import get_reverse_url_mapper @@ -262,7 +262,7 @@ async def list_solver_ports( @router.get( "/{solver_key:path}/releases/{version}/pricing_plan", - response_model=ServicePricingPlanGet, + response_model=ServicePricingPlanGetLegacy, description="Gets solver pricing plan\n\n" + FMSG_CHANGELOG_NEW_IN_VERSION.format("0.7"), responses=_SOLVER_STATUS_CODES, diff --git a/services/api-server/src/simcore_service_api_server/api/routes/solvers_jobs_getters.py b/services/api-server/src/simcore_service_api_server/api/routes/solvers_jobs_getters.py index 708b9871a68..4903d1cb815 100644 --- a/services/api-server/src/simcore_service_api_server/api/routes/solvers_jobs_getters.py +++ b/services/api-server/src/simcore_service_api_server/api/routes/solvers_jobs_getters.py @@ -11,8 +11,6 @@ from fastapi.responses import RedirectResponse from fastapi_pagination.api import create_page from models_library.api_schemas_webserver.projects import ProjectGet -from models_library.api_schemas_webserver.resource_usage import PricingUnitGet -from models_library.api_schemas_webserver.wallets import WalletGetWithAvailableCredits from models_library.projects_nodes_io import BaseFileLink from models_library.users import UserID from models_library.wallets import ZERO_CREDITS @@ -35,6 +33,10 @@ JobMetadata, JobOutputs, ) +from ...models.schemas.model_adapter import ( + PricingUnitGetLegacy, + WalletGetWithAvailableCreditsLegacy, +) from ...models.schemas.solvers import SolverKeyId from ...services.catalog import CatalogApi from ...services.director_v2 import DirectorV2Api @@ -376,7 +378,7 @@ async def get_job_custom_metadata( @router.get( "/{solver_key:path}/releases/{version}/jobs/{job_id:uuid}/wallet", - response_model=WalletGetWithAvailableCredits, + response_model=WalletGetWithAvailableCreditsLegacy, responses=WALLET_STATUS_CODES, description=("Get job wallet\n\n" + FMSG_CHANGELOG_NEW_IN_VERSION.format("0.7")), ) @@ -385,7 +387,7 @@ async def get_job_wallet( version: VersionStr, job_id: JobID, webserver_api: Annotated[AuthSession, Depends(get_webserver_session)], -) -> WalletGetWithAvailableCredits: +) -> WalletGetWithAvailableCreditsLegacy: job_name = _compose_job_resource_name(solver_key, version, job_id) _logger.debug("Getting wallet for job '%s'", job_name) @@ -396,7 +398,7 @@ async def get_job_wallet( @router.get( "/{solver_key:path}/releases/{version}/jobs/{job_id:uuid}/pricing_unit", - response_model=PricingUnitGet, + response_model=PricingUnitGetLegacy, responses=_PRICING_UNITS_STATUS_CODES, description=( "Get job pricing unit\n\n" + FMSG_CHANGELOG_NEW_IN_VERSION.format("0.7") diff --git a/services/api-server/src/simcore_service_api_server/api/routes/wallets.py b/services/api-server/src/simcore_service_api_server/api/routes/wallets.py index 0b3df66b1d5..40e2233fce0 100644 --- a/services/api-server/src/simcore_service_api_server/api/routes/wallets.py +++ b/services/api-server/src/simcore_service_api_server/api/routes/wallets.py @@ -2,10 +2,10 @@ from typing import Annotated, Any from fastapi import APIRouter, Depends, status -from models_library.api_schemas_webserver.wallets import WalletGetWithAvailableCredits from ...exceptions.service_errors_utils import DEFAULT_BACKEND_SERVICE_STATUS_CODES from ...models.schemas.errors import ErrorGet +from ...models.schemas.model_adapter import WalletGetWithAvailableCreditsLegacy from ..dependencies.webserver import AuthSession, get_webserver_session from ._constants import FMSG_CHANGELOG_NEW_IN_VERSION @@ -29,7 +29,7 @@ @router.get( "/default", description="Get default wallet\n\n" + FMSG_CHANGELOG_NEW_IN_VERSION.format("0.7"), - response_model=WalletGetWithAvailableCredits, + response_model=WalletGetWithAvailableCreditsLegacy, responses=WALLET_STATUS_CODES, ) async def get_default_wallet( @@ -40,7 +40,7 @@ async def get_default_wallet( @router.get( "/{wallet_id}", - response_model=WalletGetWithAvailableCredits, + response_model=WalletGetWithAvailableCreditsLegacy, responses=WALLET_STATUS_CODES, description="Get wallet\n\n" + FMSG_CHANGELOG_NEW_IN_VERSION.format("0.7"), ) diff --git a/services/api-server/src/simcore_service_api_server/models/schemas/model_adapter.py b/services/api-server/src/simcore_service_api_server/models/schemas/model_adapter.py new file mode 100644 index 00000000000..9cc8b768d45 --- /dev/null +++ b/services/api-server/src/simcore_service_api_server/models/schemas/model_adapter.py @@ -0,0 +1,123 @@ +# Models added here "cover" models from within the deployment in order to restore backwards compatibility + +from datetime import datetime +from decimal import Decimal +from typing import Annotated + +from models_library.api_schemas_api_server.pricing_plans import ( + ServicePricingPlanGet as _ServicePricingPlanGet, +) +from models_library.api_schemas_webserver.product import ( + GetCreditPrice as _GetCreditPrice, +) +from models_library.api_schemas_webserver.resource_usage import ( + PricingUnitGet as _PricingUnitGet, +) +from models_library.api_schemas_webserver.wallets import ( + WalletGetWithAvailableCredits as _WalletGetWithAvailableCredits, +) +from models_library.basic_types import IDStr, NonNegativeDecimal +from models_library.resource_tracker import ( + PricingPlanClassification, + PricingPlanId, + PricingUnitId, + UnitExtraInfo, +) +from models_library.users import GroupID +from models_library.wallets import WalletID, WalletStatus +from pydantic import ( + BaseModel, + ConfigDict, + Field, + NonNegativeFloat, + NonNegativeInt, + PlainSerializer, +) + + +class GetCreditPriceLegacy(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + product_name: str = Field(alias="productName") + usd_per_credit: ( + Annotated[ + NonNegativeDecimal, + PlainSerializer(float, return_type=NonNegativeFloat, when_used="json"), + ] + | None + ) = Field( + ..., + description="Price of a credit in USD. " + "If None, then this product's price is UNDEFINED", + alias="usdPerCredit", + ) + min_payment_amount_usd: NonNegativeInt | None = Field( + ..., + description="Minimum amount (included) in USD that can be paid for this product" + "Can be None if this product's price is UNDEFINED", + alias="minPaymentAmountUsd", + ) + + +assert set(GetCreditPriceLegacy.model_fields.keys()) == set( + _GetCreditPrice.model_fields.keys() +) + + +class PricingUnitGetLegacy(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + pricing_unit_id: PricingUnitId = Field(alias="pricingUnitId") + unit_name: str = Field(alias="unitName") + unit_extra_info: UnitExtraInfo = Field(alias="unitExtraInfo") + current_cost_per_unit: Annotated[ + Decimal, PlainSerializer(float, return_type=NonNegativeFloat, when_used="json") + ] = Field(alias="currentCostPerUnit") + default: bool + + +assert set(PricingUnitGetLegacy.model_fields.keys()) == set( + _PricingUnitGet.model_fields.keys() +) + + +class WalletGetWithAvailableCreditsLegacy(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + wallet_id: WalletID = Field(alias="walletId") + name: IDStr + description: str | None = None + owner: GroupID + thumbnail: str | None = None + status: WalletStatus + created: datetime + modified: datetime + available_credits: Annotated[ + Decimal, PlainSerializer(float, return_type=NonNegativeFloat, when_used="json") + ] = Field(alias="availableCredits") + + +assert set(WalletGetWithAvailableCreditsLegacy.model_fields.keys()) == set( + _WalletGetWithAvailableCredits.model_fields.keys() +) + + +class ServicePricingPlanGetLegacy(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + pricing_plan_id: PricingPlanId = Field(alias="pricingPlanId") + display_name: str = Field(alias="displayName") + description: str + classification: PricingPlanClassification + created_at: datetime = Field(alias="createdAt") + pricing_plan_key: str = Field(alias="pricingPlanKey") + pricing_units: list[PricingUnitGetLegacy] = Field(alias="pricingUnits") + + +assert set(ServicePricingPlanGetLegacy.model_fields.keys()) == set( + _ServicePricingPlanGet.model_fields.keys() +) diff --git a/services/api-server/src/simcore_service_api_server/services/webserver.py b/services/api-server/src/simcore_service_api_server/services/webserver.py index ec828687b77..cba31689654 100644 --- a/services/api-server/src/simcore_service_api_server/services/webserver.py +++ b/services/api-server/src/simcore_service_api_server/services/webserver.py @@ -14,7 +14,6 @@ from models_library.api_schemas_api_server.pricing_plans import ServicePricingPlanGet from models_library.api_schemas_long_running_tasks.tasks import TaskGet from models_library.api_schemas_webserver.computations import ComputationStart -from models_library.api_schemas_webserver.product import GetCreditPrice from models_library.api_schemas_webserver.projects import ( ProjectCreateNew, ProjectGet, @@ -29,14 +28,8 @@ ProjectInputGet, ProjectInputUpdate, ) -from models_library.api_schemas_webserver.resource_usage import ( - PricingPlanGet, - PricingUnitGet, -) -from models_library.api_schemas_webserver.wallets import ( - WalletGet, - WalletGetWithAvailableCredits, -) +from models_library.api_schemas_webserver.resource_usage import PricingPlanGet +from models_library.api_schemas_webserver.wallets import WalletGet from models_library.generics import Envelope from models_library.projects import ProjectID from models_library.projects_nodes_io import NodeID @@ -64,6 +57,7 @@ SolverOutputNotFoundError, WalletNotFoundError, ) +from simcore_service_api_server.models.schemas.model_adapter import GetCreditPriceLegacy from tenacity import TryAgain from tenacity.asyncio import AsyncRetrying from tenacity.before_sleep import before_sleep_log @@ -79,6 +73,10 @@ from ..models.basic_types import VersionStr from ..models.pagination import MAXIMUM_NUMBER_OF_ITEMS_PER_PAGE from ..models.schemas.jobs import MetaValueType +from ..models.schemas.model_adapter import ( + PricingUnitGetLegacy, + WalletGetWithAvailableCreditsLegacy, +) from ..models.schemas.profiles import Profile, ProfileUpdate from ..models.schemas.solvers import SolverKeyId from ..models.schemas.studies import StudyPort @@ -409,14 +407,14 @@ async def update_project_metadata( @_exception_mapper({status.HTTP_404_NOT_FOUND: PricingUnitNotFoundError}) async def get_project_node_pricing_unit( self, *, project_id: UUID, node_id: UUID - ) -> PricingUnitGet: + ) -> PricingUnitGetLegacy: response = await self.client.get( f"/projects/{project_id}/nodes/{node_id}/pricing-unit", cookies=self.session_cookies, ) response.raise_for_status() - data = Envelope[PricingUnitGet].model_validate_json(response.text).data + data = Envelope[PricingUnitGetLegacy].model_validate_json(response.text).data assert data is not None # nosec return data @@ -531,14 +529,14 @@ async def update_node_outputs( # WALLETS ------------------------------------------------- @_exception_mapper(_WALLET_STATUS_MAP) - async def get_default_wallet(self) -> WalletGetWithAvailableCredits: + async def get_default_wallet(self) -> WalletGetWithAvailableCreditsLegacy: response = await self.client.get( "/wallets/default", cookies=self.session_cookies, ) response.raise_for_status() data = ( - Envelope[WalletGetWithAvailableCredits] + Envelope[WalletGetWithAvailableCreditsLegacy] .model_validate_json(response.text) .data ) @@ -546,14 +544,16 @@ async def get_default_wallet(self) -> WalletGetWithAvailableCredits: return data @_exception_mapper(_WALLET_STATUS_MAP) - async def get_wallet(self, *, wallet_id: int) -> WalletGetWithAvailableCredits: + async def get_wallet( + self, *, wallet_id: int + ) -> WalletGetWithAvailableCreditsLegacy: response = await self.client.get( f"/wallets/{wallet_id}", cookies=self.session_cookies, ) response.raise_for_status() data = ( - Envelope[WalletGetWithAvailableCredits] + Envelope[WalletGetWithAvailableCreditsLegacy] .model_validate_json(response.text) .data ) @@ -574,13 +574,13 @@ async def get_project_wallet(self, *, project_id: ProjectID) -> WalletGet: # PRODUCTS ------------------------------------------------- @_exception_mapper({status.HTTP_404_NOT_FOUND: ProductPriceNotFoundError}) - async def get_product_price(self) -> GetCreditPrice: + async def get_product_price(self) -> GetCreditPriceLegacy: response = await self.client.get( "/credits-price", cookies=self.session_cookies, ) response.raise_for_status() - data = Envelope[GetCreditPrice].model_validate_json(response.text).data + data = Envelope[GetCreditPriceLegacy].model_validate_json(response.text).data assert data is not None # nosec return data diff --git a/services/api-server/tests/unit/_with_db/test_product.py b/services/api-server/tests/unit/_with_db/test_product.py index bd14faf087e..274869d094a 100644 --- a/services/api-server/tests/unit/_with_db/test_product.py +++ b/services/api-server/tests/unit/_with_db/test_product.py @@ -14,12 +14,14 @@ from fastapi import status from fastapi.encoders import jsonable_encoder from models_library.api_schemas_api_server.api_keys import ApiKeyInDB -from models_library.api_schemas_webserver.wallets import WalletGetWithAvailableCredits from models_library.generics import Envelope from models_library.users import UserID from models_library.wallets import WalletStatus from pydantic import PositiveInt from simcore_service_api_server._meta import API_VTAG +from simcore_service_api_server.models.schemas.model_adapter import ( + WalletGetWithAvailableCreditsLegacy, +) async def test_product_webserver( @@ -46,8 +48,8 @@ def _check_key_product_compatibility(request: httpx.Request, **kwargs): return httpx.Response( status.HTTP_200_OK, json=jsonable_encoder( - Envelope[WalletGetWithAvailableCredits]( - data=WalletGetWithAvailableCredits( + Envelope[WalletGetWithAvailableCreditsLegacy]( + data=WalletGetWithAvailableCreditsLegacy( wallet_id=wallet_id, name="my_wallet", description="this is my wallet", diff --git a/services/api-server/tests/unit/test_api_solver_jobs.py b/services/api-server/tests/unit/test_api_solver_jobs.py index 3f1e642a6ad..4ed52877cfa 100644 --- a/services/api-server/tests/unit/test_api_solver_jobs.py +++ b/services/api-server/tests/unit/test_api_solver_jobs.py @@ -14,8 +14,6 @@ from fastapi import status from fastapi.encoders import jsonable_encoder from httpx import AsyncClient -from models_library.api_schemas_webserver.resource_usage import PricingUnitGet -from models_library.api_schemas_webserver.wallets import WalletGetWithAvailableCredits from models_library.generics import Envelope from pydantic import TypeAdapter from pytest_simcore.helpers.httpx_calls_capture_models import ( @@ -25,6 +23,10 @@ ) from simcore_service_api_server._meta import API_VTAG from simcore_service_api_server.models.schemas.jobs import Job, JobStatus +from simcore_service_api_server.models.schemas.model_adapter import ( + PricingUnitGetLegacy, + WalletGetWithAvailableCreditsLegacy, +) from simcore_service_api_server.models.schemas.solvers import Solver from simcore_service_api_server.services.director_v2 import ComputationTaskGet @@ -182,7 +184,7 @@ def _get_pricing_unit_side_effect( ) if capture_file == "get_job_pricing_unit_success.json": assert response.status_code == status.HTTP_200_OK - _ = TypeAdapter(PricingUnitGet).validate_python(response.json()) + _ = TypeAdapter(PricingUnitGetLegacy).validate_python(response.json()) elif capture_file == "get_job_pricing_unit_invalid_job.json": assert response.status_code == status.HTTP_404_NOT_FOUND elif capture_file == "get_job_pricing_unit_invalid_solver.json": @@ -417,7 +419,7 @@ def _wallet_side_effect( capture: HttpApiCallCaptureModel, ): wallet = ( - TypeAdapter(Envelope[WalletGetWithAvailableCredits]) + TypeAdapter(Envelope[WalletGetWithAvailableCreditsLegacy]) .validate_python(capture.response_body) .data ) @@ -425,7 +427,7 @@ def _wallet_side_effect( wallet.available_credits = ( Decimal(10.0) if sufficient_credits else Decimal(-10.0) ) - envelope = Envelope[WalletGetWithAvailableCredits]() + envelope = Envelope[WalletGetWithAvailableCreditsLegacy]() envelope.data = wallet return jsonable_encoder(envelope) diff --git a/services/api-server/tests/unit/test_api_wallets.py b/services/api-server/tests/unit/test_api_wallets.py index cad3bf5e285..bf6f7167f23 100644 --- a/services/api-server/tests/unit/test_api_wallets.py +++ b/services/api-server/tests/unit/test_api_wallets.py @@ -10,12 +10,14 @@ import pytest from fastapi import status from httpx import AsyncClient -from models_library.api_schemas_webserver.wallets import WalletGetWithAvailableCredits from pytest_simcore.helpers.httpx_calls_capture_models import ( CreateRespxMockCallback, HttpApiCallCaptureModel, ) from simcore_service_api_server._meta import API_VTAG +from simcore_service_api_server.models.schemas.model_adapter import ( + WalletGetWithAvailableCreditsLegacy, +) @pytest.mark.parametrize( @@ -52,8 +54,8 @@ def _get_wallet_side_effect( response = await client.get(f"{API_VTAG}/wallets/{wallet_id}", auth=auth) if "success" in capture: assert response.status_code == 200 - wallet: WalletGetWithAvailableCredits = ( - WalletGetWithAvailableCredits.model_validate(response.json()) + wallet: WalletGetWithAvailableCreditsLegacy = ( + WalletGetWithAvailableCreditsLegacy.model_validate(response.json()) ) assert wallet.wallet_id == wallet_id elif "failure" in capture: @@ -77,4 +79,4 @@ async def test_get_default_wallet( response = await client.get(f"{API_VTAG}/wallets/default", auth=auth) assert response.status_code == status.HTTP_200_OK - _ = WalletGetWithAvailableCredits.model_validate(response.json()) + _ = WalletGetWithAvailableCreditsLegacy.model_validate(response.json()) diff --git a/services/catalog/openapi.json b/services/catalog/openapi.json index c5663631059..5f5204053cd 100644 --- a/services/catalog/openapi.json +++ b/services/catalog/openapi.json @@ -3,7 +3,7 @@ "info": { "title": "simcore-service-catalog", "description": "Manages and maintains a catalog of all published components (e.g. macro-algorithms, scripts, etc)", - "version": "0.5.0" + "version": "0.6.0" }, "paths": { "/": { @@ -628,17 +628,11 @@ }, "image": { "type": "string", - "maxLength": 2083, - "minLength": 1, - "format": "uri", "title": "Image", "description": "Url to the badge" }, "url": { "type": "string", - "maxLength": 2083, - "minLength": 1, - "format": "uri", "title": "Url", "description": "Link to the status" } @@ -3082,7 +3076,8 @@ "type": "null" } ], - "title": "Owner" + "title": "Owner", + "description": "None when the owner email cannot be found in the database" } }, "type": "object", @@ -3549,7 +3544,10 @@ "thumbnail": { "anyOf": [ { - "type": "string" + "type": "string", + "maxLength": 2083, + "minLength": 1, + "format": "uri" }, { "type": "null" diff --git a/services/director-v2/openapi.json b/services/director-v2/openapi.json index c1b38416efe..63418baabe5 100644 --- a/services/director-v2/openapi.json +++ b/services/director-v2/openapi.json @@ -1458,17 +1458,13 @@ }, "url": { "type": "string", - "minLength": 1, - "format": "uri", "title": "Url", "description": "the link where to get the status of the task" }, "stop_url": { "anyOf": [ { - "type": "string", - "minLength": 1, - "format": "uri" + "type": "string" }, { "type": "null" @@ -2447,6 +2443,9 @@ }, "type": "array" }, + "propertyNames": { + "format": "uuid" + }, "type": "object", "title": "Adjacency List", "description": "The adjacency list of the current pipeline in terms of {NodeID: [successor NodeID]}" @@ -2469,6 +2468,9 @@ "additionalProperties": { "$ref": "#/components/schemas/NodeState" }, + "propertyNames": { + "format": "uuid" + }, "type": "object", "title": "Node States", "description": "The states of each of the computational nodes in the pipeline" @@ -2978,9 +2980,16 @@ "description": "contains harware information so we know on which hardware to run the service" }, "product_name": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Product Name", - "description": "Current product upon which this service is scheduled. If set to None, the current product is undefined. Mostly for backwards compatibility" + "description": "Current product upon which this service is scheduledIf set to None, the current product is undefined. Mostly for backwards compatibility" } }, "additionalProperties": true, @@ -3000,7 +3009,8 @@ "service_resources", "request_dns", "request_scheme", - "request_simcore_user_agent" + "request_simcore_user_agent", + "proxy_service_name" ], "title": "SchedulerData" }, @@ -3106,9 +3116,7 @@ "download_link": { "anyOf": [ { - "type": "string", - "minLength": 1, - "format": "uri" + "type": "string" }, { "type": "null" @@ -3128,8 +3136,15 @@ "properties": { "nodes_outputs": { "additionalProperties": { + "propertyNames": { + "maxLength": 100, + "minLength": 1 + }, "type": "object" }, + "propertyNames": { + "format": "uuid" + }, "type": "object", "title": "Nodes Outputs" } diff --git a/services/director/src/simcore_service_director/api/Makefile b/services/director/src/simcore_service_director/api/Makefile index f3e3c172ddc..b9eb75b95b0 100644 --- a/services/director/src/simcore_service_director/api/Makefile +++ b/services/director/src/simcore_service_director/api/Makefile @@ -32,9 +32,3 @@ ${OAS_TARGET}: ${OAS_SOURCES} .update-schemas --outfile $@ \ --type yaml \ "${API_SPECS_DIR}/${APP_NAME}/openapi.yaml" - - -.PHONY: openapi-specs -openapi-specs: ${OAS_TARGET} ## creates and validates OpenAPI specs - # Validating bundled '${OAS_TARGET}' - @swagger-cli validate $< diff --git a/services/dynamic-scheduler/openapi.json b/services/dynamic-scheduler/openapi.json index b375bb8729e..0b593da90d1 100644 --- a/services/dynamic-scheduler/openapi.json +++ b/services/dynamic-scheduler/openapi.json @@ -2,14 +2,14 @@ "openapi": "3.1.0", "info": { "title": "simcore-service-dynamic-scheduler web API", - "description": " Service that manages lifecycle of dynamic services", + "description": "Service that manages lifecycle of dynamic services", "version": "1.0.0" }, "paths": { - "/": { + "/health": { "get": { "summary": "Healthcheck", - "operationId": "healthcheck__get", + "operationId": "healthcheck_health_get", "responses": { "200": { "description": "Successful Response", @@ -59,11 +59,24 @@ "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" }, + "released": { + "anyOf": [ + { + "additionalProperties": { + "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-]+)*)?$" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Released", + "description": "Maps every route's path tag with a released version" + }, "docs_url": { "type": "string", - "maxLength": 2083, - "minLength": 1, - "format": "uri", "title": "Docs Url" } }, @@ -73,12 +86,7 @@ "version", "docs_url" ], - "title": "Meta", - "example": { - "name": "simcore_service_dynamic_scheduler", - "version": "2.4.45", - "docs_url": "https://foo.io/doc" - } + "title": "Meta" } } } diff --git a/services/payments/gateway/openapi.json b/services/payments/gateway/openapi.json deleted file mode 100644 index 20b8c11bbbc..00000000000 --- a/services/payments/gateway/openapi.json +++ /dev/null @@ -1,1039 +0,0 @@ -{ - "openapi": "3.0.0", - "info": { - "title": "osparc-compliant payment-gateway", - "version": "0.3.0" - }, - "paths": { - "/init": { - "post": { - "tags": [ - "payment" - ], - "summary": "Init Payment", - "operationId": "init_payment", - "parameters": [ - { - "name": "x-init-api-secret", - "in": "header", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "X-Init-Api-Secret" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InitPayment" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PaymentInitiated" - } - } - } - }, - "4XX": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorModel" - } - } - }, - "description": "Client Error" - } - } - } - }, - "/pay": { - "get": { - "tags": [ - "payment" - ], - "summary": "Get Payment Form", - "operationId": "get_payment_form", - "parameters": [ - { - "name": "id", - "in": "query", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 100, - "title": "Id" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "text/html": { - "schema": { - "type": "string" - } - } - } - }, - "4XX": { - "content": { - "text/html": { - "schema": { - "type": "string" - } - } - }, - "description": "Client Error" - } - } - } - }, - "/cancel": { - "post": { - "tags": [ - "payment" - ], - "summary": "Cancel Payment", - "operationId": "cancel_payment", - "parameters": [ - { - "name": "x-init-api-secret", - "in": "header", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "X-Init-Api-Secret" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PaymentInitiated" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PaymentCancelled" - } - } - } - }, - "4XX": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorModel" - } - } - }, - "description": "Client Error" - } - } - } - }, - "/payment-methods:init": { - "post": { - "tags": [ - "payment-method" - ], - "summary": "Init Payment Method", - "operationId": "init_payment_method", - "parameters": [ - { - "name": "x-init-api-secret", - "in": "header", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "X-Init-Api-Secret" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InitPaymentMethod" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PaymentMethodInitiated" - } - } - } - }, - "4XX": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorModel" - } - } - }, - "description": "Client Error" - } - } - } - }, - "/payment-methods/form": { - "get": { - "tags": [ - "payment-method" - ], - "summary": "Get Form Payment Method", - "operationId": "get_form_payment_method", - "parameters": [ - { - "name": "id", - "in": "query", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 100, - "title": "Id" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "text/html": { - "schema": { - "type": "string" - } - } - } - }, - "4XX": { - "content": { - "text/html": { - "schema": { - "type": "string" - } - } - }, - "description": "Client Error" - } - } - } - }, - "/payment-methods:batchGet": { - "post": { - "tags": [ - "payment-method" - ], - "summary": "Batch Get Payment Methods", - "operationId": "batch_get_payment_methods", - "parameters": [ - { - "name": "x-init-api-secret", - "in": "header", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "X-Init-Api-Secret" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BatchGetPaymentMethods" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PaymentMethodsBatch" - } - } - } - }, - "4XX": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorModel" - } - } - }, - "description": "Client Error" - } - } - } - }, - "/payment-methods/{id}": { - "get": { - "tags": [ - "payment-method" - ], - "summary": "Get Payment Method", - "operationId": "get_payment_method", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 100, - "title": "Id" - } - }, - { - "name": "x-init-api-secret", - "in": "header", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "X-Init-Api-Secret" - } - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetPaymentMethod" - } - } - } - }, - "404": { - "description": "Payment method not found: It was not added or incomplete (i.e. create flow failed or canceled)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorModel" - } - } - } - }, - "4XX": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorModel" - } - } - }, - "description": "Client Error" - } - } - }, - "delete": { - "tags": [ - "payment-method" - ], - "summary": "Delete Payment Method", - "operationId": "delete_payment_method", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 100, - "title": "Id" - } - }, - { - "name": "x-init-api-secret", - "in": "header", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "X-Init-Api-Secret" - } - } - ], - "responses": { - "204": { - "description": "Successful Response" - }, - "4XX": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorModel" - } - } - }, - "description": "Client Error" - } - } - } - }, - "/payment-methods/{id}:pay": { - "post": { - "tags": [ - "payment-method" - ], - "summary": "Pay With Payment Method", - "operationId": "pay_with_payment_method", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string", - "minLength": 1, - "maxLength": 100, - "title": "Id" - } - }, - { - "name": "x-init-api-secret", - "in": "header", - "required": false, - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "X-Init-Api-Secret" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InitPayment" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AckPaymentWithPaymentMethod" - } - } - } - }, - "4XX": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorModel" - } - } - }, - "description": "Client Error" - } - } - } - } - }, - "components": { - "schemas": { - "AckPaymentWithPaymentMethod": { - "properties": { - "success": { - "type": "boolean", - "title": "Success" - }, - "message": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Message" - }, - "provider_payment_id": { - "anyOf": [ - { - "type": "string", - "maxLength": 100, - "minLength": 1 - }, - { - "type": "null" - } - ], - "title": "Provider Payment Id", - "description": "Payment ID from the provider (e.g. stripe payment ID)" - }, - "invoice_url": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Invoice Url", - "description": "Link to invoice is required when success=true" - }, - "invoice_pdf": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Invoice Pdf", - "description": "Link to invoice PDF" - }, - "stripe_invoice_id": { - "anyOf": [ - { - "type": "string", - "maxLength": 100, - "minLength": 1 - }, - { - "type": "null" - } - ], - "title": "Stripe Invoice Id", - "description": "Stripe invoice ID" - }, - "stripe_customer_id": { - "anyOf": [ - { - "type": "string", - "maxLength": 100, - "minLength": 1 - }, - { - "type": "null" - } - ], - "title": "Stripe Customer Id", - "description": "Stripe customer ID" - }, - "payment_id": { - "anyOf": [ - { - "type": "string", - "maxLength": 100, - "minLength": 1 - }, - { - "type": "null" - } - ], - "title": "Payment Id", - "description": "Payment ID from the gateway" - } - }, - "type": "object", - "required": [ - "success" - ], - "title": "AckPaymentWithPaymentMethod", - "example": { - "invoice_url": "https://invoices.com/id=12345", - "payment_id": "D19EE68B-B007-4B61-A8BC-32B7115FB244", - "provider_payment_id": "pi_123ABC", - "success": true - } - }, - "BatchGetPaymentMethods": { - "properties": { - "payment_methods_ids": { - "items": { - "type": "string", - "maxLength": 100, - "minLength": 1 - }, - "type": "array", - "title": "Payment Methods Ids" - } - }, - "type": "object", - "required": [ - "payment_methods_ids" - ], - "title": "BatchGetPaymentMethods" - }, - "ErrorModel": { - "properties": { - "message": { - "type": "string", - "title": "Message" - }, - "exception": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Exception" - }, - "file": { - "anyOf": [ - { - "type": "string", - "format": "path" - }, - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "File" - }, - "line": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Line" - }, - "trace": { - "anyOf": [ - { - "items": {}, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Trace" - } - }, - "type": "object", - "required": [ - "message" - ], - "title": "ErrorModel" - }, - "GetPaymentMethod": { - "properties": { - "id": { - "type": "string", - "maxLength": 100, - "minLength": 1, - "title": "Id" - }, - "card_holder_name": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Card Holder Name" - }, - "card_number_masked": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Card Number Masked" - }, - "card_type": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Card Type" - }, - "expiration_month": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Expiration Month" - }, - "expiration_year": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Expiration Year" - }, - "created": { - "type": "string", - "format": "date-time", - "title": "Created" - } - }, - "type": "object", - "required": [ - "id", - "created" - ], - "title": "GetPaymentMethod" - }, - "InitPayment": { - "properties": { - "amount_dollars": { - "anyOf": [ - { - "type": "number", - "exclusiveMaximum": true, - "exclusiveMinimum": true, - "maximum": 1000000.0, - "minimum": 0.0 - }, - { - "type": "string" - } - ], - "title": "Amount Dollars" - }, - "credits": { - "anyOf": [ - { - "type": "number", - "exclusiveMaximum": true, - "exclusiveMinimum": true, - "maximum": 1000000.0, - "minimum": 0.0 - }, - { - "type": "string" - } - ], - "title": "Credits", - "describe": "This is equal to `quantity` field in Stripe" - }, - "user_name": { - "type": "string", - "maxLength": 100, - "minLength": 1, - "title": "User Name" - }, - "user_email": { - "type": "string", - "format": "email", - "title": "User Email" - }, - "user_address": { - "$ref": "#/components/schemas/UserInvoiceAddress" - }, - "wallet_name": { - "type": "string", - "maxLength": 100, - "minLength": 1, - "title": "Wallet Name" - }, - "stripe_price_id": { - "type": "string", - "title": "Stripe Price Id" - }, - "stripe_tax_rate_id": { - "type": "string", - "title": "Stripe Tax Rate Id" - }, - "stripe_tax_exempt_value": { - "$ref": "#/components/schemas/StripeTaxExempt" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "amount_dollars", - "credits", - "user_name", - "user_email", - "user_address", - "wallet_name", - "stripe_price_id", - "stripe_tax_rate_id", - "stripe_tax_exempt_value" - ], - "title": "InitPayment" - }, - "InitPaymentMethod": { - "properties": { - "method": { - "type": "string", - "const": "CC", - "title": "Method", - "default": "CC" - }, - "user_name": { - "type": "string", - "maxLength": 100, - "minLength": 1, - "title": "User Name" - }, - "user_email": { - "type": "string", - "format": "email", - "title": "User Email" - }, - "wallet_name": { - "type": "string", - "maxLength": 100, - "minLength": 1, - "title": "Wallet Name" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "user_name", - "user_email", - "wallet_name" - ], - "title": "InitPaymentMethod" - }, - "PaymentCancelled": { - "properties": { - "message": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Message" - } - }, - "type": "object", - "title": "PaymentCancelled" - }, - "PaymentInitiated": { - "properties": { - "payment_id": { - "type": "string", - "maxLength": 100, - "minLength": 1, - "title": "Payment Id" - } - }, - "type": "object", - "required": [ - "payment_id" - ], - "title": "PaymentInitiated" - }, - "PaymentMethodInitiated": { - "properties": { - "payment_method_id": { - "type": "string", - "maxLength": 100, - "minLength": 1, - "title": "Payment Method Id" - } - }, - "type": "object", - "required": [ - "payment_method_id" - ], - "title": "PaymentMethodInitiated" - }, - "PaymentMethodsBatch": { - "properties": { - "items": { - "items": { - "$ref": "#/components/schemas/GetPaymentMethod" - }, - "type": "array", - "title": "Items" - } - }, - "type": "object", - "required": [ - "items" - ], - "title": "PaymentMethodsBatch" - }, - "StripeTaxExempt": { - "type": "string", - "enum": [ - "exempt", - "none", - "reverse" - ], - "title": "StripeTaxExempt" - }, - "UserInvoiceAddress": { - "properties": { - "line1": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Line1" - }, - "state": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "State" - }, - "postal_code": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Postal Code" - }, - "city": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "City" - }, - "country": { - "type": "string", - "title": "Country", - "description": "Currently validated in webserver via pycountry library. Two letter country code alpha_2 expected." - } - }, - "type": "object", - "required": [ - "country" - ], - "title": "UserInvoiceAddress" - } - } - } -} diff --git a/services/resource-usage-tracker/openapi.json b/services/resource-usage-tracker/openapi.json index 3ee8f2eeb73..91ee050d3d9 100644 --- a/services/resource-usage-tracker/openapi.json +++ b/services/resource-usage-tracker/openapi.json @@ -458,7 +458,7 @@ "$ref": "#/components/schemas/UnitExtraInfo" }, "current_cost_per_unit": { - "type": "number", + "type": "string", "title": "Current Cost Per Unit" }, "current_cost_per_unit_id": { diff --git a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml index 829aa2be9c2..485fb15c931 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml @@ -711,8 +711,6 @@ paths: in: query required: false schema: - enum: - - std const: std type: string default: std @@ -1010,9 +1008,7 @@ paths: - type: string minLength: 1 maxLength: 100 - - enum: - - current - const: current + - const: current type: string title: Product Name responses: @@ -1038,9 +1034,7 @@ paths: - type: string minLength: 1 maxLength: 100 - - enum: - - current - const: current + - const: current type: string title: Product Name - name: template_id @@ -4546,7 +4540,7 @@ paths: '403': description: ProjectInvalidRightsError '404': - description: UserDefaultWalletNotFoundError, ProjectNotFoundError + description: ProjectNotFoundError, UserDefaultWalletNotFoundError '409': description: ProjectTooManyProjectOpenedError '422': @@ -5452,7 +5446,7 @@ paths: schema: anyOf: - $ref: '#/components/schemas/Envelope_FileUploadSchema_' - - $ref: '#/components/schemas/Envelope_Url_' + - $ref: '#/components/schemas/Envelope_AnyUrl_' title: Response Upload File delete: tags: @@ -5840,9 +5834,7 @@ paths: anyOf: - type: integer - type: string - - enum: - - HEAD - const: HEAD + - const: HEAD type: string title: Ref Id - name: project_uuid @@ -6626,7 +6618,9 @@ components: limits: $ref: '#/components/schemas/Limits' queued: - type: boolean + anyOf: + - type: boolean + - type: 'null' title: Queued type: object required: @@ -6766,16 +6760,12 @@ components: url: anyOf: - type: string - minLength: 1 - format: uri - type: 'null' title: Url description: Link to current resource diagnostics_url: anyOf: - type: string - minLength: 1 - format: uri - type: 'null' title: Diagnostics Url description: Link to diagnostics report sub-resource. This MIGHT take some @@ -6876,9 +6866,6 @@ components: thumbnail: anyOf: - type: string - maxLength: 2083 - minLength: 1 - format: uri - type: 'null' title: Thumbnail description: @@ -7169,9 +7156,6 @@ components: title: Parents Ids url: type: string - maxLength: 2083 - minLength: 1 - format: uri title: Url type: object required: @@ -7297,15 +7281,11 @@ components: task url: type: string - minLength: 1 - format: uri title: Url description: the link where to get the status of the task stop_url: anyOf: - type: string - minLength: 1 - format: uri - type: 'null' title: Stop Url description: the link where to stop the task @@ -7565,6 +7545,20 @@ components: additionalProperties: false type: object title: EmptyModel + Envelope_AnyUrl_: + properties: + data: + anyOf: + - type: string + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[AnyUrl] Envelope_AppStatusCheck_: properties: data: @@ -8248,22 +8242,6 @@ components: title: Error type: object title: Envelope[Union[WalletGet, NoneType]] - Envelope_Url_: - properties: - data: - anyOf: - - type: string - minLength: 1 - format: uri - - type: 'null' - title: Data - error: - anyOf: - - {} - - type: 'null' - title: Error - type: object - title: Envelope[Url] Envelope_UserProfile_: properties: data: @@ -8429,6 +8407,8 @@ components: type: integer exclusiveMinimum: true minimum: 0 + propertyNames: + const: comment_id type: object - type: 'null' title: Data @@ -8445,6 +8425,8 @@ components: anyOf: - additionalProperties: $ref: '#/components/schemas/Activity' + propertyNames: + format: uuid type: object - type: 'null' title: Data @@ -8461,6 +8443,8 @@ components: anyOf: - additionalProperties: $ref: '#/components/schemas/ProjectInputGet' + propertyNames: + format: uuid type: object - type: 'null' title: Data @@ -8477,6 +8461,8 @@ components: anyOf: - additionalProperties: $ref: '#/components/schemas/ProjectOutputGet' + propertyNames: + format: uuid type: object - type: 'null' title: Data @@ -9261,8 +9247,6 @@ components: file_size: anyOf: - type: integer - enum: - - -1 const: -1 - type: integer minimum: 0 @@ -9321,8 +9305,6 @@ components: properties: state: type: string - minLength: 1 - format: uri title: State type: object required: @@ -9357,13 +9339,9 @@ components: properties: abort_upload: type: string - minLength: 1 - format: uri title: Abort Upload complete_upload: type: string - minLength: 1 - format: uri title: Complete Upload type: object required: @@ -9379,8 +9357,6 @@ components: urls: items: type: string - minLength: 1 - format: uri type: array title: Urls links: @@ -9740,8 +9716,6 @@ components: thumbnail: anyOf: - type: string - minLength: 1 - format: uri - type: 'null' title: Thumbnail description: url to the group thumbnail @@ -9985,9 +9959,6 @@ components: title: Created invitationLink: type: string - maxLength: 2083 - minLength: 1 - format: uri title: Invitationlink type: object required: @@ -10062,7 +10033,6 @@ components: type: string enum: - VIP_MODEL - const: VIP_MODEL title: LicensedResourceType Limits: properties: @@ -10292,6 +10262,10 @@ components: thumbnail: anyOf: - type: string + - type: string + maxLength: 2083 + minLength: 1 + format: uri - type: 'null' title: Thumbnail description: url of the latest screenshot of the node @@ -10302,7 +10276,6 @@ components: title: Runhash description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated - nullable: true inputs: anyOf: - type: object @@ -10366,7 +10339,6 @@ components: - type: 'null' title: Parent description: Parent's (group-nodes') node ID s. Used to group - nullable: true position: anyOf: - $ref: '#/components/schemas/Position' @@ -10433,7 +10405,6 @@ components: title: Runhash description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated - nullable: true inputs: anyOf: - type: object @@ -10497,7 +10468,6 @@ components: - type: 'null' title: Parent description: Parent's (group-nodes') node ID s. Used to group - nullable: true position: anyOf: - $ref: '#/components/schemas/Position' @@ -10598,6 +10568,7 @@ components: exclusiveMinimum: true title: Serviceport description: port to access the service within the network + default: 8080 maximum: 65535 minimum: 0 serviceBasepath: @@ -10634,7 +10605,6 @@ components: - serviceKey - serviceVersion - serviceHost - - servicePort - serviceState - userId title: NodeGet @@ -10642,8 +10612,6 @@ components: properties: serviceState: type: string - enum: - - idle const: idle title: Servicestate serviceUuid: @@ -10662,8 +10630,6 @@ components: properties: serviceState: type: string - enum: - - unknown const: unknown title: Servicestate serviceUuid: @@ -10709,6 +10675,7 @@ components: inputs: type: object title: Inputs + default: {} inputsRequired: anyOf: - items: @@ -10770,15 +10737,9 @@ components: properties: thumbnail_url: type: string - maxLength: 2083 - minLength: 1 - format: uri title: Thumbnail Url file_url: type: string - maxLength: 2083 - minLength: 1 - format: uri title: File Url mimetype: anyOf: @@ -11153,9 +11114,6 @@ components: title: Paymentmethodid paymentMethodFormUrl: type: string - maxLength: 2083 - minLength: 1 - format: uri title: Paymentmethodformurl description: Link to external site that holds the payment submission form type: object @@ -11213,9 +11171,6 @@ components: invoiceUrl: anyOf: - type: string - maxLength: 2083 - minLength: 1 - format: uri - type: 'null' title: Invoiceurl type: object @@ -11271,6 +11226,8 @@ components: type: string format: uuid type: array + propertyNames: + format: uuid type: object title: Adjacency List description: 'The adjacency list of the current pipeline in terms of {NodeID: @@ -11287,6 +11244,8 @@ components: node_states: additionalProperties: $ref: '#/components/schemas/NodeState' + propertyNames: + format: uuid type: object title: Node States description: The states of each of the computational nodes in the pipeline @@ -11405,8 +11364,6 @@ components: properties: link: type: string - minLength: 1 - format: uri title: Link type: object required: @@ -11459,7 +11416,6 @@ components: type: string enum: - TIER - const: TIER title: PricingPlanClassification PricingPlanToServiceAdminGet: properties: @@ -11695,6 +11651,9 @@ components: accessRights: additionalProperties: $ref: '#/components/schemas/AccessRights' + propertyNames: + maxLength: 100 + minLength: 1 type: object title: Accessrights tags: @@ -11746,12 +11705,7 @@ components: thumbnail: anyOf: - type: string - maxLength: 2083 - minLength: 1 - format: uri - type: string - enum: - - '' const: '' title: Thumbnail creationDate: @@ -11772,6 +11726,9 @@ components: accessRights: additionalProperties: $ref: '#/components/schemas/AccessRights' + propertyNames: + maxLength: 100 + minLength: 1 type: object title: Accessrights tags: @@ -11936,9 +11893,6 @@ components: project where this iteration is run workcopy_project_url: type: string - maxLength: 2083 - minLength: 1 - format: uri title: Workcopy Project Url description: reference to a working copy project type: object @@ -11971,9 +11925,6 @@ components: project where this iteration is run workcopy_project_url: type: string - maxLength: 2083 - minLength: 1 - format: uri title: Workcopy Project Url description: reference to a working copy project results: @@ -12002,12 +11953,7 @@ components: thumbnail: anyOf: - type: string - maxLength: 2083 - minLength: 1 - format: uri - type: string - enum: - - '' const: '' title: Thumbnail creationDate: @@ -12028,6 +11974,9 @@ components: accessRights: additionalProperties: $ref: '#/components/schemas/AccessRights' + propertyNames: + maxLength: 100 + minLength: 1 type: object title: Accessrights tags: @@ -12225,6 +12174,9 @@ components: anyOf: - additionalProperties: $ref: '#/components/schemas/AccessRights' + propertyNames: + maxLength: 100 + minLength: 1 type: object - type: 'null' title: Accessrights @@ -12255,9 +12207,6 @@ components: properties: url: type: string - maxLength: 2083 - minLength: 1 - format: uri title: Url is_public: type: boolean @@ -12492,9 +12441,6 @@ components: title: Project Uuid url: type: string - maxLength: 2083 - minLength: 1 - format: uri title: Url type: object required: @@ -12650,6 +12596,7 @@ components: exclusiveMinimum: true title: Service Port description: the service swarm internal port + default: 8080 maximum: 65535 minimum: 0 published_port: @@ -12687,7 +12634,6 @@ components: - project_id - service_uuid - service_host - - service_port - service_state title: RunningDynamicServiceDetails RunningState: @@ -12744,9 +12690,6 @@ components: description: Long description of the service thumbnail: type: string - maxLength: 2083 - minLength: 1 - format: uri title: Thumbnail description: Url to service thumbnail file_extensions: @@ -12757,9 +12700,6 @@ components: description: File extensions that this service can process view_url: type: string - maxLength: 2083 - minLength: 1 - format: uri title: View Url description: Redirection to open a service in osparc (see /view) type: object @@ -13191,7 +13131,6 @@ components: type: string enum: - services - const: services title: ServicesAggregatedUsagesType SimCoreFileLink: properties: @@ -13670,7 +13609,7 @@ components: - type: string format: email - type: 'null' - title: 'From ' + title: From description: Email sender to: type: string @@ -13939,8 +13878,6 @@ components: product: anyOf: - type: string - enum: - - UNDEFINED const: UNDEFINED - type: string title: Product @@ -13948,8 +13885,6 @@ components: resource_id: anyOf: - type: string - enum: - - '' const: '' - type: string title: Resource Id @@ -13957,8 +13892,6 @@ components: user_from_id: anyOf: - type: 'null' - enum: - - null - type: integer exclusiveMinimum: true minimum: 0 @@ -14005,8 +13938,6 @@ components: product: anyOf: - type: string - enum: - - UNDEFINED const: UNDEFINED - type: string title: Product @@ -14014,8 +13945,6 @@ components: resource_id: anyOf: - type: string - enum: - - '' const: '' - type: string title: Resource Id @@ -14023,8 +13952,6 @@ components: user_from_id: anyOf: - type: 'null' - enum: - - null - type: integer exclusiveMinimum: true minimum: 0 @@ -14160,9 +14087,6 @@ components: description: Identifier for the file type view_url: type: string - maxLength: 2083 - minLength: 1 - format: uri title: View Url description: Base url to execute viewer. Needs appending file_size,[file_name] and download_link as query parameters @@ -14318,9 +14242,6 @@ components: paymentFormUrl: anyOf: - type: string - maxLength: 2083 - minLength: 1 - format: uri - type: 'null' title: Paymentformurl description: Link to external site that holds the payment submission form.None @@ -14384,15 +14305,9 @@ components: default: {} url: type: string - maxLength: 2083 - minLength: 1 - format: uri title: Url checkpoint_url: type: string - maxLength: 2083 - minLength: 1 - format: uri title: Checkpoint Url type: object required: