Skip to content

Commit

Permalink
base rest
Browse files Browse the repository at this point in the history
  • Loading branch information
pcrespov committed Nov 14, 2024
1 parent 62a56d9 commit 15b0d4f
Show file tree
Hide file tree
Showing 10 changed files with 59 additions and 56 deletions.
19 changes: 19 additions & 0 deletions packages/models-library/src/models_library/rest_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from pydantic import BaseModel, Extra


class RequestParameters(BaseModel):
"""
Base model for any type of request parameters,
i.e. context, path, query, headers
"""

def as_params(self, **export_options) -> dict[str, str]:
data = self.dict(**export_options)
return {k: f"{v}" for k, v in data.items()}


class StrictRequestParameters(RequestParameters):
"""Use a base class for context, path and query parameters"""

class Config:
extra = Extra.forbid # strict
22 changes: 9 additions & 13 deletions packages/models-library/src/models_library/rest_ordering.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from pydantic import BaseModel, Field, Json, validator

from .basic_types import IDStr
from .rest_base import RequestParameters


class OrderDirection(str, Enum):
Expand All @@ -30,7 +31,7 @@ class Config:
}


class BaseOrderByQueryParams(BaseModel):
class _BaseOrderByQueryParams(RequestParameters):
order_by: OrderBy | None = None


