diff --git a/packages/models-library/src/models_library/api_schemas_catalog/services.py b/packages/models-library/src/models_library/api_schemas_catalog/services.py index db386a8714f..bce573a2494 100644 --- a/packages/models-library/src/models_library/api_schemas_catalog/services.py +++ b/packages/models-library/src/models_library/api_schemas_catalog/services.py @@ -244,9 +244,10 @@ class ServiceGetV2(BaseModel): quality: dict[str, Any] = {} history: list[ServiceRelease] = Field( - default=[], + default_factory=list, description="history of releases for this service at this point in time, starting from the newest to the oldest." " It includes current release.", + json_schema_extra={"default": []}, ) model_config = ConfigDict( diff --git a/packages/models-library/src/models_library/app_diagnostics.py b/packages/models-library/src/models_library/app_diagnostics.py index ce8c9331eae..a8652e84db2 100644 --- a/packages/models-library/src/models_library/app_diagnostics.py +++ b/packages/models-library/src/models_library/app_diagnostics.py @@ -7,12 +7,15 @@ class AppStatusCheck(BaseModel): app_name: str = Field(..., description="Application name") version: str = Field(..., description="Application's version") services: dict[str, Any] = Field( - default={}, description="Other backend services connected from this service" + default_factory=dict, + description="Other backend services connected from this service", + json_schema_extra={"default": {}}, ) sessions: dict[str, Any] | None = Field( - default={}, + default_factory=dict, description="Client sessions info. If single session per app, then is denoted as main", + json_schema_extra={"default": {}}, ) url: AnyUrl | None = Field( diff --git a/packages/models-library/src/models_library/services_metadata_editable.py b/packages/models-library/src/models_library/services_metadata_editable.py index 4ad106225c0..c4436583503 100644 --- a/packages/models-library/src/models_library/services_metadata_editable.py +++ b/packages/models-library/src/models_library/services_metadata_editable.py @@ -33,7 +33,9 @@ class ServiceMetaDataEditable(ServiceBaseDisplay): "If now>=deprecated, the service is retired", ) classifiers: list[str] | None - quality: dict[str, Any] = {} + quality: dict[str, Any] = Field( + default_factory=dict, json_schema_extra={"default": {}} + ) model_config = ConfigDict( json_schema_extra={ @@ -60,6 +62,7 @@ class ServiceMetaDataEditable(ServiceBaseDisplay): for n in range(1, 11) }, }, + "classifiers": [], } } ) diff --git a/packages/models-library/src/models_library/services_resources.py b/packages/models-library/src/models_library/services_resources.py index 7a2b65456a4..175c56f968a 100644 --- a/packages/models-library/src/models_library/services_resources.py +++ b/packages/models-library/src/models_library/services_resources.py @@ -33,13 +33,14 @@ CPU_100_PERCENT: Final[int] = int(1 * GIGA) -class ResourceValue(BaseModel): +class ResourceValue(BaseModel, validate_assignment=True): limit: StrictInt | StrictFloat | str reservation: StrictInt | StrictFloat | str @model_validator(mode="before") @classmethod def _ensure_limits_are_equal_or_above_reservations(cls, values): + # WARNING: this does not validate ON-ASSIGNMENT! if isinstance(values["reservation"], str): # in case of string, the limit is the same as the reservation values["limit"] = values["reservation"] @@ -56,10 +57,8 @@ def set_reservation_same_as_limit(self) -> None: def set_value(self, value: StrictInt | StrictFloat | str) -> None: self.limit = self.reservation = value - model_config = ConfigDict(validate_assignment=True) - -ResourcesDict = dict[ResourceName, ResourceValue] +ResourcesDict: TypeAlias = dict[ResourceName, ResourceValue] class BootMode(StrAutoEnum): diff --git a/packages/models-library/tests/test_services_resources.py b/packages/models-library/tests/test_services_resources.py new file mode 100644 index 00000000000..3bc4c83c0ec --- /dev/null +++ b/packages/models-library/tests/test_services_resources.py @@ -0,0 +1,21 @@ +import pytest +from models_library.services_resources import ResourceValue + + +@pytest.mark.xfail() +def test_reservation_is_cap_by_limit_on_assigment_pydantic_2_bug(): + + res = ResourceValue(limit=10, reservation=30) + assert res.limit == 10 + assert res.reservation == 10 + + # https://docs.pydantic.dev/latest/api/config/#pydantic.config.ConfigDict.validate_assignment + # before-validators DO NOT work on Assignment!!! + # SEE https://github.com/pydantic/pydantic/issues/7105 + res.reservation = 30 + assert res.reservation == 10 + + # update here is not validated neither + # + # res.model_copy(update={"reservation": 30}) + # diff --git a/packages/settings-library/src/settings_library/application.py b/packages/settings-library/src/settings_library/application.py index e7d588c7023..43740b69fd5 100644 --- a/packages/settings-library/src/settings_library/application.py +++ b/packages/settings-library/src/settings_library/application.py @@ -1,7 +1,7 @@ from pydantic import Field, PositiveInt from .base import BaseCustomSettings -from .basic_types import BuildTargetEnum +from .basic_types import BootMode, BuildTargetEnum class BaseApplicationSettings(BaseCustomSettings): @@ -16,6 +16,7 @@ class BaseApplicationSettings(BaseCustomSettings): SC_VCS_URL: str | None = None # @Dockerfile + SC_BOOT_MODE: BootMode | None = None SC_BOOT_TARGET: BuildTargetEnum | None = None SC_HEALTHCHECK_TIMEOUT: PositiveInt | None = Field( default=None, diff --git a/scripts/common-service.Makefile b/scripts/common-service.Makefile index e999b5b9e75..138b9ae27fc 100644 --- a/scripts/common-service.Makefile +++ b/scripts/common-service.Makefile @@ -192,8 +192,8 @@ _assert_target_defined: # specification of the used openapi-generator-cli (see also https://github.com/ITISFoundation/openapi-generator) -OPENAPI_GENERATOR_NAME := itisfoundation/openapi-generator-cli-openapi-generator-v4.2.3 -OPENAPI_GENERATOR_TAG := v0 +OPENAPI_GENERATOR_NAME := openapitools/openapi-generator-cli +OPENAPI_GENERATOR_TAG := latest OPENAPI_GENERATOR_IMAGE := $(OPENAPI_GENERATOR_NAME):$(OPENAPI_GENERATOR_TAG) define validate_openapi_specs diff --git a/services/api-server/Makefile b/services/api-server/Makefile index 900c0e8e0f1..5b675b22ce4 100644 --- a/services/api-server/Makefile +++ b/services/api-server/Makefile @@ -14,11 +14,6 @@ reqs: ## compiles pip requirements (.in -> .txt) cp .env-devel $@ -# specification of the used openapi-generator-cli (see also https://github.com/ITISFoundation/openapi-generator) -OPENAPI_GENERATOR_NAME := openapitools/openapi-generator-cli -OPENAPI_GENERATOR_TAG := latest -OPENAPI_GENERATOR_IMAGE := $(OPENAPI_GENERATOR_NAME):$(OPENAPI_GENERATOR_TAG) - define _create_and_validate_openapi # generating openapi specs file under $< (NOTE: Skips DEV FEATURES since this OAS is the 'offically released'!) @source .env; \ diff --git a/services/api-server/src/simcore_service_api_server/exceptions/handlers/_http_exceptions.py b/services/api-server/src/simcore_service_api_server/exceptions/handlers/_http_exceptions.py index 24be07387d7..bdff166096b 100644 --- a/services/api-server/src/simcore_service_api_server/exceptions/handlers/_http_exceptions.py +++ b/services/api-server/src/simcore_service_api_server/exceptions/handlers/_http_exceptions.py @@ -7,6 +7,6 @@ async def http_exception_handler(request: Request, exc: Exception) -> JSONResponse: assert request # nosec - assert isinstance(exc, HTTPException) + assert isinstance(exc, HTTPException) # nosec return create_error_json_response(exc.detail, status_code=exc.status_code) diff --git a/services/catalog/VERSION b/services/catalog/VERSION index 79a2734bbf3..09a3acfa138 100644 --- a/services/catalog/VERSION +++ b/services/catalog/VERSION @@ -1 +1 @@ -0.5.0 \ No newline at end of file +0.6.0 \ No newline at end of file diff --git a/services/catalog/openapi.json b/services/catalog/openapi.json index ebacaf11616..c5663631059 100644 --- a/services/catalog/openapi.json +++ b/services/catalog/openapi.json @@ -71,37 +71,44 @@ "operationId": "get_service_resources_v0_services__service_key___service_version__resources_get", "parameters": [ { + "name": "service_key", + "in": "path", "required": true, "schema": { "type": "string", "pattern": "^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", "title": "Service Key" - }, - "name": "service_key", - "in": "path" + } }, { + "name": "service_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": "Service Version" - }, - "name": "service_version", - "in": "path" + } }, { - "description": "if passed, and that user has custom resources, they will be merged with default resources and returned.", + "name": "user_id", + "in": "query", "required": false, "schema": { - "type": "integer", - "exclusiveMinimum": true, - "title": "User Id", + "anyOf": [ + { + "type": "integer", + "exclusiveMinimum": true, + "minimum": 0 + }, + { + "type": "null" + } + ], "description": "if passed, and that user has custom resources, they will be merged with default resources and returned.", - "minimum": 0 + "title": "User Id" }, - "name": "user_id", - "in": "query" + "description": "if passed, and that user has custom resources, they will be merged with default resources and returned." } ], "responses": { @@ -110,9 +117,6 @@ "content": { "application/json": { "schema": { - "additionalProperties": { - "$ref": "#/components/schemas/ImageResources" - }, "type": "object", "title": "Response Get Service Resources V0 Services Service Key Service Version Resources Get" } @@ -141,47 +145,47 @@ "operationId": "get_service_specifications_v0_services__service_key___service_version__specifications_get", "parameters": [ { + "name": "service_key", + "in": "path", "required": true, "schema": { "type": "string", "pattern": "^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", "title": "Service Key" - }, - "name": "service_key", - "in": "path" + } }, { + "name": "service_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": "Service Version" - }, - "name": "service_version", - "in": "path" + } }, { + "name": "user_id", + "in": "query", "required": true, "schema": { "type": "integer", "exclusiveMinimum": true, "title": "User Id", "minimum": 0 - }, - "name": "user_id", - "in": "query" + } }, { - "description": "if True only the version specs will be retrieved, if False the latest version will be used instead", + "name": "strict", + "in": "query", "required": false, "schema": { "type": "boolean", - "title": "Strict", "description": "if True only the version specs will be retrieved, if False the latest version will be used instead", - "default": false + "default": false, + "title": "Strict" }, - "name": "strict", - "in": "query" + "description": "if True only the version specs will be retrieved, if False the latest version will be used instead" } ], "responses": { @@ -218,42 +222,42 @@ "operationId": "list_service_ports_v0_services__service_key___service_version__ports_get", "parameters": [ { + "name": "service_key", + "in": "path", "required": true, "schema": { "type": "string", "pattern": "^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", "title": "Service Key" - }, - "name": "service_key", - "in": "path" + } }, { + "name": "service_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": "Service Version" - }, - "name": "service_version", - "in": "path" + } }, { + "name": "user_id", + "in": "query", "required": true, "schema": { "type": "integer", "title": "User Id" - }, - "name": "user_id", - "in": "query" + } }, { + "name": "x-simcore-products-name", + "in": "header", "required": false, "schema": { "type": "string", "title": "X-Simcore-Products-Name" - }, - "name": "x-simcore-products-name", - "in": "header" + } } ], "responses": { @@ -262,10 +266,10 @@ "content": { "application/json": { "schema": { + "type": "array", "items": { "$ref": "#/components/schemas/ServicePortGet" }, - "type": "array", "title": "Response List Service Ports V0 Services Service Key Service Version Ports Get" } } @@ -294,42 +298,42 @@ "operationId": "get_service_access_rights_v0_services__service_key___service_version__accessRights_get", "parameters": [ { + "name": "service_key", + "in": "path", "required": true, "schema": { "type": "string", "pattern": "^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", "title": "Service Key" - }, - "name": "service_key", - "in": "path" + } }, { + "name": "service_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": "Service Version" - }, - "name": "service_version", - "in": "path" + } }, { + "name": "user_id", + "in": "query", "required": true, "schema": { "type": "integer", "title": "User Id" - }, - "name": "user_id", - "in": "query" + } }, { + "name": "x-simcore-products-name", + "in": "header", "required": true, "schema": { "type": "string", "title": "X-Simcore-Products-Name" - }, - "name": "x-simcore-products-name", - "in": "header" + } } ], "responses": { @@ -365,34 +369,34 @@ "operationId": "list_services_v0_services_get", "parameters": [ { + "name": "user_id", + "in": "query", "required": true, "schema": { "type": "integer", "exclusiveMinimum": true, "title": "User Id", "minimum": 0 - }, - "name": "user_id", - "in": "query" + } }, { + "name": "details", + "in": "query", "required": false, "schema": { "type": "boolean", - "title": "Details", - "default": true - }, - "name": "details", - "in": "query" + "default": true, + "title": "Details" + } }, { + "name": "x-simcore-products-name", + "in": "header", "required": true, "schema": { "type": "string", "title": "X-Simcore-Products-Name" - }, - "name": "x-simcore-products-name", - "in": "header" + } } ], "responses": { @@ -401,10 +405,10 @@ "content": { "application/json": { "schema": { + "type": "array", "items": { "$ref": "#/components/schemas/ServiceGet" }, - "type": "array", "title": "Response List Services V0 Services Get" } } @@ -432,42 +436,42 @@ "operationId": "get_service_v0_services__service_key___service_version__get", "parameters": [ { + "name": "service_key", + "in": "path", "required": true, "schema": { "type": "string", "pattern": "^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", "title": "Service Key" - }, - "name": "service_key", - "in": "path" + } }, { + "name": "service_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": "Service Version" - }, - "name": "service_version", - "in": "path" + } }, { + "name": "user_id", + "in": "query", "required": true, "schema": { "type": "integer", "title": "User Id" - }, - "name": "user_id", - "in": "query" + } }, { + "name": "x-simcore-products-name", + "in": "header", "required": false, "schema": { "type": "string", "title": "X-Simcore-Products-Name" - }, - "name": "x-simcore-products-name", - "in": "header" + } } ], "responses": { @@ -501,53 +505,60 @@ "operationId": "update_service_v0_services__service_key___service_version__patch", "parameters": [ { + "name": "service_key", + "in": "path", "required": true, "schema": { "type": "string", "pattern": "^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", "title": "Service Key" - }, - "name": "service_key", - "in": "path" + } }, { + "name": "service_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": "Service Version" - }, - "name": "service_version", - "in": "path" + } }, { + "name": "user_id", + "in": "query", "required": true, "schema": { "type": "integer", "title": "User Id" - }, - "name": "user_id", - "in": "query" + } }, { + "name": "x-simcore-products-name", + "in": "header", "required": false, "schema": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "X-Simcore-Products-Name" - }, - "name": "x-simcore-products-name", - "in": "header" + } } ], "requestBody": { + "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ServiceUpdate" } } - }, - "required": true + } }, "responses": { "200": { @@ -590,7 +601,14 @@ "description": "Email address" }, "affiliation": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Affiliation" } }, @@ -633,8 +651,8 @@ ], "title": "Badge", "example": { - "name": "osparc.io", "image": "https://img.shields.io/website-up-down-green-red/https/itisfoundation.github.io.svg?label=documentation", + "name": "osparc.io", "url": "https://itisfoundation.github.io/" } }, @@ -650,11 +668,18 @@ "title": "Version" }, "released": { - "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", + "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" } @@ -667,25 +692,35 @@ "title": "BaseMeta", "example": { "name": "simcore_service_foo", - "version": "2.4.45", "released": { "v1": "1.3.4", "v2": "2.4.45" - } + }, + "version": "2.4.45" } }, "BindOptions": { "properties": { "Propagation": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/Propagation" + }, + { + "type": "null" } ], "description": "A propagation mode with the value `[r]private`, `[r]shared`, or `[r]slave`." }, "NonRecursive": { - "type": "boolean", + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], "title": "Nonrecursive", "description": "Disable recursive bind mount.", "default": false @@ -720,8 +755,7 @@ "GPU", "MPI" ], - "title": "BootMode", - "description": "An enumeration." + "title": "BootMode" }, "BootOption": { "properties": { @@ -767,26 +801,49 @@ "Config1": { "properties": { "File": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/File1" + }, + { + "type": "null" } ], - "title": "File", "description": "File represents a specific target that is backed by a file.\n\n


\n\n> **Note**: `Configs.File` and `Configs.Runtime` are mutually exclusive\n" }, "Runtime": { - "type": "object", + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], "title": "Runtime", "description": "Runtime represents a target that is not mounted into the\ncontainer but is used by the task\n\n


