Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion services/datcore-adapter/requirements/_base.in
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ aiofiles
fastapi
fastapi-pagination
httpx[http2]
pydantic[email]
pydantic
python-multipart # for fastapi multipart uploads
uvicorn[standard]
97 changes: 84 additions & 13 deletions services/datcore-adapter/requirements/_base.txt

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
from typing import Final

from models_library.basic_types import VersionStr
from pydantic import parse_obj_as
from pydantic import TypeAdapter

current_distribution = distribution("simcore_service_datcore_adapter")
__version__ = version("simcore_service_datcore_adapter")

API_VERSION: Final[VersionStr] = parse_obj_as(VersionStr, __version__)
API_VERSION: Final[VersionStr] = TypeAdapter(VersionStr).validate_python(__version__)
MAJOR, MINOR, PATCH = __version__.split(".")
API_VTAG: Final[str] = f"v{MAJOR}"
APP_NAME: Final[str] = current_distribution.metadata["Name"]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from typing import Callable, Optional
from typing import Callable

from fastapi import HTTPException
from fastapi.encoders import jsonable_encoder
from starlette.requests import Request
from starlette.responses import JSONResponse


async def http_error_handler(_: Request, exc: HTTPException) -> JSONResponse:
async def http_error_handler(_: Request, exc: Exception) -> JSONResponse:
assert isinstance(exc, HTTPException) # nosec
return JSONResponse(
content=jsonable_encoder({"errors": [exc.detail]}), status_code=exc.status_code
)
Expand All @@ -16,7 +17,7 @@ def make_http_error_handler_for_exception(
status_code: int,
exception_cls: type[BaseException],
*,
override_detail_message: Optional[str] = None,
override_detail_message: str | None = None,
) -> Callable:
"""
Produces a handler for BaseException-type exceptions which converts them
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@

async def botocore_exceptions_handler(
_: Request,
exc: ClientError,
exc: Exception,
) -> JSONResponse:
assert isinstance(exc, ClientError) # nosec
assert "Error" in exc.response # nosec
assert "Code" in exc.response["Error"] # nosec
if exc.response["Error"]["Code"] == "NotAuthorizedException":
return JSONResponse(
content=jsonable_encoder({"errors": exc.response["Error"]}),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from typing import Union

from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.openapi.constants import REF_PREFIX
Expand All @@ -12,8 +10,9 @@

async def http422_error_handler(
_: Request,
exc: Union[RequestValidationError, ValidationError],
exc: Exception,
) -> JSONResponse:
assert isinstance(exc, RequestValidationError | ValidationError) # nosec
return JSONResponse(
content=jsonable_encoder({"errors": exc.errors()}),
status_code=HTTP_422_UNPROCESSABLE_ENTITY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import Annotated, Any

from fastapi import APIRouter, Depends, Header, Request
from pydantic import AnyUrl, parse_obj_as
from pydantic import AnyUrl, TypeAdapter
from servicelib.fastapi.requests_decorators import cancel_on_disconnect
from starlette import status

Expand Down Expand Up @@ -34,7 +34,9 @@ async def download_file(
api_secret=x_datcore_api_secret,
package_id=file_id,
)
return FileDownloadOut(link=parse_obj_as(AnyUrl, f"{presigned_download_link}"))
return FileDownloadOut(
link=TypeAdapter(AnyUrl).validate_python(f"{presigned_download_link}")
)


@router.delete(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def create_app(settings: ApplicationSettings | None = None) -> FastAPI:

for name in NOISY_LOGGERS:
logging.getLogger(name).setLevel(quiet_level)
logger.debug("App settings:\n%s", settings.json(indent=2))
logger.debug("App settings:\n%s", settings.model_dump_json(indent=2))

app = FastAPI(
debug=settings.debug,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from functools import cached_property

from models_library.basic_types import BootModeEnum, LogLevel
from pydantic import Field, parse_obj_as, validator
from pydantic import AliasChoices, Field, TypeAdapter, field_validator
from pydantic.networks import AnyUrl
from settings_library.base import BaseCustomSettings
from settings_library.tracing import TracingSettings
Expand All @@ -11,7 +11,9 @@
class PennsieveSettings(BaseCustomSettings):
PENNSIEVE_ENABLED: bool = True

PENNSIEVE_API_URL: AnyUrl = parse_obj_as(AnyUrl, "https://api.pennsieve.io")
PENNSIEVE_API_URL: AnyUrl = TypeAdapter(AnyUrl).validate_python(
"https://api.pennsieve.io"
)
PENNSIEVE_API_GENERAL_TIMEOUT: float = 20.0
PENNSIEVE_HEALTCHCHECK_TIMEOUT: float = 1.0

Expand All @@ -21,28 +23,31 @@ class ApplicationSettings(BaseCustomSettings, MixinLoggingSettings):
SC_BOOT_MODE: BootModeEnum | None

LOG_LEVEL: LogLevel = Field(
LogLevel.INFO.value,
env=[
default=LogLevel.INFO.value,
validation_alias=AliasChoices(
"DATCORE_ADAPTER_LOGLEVEL",
"DATCORE_ADAPTER_LOG_LEVEL",
"LOG_LEVEL",
"LOGLEVEL",
],
),
)

PENNSIEVE: PennsieveSettings = Field(auto_default_from_env=True)
PENNSIEVE: PennsieveSettings = Field(
json_schema_extra={"auto_default_from_env": True}
)

DATCORE_ADAPTER_LOG_FORMAT_LOCAL_DEV_ENABLED: bool = Field(
False,
env=[
default=False,
validation_alias=AliasChoices(
"DATCORE_ADAPTER_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!",
)
DATCORE_ADAPTER_PROMETHEUS_INSTRUMENTATION_ENABLED: bool = True
DATCORE_ADAPTER_TRACING: TracingSettings | None = Field(
auto_default_from_env=True, description="settings for opentelemetry tracing"
description="settings for opentelemetry tracing",
json_schema_extra={"auto_default_from_env": True},
)

@cached_property
Expand All @@ -54,7 +59,7 @@ def debug(self) -> bool:
BootModeEnum.LOCAL,
]

@validator("LOG_LEVEL", pre=True)
@field_validator("LOG_LEVEL", mode="before")
@classmethod
def _validate_loglevel(cls, value) -> str:
def _validate_loglevel(cls, value: str) -> str:
return cls.validate_log_level(value)
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,16 @@ def from_pennsieve_package(
return cls(
dataset_id=package["content"]["datasetNodeId"],
package_id=package["content"]["nodeId"],
id=package["content"]["id"],
id=f"{package['content']['id']}",
name=pck_name,
path=base_path / pck_name,
type=package["content"]["packageType"],
size=file_size,
created_at=package["content"]["createdAt"],
last_modified_at=package["content"]["updatedAt"],
data_type=DataType.FOLDER
if package["content"]["packageType"] == "Collection"
else DataType.FILE,
data_type=(
DataType.FOLDER
if package["content"]["packageType"] == "Collection"
else DataType.FILE
),
)
16 changes: 10 additions & 6 deletions services/datcore-adapter/tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
# pylint:disable=redefined-outer-name

import json
from collections.abc import AsyncIterator, Callable
from pathlib import Path
from typing import Any, AsyncIterator, Callable
from typing import Any
from uuid import uuid4

import faker
Expand All @@ -14,7 +15,9 @@
import simcore_service_datcore_adapter
from asgi_lifespan import LifespanManager
from fastapi.applications import FastAPI
from models_library.basic_types import BootModeEnum
from pytest_mock import MockFixture
from pytest_simcore.helpers.monkeypatch_envs import setenvs_from_dict
from simcore_service_datcore_adapter.modules.pennsieve import (
PennsieveAuthorizationHeaders,
)
Expand Down Expand Up @@ -61,7 +64,9 @@ def pennsieve_mock_dataset_packages(mocks_dir: Path) -> dict[str, Any]:


@pytest.fixture()
def minimal_app() -> FastAPI:
def minimal_app(
app_envs: None,
) -> FastAPI:
from simcore_service_datcore_adapter.main import the_app

return the_app
Expand All @@ -76,7 +81,7 @@ def client(minimal_app: FastAPI) -> TestClient:
@pytest.fixture
def app_envs(monkeypatch: pytest.MonkeyPatch):
# disable tracing as together with LifespanManager, it does not remove itself nicely
...
return setenvs_from_dict(monkeypatch, {"SC_BOOT_MODE": BootModeEnum.DEBUG})


@pytest.fixture()
Expand All @@ -87,7 +92,7 @@ async def initialized_app(
yield minimal_app


@pytest.fixture(scope="function")
@pytest.fixture
async def async_client(initialized_app: FastAPI) -> AsyncIterator[httpx.AsyncClient]:
async with httpx.AsyncClient(
app=initialized_app,
Expand Down Expand Up @@ -215,14 +220,13 @@ def pennsieve_api_headers(
def pennsieve_random_fake_datasets(
create_pennsieve_fake_dataset_id: Callable,
) -> dict[str, Any]:
datasets = {
return {
"datasets": [
{"content": {"id": create_pennsieve_fake_dataset_id(), "name": fake.text()}}
for _ in range(10)
],
"totalCount": 20,
}
return datasets


@pytest.fixture
Expand Down
14 changes: 6 additions & 8 deletions services/datcore-adapter/tests/unit/test_route_datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
# pylint:disable=redefined-outer-name


from typing import Optional

import httpx
import respx
from fastapi_pagination import Page
from pydantic import parse_obj_as
from pydantic import TypeAdapter
from simcore_service_datcore_adapter.models.schemas.datasets import (
DatasetMetaData,
FileMetaData,
Expand All @@ -18,7 +16,7 @@

async def test_list_datasets_entrypoint(
async_client: httpx.AsyncClient,
pennsieve_subsystem_mock: Optional[respx.MockRouter],
pennsieve_subsystem_mock: respx.MockRouter | None,
pennsieve_api_headers: dict[str, str],
):
response = await async_client.get(
Expand All @@ -29,7 +27,7 @@ async def test_list_datasets_entrypoint(
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data
parse_obj_as(Page[DatasetMetaData], data)
TypeAdapter(Page[DatasetMetaData]).validate_python(data)


async def test_list_dataset_files_legacy_entrypoint(
Expand All @@ -47,7 +45,7 @@ async def test_list_dataset_files_legacy_entrypoint(
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data
parse_obj_as(list[FileMetaData], data)
TypeAdapter(list[FileMetaData]).validate_python(data)


async def test_list_dataset_top_level_files_entrypoint(
Expand All @@ -65,7 +63,7 @@ async def test_list_dataset_top_level_files_entrypoint(
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data
parse_obj_as(Page[FileMetaData], data)
TypeAdapter(Page[FileMetaData]).validate_python(data)


async def test_list_dataset_collection_files_entrypoint(
Expand All @@ -85,4 +83,4 @@ async def test_list_dataset_collection_files_entrypoint(
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data
parse_obj_as(Page[FileMetaData], data)
TypeAdapter(Page[FileMetaData]).validate_python(data)
4 changes: 2 additions & 2 deletions services/datcore-adapter/tests/unit/test_route_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from unittest.mock import Mock

import httpx
from pydantic import parse_obj_as
from pydantic import TypeAdapter
from simcore_service_datcore_adapter.models.domains.files import FileDownloadOut
from starlette import status

Expand All @@ -23,7 +23,7 @@ async def test_download_file_entrypoint(
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data
parse_obj_as(FileDownloadOut, data)
TypeAdapter(FileDownloadOut).validate_python(data)


async def test_delete_file_entrypoint(
Expand Down
4 changes: 2 additions & 2 deletions services/datcore-adapter/tests/unit/test_route_health.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ async def test_check_subsystem_health(async_client: httpx.AsyncClient):

assert pennsieve_health_route.called
assert response.status_code == status.HTTP_200_OK
app_status = AppStatusCheck.parse_obj(response.json())
app_status = AppStatusCheck.model_validate(response.json())
assert app_status
assert app_status.app_name == "simcore-service-datcore-adapter"
assert app_status.services == {"pennsieve": True}
Expand All @@ -43,7 +43,7 @@ async def test_check_subsystem_health(async_client: httpx.AsyncClient):

assert pennsieve_health_route.called
assert response.status_code == status.HTTP_200_OK
app_status = AppStatusCheck.parse_obj(response.json())
app_status = AppStatusCheck.model_validate(response.json())
assert app_status
assert app_status.app_name == "simcore-service-datcore-adapter"
assert app_status.services == {"pennsieve": False}
Loading