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

♻️ Maintenance: reduce code duplication #4982

Merged
merged 18 commits into from
Nov 7, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import http
from typing import Any

from pydantic import BaseModel, Field

from ..basic_types import IDStr
from ..utils.pydantic_tools_extension import NOT_REQUIRED


class DefaultApiError(BaseModel):
name: IDStr = Field(
...,
description="Error identifier as a code or a name. Mostly for machine-machine communication.",
)
detail: Any | None = Field(NOT_REQUIRED, description="Human readable error message")

@classmethod
def from_status_code(
cls, code: int, *, detail: str | None = None
) -> "DefaultApiError":
httplib_code = http.HTTPStatus(code)

return cls(
name=f"{code}",
detail=detail or httplib_code.description or httplib_code.phrase,
)
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from typing import Any, ClassVar, TypeAlias

from models_library.resource_tracker import HardwareInfo, PricingInfo
from pydantic import BaseModel, ByteSize, Field

from ..resource_tracker import HardwareInfo, PricingInfo
from ..services import ServicePortKey
from ..services_resources import ServiceResourcesDict, ServiceResourcesDictHelpers
from ..wallets import WalletInfo
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from datetime import datetime
from decimal import Decimal

from models_library.products import ProductName
from models_library.resource_tracker import CreditTransactionId
from models_library.users import UserID
from models_library.wallets import WalletID
from pydantic import BaseModel, validator

from ..products import ProductName
from ..resource_tracker import CreditTransactionId
from ..users import UserID
from ..wallets import WalletID


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
from decimal import Decimal
from typing import Any, ClassVar