Expand All @@ -39,7 +40,7 @@ def create_order_by_query_model_classes(
sortable_fields: set[str],
default_order_by: OrderBy,
override_direction_default: bool = False,
) -> tuple[type[BaseOrderByQueryParams], type[BaseModel]]:
) -> tuple[type[_BaseOrderByQueryParams], type[BaseModel]]:
"""
Factory to create an uniform model used as ordering parameters in a query
Expand All @@ -52,7 +53,7 @@ def create_order_by_query_model_classes(
msg_direction_options = "|".join(sorted(OrderDirection))
order_by_example: dict[str, Any] = OrderBy.Config.schema_extra["example"]

class _JsonOrderBy(OrderBy):
class _OrderByJsonable(OrderBy):
direction: OrderDirection = Field(
default=default_order_by.direction
if override_direction_default
Expand Down Expand Up @@ -87,9 +88,9 @@ def _check_if_sortable_field(cls, v):
f"The default sorting order is '{default_order_by.direction.value}' on '{default_order_by.field}'."
)

class _RequestValidatorModel(BaseOrderByQueryParams):
class _RequestValidatorModel(_BaseOrderByQueryParams):
# Used in rest handler for verification
order_by: _JsonOrderBy = Field(
order_by: _OrderByJsonable = Field(
default=default_order_by,
description=description,
)
Expand All @@ -109,14 +110,9 @@ class _OpenapiModel(BaseModel):
description=description,
)

@validator("order_by", allow_reuse=True)
@classmethod
def _validate_json_content(cls, v):
if v:
_RequestValidatorModel(order_by=v)
return v

class Config:
schema_extra: ClassVar[dict[str, Any]] = {"title": "Order By Parameters"}
schema_extra: ClassVar[dict[str, Any]] = {
"title": "Order By Parameters",
}

return _RequestValidatorModel, _OpenapiModel
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
)
from pydantic.generics import GenericModel

from .rest_base import RequestParameters
from .utils.common_validators import none_to_empty_list_pre_validator

# Default limit values
Expand All @@ -29,7 +30,7 @@ class PageLimitInt(ConstrainedInt):
DEFAULT_NUMBER_OF_ITEMS_PER_PAGE: Final[PageLimitInt] = parse_obj_as(PageLimitInt, 20)


class PageQueryParameters(BaseModel):
class PageQueryParameters(RequestParameters):
"""Use as pagination options in query parameters"""

limit: PageLimitInt = Field(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from aiohttp import web
from models_library.utils.json_serialization import json_dumps
from pydantic import BaseModel, Extra, ValidationError, parse_obj_as
from pydantic import BaseModel, ValidationError, parse_obj_as

from ..mimetype_constants import MIMETYPE_APPLICATION_JSON
from . import status
Expand All @@ -24,17 +24,6 @@
UnionOfModelTypes: TypeAlias = Union[type[ModelClass], type[ModelClass]] # noqa: UP007


class RequestParams(BaseModel):
...


class StrictRequestParams(BaseModel):
"""Use a base class for context, path and query parameters"""

class Config:
extra = Extra.forbid # strict


@contextmanager
def handle_validation_as_http_error(
*, error_msg_template: str, resource_name: str, use_error_v1: bool
Expand Down
21 changes: 12 additions & 9 deletions packages/service-library/tests/aiohttp/test_requests_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from aiohttp import web
from aiohttp.test_utils import TestClient, make_mocked_request
from faker import Faker
from models_library.rest_base import RequestParameters, StrictRequestParameters
from models_library.rest_ordering import (
OrderBy,
OrderDirection,
Expand All @@ -19,8 +20,6 @@
from pydantic import BaseModel, Field
from servicelib.aiohttp import status
from servicelib.aiohttp.requests_validation import (
RequestParams,
StrictRequestParams,
parse_request_body_as,
parse_request_headers_as,
parse_request_path_parameters_as,
Expand All @@ -38,7 +37,7 @@ def jsonable_encoder(data):
return json.loads(json_dumps(data))


class MyRequestContext(RequestParams):
class MyRequestContext(RequestParameters):
user_id: int = Field(alias=RQT_USERID_KEY)
secret: str = Field(alias=APP_SECRET_KEY)

Expand All @@ -47,15 +46,15 @@ def create_fake(cls, faker: Faker):
return cls(user_id=faker.pyint(), secret=faker.password())


class MyRequestPathParams(StrictRequestParams):
class MyRequestPathParams(StrictRequestParameters):
project_uuid: UUID

@classmethod
def create_fake(cls, faker: Faker):
return cls(project_uuid=faker.uuid4())


class MyRequestQueryParams(RequestParams):
class MyRequestQueryParams(RequestParameters):
is_ok: bool = True
label: str

Expand All @@ -64,7 +63,7 @@ def create_fake(cls, faker: Faker):
return cls(is_ok=faker.pybool(), label=faker.word())


class MyRequestHeadersParams(RequestParams):
class MyRequestHeadersParams(RequestParameters):
user_agent: str = Field(alias="X-Simcore-User-Agent")
optional_header: str | None = Field(default=None, alias="X-Simcore-Optional-Header")

Expand Down Expand Up @@ -364,7 +363,7 @@ async def test_parse_request_with_invalid_headers_params(

def test_parse_request_query_parameters_as_with_order_by_query_models():

OrderByModel, _ = create_order_by_query_model_classes(
OrderByModel, OrderByModelOAS = create_order_by_query_model_classes(
sortable_fields={"modified", "name"}, default_order_by=OrderBy(field="name")
)

Expand All @@ -377,5 +376,9 @@ def test_parse_request_query_parameters_as_with_order_by_query_models():
query_params = parse_request_query_parameters_as(OrderByModel, request)
assert query_params.order_by == expected

assert OrderByModel.schema()["properties"]["order_by"]["type"] == "string"
assert OrderByModel.schema()["properties"]["order_by"]["format"] == "json-string"
expected_schema = {"type": "string", "format": "json-string"}
assert {
k: v
for k, v in OrderByModel.schema()["properties"]["order_by"]
if k in expected
} == expected_schema
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from models_library.basic_types import IDStr
from models_library.folders import FolderID
from models_library.rest_base import RequestParameters, StrictRequestParameters
from models_library.rest_filters import Filters, FiltersQueryParameters
from models_library.rest_ordering import OrderBy, OrderDirection
from models_library.rest_pagination import PageQueryParameters
Expand All @@ -13,20 +14,19 @@
)
from models_library.workspaces import WorkspaceID
from pydantic import BaseModel, Extra, Field, Json, validator
from servicelib.aiohttp.requests_validation import RequestParams, StrictRequestParams
from servicelib.request_keys import RQT_USERID_KEY

from .._constants import RQ_PRODUCT_KEY

_logger = logging.getLogger(__name__)


class FoldersRequestContext(RequestParams):
class FoldersRequestContext(RequestParameters):
user_id: UserID = Field(..., alias=RQT_USERID_KEY) # type: ignore[literal-required]
product_name: str = Field(..., alias=RQ_PRODUCT_KEY) # type: ignore[literal-required]


class FoldersPathParams(StrictRequestParams):
class FoldersPathParams(StrictRequestParameters):
folder_id: FolderID


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,10 @@
from aiohttp import web
from models_library.api_schemas_webserver.product import GetCreditPrice, GetProduct
from models_library.basic_types import IDStr
from models_library.rest_base import RequestParameters, StrictRequestParameters
from models_library.users import UserID
from pydantic import Extra, Field
from servicelib.aiohttp.requests_validation import (
RequestParams,
StrictRequestParams,
parse_request_path_parameters_as,
)
from servicelib.aiohttp.requests_validation import parse_request_path_parameters_as
from servicelib.request_keys import RQT_USERID_KEY
from simcore_service_webserver.utils_aiohttp import envelope_json_response

Expand All @@ -27,7 +24,7 @@
_logger = logging.getLogger(__name__)


class _ProductsRequestContext(RequestParams):
class _ProductsRequestContext(RequestParameters):
user_id: UserID = Field(..., alias=RQT_USERID_KEY) # type: ignore[literal-required]
product_name: str = Field(..., alias=RQ_PRODUCT_KEY) # type: ignore[literal-required]

Expand All @@ -49,7 +46,7 @@ async def _get_current_product_price(request: web.Request):
return envelope_json_response(credit_price)


class _ProductsRequestParams(StrictRequestParams):
class _ProductsRequestParams(StrictRequestParameters):
product_name: IDStr | Literal["current"]


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
from datetime import datetime

from models_library.api_schemas_webserver._base import InputSchema, OutputSchema
from models_library.rest_base import RequestParameters, StrictRequestParameters
from models_library.users import GroupID, UserID
from pydantic import ConstrainedStr, Field, PositiveInt
from servicelib.aiohttp.requests_validation import RequestParams, StrictRequestParams
from servicelib.request_keys import RQT_USERID_KEY
from simcore_postgres_database.utils_tags import TagDict


class TagRequestContext(RequestParams):
class TagRequestContext(RequestParameters):
user_id: UserID = Field(..., alias=RQT_USERID_KEY) # type: ignore[literal-required]


class TagPathParams(StrictRequestParams):
class TagPathParams(StrictRequestParameters):
tag_id: PositiveInt


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@
WalletGetWithAvailableCredits,
)
from models_library.error_codes import create_error_code
from models_library.rest_base import RequestParameters, StrictRequestParameters
from models_library.users import UserID
from models_library.wallets import WalletID
from pydantic import Field
from servicelib.aiohttp.requests_validation import (
RequestParams,
StrictRequestParams,
parse_request_body_as,
parse_request_path_parameters_as,
)
Expand Down Expand Up @@ -106,19 +105,18 @@ async def wrapper(request: web.Request) -> web.StreamResponse:
return wrapper


#
# wallets COLLECTION -------------------------
#

routes = web.RouteTableDef()


class WalletsRequestContext(RequestParams):
class WalletsRequestContext(RequestParameters):
user_id: UserID = Field(..., alias=RQT_USERID_KEY) # type: ignore[literal-required]
product_name: str = Field(..., alias=RQ_PRODUCT_KEY) # type: ignore[literal-required]


class WalletsPathParams(StrictRequestParams):
class WalletsPathParams(StrictRequestParameters):
wallet_id: WalletID


Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging

from models_library.basic_types import IDStr
from models_library.rest_base import RequestParameters, StrictRequestParameters
from models_library.rest_filters import Filters, FiltersQueryParameters
from models_library.rest_ordering import (
OrderBy,
Expand All @@ -12,20 +13,19 @@
from models_library.users import GroupID, UserID
from models_library.workspaces import WorkspaceID
from pydantic import BaseModel, Extra, Field
from servicelib.aiohttp.requests_validation import RequestParams, StrictRequestParams
from servicelib.request_keys import RQT_USERID_KEY

from .._constants import RQ_PRODUCT_KEY

_logger = logging.getLogger(__name__)


class WorkspacesRequestContext(RequestParams):
class WorkspacesRequestContext(RequestParameters):
user_id: UserID = Field(..., alias=RQT_USERID_KEY) # type: ignore[literal-required]
product_name: str = Field(..., alias=RQ_PRODUCT_KEY) # type: ignore[literal-required]


class WorkspacesPathParams(StrictRequestParams):
class WorkspacesPathParams(StrictRequestParameters):
workspace_id: WorkspaceID


Expand Down

0 comments on commit 15b0d4f

Please sign in to comment.