Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: ITISFoundation/osparc-simcore
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 9b8ab02c124fb792975e23ffe48d4ad0e8632a1a
Choose a base ref
..
head repository: ITISFoundation/osparc-simcore
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: daf930493fecd9c18b4e7ce670cc52900748e38a
Choose a head ref
Showing with 1,080 additions and 499 deletions.
  1. +1 −1 .env-devel
  2. +13 −13 api/specs/web-server/_auth.py
  3. +17 −37 api/specs/web-server/_common.py
  4. +5 −0 api/specs/web-server/_folders.py
  5. +5 −0 api/specs/web-server/_workspaces.py
  6. +4 −1 packages/common-library/src/common_library/json_serialization.py
  7. +16 −1 packages/common-library/tests/test_json_serialization.py
  8. +121 −0 packages/models-library/src/models_library/rest_error.py
  9. +3 −2 packages/models-library/src/models_library/utils/_original_fastapi_encoders.py
  10. +7 −3 packages/pytest-simcore/src/pytest_simcore/helpers/assert_checks.py
  11. +2 −2 packages/service-library/src/servicelib/aiohttp/rest_middlewares.py
  12. +0 −30 packages/service-library/src/servicelib/aiohttp/rest_models.py
  13. +6 −5 packages/service-library/src/servicelib/aiohttp/rest_responses.py
  14. +1 −7 packages/service-library/src/servicelib/aiohttp/rest_utils.py
  15. +34 −25 packages/service-library/src/servicelib/logging_utils.py
  16. +2 −2 services/api-server/src/simcore_service_api_server/exceptions/handlers/_log_streaming_errors.py
  17. +1 −1 services/api-server/src/simcore_service_api_server/exceptions/log_streaming_errors.py
  18. +12 −14 services/api-server/src/simcore_service_api_server/services/log_streaming.py
  19. +2 −2 services/api-server/tests/unit/test_services_rabbitmq.py
  20. +30 −7 services/dynamic-scheduler/tests/unit/test_cli.py
  21. +102 −94 services/dynamic-sidecar/requirements/_base.txt
  22. +35 −25 services/dynamic-sidecar/requirements/_test.txt
  23. +20 −21 services/dynamic-sidecar/requirements/_tools.txt
  24. +3 −1 services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/api/rest/containers.py
  25. +2 −2 services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/core/docker_utils.py
  26. +5 −1 ...c-sidecar/src/simcore_service_dynamic_sidecar/modules/attribute_monitor/_logging_event_handler.py
  27. +5 −3 ...mic-sidecar/src/simcore_service_dynamic_sidecar/modules/attribute_monitor/_watchdog_extensions.py
  28. +5 −1 services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/outputs/_event_handler.py
  29. +5 −3 services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/modules/outputs/_watchdog_extensions.py
  30. +0 −6 services/invitations/openapi.json
  31. +89 −79 services/invitations/requirements/_base.txt
  32. +16 −12 services/invitations/requirements/_test.txt
  33. +21 −22 services/invitations/requirements/_tools.txt
  34. +1 −1 services/static-webserver/client/source/class/osparc/data/model/Wallet.js
  35. +4 −1 services/static-webserver/client/source/class/osparc/desktop/credits/BuyCreditsStepper.js
  36. +3 −3 services/static-webserver/client/source/class/osparc/desktop/credits/CreditsPerService.js
  37. +1 −1 services/static-webserver/client/source/class/osparc/desktop/credits/CurrentUsage.js
  38. +2 −2 services/static-webserver/client/source/class/osparc/desktop/credits/TransactionsTableModel.js
  39. +1 −1 services/static-webserver/client/source/class/osparc/desktop/credits/UsageTableModel.js
  40. +1 −1 services/static-webserver/client/source/class/osparc/pricing/UnitData.js
  41. +1 −1 services/static-webserver/client/source/class/osparc/pricing/UnitEditor.js
  42. +2 −1 services/static-webserver/client/source/class/osparc/widget/PersistentIframe.js
  43. +435 −51 services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml
  44. +13 −2 services/web/server/src/simcore_service_webserver/application_settings.py
  45. +1 −1 services/web/server/src/simcore_service_webserver/login/utils.py
  46. +3 −7 services/web/server/src/simcore_service_webserver/projects/settings.py
  47. +19 −0 services/web/server/tests/unit/isolated/test_application_settings.py
  48. +3 −3 services/web/server/tests/unit/with_dbs/conftest.py
2 changes: 1 addition & 1 deletion .env-devel
Original file line number Diff line number Diff line change
@@ -333,7 +333,7 @@ LOGIN_2FA_REQUIRED=0
LOGIN_ACCOUNT_DELETION_RETENTION_DAYS=31
LOGIN_REGISTRATION_CONFIRMATION_REQUIRED=0
LOGIN_REGISTRATION_INVITATION_REQUIRED=0
PROJECTS_INACTIVITY_INTERVAL=20
PROJECTS_INACTIVITY_INTERVAL=00:00:20
PROJECTS_TRASH_RETENTION_DAYS=7
PROJECTS_MAX_COPY_SIZE_BYTES=30Gib
PROJECTS_MAX_NUM_RUNNING_DYNAMIC_NODES=5
26 changes: 13 additions & 13 deletions api/specs/web-server/_auth.py
Original file line number Diff line number Diff line change
@@ -6,7 +6,6 @@

