Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pydantic2 migration made integration tests green #6719

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
cdd69d9
fixed integration tests for simcore-sdk
Nov 14, 2024
cd6db0b
added missing dependency
Nov 14, 2024
10b28a7
fixed prod.txt
Nov 14, 2024
941039c
fixed library name
Nov 14, 2024
ffb95f1
fixed broken validator
Nov 14, 2024
17798cc
fixed broken tests
Nov 14, 2024
f3bf668
disable opentelementry for testing stacks
Nov 14, 2024
732d5f9
fixed conftest
Nov 14, 2024
657075c
fixed broken test
Nov 14, 2024
d0ba06a
refactor
Nov 14, 2024
71f10c5
fixed integration tests for osparc-gateway-server
Nov 14, 2024
7b3c820
refactor
Nov 14, 2024
ff307d6
Merge remote-tracking branch 'upstream/pydantic_v2_migration_do_not_s…
Nov 14, 2024
838bc84
added missing imports
Nov 14, 2024
4b8dc22
fix url
giancarloromeo Nov 14, 2024
8a22850
fixed pydanticv1 issue
Nov 14, 2024
88d43e4
fixed test
Nov 14, 2024
5c7fc53
Merge branch 'pr-osparc-pydantic2-integration-libraries2' of github.c…
Nov 14, 2024
964b515
fixed routes
Nov 14, 2024
dbc9f5b
fixed patch
Nov 14, 2024
9efe3b9
fixed union parsing
Nov 14, 2024
e283d7e
remove assert
Nov 14, 2024
06071a6
aiodocker format
Nov 14, 2024
a34dfdd
fix url type
giancarloromeo Nov 14, 2024
ba57a0e
fix urls
giancarloromeo Nov 14, 2024
1914139
deprecating legacy url
Nov 15, 2024
777b469
Merge branch 'pr-osparc-pydantic2-integration-libraries2' of github.c…
Nov 15, 2024
188f97c
removed LegacyUrl
Nov 15, 2024
a66bc5e
using newer version
Nov 15, 2024
3bcd09e
fixed broken test
Nov 15, 2024
2daf0ca
bump service version
Nov 15, 2024
d48271c
Merge remote-tracking branch 'upstream/pydantic_v2_migration_do_not_s…
Nov 15, 2024
ddaa28b
fixed typing
Nov 15, 2024
48c0058
bumped service versions
Nov 15, 2024
2558636
fixed typing warnings
Nov 15, 2024
871c27b
added missing packages
Nov 15, 2024
e2fc69b
fixed missing package
Nov 15, 2024
00f906b
attempt to avoid flaky test
Nov 15, 2024
b968a23
refactor and migrated to yarl
Nov 15, 2024
30dab5e
pylint
Nov 15, 2024
08f3c52
revert flaky test
Nov 15, 2024
3cb8af8
added missing
Nov 15, 2024
a7a3257
Merge remote-tracking branch 'upstream/pydantic_v2_migration_do_not_s…
Nov 18, 2024
8e6175d
reverting
Nov 18, 2024
6bdc21a
renaming
Nov 18, 2024
d34318d
service owner is None by default if not found
Nov 18, 2024
c30b583
fixed warnings in tests
Nov 18, 2024
8ce43ab
revert
Nov 18, 2024
0e52fdd
pylint
Nov 18, 2024
e2fe840
pylint
Nov 18, 2024
ac299de
mypy fixes adding access_rights
Nov 18, 2024
f75fb75
fixed broken tests
Nov 18, 2024
819cee6
updated description
Nov 18, 2024
c369d3e
Merge remote-tracking branch 'upstream/pydantic_v2_migration_do_not_s…
Nov 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json
from contextlib import suppress
from pathlib import Path
from typing import Any, TypeAlias, Union
from typing import Annotated, Any, TypeAlias