\n\n> **Note**: `Configs.File` and `Configs.Runtime` are mutually\n> exclusive\n" }, "ConfigID": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Configid", "description": "ConfigID represents the ID of the specific config that we're\nreferencing.\n" }, "ConfigName": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Configname", "description": "ConfigName is the name of the config that this references,\nbut this is just provided for lookup/display purposes. The\nconfig in the reference will be identified by its ID.\n" } @@ -797,218 +854,403 @@ "ContainerSpec": { "properties": { "Image": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Image", "description": "The image name to use for the container" }, "Labels": { - "additionalProperties": { - "type": "string" - }, - "type": "object", + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], "title": "Labels", "description": "User-defined key/value data." }, "Command": { - "items": { - "type": "string" - }, - "type": "array", + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Command", "description": "The command to be run in the image." }, "Args": { - "items": { - "type": "string" - }, - "type": "array", + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Args", "description": "Arguments to the command." }, "Hostname": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Hostname", "description": "The hostname to use for the container, as a valid\n[RFC 1123](https://tools.ietf.org/html/rfc1123) hostname.\n" }, "Env": { - "items": { - "type": "string" - }, - "type": "array", + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Env", "description": "A list of environment variables in the form `VAR=value`.\n" }, "Dir": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Dir", "description": "The working directory for commands to run in." }, "User": { - "type": "string", - "title": "User", - "description": "The user inside the container." + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "User", + "description": "The user inside the container." }, "Groups": { - "items": { - "type": "string" - }, - "type": "array", + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Groups", "description": "A list of additional groups that the container process will run as.\n" }, "Privileges": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/Privileges" + }, + { + "type": "null" } ], - "title": "Privileges", "description": "Security options for the container" }, "TTY": { - "type": "boolean", + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], "title": "Tty", "description": "Whether a pseudo-TTY should be allocated." }, "OpenStdin": { - "type": "boolean", + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], "title": "Openstdin", "description": "Open `stdin`" }, "ReadOnly": { - "type": "boolean", + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], "title": "Readonly", "description": "Mount the container's root filesystem as read only." }, "Mounts": { - "items": { - "$ref": "#/components/schemas/Mount" - }, - "type": "array", + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/Mount" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Mounts", "description": "Specification for mounts to be added to containers created as part\nof the service.\n" }, "StopSignal": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Stopsignal", "description": "Signal to stop the container." }, "StopGracePeriod": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Stopgraceperiod", "description": "Amount of time to wait for the container to terminate before\nforcefully killing it.\n" }, "HealthCheck": { - "$ref": "#/components/schemas/HealthConfig" + "anyOf": [ + { + "$ref": "#/components/schemas/HealthConfig" + }, + { + "type": "null" + } + ] }, "Hosts": { - "items": { - "type": "string" - }, - "type": "array", + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Hosts", "description": "A list of hostname/IP mappings to add to the container's `hosts`\nfile. The format of extra hosts is specified in the\n[hosts(5)](http://man7.org/linux/man-pages/man5/hosts.5.html)\nman page:\n\n IP_address canonical_hostname [aliases...]\n" }, "DNSConfig": { - "allOf": [ + "anyOf": [ { - "$ref": "#/components/schemas/DNSConfig" + "$ref": "#/components/schemas/DnsConfig" + }, + { + "type": "null" } ], - "title": "Dnsconfig", "description": "Specification for DNS related configurations in resolver configuration\nfile (`resolv.conf`).\n" }, "Secrets": { - "items": { - "$ref": "#/components/schemas/Secret" - }, - "type": "array", + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/Secret" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Secrets", "description": "Secrets contains references to zero or more secrets that will be\nexposed to the service.\n" }, "Configs": { - "items": { - "$ref": "#/components/schemas/Config1" - }, - "type": "array", + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/Config1" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Configs", "description": "Configs contains references to zero or more configs that will be\nexposed to the service.\n" }, "Isolation": { - "allOf": [ + "anyOf": [ + { + "$ref": "#/components/schemas/Isolation1" + }, { - "$ref": "#/components/schemas/Isolation" + "type": "null" } ], "description": "Isolation technology of the containers running the service.\n(Windows only)\n" }, "Init": { - "type": "boolean", + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], "title": "Init", "description": "Run an init inside the container that forwards signals and reaps\nprocesses. This field is omitted if empty, and the default (as\nconfigured on the daemon) is used.\n" }, "Sysctls": { - "additionalProperties": { - "type": "string" - }, - "type": "object", + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], "title": "Sysctls", "description": "Set kernel namedspaced parameters (sysctls) in the container.\nThe Sysctls option on services accepts the same sysctls as the\nare supported on containers. Note that while the same sysctls are\nsupported, no guarantees or checks are made about their\nsuitability for a clustered environment, and it's up to the user\nto determine whether a given sysctl will work properly in a\nService.\n" }, "CapabilityAdd": { - "items": { - "type": "string" - }, - "type": "array", + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Capabilityadd", - "description": "A list of kernel capabilities to add to the default set\nfor the container.\n", - "example": [ - "CAP_NET_RAW", - "CAP_SYS_ADMIN", - "CAP_SYS_CHROOT", - "CAP_SYSLOG" - ] + "description": "A list of kernel capabilities to add to the default set\nfor the container.\n" }, "CapabilityDrop": { - "items": { - "type": "string" - }, - "type": "array", + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Capabilitydrop", - "description": "A list of kernel capabilities to drop from the default set\nfor the container.\n", - "example": [ - "CAP_NET_RAW" - ] + "description": "A list of kernel capabilities to drop from the default set\nfor the container.\n" }, "Ulimits": { - "items": { - "$ref": "#/components/schemas/Ulimit1" - }, - "type": "array", + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/Ulimit" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Ulimits", "description": "A list of resource limits to set in the container. For example: `{\"Name\": \"nofile\", \"Soft\": 1024, \"Hard\": 2048}`\"\n" } }, "type": "object", "title": "ContainerSpec", - "description": " Container spec for the service.\n\n


\n\n> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are\n> mutually exclusive. PluginSpec is only used when the Runtime field\n> is set to `plugin`. NetworkAttachmentSpec is used when the Runtime\n> field is set to `attachment`." + "description": "Container spec for the service.\n\n


\n\n> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are\n> mutually exclusive. PluginSpec is only used when the Runtime field\n> is set to `plugin`. NetworkAttachmentSpec is used when the Runtime\n> field is set to `attachment`." }, "CredentialSpec": { "properties": { "Config": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Config", - "description": "Load credential spec from a Swarm Config with the given ID.\nThe specified config must also be present in the Configs\nfield with the Runtime property set.\n\n


\n\n\n> **Note**: `CredentialSpec.File`, `CredentialSpec.Registry`,\n> and `CredentialSpec.Config` are mutually exclusive.\n", - "example": "0bt9dmxjvjiqermk6xrop3ekq" + "description": "Load credential spec from a Swarm Config with the given ID.\nThe specified config must also be present in the Configs\nfield with the Runtime property set.\n\n


\n\n\n> **Note**: `CredentialSpec.File`, `CredentialSpec.Registry`,\n> and `CredentialSpec.Config` are mutually exclusive.\n" }, "File": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "File", - "description": "Load credential spec from this file. The file is read by\nthe daemon, and must be present in the `CredentialSpecs`\nsubdirectory in the docker data directory, which defaults\nto `C:\\ProgramData\\Docker\\` on Windows.\n\nFor example, specifying `spec.json` loads\n`C:\\ProgramData\\Docker\\CredentialSpecs\\spec.json`.\n\n


\n\n> **Note**: `CredentialSpec.File`, `CredentialSpec.Registry`,\n> and `CredentialSpec.Config` are mutually exclusive.\n", - "example": "spec.json" + "description": "Load credential spec from this file. The file is read by\nthe daemon, and must be present in the `CredentialSpecs`\nsubdirectory in the docker data directory, which defaults\nto `C:\\ProgramData\\Docker\\` on Windows.\n\nFor example, specifying `spec.json` loads\n`C:\\ProgramData\\Docker\\CredentialSpecs\\spec.json`.\n\n


\n\n> **Note**: `CredentialSpec.File`, `CredentialSpec.Registry`,\n> and `CredentialSpec.Config` are mutually exclusive.\n" }, "Registry": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Registry", "description": "Load credential spec from this value in the Windows\nregistry. The specified registry value must be located in:\n\n`HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Virtualization\\Containers\\CredentialSpecs`\n\n


\n\n\n> **Note**: `CredentialSpec.File`, `CredentialSpec.Registry`,\n> and `CredentialSpec.Config` are mutually exclusive.\n" } @@ -1017,63 +1259,112 @@ "title": "CredentialSpec", "description": "CredentialSpec for managed service account (Windows only)" }, - "DNSConfig": { + "DiscreteResourceSpec": { + "properties": { + "Kind": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Kind" + }, + "Value": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Value" + } + }, + "type": "object", + "title": "DiscreteResourceSpec" + }, + "DnsConfig": { "properties": { "Nameservers": { - "items": { - "type": "string" - }, - "type": "array", + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Nameservers", "description": "The IP addresses of the name servers." }, "Search": { - "items": { - "type": "string" - }, - "type": "array", + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Search", "description": "A search list for host-name lookup." }, "Options": { - "items": { - "type": "string" - }, - "type": "array", + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Options", "description": "A list of internal resolver variables to be modified (e.g.,\n`debug`, `ndots:3`, etc.).\n" } }, "type": "object", - "title": "DNSConfig", - "description": " Specification for DNS related configurations in resolver configuration\nfile (`resolv.conf`)." - }, - "DiscreteResourceSpec": { - "properties": { - "Kind": { - "type": "string", - "title": "Kind" - }, - "Value": { - "type": "integer", - "title": "Value" - } - }, - "type": "object", - "title": "DiscreteResourceSpec" + "title": "DnsConfig", + "description": "Specification for DNS related configurations in resolver configuration\nfile (`resolv.conf`)." }, "DriverConfig": { "properties": { "Name": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Name", "description": "Name of the driver to use to create the volume." }, "Options": { - "additionalProperties": { - "type": "string" - }, - "type": "object", + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], "title": "Options", "description": "key/value map of driver specific options." } @@ -1085,31 +1376,61 @@ "EndpointPortConfig": { "properties": { "Name": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Name" }, "Protocol": { - "$ref": "#/components/schemas/Type" + "anyOf": [ + { + "$ref": "#/components/schemas/Type" + }, + { + "type": "null" + } + ] }, "TargetPort": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Targetport", "description": "The port inside the container." }, "PublishedPort": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Publishedport", "description": "The port on the swarm hosts." }, "PublishMode": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/PublishMode" + }, + { + "type": "null" } ], "description": "The mode in which port is published.\n\n


