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
12 changes: 11 additions & 1 deletion mpt_api_client/http/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
from mpt_api_client.http.async_client import AsyncHTTPClient
from mpt_api_client.http.async_service import AsyncService
from mpt_api_client.http.client import HTTPClient
from mpt_api_client.http.mixins import AsyncCreateMixin, AsyncDeleteMixin, CreateMixin, DeleteMixin
from mpt_api_client.http.service import Service

__all__ = ["AsyncHTTPClient", "AsyncService", "HTTPClient", "Service"] # noqa: WPS410
__all__ = [ # noqa: WPS410
"AsyncCreateMixin",
"AsyncDeleteMixin",
"AsyncHTTPClient",
"AsyncService",
"CreateMixin",
"DeleteMixin",
"HTTPClient",
"Service",
]
62 changes: 41 additions & 21 deletions mpt_api_client/http/async_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from mpt_api_client.http.async_client import AsyncHTTPClient
from mpt_api_client.http.base_service import ServiceBase
from mpt_api_client.http.types import QueryParam
from mpt_api_client.models import Collection, ResourceData
from mpt_api_client.models import Model as BaseModel
from mpt_api_client.models.collection import ResourceList
Expand All @@ -23,7 +24,11 @@ class AsyncService[Model: BaseModel](ServiceBase[AsyncHTTPClient, Model]): # no
"""

async def fetch_page(self, limit: int = 100, offset: int = 0) -> Collection[Model]:
"""Fetch one page of resources."""
"""Fetch one page of resources.

Returns:
Collection of resources.
"""
response = await self._fetch_page_as_response(limit=limit, offset=offset)
return self._create_collection(response)

Expand Down Expand Up @@ -72,30 +77,32 @@ async def iterate(self, batch_size: int = 100) -> AsyncIterator[Model]:
break
offset = items_collection.meta.pagination.next_offset()

async def create(self, resource_data: ResourceData) -> Model:
"""Create a new resource using `POST /endpoint`.
async def get(self, resource_id: str, select: list[str] | str | None = None) -> Model:
"""Fetch a specific resource using `GET /endpoint/{resource_id}`.

Args:
resource_id: Resource ID.
select: List of fields to select.

Returns:
New resource created.
Resource object.
"""
response = await self.http_client.post(self._endpoint, json=resource_data)
response.raise_for_status()
if isinstance(select, list):
select = ",".join(select) if select else None
return await self._resource_action(resource_id=resource_id, query_params={"select": select})

return self._model_class.from_response(response)
async def update(self, resource_id: str, resource_data: ResourceData) -> Model:
"""Update a resource using `PUT /endpoint/{resource_id}`.

async def get(self, resource_id: str) -> Model:
"""Fetch a specific resource using `GET /endpoint/{resource_id}`."""
return await self._resource_action(resource_id=resource_id)
Args:
resource_id: Resource ID.
resource_data: Resource data.

async def update(self, resource_id: str, resource_data: ResourceData) -> Model:
"""Update a resource using `PUT /endpoint/{resource_id}`."""
return await self._resource_action(resource_id, "PUT", json=resource_data)
Returns:
Resource object.

async def delete(self, resource_id: str) -> None:
"""Delete resource using `DELETE /endpoint/{resource_id}`."""
url = urljoin(f"{self._endpoint}/", resource_id)
response = await self.http_client.delete(url)
response.raise_for_status()
"""
return await self._resource_action(resource_id, "PUT", json=resource_data)

async def _fetch_page_as_response(self, limit: int = 100, offset: int = 0) -> httpx.Response:
"""Fetch one page of resources.
Expand All @@ -118,6 +125,7 @@ async def _resource_do_request(
method: str = "GET",
action: str | None = None,
json: ResourceData | ResourceList | None = None,
query_params: QueryParam | None = None,
) -> httpx.Response:
"""Perform an action on a specific resource using.