from models_library.basic_regex import MIME_TYPE_RE
from models_library.generics import DictModel
Expand Down Expand Up @@ -84,15 +84,16 @@ class FileUrl(BaseModel):
)


PortValue: TypeAlias = Union[
StrictBool,
StrictInt,
StrictFloat,
StrictStr,
FileUrl,
list[Any],
dict[str, Any],
None,
PortValue: TypeAlias = Annotated[
StrictBool
| StrictInt
| StrictFloat
| StrictStr
| FileUrl
| list[Any]
| dict[str, Any]
| None,
Field(union_mode="left_to_right"),
GitHK marked this conversation as resolved.
Show resolved Hide resolved
]


Expand All @@ -112,7 +113,9 @@ class TaskInputData(DictModel[ServicePortKey, PortValue]):
)


PortSchemaValue: TypeAlias = Union[PortSchema, FilePortSchema]
PortSchemaValue: TypeAlias = Annotated[
PortSchema | FilePortSchema, Field(union_mode="left_to_right")
]


class TaskOutputDataSchema(DictModel[ServicePortKey, PortSchemaValue]):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,9 @@ class ServiceUpdate(ServiceMetaDataEditable, ServiceAccessRights):
class ServiceGet(
ServiceMetaDataPublished, ServiceAccessRights, ServiceMetaDataEditable
): # pylint: disable=too-many-ancestors
owner: LowerCaseEmailStr | None
owner: LowerCaseEmailStr | None = Field(
description="None when the owner email cannot be found in the database"
)

