From f07afbf19271d7853dc94934c0a6493996a11f19 Mon Sep 17 00:00:00 2001 From: Andrei Neagu <5694077+GitHK@users.noreply.github.com> Date: Mon, 11 Nov 2024 08:53:54 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20packages=20mypy,=20pylin?= =?UTF-8?q?t=20and=20tests=20(#6678)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Andrei Neagu --- .../tests/test_pydantic_validators.py | 4 +- .../api_schemas_directorv2/clusters.py | 2 +- .../api_schemas_directorv2/comp_tasks.py | 13 ++++- .../api_schemas_dynamic_sidecar/telemetry.py | 23 +------- .../src/models_library/api_schemas_storage.py | 8 +-- .../api_schemas_webserver/projects.py | 10 +++- .../src/models_library/progress_bar.py | 4 +- .../src/models_library/rest_pagination.py | 32 +---------- .../src/models_library/rpc_pagination.py | 32 +---------- .../models_library/service_settings_labels.py | 17 +++--- .../src/models_library/services_base.py | 12 ++-- .../src/models_library/services_types.py | 4 +- .../models_library/utils/common_validators.py | 3 +- .../tests/test__models_examples.py | 17 +++++- ...t_api_schemas_dynamic_sidecar_telemetry.py | 8 +-- .../tests/test_project_nodes.py | 5 +- .../models-library/tests/test_rest_filters.py | 12 ++-- .../tests/test_rest_pagination.py | 19 +++++-- .../tests/test_rpc_pagination.py | 5 +- .../src/notifications_library/errors.py | 5 +- .../utils_projects.py | 4 +- .../src/pytest_simcore/examples/__init__.py | 0 .../pytest_simcore/examples/models_library.py | 55 +++++++++++++++++++ .../src/pytest_simcore/pydantic_models.py | 9 +++ .../simcore_webserver_projects_rest_api.py | 22 ++++++++ .../src/servicelib/aiohttp/tracing.py | 4 +- .../src/servicelib/fastapi/tracing.py | 4 +- .../src/servicelib/rabbitmq/_models.py | 1 - .../tests/fastapi/test_http_client_thin.py | 6 +- .../src/settings_library/base.py | 8 ++- .../src/settings_library/node_ports.py | 14 ++--- .../src/settings_library/twilio.py | 3 +- .../tests/test_base_w_postgres.py | 1 - .../node_ports_common/storage_endpoint.py | 1 + .../tests/unit/test_node_ports_v2_links.py | 9 ++- .../unit/test_node_ports_v2_nodeports_v2.py | 7 +-- .../_logging_event_handler.py | 6 +- .../modules/outputs/_event_handler.py | 4 +- 38 files changed, 219 insertions(+), 174 deletions(-) create mode 100644 packages/pytest-simcore/src/pytest_simcore/examples/__init__.py create mode 100644 packages/pytest-simcore/src/pytest_simcore/examples/models_library.py diff --git a/packages/common-library/tests/test_pydantic_validators.py b/packages/common-library/tests/test_pydantic_validators.py index 89e57bc0999..c1cfea84c67 100644 --- a/packages/common-library/tests/test_pydantic_validators.py +++ b/packages/common-library/tests/test_pydantic_validators.py @@ -3,7 +3,7 @@ import pytest from common_library.pydantic_validators import ( - validate_legacy_timedelta_str, + _validate_legacy_timedelta_str, validate_numeric_string_as_timedelta, ) from faker import Faker @@ -16,7 +16,7 @@ def test_validate_legacy_timedelta(monkeypatch: pytest.MonkeyPatch, faker: Faker class Settings(BaseSettings): APP_NAME: str REQUEST_TIMEOUT: Annotated[ - timedelta, BeforeValidator(validate_legacy_timedelta_str) + timedelta, BeforeValidator(_validate_legacy_timedelta_str) ] = Field(default=timedelta(hours=1)) model_config = SettingsConfigDict() diff --git a/packages/models-library/src/models_library/api_schemas_directorv2/clusters.py b/packages/models-library/src/models_library/api_schemas_directorv2/clusters.py index 9c4698a5dc3..158242195e1 100644 --- a/packages/models-library/src/models_library/api_schemas_directorv2/clusters.py +++ b/packages/models-library/src/models_library/api_schemas_directorv2/clusters.py @@ -113,7 +113,7 @@ class ClusterDetailsGet(ClusterDetails): class ClusterCreate(BaseCluster): - owner: GroupID | None + owner: GroupID | None = None # type: ignore[assignment] authentication: ExternalClusterAuthentication access_rights: dict[GroupID, ClusterAccessRights] = Field( alias="accessRights", default_factory=dict diff --git a/packages/models-library/src/models_library/api_schemas_directorv2/comp_tasks.py b/packages/models-library/src/models_library/api_schemas_directorv2/comp_tasks.py index e383d45f20e..2204186c3ed 100644 --- a/packages/models-library/src/models_library/api_schemas_directorv2/comp_tasks.py +++ b/packages/models-library/src/models_library/api_schemas_directorv2/comp_tasks.py @@ -1,7 +1,7 @@ from typing import Any, TypeAlias from models_library.basic_types import IDStr -from pydantic import AnyHttpUrl, AnyUrl, BaseModel, Field, field_validator +from pydantic import AnyHttpUrl, AnyUrl, BaseModel, ConfigDict, Field, field_validator from ..clusters import ClusterID from ..projects import ProjectID @@ -19,6 +19,17 @@ class ComputationGet(ComputationTask): None, description="the link where to stop the task" ) + model_config = ConfigDict( + json_schema_extra={ + "examples": [ + x | {"url": "http://url.local"} # type:ignore[operator] + for x in ComputationTask.model_config[ # type:ignore[index,union-attr] + "json_schema_extra" + ]["examples"] + ] + } + ) + class ComputationCreate(BaseModel): user_id: UserID diff --git a/packages/models-library/src/models_library/api_schemas_dynamic_sidecar/telemetry.py b/packages/models-library/src/models_library/api_schemas_dynamic_sidecar/telemetry.py index b7d315a8aa6..25b71df9e0f 100644 --- a/packages/models-library/src/models_library/api_schemas_dynamic_sidecar/telemetry.py +++ b/packages/models-library/src/models_library/api_schemas_dynamic_sidecar/telemetry.py @@ -8,8 +8,7 @@ Field, NonNegativeFloat, NonNegativeInt, - root_validator, - validator, + model_validator, ) from ..projects_nodes_io import NodeID @@ -56,29 +55,13 @@ class DiskUsage(BaseModel): free: ByteSize = Field(description="remaining space") total: ByteSize = Field(description="total space = free + used") - used_percent: NonNegativeFloat = Field( + used_percent: float = Field( ge=0.00, le=100.00, description="Percent of used space relative to the total space", ) - @validator("free") - @classmethod - def _free_positive(cls, v: float) -> float: - if v < 0: - msg = f"free={v} cannot be a negative value" - raise ValueError(msg) - return v - - @validator("used") - @classmethod - def _used_positive(cls, v: float) -> float: - if v < 0: - msg = f"used={v} cannot be a negative value" - raise ValueError(msg) - return v - - @root_validator(pre=True) + @model_validator(mode="before") @classmethod def _check_total(cls, values: dict[str, Any]) -> dict[str, Any]: total = values["total"] diff --git a/packages/models-library/src/models_library/api_schemas_storage.py b/packages/models-library/src/models_library/api_schemas_storage.py index 54c5e28d43d..cdbbeebffcd 100644 --- a/packages/models-library/src/models_library/api_schemas_storage.py +++ b/packages/models-library/src/models_library/api_schemas_storage.py @@ -311,12 +311,12 @@ class FileUploadCompleteFutureResponse(BaseModel): class FoldersBody(BaseModel): - source: dict[str, Any] = Field(default_factory=dict) - destination: dict[str, Any] = Field(default_factory=dict) - nodes_map: dict[NodeID, NodeID] = Field(default_factory=dict) + source: Annotated[dict[str, Any], Field(default_factory=dict)] + destination: Annotated[dict[str, Any], Field(default_factory=dict)] + nodes_map: Annotated[dict[NodeID, NodeID], Field(default_factory=dict)] @model_validator(mode="after") - def ensure_consistent_entries(self) -> Self: + def ensure_consistent_entries(self: Self) -> Self: source_node_keys = (NodeID(n) for n in self.source.get("workbench", {})) if set(source_node_keys) != set(self.nodes_map.keys()): msg = "source project nodes do not fit with nodes_map entries" diff --git a/packages/models-library/src/models_library/api_schemas_webserver/projects.py b/packages/models-library/src/models_library/api_schemas_webserver/projects.py index 0ae2bdee311..a7d63904350 100644 --- a/packages/models-library/src/models_library/api_schemas_webserver/projects.py +++ b/packages/models-library/src/models_library/api_schemas_webserver/projects.py @@ -107,7 +107,9 @@ class ProjectReplace(InputSchema): uuid: ProjectID name: ShortTruncatedStr description: LongTruncatedStr - thumbnail: Annotated[HttpUrl | None, BeforeValidator(empty_str_to_none_pre_validator)] = Field(default=None) + thumbnail: Annotated[ + HttpUrl | None, BeforeValidator(empty_str_to_none_pre_validator) + ] = Field(default=None) creation_date: DateTimeStr last_change_date: DateTimeStr workbench: NodesDict @@ -127,13 +129,15 @@ class ProjectReplace(InputSchema): class ProjectPatch(InputSchema): name: ShortTruncatedStr | None = Field(default=None) description: LongTruncatedStr | None = Field(default=None) - thumbnail: Annotated[HttpUrl | None, BeforeValidator(empty_str_to_none_pre_validator)] = Field(default=None) + thumbnail: Annotated[ + HttpUrl | None, BeforeValidator(empty_str_to_none_pre_validator) + ] = Field(default=None) access_rights: dict[GroupIDStr, AccessRights] | None = Field(default=None) classifiers: list[ClassifierID] | None = Field(default=None) dev: dict | None = Field(default=None) ui: StudyUI | None = Field(default=None) quality: dict[str, Any] | None = Field(default=None) - + __all__: tuple[str, ...] = ( "EmptyModel", diff --git a/packages/models-library/src/models_library/progress_bar.py b/packages/models-library/src/models_library/progress_bar.py index da2829b0c94..90232847bbc 100644 --- a/packages/models-library/src/models_library/progress_bar.py +++ b/packages/models-library/src/models_library/progress_bar.py @@ -12,8 +12,8 @@ class ProgressStructuredMessage(BaseModel): description: IDStr current: float total: int - unit: str | None - sub: "ProgressStructuredMessage | None" + unit: str | None = None + sub: "ProgressStructuredMessage | None" = None model_config = ConfigDict( json_schema_extra={ diff --git a/packages/models-library/src/models_library/rest_pagination.py b/packages/models-library/src/models_library/rest_pagination.py index 28556d50303..e3e5e04e6ef 100644 --- a/packages/models-library/src/models_library/rest_pagination.py +++ b/packages/models-library/src/models_library/rest_pagination.py @@ -136,34 +136,4 @@ def _check_data_compatible_with_meta(cls, v, info: ValidationInfo): raise ValueError(msg) return v - model_config = ConfigDict( - extra="forbid", - json_schema_extra={ - "examples": [ - # first page Page[str] - { - "_meta": {"total": 7, "count": 4, "limit": 4, "offset": 0}, - "_links": { - "self": "https://osparc.io/v2/listing?offset=0&limit=4", - "first": "https://osparc.io/v2/listing?offset=0&limit=4", - "prev": None, - "next": "https://osparc.io/v2/listing?offset=1&limit=4", - "last": "https://osparc.io/v2/listing?offset=1&limit=4", - }, - "data": ["data 1", "data 2", "data 3", "data 4"], - }, - # second and last page - { - "_meta": {"total": 7, "count": 3, "limit": 4, "offset": 1}, - "_links": { - "self": "https://osparc.io/v2/listing?offset=1&limit=4", - "first": "https://osparc.io/v2/listing?offset=0&limit=4", - "prev": "https://osparc.io/v2/listing?offset=0&limit=4", - "next": None, - "last": "https://osparc.io/v2/listing?offset=1&limit=4", - }, - "data": ["data 5", "data 6", "data 7"], - }, - ] - }, - ) + model_config = ConfigDict(extra="forbid") diff --git a/packages/models-library/src/models_library/rpc_pagination.py b/packages/models-library/src/models_library/rpc_pagination.py index 92470b30d67..96d6308f66c 100644 --- a/packages/models-library/src/models_library/rpc_pagination.py +++ b/packages/models-library/src/models_library/rpc_pagination.py @@ -74,34 +74,4 @@ def create( data=chunk, ) - model_config = ConfigDict( - extra="forbid", - json_schema_extra={ - "examples": [ - # first page Page[str] - { - "_meta": {"total": 7, "count": 4, "limit": 4, "offset": 0}, - "_links": { - "self": {"offset": 0, "limit": 4}, - "first": {"offset": 0, "limit": 4}, - "prev": None, - "next": {"offset": 1, "limit": 4}, - "last": {"offset": 1, "limit": 4}, - }, - "data": ["data 1", "data 2", "data 3", "data 4"], - }, - # second and last page - { - "_meta": {"total": 7, "count": 3, "limit": 4, "offset": 1}, - "_links": { - "self": {"offset": 1, "limit": 4}, - "first": {"offset": 0, "limit": 4}, - "prev": {"offset": 0, "limit": 4}, - "next": None, - "last": {"offset": 1, "limit": 4}, - }, - "data": ["data 5", "data 6", "data 7"], - }, - ] - }, - ) + model_config = ConfigDict(extra="forbid") diff --git a/packages/models-library/src/models_library/service_settings_labels.py b/packages/models-library/src/models_library/service_settings_labels.py index 0a632a7b70d..0a4e35717bb 100644 --- a/packages/models-library/src/models_library/service_settings_labels.py +++ b/packages/models-library/src/models_library/service_settings_labels.py @@ -3,7 +3,7 @@ from enum import Enum from functools import cached_property from pathlib import Path -from typing import Any, Literal, TypeAlias +from typing import Annotated, Any, Literal, TypeAlias from common_library.json_serialization import json_dumps from pydantic import ( @@ -317,13 +317,14 @@ class DynamicSidecarServiceLabels(BaseModel): ), ) - containers_allowed_outgoing_permit_list: None | ( - Json[dict[str, list[NATRule]]] - ) = Field( - None, - alias="simcore.service.containers-allowed-outgoing-permit-list", - description="allow internet access to certain domain names and ports per container", - ) + containers_allowed_outgoing_permit_list: Annotated[ + None | (Json[dict[str, list[NATRule]]]), + Field( + None, + alias="simcore.service.containers-allowed-outgoing-permit-list", + description="allow internet access to certain domain names and ports per container", + ), + ] containers_allowed_outgoing_internet: Json[set[str]] | None = Field( None, diff --git a/packages/models-library/src/models_library/services_base.py b/packages/models-library/src/models_library/services_base.py index 5f92d6e46b6..48afb0b6c04 100644 --- a/packages/models-library/src/models_library/services_base.py +++ b/packages/models-library/src/models_library/services_base.py @@ -1,4 +1,5 @@ from typing import Annotated + from pydantic import BaseModel, ConfigDict, Field, HttpUrl, field_validator from .services_types import ServiceKey, ServiceVersion @@ -8,10 +9,13 @@ class ServiceKeyVersion(BaseModel): """Service `key-version` pair uniquely identifies a service""" - key: ServiceKey = Field( - ..., - description="distinctive name for the node based on the docker registry path", - ) + key: Annotated[ + ServiceKey, + Field( + ..., + description="distinctive name for the node based on the docker registry path", + ), + ] version: ServiceVersion = Field( ..., description="service version number", diff --git a/packages/models-library/src/models_library/services_types.py b/packages/models-library/src/models_library/services_types.py index e882c8dae09..03c0bb4bf5d 100644 --- a/packages/models-library/src/models_library/services_types.py +++ b/packages/models-library/src/models_library/services_types.py @@ -61,7 +61,9 @@ def create(cls) -> "RunID": @classmethod def __get_pydantic_core_schema__( - cls, source_type: Any, handler: GetCoreSchemaHandler + cls, + source_type: Any, # pylint:disable=unused-argument + handler: GetCoreSchemaHandler, ) -> CoreSchema: return core_schema.no_info_after_validator_function(cls, handler(str)) diff --git a/packages/models-library/src/models_library/utils/common_validators.py b/packages/models-library/src/models_library/utils/common_validators.py index d488c9d4acc..79c07249e03 100644 --- a/packages/models-library/src/models_library/utils/common_validators.py +++ b/packages/models-library/src/models_library/utils/common_validators.py @@ -93,7 +93,8 @@ def _validator(cls, values): assert set(alternative_field_names).issubset(cls.__fields__) # nosec got = { - field_name: values.get(field_name) for field_name in alternative_field_names + field_name: getattr(values, field_name) + for field_name in alternative_field_names } if not functools.reduce(operator.xor, (v is not None for v in got.values())): diff --git a/packages/models-library/tests/test__models_examples.py b/packages/models-library/tests/test__models_examples.py index 2345b5451f1..482f586df7c 100644 --- a/packages/models-library/tests/test__models_examples.py +++ b/packages/models-library/tests/test__models_examples.py @@ -1,15 +1,28 @@ import json +from itertools import chain from typing import Any import models_library import pytest +from models_library.rest_pagination import Page +from models_library.rpc_pagination import PageRpc from pydantic import BaseModel -from pytest_simcore.pydantic_models import walk_model_examples_in_package +from pytest_simcore.examples.models_library import PAGE_EXAMPLES, RPC_PAGE_EXAMPLES +from pytest_simcore.pydantic_models import ( + ModelExample, + iter_examples, + walk_model_examples_in_package, +) + +GENERIC_EXAMPLES: list[ModelExample] = [ + *iter_examples(model_cls=Page[str], examples=PAGE_EXAMPLES), + *iter_examples(model_cls=PageRpc[str], examples=RPC_PAGE_EXAMPLES), +] @pytest.mark.parametrize( "model_cls, example_name, example_data", - walk_model_examples_in_package(models_library), + chain(GENERIC_EXAMPLES, walk_model_examples_in_package(models_library)), ) def test_all_models_library_models_config_examples( model_cls: type[BaseModel], example_name: int, example_data: Any diff --git a/packages/models-library/tests/test_api_schemas_dynamic_sidecar_telemetry.py b/packages/models-library/tests/test_api_schemas_dynamic_sidecar_telemetry.py index 8de5c01dc83..d5ffc459397 100644 --- a/packages/models-library/tests/test_api_schemas_dynamic_sidecar_telemetry.py +++ b/packages/models-library/tests/test_api_schemas_dynamic_sidecar_telemetry.py @@ -42,8 +42,8 @@ def test_failing_validation(): with pytest.raises(ValidationError) as exc: assert DiskUsage.from_efs_guardian(100, 10) - assert "free=" in f"{exc.value}" - assert "negative value" in f"{exc.value}" + assert "free" in f"{exc.value}" + assert "input_value=-90" in f"{exc.value}" with pytest.raises(ValidationError) as exc: assert DiskUsage( @@ -52,8 +52,8 @@ def test_failing_validation(): total=ByteSize(0), used_percent=-10, ) - assert "used=" in f"{exc.value}" - assert "negative value" in f"{exc.value}" + assert "used" in f"{exc.value}" + assert "input_value=-10" in f"{exc.value}" with pytest.raises(ValidationError) as exc: DiskUsage( diff --git a/packages/models-library/tests/test_project_nodes.py b/packages/models-library/tests/test_project_nodes.py index 96f427a19cb..09b5511e2bf 100644 --- a/packages/models-library/tests/test_project_nodes.py +++ b/packages/models-library/tests/test_project_nodes.py @@ -1,6 +1,7 @@ -# pylint:disable=unused-variable -# pylint:disable=unused-argument +# pylint:disable=no-member # pylint:disable=redefined-outer-name +# pylint:disable=unused-argument +# pylint:disable=unused-variable from typing import Any diff --git a/packages/models-library/tests/test_rest_filters.py b/packages/models-library/tests/test_rest_filters.py index 0a46bd3a25b..1b470fc1767 100644 --- a/packages/models-library/tests/test_rest_filters.py +++ b/packages/models-library/tests/test_rest_filters.py @@ -2,7 +2,7 @@ import pytest from models_library.rest_filters import Filters, FiltersQueryParameters -from pydantic import Extra, ValidationError +from pydantic import ConfigDict, ValidationError # 1. create filter model @@ -12,8 +12,7 @@ class CustomFilter(Filters): class CustomFilterStrict(CustomFilter): - class Config(CustomFilter.Config): - extra = Extra.forbid + model_config = ConfigDict(extra="forbid") def test_custom_filter_query_parameters(): @@ -21,12 +20,12 @@ def test_custom_filter_query_parameters(): # 2. use generic as query parameters logging.info( "json schema is for the query \n %s", - FiltersQueryParameters[CustomFilter].schema_json(indent=1), + FiltersQueryParameters[CustomFilter].model_json_schema(), ) # lets filter only is_trashed and unset is_hidden custom_filter = CustomFilter(is_trashed=True) - assert custom_filter.json() == '{"is_trashed": true, "is_hidden": null}' + assert custom_filter.model_dump_json() == '{"is_trashed":true,"is_hidden":null}' # default to None (optional) query_param = FiltersQueryParameters[CustomFilter]() @@ -56,9 +55,8 @@ def test_invalid_filter_query_is_ignored(): assert query_param.filters == CustomFilter(is_hidden=True) -@pytest.mark.xfail def test_invalid_filter_query_fails(): - # NOTE: this should fail according to pydantic manual but it does not + # with pydantic1 this used to not pass but now passes url_query_value = '{"undefined_filter": true, "is_hidden": true}' with pytest.raises(ValidationError): diff --git a/packages/models-library/tests/test_rest_pagination.py b/packages/models-library/tests/test_rest_pagination.py index d0f5f9f7d92..a32bec673bb 100644 --- a/packages/models-library/tests/test_rest_pagination.py +++ b/packages/models-library/tests/test_rest_pagination.py @@ -3,11 +3,20 @@ import pytest from models_library.rest_pagination import Page, PageMetaInfoLimitOffset from pydantic.main import BaseModel +from pytest_simcore.examples.models_library import PAGE_EXAMPLES -@pytest.mark.parametrize("cls_model", [Page[str], PageMetaInfoLimitOffset]) -def test_page_response_limit_offset_models(cls_model: BaseModel): - examples = cls_model.model_config["json_schema_extra"]["examples"] +@pytest.mark.parametrize( + "cls_model, examples", + [ + (Page[str], PAGE_EXAMPLES), + ( + PageMetaInfoLimitOffset, + PageMetaInfoLimitOffset.model_config["json_schema_extra"]["examples"], + ), + ], +) +def test_page_response_limit_offset_models(cls_model: BaseModel, examples: list[dict]): for index, example in enumerate(examples): print(f"{index:-^10}:\n", example) @@ -35,14 +44,14 @@ def test_invalid_count(count: int, offset: int): def test_data_size_does_not_fit_count(): - example = deepcopy(Page[str].model_config["json_schema_extra"]["examples"][0]) + example = deepcopy(PAGE_EXAMPLES[0]) example["_meta"]["count"] = len(example["data"]) - 1 with pytest.raises(ValueError): Page[str](**example) def test_empty_data_is_converted_to_list(): - example = deepcopy(Page[str].model_config["json_schema_extra"]["examples"][0]) + example = deepcopy(PAGE_EXAMPLES[0]) example["data"] = None example["_meta"]["count"] = 0 model_instance = Page[str](**example) diff --git a/packages/models-library/tests/test_rpc_pagination.py b/packages/models-library/tests/test_rpc_pagination.py index 26931b9032e..b8f78c737e5 100644 --- a/packages/models-library/tests/test_rpc_pagination.py +++ b/packages/models-library/tests/test_rpc_pagination.py @@ -2,11 +2,10 @@ import pytest from models_library.rpc_pagination import PageRpc +from pytest_simcore.examples.models_library import RPC_PAGE_EXAMPLES -@pytest.mark.parametrize( - "example", PageRpc.model_config["json_schema_extra"]["examples"] -) +@pytest.mark.parametrize("example", RPC_PAGE_EXAMPLES) def test_create_page_rpc(example: dict[str, Any]): expected = PageRpc.model_validate(example) diff --git a/packages/notifications-library/src/notifications_library/errors.py b/packages/notifications-library/src/notifications_library/errors.py index 21edbbb0dc7..9c250909f21 100644 --- a/packages/notifications-library/src/notifications_library/errors.py +++ b/packages/notifications-library/src/notifications_library/errors.py @@ -1,11 +1,8 @@ -from typing import Any - from common_library.errors_classes import OsparcErrorMixin class NotifierError(OsparcErrorMixin, Exception): - def __init__(self, **ctx: Any) -> None: - super().__init__(**ctx) + pass class TemplatesNotFoundError(NotifierError): diff --git a/packages/postgres-database/src/simcore_postgres_database/utils_projects.py b/packages/postgres-database/src/simcore_postgres_database/utils_projects.py index 8dfd97b759b..577f9441004 100644 --- a/packages/postgres-database/src/simcore_postgres_database/utils_projects.py +++ b/packages/postgres-database/src/simcore_postgres_database/utils_projects.py @@ -2,15 +2,15 @@ from datetime import UTC, datetime import sqlalchemy as sa +from common_library.errors_classes import OsparcErrorMixin from pydantic import TypeAdapter -from pydantic.errors import PydanticErrorMixin from sqlalchemy.ext.asyncio import AsyncConnection from .models.projects import projects from .utils_repos import transaction_context -class DBBaseProjectError(PydanticErrorMixin, Exception): +class DBBaseProjectError(OsparcErrorMixin, Exception): msg_template: str = "Project utils unexpected error" diff --git a/packages/pytest-simcore/src/pytest_simcore/examples/__init__.py b/packages/pytest-simcore/src/pytest_simcore/examples/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/pytest-simcore/src/pytest_simcore/examples/models_library.py b/packages/pytest-simcore/src/pytest_simcore/examples/models_library.py new file mode 100644 index 00000000000..8af09913f71 --- /dev/null +++ b/packages/pytest-simcore/src/pytest_simcore/examples/models_library.py @@ -0,0 +1,55 @@ +from typing import Final + +PAGE_EXAMPLES: Final[list[dict]] = [ + # first page Page[str] + { + "_meta": {"total": 7, "count": 4, "limit": 4, "offset": 0}, + "_links": { + "self": "https://osparc.io/v2/listing?offset=0&limit=4", + "first": "https://osparc.io/v2/listing?offset=0&limit=4", + "prev": None, + "next": "https://osparc.io/v2/listing?offset=1&limit=4", + "last": "https://osparc.io/v2/listing?offset=1&limit=4", + }, + "data": ["data 1", "data 2", "data 3", "data 4"], + }, + # second and last page + { + "_meta": {"total": 7, "count": 3, "limit": 4, "offset": 1}, + "_links": { + "self": "https://osparc.io/v2/listing?offset=1&limit=4", + "first": "https://osparc.io/v2/listing?offset=0&limit=4", + "prev": "https://osparc.io/v2/listing?offset=0&limit=4", + "next": None, + "last": "https://osparc.io/v2/listing?offset=1&limit=4", + }, + "data": ["data 5", "data 6", "data 7"], + }, +] + +RPC_PAGE_EXAMPLES: Final[list[dict]] = [ + # first page Page[str] + { + "_meta": {"total": 7, "count": 4, "limit": 4, "offset": 0}, + "_links": { + "self": {"offset": 0, "limit": 4}, + "first": {"offset": 0, "limit": 4}, + "prev": None, + "next": {"offset": 1, "limit": 4}, + "last": {"offset": 1, "limit": 4}, + }, + "data": ["data 1", "data 2", "data 3", "data 4"], + }, + # second and last page + { + "_meta": {"total": 7, "count": 3, "limit": 4, "offset": 1}, + "_links": { + "self": {"offset": 1, "limit": 4}, + "first": {"offset": 0, "limit": 4}, + "prev": {"offset": 0, "limit": 4}, + "next": None, + "last": {"offset": 1, "limit": 4}, + }, + "data": ["data 5", "data 6", "data 7"], + }, +] diff --git a/packages/pytest-simcore/src/pytest_simcore/pydantic_models.py b/packages/pytest-simcore/src/pytest_simcore/pydantic_models.py index 1c2809fa52e..e9a7c318a9c 100644 --- a/packages/pytest-simcore/src/pytest_simcore/pydantic_models.py +++ b/packages/pytest-simcore/src/pytest_simcore/pydantic_models.py @@ -35,6 +35,15 @@ class ModelExample(NamedTuple): example_data: Any +def iter_examples( + *, model_cls: type[BaseModel], examples: list[Any] +) -> Iterator[ModelExample]: + for k, data in enumerate(examples): + yield ModelExample( + model_cls=model_cls, example_name=f"example_{k}", example_data=data + ) + + def walk_model_examples_in_package(package: ModuleType) -> Iterator[ModelExample]: """Walks recursively all sub-modules and collects BaseModel.Config examples""" assert inspect.ismodule(package) diff --git a/packages/pytest-simcore/src/pytest_simcore/simcore_webserver_projects_rest_api.py b/packages/pytest-simcore/src/pytest_simcore/simcore_webserver_projects_rest_api.py index ca386f1914c..fd6dd234720 100644 --- a/packages/pytest-simcore/src/pytest_simcore/simcore_webserver_projects_rest_api.py +++ b/packages/pytest-simcore/src/pytest_simcore/simcore_webserver_projects_rest_api.py @@ -73,6 +73,10 @@ def request_desc(self) -> str: "locked": {"value": False, "status": "CLOSED"}, "state": {"value": "NOT_STARTED"}, }, + "dev": None, + "workspace_id": None, + "folder_id": None, + "trashed_at": None, }, "error": None, }, @@ -109,6 +113,9 @@ def request_desc(self) -> str: "locked": {"value": False, "status": "CLOSED"}, "state": {"value": "NOT_STARTED"}, }, + "workspace_id": None, + "folder_id": None, + "trashed_at": None, } }, ) @@ -148,6 +155,9 @@ def request_desc(self) -> str: }, "state": {"value": "NOT_STARTED"}, }, + "workspace_id": None, + "folder_id": None, + "trashed_at": None, } }, ) @@ -275,6 +285,9 @@ def request_desc(self) -> str: }, "state": {"value": "NOT_STARTED"}, }, + "workspace_id": None, + "folder_id": None, + "trashed_at": None, } }, ) @@ -465,6 +478,9 @@ def request_desc(self) -> str: }, "accessRights": {"2": {"read": True, "write": True, "delete": True}}, "dev": {}, + "workspace_id": None, + "folder_id": None, + "trashed_at": None, "classifiers": [], "ui": { "mode": "workbench", @@ -663,6 +679,9 @@ def request_desc(self) -> str: }, "classifiers": [], "dev": {}, + "workspace_id": None, + "folder_id": None, + "trashed_at": None, "quality": { "enabled": True, "tsr_target": { @@ -910,6 +929,9 @@ def request_desc(self) -> str: }, "classifiers": [], "dev": {}, + "workspace_id": None, + "folder_id": None, + "trashed_at": None, "quality": { "enabled": True, "tsr_target": { diff --git a/packages/service-library/src/servicelib/aiohttp/tracing.py b/packages/service-library/src/servicelib/aiohttp/tracing.py index 3da3b28e3b3..fa9cec661a4 100644 --- a/packages/service-library/src/servicelib/aiohttp/tracing.py +++ b/packages/service-library/src/servicelib/aiohttp/tracing.py @@ -31,7 +31,9 @@ except ImportError: HAS_BOTOCORE = False try: - from opentelemetry.instrumentation.aiopg import AiopgInstrumentor + from opentelemetry.instrumentation.aiopg import ( # type: ignore[import-not-found] + AiopgInstrumentor, + ) HAS_AIOPG = True except ImportError: diff --git a/packages/service-library/src/servicelib/fastapi/tracing.py b/packages/service-library/src/servicelib/fastapi/tracing.py index b5179a8a5f6..bdd371fae1a 100644 --- a/packages/service-library/src/servicelib/fastapi/tracing.py +++ b/packages/service-library/src/servicelib/fastapi/tracing.py @@ -28,7 +28,9 @@ HAS_ASYNCPG = False try: - from opentelemetry.instrumentation.aiopg import AiopgInstrumentor + from opentelemetry.instrumentation.aiopg import ( # type: ignore[import-not-found] + AiopgInstrumentor, + ) HAS_AIOPG = True except ImportError: diff --git a/packages/service-library/src/servicelib/rabbitmq/_models.py b/packages/service-library/src/servicelib/rabbitmq/_models.py index e48e4bb13aa..c76800a4d8a 100644 --- a/packages/service-library/src/servicelib/rabbitmq/_models.py +++ b/packages/service-library/src/servicelib/rabbitmq/_models.py @@ -1,4 +1,3 @@ -import re from collections.abc import Awaitable, Callable from typing import Any, Protocol diff --git a/packages/service-library/tests/fastapi/test_http_client_thin.py b/packages/service-library/tests/fastapi/test_http_client_thin.py index 7bd96b25eee..103584f3f77 100644 --- a/packages/service-library/tests/fastapi/test_http_client_thin.py +++ b/packages/service-library/tests/fastapi/test_http_client_thin.py @@ -3,10 +3,10 @@ import logging from collections.abc import AsyncIterable, Iterable from typing import Final -from common_library.pydantic_networks_extension import AnyHttpUrlLegacy import arrow import pytest +from common_library.pydantic_networks_extension import AnyHttpUrlLegacy from httpx import ( HTTPError, PoolTimeout, @@ -16,7 +16,7 @@ TransportError, codes, ) -from pydantic import AnyHttpUrl, TypeAdapter +from pydantic import TypeAdapter from respx import MockRouter from servicelib.fastapi.http_client_thin import ( BaseThinClient, @@ -78,7 +78,7 @@ async def thick_client(request_timeout: int) -> AsyncIterable[FakeThickClient]: @pytest.fixture def test_url() -> str: - url =TypeAdapter(AnyHttpUrlLegacy).validate_python("http://missing-host:1111") + url = TypeAdapter(AnyHttpUrlLegacy).validate_python("http://missing-host:1111") return f"{url}" diff --git a/packages/settings-library/src/settings_library/base.py b/packages/settings-library/src/settings_library/base.py index 74d2a3fa1b8..6d436ea93a9 100644 --- a/packages/settings-library/src/settings_library/base.py +++ b/packages/settings-library/src/settings_library/base.py @@ -54,7 +54,7 @@ def _default_factory(): def _is_auto_default_from_env_enabled(field: FieldInfo) -> bool: return bool( field.json_schema_extra is not None - and field.json_schema_extra.get("auto_default_from_env", False) + and field.json_schema_extra.get("auto_default_from_env", False) # type: ignore[union-attr] ) @@ -171,7 +171,7 @@ def create_from_envs(cls, **overrides): @classmethod def settings_customise_sources( cls, - settings_cls: BaseSettings, + settings_cls: type[BaseSettings], init_settings: PydanticBaseSettingsSource, env_settings: PydanticBaseSettingsSource, dotenv_settings: PydanticBaseSettingsSource, @@ -180,7 +180,9 @@ def settings_customise_sources( assert env_settings # nosec return ( init_settings, - EnvSettingsWithAutoDefaultSource(settings_cls, env_settings=env_settings), + EnvSettingsWithAutoDefaultSource( + settings_cls, env_settings=env_settings # type:ignore[arg-type] + ), dotenv_settings, file_secret_settings, ) diff --git a/packages/settings-library/src/settings_library/node_ports.py b/packages/settings-library/src/settings_library/node_ports.py index 8498c882e4d..562e71e038a 100644 --- a/packages/settings-library/src/settings_library/node_ports.py +++ b/packages/settings-library/src/settings_library/node_ports.py @@ -1,5 +1,5 @@ from datetime import timedelta -from typing import Final, Self +from typing import Annotated, Final, Self from pydantic import Field, NonNegativeInt, PositiveInt, SecretStr, model_validator @@ -32,13 +32,13 @@ def _validate_auth_fields(self) -> Self: class NodePortsSettings(BaseCustomSettings): - NODE_PORTS_STORAGE_AUTH: StorageAuthSettings = Field( - json_schema_extra={"auto_default_from_env": True} - ) + NODE_PORTS_STORAGE_AUTH: Annotated[ + StorageAuthSettings, Field(json_schema_extra={"auto_default_from_env": True}) + ] - POSTGRES_SETTINGS: PostgresSettings = Field( - json_schema_extra={"auto_default_from_env": True} - ) + POSTGRES_SETTINGS: Annotated[ + PostgresSettings, Field(json_schema_extra={"auto_default_from_env": True}) + ] NODE_PORTS_MULTIPART_UPLOAD_COMPLETION_TIMEOUT_S: NonNegativeInt = int( timedelta(minutes=5).total_seconds() diff --git a/packages/settings-library/src/settings_library/twilio.py b/packages/settings-library/src/settings_library/twilio.py index b63e35caf61..343cbda4732 100644 --- a/packages/settings-library/src/settings_library/twilio.py +++ b/packages/settings-library/src/settings_library/twilio.py @@ -5,7 +5,6 @@ SEE https://support.twilio.com/hc/en-us/articles/223136027-Auth-Tokens-and-How-to-Change-Them """ - from typing import Annotated, TypeAlias from pydantic import BeforeValidator, Field, StringConstraints, TypeAdapter @@ -42,5 +41,5 @@ def is_alphanumeric_supported(self, phone_number: str) -> bool: ) return any( phone_number_wo_international_code.startswith(code) - for code in self.TWILIO_COUNTRY_CODES_W_ALPHANUMERIC_SID_SUPPORT + for code in self.TWILIO_COUNTRY_CODES_W_ALPHANUMERIC_SID_SUPPORT # pylint:disable=not-an-iterable ) diff --git a/packages/settings-library/tests/test_base_w_postgres.py b/packages/settings-library/tests/test_base_w_postgres.py index c2c877ca703..b1d4958378f 100644 --- a/packages/settings-library/tests/test_base_w_postgres.py +++ b/packages/settings-library/tests/test_base_w_postgres.py @@ -345,7 +345,6 @@ def test_parse_from_mixed_envs( POSTGRES_USER=test POSTGRES_PASSWORD=ssh POSTGRES_DB=db - POSTGRES_CLIENT_NAME=client-name """ with monkeypatch.context(): diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports_common/storage_endpoint.py b/packages/simcore-sdk/src/simcore_sdk/node_ports_common/storage_endpoint.py index 8e5e38cc0a3..7efbf45af37 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports_common/storage_endpoint.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports_common/storage_endpoint.py @@ -15,6 +15,7 @@ def is_storage_secure() -> bool: @lru_cache def get_base_url() -> str: settings = NodePortsSettings.create_from_envs() + # pylint:disable=no-member base_url: str = settings.NODE_PORTS_STORAGE_AUTH.api_base_url return base_url diff --git a/packages/simcore-sdk/tests/unit/test_node_ports_v2_links.py b/packages/simcore-sdk/tests/unit/test_node_ports_v2_links.py index 95b114ae563..72ba5e76570 100644 --- a/packages/simcore-sdk/tests/unit/test_node_ports_v2_links.py +++ b/packages/simcore-sdk/tests/unit/test_node_ports_v2_links.py @@ -1,8 +1,7 @@ -from typing import Dict from uuid import uuid4 import pytest -from pydantic import TypeAdapter, ValidationError +from pydantic import ValidationError from simcore_sdk.node_ports_v2.links import DownloadLink, FileLink, PortLink @@ -23,7 +22,7 @@ def test_valid_port_link(): {"nodeUuid": f"{uuid4()}", "output": "some:key"}, ], ) -def test_invalid_port_link(port_link: Dict[str, str]): +def test_invalid_port_link(port_link: dict[str, str]): with pytest.raises(ValidationError): PortLink(**port_link) @@ -36,7 +35,7 @@ def test_invalid_port_link(port_link: Dict[str, str]): {"label": "some stuff"}, ], ) -def test_invalid_download_link(download_link: Dict[str, str]): +def test_invalid_download_link(download_link: dict[str, str]): with pytest.raises(ValidationError): DownloadLink(**download_link) @@ -49,6 +48,6 @@ def test_invalid_download_link(download_link: Dict[str, str]): {"path": "/somefile/blahblah:"}, ], ) -def test_invalid_file_link(file_link: Dict[str, str]): +def test_invalid_file_link(file_link: dict[str, str]): with pytest.raises(ValidationError): FileLink(**file_link) diff --git a/packages/simcore-sdk/tests/unit/test_node_ports_v2_nodeports_v2.py b/packages/simcore-sdk/tests/unit/test_node_ports_v2_nodeports_v2.py index 848b5d60d98..250f9d2599d 100644 --- a/packages/simcore-sdk/tests/unit/test_node_ports_v2_nodeports_v2.py +++ b/packages/simcore-sdk/tests/unit/test_node_ports_v2_nodeports_v2.py @@ -3,7 +3,6 @@ # pylint:disable=redefined-outer-name # pylint:disable=protected-access -import asyncio from pathlib import Path from typing import Any, Callable from unittest.mock import AsyncMock @@ -230,10 +229,7 @@ async def test_node_ports_v2_packages( @pytest.fixture def mock_port_set(mocker: MockFixture) -> None: async def _always_raise_error(*args, **kwargs): - async def _i_raise_errors(): - raise ValidationError("invalid") - - return asyncio.create_task(_i_raise_errors()) + raise ValidationError.from_exception_data(title="Just a test", line_errors=[]) mocker.patch( "simcore_sdk.node_ports_v2.port.Port._set", side_effect=_always_raise_error @@ -278,4 +274,5 @@ async def _mock_callback(*args, **kwargs): + list(original_outputs.values()) }, progress_bar=progress_bar, + outputs_callbacks=None, ) diff --git a/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/attribute_monitor/_logging_event_handler.py b/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/attribute_monitor/_logging_event_handler.py index 527fa32f3ea..f1537a87389 100644 --- a/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/attribute_monitor/_logging_event_handler.py +++ b/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/attribute_monitor/_logging_event_handler.py @@ -1,5 +1,3 @@ -# pylint:disable=no-member - import logging import stat from asyncio import CancelledError, Task, create_task, get_event_loop @@ -70,7 +68,7 @@ def start_process(self) -> None: self._process = aioprocessing.AioProcess( target=self._process_worker, daemon=True ) - self._process.start() + self._process.start() # pylint:disable=no-member def _stop_process(self) -> None: with log_context( @@ -175,7 +173,7 @@ async def _health_worker(self) -> None: try: self._health_check_queue.get_nowait() heart_beat_count += 1 - except Empty: # noqa: PERF203 + except Empty: break if heart_beat_count == 0: diff --git a/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/outputs/_event_handler.py b/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/outputs/_event_handler.py index 784db423ed6..9b256a3d037 100644 --- a/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/outputs/_event_handler.py +++ b/services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/outputs/_event_handler.py @@ -1,5 +1,3 @@ -# pylint:disable=no-member - import logging from asyncio import CancelledError, Task, create_task, get_event_loop from asyncio import sleep as async_sleep @@ -97,7 +95,7 @@ def start_process(self) -> None: self._process = aioprocessing.AioProcess( target=self._process_worker, daemon=True ) - self._process.start() + self._process.start() # pylint:disable=no-member def stop_process(self) -> None: # NOTE: runs in asyncio thread