from typing import Any

from _common import Error, Log
from fastapi import APIRouter, status
from models_library.api_schemas_webserver.auth import (
AccountRequestInfo,
@@ -15,6 +14,7 @@
UnregisterCheck,
)
from models_library.generics import Envelope
from models_library.rest_error import EnvelopedError, Log
from pydantic import BaseModel, Field, confloat
from simcore_service_webserver._meta import API_VTAG
from simcore_service_webserver.login._2fa_handlers import Resend2faBody
@@ -75,7 +75,7 @@ async def register(_body: RegisterBody):
"/auth/unregister",
response_model=Envelope[Log],
status_code=status.HTTP_200_OK,
responses={status.HTTP_409_CONFLICT: {"model": Envelope[Error]}},
responses={status.HTTP_409_CONFLICT: {"model": EnvelopedError}},
)
async def unregister_account(_body: UnregisterCheck):
...
@@ -107,7 +107,7 @@ async def phone_confirmation(_body: PhoneConfirmationBody):
responses={
# status.HTTP_503_SERVICE_UNAVAILABLE
status.HTTP_401_UNAUTHORIZED: {
"model": Envelope[Error],
"model": EnvelopedError,
"description": "unauthorized reset due to invalid token code",
}
},
@@ -122,7 +122,7 @@ async def login(_body: LoginBody):
operation_id="auth_login_2fa",
responses={
status.HTTP_401_UNAUTHORIZED: {
"model": Envelope[Error],
"model": EnvelopedError,
"description": "unauthorized reset due to invalid token code",
}
},
@@ -137,7 +137,7 @@ async def login_2fa(_body: LoginTwoFactorAuthBody):
operation_id="auth_resend_2fa_code",
responses={
status.HTTP_401_UNAUTHORIZED: {
"model": Envelope[Error],
"model": EnvelopedError,
"description": "unauthorized reset due to invalid token code",
}
},
@@ -161,7 +161,7 @@ async def logout(_body: LogoutBody):
status_code=status.HTTP_204_NO_CONTENT,
responses={
status.HTTP_401_UNAUTHORIZED: {
"model": Envelope[Error],
"model": EnvelopedError,
"description": "unauthorized reset due to invalid token code",
}
},
@@ -174,7 +174,7 @@ async def check_auth():
"/auth/reset-password",
response_model=Envelope[Log],
operation_id="auth_reset_password",
responses={status.HTTP_503_SERVICE_UNAVAILABLE: {"model": Envelope[Error]}},
responses={status.HTTP_503_SERVICE_UNAVAILABLE: {"model": EnvelopedError}},
)
async def reset_password(_body: ResetPasswordBody):
"""a non logged-in user requests a password reset"""
@@ -186,7 +186,7 @@ async def reset_password(_body: ResetPasswordBody):
operation_id="auth_reset_password_allowed",
responses={
status.HTTP_401_UNAUTHORIZED: {
"model": Envelope[Error],
"model": EnvelopedError,
"description": "unauthorized reset due to invalid token code",
}
},
@@ -201,11 +201,11 @@ async def reset_password_allowed(code: str, _body: ResetPasswordConfirmation):
operation_id="auth_change_email",
responses={
status.HTTP_401_UNAUTHORIZED: {
"model": Envelope[Error],
"model": EnvelopedError,
"description": "unauthorized user. Login required",
},
status.HTTP_503_SERVICE_UNAVAILABLE: {
"model": Envelope[Error],
"model": EnvelopedError,
"description": "unable to send confirmation email",
},
},
@@ -233,15 +233,15 @@ class PasswordCheckSchema(BaseModel):
operation_id="auth_change_password",
responses={
status.HTTP_401_UNAUTHORIZED: {
"model": Envelope[Error],
"model": EnvelopedError,
"description": "unauthorized user. Login required",
},
status.HTTP_409_CONFLICT: {
"model": Envelope[Error],
"model": EnvelopedError,
"description": "mismatch between new and confirmation passwords",
},
status.HTTP_422_UNPROCESSABLE_ENTITY: {
"model": Envelope[Error],
"model": EnvelopedError,
"description": "current password is invalid",
},
},
54 changes: 17 additions & 37 deletions api/specs/web-server/_common.py
Original file line number Diff line number Diff line change
@@ -5,13 +5,22 @@
import sys
from collections.abc import Callable
from pathlib import Path
from typing import Annotated, NamedTuple, Optional, Union, get_args, get_origin
from typing import (
Annotated,
Any,
Generic,
NamedTuple,
Optional,
TypeVar,
Union,
get_args,
get_origin,
)

from common_library.json_serialization import json_dumps
from common_library.pydantic_fields_extension import get_type
from fastapi import Query
from models_library.basic_types import LogLevel
from pydantic import BaseModel, ConfigDict, Field, Json, create_model
from pydantic import BaseModel, Json, create_model
from pydantic.fields import FieldInfo

