Skip to content

Commit 830b3ac

Browse files
committed
Extract Create and Delete mixins as Agreements can't be created or deleted
1 parent 4243093 commit 830b3ac

File tree

9 files changed

+168
-146
lines changed

9 files changed

+168
-146
lines changed

mpt_api_client/http/__init__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
from mpt_api_client.http.async_client import AsyncHTTPClient
22
from mpt_api_client.http.async_service import AsyncService
33
from mpt_api_client.http.client import HTTPClient
4+
from mpt_api_client.http.mixins import AsyncCreateMixin, AsyncDeleteMixin, CreateMixin, DeleteMixin
45
from mpt_api_client.http.service import Service
56

6-
__all__ = ["AsyncHTTPClient", "AsyncService", "HTTPClient", "Service"] # noqa: WPS410
7+
__all__ = [ # noqa: WPS410
8+
"AsyncCreateMixin",
9+
"AsyncDeleteMixin",
10+
"AsyncHTTPClient",
11+
"AsyncService",
12+
"CreateMixin",
13+
"DeleteMixin",
14+
"HTTPClient",
15+
"Service",
16+
]

mpt_api_client/http/async_service.py

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
from mpt_api_client.http.async_client import AsyncHTTPClient
77
from mpt_api_client.http.base_service import ServiceBase
8-
from mpt_api_client.http.helper import prepare_query_params
98
from mpt_api_client.http.types import QueryParam
109
from mpt_api_client.models import Collection, ResourceData
1110
from mpt_api_client.models import Model as BaseModel
@@ -78,17 +77,6 @@ async def iterate(self, batch_size: int = 100) -> AsyncIterator[Model]:
7877
break
7978
offset = items_collection.meta.pagination.next_offset()
8079

81-
async def create(self, resource_data: ResourceData) -> Model:
82-
"""Create a new resource using `POST /endpoint`.
83-
84-
Returns:
85-
New resource created.
86-
"""
87-
response = await self.http_client.post(self._endpoint, json=resource_data)
88-
response.raise_for_status()
89-
90-
return self._model_class.from_response(response)
91-
9280
async def get(self, resource_id: str, select: list[str] | str | None = None) -> Model:
9381
"""Fetch a specific resource using `GET /endpoint/{resource_id}`.
9482
@@ -116,16 +104,6 @@ async def update(self, resource_id: str, resource_data: ResourceData) -> Model:
116104
"""
117105
return await self._resource_action(resource_id, "PUT", json=resource_data)
118106

119-
async def delete(self, resource_id: str) -> None:
120-
"""Delete resource using `DELETE /endpoint/{resource_id}`.
121-
122-
Args:
123-
resource_id: Resource ID.
124-
"""
125-
url = urljoin(f"{self._endpoint}/", resource_id)
126-
response = await self.http_client.delete(url)
127-
response.raise_for_status()
128-
129107
async def _fetch_page_as_response(self, limit: int = 100, offset: int = 0) -> httpx.Response:
130108
"""Fetch one page of resources.
131109
@@ -166,9 +144,7 @@ async def _resource_do_request(
166144
"""
167145
resource_url = urljoin(f"{self._endpoint}/", resource_id)
168146
url = urljoin(f"{resource_url}/", action) if action else resource_url
169-
response = await self.http_client.request(
170-
method, url, json=json, params=prepare_query_params(query_params)
171-
)
147+
response = await self.http_client.request(method, url, json=json, params=query_params)
172148
response.raise_for_status()
173149
return response
174150

mpt_api_client/http/helper.py

Lines changed: 0 additions & 24 deletions
This file was deleted.

mpt_api_client/http/mixins.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from urllib.parse import urljoin
2+
3+
from mpt_api_client.models import ResourceData
4+
5+
6+
class CreateMixin[Model]:
7+
"""Create resource mixin."""
8+
9+
def create(self, resource_data: ResourceData) -> Model:
10+
"""Create a new resource using `POST /endpoint`.
11+
12+
Returns:
13+
New resource created.
14+
"""
15+
response = self.http_client.post(self._endpoint, json=resource_data) # type: ignore[attr-defined]
16+
response.raise_for_status()
17+
18+
return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return]
19+
20+
21+
class DeleteMixin:
22+
"""Delete resource mixin."""
23+
24+
def delete(self, resource_id: str) -> None:
25+
"""Delete resource using `DELETE /endpoint/{resource_id}`.
26+
27+
Args:
28+
resource_id: Resource ID.
29+
"""
30+
response = self._resource_do_request(resource_id, "DELETE") # type: ignore[attr-defined]
31+
response.raise_for_status()
32+
33+
34+
class AsyncCreateMixin[Model]:
35+
"""Create resource mixin."""
36+
37+
async def create(self, resource_data: ResourceData) -> Model:
38+
"""Create a new resource using `POST /endpoint`.
39+
40+
Returns:
41+
New resource created.
42+
"""
43+
response = await self.http_client.post(self._endpoint, json=resource_data) # type: ignore[attr-defined]
44+
response.raise_for_status()
45+
46+
return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return]
47+
48+
49+
class AsyncDeleteMixin:
50+
"""Delete resource mixin."""
51+
52+
async def delete(self, resource_id: str) -> None:
53+
"""Delete resource using `DELETE /endpoint/{resource_id}`.
54+
55+
Args:
56+
resource_id: Resource ID.
57+
"""
58+
url = urljoin(f"{self._endpoint}/", resource_id) # type: ignore[attr-defined]
59+
response = await self.http_client.delete(url) # type: ignore[attr-defined]
60+
response.raise_for_status()