from models_library.resource_tracker import (
from pydantic import BaseModel

from ..resource_tracker import (
HardwareInfo,
PricingPlanClassification,
PricingPlanId,
PricingUnitCostId,
PricingUnitId,
)
from pydantic import BaseModel


class PricingUnitGet(BaseModel):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
from datetime import datetime
from decimal import Decimal

from models_library.projects import ProjectID
from models_library.projects_nodes_io import NodeID
from models_library.resource_tracker import (
CreditTransactionStatus,
ServiceRunId,
ServiceRunStatus,
)
from models_library.services import ServiceKey, ServiceVersion
from models_library.users import UserID
from models_library.wallets import WalletID
from pydantic import BaseModel

from ..projects import ProjectID
from ..projects_nodes_io import NodeID
from ..resource_tracker import CreditTransactionStatus, ServiceRunId, ServiceRunStatus
from ..services import ServiceKey, ServiceVersion
from ..users import UserID
from ..wallets import WalletID


class ServiceRunGet(BaseModel):
service_run_id: ServiceRunId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from typing import Any, ClassVar
from uuid import UUID

from models_library.basic_types import SHA256Str
from pydantic import (
BaseModel,
ByteSize,
Expand All @@ -27,6 +26,7 @@
from pydantic.networks import AnyUrl

from .basic_regex import DATCORE_DATASET_NAME_RE, S3_BUCKET_NAME_RE
from .basic_types import SHA256Str
from .generics import ListModel
from .projects_nodes_io import (
LocationID,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from datetime import timedelta
from typing import Any, ClassVar

from models_library.emails import LowerCaseEmailStr
from pydantic import BaseModel, Field, SecretStr

from ..emails import LowerCaseEmailStr
from ._base import InputSchema


Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from models_library.clusters import ClusterID
from pydantic import BaseModel

from ..clusters import ClusterID


class ComputationStart(BaseModel):
force_restart: bool = False
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from contextlib import suppress
from typing import Any, ClassVar

from models_library.emails import LowerCaseEmailStr
from pydantic import AnyUrl, BaseModel, Field, ValidationError, parse_obj_as, validator

from ..emails import LowerCaseEmailStr

#
# GROUPS MODELS defined in OPENAPI specs
#
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from datetime import datetime
from typing import Any, ClassVar

from models_library.products import ProductName
from pydantic import ConstrainedInt, Field, HttpUrl, PositiveInt

from ..basic_types import IDStr, NonNegativeDecimal
from ..emails import LowerCaseEmailStr
from ..products import ProductName
from ._base import InputSchema, OutputSchema


Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
from datetime import datetime
from decimal import Decimal

from models_library.resource_tracker import (
from pydantic import BaseModel

from ..projects import ProjectID
from ..projects_nodes_io import NodeID
from ..resource_tracker import (
PricingPlanClassification,
PricingPlanId,
PricingUnitId,
ServiceRunId,
ServiceRunStatus,
)
from models_library.users import UserID
from models_library.wallets import WalletID
from pydantic import BaseModel

from ..projects import ProjectID
from ..projects_nodes_io import NodeID
from ..resource_tracker import PricingUnitId, ServiceRunStatus
from ..services import ServiceKey, ServiceVersion
from ..users import UserID
from ..wallets import WalletID
from ._base import OutputSchema

# Frontend API
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
import http

import pytest
from simcore_service_payments.models.schemas.errors import DefaultApiError
from models_library.api_schemas__common.errors import DefaultApiError


@pytest.mark.parametrize("code", [e.value for e in http.HTTPStatus if e.value >= 400])
def test_default_api_error_model(code: int):
def test_create_default_api_error_from_status_code(code: int):

error = DefaultApiError.from_status_code(code)
assert error.name
assert error.name == f"{code}"
assert error.detail

assert DefaultApiError.from_status_code(code, detail="FOO").detail == "FOO"
1 change: 1 addition & 0 deletions packages/service-library/requirements/_fastapi.in
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@

fastapi
httpx
respx
uvicorn
3 changes: 3 additions & 0 deletions packages/service-library/requirements/_fastapi.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ httpx==0.25.0
# -c requirements/./../../../packages/settings-library/requirements/../../../requirements/constraints.txt
# -c requirements/./../../../requirements/constraints.txt
# -r requirements/_fastapi.in
# respx
idna==3.4
# via
# anyio
Expand Down Expand Up @@ -84,6 +85,8 @@ referencing==0.29.3
# -c requirements/././constraints.txt
# jsonschema
# jsonschema-specifications
respx==0.20.2
# via -r requirements/_fastapi.in
rich==13.6.0
# via -r requirements/./../../../packages/settings-library/requirements/_base.in
rpds-py==0.10.6
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
from fastapi import HTTPException, Request, status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse

from ...models.schemas.errors import DefaultApiError
from models_library.api_schemas__common.errors import DefaultApiError

_logger = logging.getLogger(__name__)

Expand Down
36 changes: 11 additions & 25 deletions packages/service-library/tests/fastapi/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,15 @@
# pylint: disable=unused-variable

import socket
from datetime import datetime
from typing import AsyncIterable, Callable, cast
from collections.abc import AsyncIterator, Callable
from typing import cast

import arrow
import pytest
from fastapi import FastAPI
from fastapi import APIRouter, FastAPI
from fastapi.params import Query
from fastapi.routing import APIRouter
from httpx import AsyncClient
from pydantic.types import PositiveFloat
from pytest import FixtureRequest
from servicelib.fastapi import long_running_tasks


@pytest.fixture
Expand All @@ -23,7 +21,7 @@ def app() -> FastAPI:

@api_router.get("/")
def _get_root():
return {"name": __name__, "timestamp": datetime.utcnow().isoformat()}
return {"name": __name__, "timestamp": arrow.utcnow().datetime.isoformat()}

@api_router.get("/data")
def _get_data(x: PositiveFloat, y: int = Query(..., gt=3, lt=4)):
Expand All @@ -35,27 +33,15 @@ def _get_data(x: PositiveFloat, y: int = Query(..., gt=3, lt=4)):
return _app


@pytest.fixture(params=["", "/base-path", "/nested/path"])
def router_prefix(request: FixtureRequest) -> str:
return request.param


@pytest.fixture
async def bg_task_app(router_prefix: str) -> AsyncIterable[FastAPI]:
app = FastAPI()

long_running_tasks.server.setup(app, router_prefix=router_prefix)
yield app
async def client(app: FastAPI) -> AsyncIterator[AsyncClient]:
async with AsyncClient(app=app, base_url="http://test") as client:
yield client


@pytest.fixture(scope="function")
async def async_client(bg_task_app: FastAPI) -> AsyncIterable[AsyncClient]:
async with AsyncClient(
app=bg_task_app,
base_url="http://backgroud.testserver.io",
headers={"Content-Type": "application/json"},
) as client:
yield client
@pytest.fixture(params=["", "/base-path", "/nested/path"])
def router_prefix(request: pytest.FixtureRequest) -> str:
return request.param


@pytest.fixture
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# pylint: disable=redefined-outer-name
# pylint: disable=unused-argument
# pylint: disable=unused-variable
# pylint: disable=too-many-arguments

from collections.abc import AsyncIterable

import pytest
from fastapi import FastAPI
from httpx import AsyncClient
from servicelib.fastapi import long_running_tasks


@pytest.fixture
async def bg_task_app(router_prefix: str) -> FastAPI:
app = FastAPI()

long_running_tasks.server.setup(app, router_prefix=router_prefix)
return app


@pytest.fixture
async def async_client(bg_task_app: FastAPI) -> AsyncIterable[AsyncClient]:
async with AsyncClient(
app=bg_task_app,
base_url="http://backgroud.testserver.io",
headers={"Content-Type": "application/json"},
) as client:
yield client
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@

import asyncio
import json
from typing import AsyncIterator, Awaitable, Callable, Final
from collections.abc import AsyncIterator, Awaitable, Callable
from typing import Final

import pytest
from asgi_lifespan import LifespanManager
Expand Down Expand Up @@ -76,6 +77,7 @@ async def create_string_list_task(

@pytest.fixture
async def app(server_routes: APIRouter) -> AsyncIterator[FastAPI]:
# overrides fastapi/conftest.py:app
app = FastAPI(title="test app")
app.include_router(server_routes)
long_running_tasks.server.setup(app)
Expand All @@ -84,12 +86,6 @@ async def app(server_routes: APIRouter) -> AsyncIterator[FastAPI]:
yield app


@pytest.fixture
async def client(app: FastAPI) -> AsyncIterator[AsyncClient]:
async with AsyncClient(app=app, base_url="http://test") as client:
yield client


@pytest.fixture
def start_long_running_task() -> Callable[[FastAPI, AsyncClient], Awaitable[TaskId]]:
async def _caller(app: FastAPI, client: AsyncClient, **query_kwargs) -> TaskId:
Expand Down Expand Up @@ -134,6 +130,7 @@ async def _waiter(

return _waiter


async def test_workflow(
app: FastAPI,
client: AsyncClient,
Expand Down Expand Up @@ -233,6 +230,7 @@ async def test_failing_task_returns_error(
# assert task_result.error["errors"][0]["code"] == "RuntimeError"
# assert task_result.error["errors"][0]["message"] == "We were asked to fail!!"


async def test_get_results_before_tasks_finishes_returns_404(
app: FastAPI,
client: AsyncClient,
Expand Down
Loading