Skip to content

Commit abb6d87

Browse files
authored
MPT-12329 Order resource client (#15)
2 parents c2704a7 + c5027f5 commit abb6d87

24 files changed

+351
-133
lines changed

mpt_api_client/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from mpt_api_client.mptclient import MPTClient
2+
from mpt_api_client.rql import RQLQuery
3+
4+
__all__ = ["MPTClient", "RQLQuery"] # noqa: WPS410

mpt_api_client/http/client.py

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

55

6-
class MPTClient(httpx.Client):
6+
class HTTPClient(httpx.Client):
77
"""A client for interacting with SoftwareOne Marketplace Platform API."""
88

99
def __init__(

mpt_api_client/http/collection.py

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55

66
import httpx
77

8-
from mpt_api_client.http.client import MPTClient
8+
from mpt_api_client.http.client import HTTPClient
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,21 +26,24 @@ 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,
3135
query_rql: RQLQuery | None = None,
32-
client: MPTClient | None = None,
36+
client: HTTPClient | None = None,
3337
) -> None:
34-
self.mpt_client = client or MPTClient()
38+
self.mpt_client = client or HTTPClient()
3539
self.query_rql: RQLQuery | None = query_rql
3640
self.query_order_by: list[str] | None = None
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,18 +158,23 @@ def fetch_one(self) -> ResourceType:
152158

153159
return resource_list[0]
154160

155-
def iterate(self) -> Iterator[ResourceType]:
161+
def iterate(self, batch_size: int = 100) -> Iterator[ResourceModel]:
156162
"""Iterate over all resources, yielding GenericResource objects.
157163
164+
Args:
165+
batch_size: Number of resources to fetch per request
166+
158167
Returns:
159168
Iterator of resources.
160169
"""
161170
offset = 0
162-
limit = 100 # Default page size
171+
limit = batch_size # Default page size
163172

164173
while True:
165174
response = self._fetch_page_as_response(limit=limit, offset=offset)
166-
items_collection: Collection[ResourceType] = Collection.from_response(response)
175+
items_collection: Collection[ResourceModel] = self._collection_class.from_response(
176+
response
177+
)
167178
yield from items_collection
168179

169180
if not items_collection.meta:
@@ -172,7 +183,11 @@ def iterate(self) -> Iterator[ResourceType]:
172183
break
173184
offset = items_collection.meta.pagination.next_offset()
174185

175-
def create(self, resource_data: dict[str, Any]) -> ResourceType:
186+
def get(self, resource_id: str) -> ResourceClient:
187+
"""Get resource by resource_id."""
188+
return self._resource_client_class(client=self.mpt_client, resource_id=resource_id)
189+
190+
def create(self, resource_data: dict[str, Any]) -> ResourceModel:
176191
"""Create a new resource using `POST /endpoint`.
177192
178193
Returns:

mpt_api_client/http/resource.py

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
from abc import ABC
22
from typing import Any, ClassVar, Self, override
33

4-
from mpt_api_client.http.client import MPTClient
4+
from httpx import Response
5+
6+
from mpt_api_client.http.client import HTTPClient
57
from mpt_api_client.models import Resource
68

79

8-
class ResourceBaseClient[ResourceType: Resource](ABC): # noqa: WPS214
10+
class ResourceBaseClient[ResourceModel: Resource](ABC): # noqa: WPS214
911
"""Client for RESTful resources."""
1012

1113
_endpoint: str
12-
_resource_class: type[Resource]
14+
_resource_class: type[ResourceModel]
1315
_safe_attributes: ClassVar[set[str]] = {"mpt_client_", "resource_id_", "resource_"}
1416

15-
def __init__(self, client: MPTClient, resource_id: str) -> None:
17+
def __init__(self, client: HTTPClient, resource_id: str) -> None:
1618
self.mpt_client_ = client # noqa: WPS120
1719
self.resource_id_ = resource_id # noqa: WPS120
1820
self.resource_: Resource | None = None # noqa: WPS120
@@ -35,21 +37,52 @@ def __setattr__(self, attribute: str, attribute_value: Any) -> None:
3537
self._ensure_resource_is_fetched()
3638
self.resource_.__setattr__(attribute, attribute_value)
3739

38-
def fetch(self) -> Resource:
40+
def fetch(self) -> ResourceModel:
3941
"""Fetch a specific resource using `GET /endpoint/{resource_id}`.
4042
4143
It fetches and caches the resource.
4244
4345
Returns:
4446
The fetched resource.
4547
"""
46-
response = self.mpt_client_.get(self.resource_url)
47-
response.raise_for_status()
48+
response = self.do_action("GET")
4849

4950
self.resource_ = self._resource_class.from_response(response) # noqa: WPS120
5051
return self.resource_
5152

52-
def update(self, resource_data: dict[str, Any]) -> Resource:
53+
def resource_action(
54+
self,
55+
method: str = "GET",
56+
url: str | None = None,
57+
json: dict[str, Any] | list[Any] | None = None, # noqa: WPS221
58+
) -> ResourceModel:
59+
"""Perform an action on a specific resource using `HTTP_METHOD /endpoint/{resource_id}`."""
60+
response = self.do_action(method, url, json=json)
61+
self.resource_ = self._resource_class.from_response(response) # noqa: WPS120
62+
return self.resource_
63+
64+
def do_action(
65+
self,
66+
method: str = "GET",
67+
url: str | None = None,
68+
json: dict[str, Any] | list[Any] | None = None, # noqa: WPS221
69+
) -> Response:
70+
"""Perform an action on a specific resource using `HTTP_METHOD /endpoint/{resource_id}`.
71+
72+
Args:
73+
method: The HTTP method to use.
74+
url: The action name to use.
75+
json: The updated resource data.
76+
77+
Raises:
78+
HTTPError: If the action fails.
79+
"""
80+
url = f"{self.resource_url}/{url}" if url else self.resource_url
81+
response = self.mpt_client_.request(method, url, json=json)
82+
response.raise_for_status()
83+
return response
84+
85+
def update(self, resource_data: dict[str, Any]) -> ResourceModel:
5386
"""Update a specific in the API and catches the result as a current resource.
5487
5588
Args:
@@ -63,9 +96,7 @@ def update(self, resource_data: dict[str, Any]) -> Resource:
6396
6497
6598
"""
66-
response = self.mpt_client_.put(self.resource_url, json=resource_data)
67-
response.raise_for_status()
68-
99+
response = self.do_action("PUT", json=resource_data)
69100
self.resource_ = self._resource_class.from_response(response) # noqa: WPS120
70101
return self.resource_
71102

@@ -94,7 +125,7 @@ def delete(self) -> None:
94125
Examples:
95126
contact.delete()
96127
"""
97-
response = self.mpt_client_.delete(self.resource_url)
128+
response = self.do_action("DELETE")
98129
response.raise_for_status()
99130

100131
self.resource_ = None # noqa: WPS120

mpt_api_client/models/resource.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
class Resource(BaseResource):
1111
"""Provides a resource to interact with api data using fluent interfaces."""
1212

13-
_data_key: ClassVar[str] = "data"
13+
_data_key: ClassVar[str | None] = None
1414
_safe_attributes: ClassVar[list[str]] = ["meta", "_resource_data"]
1515

1616
def __init__(self, resource_data: ResourceData | None = None, meta: Meta | None = None) -> None:
@@ -37,7 +37,11 @@ def __setattr__(self, attribute: str, attribute_value: Any) -> None:
3737
@classmethod
3838
@override
3939
def from_response(cls, response: Response) -> Self:
40-
response_data = response.json().get(cls._data_key)
40+
response_data = response.json()
41+
if isinstance(response_data, dict):
42+
response_data.pop("$meta", None)
43+
if cls._data_key:
44+
response_data = response_data.get(cls._data_key)
4145
if not isinstance(response_data, dict):
4246
raise TypeError("Response data must be a dict.")
4347
meta = Meta.from_response(response)

mpt_api_client/modules/__init__.py

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

mpt_api_client/modules/order.py

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

mpt_api_client/mpt.py renamed to mpt_api_client/mptclient.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1-
from mpt_api_client.http.client import MPTClient
2-
from mpt_api_client.modules import OrderCollectionClient
1+
from mpt_api_client.http.client import HTTPClient
32
from mpt_api_client.registry import Registry, commerce
3+
from mpt_api_client.resources import OrderCollectionClient
44

55

6-
class MPT:
6+
class MPTClient:
77
"""MPT API Client."""
88

99
def __init__(
1010
self,
1111
base_url: str | None = None,
1212
api_key: str | None = None,
1313
registry: Registry | None = None,
14-
mpt_client: MPTClient | None = None,
14+
mpt_client: HTTPClient | None = None,
1515
):
16-
17-
self.mpt_client = mpt_client or MPTClient(base_url=base_url, api_token=api_key)
16+
self.mpt_client = mpt_client or HTTPClient(base_url=base_url, api_token=api_key)
1817
self.registry: Registry = registry or Registry()
1918

2019
def __getattr__(self, name): # type: ignore[no-untyped-def]
@@ -31,7 +30,7 @@ def commerce(self) -> "CommerceMpt":
3130
return CommerceMpt(mpt_client=self.mpt_client, registry=commerce)
3231

3332

34-
class CommerceMpt(MPT):
33+
class CommerceMpt(MPTClient):
3534
"""Commerce MPT API Client."""
3635

3736
@property

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:
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from mpt_api_client.resources.order import Order, OrderCollectionClient, OrderResourceClient
2+
3+
__all__ = ["Order", "OrderCollectionClient", "OrderResourceClient"] # noqa: WPS410

0 commit comments

Comments
 (0)