mpt_api_client/http/service.py

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
from mpt_api_client.http.base_service import ServiceBase
77
from mpt_api_client.http.client import HTTPClient
8-
from mpt_api_client.http.helper import prepare_query_params
98
from mpt_api_client.http.types import QueryParam
109
from mpt_api_client.models import Collection, ResourceData
1110
from mpt_api_client.models import Model as BaseModel
@@ -77,17 +76,6 @@ def iterate(self, batch_size: int = 100) -> Iterator[Model]:
7776
break
7877
offset = items_collection.meta.pagination.next_offset()
7978

80-
def create(self, resource_data: ResourceData) -> Model:
81-
"""Create a new resource using `POST /endpoint`.
82-
83-
Returns:
84-
New resource created.
85-
"""
86-
response = self.http_client.post(self._endpoint, json=resource_data)
87-
response.raise_for_status()
88-
89-
return self._model_class.from_response(response)
90-
9179
def get(self, resource_id: str, select: list[str] | str | None = None) -> Model:
9280
"""Fetch a specific resource using `GET /endpoint/{resource_id}`.
9381
@@ -116,15 +104,6 @@ def update(self, resource_id: str, resource_data: ResourceData) -> Model:
116104
"""
117105
return self._resource_action(resource_id, "PUT", json=resource_data)
118106

119-
def delete(self, resource_id: str) -> None:
120-
"""Delete resource using `DELETE /endpoint/{resource_id}`.
121-
122-
Args:
123-
resource_id: Resource ID.
124-
"""
125-
response = self._resource_do_request(resource_id, "DELETE")
126-
response.raise_for_status()
127-
128107
def _fetch_page_as_response(self, limit: int = 100, offset: int = 0) -> httpx.Response:
129108
"""Fetch one page of resources.
130109
@@ -165,9 +144,7 @@ def _resource_do_request(
165144
"""
166145
resource_url = urljoin(f"{self._endpoint}/", resource_id)
167146
url = urljoin(f"{resource_url}/", action) if action else resource_url
168-
response = self.http_client.request(
169-
method, url, json=json, params=prepare_query_params(query_params)
170-
)
147+
response = self.http_client.request(method, url, json=json, params=query_params)
171148
response.raise_for_status()
172149
return response
173150

mpt_api_client/resources/commerce/orders.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
from mpt_api_client.http import AsyncService, Service
1+
from mpt_api_client.http import (
2+
AsyncCreateMixin,
3+
AsyncDeleteMixin,
4+
AsyncService,
5+
CreateMixin,
6+
DeleteMixin,
7+
Service,
8+
)
29
from mpt_api_client.models import Model, ResourceData
310

411

@@ -14,7 +21,12 @@ class OrdersServiceConfig:
1421
_collection_key = "data"
1522

1623

17-
class OrdersService(Service[Order], OrdersServiceConfig):
24+
class OrdersService( # noqa: WPS215
25+
CreateMixin[Order],
26+
DeleteMixin,
27+
Service[Order],
28+
OrdersServiceConfig,
29+
):
1830
"""Orders service."""
1931

2032
def validate(self, resource_id: str, resource_data: ResourceData | None = None) -> Order:
@@ -84,7 +96,12 @@ def template(self, resource_id: str) -> str:
8496
return response.text
8597

8698

87-
class AsyncOrdersService(AsyncService[Order], OrdersServiceConfig):
99+
class AsyncOrdersService( # noqa: WPS215
100+
AsyncCreateMixin[Order],
101+
AsyncDeleteMixin,
102+
AsyncService[Order],
103+
OrdersServiceConfig,
104+
):
88105
"""Async Orders service."""
89106

