Skip to content

Commit 81d44d5

Browse files
committed
MPT-12330 Order resource client
1 parent cf7186b commit 81d44d5

File tree

12 files changed

+224
-52
lines changed

12 files changed

+224
-52
lines changed

mpt_api_client/http/collection.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@
66
import httpx
77

88
from mpt_api_client.http.client import MPTClient
9+
from mpt_api_client.http.resource import ResourceBaseClient
910
from mpt_api_client.models import Collection, Resource
1011
from mpt_api_client.rql.query_builder import RQLQuery
1112

1213

13-
class CollectionBaseClient[ResourceType: Resource](ABC): # noqa: WPS214
14+
class CollectionBaseClient[ResourceModel: Resource, ResourceClient: ResourceBaseClient[Resource]]( # noqa: WPS214
15+
ABC
16+
):
1417
"""Immutable Base client for RESTful resource collections.
1518
1619
Examples:
@@ -23,8 +26,9 @@ class CollectionBaseClient[ResourceType: Resource](ABC): # noqa: WPS214
2326
"""
2427

2528
_endpoint: str
26-
_resource_class: type[ResourceType]
27-
_collection_class: type[Collection[ResourceType]]
29+
_resource_class: type[ResourceModel]
30+
_resource_client_class: type[ResourceClient]
31+
_collection_class: type[Collection[ResourceModel]]
2832