Expand All @@ -129,13 +137,14 @@ async def _resource_do_request(
method: The HTTP method to use.
action: The action name to use.
json: The updated resource data.
query_params: Additional query parameters.

Raises:
HTTPError: If the action fails.
"""
resource_url = urljoin(f"{self._endpoint}/", resource_id)
url = urljoin(f"{resource_url}/", action) if action else resource_url
response = await self.http_client.request(method, url, json=json)
response = await self.http_client.request(method, url, json=json, params=query_params)
response.raise_for_status()
return response

Expand All @@ -145,7 +154,18 @@ async def _resource_action(
method: str = "GET",
action: str | None = None,
json: ResourceData | ResourceList | None = None,
query_params: QueryParam | None = None,
) -> Model:
"""Perform an action on a specific resource using `HTTP_METHOD /endpoint/{resource_id}`."""
response = await self._resource_do_request(resource_id, method, action, json=json)
"""Perform an action on a specific resource using `HTTP_METHOD /endpoint/{resource_id}`.

Args:
resource_id: The resource ID to operate on.
method: The HTTP method to use.
action: The action name to use.
json: The updated resource data.
query_params: Additional query parameters.
"""
response = await self._resource_do_request(
resource_id, method, action, json=json, query_params=query_params
)
return self._model_class.from_response(response)
60 changes: 60 additions & 0 deletions mpt_api_client/http/mixins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from urllib.parse import urljoin

from mpt_api_client.models import ResourceData


class CreateMixin[Model]:
"""Create resource mixin."""

def create(self, resource_data: ResourceData) -> Model:
"""Create a new resource using `POST /endpoint`.

Returns:
New resource created.
"""
response = self.http_client.post(self._endpoint, json=resource_data) # type: ignore[attr-defined]
response.raise_for_status()

return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return]


class DeleteMixin:
"""Delete resource mixin."""

def delete(self, resource_id: str) -> None:
"""Delete resource using `DELETE /endpoint/{resource_id}`.

Args:
resource_id: Resource ID.
"""
response = self._resource_do_request(resource_id, "DELETE") # type: ignore[attr-defined]
response.raise_for_status()


class AsyncCreateMixin[Model]:
"""Create resource mixin."""

async def create(self, resource_data: ResourceData) -> Model:
"""Create a new resource using `POST /endpoint`.

Returns:
New resource created.
"""
response = await self.http_client.post(self._endpoint, json=resource_data) # type: ignore[attr-defined]
response.raise_for_status()

return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return]


class AsyncDeleteMixin:
"""Delete resource mixin."""

async def delete(self, resource_id: str) -> None:
"""Delete resource using `DELETE /endpoint/{resource_id}`.

Args:
resource_id: Resource ID.
"""
url = urljoin(f"{self._endpoint}/", resource_id) # type: ignore[attr-defined]
response = await self.http_client.delete(url) # type: ignore[attr-defined]
response.raise_for_status()
56 changes: 37 additions & 19 deletions mpt_api_client/http/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from mpt_api_client.http.base_service import ServiceBase
from mpt_api_client.http.client import HTTPClient
from mpt_api_client.http.types import QueryParam
from mpt_api_client.models import Collection, ResourceData
from mpt_api_client.models import Model as BaseModel
from mpt_api_client.models.collection import ResourceList
Expand Down Expand Up @@ -75,29 +76,33 @@ def iterate(self, batch_size: int = 100) -> Iterator[Model]:
break
offset = items_collection.meta.pagination.next_offset()

def create(self, resource_data: ResourceData) -> Model:
"""Create a new resource using `POST /endpoint`.
def get(self, resource_id: str, select: list[str] | str | None = None) -> Model:
"""Fetch a specific resource using `GET /endpoint/{resource_id}`.

Args:
resource_id: Resource ID.
select: List of fields to select.

Returns:
New resource created.
Resource object.
"""
response = self.http_client.post(self._endpoint, json=resource_data)
response.raise_for_status()
if isinstance(select, list):
select = ",".join(select) if select else None

return self._model_class.from_response(response)

def get(self, resource_id: str) -> Model:
"""Fetch a specific resource using `GET /endpoint/{resource_id}`."""
return self._resource_action(resource_id=resource_id)
return self._resource_action(resource_id=resource_id, query_params={"select": select})

def update(self, resource_id: str, resource_data: ResourceData) -> Model:
"""Update a resource using `PUT /endpoint/{resource_id}`."""
return self._resource_action(resource_id, "PUT", json=resource_data)
"""Update a resource using `PUT /endpoint/{resource_id}`.

def delete(self, resource_id: str) -> None:
"""Delete the resoruce using `DELETE /endpoint/{resource_id}`."""
response = self._resource_do_request(resource_id, "DELETE")
response.raise_for_status()
Args:
resource_id: Resource ID.
resource_data: Resource data.

Returns:
Resource object.

"""
return self._resource_action(resource_id, "PUT", json=resource_data)

def _fetch_page_as_response(self, limit: int = 100, offset: int = 0) -> httpx.Response:
"""Fetch one page of resources.
Expand All @@ -120,6 +125,7 @@ def _resource_do_request(
method: str = "GET",
action: str | None = None,
json: ResourceData | ResourceList | None = None,
query_params: QueryParam | None = None,
) -> httpx.Response:
"""Perform an action on a specific resource using `HTTP_METHOD /endpoint/{resource_id}`.

Expand All @@ -128,6 +134,7 @@ def _resource_do_request(
method: The HTTP method to use.
action: The action name to use.
json: The updated resource data.
query_params: Additional query parameters.

Returns:
HTTP response object.
Expand All @@ -137,7 +144,7 @@ def _resource_do_request(
"""
resource_url = urljoin(f"{self._endpoint}/", resource_id)
url = urljoin(f"{resource_url}/", action) if action else resource_url
response = self.http_client.request(method, url, json=json)
response = self.http_client.request(method, url, json=json, params=query_params)
response.raise_for_status()
return response

Expand All @@ -147,7 +154,18 @@ def _resource_action(
method: str = "GET",
action: str | None = None,
json: ResourceData | ResourceList | None = None,
query_params: QueryParam | None = None,
) -> Model:
"""Perform an action on a specific resource using `HTTP_METHOD /endpoint/{resource_id}`."""
response = self._resource_do_request(resource_id, method, action, json=json)
"""Perform an action on a specific resource using `HTTP_METHOD /endpoint/{resource_id}`.

Args:
resource_id: The resource ID to operate on.
method: The HTTP method to use.
action: The action name to use.
json: The updated resource data.
query_params: Additional query parameters.
"""
response = self._resource_do_request(
resource_id, method, action, json=json, query_params=query_params
)
return self._model_class.from_response(response)
2 changes: 2 additions & 0 deletions mpt_api_client/http/types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
PrimitiveType = str | int | float | bool | None
QueryParam = dict[str, PrimitiveType]
27 changes: 22 additions & 5 deletions mpt_api_client/resources/commerce/orders.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
from mpt_api_client.http import AsyncService, Service
from mpt_api_client.http import (
AsyncCreateMixin,
AsyncDeleteMixin,
AsyncService,
CreateMixin,
DeleteMixin,
Service,
)
from mpt_api_client.models import Model, ResourceData


Expand All @@ -14,8 +21,13 @@ class OrdersServiceConfig:
_collection_key = "data"


class OrdersService(Service[Order], OrdersServiceConfig):
"""Orders client."""
class OrdersService( # noqa: WPS215
CreateMixin[Order],
DeleteMixin,
Service[Order],
OrdersServiceConfig,
):
"""Orders service."""

def validate(self, resource_id: str, resource_data: ResourceData | None = None) -> Order:
"""Switch order to validate state.
Expand Down Expand Up @@ -84,8 +96,13 @@ def template(self, resource_id: str) -> str:
return response.text


class AsyncOrdersService(AsyncService[Order], OrdersServiceConfig):
"""Async Orders client."""
class AsyncOrdersService( # noqa: WPS215
AsyncCreateMixin[Order],
AsyncDeleteMixin,
AsyncService[Order],
OrdersServiceConfig,
):
"""Async Orders service."""

async def validate(self, resource_id: str, resource_data: ResourceData | None = None) -> Order:
"""Switch order to validate state.
Expand Down
14 changes: 10 additions & 4 deletions tests/http/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@
import pytest

from mpt_api_client import RQLQuery
from mpt_api_client.http.async_service import AsyncService
from mpt_api_client.http.service import Service
from mpt_api_client.http import (
AsyncCreateMixin,
AsyncDeleteMixin,
AsyncService,
CreateMixin,
DeleteMixin,
Service,
)
from tests.conftest import DummyModel


class DummyService(Service[DummyModel]):
class DummyService(CreateMixin[DummyModel], DeleteMixin, Service[DummyModel]):
_endpoint = "/api/v1/test"
_model_class = DummyModel


class AsyncDummyService(AsyncService[DummyModel]):
class AsyncDummyService(AsyncCreateMixin[DummyModel], AsyncDeleteMixin, AsyncService[DummyModel]):
_endpoint = "/api/v1/test"
_model_class = DummyModel

Expand Down
Loading