90107
async def validate(self, resource_id: str, resource_data: ResourceData | None = None) -> Order:

tests/http/conftest.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,23 @@
22
import pytest
33

44
from mpt_api_client import RQLQuery
5-
from mpt_api_client.http.async_service import AsyncService
6-
from mpt_api_client.http.service import Service
5+
from mpt_api_client.http import (
6+
AsyncCreateMixin,
7+
AsyncDeleteMixin,
8+
AsyncService,
9+
CreateMixin,
10+
DeleteMixin,
11+
Service,
12+
)
713
from tests.conftest import DummyModel
814

915

10-
class DummyService(Service[DummyModel]):
16+
class DummyService(CreateMixin[DummyModel], DeleteMixin, Service[DummyModel]):
1117
_endpoint = "/api/v1/test"
1218
_model_class = DummyModel
1319

1420

15-
class AsyncDummyService(AsyncService[DummyModel]):
21+
class AsyncDummyService(AsyncCreateMixin[DummyModel], AsyncDeleteMixin, AsyncService[DummyModel]):
1622
_endpoint = "/api/v1/test"
1723
_model_class = DummyModel
1824

tests/http/test_async_service.py

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,39 @@
88
from tests.http.conftest import AsyncDummyService
99

1010

11+
async def test_async_create_mixin(async_dummy_service): # noqa: WPS210
12+
resource_data = {"name": "Test Resource", "status": "active"}
13+
new_resource_data = {"id": "new-resource-id", "name": "Test Resource", "status": "active"}
14+
create_response = httpx.Response(httpx.codes.OK, json=new_resource_data)
15+
16+
with respx.mock:
17+
mock_route = respx.post("https://api.example.com/api/v1/test").mock(
18+
return_value=create_response
19+
)
20+
21+
created_resource = await async_dummy_service.create(resource_data)
22+
23+
assert created_resource.to_dict() == new_resource_data
24+
assert mock_route.call_count == 1
25+
request = mock_route.calls[0].request
26+
assert request.method == "POST"
27+
assert request.url == "https://api.example.com/api/v1/test"
28+
assert json.loads(request.content.decode()) == resource_data
29+
30+
31+
async def test_async_delete_mixin(async_dummy_service): # noqa: WPS210
32+
delete_response = httpx.Response(httpx.codes.NO_CONTENT, json=None)
33+
34+
with respx.mock:
35+
mock_route = respx.delete("https://api.example.com/api/v1/test/RES-123").mock(
36+
return_value=delete_response
37+
)
38+
39+
await async_dummy_service.delete("RES-123")
40+
41+
assert mock_route.call_count == 1
42+
43+
1144
async def test_async_fetch_one_success(async_dummy_service, single_result_response):
1245
with respx.mock:
1346
mock_route = respx.get("https://api.example.com/api/v1/test").mock(
@@ -244,39 +277,6 @@ async def test_async_iterate_lazy_evaluation(async_dummy_service):
244277
assert mock_route.call_count == 1
245278

246279

247-
async def test_async_create_resource(async_dummy_service): # noqa: WPS210
248-
resource_data = {"name": "Test Resource", "status": "active"}
249-
new_resource_data = {"id": "new-resource-id", "name": "Test Resource", "status": "active"}
250-
create_response = httpx.Response(httpx.codes.OK, json=new_resource_data)
251-
252-
with respx.mock:
253-
mock_route = respx.post("https://api.example.com/api/v1/test").mock(
254-
return_value=create_response
255-
)
256-
257-
created_resource = await async_dummy_service.create(resource_data)
258-
259-
assert created_resource.to_dict() == new_resource_data
260-
assert mock_route.call_count == 1
261-
request = mock_route.calls[0].request
262-
assert request.method == "POST"
263-
assert request.url == "https://api.example.com/api/v1/test"
264-
assert json.loads(request.content.decode()) == resource_data
265-
266-
267-
async def test_async_delete_resource(async_dummy_service): # noqa: WPS210
268-
delete_response = httpx.Response(httpx.codes.NO_CONTENT, json=None)
269-
270-
with respx.mock:
271-
mock_route = respx.delete("https://api.example.com/api/v1/test/RES-123").mock(
272-
return_value=delete_response
273-
)
274-
275-
await async_dummy_service.delete("RES-123")
276-
277-
assert mock_route.call_count == 1
278-
279-
280280
async def test_async_update_resource(async_dummy_service): # noqa: WPS210
281281
resource_data = {"name": "Test Resource", "status": "active"}
282282
update_response = httpx.Response(httpx.codes.OK, json=resource_data)

0 commit comments

Comments
 (0)