CURRENT_DIR = Path(sys.argv[0] if __name__ == "__main__" else __file__).resolve().parent
@@ -78,43 +87,14 @@ def as_query(model_class: type[BaseModel]) -> type[BaseModel]:
return create_model(new_model_name, **fields)


class Log(BaseModel):
level: LogLevel | None = Field("INFO", description="log level")
message: str = Field(
...,
description="log message. If logger is USER, then it MUST be human readable",
)
logger: str | None = Field(
None, description="name of the logger receiving this message"
)

model_config = ConfigDict(
json_schema_extra={
"example": {
"message": "Hi there, Mr user",
"level": "INFO",
"logger": "user-logger",
}
}
)

ErrorT = TypeVar("ErrorT")

class ErrorItem(BaseModel):
code: str = Field(
...,
description="Typically the name of the exception that produced it otherwise some known error code",
)
message: str = Field(..., description="Error message specific to this item")
resource: str | None = Field(
None, description="API resource affected by this error"
)
field: str | None = Field(None, description="Specific field within the resource")

class EnvelopeE(BaseModel, Generic[ErrorT]):
"""Complementary to models_library.generics.Envelope just for the generators"""

class Error(BaseModel):
logs: list[Log] | None = Field(None, description="log messages")
errors: list[ErrorItem] | None = Field(None, description="errors metadata")
status: int | None = Field(None, description="HTTP error code")
error: ErrorT | None = None
data: Any | None = None


class ParamSpec(NamedTuple):
5 changes: 5 additions & 0 deletions api/specs/web-server/_folders.py
Original file line number Diff line number Diff line change
@@ -17,7 +17,9 @@
FolderReplaceBodyParams,
)
from models_library.generics import Envelope
from models_library.rest_error import EnvelopedError
from simcore_service_webserver._meta import API_VTAG
from simcore_service_webserver.folders._exceptions_handlers import _TO_HTTP_ERROR_MAP
from simcore_service_webserver.folders._models import (
FolderSearchQueryParams,
FoldersListQueryParams,
@@ -29,6 +31,9 @@
tags=[
"folders",
],
responses={
i.status_code: {"model": EnvelopedError} for i in _TO_HTTP_ERROR_MAP.values()
},
)


5 changes: 5 additions & 0 deletions api/specs/web-server/_workspaces.py
Original file line number Diff line number Diff line change
@@ -17,7 +17,9 @@
WorkspaceReplaceBodyParams,
)
from models_library.generics import Envelope
from models_library.rest_error import EnvelopedError
from simcore_service_webserver._meta import API_VTAG
from simcore_service_webserver.folders._exceptions_handlers import _TO_HTTP_ERROR_MAP
from simcore_service_webserver.workspaces._groups_api import WorkspaceGroupGet
from simcore_service_webserver.workspaces._models import (
WorkspacesGroupsBodyParams,
@@ -31,6 +33,9 @@
tags=[
"workspaces",
],
responses={
i.status_code: {"model": EnvelopedError} for i in _TO_HTTP_ERROR_MAP.values()
},
)


Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@
from uuid import UUID

import orjson
from pydantic import NameEmail, SecretBytes, SecretStr
from pydantic import AnyHttpUrl, AnyUrl, HttpUrl, NameEmail, SecretBytes, SecretStr
from pydantic_core import Url
from pydantic_extra_types.color import Color

@@ -62,6 +62,8 @@ def decimal_encoder(dec_value: Decimal) -> int | float:


ENCODERS_BY_TYPE: dict[type[Any], Callable[[Any], Any]] = {
AnyHttpUrl: str,
AnyUrl: str,
bytes: lambda o: o.decode(),
Color: str,
datetime.date: isoformat,
@@ -73,6 +75,7 @@ def decimal_encoder(dec_value: Decimal) -> int | float:
frozenset: list,
deque: list,
GeneratorType: list,
HttpUrl: str,
IPv4Address: str,
IPv4Interface: str,
IPv4Network: str,
17 changes: 16 additions & 1 deletion packages/common-library/tests/test_json_serialization.py
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@
json_loads,
)
from faker import Faker
from pydantic import Field, TypeAdapter
from pydantic import AnyHttpUrl, AnyUrl, BaseModel, Field, HttpUrl, TypeAdapter
from pydantic.json import pydantic_encoder


@@ -95,3 +95,18 @@ def test_compatiblity_with_json_interface(

# NOTE: cannot compare dumps directly because orjson compacts it more
assert json_loads(orjson_dump) == json_loads(json_dump)


def test_serialized_model_with_urls(faker: Faker):
# See: https://github.com/ITISFoundation/osparc-simcore/pull/6852
class M(BaseModel):
any_http_url: AnyHttpUrl
any_url: AnyUrl
http_url: HttpUrl

obj = M(
any_http_url=faker.url(),
any_url=faker.url(),
http_url=faker.url(),
)
json_dumps(obj)
Loading