model_config = ConfigDict(
extra="ignore",
Expand All @@ -230,7 +232,9 @@ class ServiceGetV2(BaseModel):

contact: LowerCaseEmailStr | None
authors: list[Author] = Field(..., min_length=1)
owner: LowerCaseEmailStr | None
owner: LowerCaseEmailStr | None = Field(
description="None when the owner email cannot be found in the database"
)

inputs: ServiceInputsDict
outputs: ServiceOutputsDict
Expand Down
6 changes: 4 additions & 2 deletions packages/models-library/src/models_library/clusters.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from enum import auto
from pathlib import Path
from typing import Final, Literal, Self, TypeAlias
from typing import Annotated, Final, Literal, Self, TypeAlias

from pydantic import (
AnyUrl,
Expand Down Expand Up @@ -142,7 +142,9 @@ class BaseCluster(BaseModel):
authentication: ClusterAuthentication = Field(
..., description="Dask gateway authentication", discriminator="type"
)
access_rights: dict[GroupID, ClusterAccessRights] = Field(default_factory=dict)
access_rights: Annotated[
dict[GroupID, ClusterAccessRights], Field(default_factory=dict)
]

_from_equivalent_enums = field_validator("type", mode="before")(
create_enums_pre_validator(ClusterTypeInModel)
Expand Down
26 changes: 25 additions & 1 deletion packages/pytest-simcore/src/pytest_simcore/docker_compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,37 @@ def testing_environ_vars(env_devel_file: Path) -> EnvVarsDict:
# ensure we do not use the bucket of simcore or so
env_devel["S3_BUCKET_NAME"] = "pytestbucket"

# ensure OpenTelemetry is not enabled
env_devel |= {
tracing_setting: "null"
for tracing_setting in (
"AGENT_TRACING",
GitHK marked this conversation as resolved.
Show resolved Hide resolved
"API_SERVER_TRACING",
"AUTOSCALING_TRACING",
"CATALOG_TRACING",
"CLUSTERS_KEEPER_TRACING",
"DATCORE_ADAPTER_TRACING",
"DIRECTOR_TRACING",
"DIRECTOR_V2_TRACING",
"DYNAMIC_SCHEDULER_TRACING",
"EFS_GUARDIAN_TRACING",
"INVITATIONS_TRACING",
"PAYMENTS_TRACING",
"RESOURCE_USAGE_TRACKER_TRACING",
"STORAGE_TRACING",
"WB_DB_EL_TRACING",
"WB_GC_TRACING",
"WEBSERVER_TRACING",
)
}

return {key: value for key, value in env_devel.items() if value is not None}


@pytest.fixture(scope="module")
def env_file_for_testing(
temp_folder: Path,
testing_environ_vars: dict[str, str],
testing_environ_vars: EnvVarsDict,
osparc_simcore_root_dir: Path,
) -> Iterator[Path]:
"""Dumps all the environment variables into an $(temp_folder)/.env.test file
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ def jupyter_service(docker_registry: str, node_meta_schema: dict) -> dict[str, A
)


@pytest.fixture(scope="session", params=["2.0.4"])
@pytest.fixture(scope="session", params=["2.0.7"])
GitHK marked this conversation as resolved.
Show resolved Hide resolved
def dy_static_file_server_version(request: pytest.FixtureRequest):
return request.param

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,19 @@ def regex_pattern(self) -> str:
msg = "Current version cannot compute regex patterns in case of oneOf. Please go ahead and implement it yourself."
raise NotImplementedError(msg)
if self.anyOf:
return "|".join([elm.regex_pattern for elm in self.anyOf])
return "|".join(
[
elm.regex_pattern
for elm in self.anyOf # pylint:disable=not-an-iterable
]
)
if self.allOf:
return "&".join([elm.regex_pattern for elm in self.allOf])
return "&".join(
[
elm.regex_pattern
for elm in self.allOf # pylint:disable=not-an-iterable
]
)

# now deal with non-recursive cases
pattern: str | None = None
Expand Down
11 changes: 11 additions & 0 deletions packages/pytest-simcore/src/pytest_simcore/helpers/storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from collections.abc import Callable

from yarl import URL


def replace_storage_endpoint(host: str, port: int) -> Callable[[str], str]:
def _(url: str) -> str:
url_obj = URL(url).with_host(host).with_port(port)
GitHK marked this conversation as resolved.
Show resolved Hide resolved
return f"{url_obj}"

return _
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ async def dask_scheduler_service(
)
# override the port
monkeypatch.setenv("DASK_SCHEDULER_PORT", f"{dask_scheduler_api_port}")
return AnyUrl.build(
scheme="tls", host=get_localhost_ip(), port=dask_scheduler_api_port
url = AnyUrl.build(
scheme="tls", host=get_localhost_ip(), port=int(dask_scheduler_api_port)
)
return f"{url}"


@pytest.fixture
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@
import tenacity
from models_library.projects import ProjectID
from models_library.projects_nodes_io import NodeID, SimcoreS3FileID
from pydantic import AnyUrl, TypeAdapter
from pydantic import TypeAdapter
from pytest_mock import MockerFixture
from servicelib.minio_utils import ServiceRetryPolicyUponInitialization
from yarl import URL

from .helpers.docker import get_service_published_port
from .helpers.host import get_localhost_ip
from .helpers.storage import replace_storage_endpoint


@pytest.fixture(scope="module")
Expand Down Expand Up @@ -45,22 +46,12 @@ async def storage_service(
) -> URL:
await wait_till_storage_responsive(storage_endpoint)

def correct_ip(url: AnyUrl):
assert storage_endpoint.host is not None
assert storage_endpoint.port is not None

return AnyUrl.build(
scheme=url.scheme,
host=storage_endpoint.host,
port=f"{storage_endpoint.port}",
path=url.path,
query=url.query,
)

# NOTE: Mock to ensure container IP agrees with host IP when testing
assert storage_endpoint.host is not None
assert storage_endpoint.port is not None
mocker.patch(
"simcore_sdk.node_ports_common._filemanager._get_https_link_if_storage_secure",
correct_ip,
replace_storage_endpoint(storage_endpoint.host, storage_endpoint.port),
)

return storage_endpoint
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@

from aiohttp import web
from common_library.json_serialization import json_dumps
from common_library.pydantic_networks_extension import AnyHttpUrlLegacy
from pydantic import PositiveFloat, TypeAdapter
from pydantic import AnyHttpUrl, PositiveFloat, TypeAdapter

from ...aiohttp import status
from ...long_running_tasks._models import TaskGet
Expand Down Expand Up @@ -68,13 +67,13 @@ async def start_long_running_task(
ip_addr, port = request_.transport.get_extra_info(
"sockname"
) # https://docs.python.org/3/library/asyncio-protocol.html#asyncio.BaseTransport.get_extra_info
status_url = TypeAdapter(AnyHttpUrlLegacy).validate_python(
status_url = TypeAdapter(AnyHttpUrl).validate_python(
f"http://{ip_addr}:{port}{request_.app.router['get_task_status'].url_for(task_id=task_id)}" # NOSONAR
)
result_url = TypeAdapter(AnyHttpUrlLegacy).validate_python(
result_url = TypeAdapter(AnyHttpUrl).validate_python(
f"http://{ip_addr}:{port}{request_.app.router['get_task_result'].url_for(task_id=task_id)}" # NOSONAR
)
abort_url = TypeAdapter(AnyHttpUrlLegacy).validate_python(
abort_url = TypeAdapter(AnyHttpUrl).validate_python(
f"http://{ip_addr}:{port}{request_.app.router['cancel_and_delete_task'].url_for(task_id=task_id)}" # NOSONAR
)
task_get = TaskGet(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
import warnings
from typing import Any, Awaitable, Callable, Final

from common_library.pydantic_networks_extension import AnyHttpUrlLegacy
from fastapi import FastAPI, status
from httpx import AsyncClient, HTTPError
from pydantic import PositiveFloat, TypeAdapter
from pydantic import AnyHttpUrl, PositiveFloat, TypeAdapter
from tenacity import RetryCallState
from tenacity.asyncio import AsyncRetrying
from tenacity.retry import retry_if_exception_type
Expand Down Expand Up @@ -106,6 +105,9 @@ async def request_wrapper(zelf: "Client", *args, **kwargs) -> Any:
with attempt:
return await request_func(zelf, *args, **kwargs)

msg = "Unexpected"
raise RuntimeError(msg)

return request_wrapper


Expand All @@ -131,12 +133,13 @@ def _client_configuration(self) -> ClientConfiguration:
return output

def _get_url(self, path: str) -> str:
url = f"{self._base_url}{self._client_configuration.router_prefix}{path}"
return f"{TypeAdapter(AnyHttpUrlLegacy).validate_python(url)}"
url_path = f"{self._client_configuration.router_prefix}{path}".lstrip("/")
url = TypeAdapter(AnyHttpUrl).validate_python(f"{self._base_url}{url_path}")
return f"{url}"

@retry_on_http_errors
async def get_task_status(
self, task_id: TaskId, *, timeout: PositiveFloat | None = None
self, task_id: TaskId, *, timeout: PositiveFloat | None = None # noqa: ASYNC109
) -> TaskStatus:
timeout = timeout or self._client_configuration.default_timeout
result = await self._async_client.get(
Expand All @@ -155,7 +158,7 @@ async def get_task_status(

@retry_on_http_errors
async def get_task_result(
self, task_id: TaskId, *, timeout: PositiveFloat | None = None
self, task_id: TaskId, *, timeout: PositiveFloat | None = None # noqa: ASYNC109
) -> Any | None:
timeout = timeout or self._client_configuration.default_timeout
result = await self._async_client.get(
Expand All @@ -177,7 +180,7 @@ async def get_task_result(

@retry_on_http_errors
async def cancel_and_delete_task(
self, task_id: TaskId, *, timeout: PositiveFloat | None = None
self, task_id: TaskId, *, timeout: PositiveFloat | None = None # noqa: ASYNC109
) -> None:
timeout = timeout or self._client_configuration.default_timeout
result = await self._async_client.delete(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@
import asyncio
from typing import AsyncIterable, Final

from common_library.pydantic_networks_extension import AnyHttpUrlLegacy
import pytest
from asgi_lifespan import LifespanManager
from fastapi import APIRouter, Depends, FastAPI, status
from httpx import AsyncClient
from pydantic import PositiveFloat, TypeAdapter
from pydantic import AnyHttpUrl, PositiveFloat, TypeAdapter
from servicelib.fastapi.long_running_tasks._context_manager import _ProgressManager
from servicelib.fastapi.long_running_tasks.client import (
Client,
Expand Down Expand Up @@ -50,7 +49,8 @@ async def a_test_task(task_progress: TaskProgress) -> int:

async def a_failing_test_task(task_progress: TaskProgress) -> None:
await asyncio.sleep(TASK_SLEEP_INTERVAL)
raise RuntimeError("I am failing as requested")
msg = "I am failing as requested"
raise RuntimeError(msg)


@pytest.fixture
Expand Down Expand Up @@ -101,7 +101,7 @@ async def test_task_result(
assert result.status_code == status.HTTP_200_OK, result.text
task_id = result.json()

url = TypeAdapter(AnyHttpUrlLegacy).validate_python("http://backgroud.testserver.io")
url = TypeAdapter(AnyHttpUrl).validate_python("http://backgroud.testserver.io/")
client = Client(app=bg_task_app, async_client=async_client, base_url=url)
async with periodic_task_result(
client,
Expand All @@ -121,7 +121,7 @@ async def test_task_result_times_out(
assert result.status_code == status.HTTP_200_OK, result.text
task_id = result.json()

url = TypeAdapter(AnyHttpUrlLegacy).validate_python("http://backgroud.testserver.io")
url = TypeAdapter(AnyHttpUrl).validate_python("http://backgroud.testserver.io/")
client = Client(app=bg_task_app, async_client=async_client, base_url=url)
timeout = TASK_SLEEP_INTERVAL / 10
with pytest.raises(TaskClientTimeoutError) as exec_info:
Expand All @@ -147,7 +147,7 @@ async def test_task_result_task_result_is_an_error(
assert result.status_code == status.HTTP_200_OK, result.text
task_id = result.json()

url = TypeAdapter(AnyHttpUrlLegacy).validate_python("http://backgroud.testserver.io")
url = TypeAdapter(AnyHttpUrl).validate_python("http://backgroud.testserver.io/")
client = Client(app=bg_task_app, async_client=async_client, base_url=url)
with pytest.raises(TaskClientResultError) as exec_info:
async with periodic_task_result(
Expand All @@ -158,7 +158,7 @@ async def test_task_result_task_result_is_an_error(
):
pass
assert f"{exec_info.value}".startswith(f"Task {task_id} finished with exception:")
assert 'raise RuntimeError("I am failing as requested")' in f"{exec_info.value}"
assert "I am failing as requested" in f"{exec_info.value}"
await _assert_task_removed(async_client, task_id, router_prefix)


Expand Down Expand Up @@ -186,13 +186,17 @@ async def progress_update(
assert received == ("", None)

for _ in range(repeat):
await progress_updater.update(mock_task_id, percent=TypeAdapter(ProgressPercent).validate_python(0.0))
await progress_updater.update(
mock_task_id, percent=TypeAdapter(ProgressPercent).validate_python(0.0)
)
assert counter == 2
assert received == ("", 0.0)

for _ in range(repeat):
await progress_updater.update(
mock_task_id, percent=TypeAdapter(ProgressPercent).validate_python(1.0), message="done"
mock_task_id,
percent=TypeAdapter(ProgressPercent).validate_python(1.0),
message="done",
)
assert counter == 3
assert received == ("done", 1.0)
Expand Down
Loading
Loading