2933
def __init__(
3034
self,
@@ -37,7 +41,9 @@ def __init__(
3741
self.query_select: list[str] | None = None
3842

3943
@classmethod
40-
def clone(cls, collection_client: "CollectionBaseClient[ResourceType]") -> Self:
44+
def clone(
45+
cls, collection_client: "CollectionBaseClient[ResourceModel, ResourceClient]"
46+
) -> Self:
4147
"""Create a copy of collection client for immutable operations.
4248
4349
Returns:
@@ -122,7 +128,7 @@ def select(self, *fields: str) -> Self:
122128
new_client.query_select = list(fields)
123129
return new_client
124130

125-
def fetch_page(self, limit: int = 100, offset: int = 0) -> Collection[ResourceType]:
131+
def fetch_page(self, limit: int = 100, offset: int = 0) -> Collection[ResourceModel]:
126132
"""Fetch one page of resources.
127133
128134
Returns:
@@ -131,7 +137,7 @@ def fetch_page(self, limit: int = 100, offset: int = 0) -> Collection[ResourceTy
131137
response = self._fetch_page_as_response(limit=limit, offset=offset)
132138
return Collection.from_response(response)
133139

134-
def fetch_one(self) -> ResourceType:
140+
def fetch_one(self) -> ResourceModel:
135141
"""Fetch one page, expect exactly one result.
136142
137143
Returns:
@@ -141,7 +147,7 @@ def fetch_one(self) -> ResourceType:
141147
ValueError: If the total matching records are not exactly one.
142148
"""
143149
response = self._fetch_page_as_response(limit=1, offset=0)
144-
resource_list: Collection[ResourceType] = Collection.from_response(response)
150+
resource_list: Collection[ResourceModel] = Collection.from_response(response)
145151
total_records = len(resource_list)
146152
if resource_list.meta:
147153
total_records = resource_list.meta.pagination.total
@@ -152,7 +158,7 @@ def fetch_one(self) -> ResourceType:
152158

153159
return resource_list[0]
154160

155-
def iterate(self) -> Iterator[ResourceType]:
161+
def iterate(self) -> Iterator[ResourceModel]:
156162
"""Iterate over all resources, yielding GenericResource objects.
157163
158164
Returns:
@@ -163,7 +169,7 @@ def iterate(self) -> Iterator[ResourceType]:
163169

164170
while True:
165171
response = self._fetch_page_as_response(limit=limit, offset=offset)
166-
items_collection: Collection[ResourceType] = Collection.from_response(response)
172+
items_collection: Collection[ResourceModel] = Collection.from_response(response)
167173
yield from items_collection
168174

169175
if not items_collection.meta:
@@ -172,7 +178,11 @@ def iterate(self) -> Iterator[ResourceType]:
172178
break
173179
offset = items_collection.meta.pagination.next_offset()
174180

175-
def create(self, resource_data: dict[str, Any]) -> ResourceType:
181+
def get(self, resource_id: str) -> ResourceClient:
182+
"""Get resource by resource_id."""
183+
return self._resource_client_class(client=self.mpt_client, resource_id=resource_id)
184+
185+
def create(self, resource_data: dict[str, Any]) -> ResourceModel:
176186
"""Create a new resource using `POST /endpoint`.
177187
178188
Returns:

mpt_api_client/http/resource.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,23 @@ def fetch(self) -> Resource:
5151
return self.resource_
5252

5353
def do_action(
54-
self, http_method: str = "GET", payload: dict[str, Any] | list[Any] | None = None # noqa: WPS221
54+
self,
55+
method: str = "GET",
56+
url: str | None = None,
57+
json: dict[str, Any] | list[Any] | None = None, # noqa: WPS221
5558
) -> Response:
5659
"""Perform an action on a specific resource using `HTTP_METHOD /endpoint/{resource_id}`.
5760
5861
Args:
59-
http_method: The HTTP method to use.
60-
payload: The updated resource data.
62+
method: The HTTP method to use.
63+
url: The action name to use.
64+
json: The updated resource data.
6165
6266
Raises:
6367
HTTPError: If the action fails.
6468
"""
65-
response = self.mpt_client_.request(http_method, self.resource_url, json=payload)
69+
url = f"{self.resource_url}/{url}" if url else self.resource_url
70+
response = self.mpt_client_.request(method, url, json=json)
6671
response.raise_for_status()
6772
return response
6873

@@ -80,7 +85,7 @@ def update(self, resource_data: dict[str, Any]) -> Resource:
8085
8186
8287
"""
83-
response = self.do_action("PUT", resource_data)
88+
response = self.do_action("PUT", json=resource_data)
8489
self.resource_ = self._resource_class.from_response(response) # noqa: WPS120
8590
return self.resource_
8691

@@ -109,7 +114,7 @@ def delete(self) -> None:
109114
Examples:
110115
contact.delete()
111116
"""
112-
response = self.mpt_client_.delete(self.resource_url)
117+
response = self.do_action("DELETE")
113118
response.raise_for_status()
114119

115120
self.resource_ = None # noqa: WPS120

mpt_api_client/modules/order.py

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Any
2+
13
from mpt_api_client.http.collection import CollectionBaseClient
24
from mpt_api_client.http.resource import ResourceBaseClient
35
from mpt_api_client.models import Collection, Resource
@@ -8,17 +10,80 @@ class Order(Resource):
810
"""Order resource."""
911

1012

11-
@commerce("orders")
12-
class OrderCollectionClient(CollectionBaseClient[Order]):
13-
"""Orders client."""
13+
class OrderResourceClient(ResourceBaseClient[Order]):
14+
"""Order resource client."""
1415

1516
_endpoint = "/public/v1/commerce/orders"
1617
_resource_class = Order
17-
_collection_class = Collection[Order]
1818

19+
def validate(self, order: dict[str, Any]) -> Order:
20+
"""Switch order to validate state.
1921
20-
class OrderResourceClient(ResourceBaseClient[Order]):
21-
"""Order resource client."""
22+
Args:
23+
order: Order data will be updated
24+
"""
25+
response = self.do_action("POST", "validate", json=order)
26+
return self._resource_class.from_response(response)
27+
28+
def process(self, order: dict[str, Any]) -> Order:
29+
"""Switch order to process state.
30+
31+
Args:
32+
order: Order data will be updated
33+
"""
34+
response = self.do_action("POST", "process", json=order)
35+
return self._resource_class.from_response(response)
36+
37+
def query(self, order: dict[str, Any]) -> Order:
38+
"""Switch order to query state.
39+
40+
Args:
41+
order: Order data will be updated
42+
"""
43+
response = self.do_action("POST", "query", json=order)
44+
return self._resource_class.from_response(response)
45+
46+
def complete(self, order: dict[str, Any]) -> Order:
47+
"""Switch order to complete state.
48+
49+
Args:
50+
order: Order data will be updated
51+
"""
52+
response = self.do_action("POST", "complete", json=order)
53+
return self._resource_class.from_response(response)
54+
55+
def fail(self, order: dict[str, Any]) -> Order:
56+
"""Switch order to fail state.
57+
58+
Args:
59+
order: Order data will be updated
60+
"""
61+
response = self.do_action("POST", "fail", json=order)
62+
return self._resource_class.from_response(response)
63+
64+
def notify(self, user: dict[str, Any]) -> None:
65+
"""Notify user about order status.
66+
67+
Args:
68+
user: User data
69+
"""
70+
self.do_action("POST", "notify", json=user)
71+
72+
def template(self) -> str:
73+
"""Render order template.
74+
75+
Returns:
76+
Order template text in markdown format.
77+
"""
78+
response = self.do_action("GET", "template")
79+
return response.text
80+
81+
82+
@commerce("orders")
83+
class OrderCollectionClient(CollectionBaseClient[Order, OrderResourceClient]):
84+
"""Orders client."""
2285

2386
_endpoint = "/public/v1/commerce/orders"
2487
_resource_class = Order
88+
_resource_client_class = OrderResourceClient
89+
_collection_class = Collection[Order]

mpt_api_client/registry.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from mpt_api_client.http.collection import CollectionBaseClient
55

6-
ItemType = type[CollectionBaseClient[Any]]
6+
ItemType = type[CollectionBaseClient[Any, Any]]
77

88

99
class Registry:

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,6 @@ per-file-ignores =
4848
tests/*:
4949
# Allow magic strings
5050
WPS432
51+
# Found too many modules members
52+
WPS202
5153

tests/http/collection/conftest.py

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

tests/http/collection/test_collection_client_init.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
import pytest
22

33
from mpt_api_client.http.client import MPTClient
4-
from mpt_api_client.models import Resource
54
from mpt_api_client.rql.query_builder import RQLQuery
6-
from tests.http.collection.conftest import DummyCollectionClient
7-
8-
9-
class DummyResource(Resource):
10-
"""Dummy resource for testing."""
5+
from tests.http.conftest import DummyCollectionClient
116

127

138
@pytest.fixture
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
def test_get(collection_client):
2+
resource = collection_client.get("RES-123")
3+
assert resource.resource_id_ == "RES-123"
4+
assert isinstance(resource, collection_client._resource_client_class) # noqa: SLF001
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import pytest
22

33
from mpt_api_client.http.client import MPTClient
4+
from mpt_api_client.http.collection import CollectionBaseClient
45
from mpt_api_client.http.resource import ResourceBaseClient
6+
from mpt_api_client.models import Collection
57
from tests.conftest import DummyResource
68

79

@@ -10,6 +12,13 @@ class DummyResourceClient(ResourceBaseClient[DummyResource]):
1012
_resource_class = DummyResource
1113

1214

15+
class DummyCollectionClient(CollectionBaseClient[DummyResource, DummyResourceClient]):
16+
_endpoint = "/api/v1/test"
17+
_resource_class = DummyResource
18+
_resource_client_class = DummyResourceClient
19+
_collection_class = Collection[DummyResource]
20+
21+
1322
@pytest.fixture
1423
def api_url():
1524
return "https://api.example.com"
@@ -28,3 +37,8 @@ def mpt_client(api_url, api_token):
2837
@pytest.fixture
2938
def resource_client(mpt_client):
3039
return DummyResourceClient(client=mpt_client, resource_id="RES-123")
40+
41+
42+
@pytest.fixture
43+
def collection_client(mpt_client) -> DummyCollectionClient:
44+
return DummyCollectionClient(client=mpt_client)

tests/modules/test_order.py renamed to tests/modules/orders/test_order_collection_client.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
1-
from mpt_api_client.modules.order import Order, OrderCollectionClient
1+
from mpt_api_client.modules.order import OrderCollectionClient
22

33

44
def test_order_collection_client(mpt_client):
55
order_cc = OrderCollectionClient(client=mpt_client)
66
assert order_cc.query_rql is None
7-
8-
9-
def test_order():
10-
order = Order()
11-
assert order is not None

0 commit comments

Comments
 (0)