\n\n- \"ingress\" makes the target port accessible on every node,\n regardless of whether there is a task for the service running on\n that node or not.\n- \"host\" bypasses the routing mesh and publish the port directly on\n the swarm node where that service is running.\n", - "default": "ingress", - "example": "ingress" + "default": "ingress" } }, "type": "object", @@ -1118,19 +1439,29 @@ "EndpointSpec": { "properties": { "Mode": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/Mode1" + }, + { + "type": "null" } ], "description": "The mode of resolution to use for internal load balancing between tasks.\n", "default": "vip" }, "Ports": { - "items": { - "$ref": "#/components/schemas/EndpointPortConfig" - }, - "type": "array", + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/EndpointPortConfig" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Ports", "description": "List of exposed ports that this service is accessible on from the\noutside. Ports can only be provided if `vip` resolution mode is used.\n" } @@ -1147,7 +1478,7 @@ "rollback" ], "title": "FailureAction", - "description": " Action to take if an updated task fails to run, or stops running\nduring the update." + "description": "Action to take if an updated task fails to run, or stops running\nduring the update." }, "FailureAction1": { "type": "string", @@ -1156,27 +1487,55 @@ "pause" ], "title": "FailureAction1", - "description": " Action to take if an rolled back task fails to run, or stops\nrunning during the rollback." + "description": "Action to take if an rolled back task fails to run, or stops\nrunning during the rollback." }, "File": { "properties": { "Name": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Name", "description": "Name represents the final filename in the filesystem.\n" }, "UID": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Uid", "description": "UID represents the file UID." }, "GID": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Gid", "description": "GID represents the file GID." }, "Mode": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Mode", "description": "Mode represents the FileMode of the file." } @@ -1188,37 +1547,79 @@ "File1": { "properties": { "Name": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Name", "description": "Name represents the final filename in the filesystem.\n" }, "UID": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Uid", "description": "UID represents the file UID." }, "GID": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Gid", "description": "GID represents the file GID." }, "Mode": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Mode", "description": "Mode represents the FileMode of the file." } }, "type": "object", "title": "File1", - "description": " File represents a specific target that is backed by a file.\n\n


\n\n> **Note**: `Configs.File` and `Configs.Runtime` are mutually exclusive" + "description": "File represents a specific target that is backed by a file.\n\n


\n\n> **Note**: `Configs.File` and `Configs.Runtime` are mutually exclusive" }, "GenericResource": { "properties": { "NamedResourceSpec": { - "$ref": "#/components/schemas/NamedResourceSpec" + "anyOf": [ + { + "$ref": "#/components/schemas/NamedResourceSpec" + }, + { + "type": "null" + } + ] }, "DiscreteResourceSpec": { - "$ref": "#/components/schemas/DiscreteResourceSpec" + "anyOf": [ + { + "$ref": "#/components/schemas/DiscreteResourceSpec" + }, + { + "type": "null" + } + ] } }, "type": "object", @@ -1230,27 +1631,7 @@ }, "type": "array", "title": "GenericResources", - "description": "User-defined resources can be either Integer resources (e.g, `SSD=3`) or\nString resources (e.g, `GPU=UUID1`).\n", - "example": [ - { - "DiscreteResourceSpec": { - "Kind": "SSD", - "Value": 3 - } - }, - { - "NamedResourceSpec": { - "Kind": "GPU", - "Value": "UUID1" - } - }, - { - "NamedResourceSpec": { - "Kind": "GPU", - "Value": "UUID2" - } - } - ] + "description": "User-defined resources can be either Integer resources (e.g, `SSD=3`) or\nString resources (e.g, `GPU=UUID1`)." }, "HTTPValidationError": { "properties": { @@ -1268,30 +1649,65 @@ "HealthConfig": { "properties": { "Test": { - "items": { - "type": "string" - }, - "type": "array", + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Test", "description": "The test to perform. Possible values are:\n\n- `[]` inherit healthcheck from image or parent image\n- `[\"NONE\"]` disable healthcheck\n- `[\"CMD\", args...]` exec arguments directly\n- `[\"CMD-SHELL\", command]` run command with system's default shell\n" }, "Interval": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Interval", "description": "The time to wait between checks in nanoseconds. It should be 0 or at\nleast 1000000 (1 ms). 0 means inherit.\n" }, "Timeout": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Timeout", "description": "The time to wait before considering the check to have hung. It should\nbe 0 or at least 1000000 (1 ms). 0 means inherit.\n" }, "Retries": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Retries", "description": "The number of consecutive failures needed to consider a container as\nunhealthy. 0 means inherit.\n" }, "StartPeriod": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Startperiod", "description": "Start period for the container to initialize before starting\nhealth-retries countdown in nanoseconds. It should be 0 or at least\n1000000 (1 ms). 0 means inherit.\n" } @@ -1320,6 +1736,7 @@ "$ref": "#/components/schemas/BootMode" }, "type": "array", + "title": "Boot Modes", "description": "describe how a service shall be booted, using CPU, MPI, openMP or GPU", "default": [ "CPU" @@ -1335,6 +1752,14 @@ "example": { "image": "simcore/service/dynamic/pretty-intense:1.0.0", "resources": { + "AIRAM": { + "limit": 1, + "reservation": 1 + }, + "ANY_resource": { + "limit": "some_value", + "reservation": "some_value" + }, "CPU": { "limit": 4, "reservation": 0.1 @@ -1346,46 +1771,56 @@ "VRAM": { "limit": 1, "reservation": 1 - }, - "AIRAM": { - "limit": 1, - "reservation": 1 - }, - "ANY_resource": { - "limit": "some_value", - "reservation": "some_value" } } } }, - "Isolation": { + "Isolation1": { "type": "string", "enum": [ "default", "process", "hyperv" ], - "title": "Isolation", - "description": "Isolation technology of the container. (Windows only)" + "title": "Isolation1", + "description": "Isolation technology of the containers running the service.\n(Windows only)" }, "Limit": { "properties": { "NanoCPUs": { - "type": "integer", - "title": "Nanocpus", - "example": 4000000000 + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Nanocpus" }, "MemoryBytes": { - "type": "integer", - "title": "Memorybytes", - "example": 8272408576 + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Memorybytes" }, "Pids": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Pids", "description": "Limits the maximum number of PIDs in the container. Set `0` for unlimited.\n", - "default": 0, - "example": 100 + "default": 0 } }, "type": "object", @@ -1395,41 +1830,78 @@ "LogDriver1": { "properties": { "Name": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Name" }, "Options": { - "additionalProperties": { - "type": "string" - }, - "type": "object", + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], "title": "Options" } }, "type": "object", "title": "LogDriver1", - "description": " Specifies the log driver to use for tasks created from this spec. If\nnot present, the default one for the swarm will be used, finally\nfalling back to the engine default if not specified." + "description": "Specifies the log driver to use for tasks created from this spec. If\nnot present, the default one for the swarm will be used, finally\nfalling back to the engine default if not specified." }, "Mode": { "properties": { "Replicated": { - "$ref": "#/components/schemas/Replicated" + "anyOf": [ + { + "$ref": "#/components/schemas/Replicated" + }, + { + "type": "null" + } + ] }, "Global": { - "type": "object", + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], "title": "Global" }, "ReplicatedJob": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/ReplicatedJob" + }, + { + "type": "null" } ], - "title": "Replicatedjob", "description": "The mode used for services with a finite number of tasks that run\nto a completed state.\n" }, "GlobalJob": { - "type": "object", + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], "title": "Globaljob", "description": "The mode used for services which run a task to the completed state\non each valid node.\n" } @@ -1450,58 +1922,95 @@ "Mount": { "properties": { "Target": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Target", "description": "Container path." }, "Source": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Source", "description": "Mount source (e.g. a volume name, a host path)." }, "Type": { - "allOf": [ + "anyOf": [ { - "$ref": "#/components/schemas/Type1" + "$ref": "#/components/schemas/Type2" + }, + { + "type": "null" } ], "description": "The mount type. Available types:\n\n- `bind` Mounts a file or directory from the host into the container. Must exist prior to creating the container.\n- `volume` Creates a volume with the given name and options (or uses a pre-existing volume with the same name and options). These are **not** removed when the container is removed.\n- `tmpfs` Create a tmpfs with the given options. The mount source cannot be specified for tmpfs.\n- `npipe` Mounts a named pipe from the host into the container. Must exist prior to creating the container.\n" }, "ReadOnly": { - "type": "boolean", + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], "title": "Readonly", "description": "Whether the mount should be read-only." }, "Consistency": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Consistency", "description": "The consistency requirement for the mount: `default`, `consistent`, `cached`, or `delegated`." }, "BindOptions": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/BindOptions" + }, + { + "type": "null" } ], - "title": "Bindoptions", "description": "Optional configuration for the `bind` type." }, "VolumeOptions": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/VolumeOptions" + }, + { + "type": "null" } ], - "title": "Volumeoptions", "description": "Optional configuration for the `volume` type." }, "TmpfsOptions": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/TmpfsOptions" + }, + { + "type": "null" } ], - "title": "Tmpfsoptions", "description": "Optional configuration for the `tmpfs` type." } }, @@ -1511,11 +2020,25 @@ "NamedResourceSpec": { "properties": { "Kind": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Kind" }, "Value": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Value" } }, @@ -1525,23 +2048,44 @@ "NetworkAttachmentConfig": { "properties": { "Target": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Target", "description": "The target network for attachment. Must be a network name or ID.\n" }, "Aliases": { - "items": { - "type": "string" - }, - "type": "array", + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Aliases", "description": "Discoverable alternate names for the service on this network.\n" }, "DriverOpts": { - "additionalProperties": { - "type": "string" - }, - "type": "object", + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], "title": "Driveropts", "description": "Driver attachment options for the network target.\n" } @@ -1553,14 +2097,21 @@ "NetworkAttachmentSpec": { "properties": { "ContainerID": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Containerid", "description": "ID of the container represented by this task" } }, "type": "object", "title": "NetworkAttachmentSpec", - "description": " Read-only spec type for non-swarm containers attached to swarm overlay\nnetworks.\n\n


\n\n> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are\n> mutually exclusive. PluginSpec is only used when the Runtime field\n> is set to `plugin`. NetworkAttachmentSpec is used when the Runtime\n> field is set to `attachment`." + "description": "Read-only spec type for non-swarm containers attached to swarm overlay\nnetworks.\n\n


\n\n> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are\n> mutually exclusive. PluginSpec is only used when the Runtime field\n> is set to `plugin`. NetworkAttachmentSpec is used when the Runtime\n> field is set to `attachment`." }, "Order": { "type": "string", @@ -1569,56 +2120,74 @@ "start-first" ], "title": "Order", - "description": " The order of operations when rolling out an updated task. Either\nthe old task is shut down before the new task is started, or the\nnew task is started before the old task is shut down." + "description": "The order of operations when rolling out an updated task. Either\nthe old task is shut down before the new task is started, or the\nnew task is started before the old task is shut down." + }, + "Order1": { + "type": "string", + "enum": [ + "stop-first", + "start-first" + ], + "title": "Order1", + "description": "The order of operations when rolling back a task. Either the old\ntask is shut down before the new task is started, or the new task\nis started before the old task is shut down." }, "Placement": { "properties": { "Constraints": { - "items": { - "type": "string" - }, - "type": "array", + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Constraints", - "description": "An array of constraint expressions to limit the set of nodes where\na task can be scheduled. Constraint expressions can either use a\n_match_ (`==`) or _exclude_ (`!=`) rule. Multiple constraints find\nnodes that satisfy every expression (AND match). Constraints can\nmatch node or Docker Engine labels as follows:\n\nnode attribute | matches | example\n---------------------|--------------------------------|-----------------------------------------------\n`node.id` | Node ID | `node.id==2ivku8v2gvtg4`\n`node.hostname` | Node hostname | `node.hostname!=node-2`\n`node.role` | Node role (`manager`/`worker`) | `node.role==manager`\n`node.platform.os` | Node operating system | `node.platform.os==windows`\n`node.platform.arch` | Node architecture | `node.platform.arch==x86_64`\n`node.labels` | User-defined node labels | `node.labels.security==high`\n`engine.labels` | Docker Engine's labels | `engine.labels.operatingsystem==ubuntu-14.04`\n\n`engine.labels` apply to Docker Engine labels like operating system,\ndrivers, etc. Swarm administrators add `node.labels` for operational\npurposes by using the [`node update endpoint`](#operation/NodeUpdate).\n", - "example": [ - "node.hostname!=node3.corp.example.com", - "node.role!=manager", - "node.labels.type==production", - "node.platform.os==linux", - "node.platform.arch==x86_64" - ] + "description": "An array of constraint expressions to limit the set of nodes where\na task can be scheduled. Constraint expressions can either use a\n_match_ (`==`) or _exclude_ (`!=`) rule. Multiple constraints find\nnodes that satisfy every expression (AND match). Constraints can\nmatch node or Docker Engine labels as follows:\n\nnode attribute | matches | example\n---------------------|--------------------------------|-----------------------------------------------\n`node.id` | Node ID | `node.id==2ivku8v2gvtg4`\n`node.hostname` | Node hostname | `node.hostname!=node-2`\n`node.role` | Node role (`manager`/`worker`) | `node.role==manager`\n`node.platform.os` | Node operating system | `node.platform.os==windows`\n`node.platform.arch` | Node architecture | `node.platform.arch==x86_64`\n`node.labels` | User-defined node labels | `node.labels.security==high`\n`engine.labels` | Docker Engine's labels | `engine.labels.operatingsystem==ubuntu-14.04`\n\n`engine.labels` apply to Docker Engine labels like operating system,\ndrivers, etc. Swarm administrators add `node.labels` for operational\npurposes by using the [`node update endpoint`](#operation/NodeUpdate).\n" }, "Preferences": { - "items": { - "$ref": "#/components/schemas/Preference" - }, - "type": "array", - "title": "Preferences", - "description": "Preferences provide a way to make the scheduler aware of factors\nsuch as topology. They are provided in order from highest to\nlowest precedence.\n", - "example": [ + "anyOf": [ { - "Spread": { - "SpreadDescriptor": "node.labels.datacenter" - } + "items": { + "$ref": "#/components/schemas/Preference" + }, + "type": "array" }, { - "Spread": { - "SpreadDescriptor": "node.labels.rack" - } + "type": "null" } - ] + ], + "title": "Preferences", + "description": "Preferences provide a way to make the scheduler aware of factors\nsuch as topology. They are provided in order from highest to\nlowest precedence.\n" }, "MaxReplicas": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Maxreplicas", "description": "Maximum number of replicas for per node (default value is 0, which\nis unlimited)\n", "default": 0 }, "Platforms": { - "items": { - "$ref": "#/components/schemas/Platform" - }, - "type": "array", + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/Platform" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Platforms", "description": "Platforms stores all the platforms that the service's image can\nrun on. This field is used in the platform filter for scheduling.\nIf empty, then the platform filter is off, meaning there are no\nscheduling restrictions.\n" } @@ -1629,16 +2198,28 @@ "Platform": { "properties": { "Architecture": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Architecture", - "description": "Architecture represents the hardware architecture (for example,\n`x86_64`).\n", - "example": "x86_64" + "description": "Architecture represents the hardware architecture (for example,\n`x86_64`).\n" }, "OS": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Os", - "description": "OS represents the Operating System (for example, `linux` or `windows`).\n", - "example": "linux" + "description": "OS represents the Operating System (for example, `linux` or `windows`).\n" } }, "type": "object", @@ -1648,62 +2229,114 @@ "PluginPrivilege": { "properties": { "Name": { - "type": "string", - "title": "Name", - "example": "network" + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name" }, "Description": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Description" }, "Value": { - "items": { - "type": "string" - }, - "type": "array", - "title": "Value", - "example": [ - "host" - ] + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Value" } }, "type": "object", "title": "PluginPrivilege", - "description": " Describes a permission the user has to accept upon installing\nthe plugin." + "description": "Describes a permission the user has to accept upon installing\nthe plugin." }, "PluginSpec": { "properties": { "Name": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Name", "description": "The name or 'alias' to use for the plugin." }, "Remote": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Remote", "description": "The plugin image reference to use." }, "Disabled": { - "type": "boolean", + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], "title": "Disabled", "description": "Disable the plugin once scheduled." }, "PluginPrivilege": { - "items": { - "$ref": "#/components/schemas/PluginPrivilege" - }, - "type": "array", + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/PluginPrivilege" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Pluginprivilege" } }, "type": "object", "title": "PluginSpec", - "description": " Plugin spec for the service. *(Experimental release only.)*\n\n


\n\n> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are\n> mutually exclusive. PluginSpec is only used when the Runtime field\n> is set to `plugin`. NetworkAttachmentSpec is used when the Runtime\n> field is set to `attachment`." + "description": "Plugin spec for the service. *(Experimental release only.)*\n\n


\n\n> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are\n> mutually exclusive. PluginSpec is only used when the Runtime field\n> is set to `plugin`. NetworkAttachmentSpec is used when the Runtime\n> field is set to `attachment`." }, "Preference": { "properties": { "Spread": { - "$ref": "#/components/schemas/Spread" + "anyOf": [ + { + "$ref": "#/components/schemas/Spread" + }, + { + "type": "null" + } + ] } }, "type": "object", @@ -1712,21 +2345,25 @@ "Privileges": { "properties": { "CredentialSpec": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/CredentialSpec" + }, + { + "type": "null" } ], - "title": "Credentialspec", "description": "CredentialSpec for managed service account (Windows only)" }, "SELinuxContext": { - "allOf": [ + "anyOf": [ + { + "$ref": "#/components/schemas/SeLinuxContext" + }, { - "$ref": "#/components/schemas/SELinuxContext" + "type": "null" } ], - "title": "Selinuxcontext", "description": "SELinux labels of the container" } }, @@ -1754,12 +2391,19 @@ "host" ], "title": "PublishMode", - "description": " The mode in which port is published.\n\n


\n\n- \"ingress\" makes the target port accessible on every node,\n regardless of whether there is a task for the service running on\n that node or not.\n- \"host\" bypasses the routing mesh and publish the port directly on\n the swarm node where that service is running." + "description": "The mode in which port is published.\n\n


\n\n- \"ingress\" makes the target port accessible on every node,\n regardless of whether there is a task for the service running on\n that node or not.\n- \"host\" bypasses the routing mesh and publish the port directly on\n the swarm node where that service is running." }, "Replicated": { "properties": { "Replicas": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Replicas" } }, @@ -1769,40 +2413,73 @@ "ReplicatedJob": { "properties": { "MaxConcurrent": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Maxconcurrent", "description": "The maximum number of replicas to run simultaneously.\n", "default": 1 }, "TotalCompletions": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Totalcompletions", "description": "The total number of replicas desired to reach the Completed\nstate. If unset, will default to the value of `MaxConcurrent`\n" } }, "type": "object", "title": "ReplicatedJob", - "description": " The mode used for services with a finite number of tasks that run\nto a completed state." + "description": "The mode used for services with a finite number of tasks that run\nto a completed state." }, "ResourceObject": { "properties": { "NanoCPUs": { - "type": "integer", - "title": "Nanocpus", - "example": 4000000000 + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Nanocpus" }, "MemoryBytes": { - "type": "integer", - "title": "Memorybytes", - "example": 8272408576 + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Memorybytes" }, "GenericResources": { - "$ref": "#/components/schemas/GenericResources" + "anyOf": [ + { + "$ref": "#/components/schemas/GenericResources" + }, + { + "type": "null" + } + ] } }, "type": "object", "title": "ResourceObject", - "description": " An object describing the resources which can be advertised by a node and\nrequested by a task." + "description": "An object describing the resources which can be advertised by a node and\nrequested by a task." }, "ResourceValue": { "properties": { @@ -1845,51 +2522,79 @@ "Resources1": { "properties": { "Limits": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/Limit" + }, + { + "type": "null" } ], - "title": "Limits", "description": "Define resources limits." }, "Reservations": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/ResourceObject" + }, + { + "type": "null" } ], - "title": "Reservations", "description": "Define resources reservation." } }, "type": "object", "title": "Resources1", - "description": " Resource requirements which apply to each individual container created\nas part of the service." + "description": "Resource requirements which apply to each individual container created\nas part of the service." }, "RestartPolicy1": { "properties": { "Condition": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/Condition" + }, + { + "type": "null" } ], "description": "Condition for restart." }, "Delay": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Delay", "description": "Delay between restart attempts." }, "MaxAttempts": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Maxattempts", "description": "Maximum attempts to restart a given container before giving up\n(default value is 0, which is ignored).\n", "default": 0 }, "Window": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Window", "description": "Windows is the time window used to evaluate the restart policy\n(default value is 0, which is unbounded).\n", "default": 0 @@ -1897,43 +2602,77 @@ }, "type": "object", "title": "RestartPolicy1", - "description": " Specification for the restart policy which applies to containers\ncreated as part of this service." + "description": "Specification for the restart policy which applies to containers\ncreated as part of this service." }, "RollbackConfig": { "properties": { "Parallelism": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Parallelism", "description": "Maximum number of tasks to be rolled back in one iteration (0 means\nunlimited parallelism).\n" }, "Delay": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Delay", "description": "Amount of time between rollback iterations, in nanoseconds.\n" }, "FailureAction": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/FailureAction1" + }, + { + "type": "null" } ], "description": "Action to take if an rolled back task fails to run, or stops\nrunning during the rollback.\n" }, "Monitor": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Monitor", "description": "Amount of time to monitor each rolled back task for failures, in\nnanoseconds.\n" }, "MaxFailureRatio": { - "type": "number", + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], "title": "Maxfailureratio", "description": "The fraction of tasks that may fail during a rollback before the\nfailure action is invoked, specified as a floating point number\nbetween 0 and 1.\n", "default": 0 }, "Order": { - "allOf": [ + "anyOf": [ { - "$ref": "#/components/schemas/Order" + "$ref": "#/components/schemas/Order1" + }, + { + "type": "null" } ], "description": "The order of operations when rolling back a task. Either the old\ntask is shut down before the new task is started, or the new task\nis started before the old task is shut down.\n" @@ -1943,56 +2682,107 @@ "title": "RollbackConfig", "description": "Specification for the rollback strategy of the service." }, - "SELinuxContext": { + "SeLinuxContext": { "properties": { "Disable": { - "type": "boolean", + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], "title": "Disable", "description": "Disable SELinux" }, "User": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "User", "description": "SELinux user label" }, "Role": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Role", "description": "SELinux role label" }, "Type": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Type", "description": "SELinux type label" }, "Level": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Level", "description": "SELinux level label" } }, "type": "object", - "title": "SELinuxContext", + "title": "SeLinuxContext", "description": "SELinux labels of the container" }, "Secret": { "properties": { "File": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/File" + }, + { + "type": "null" } ], - "title": "File", "description": "File represents a specific target that is backed by a file.\n" }, "SecretID": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Secretid", "description": "SecretID represents the ID of the specific secret that we're\nreferencing.\n" }, "SecretName": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Secretname", "description": "SecretName is the name of the secret that this references,\nbut this is just provided for lookup/display purposes. The\nsecret in the reference will be identified by its ID.\n" } @@ -2054,14 +2844,17 @@ "name": { "type": "string", "title": "Name", - "description": "Display name: short, human readable name for the node", - "example": "Fast Counter" + "description": "Display name: short, human readable name for the node" }, "thumbnail": { - "type": "string", - "maxLength": 2083, - "minLength": 1, - "format": "uri", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Thumbnail", "description": "url to the thumbnail" }, @@ -2077,21 +2870,42 @@ "default": false }, "version_display": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Version Display", "description": "A user-friendly or marketing name for the release. This can be used to reference the release in a more readable and recognizable format, such as 'Matterhorn Release,' 'Spring Update,' or 'Holiday Edition.' This name is not used for version comparison but is useful for communication and documentation purposes." }, "deprecated": { - "type": "string", - "format": "date-time", + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], "title": "Deprecated", "description": "Owner can set the date to retire the service. Three possibilities:If None, the service is marked as `published`;If now=deprecated, the service is retired" }, "classifiers": { - "items": { - "type": "string" - }, - "type": "array", + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Classifiers" }, "quality": { @@ -2100,10 +2914,17 @@ "default": {} }, "accessRights": { - "additionalProperties": { - "$ref": "#/components/schemas/ServiceGroupAccessRights" - }, - "type": "object", + "anyOf": [ + { + "additionalProperties": { + "$ref": "#/components/schemas/ServiceGroupAccessRights" + }, + "type": "object" + }, + { + "type": "null" + } + ], "title": "Accessrights", "description": "service access rights per group id" }, @@ -2120,30 +2941,47 @@ "description": "service version number" }, "release_date": { - "type": "string", - "format": "date-time", + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], "title": "Release Date", "description": "A timestamp when the specific version of the service was released. This field helps in tracking the timeline of releases and understanding the sequence of updates. A timestamp string should be formatted as YYYY-MM-DD[T]HH:MM[:SS[.ffffff]][Z or [\u00b1]HH[:]MM]" }, "integration-version": { - "type": "string", - "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", + "anyOf": [ + { + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" + }, + { + "type": "null" + } + ], "title": "Integration-Version", "description": "This version is used to maintain backward compatibility when there are changes in the way a service is integrated into the framework" }, "type": { - "allOf": [ - { - "$ref": "#/components/schemas/ServiceType" - } - ], + "$ref": "#/components/schemas/ServiceType", "description": "service type" }, "badges": { - "items": { - "$ref": "#/components/schemas/Badge" - }, - "type": "array", + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/Badge" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Badges", "deprecated": true }, @@ -2162,48 +3000,88 @@ "description": "email to correspond to the authors about the node" }, "inputs": { - "additionalProperties": { - "$ref": "#/components/schemas/ServiceInput" - }, - "type": "object", + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], "title": "Inputs", "description": "definition of the inputs of this node" }, "outputs": { - "additionalProperties": { - "$ref": "#/components/schemas/ServiceOutput" - }, - "type": "object", + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], "title": "Outputs", "description": "definition of the outputs of this node" }, "boot-options": { - "additionalProperties": { - "$ref": "#/components/schemas/BootOption" - }, - "type": "object", + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], "title": "Boot-Options", "description": "Service defined boot options. These get injected in the service as env variables." }, "min-visible-inputs": { - "type": "integer", - "minimum": 0, + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "null" + } + ], "title": "Min-Visible-Inputs", "description": "The number of 'data type inputs' displayed by default in the UI. When None all 'data type inputs' are displayed." }, "progress_regexp": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Progress Regexp", "description": "regexp pattern for detecting computational service's progress" }, "image_digest": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Image Digest", "description": "Image manifest digest. Note that this is NOT injected as an image label" }, "owner": { - "type": "string", - "format": "email", + "anyOf": [ + { + "type": "string", + "format": "email" + }, + { + "type": "null" + } + ], "title": "Owner" } }, @@ -2211,16 +3089,17 @@ "required": [ "name", "description", + "classifiers", "key", "version", "type", "authors", "contact", "inputs", - "outputs" + "outputs", + "owner" ], - "title": "ServiceGet", - "description": "Service metadata at publication time\n\n- read-only (can only be changed overwriting the image labels in the registry)\n- base metaddata\n- injected in the image labels\n\nNOTE: This model is serialized in .osparc/metadata.yml and in the labels of the docker image" + "title": "ServiceGet" }, "ServiceGroupAccessRights": { "properties": { @@ -2243,7 +3122,14 @@ "ServiceInput": { "properties": { "displayOrder": { - "type": "number", + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], "title": "Displayorder", "description": "DEPRECATED: new display order is taken from the item position. This will be removed.", "deprecated": true @@ -2251,14 +3137,12 @@ "label": { "type": "string", "title": "Label", - "description": "short name for the property", - "example": "Age" + "description": "short name for the property" }, "description": { "type": "string", "title": "Description", - "description": "description of the property", - "example": "Age in seconds since 1970" + "description": "description of the property" }, "type": { "type": "string", @@ -2267,21 +3151,38 @@ "description": "data type expected on this input glob matching for data type is allowed" }, "contentSchema": { - "type": "object", + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], "title": "Contentschema", "description": "jsonschema of this input/output. Required when type='ref_contentSchema'" }, "fileToKeyMap": { - "additionalProperties": { - "type": "string", - "pattern": "^[-_a-zA-Z0-9]+$" - }, - "type": "object", + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], "title": "Filetokeymap", "description": "Place the data associated with the named keys in files" }, "unit": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Unit", "description": "Units, when it refers to a physical quantity", "deprecated": true @@ -2299,18 +3200,23 @@ }, { "type": "string" + }, + { + "type": "null" } ], "title": "Defaultvalue", "deprecated": true }, "widget": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/Widget" + }, + { + "type": "null" } ], - "title": "Widget", "description": "custom widget to use instead of the default one determined from the data-type" } }, @@ -2327,7 +3233,14 @@ "ServiceOutput": { "properties": { "displayOrder": { - "type": "number", + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], "title": "Displayorder", "description": "DEPRECATED: new display order is taken from the item position. This will be removed.", "deprecated": true @@ -2335,14 +3248,12 @@ "label": { "type": "string", "title": "Label", - "description": "short name for the property", - "example": "Age" + "description": "short name for the property" }, "description": { "type": "string", "title": "Description", - "description": "description of the property", - "example": "Age in seconds since 1970" + "description": "description of the property" }, "type": { "type": "string", @@ -2351,32 +3262,51 @@ "description": "data type expected on this input glob matching for data type is allowed" }, "contentSchema": { - "type": "object", + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], "title": "Contentschema", "description": "jsonschema of this input/output. Required when type='ref_contentSchema'" }, "fileToKeyMap": { - "additionalProperties": { - "type": "string", - "pattern": "^[-_a-zA-Z0-9]+$" - }, - "type": "object", + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], "title": "Filetokeymap", "description": "Place the data associated with the named keys in files" }, "unit": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Unit", "description": "Units, when it refers to a physical quantity", "deprecated": true }, "widget": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/Widget" + }, + { + "type": "null" } ], - "title": "Widget", "description": "custom widget to use instead of the default one determined from the data-type", "deprecated": true } @@ -2388,8 +3318,7 @@ "description", "type" ], - "title": "ServiceOutput", - "description": "Base class for service input/outputs" + "title": "ServiceOutput" }, "ServicePortGet": { "properties": { @@ -2408,11 +3337,25 @@ "title": "Kind" }, "content_media_type": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Content Media Type" }, "content_schema": { - "type": "object", + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], "title": "Content Schema", "description": "jsonschema for the port's value. SEE https://json-schema.org/understanding-json-schema/" } @@ -2424,72 +3367,113 @@ ], "title": "ServicePortGet", "example": { - "key": "input_1", - "kind": "input", "content_schema": { + "maximum": 5, + "minimum": 0, "title": "Sleep interval", "type": "integer", - "x_unit": "second", - "minimum": 0, - "maximum": 5 - } + "x_unit": "second" + }, + "key": "input_1", + "kind": "input" } }, "ServiceSpec": { "properties": { "Name": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Name", "description": "Name of the service." }, "Labels": { - "additionalProperties": { - "type": "string" - }, - "type": "object", + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], "title": "Labels", "description": "User-defined key/value metadata." }, "TaskTemplate": { - "$ref": "#/components/schemas/TaskSpec" + "anyOf": [ + { + "$ref": "#/components/schemas/TaskSpec" + }, + { + "type": "null" + } + ] }, "Mode": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/Mode" + }, + { + "type": "null" } ], - "title": "Mode", "description": "Scheduling mode for the service." }, "UpdateConfig": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/UpdateConfig" + }, + { + "type": "null" } ], - "title": "Updateconfig", "description": "Specification for the update strategy of the service." }, "RollbackConfig": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/RollbackConfig" + }, + { + "type": "null" } ], - "title": "Rollbackconfig", "description": "Specification for the rollback strategy of the service." }, "Networks": { - "items": { - "$ref": "#/components/schemas/NetworkAttachmentConfig" - }, - "type": "array", + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/NetworkAttachmentConfig" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Networks", "description": "Specifies which networks the service should attach to." }, "EndpointSpec": { - "$ref": "#/components/schemas/EndpointSpec" + "anyOf": [ + { + "$ref": "#/components/schemas/EndpointSpec" + }, + { + "type": "null" + } + ] } }, "type": "object", @@ -2499,21 +3483,25 @@ "ServiceSpecificationsGet": { "properties": { "sidecar": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/ServiceSpec" + }, + { + "type": "null" } ], - "title": "Sidecar", "description": "schedule-time specifications for the service sidecar (follows Docker Service creation API, see https://docs.docker.com/engine/api/v1.25/#operation/ServiceCreate)" }, "service": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/ServiceSpec" + }, + { + "type": "null" } ], - "title": "Service", "description": "schedule-time specifications specifications for the service (follows Docker Service creation API (specifically only the Resources part), see https://docs.docker.com/engine/api/v1.41/#tag/Service/operation/ServiceCreate" } }, @@ -2528,32 +3516,56 @@ "frontend", "backend" ], - "title": "ServiceType", - "description": "An enumeration." + "title": "ServiceType" }, "ServiceUpdate": { "properties": { "accessRights": { - "additionalProperties": { - "$ref": "#/components/schemas/ServiceGroupAccessRights" - }, - "type": "object", + "anyOf": [ + { + "additionalProperties": { + "$ref": "#/components/schemas/ServiceGroupAccessRights" + }, + "type": "object" + }, + { + "type": "null" + } + ], "title": "Accessrights", "description": "service access rights per group id" }, "name": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Name" }, "thumbnail": { - "type": "string", - "maxLength": 2083, - "minLength": 1, - "format": "uri", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Thumbnail" }, "description": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Description" }, "description_ui": { @@ -2562,20 +3574,41 @@ "default": false }, "version_display": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Version Display" }, "deprecated": { - "type": "string", - "format": "date-time", + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], "title": "Deprecated", "description": "Owner can set the date to retire the service. Three possibilities:If None, the service is marked as `published`;If now=deprecated, the service is retired" }, "classifiers": { - "items": { - "type": "string" - }, - "type": "array", + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Classifiers" }, "quality": { @@ -2585,6 +3618,12 @@ } }, "type": "object", + "required": [ + "name", + "thumbnail", + "description", + "classifiers" + ], "title": "ServiceUpdate", "example": { "accessRights": { @@ -2601,13 +3640,23 @@ "write_access": false } }, - "name": "My Human Readable Service Name", - "description": "An interesting service that does something", "classifiers": [ "RRID:SCR_018997", "RRID:SCR_019001" ], + "description": "An interesting service that does something", + "name": "My Human Readable Service Name", "quality": { + "annotations": { + "certificationLink": "", + "certificationStatus": "Uncertified", + "documentation": "", + "limitations": "", + "purpose": "", + "standards": "", + "vandv": "" + }, + "enabled": true, "tsr": { "r01": { "level": 3, @@ -2649,16 +3698,6 @@ "level": 0, "references": "" } - }, - "enabled": true, - "annotations": { - "vandv": "", - "purpose": "", - "standards": "", - "limitations": "", - "documentation": "", - "certificationLink": "", - "certificationStatus": "Uncertified" } } } @@ -2666,7 +3705,14 @@ "Spread": { "properties": { "SpreadDescriptor": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Spreaddescriptor", "description": "label descriptor, such as `engine.labels.az`.\n" } @@ -2706,78 +3752,118 @@ "TaskSpec": { "properties": { "PluginSpec": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/PluginSpec" + }, + { + "type": "null" } ], - "title": "Pluginspec", "description": "Plugin spec for the service. *(Experimental release only.)*\n\n


\n\n> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are\n> mutually exclusive. PluginSpec is only used when the Runtime field\n> is set to `plugin`. NetworkAttachmentSpec is used when the Runtime\n> field is set to `attachment`.\n" }, "ContainerSpec": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/ContainerSpec" + }, + { + "type": "null" } ], - "title": "Containerspec", "description": "Container spec for the service.\n\n


\n\n> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are\n> mutually exclusive. PluginSpec is only used when the Runtime field\n> is set to `plugin`. NetworkAttachmentSpec is used when the Runtime\n> field is set to `attachment`.\n" }, "NetworkAttachmentSpec": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/NetworkAttachmentSpec" + }, + { + "type": "null" } ], - "title": "Networkattachmentspec", "description": "Read-only spec type for non-swarm containers attached to swarm overlay\nnetworks.\n\n


\n\n> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are\n> mutually exclusive. PluginSpec is only used when the Runtime field\n> is set to `plugin`. NetworkAttachmentSpec is used when the Runtime\n> field is set to `attachment`.\n" }, "Resources": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/Resources1" + }, + { + "type": "null" } ], - "title": "Resources", "description": "Resource requirements which apply to each individual container created\nas part of the service.\n" }, "RestartPolicy": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/RestartPolicy1" + }, + { + "type": "null" } ], - "title": "Restartpolicy", "description": "Specification for the restart policy which applies to containers\ncreated as part of this service.\n" }, "Placement": { - "$ref": "#/components/schemas/Placement" + "anyOf": [ + { + "$ref": "#/components/schemas/Placement" + }, + { + "type": "null" + } + ] }, "ForceUpdate": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Forceupdate", "description": "A counter that triggers an update even if no relevant parameters have\nbeen changed.\n" }, "Runtime": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Runtime", "description": "Runtime is the type of runtime specified for the task executor.\n" }, "Networks": { - "items": { - "$ref": "#/components/schemas/NetworkAttachmentConfig" - }, - "type": "array", + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/NetworkAttachmentConfig" + }, + "type": "array" + }, + { + "type": "null" + } + ], "title": "Networks", "description": "Specifies which networks the service should attach to." }, "LogDriver": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/LogDriver1" + }, + { + "type": "null" } ], - "title": "Logdriver", "description": "Specifies the log driver to use for tasks created from this spec. If\nnot present, the default one for the swarm will be used, finally\nfalling back to the engine default if not specified.\n" } }, @@ -2805,12 +3891,26 @@ "TmpfsOptions": { "properties": { "SizeBytes": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Sizebytes", "description": "The size for the tmpfs mount in bytes." }, "Mode": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Mode", "description": "The permission mode for the tmpfs mount in an integer." } @@ -2826,10 +3926,9 @@ "udp", "sctp" ], - "title": "Type", - "description": "An enumeration." + "title": "Type" }, - "Type1": { + "Type2": { "type": "string", "enum": [ "bind", @@ -2837,65 +3936,120 @@ "tmpfs", "npipe" ], - "title": "Type1", - "description": " The mount type:\n\n- `bind` a mount of a file or directory from the host into the container.\n- `volume` a docker volume with the given `Name`.\n- `tmpfs` a `tmpfs`.\n- `npipe` a named pipe from the host into the container." + "title": "Type2", + "description": "The mount type. Available types:\n\n- `bind` Mounts a file or directory from the host into the container. Must exist prior to creating the container.\n- `volume` Creates a volume with the given name and options (or uses a pre-existing volume with the same name and options). These are **not** removed when the container is removed.\n- `tmpfs` Create a tmpfs with the given options. The mount source cannot be specified for tmpfs.\n- `npipe` Mounts a named pipe from the host into the container. Must exist prior to creating the container." }, - "Ulimit1": { + "Ulimit": { "properties": { "Name": { - "type": "string", + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], "title": "Name", "description": "Name of ulimit" }, "Soft": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Soft", "description": "Soft limit" }, "Hard": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Hard", "description": "Hard limit" } }, "type": "object", - "title": "Ulimit1" + "title": "Ulimit" }, "UpdateConfig": { "properties": { "Parallelism": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Parallelism", "description": "Maximum number of tasks to be updated in one iteration (0 means\nunlimited parallelism).\n" }, "Delay": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Delay", "description": "Amount of time between updates, in nanoseconds." }, "FailureAction": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/FailureAction" + }, + { + "type": "null" } ], "description": "Action to take if an updated task fails to run, or stops running\nduring the update.\n" }, "Monitor": { - "type": "integer", + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], "title": "Monitor", "description": "Amount of time to monitor each updated task for failures, in\nnanoseconds.\n" }, "MaxFailureRatio": { - "type": "number", + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], "title": "Maxfailureratio", "description": "The fraction of tasks that may fail during an update before the\nfailure action is invoked, specified as a floating point number\nbetween 0 and 1.\n", "default": 0 }, "Order": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/Order" + }, + { + "type": "null" } ], "description": "The order of operations when rolling out an updated task. Either\nthe old task is shut down before the new task is started, or the\nnew task is started before the old task is shut down.\n" @@ -2941,26 +4095,42 @@ "VolumeOptions": { "properties": { "NoCopy": { - "type": "boolean", + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], "title": "Nocopy", "description": "Populate volume with data from the target.", "default": false }, "Labels": { - "additionalProperties": { - "type": "string" - }, - "type": "object", + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], "title": "Labels", "description": "User-defined key/value metadata." }, "DriverConfig": { - "allOf": [ + "anyOf": [ { "$ref": "#/components/schemas/DriverConfig" + }, + { + "type": "null" } ], - "title": "Driverconfig", "description": "Map of driver specific options" } }, @@ -2971,11 +4141,7 @@ "Widget": { "properties": { "type": { - "allOf": [ - { - "$ref": "#/components/schemas/WidgetType" - } - ], + "$ref": "#/components/schemas/WidgetType", "description": "type of the property" }, "details": { @@ -3004,8 +4170,7 @@ "TextArea", "SelectBox" ], - "title": "WidgetType", - "description": "An enumeration." + "title": "WidgetType" } } } diff --git a/services/catalog/requirements/_base.txt b/services/catalog/requirements/_base.txt index d78334d994c..e5aeb9fc368 100644 --- a/services/catalog/requirements/_base.txt +++ b/services/catalog/requirements/_base.txt @@ -12,11 +12,18 @@ aiofiles==23.2.1 # via -r requirements/../../../packages/service-library/requirements/_base.in aiohttp==3.9.3 # via + # -c requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/postgres-database/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/postgres-database/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../requirements/constraints.txt # aiodocker @@ -26,6 +33,8 @@ aiosignal==1.3.1 # via aiohttp alembic==1.13.1 # via -r requirements/../../../packages/postgres-database/requirements/_base.in +annotated-types==0.7.0 + # via pydantic anyio==4.3.0 # via # fast-depends @@ -53,11 +62,18 @@ attrs==23.2.0 # referencing certifi==2024.2.2 # via + # -c requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/postgres-database/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/postgres-database/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../requirements/constraints.txt # httpcore @@ -83,18 +99,13 @@ email-validator==2.1.1 # pydantic fast-depends==2.4.12 # via faststream -fastapi==0.99.1 +fastapi==0.115.3 # via - # -c requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt - # -c requirements/../../../packages/postgres-database/requirements/../../../requirements/constraints.txt - # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt - # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt - # -c requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt - # -c requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt - # -c requirements/../../../requirements/constraints.txt # -r requirements/../../../packages/service-library/requirements/_fastapi.in # -r requirements/_base.in # prometheus-fastapi-instrumentator +fastapi-cli==0.0.5 + # via fastapi faststream==0.5.28 # via -r requirements/../../../packages/service-library/requirements/_base.in frozenlist==1.4.1 @@ -119,11 +130,18 @@ httptools==0.6.1 # via uvicorn httpx==0.27.0 # via + # -c requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/postgres-database/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/postgres-database/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../requirements/constraints.txt # -r requirements/../../../packages/service-library/requirements/_fastapi.in @@ -142,11 +160,18 @@ itsdangerous==2.1.2 # via fastapi jinja2==3.1.3 # via + # -c requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/postgres-database/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/postgres-database/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../requirements/constraints.txt # fastapi @@ -158,11 +183,18 @@ jsonschema-specifications==2023.7.1 # via jsonschema mako==1.3.2 # via + # -c requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/postgres-database/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/postgres-database/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../requirements/constraints.txt # alembic @@ -235,11 +267,18 @@ opentelemetry-util-http==0.47b0 # opentelemetry-instrumentation-requests orjson==3.10.0 # via + # -c requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/postgres-database/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/postgres-database/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../requirements/constraints.txt # -r requirements/../../../packages/models-library/requirements/_base.in @@ -263,25 +302,54 @@ psutil==6.0.0 # via -r requirements/../../../packages/service-library/requirements/_base.in psycopg2-binary==2.9.9 # via sqlalchemy -pydantic==1.10.14 +pydantic==2.9.2 # via + # -c requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/postgres-database/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/postgres-database/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt - # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/_base.in # -c requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../requirements/constraints.txt + # -r requirements/../../../packages/common-library/requirements/_base.in + # -r requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/_base.in # -r requirements/../../../packages/models-library/requirements/_base.in + # -r requirements/../../../packages/postgres-database/requirements/../../../packages/common-library/requirements/_base.in # -r requirements/../../../packages/postgres-database/requirements/_base.in + # -r requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/_base.in + # -r requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/_base.in # -r requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/_base.in + # -r requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/_base.in # -r requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/_base.in # -r requirements/../../../packages/service-library/requirements/_base.in + # -r requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/_base.in # -r requirements/../../../packages/settings-library/requirements/_base.in # -r requirements/_base.in # fast-depends # fastapi + # pydantic-extra-types + # pydantic-settings +pydantic-core==2.23.4 + # via pydantic +pydantic-extra-types==2.9.0 + # via + # -r requirements/../../../packages/models-library/requirements/_base.in + # -r requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/_base.in + # fastapi +pydantic-settings==2.6.0 + # via + # -r requirements/../../../packages/models-library/requirements/_base.in + # -r requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/_base.in + # -r requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/_base.in + # -r requirements/../../../packages/settings-library/requirements/_base.in + # fastapi pygments==2.17.2 # via rich pyinstrument==4.6.2 @@ -290,17 +358,24 @@ python-dateutil==2.9.0.post0 # via arrow python-dotenv==1.0.1 # via - # pydantic + # pydantic-settings # uvicorn python-multipart==0.0.9 # via fastapi pyyaml==6.0.1 # via + # -c requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/postgres-database/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/postgres-database/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../requirements/constraints.txt # -r requirements/../../../packages/service-library/requirements/_base.in @@ -309,11 +384,18 @@ pyyaml==6.0.1 # uvicorn redis==5.0.4 # via + # -c requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/postgres-database/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/postgres-database/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../requirements/constraints.txt # -r requirements/../../../packages/service-library/requirements/_base.in @@ -348,22 +430,36 @@ sniffio==1.3.1 # httpx sqlalchemy==1.4.52 # via + # -c requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/postgres-database/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/postgres-database/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../requirements/constraints.txt # -r requirements/../../../packages/postgres-database/requirements/_base.in # alembic -starlette==0.27.0 +starlette==0.41.0 # via + # -c requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/postgres-database/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/postgres-database/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../requirements/constraints.txt # fastapi @@ -379,6 +475,7 @@ typer==0.12.3 # via # -r requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/_base.in # -r requirements/../../../packages/settings-library/requirements/_base.in + # fastapi-cli types-python-dateutil==2.9.0.20240316 # via arrow typing-extensions==4.10.0 @@ -390,24 +487,39 @@ typing-extensions==4.10.0 # faststream # opentelemetry-sdk # pydantic + # pydantic-core # typer ujson==5.9.0 # via + # -c requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/postgres-database/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/postgres-database/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../requirements/constraints.txt # fastapi urllib3==2.2.2 # via + # -c requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/postgres-database/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/postgres-database/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../requirements/constraints.txt # requests @@ -415,6 +527,7 @@ uvicorn==0.29.0 # via # -r requirements/../../../packages/service-library/requirements/_fastapi.in # fastapi + # fastapi-cli uvloop==0.19.0 # via uvicorn watchfiles==0.21.0 diff --git a/services/catalog/requirements/prod.txt b/services/catalog/requirements/prod.txt index a830c6815bd..c72846d7091 100644 --- a/services/catalog/requirements/prod.txt +++ b/services/catalog/requirements/prod.txt @@ -10,6 +10,7 @@ --requirement _base.txt # installs this repo's packages +simcore-common-library @ ../../packages/common-library simcore-models-library @ ../../packages/models-library simcore-postgres-database @ ../../packages/postgres-database/ simcore-service-library[fastapi] @ ../../packages/service-library diff --git a/services/catalog/setup.cfg b/services/catalog/setup.cfg index 144dbc1a4b9..ba46449cb68 100644 --- a/services/catalog/setup.cfg +++ b/services/catalog/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.5.0 +current_version = 0.6.0 commit = True message = services/catalog version: {current_version} → {new_version} tag = False @@ -9,10 +9,10 @@ commit_args = --no-verify [tool:pytest] asyncio_mode = auto -markers = +markers = testit: "marks test to run during development" [mypy] -plugins = +plugins = pydantic.mypy sqlalchemy.ext.mypy.plugin diff --git a/services/catalog/src/simcore_service_catalog/_meta.py b/services/catalog/src/simcore_service_catalog/_meta.py index 6aab1be93b2..770d24a4e28 100644 --- a/services/catalog/src/simcore_service_catalog/_meta.py +++ b/services/catalog/src/simcore_service_catalog/_meta.py @@ -1,6 +1,3 @@ -""" Application's metadata - -""" from typing import Final from models_library.basic_types import VersionStr diff --git a/services/catalog/src/simcore_service_catalog/api/rest/_health.py b/services/catalog/src/simcore_service_catalog/api/rest/_health.py index aa59a59181a..a4360dff292 100644 --- a/services/catalog/src/simcore_service_catalog/api/rest/_health.py +++ b/services/catalog/src/simcore_service_catalog/api/rest/_health.py @@ -7,4 +7,4 @@ @router.get("/") async def check_service_health(): - return f"{__name__}@{datetime.datetime.now(tz=datetime.timezone.utc).isoformat()}" + return f"{__name__}@{datetime.datetime.now(tz=datetime.UTC).isoformat()}" diff --git a/services/catalog/src/simcore_service_catalog/api/rest/_meta.py b/services/catalog/src/simcore_service_catalog/api/rest/_meta.py index 24369f696e1..e78a06ddb61 100644 --- a/services/catalog/src/simcore_service_catalog/api/rest/_meta.py +++ b/services/catalog/src/simcore_service_catalog/api/rest/_meta.py @@ -1,7 +1,5 @@ from fastapi import APIRouter from models_library.api_schemas__common.meta import BaseMeta -from models_library.basic_types import VersionStr -from pydantic import parse_obj_as from ..._meta import API_VERSION, API_VTAG @@ -12,6 +10,6 @@ async def get_service_metadata(): return BaseMeta( name=__name__.split(".")[0], - version=parse_obj_as(VersionStr, API_VERSION), - released={API_VTAG: parse_obj_as(VersionStr, API_VERSION)}, + version=API_VERSION, + released={API_VTAG: API_VERSION}, ) diff --git a/services/catalog/src/simcore_service_catalog/api/rest/_services.py b/services/catalog/src/simcore_service_catalog/api/rest/_services.py index 68a9f6490ba..f8d4bddabb0 100644 --- a/services/catalog/src/simcore_service_catalog/api/rest/_services.py +++ b/services/catalog/src/simcore_service_catalog/api/rest/_services.py @@ -43,7 +43,7 @@ def _compose_service_details( # compose service from registry and DB service = service_in_registry service.update( - service_in_db.dict(exclude_unset=True, exclude={"owner"}), + service_in_db.model_dump(exclude_unset=True, exclude={"owner"}), access_rights={rights.gid: rights for rights in service_access_rights_in_db}, owner=service_owner if service_owner else None, ) @@ -121,7 +121,7 @@ async def list_services( # NOTE: here validation is not necessary since key,version were already validated # in terms of time, this takes the most return [ - ServiceGet.construct( + ServiceGet.model_construct( key=key, version=version, name="nodetails", @@ -132,6 +132,8 @@ async def list_services( inputs={}, outputs={}, deprecated=services_in_db[(key, version)].deprecated, + classifiers=[], + owner=None, ) for key, version in services_in_db ] @@ -254,10 +256,10 @@ async def get_service( ) # access is allowed, override some of the values with what is in the db - service_in_manifest = service_in_manifest.copy( - update=service_in_db.dict(exclude_unset=True, exclude={"owner"}) + service_data.update( + service_in_manifest.model_dump(exclude_unset=True, by_alias=True) + | service_in_db.model_dump(exclude_unset=True, exclude={"owner"}) ) - service_data.update(service_in_manifest.dict(exclude_unset=True, by_alias=True)) return service_data @@ -322,7 +324,7 @@ async def update_service( ServiceMetaDataAtDB( key=service_key, version=service_version, - **updated_service.dict(exclude_unset=True), + **updated_service.model_dump(exclude_unset=True), ) ) # let's modify the service access rights (they can be added/removed/modified) diff --git a/services/catalog/src/simcore_service_catalog/api/rest/_services_resources.py b/services/catalog/src/simcore_service_catalog/api/rest/_services_resources.py index 701c4b41f3d..e8d552c7059 100644 --- a/services/catalog/src/simcore_service_catalog/api/rest/_services_resources.py +++ b/services/catalog/src/simcore_service_catalog/api/rest/_services_resources.py @@ -20,7 +20,7 @@ ServiceResourcesDictHelpers, ) from models_library.utils.docker_compose import replace_env_vars_in_compose_spec -from pydantic import parse_obj_as, parse_raw_as +from pydantic import TypeAdapter from ..._constants import RESPONSE_MODEL_POLICY, SIMCORE_SERVICE_SETTINGS_LABELS from ...db.repositories.services import ServicesRepository @@ -61,7 +61,7 @@ def _compute_service_available_boot_modes( if not isinstance(entry.value, dict): _logger.warning( "resource %s for %s got invalid type", - f"{entry.dict()!r}", + f"{entry.model_dump()!r}", f"{service_key}:{service_version}", ) continue @@ -99,7 +99,7 @@ def _resources_from_settings( if not isinstance(entry.value, dict): _logger.warning( "resource %s for %s got invalid type", - f"{entry.dict()!r}", + f"{entry.model_dump()!r}", f"{service_key}:{service_version}", ) continue @@ -156,8 +156,7 @@ async def _get_service_labels( def _get_service_settings( labels: dict[str, Any] ) -> list[SimcoreServiceSettingLabelEntry]: - service_settings = parse_raw_as( - list[SimcoreServiceSettingLabelEntry], + service_settings = TypeAdapter(list[SimcoreServiceSettingLabelEntry]).validate_json( labels.get(SIMCORE_SERVICE_SETTINGS_LABELS, "[]"), ) _logger.debug("received %s", f"{service_settings=}") @@ -181,7 +180,9 @@ async def get_service_resources( ], user_groups: Annotated[list[GroupAtDB], Depends(list_user_groups)], ) -> ServiceResourcesDict: - image_version = parse_obj_as(DockerGenericTag, f"{service_key}:{service_version}") + image_version = TypeAdapter(DockerGenericTag).validate_python( + f"{service_key}:{service_version}" + ) if is_function_service(service_key): return ServiceResourcesDictHelpers.create_from_single_service( image_version, default_service_resources @@ -196,10 +197,9 @@ async def get_service_resources( image_version, default_service_resources ) - service_spec: ComposeSpecLabelDict | None = parse_raw_as( - ComposeSpecLabelDict | None, # type: ignore[arg-type] - service_labels.get(SIMCORE_SERVICE_COMPOSE_SPEC_LABEL, "null"), - ) + service_spec: ComposeSpecLabelDict | None = TypeAdapter( + ComposeSpecLabelDict | None + ).validate_json(service_labels.get(SIMCORE_SERVICE_COMPOSE_SPEC_LABEL, "null")) _logger.debug("received %s", f"{service_spec=}") if service_spec is None: @@ -235,7 +235,9 @@ async def get_service_resources( ) full_service_spec: ComposeSpecLabelDict = yaml.safe_load(stringified_service_spec) - service_to_resources: ServiceResourcesDict = parse_obj_as(ServiceResourcesDict, {}) + service_to_resources: ServiceResourcesDict = TypeAdapter( + ServiceResourcesDict + ).validate_python({}) for spec_key, spec_data in full_service_spec["services"].items(): # image can be: @@ -277,7 +279,7 @@ async def get_service_resources( spec_service_resources, user_specific_service_specs.service ) - service_to_resources[spec_key] = ImageResources.parse_obj( + service_to_resources[spec_key] = ImageResources.model_validate( { "image": image, "resources": spec_service_resources, diff --git a/services/catalog/src/simcore_service_catalog/api/rest/_services_specifications.py b/services/catalog/src/simcore_service_catalog/api/rest/_services_specifications.py index d5481b3cefd..49751a196e8 100644 --- a/services/catalog/src/simcore_service_catalog/api/rest/_services_specifications.py +++ b/services/catalog/src/simcore_service_catalog/api/rest/_services_specifications.py @@ -70,7 +70,7 @@ async def get_service_specifications( if not service_specs: # nothing found, let's return the default then - service_specs = default_service_specifications.copy() + service_specs = default_service_specifications.model_copy() _logger.debug("returning %s", f"{service_specs=}") return service_specs diff --git a/services/catalog/src/simcore_service_catalog/cli.py b/services/catalog/src/simcore_service_catalog/cli.py index 5218b369ecc..0d4fbf5107b 100644 --- a/services/catalog/src/simcore_service_catalog/cli.py +++ b/services/catalog/src/simcore_service_catalog/cli.py @@ -5,9 +5,13 @@ from settings_library.http_client_request import ClientRequestSettings from settings_library.postgres import PostgresSettings from settings_library.rabbit import RabbitSettings -from settings_library.utils_cli import create_settings_command, print_as_envfile +from settings_library.utils_cli import ( + create_settings_command, + create_version_callback, + print_as_envfile, +) -from ._meta import PROJECT_NAME +from ._meta import PROJECT_NAME, __version__ from .core.settings import ApplicationSettings, DirectorSettings _logger = logging.getLogger(__name__) @@ -18,6 +22,7 @@ main.command()( create_settings_command(settings_cls=ApplicationSettings, logger=_logger) ) +main.callback()(create_version_callback(__version__)) @main.command() diff --git a/services/catalog/src/simcore_service_catalog/core/application.py b/services/catalog/src/simcore_service_catalog/core/application.py index a28dc8c5a32..a753f206a3e 100644 --- a/services/catalog/src/simcore_service_catalog/core/application.py +++ b/services/catalog/src/simcore_service_catalog/core/application.py @@ -29,7 +29,7 @@ def create_app(settings: ApplicationSettings | None = None) -> FastAPI: settings = ApplicationSettings.create_from_envs() assert settings # nosec - _logger.debug(settings.json(indent=2)) + _logger.debug(settings.model_dump_json(indent=2)) app = FastAPI( debug=settings.SC_BOOT_MODE diff --git a/services/catalog/src/simcore_service_catalog/core/background_tasks.py b/services/catalog/src/simcore_service_catalog/core/background_tasks.py index e6eb7c59fcb..5e513246732 100644 --- a/services/catalog/src/simcore_service_catalog/core/background_tasks.py +++ b/services/catalog/src/simcore_service_catalog/core/background_tasks.py @@ -89,7 +89,7 @@ def _by_version(t: tuple[ServiceKey, ServiceVersion]) -> Version: # set the service in the DB await services_repo.create_or_update_service( - ServiceMetaDataAtDB(**service_metadata.dict(), owner=owner_gid), + ServiceMetaDataAtDB(**service_metadata.model_dump(), owner=owner_gid), service_access_rights, ) diff --git a/services/catalog/src/simcore_service_catalog/core/settings.py b/services/catalog/src/simcore_service_catalog/core/settings.py index 6235dcfd37f..38f9e22c5c2 100644 --- a/services/catalog/src/simcore_service_catalog/core/settings.py +++ b/services/catalog/src/simcore_service_catalog/core/settings.py @@ -5,9 +5,10 @@ from models_library.api_schemas_catalog.services_specifications import ( ServiceSpecifications, ) -from models_library.basic_types import BootModeEnum, BuildTargetEnum, LogLevel -from models_library.services_resources import ResourcesDict -from pydantic import ByteSize, Field, PositiveInt, parse_obj_as +from models_library.basic_types import LogLevel +from models_library.services_resources import ResourcesDict, ResourceValue +from pydantic import AliasChoices, ByteSize, Field, PositiveInt, TypeAdapter +from settings_library.application import BaseApplicationSettings from settings_library.base import BaseCustomSettings from settings_library.http_client_request import ClientRequestSettings from settings_library.postgres import PostgresSettings @@ -28,37 +29,31 @@ def base_url(self) -> str: return f"http://{self.DIRECTOR_HOST}:{self.DIRECTOR_PORT}/{self.DIRECTOR_VTAG}" -_DEFAULT_RESOURCES: Final[ResourcesDict] = parse_obj_as( - ResourcesDict, - { - "CPU": { - "limit": 0.1, - "reservation": 0.1, - }, - "RAM": { - "limit": parse_obj_as(ByteSize, "2Gib"), - "reservation": parse_obj_as(ByteSize, "2Gib"), - }, - }, +_in_bytes = TypeAdapter(ByteSize).validate_python + +_DEFAULT_RESOURCES: Final[ResourcesDict] = ResourcesDict( + CPU=ResourceValue(limit=0.1, reservation=0.1), + RAM=ResourceValue(limit=_in_bytes("2Gib"), reservation=_in_bytes("2Gib")), ) + _DEFAULT_SERVICE_SPECIFICATIONS: Final[ ServiceSpecifications -] = ServiceSpecifications.parse_obj({}) - +] = ServiceSpecifications.model_validate({}) -class ApplicationSettings(BaseCustomSettings, MixinLoggingSettings): - # docker environs - SC_BOOT_MODE: BootModeEnum | None - SC_BOOT_TARGET: BuildTargetEnum | None - CATALOG_LOG_LEVEL: LogLevel = Field( +class ApplicationSettings(BaseApplicationSettings, MixinLoggingSettings): + LOG_LEVEL: LogLevel = Field( LogLevel.INFO.value, - env=["CATALOG_LOGLEVEL", "LOG_LEVEL", "LOGLEVEL"], + validation_alias=AliasChoices( + "CATALOG_LOG_LEVEL", "CATALOG_LOGLEVEL", "LOG_LEVEL", "LOGLEVEL" + ), ) CATALOG_LOG_FORMAT_LOCAL_DEV_ENABLED: bool = Field( default=False, - env=["CATALOG_LOG_FORMAT_LOCAL_DEV_ENABLED", "LOG_FORMAT_LOCAL_DEV_ENABLED"], + validation_alias=AliasChoices( + "CATALOG_LOG_FORMAT_LOCAL_DEV_ENABLED", "LOG_FORMAT_LOCAL_DEV_ENABLED" + ), description="Enables local development log format. WARNING: make sure it is disabled if you want to have structured logs!", ) CATALOG_DEV_FEATURES_ENABLED: bool = Field( @@ -66,15 +61,21 @@ class ApplicationSettings(BaseCustomSettings, MixinLoggingSettings): description="Enables development features. WARNING: make sure it is disabled in production .env file!", ) - CATALOG_POSTGRES: PostgresSettings | None = Field(auto_default_from_env=True) + CATALOG_POSTGRES: PostgresSettings | None = Field( + json_schema_extra={"auto_default_from_env": True} + ) - CATALOG_RABBITMQ: RabbitSettings = Field(auto_default_from_env=True) + CATALOG_RABBITMQ: RabbitSettings = Field( + json_schema_extra={"auto_default_from_env": True} + ) CATALOG_CLIENT_REQUEST: ClientRequestSettings | None = Field( - auto_default_from_env=True + json_schema_extra={"auto_default_from_env": True} ) - CATALOG_DIRECTOR: DirectorSettings | None = Field(auto_default_from_env=True) + CATALOG_DIRECTOR: DirectorSettings | None = Field( + json_schema_extra={"auto_default_from_env": True} + ) CATALOG_PROMETHEUS_INSTRUMENTATION_ENABLED: bool = True @@ -89,5 +90,6 @@ class ApplicationSettings(BaseCustomSettings, MixinLoggingSettings): _DEFAULT_SERVICE_SPECIFICATIONS ) CATALOG_TRACING: TracingSettings | None = Field( - auto_default_from_env=True, description="settings for opentelemetry tracing" + json_schema_extra={"auto_default_from_env": True}, + description="settings for opentelemetry tracing", ) diff --git a/services/catalog/src/simcore_service_catalog/db/repositories/groups.py b/services/catalog/src/simcore_service_catalog/db/repositories/groups.py index 4f339846301..8a1540b3f1a 100644 --- a/services/catalog/src/simcore_service_catalog/db/repositories/groups.py +++ b/services/catalog/src/simcore_service_catalog/db/repositories/groups.py @@ -3,7 +3,7 @@ import sqlalchemy as sa from models_library.emails import LowerCaseEmailStr from models_library.groups import GroupAtDB -from pydantic import parse_obj_as +from pydantic import TypeAdapter from pydantic.types import PositiveInt from ...exceptions.errors import UninitializedGroupError @@ -15,7 +15,7 @@ class GroupsRepository(BaseRepository): async def list_user_groups(self, user_id: int) -> list[GroupAtDB]: async with self.db_engine.connect() as conn: return [ - GroupAtDB.from_orm(row) + GroupAtDB.model_validate(row) async for row in await conn.stream( sa.select(groups) .select_from( @@ -66,7 +66,7 @@ async def get_user_email_from_gid( email = await conn.scalar( sa.select(users.c.email).where(users.c.primary_gid == gid) ) - return cast(LowerCaseEmailStr, f"{email}") if email else None + return email or None async def list_user_emails_from_gids( self, gids: set[PositiveInt] @@ -79,7 +79,7 @@ async def list_user_emails_from_gids( ) ): service_owners[row[users.c.primary_gid]] = ( - parse_obj_as(LowerCaseEmailStr, row[users.c.email]) + TypeAdapter(LowerCaseEmailStr).validate_python(row[users.c.email]) if row[users.c.email] else None ) diff --git a/services/catalog/src/simcore_service_catalog/db/repositories/services.py b/services/catalog/src/simcore_service_catalog/db/repositories/services.py index 0f611c932b8..bae22e11597 100644 --- a/services/catalog/src/simcore_service_catalog/db/repositories/services.py +++ b/services/catalog/src/simcore_service_catalog/db/repositories/services.py @@ -15,7 +15,7 @@ from models_library.services import ServiceKey, ServiceVersion from models_library.users import GroupID, UserID from psycopg2.errors import ForeignKeyViolation -from pydantic import PositiveInt, ValidationError, parse_obj_as +from pydantic import PositiveInt, TypeAdapter, ValidationError from simcore_postgres_database.utils_services import create_select_latest_services_query from sqlalchemy import literal_column from sqlalchemy.dialects.postgresql import insert as pg_insert @@ -62,7 +62,7 @@ def _merge_specs( merged_spec = {} for spec in itertools.chain([everyone_spec], team_specs.values(), [user_spec]): if spec is not None: - merged_spec.update(spec.dict(include={"sidecar", "service"})) + merged_spec.update(spec.model_dump(include={"sidecar", "service"})) return merged_spec @@ -229,7 +229,7 @@ async def create_or_update_service( result = await conn.execute( # pylint: disable=no-value-for-parameter services_meta_data.insert() - .values(**new_service.dict(by_alias=True)) + .values(**new_service.model_dump(by_alias=True)) .returning(literal_column("*")) ) row = result.first() @@ -252,7 +252,7 @@ async def update_service(self, patched_service: ServiceMetaDataAtDB) -> None: & (services_meta_data.c.version == patched_service.version) ) .values( - **patched_service.dict( + **patched_service.model_dump( by_alias=True, exclude_unset=True, exclude={"key", "version"}, @@ -441,7 +441,11 @@ async def get_service_history( result = await conn.execute(stmt_history) row = result.one_or_none() - return parse_obj_as(list[ReleaseFromDB], row.history) if row else None + return ( + TypeAdapter(list[ReleaseFromDB]).validate_python(row.history) + if row + else None + ) # Service Access Rights ---- @@ -500,7 +504,7 @@ async def upsert_service_access_rights( # update the services_access_rights table (some might be added/removed/modified) for rights in new_access_rights: insert_stmt = pg_insert(services_access_rights).values( - **rights.dict(by_alias=True) + **rights.model_dump(by_alias=True) ) on_update_stmt = insert_stmt.on_conflict_do_update( index_elements=[ @@ -509,7 +513,7 @@ async def upsert_service_access_rights( services_access_rights.c.gid, services_access_rights.c.product_name, ], - set_=rights.dict( + set_=rights.model_dump( by_alias=True, exclude_unset=True, exclude={"key", "version", "gid", "product_name"}, @@ -617,5 +621,5 @@ async def get_service_specifications( if merged_specifications := _merge_specs( everyone_specs, teams_specs, primary_specs ): - return ServiceSpecifications.parse_obj(merged_specifications) + return ServiceSpecifications.model_validate(merged_specifications) return None # mypy diff --git a/services/catalog/src/simcore_service_catalog/exceptions/errors.py b/services/catalog/src/simcore_service_catalog/exceptions/errors.py index 0384088d37c..84010d9a700 100644 --- a/services/catalog/src/simcore_service_catalog/exceptions/errors.py +++ b/services/catalog/src/simcore_service_catalog/exceptions/errors.py @@ -1,11 +1,8 @@ -from typing import Any - from common_library.errors_classes import OsparcErrorMixin class CatalogBaseError(OsparcErrorMixin, Exception): - def __init__(self, **ctx: Any) -> None: - super().__init__(**ctx) + ... class RepositoryError(CatalogBaseError): diff --git a/services/catalog/src/simcore_service_catalog/exceptions/handlers/_http_error.py b/services/catalog/src/simcore_service_catalog/exceptions/handlers/_http_error.py index a28839dad42..7af4b5d93bd 100644 --- a/services/catalog/src/simcore_service_catalog/exceptions/handlers/_http_error.py +++ b/services/catalog/src/simcore_service_catalog/exceptions/handlers/_http_error.py @@ -1,13 +1,14 @@ -from collections.abc import Callable -from typing import Awaitable - from fastapi import HTTPException from fastapi.encoders import jsonable_encoder from starlette.requests import Request from starlette.responses import JSONResponse +from starlette.types import HTTPExceptionHandler + +async def http_error_handler(request: Request, exc: Exception) -> JSONResponse: + assert request # nosec + assert isinstance(exc, HTTPException) # nosec -async def http_error_handler(_: Request, exc: HTTPException) -> JSONResponse: return JSONResponse( content=jsonable_encoder({"errors": [exc.detail]}), status_code=exc.status_code ) @@ -15,7 +16,7 @@ async def http_error_handler(_: Request, exc: HTTPException) -> JSONResponse: def make_http_error_handler_for_exception( status_code: int, exception_cls: type[BaseException] -) -> Callable[[Request, type[BaseException]], Awaitable[JSONResponse]]: +) -> HTTPExceptionHandler: """ Produces a handler for BaseException-type exceptions which converts them into an error JSON response with a given status code @@ -29,4 +30,4 @@ async def _http_error_handler(_: Request, exc: type[BaseException]) -> JSONRespo content=jsonable_encoder({"errors": [str(exc)]}), status_code=status_code ) - return _http_error_handler + return _http_error_handler # type: ignore[return-value] diff --git a/services/catalog/src/simcore_service_catalog/exceptions/handlers/_validation_error.py b/services/catalog/src/simcore_service_catalog/exceptions/handlers/_validation_error.py index 23aaa1d0f4e..8e3ad77f15d 100644 --- a/services/catalog/src/simcore_service_catalog/exceptions/handlers/_validation_error.py +++ b/services/catalog/src/simcore_service_catalog/exceptions/handlers/_validation_error.py @@ -1,17 +1,13 @@ from fastapi.encoders import jsonable_encoder -from fastapi.exceptions import RequestValidationError from fastapi.openapi.constants import REF_PREFIX from fastapi.openapi.utils import validation_error_response_definition -from pydantic import ValidationError from starlette.requests import Request from starlette.responses import JSONResponse from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY -async def http422_error_handler( - _: Request, - exc: RequestValidationError | ValidationError, -) -> JSONResponse: +async def http422_error_handler(_: Request, exc: Exception) -> JSONResponse: + assert hasattr(exc, "errors") # nosec return JSONResponse( content=jsonable_encoder({"errors": exc.errors()}), status_code=HTTP_422_UNPROCESSABLE_ENTITY, diff --git a/services/catalog/src/simcore_service_catalog/main.py b/services/catalog/src/simcore_service_catalog/main.py index f5c9f4ee97c..cd8f0405858 100644 --- a/services/catalog/src/simcore_service_catalog/main.py +++ b/services/catalog/src/simcore_service_catalog/main.py @@ -11,8 +11,8 @@ _the_settings = ApplicationSettings.create_from_envs() # SEE https://github.com/ITISFoundation/osparc-simcore/issues/3148 -logging.basicConfig(level=_the_settings.CATALOG_LOG_LEVEL.value) # NOSONAR -logging.root.setLevel(_the_settings.CATALOG_LOG_LEVEL.value) +logging.basicConfig(level=_the_settings.log_level) # NOSONAR +logging.root.setLevel(_the_settings.log_level) config_all_loggers( log_format_local_dev_enabled=_the_settings.CATALOG_LOG_FORMAT_LOCAL_DEV_ENABLED ) diff --git a/services/catalog/src/simcore_service_catalog/models/services_db.py b/services/catalog/src/simcore_service_catalog/models/services_db.py index 2fd92f479ac..0412ba6878a 100644 --- a/services/catalog/src/simcore_service_catalog/models/services_db.py +++ b/services/catalog/src/simcore_service_catalog/models/services_db.py @@ -1,24 +1,27 @@ from datetime import datetime -from typing import Any, ClassVar +from typing import Annotated, Any from models_library.products import ProductName from models_library.services_access import ServiceGroupAccessRights from models_library.services_base import ServiceKeyVersion from models_library.services_metadata_editable import ServiceMetaDataEditable from models_library.services_types import ServiceKey, ServiceVersion -from pydantic import BaseModel, Field +from pydantic import BaseModel, ConfigDict, Field, HttpUrl from pydantic.types import PositiveInt from simcore_postgres_database.models.services_compatibility import CompatiblePolicyDict class ServiceMetaDataAtDB(ServiceKeyVersion, ServiceMetaDataEditable): - # for a partial update all members must be Optional - classifiers: list[str] | None = Field(default_factory=list) - owner: PositiveInt | None + # for a partial update all Editable members must be Optional + name: str | None = None + thumbnail: Annotated[str, HttpUrl] | None = None + description: str | None = None - class Config: - orm_mode = True - schema_extra: ClassVar[dict[str, Any]] = { + classifiers: list[str] | None = Field(default_factory=list) + owner: PositiveInt | None = None + model_config = ConfigDict( + from_attributes=True, + json_schema_extra={ "example": { "key": "simcore/services/dynamic/sim4life", "version": "1.0.9", @@ -49,7 +52,8 @@ class Config: }, }, } - } + }, + ) class ReleaseFromDB(BaseModel): @@ -83,19 +87,18 @@ class ServiceWithHistoryFromDB(BaseModel): assert ( # nosec - set(ReleaseFromDB.__fields__) + set(ReleaseFromDB.model_fields) .difference({"compatibility_policy"}) - .issubset(set(ServiceWithHistoryFromDB.__fields__)) + .issubset(set(ServiceWithHistoryFromDB.model_fields)) ) class ServiceAccessRightsAtDB(ServiceKeyVersion, ServiceGroupAccessRights): gid: PositiveInt product_name: ProductName - - class Config: - orm_mode = True - schema_extra: ClassVar[dict[str, Any]] = { + model_config = ConfigDict( + from_attributes=True, + json_schema_extra={ "example": { "key": "simcore/services/dynamic/sim4life", "version": "1.0.9", @@ -106,4 +109,5 @@ class Config: "created": "2021-01-18 12:46:57.7315", "modified": "2021-01-19 12:45:00", } - } + }, + ) diff --git a/services/catalog/src/simcore_service_catalog/models/services_specifications.py b/services/catalog/src/simcore_service_catalog/models/services_specifications.py index ce40b492f07..d53e56a8c56 100644 --- a/services/catalog/src/simcore_service_catalog/models/services_specifications.py +++ b/services/catalog/src/simcore_service_catalog/models/services_specifications.py @@ -3,6 +3,7 @@ ) from models_library.services import ServiceKey, ServiceVersion from models_library.users import GroupID +from pydantic import ConfigDict class ServiceSpecificationsAtDB(ServiceSpecifications): @@ -10,5 +11,4 @@ class ServiceSpecificationsAtDB(ServiceSpecifications): service_version: ServiceVersion gid: GroupID - class Config(ServiceSpecifications.Config): - orm_mode: bool = True + model_config = ConfigDict(from_attributes=True) diff --git a/services/catalog/src/simcore_service_catalog/services/access_rights.py b/services/catalog/src/simcore_service_catalog/services/access_rights.py index d35e7d4e5c0..0c83baf2a01 100644 --- a/services/catalog/src/simcore_service_catalog/services/access_rights.py +++ b/services/catalog/src/simcore_service_catalog/services/access_rights.py @@ -143,8 +143,7 @@ async def evaluate_auto_upgrade_policy( ) service_access_rights = [ - access.copy( - exclude={"created", "modified"}, + access.model_copy( update={"version": service_metadata.version}, deep=True, ) @@ -170,7 +169,7 @@ def _get_target(access: ServiceAccessRightsAtDB) -> tuple[str | int, ...]: def _get_flags(access: ServiceAccessRightsAtDB) -> dict[str, bool]: """Extracts only""" - flags = access.dict(include={"execute_access", "write_access"}) + flags = access.model_dump(include={"execute_access", "write_access"}) return cast(dict[str, bool], flags) access_flags_map: dict[tuple[str | int, ...], dict[str, bool]] = {} diff --git a/services/catalog/src/simcore_service_catalog/services/compatibility.py b/services/catalog/src/simcore_service_catalog/services/compatibility.py index 1e9ea2b9a48..9c21e8b7ea7 100644 --- a/services/catalog/src/simcore_service_catalog/services/compatibility.py +++ b/services/catalog/src/simcore_service_catalog/services/compatibility.py @@ -8,7 +8,6 @@ from models_library.users import UserID from packaging.specifiers import SpecifierSet from packaging.version import Version -from pydantic import parse_obj_as from simcore_service_catalog.utils.versioning import as_version from ..db.repositories.services import ServicesRepository @@ -79,12 +78,12 @@ async def _evaluate_custom_compatibility( return Compatibility( can_update_to=CompatibleService( key=other_service_key, - version=parse_obj_as(ServiceVersion, f"{latest_version}"), + version=f"{latest_version}", ) ) return Compatibility( can_update_to=CompatibleService( - version=parse_obj_as(ServiceVersion, f"{latest_version}"), + version=f"{latest_version}", ) ) @@ -116,9 +115,7 @@ async def evaluate_service_compatibility_map( released_versions, ): compatibility = Compatibility( - can_update_to=CompatibleService( - version=parse_obj_as(ServiceVersion, f"{latest_version}") - ) + can_update_to=CompatibleService(version=f"{latest_version}") ) result[release.version] = compatibility diff --git a/services/catalog/src/simcore_service_catalog/services/director.py b/services/catalog/src/simcore_service_catalog/services/director.py index ee1327b475c..a762892dd77 100644 --- a/services/catalog/src/simcore_service_catalog/services/director.py +++ b/services/catalog/src/simcore_service_catalog/services/director.py @@ -148,7 +148,7 @@ async def get_service( # NOTE: the fact that it returns a list of one element is a defect of the director API assert isinstance(data, list) # nosec assert len(data) == 1 # nosec - return ServiceMetaDataPublished.parse_obj(data[0]) + return ServiceMetaDataPublished.model_validate(data[0]) async def setup_director(app: FastAPI) -> None: diff --git a/services/catalog/src/simcore_service_catalog/services/function_services.py b/services/catalog/src/simcore_service_catalog/services/function_services.py index 006da49d413..93abd9466f8 100644 --- a/services/catalog/src/simcore_service_catalog/services/function_services.py +++ b/services/catalog/src/simcore_service_catalog/services/function_services.py @@ -14,7 +14,7 @@ def _as_dict(model_instance: ServiceMetaDataPublished) -> dict[str, Any]: - return model_instance.dict(by_alias=True, exclude_unset=True) + return model_instance.model_dump(by_alias=True, exclude_unset=True) def get_function_service(key, version) -> ServiceMetaDataPublished: diff --git a/services/catalog/src/simcore_service_catalog/services/manifest.py b/services/catalog/src/simcore_service_catalog/services/manifest.py index aa6caf52618..bf7c26a6b63 100644 --- a/services/catalog/src/simcore_service_catalog/services/manifest.py +++ b/services/catalog/src/simcore_service_catalog/services/manifest.py @@ -64,7 +64,7 @@ async def get_services_map( } for service in services_in_registry: try: - service_data = ServiceMetaDataPublished.parse_obj(service) + service_data = ServiceMetaDataPublished.model_validate(service) services[(service_data.key, service_data.version)] = service_data except ValidationError: # noqa: PERF203 diff --git a/services/catalog/src/simcore_service_catalog/services/services_api.py b/services/catalog/src/simcore_service_catalog/services/services_api.py index 032909f4853..4122a035b0f 100644 --- a/services/catalog/src/simcore_service_catalog/services/services_api.py +++ b/services/catalog/src/simcore_service_catalog/services/services_api.py @@ -1,7 +1,6 @@ import logging from models_library.api_schemas_catalog.services import ServiceGetV2, ServiceUpdateV2 -from models_library.emails import LowerCaseEmailStr from models_library.products import ProductName from models_library.rest_pagination import PageLimitInt from models_library.services_access import ServiceGroupAccessRightsV2 @@ -9,7 +8,7 @@ from models_library.services_metadata_published import ServiceMetaDataPublished from models_library.services_types import ServiceKey, ServiceVersion from models_library.users import UserID -from pydantic import HttpUrl, NonNegativeInt, parse_obj_as +from pydantic import HttpUrl, NonNegativeInt from servicelib.rabbitmq.rpc_interfaces.catalog.errors import ( CatalogForbiddenError, CatalogItemNotFoundError, @@ -41,28 +40,20 @@ def _db_to_api_model( key=service_db.key, version=service_db.version, name=service_db.name, - thumbnail=( - parse_obj_as(HttpUrl, service_db.thumbnail) - if service_db.thumbnail - else None - ), + thumbnail=HttpUrl(service_db.thumbnail) if service_db.thumbnail else None, description=service_db.description, description_ui=service_db.description_ui, version_display=service_db.version_display, type=service_manifest.service_type, contact=service_manifest.contact, authors=service_manifest.authors, - owner=( - LowerCaseEmailStr(service_db.owner_email) - if service_db.owner_email - else None - ), + owner=(service_db.owner_email if service_db.owner_email else None), inputs=service_manifest.inputs or {}, outputs=service_manifest.outputs or {}, boot_options=service_manifest.boot_options, min_visible_inputs=service_manifest.min_visible_inputs, access_rights={ - a.gid: ServiceGroupAccessRightsV2.construct( + a.gid: ServiceGroupAccessRightsV2.model_construct( execute=a.execute_access, write=a.write_access, ) @@ -71,7 +62,7 @@ def _db_to_api_model( classifiers=service_db.classifiers, quality=service_db.quality, history=[ - ServiceRelease.construct( + ServiceRelease.model_construct( version=h.version, version_display=h.version_display, released=h.created, @@ -251,7 +242,7 @@ async def update_service( ServiceMetaDataAtDB( key=service_key, version=service_version, - **update.dict(exclude_unset=True), + **update.model_dump(exclude_unset=True), ) ) diff --git a/services/catalog/src/simcore_service_catalog/utils/service_resources.py b/services/catalog/src/simcore_service_catalog/utils/service_resources.py index 1b6b7ddcbc9..1e61dfffbe5 100644 --- a/services/catalog/src/simcore_service_catalog/utils/service_resources.py +++ b/services/catalog/src/simcore_service_catalog/utils/service_resources.py @@ -42,12 +42,15 @@ def merge_service_resources_with_user_specs( service_resources: ResourcesDict, user_specific_spec: ServiceSpec ) -> ResourcesDict: if ( - not user_specific_spec.TaskTemplate - or not user_specific_spec.TaskTemplate.Resources + not user_specific_spec.task_template + or not user_specific_spec.task_template.resources ): return service_resources - user_specific_resources = user_specific_spec.dict( - include={"TaskTemplate": {"Resources"}} + + assert "task_template" in user_specific_spec.model_fields # nosec + + user_specific_resources = user_specific_spec.model_dump( + include={"task_template": {"resources"}}, by_alias=True )["TaskTemplate"]["Resources"] merged_resources = deepcopy(service_resources) @@ -58,25 +61,29 @@ def merge_service_resources_with_user_specs( # res_name: NanoCPUs, MemoryBytes, Pids, GenericResources if res_value is None: continue + if res_name == "GenericResources": # special case here merged_resources |= parse_generic_resource(res_value) continue + if res_name not in _DOCKER_TO_OSPARC_RESOURCE_MAP: continue - if _DOCKER_TO_OSPARC_RESOURCE_MAP[res_name] in merged_resources: - # upgrade - merged_resources[_DOCKER_TO_OSPARC_RESOURCE_MAP[res_name]].__setattr__( - osparc_res_attr, - res_value * _DOCKER_TO_OSPARC_RESOURCE_CONVERTER[res_name], - ) + + scale = _DOCKER_TO_OSPARC_RESOURCE_CONVERTER[res_name] + key = _DOCKER_TO_OSPARC_RESOURCE_MAP[res_name] + if key in merged_resources: + # updates. + # NOTE: do not use assignment! + # SEE test_reservation_is_cap_by_limit_on_assigment_pydantic_2_bug + data = merged_resources[key].model_dump() + data[osparc_res_attr] = res_value * scale + merged_resources[key] = ResourceValue(**data) else: - merged_resources[ - _DOCKER_TO_OSPARC_RESOURCE_MAP[res_name] - ] = ResourceValue( - limit=res_value * _DOCKER_TO_OSPARC_RESOURCE_CONVERTER[res_name], - reservation=res_value - * _DOCKER_TO_OSPARC_RESOURCE_CONVERTER[res_name], + # constructs + merged_resources[key] = ResourceValue( + limit=res_value * scale, + reservation=res_value * scale, ) return merged_resources diff --git a/services/catalog/tests/unit/conftest.py b/services/catalog/tests/unit/conftest.py index 6069514b085..278af317091 100644 --- a/services/catalog/tests/unit/conftest.py +++ b/services/catalog/tests/unit/conftest.py @@ -31,6 +31,7 @@ from simcore_service_catalog.core.settings import ApplicationSettings pytest_plugins = [ + "pytest_simcore.cli_runner", "pytest_simcore.docker_compose", "pytest_simcore.docker_registry", "pytest_simcore.docker_swarm", @@ -86,7 +87,12 @@ def app_environment( """ return setenvs_from_dict( monkeypatch, - {**docker_compose_service_environment_dict}, + { + "SC_BOOT_MODE": "production", + "SC_BOOT_TARGET": "null", + **docker_compose_service_environment_dict, + "CATALOG_TRACING": "null", + }, ) diff --git a/services/catalog/tests/unit/test__model_examples.py b/services/catalog/tests/unit/test__model_examples.py new file mode 100644 index 00000000000..7592b8d21f1 --- /dev/null +++ b/services/catalog/tests/unit/test__model_examples.py @@ -0,0 +1,28 @@ +# pylint: disable=protected-access +# pylint: disable=redefined-outer-name +# pylint: disable=too-many-arguments +# pylint: disable=unused-argument +# pylint: disable=unused-variable + +import json +from typing import Any + +import pytest +import simcore_service_catalog.models +from pydantic import BaseModel, ValidationError +from pytest_simcore.pydantic_models import walk_model_examples_in_package + + +@pytest.mark.parametrize( + "model_cls, example_name, example_data", + walk_model_examples_in_package(simcore_service_catalog.models), +) +def test_catalog_service_model_examples( + model_cls: type[BaseModel], example_name: int, example_data: Any +): + try: + assert model_cls.model_validate(example_data) is not None + except ValidationError as err: + pytest.fail( + f"\n{example_name}: {json.dumps(example_data, indent=1)}\nError: {err}" + ) diff --git a/services/catalog/tests/unit/test_cli.py b/services/catalog/tests/unit/test_cli.py new file mode 100644 index 00000000000..044cc6c5884 --- /dev/null +++ b/services/catalog/tests/unit/test_cli.py @@ -0,0 +1,34 @@ +# pylint:disable=unused-variable +# pylint:disable=unused-argument +# pylint:disable=redefined-outer-name + +import os + +from pytest_simcore.helpers.typing_env import EnvVarsDict +from simcore_service_catalog._meta import API_VERSION +from simcore_service_catalog.cli import main +from simcore_service_catalog.core.settings import ApplicationSettings +from typer.testing import CliRunner + + +def test_cli_help_and_version(cli_runner: CliRunner): + result = cli_runner.invoke(main, "--help") + assert result.exit_code == os.EX_OK, result.output + + result = cli_runner.invoke(main, "--version") + assert result.exit_code == os.EX_OK, result.output + assert result.stdout.strip() == API_VERSION + + +def test_settings(cli_runner: CliRunner, app_environment: EnvVarsDict): + result = cli_runner.invoke(main, ["settings", "--show-secrets", "--as-json"]) + assert result.exit_code == os.EX_OK + + settings = ApplicationSettings.model_validate_json(result.output) + assert settings.model_dump() == ApplicationSettings.create_from_envs().model_dump() + + +def test_run(cli_runner: CliRunner): + result = cli_runner.invoke(main, ["run"]) + assert result.exit_code == 0 + assert "disabled" in result.stdout diff --git a/services/catalog/tests/unit/test_core_settings.py b/services/catalog/tests/unit/test_core_settings.py new file mode 100644 index 00000000000..9f94c6c3588 --- /dev/null +++ b/services/catalog/tests/unit/test_core_settings.py @@ -0,0 +1,22 @@ +# pylint: disable=redefined-outer-name +# pylint: disable=unused-argument +# pylint: disable=unused-variable +# pylint: disable=too-many-arguments + + +from pytest_simcore.helpers.typing_env import EnvVarsDict +from simcore_service_catalog.core.settings import ApplicationSettings + + +def test_valid_web_application_settings(app_environment: EnvVarsDict): + """ + We can validate actual .env files (also refered as `repo.config` files) by passing them via the CLI + + $ ln -s /path/to/osparc-config/deployments/mydeploy.com/repo.config .secrets + $ pytest --external-envfile=.secrets --pdb tests/unit/test_core_settings.py + + """ + settings = ApplicationSettings() # type: ignore + assert settings + + assert settings == ApplicationSettings.create_from_envs() diff --git a/services/catalog/tests/unit/test_services_compatibility.py b/services/catalog/tests/unit/test_services_compatibility.py index 2fc9f8f06b5..04ef4bafd4d 100644 --- a/services/catalog/tests/unit/test_services_compatibility.py +++ b/services/catalog/tests/unit/test_services_compatibility.py @@ -160,9 +160,10 @@ def test_get_latest_compatible_version(versions_history: list[Version]): def _create_as(cls, **overrides): kwargs = { - "deprecated": None, - "created": arrow.now().datetime, "compatibility_policy": None, + "created": arrow.now().datetime, + "deprecated": None, + "version_display": None, } kwargs.update(overrides) return cls(**kwargs) @@ -265,7 +266,7 @@ async def test_evaluate_service_compatibility_map_with_deprecated_versions( ): service_release_history = [ _create_as(ReleaseFromDB, version="1.0.0"), - _create_as(ReleaseFromDB, version="1.0.1", deprecated=True), + _create_as(ReleaseFromDB, version="1.0.1", deprecated=arrow.now().datetime), _create_as(ReleaseFromDB, version="1.2.0"), _create_as(ReleaseFromDB, version="1.2.5"), ] diff --git a/services/catalog/tests/unit/test_utils_service_resources.py b/services/catalog/tests/unit/test_utils_service_resources.py index 1df8b18b896..3fc329d2f50 100644 --- a/services/catalog/tests/unit/test_utils_service_resources.py +++ b/services/catalog/tests/unit/test_utils_service_resources.py @@ -291,6 +291,6 @@ def test_merge_service_resources_with_user_specs( assert all(key in merged_resources for key in expected_resources) for resource_key, resource_value in merged_resources.items(): # NOTE: so that float values are compared correctly - assert resource_value.dict() == pytest.approx( - expected_resources[resource_key].dict() + assert resource_value.model_dump() == pytest.approx( + expected_resources[resource_key].model_dump() ) diff --git a/services/catalog/tests/unit/with_dbs/conftest.py b/services/catalog/tests/unit/with_dbs/conftest.py index e31913ab9bb..1bd0bb27e50 100644 --- a/services/catalog/tests/unit/with_dbs/conftest.py +++ b/services/catalog/tests/unit/with_dbs/conftest.py @@ -1,7 +1,6 @@ # pylint: disable=not-context-manager # pylint: disable=protected-access # pylint: disable=redefined-outer-name -# pylint: disable=too-many-positional-arguments # pylint: disable=unused-argument # pylint: disable=unused-variable @@ -18,7 +17,7 @@ from models_library.products import ProductName from models_library.services import ServiceMetaDataPublished from models_library.users import UserID -from pydantic import Extra, parse_obj_as +from pydantic import ConfigDict, TypeAdapter from pytest_simcore.helpers.faker_factories import ( random_service_access_rights, random_service_meta_data, @@ -122,13 +121,13 @@ async def product( @pytest.fixture def target_product(product: dict[str, Any], product_name: ProductName) -> ProductName: - assert product_name == parse_obj_as(ProductName, product["name"]) + assert product_name == TypeAdapter(ProductName).validate_python(product["name"]) return product_name @pytest.fixture def other_product(product: dict[str, Any]) -> ProductName: - other = parse_obj_as(ProductName, "osparc") + other = TypeAdapter(ProductName).validate_python("osparc") assert other != product["name"] return other @@ -347,7 +346,7 @@ def _fake_factory(**overrides): data = deepcopy(template) data.update(**overrides) - assert ServiceMetaDataPublished.parse_obj( + assert ServiceMetaDataPublished.model_validate( data ), "Invalid fake data. Out of sync!" return data @@ -454,9 +453,7 @@ def create_director_list_services_from() -> ( """ class _Loader(ServiceMetaDataPublished): - class Config: - extra = Extra.ignore - allow_population_by_field_name = True + model_config = ConfigDict(extra="ignore", populate_by_name=True) def _( expected_director_list_services: list[dict[str, Any]], @@ -464,7 +461,7 @@ def _( ): return [ jsonable_encoder( - _Loader.parse_obj( + _Loader.model_validate( { **next(itertools.cycle(expected_director_list_services)), **data[0], # service, **access_rights = data diff --git a/services/catalog/tests/unit/with_dbs/test_api_rest_services__get.py b/services/catalog/tests/unit/with_dbs/test_api_rest_services__get.py index a3c85d3f31b..d4ca2539eb8 100644 --- a/services/catalog/tests/unit/with_dbs/test_api_rest_services__get.py +++ b/services/catalog/tests/unit/with_dbs/test_api_rest_services__get.py @@ -90,7 +90,7 @@ def test_get_service_with_details( assert response.status_code == 200 - got = ServiceGet.parse_obj(response.json()) + got = ServiceGet.model_validate(response.json()) assert got.key == service_key assert got.version == service_version diff --git a/services/catalog/tests/unit/with_dbs/test_api_rest_services__list.py b/services/catalog/tests/unit/with_dbs/test_api_rest_services__list.py index 4b0bd5dceb6..8c2071fea23 100644 --- a/services/catalog/tests/unit/with_dbs/test_api_rest_services__list.py +++ b/services/catalog/tests/unit/with_dbs/test_api_rest_services__list.py @@ -13,7 +13,7 @@ from models_library.products import ProductName from models_library.services import ServiceMetaDataPublished from models_library.users import UserID -from pydantic import parse_obj_as +from pydantic import TypeAdapter from respx.router import MockRouter from starlette import status from starlette.testclient import TestClient @@ -56,9 +56,9 @@ async def test_list_services_with_details( url = URL("/v0/services").with_query({"user_id": user_id, "details": "true"}) # now fake the director such that it returns half the services - fake_registry_service_data = ServiceMetaDataPublished.Config.schema_extra[ - "examples" - ][0] + fake_registry_service_data = ServiceMetaDataPublished.model_config[ + "json_schema_extra" + ]["examples"][0] mocked_director_service_api_base.get("/services", name="list_services").respond( 200, @@ -255,16 +255,16 @@ async def test_list_services_that_are_deprecated( url = URL("/v0/services").with_query({"user_id": user_id, "details": "false"}) resp = client.get(f"{url}", headers={"x-simcore-products-name": target_product}) assert resp.status_code == status.HTTP_200_OK - list_of_services = parse_obj_as(list[ServiceGet], resp.json()) + list_of_services = TypeAdapter(list[ServiceGet]).validate_python(resp.json()) assert list_of_services assert len(list_of_services) == 1 received_service = list_of_services[0] assert received_service.deprecated == deprecation_date # for details, the director must return the same service - fake_registry_service_data = ServiceMetaDataPublished.Config.schema_extra[ - "examples" - ][0] + fake_registry_service_data = ServiceMetaDataPublished.model_config[ + "json_schema_extra" + ]["examples"][0] mocked_director_service_api_base.get("/services", name="list_services").respond( 200, json={ @@ -281,7 +281,7 @@ async def test_list_services_that_are_deprecated( url = URL("/v0/services").with_query({"user_id": user_id, "details": "true"}) resp = client.get(f"{url}", headers={"x-simcore-products-name": target_product}) assert resp.status_code == status.HTTP_200_OK - list_of_services = parse_obj_as(list[ServiceGet], resp.json()) + list_of_services = TypeAdapter(list[ServiceGet]).validate_python(resp.json()) assert list_of_services assert len(list_of_services) == 1 received_service = list_of_services[0] diff --git a/services/catalog/tests/unit/with_dbs/test_api_rest_services_access_rights.py b/services/catalog/tests/unit/with_dbs/test_api_rest_services_access_rights.py index 87ac133a0df..425b1318950 100644 --- a/services/catalog/tests/unit/with_dbs/test_api_rest_services_access_rights.py +++ b/services/catalog/tests/unit/with_dbs/test_api_rest_services_access_rights.py @@ -14,7 +14,7 @@ ServiceAccessRightsGet, ) from models_library.products import ProductName -from pydantic import parse_obj_as +from pydantic import TypeAdapter from respx.router import MockRouter from starlette.testclient import TestClient from yarl import URL @@ -66,7 +66,7 @@ async def test_get_service_access_rights( headers={"x-simcore-products-name": target_product}, ) assert response.status_code == 200 - data = parse_obj_as(ServiceAccessRightsGet, response.json()) + data = TypeAdapter(ServiceAccessRightsGet).validate_python(response.json()) assert data.service_key == service_to_check["key"] assert data.service_version == service_to_check["version"] assert data.gids_with_access_rights == { @@ -108,7 +108,7 @@ async def test_get_service_access_rights_with_more_gids( headers={"x-simcore-products-name": other_product}, ) assert response.status_code == 200 - data = parse_obj_as(ServiceAccessRightsGet, response.json()) + data = TypeAdapter(ServiceAccessRightsGet).validate_python(response.json()) assert data.service_key == service_to_check["key"] assert data.service_version == service_to_check["version"] assert data.gids_with_access_rights == { diff --git a/services/catalog/tests/unit/with_dbs/test_api_rest_services_resources.py b/services/catalog/tests/unit/with_dbs/test_api_rest_services_resources.py index 1ea7e40f18f..d9ef5ea328f 100644 --- a/services/catalog/tests/unit/with_dbs/test_api_rest_services_resources.py +++ b/services/catalog/tests/unit/with_dbs/test_api_rest_services_resources.py @@ -6,13 +6,13 @@ from collections.abc import Callable from copy import deepcopy from dataclasses import dataclass -from random import choice, randint from typing import Any import httpx import pytest import respx from faker import Faker +from fastapi.encoders import jsonable_encoder from models_library.docker import DockerGenericTag from models_library.services_resources import ( BootMode, @@ -21,7 +21,7 @@ ServiceResourcesDict, ServiceResourcesDictHelpers, ) -from pydantic import ByteSize, parse_obj_as +from pydantic import ByteSize, TypeAdapter from respx.models import Route from simcore_service_catalog.core.settings import _DEFAULT_RESOURCES from starlette.testclient import TestClient @@ -58,13 +58,15 @@ def creator(): @pytest.fixture -def service_key() -> str: - return f"simcore/services/{choice(['comp', 'dynamic','frontend'])}/jupyter-math" +def service_key(faker: Faker) -> str: + return f"simcore/services/{faker.random_element(['comp', 'dynamic','frontend'])}/jupyter-math" @pytest.fixture -def service_version() -> str: - return f"{randint(0,100)}.{randint(0,100)}.{randint(0,100)}" +def service_version(faker: Faker) -> str: + return ( + f"{faker.random_int(0,100)}.{faker.random_int(0,100)}.{faker.random_int(0,100)}" + ) @pytest.fixture @@ -189,24 +191,27 @@ async def test_get_service_resources( mocked_director_service_labels: Route, client: TestClient, params: _ServiceResourceParams, + service_key: str, + service_version: str, ) -> None: - service_key = f"simcore/services/{choice(['comp', 'dynamic'])}/jupyter-math" - service_version = f"{randint(0,100)}.{randint(0,100)}.{randint(0,100)}" + mocked_director_service_labels.respond(json={"data": params.simcore_service_label}) url = URL(f"/v0/services/{service_key}/{service_version}/resources") response = client.get(f"{url}") assert response.status_code == 200, f"{response.text}" data = response.json() - received_resources: ServiceResourcesDict = parse_obj_as(ServiceResourcesDict, data) + received_resources: ServiceResourcesDict = ServiceResourcesDict(**data) assert isinstance(received_resources, dict) expected_service_resources = ServiceResourcesDictHelpers.create_from_single_service( - parse_obj_as(DockerGenericTag, f"{service_key}:{service_version}"), + TypeAdapter(DockerGenericTag).validate_python( + f"{service_key}:{service_version}" + ), params.expected_resources, boot_modes=params.expected_boot_modes, ) assert isinstance(expected_service_resources, dict) - assert received_resources == expected_service_resources + assert received_resources == jsonable_encoder(expected_service_resources) @pytest.fixture @@ -241,9 +246,10 @@ def factory(services_labels: dict[str, dict[str, Any]]) -> None: }, "sym-server": {"simcore.service.settings": "[]"}, }, - parse_obj_as( - ServiceResourcesDict, - ServiceResourcesDictHelpers.Config.schema_extra["examples"][1], + TypeAdapter(ServiceResourcesDict).validate_python( + ServiceResourcesDictHelpers.model_config["json_schema_extra"][ + "examples" + ][1] ), "simcore/services/dynamic/sim4life-dy", "3.0.0", @@ -257,16 +263,17 @@ def factory(services_labels: dict[str, dict[str, Any]]) -> None: }, "busybox": {"simcore.service.settings": "[]"}, }, - parse_obj_as( - ServiceResourcesDict, + TypeAdapter(ServiceResourcesDict).validate_python( { "jupyter-math": { "image": "simcore/services/dynamic/jupyter-math:2.0.5", "resources": { "CPU": {"limit": 0.1, "reservation": 0.1}, "RAM": { - "limit": parse_obj_as(ByteSize, "2Gib"), - "reservation": parse_obj_as(ByteSize, "2Gib"), + "limit": TypeAdapter(ByteSize).validate_python("2Gib"), + "reservation": TypeAdapter(ByteSize).validate_python( + "2Gib" + ), }, }, }, @@ -275,8 +282,10 @@ def factory(services_labels: dict[str, dict[str, Any]]) -> None: "resources": { "CPU": {"limit": 0.1, "reservation": 0.1}, "RAM": { - "limit": parse_obj_as(ByteSize, "2Gib"), - "reservation": parse_obj_as(ByteSize, "2Gib"), + "limit": TypeAdapter(ByteSize).validate_python("2Gib"), + "reservation": TypeAdapter(ByteSize).validate_python( + "2Gib" + ), }, }, }, @@ -304,7 +313,7 @@ async def test_get_service_resources_sim4life_case( response = client.get(f"{url}") assert response.status_code == 200, f"{response.text}" data = response.json() - received_service_resources = parse_obj_as(ServiceResourcesDict, data) + received_service_resources = TypeAdapter(ServiceResourcesDict).validate_python(data) assert received_service_resources == expected_service_resources @@ -314,10 +323,10 @@ async def test_get_service_resources_raises_errors( rabbitmq_and_rpc_setup_disabled: None, mocked_director_service_labels: Route, client: TestClient, + service_key: str, + service_version: str, ) -> None: - service_key = f"simcore/services/{choice(['comp', 'dynamic'])}/jupyter-math" - service_version = f"{randint(0,100)}.{randint(0,100)}.{randint(0,100)}" url = URL(f"/v0/services/{service_key}/{service_version}/resources") # simulate a communication error mocked_director_service_labels.side_effect = httpx.HTTPError diff --git a/services/catalog/tests/unit/with_dbs/test_api_rest_services_specifications.py b/services/catalog/tests/unit/with_dbs/test_api_rest_services_specifications.py index 394ea9123ad..e024a511ad4 100644 --- a/services/catalog/tests/unit/with_dbs/test_api_rest_services_specifications.py +++ b/services/catalog/tests/unit/with_dbs/test_api_rest_services_specifications.py @@ -4,10 +4,8 @@ # pylint: disable=unused-argument # pylint: disable=unused-variable - import asyncio from collections.abc import AsyncIterator, Awaitable, Callable -from random import choice, randint from typing import Any import pytest @@ -16,7 +14,6 @@ from fastapi.encoders import jsonable_encoder from models_library.api_schemas_catalog.services_specifications import ( ServiceSpecifications, - ServiceSpecificationsGet, ) from models_library.generated_models.docker_rest_api import ( DiscreteResourceSpec, @@ -29,7 +26,7 @@ from models_library.generated_models.docker_rest_api import ( Resources1 as ServiceTaskResources, ) -from models_library.generated_models.docker_rest_api import ServiceSpec +from models_library.generated_models.docker_rest_api import ServiceSpec, TaskSpec from models_library.products import ProductName from models_library.users import UserID from simcore_postgres_database.models.groups import user_to_groups @@ -93,29 +90,33 @@ def _creator(service_key, service_version, gid) -> ServiceSpecificationsAtDB: service_key=service_key, service_version=service_version, gid=gid, - sidecar=ServiceSpec(Labels=faker.pydict(allowed_types=(str,))), # type: ignore - service=ServiceTaskResources( - Limits=Limit( - NanoCPUs=faker.pyint(), - MemoryBytes=faker.pyint(), - Pids=faker.pyint(), - ), - Reservations=ResourceObject( - NanoCPUs=faker.pyint(), - MemoryBytes=faker.pyint(), - GenericResources=GenericResources( - __root__=[ - GenericResource( - NamedResourceSpec=NamedResourceSpec( - Kind=faker.pystr(), Value=faker.pystr() - ), - DiscreteResourceSpec=DiscreteResourceSpec( - Kind=faker.pystr(), Value=faker.pyint() - ), - ) - ] - ), - ), + sidecar=ServiceSpec(Labels=faker.pydict(allowed_types=(str,))), + service=ServiceSpec( + TaskTemplate=TaskSpec( + Resources=ServiceTaskResources( + Limits=Limit( + NanoCPUs=faker.pyint(), + MemoryBytes=faker.pyint(), + Pids=faker.pyint(), + ), + Reservations=ResourceObject( + NanoCPUs=faker.pyint(), + MemoryBytes=faker.pyint(), + GenericResources=GenericResources( + root=[ + GenericResource( + NamedResourceSpec=NamedResourceSpec( + Kind=faker.pystr(), Value=faker.pystr() + ), + DiscreteResourceSpec=DiscreteResourceSpec( + Kind=faker.pystr(), Value=faker.pyint() + ), + ) + ] + ), + ), + ) + ) ), ) @@ -128,9 +129,14 @@ async def test_get_service_specifications_returns_403_if_user_does_not_exist( rabbitmq_and_rpc_setup_disabled: None, client: TestClient, user_id: UserID, + faker: Faker, ): - service_key = f"simcore/services/{choice(['comp', 'dynamic'])}/jupyter-math" - service_version = f"{randint(0,100)}.{randint(0,100)}.{randint(0,100)}" + service_key = ( + f"simcore/services/{faker.random_element(['comp', 'dynamic'])}/jupyter-math" + ) + service_version = ( + f"{faker.random_int(0,100)}.{faker.random_int(0,100)}.{faker.random_int(0,100)}" + ) url = URL( f"/v0/services/{service_key}/{service_version}/specifications" ).with_query(user_id=user_id) @@ -147,21 +153,21 @@ async def test_get_service_specifications_of_unknown_service_returns_default_spe user: dict[str, Any], faker: Faker, ): - service_key = ( - f"simcore/services/{choice(['comp', 'dynamic'])}/{faker.pystr().lower()}" + service_key = f"simcore/services/{faker.random_element(['comp', 'dynamic'])}/{faker.pystr().lower()}" + service_version = ( + f"{faker.random_int(0,100)}.{faker.random_int(0,100)}.{faker.random_int(0,100)}" ) - service_version = f"{randint(0,100)}.{randint(0,100)}.{randint(0,100)}" url = URL( f"/v0/services/{service_key}/{service_version}/specifications" ).with_query(user_id=user_id) response = client.get(f"{url}") assert response.status_code == status.HTTP_200_OK - service_specs = ServiceSpecificationsGet.parse_obj(response.json()) + service_specs = ServiceSpecifications.model_validate(response.json()) assert service_specs assert ( - service_specs - == client.app.state.settings.CATALOG_SERVICES_DEFAULT_SPECIFICATIONS + service_specs.model_dump() + == client.app.state.settings.CATALOG_SERVICES_DEFAULT_SPECIFICATIONS.model_dump() ) @@ -201,11 +207,11 @@ async def test_get_service_specifications( # this should now return default specs since there are none in the db response = client.get(f"{url}") assert response.status_code == status.HTTP_200_OK - service_specs = ServiceSpecificationsGet.parse_obj(response.json()) + service_specs = ServiceSpecifications.model_validate(response.json()) assert service_specs assert ( - service_specs - == client.app.state.settings.CATALOG_SERVICES_DEFAULT_SPECIFICATIONS + service_specs.model_dump() + == client.app.state.settings.CATALOG_SERVICES_DEFAULT_SPECIFICATIONS.model_dump() ) everyone_gid, user_gid, team_gid = user_groups_ids @@ -216,10 +222,10 @@ async def test_get_service_specifications( await services_specifications_injector(everyone_service_specs) response = client.get(f"{url}") assert response.status_code == status.HTTP_200_OK - service_specs = ServiceSpecificationsGet.parse_obj(response.json()) + service_specs = ServiceSpecifications.model_validate(response.json()) assert service_specs - assert service_specs == ServiceSpecifications.parse_obj( - everyone_service_specs.dict() + assert service_specs == ServiceSpecifications.model_validate( + everyone_service_specs.model_dump() ) # let's inject some rights in a standard group, user is not part of that group yet, so it should still return only everyone @@ -229,10 +235,10 @@ async def test_get_service_specifications( await services_specifications_injector(standard_group_service_specs) response = client.get(f"{url}") assert response.status_code == status.HTTP_200_OK - service_specs = ServiceSpecificationsGet.parse_obj(response.json()) + service_specs = ServiceSpecifications.model_validate(response.json()) assert service_specs - assert service_specs == ServiceSpecifications.parse_obj( - everyone_service_specs.dict() + assert service_specs == ServiceSpecifications.model_validate( + everyone_service_specs.model_dump() ) # put the user in that group now and try again @@ -240,10 +246,10 @@ async def test_get_service_specifications( await conn.execute(user_to_groups.insert().values(uid=user_id, gid=team_gid)) response = client.get(f"{url}") assert response.status_code == status.HTTP_200_OK - service_specs = ServiceSpecificationsGet.parse_obj(response.json()) + service_specs = ServiceSpecifications.model_validate(response.json()) assert service_specs - assert service_specs == ServiceSpecifications.parse_obj( - standard_group_service_specs.dict() + assert service_specs == ServiceSpecifications.model_validate( + standard_group_service_specs.model_dump() ) # now add some other spec in the primary gid, this takes precedence @@ -253,10 +259,10 @@ async def test_get_service_specifications( await services_specifications_injector(user_group_service_specs) response = client.get(f"{url}") assert response.status_code == status.HTTP_200_OK - service_specs = ServiceSpecificationsGet.parse_obj(response.json()) + service_specs = ServiceSpecifications.model_validate(response.json()) assert service_specs - assert service_specs == ServiceSpecifications.parse_obj( - user_group_service_specs.dict() + assert service_specs == ServiceSpecifications.model_validate( + user_group_service_specs.model_dump() ) @@ -328,11 +334,11 @@ async def test_get_service_specifications_are_passed_to_newer_versions_of_servic ) response = client.get(f"{url}") assert response.status_code == status.HTTP_200_OK - service_specs = ServiceSpecificationsGet.parse_obj(response.json()) + service_specs = ServiceSpecifications.model_validate(response.json()) assert service_specs assert ( - service_specs - == client.app.state.settings.CATALOG_SERVICES_DEFAULT_SPECIFICATIONS + service_specs.model_dump() + == client.app.state.settings.CATALOG_SERVICES_DEFAULT_SPECIFICATIONS.model_dump() ) # check version between first index and second all return the specs of the first @@ -344,10 +350,10 @@ async def test_get_service_specifications_are_passed_to_newer_versions_of_servic ) response = client.get(f"{url}") assert response.status_code == status.HTTP_200_OK - service_specs = ServiceSpecificationsGet.parse_obj(response.json()) + service_specs = ServiceSpecifications.model_validate(response.json()) assert service_specs - assert service_specs == ServiceSpecifications.parse_obj( - version_speced[0].dict() + assert service_specs == ServiceSpecifications.model_validate( + version_speced[0].model_dump() ), f"specifications for {version=} are not passed down from {sorted_versions[INDEX_FIRST_SERVICE_VERSION_WITH_SPEC]}" # check version from second to last use the second version @@ -357,10 +363,10 @@ async def test_get_service_specifications_are_passed_to_newer_versions_of_servic ) response = client.get(f"{url}") assert response.status_code == status.HTTP_200_OK - service_specs = ServiceSpecificationsGet.parse_obj(response.json()) + service_specs = ServiceSpecifications.model_validate(response.json()) assert service_specs - assert service_specs == ServiceSpecifications.parse_obj( - version_speced[1].dict() + assert service_specs == ServiceSpecifications.model_validate( + version_speced[1].model_dump() ), f"specifications for {version=} are not passed down from {sorted_versions[INDEX_SECOND_SERVICE_VERSION_WITH_SPEC]}" # if we call with the strict parameter set to true, then we should only get the specs for the one that were specified @@ -370,7 +376,7 @@ async def test_get_service_specifications_are_passed_to_newer_versions_of_servic ) response = client.get(f"{url}") assert response.status_code == status.HTTP_200_OK - service_specs = ServiceSpecificationsGet.parse_obj(response.json()) + service_specs = ServiceSpecifications.model_validate(response.json()) assert service_specs if version in versions_with_specs: assert ( diff --git a/services/catalog/tests/unit/with_dbs/test_api_rpc.py b/services/catalog/tests/unit/with_dbs/test_api_rpc.py index 3aeaaf4ef73..16fb6adb4cb 100644 --- a/services/catalog/tests/unit/with_dbs/test_api_rpc.py +++ b/services/catalog/tests/unit/with_dbs/test_api_rpc.py @@ -161,7 +161,7 @@ async def test_rpc_catalog_client( "description": "bar", "version_display": "this is a nice version", "description_ui": True, # owner activates wiki view - }, + }, # type: ignore ) assert updated.key == got.key @@ -392,7 +392,7 @@ async def test_rpc_get_service_access_rights( service_key=service_key, service_version=service_version, ) - assert updated_service.dict(include={"name", "description"}) == { + assert updated_service.model_dump(include={"name", "description"}) == { "name": "foo", "description": "bar", } diff --git a/services/catalog/tests/unit/with_dbs/test_db_repositories.py b/services/catalog/tests/unit/with_dbs/test_db_repositories.py index 3438492f740..8c4053c4ca6 100644 --- a/services/catalog/tests/unit/with_dbs/test_db_repositories.py +++ b/services/catalog/tests/unit/with_dbs/test_db_repositories.py @@ -10,7 +10,7 @@ from models_library.users import UserID from packaging import version from packaging.version import Version -from pydantic import EmailStr, parse_obj_as +from pydantic import EmailStr, TypeAdapter from simcore_service_catalog.db.repositories.services import ServicesRepository from simcore_service_catalog.models.services_db import ( ServiceAccessRightsAtDB, @@ -109,16 +109,18 @@ async def test_create_services( ) # validation - service = ServiceMetaDataAtDB.parse_obj(fake_service) + service = ServiceMetaDataAtDB.model_validate(fake_service) service_access_rights = [ - ServiceAccessRightsAtDB.parse_obj(a) for a in fake_access_rights + ServiceAccessRightsAtDB.model_validate(a) for a in fake_access_rights ] new_service = await services_repo.create_or_update_service( service, service_access_rights ) - assert new_service.dict(include=set(fake_service.keys())) == service.dict() + assert ( + new_service.model_dump(include=set(fake_service.keys())) == service.model_dump() + ) async def test_read_services( @@ -177,7 +179,7 @@ async def test_read_services( assert service access_rights = await services_repo.get_service_access_rights( - product_name=target_product, **service.dict(include={"key", "version"}) + product_name=target_product, **service.model_dump(include={"key", "version"}) ) assert { user_gid, @@ -190,7 +192,7 @@ async def test_read_services( assert service access_rights = await services_repo.get_service_access_rights( - product_name=target_product, **service.dict(include={"key", "version"}) + product_name=target_product, **service.model_dump(include={"key", "version"}) ) assert {user_gid, team_gid} == {a.gid for a in access_rights} @@ -347,7 +349,9 @@ async def test_list_all_services_and_history_with_pagination( for service in services_items: assert len(service.history) == num_versions_per_service - assert parse_obj_as(EmailStr, service.owner_email), "resolved own'es email" + assert TypeAdapter(EmailStr).validate_python( + service.owner_email + ), "resolved own'es email" expected_latest_version = service.history[0].version # latest service is first assert service.version == expected_latest_version @@ -382,13 +386,13 @@ async def test_get_and_update_service_meta_data( assert got.version == service_version await services_repo.update_service( - ServiceMetaDataAtDB.construct( + ServiceMetaDataAtDB.model_construct( key=service_key, version=service_version, name="foo" ), ) updated = await services_repo.get_service(service_key, service_version) - assert got.copy(update={"name": "foo"}) == updated + assert got.model_copy(update={"name": "foo"}) == updated assert await services_repo.get_service(service_key, service_version) == updated diff --git a/services/catalog/tests/unit/with_dbs/test_services_access_rights.py b/services/catalog/tests/unit/with_dbs/test_services_access_rights.py index d4506855f6d..47d0dc201ac 100644 --- a/services/catalog/tests/unit/with_dbs/test_services_access_rights.py +++ b/services/catalog/tests/unit/with_dbs/test_services_access_rights.py @@ -8,7 +8,7 @@ from models_library.groups import GroupAtDB from models_library.products import ProductName from models_library.services import ServiceMetaDataPublished, ServiceVersion -from pydantic import parse_obj_as +from pydantic import TypeAdapter from simcore_service_catalog.db.repositories.services import ServicesRepository from simcore_service_catalog.models.services_db import ServiceAccessRightsAtDB from simcore_service_catalog.services.access_rights import ( @@ -27,7 +27,7 @@ def test_reduce_access_rights(): - sample = ServiceAccessRightsAtDB.parse_obj( + sample = ServiceAccessRightsAtDB.model_validate( { "key": "simcore/services/dynamic/sim4life", "version": "1.0.9", @@ -41,20 +41,20 @@ def test_reduce_access_rights(): # fixture with overrides and with other products reduced = reduce_access_rights( [ - sample.copy(deep=True), - sample.copy(deep=True), - sample.copy(update={"execute_access": False}, deep=True), - sample.copy(update={"product_name": "s4l"}, deep=True), + sample.model_copy(deep=True), + sample.model_copy(deep=True), + sample.model_copy(update={"execute_access": False}, deep=True), + sample.model_copy(update={"product_name": "s4l"}, deep=True), ] ) # two products with the same flags assert len(reduced) == 2 - assert reduced[0].dict(include={"execute_access", "write_access"}) == { + assert reduced[0].model_dump(include={"execute_access", "write_access"}) == { "execute_access": True, "write_access": True, } - assert reduced[1].dict(include={"execute_access", "write_access"}) == { + assert reduced[1].model_dump(include={"execute_access", "write_access"}) == { "execute_access": True, "write_access": True, } @@ -62,8 +62,8 @@ def test_reduce_access_rights(): # two gids with the different falgs reduced = reduce_access_rights( [ - sample.copy(deep=True), - sample.copy( + sample.model_copy(deep=True), + sample.model_copy( update={"gid": 1, "execute_access": True, "write_access": False}, deep=True, ), @@ -71,11 +71,11 @@ def test_reduce_access_rights(): ) assert len(reduced) == 2 - assert reduced[0].dict(include={"execute_access", "write_access"}) == { + assert reduced[0].model_dump(include={"execute_access", "write_access"}) == { "execute_access": True, "write_access": True, } - assert reduced[1].dict(include={"execute_access", "write_access"}) == { + assert reduced[1].model_dump(include={"execute_access", "write_access"}) == { "execute_access": True, "write_access": False, } @@ -98,11 +98,11 @@ async def test_auto_upgrade_policy( return_value=False, ) # Avoids creating a users + user_to_group table - data = GroupAtDB.Config.schema_extra["example"] + data = GroupAtDB.model_config["json_schema_extra"]["example"] data["gid"] = everyone_gid mocker.patch( "simcore_service_catalog.services.access_rights.GroupsRepository.get_everyone_group", - return_value=GroupAtDB.parse_obj(data), + return_value=GroupAtDB.model_validate(data), ) mocker.patch( "simcore_service_catalog.services.access_rights.GroupsRepository.get_user_gid_from_email", @@ -111,10 +111,12 @@ async def test_auto_upgrade_policy( # SETUP --- MOST_UPDATED_EXAMPLE = -1 - new_service_metadata = ServiceMetaDataPublished.parse_obj( - ServiceMetaDataPublished.Config.schema_extra["examples"][MOST_UPDATED_EXAMPLE] + new_service_metadata = ServiceMetaDataPublished.model_validate( + ServiceMetaDataPublished.model_config["json_schema_extra"]["examples"][ + MOST_UPDATED_EXAMPLE + ] ) - new_service_metadata.version = parse_obj_as(ServiceVersion, "1.0.11") + new_service_metadata.version = TypeAdapter(ServiceVersion).validate_python("1.0.11") # we have three versions of the service in the database for which the sorting matters: (1.0.11 should inherit from 1.0.10 not 1.0.9) await services_db_tables_injector( @@ -167,7 +169,7 @@ async def test_auto_upgrade_policy( assert owner_gid == user_gid assert len(service_access_rights) == 1 assert {a.gid for a in service_access_rights} == {owner_gid} - assert service_access_rights[0].dict() == { + assert service_access_rights[0].model_dump() == { "key": new_service_metadata.key, "version": new_service_metadata.version, "gid": user_gid,