Skip to content

Commit 3cde683

Browse files
committed
MPT-12358 Refactor for collection client async
1 parent c725640 commit 3cde683

File tree

12 files changed

+99
-67
lines changed

12 files changed

+99
-67
lines changed

mpt_api_client/http/collection.py

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,52 +5,39 @@
55

66
import httpx
77

8-
from mpt_api_client.http.client import HTTPClient
8+
from mpt_api_client.http.client import HTTPClient, HTTPClientAsync
99
from mpt_api_client.http.resource import ResourceBaseClient
1010
from mpt_api_client.models import Collection, Resource
1111
from mpt_api_client.rql.query_builder import RQLQuery
1212

1313

14-
class CollectionBaseClient[ResourceModel: Resource, ResourceClient: ResourceBaseClient[Resource]]( # noqa: WPS214
15-
ABC
16-
):
17-
"""Immutable Base client for RESTful resource collections.
18-
19-
Examples:
20-
active_orders_cc = order_collection.filter(RQLQuery(status="active"))
21-
active_orders = active_orders_cc.order_by("created").iterate()
22-
product_active_orders = active_orders_cc.filter(RQLQuery(product__id="PRD-1")).iterate()
23-
24-
new_order = order_collection.create(order_data)
25-
26-
"""
14+
class CollectionMixin:
15+
"""Mixin for collection clients."""
2716

2817
_endpoint: str
29-
_resource_class: type[ResourceModel]
30-
_resource_client_class: type[ResourceClient]
31-
_collection_class: type[Collection[ResourceModel]]
18+
_resource_class: type[Any]
19+
_resource_client_class: type[Any]
20+
_collection_class: type[Collection[Any]]
3221

3322
def __init__(
3423
self,
24+
http_client: HTTPClient | HTTPClientAsync,
3525
query_rql: RQLQuery | None = None,
36-
client: HTTPClient | None = None,
3726
) -> None:
38-
self.mpt_client = client or HTTPClient()
27+
self.http_client = http_client
3928
self.query_rql: RQLQuery | None = query_rql
4029
self.query_order_by: list[str] | None = None
4130
self.query_select: list[str] | None = None
4231

4332
@classmethod
44-
def clone(
45-
cls, collection_client: "CollectionBaseClient[ResourceModel, ResourceClient]"
46-
) -> Self:
33+
def clone(cls, collection_client: "CollectionMixin") -> Self:
4734
"""Create a copy of collection client for immutable operations.
4835
4936
Returns:
5037
New collection client with same settings.
5138
"""
5239
new_collection = cls(
53-
client=collection_client.mpt_client,
40+
http_client=collection_client.http_client,
5441
query_rql=collection_client.query_rql,
5542
)
5643
new_collection.query_order_by = (
@@ -128,6 +115,33 @@ def select(self, *fields: str) -> Self:
128115
new_client.query_select = list(fields)
129116
return new_client
130117

118+
119+
class CollectionClientBase[ResourceModel: Resource, ResourceClient: ResourceBaseClient[Resource]]( # noqa: WPS214
120+
ABC, CollectionMixin
121+
):
122+
"""Immutable Base client for RESTful resource collections.
123+
124+
Examples:
125+
active_orders_cc = order_collection.filter(RQLQuery(status="active"))
126+
active_orders = active_orders_cc.order_by("created").iterate()
127+
product_active_orders = active_orders_cc.filter(RQLQuery(product__id="PRD-1")).iterate()
128+
129+
new_order = order_collection.create(order_data)
130+
131+
"""
132+
133+
_resource_class: type[ResourceModel]
134+
_resource_client_class: type[ResourceClient]
135+
_collection_class: type[Collection[ResourceModel]]
136+
137+
def __init__(
138+
self,
139+
query_rql: RQLQuery | None = None,
140+
http_client: HTTPClient | None = None,
141+
) -> None:
142+
self.http_client: HTTPClient = http_client or HTTPClient() # type: ignore[mutable-override]
143+
CollectionMixin.__init__(self, http_client=self.http_client, query_rql=query_rql)
144+
131145
def fetch_page(self, limit: int = 100, offset: int = 0) -> Collection[ResourceModel]:
132146
"""Fetch one page of resources.
133147
@@ -185,15 +199,15 @@ def iterate(self, batch_size: int = 100) -> Iterator[ResourceModel]:
185199

186200
def get(self, resource_id: str) -> ResourceClient:
187201
"""Get resource by resource_id."""
188-
return self._resource_client_class(client=self.mpt_client, resource_id=resource_id)
202+
return self._resource_client_class(http_client=self.http_client, resource_id=resource_id)
189203

190204
def create(self, resource_data: dict[str, Any]) -> ResourceModel:
191205
"""Create a new resource using `POST /endpoint`.
192206
193207
Returns:
194208
New resource created.
195209
"""
196-
response = self.mpt_client.post(self._endpoint, json=resource_data)
210+
response = self.http_client.post(self._endpoint, json=resource_data)
197211
response.raise_for_status()
198212

199213
return self._resource_class.from_response(response)
@@ -208,7 +222,7 @@ def _fetch_page_as_response(self, limit: int = 100, offset: int = 0) -> httpx.Re
208222
HTTPStatusError: if the response status code is not 200.
209223
"""
210224
pagination_params: dict[str, int] = {"limit": limit, "offset": offset}
211-
response = self.mpt_client.get(self.build_url(pagination_params))
225+
response = self.http_client.get(self.build_url(pagination_params))
212226
response.raise_for_status()
213227

214228
return response

mpt_api_client/http/resource.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ class ResourceBaseClient[ResourceModel: Resource](ABC): # noqa: WPS214
1212

1313
_endpoint: str
1414
_resource_class: type[ResourceModel]
15-
_safe_attributes: ClassVar[set[str]] = {"mpt_client_", "resource_id_", "resource_"}
15+
_safe_attributes: ClassVar[set[str]] = {"http_client_", "resource_id_", "resource_"}
1616

17-
def __init__(self, client: HTTPClient, resource_id: str) -> None:
18-
self.mpt_client_ = client # noqa: WPS120
17+
def __init__(self, http_client: HTTPClient, resource_id: str) -> None:
18+
self.http_client_ = http_client # noqa: WPS120
1919
self.resource_id_ = resource_id # noqa: WPS120
2020
self.resource_: Resource | None = None # noqa: WPS120
2121

@@ -78,7 +78,7 @@ def do_action(
7878
HTTPError: If the action fails.
7979
"""
8080
url = f"{self.resource_url}/{url}" if url else self.resource_url
81-
response = self.mpt_client_.request(method, url, json=json)
81+
response = self.http_client_.request(method, url, json=json)
8282
response.raise_for_status()
8383
return response
8484

mpt_api_client/mptclient.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from mpt_api_client.http.client import HTTPClient
22
from mpt_api_client.registry import Registry, commerce
3-
from mpt_api_client.resources import OrderCollectionClient
3+
from mpt_api_client.resources import OrderCollectionClientBase
44

55

66
class MPTClientBase:
@@ -11,13 +11,13 @@ def __init__(
1111
base_url: str | None = None,
1212
api_key: str | None = None,
1313
registry: Registry | None = None,
14-
mpt_client: HTTPClient | None = None,
14+
http_client: HTTPClient | None = None,
1515
):
16-
self.mpt_client = mpt_client or HTTPClient(base_url=base_url, api_token=api_key)
16+
self.http_client = http_client or HTTPClient(base_url=base_url, api_token=api_key)
1717
self.registry: Registry = registry or Registry()
1818

1919
def __getattr__(self, name): # type: ignore[no-untyped-def]
20-
return self.registry.get(name)(client=self.mpt_client)
20+
return self.registry.get(name)(http_client=self.http_client)
2121

2222

2323
class MPTClient(MPTClientBase):
@@ -31,14 +31,14 @@ def commerce(self) -> "CommerceMpt":
3131
for managing agreements, requests, subscriptions, and orders
3232
within a vendor-client-ops ecosystem.
3333
"""
34-
return CommerceMpt(mpt_client=self.mpt_client, registry=commerce)
34+
return CommerceMpt(http_client=self.http_client, registry=commerce)
3535

3636

3737
class CommerceMpt(MPTClientBase):
3838
"""Commerce MPT API Client."""
3939

4040
@property
41-
def orders(self) -> OrderCollectionClient:
41+
def orders(self) -> OrderCollectionClientBase:
4242
"""Orders MPT API collection.
4343
4444
The Orders API provides a comprehensive set of endpoints
@@ -54,4 +54,4 @@ def orders(self) -> OrderCollectionClient:
5454
[...]
5555
5656
"""
57-
return self.registry.get("orders")(client=self.mpt_client) # type: ignore[return-value]
57+
return self.registry.get("orders")(http_client=self.http_client) # type: ignore[return-value]

mpt_api_client/registry.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from collections.abc import Callable
22
from typing import Any
33

4-
from mpt_api_client.http.collection import CollectionBaseClient
4+
from mpt_api_client.http.collection import CollectionClientBase
55

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

88

99
class Registry:
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
from mpt_api_client.resources.order import Order, OrderCollectionClient, OrderResourceClient
1+
from mpt_api_client.resources.order import Order, OrderCollectionClientBase, OrderResourceClient
22

3-
__all__ = ["Order", "OrderCollectionClient", "OrderResourceClient"] # noqa: WPS410
3+
__all__ = ["Order", "OrderCollectionClientBase", "OrderResourceClient"] # noqa: WPS410

mpt_api_client/resources/order.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Any
22

3-
from mpt_api_client.http.collection import CollectionBaseClient
3+
from mpt_api_client.http.collection import CollectionClientBase
44
from mpt_api_client.http.resource import ResourceBaseClient
55
from mpt_api_client.models import Collection, Resource
66
from mpt_api_client.registry import commerce
@@ -76,7 +76,7 @@ def template(self) -> str:
7676

7777

7878
@commerce("orders")
79-
class OrderCollectionClient(CollectionBaseClient[Order, OrderResourceClient]):
79+
class OrderCollectionClientBase(CollectionClientBase[Order, OrderResourceClient]):
8080
"""Orders client."""
8181

8282
_endpoint = "/public/v1/commerce/orders"

tests/http/collection/test_collection_client_init.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from mpt_api_client.http.client import HTTPClient
44
from mpt_api_client.rql.query_builder import RQLQuery
5-
from tests.http.conftest import DummyCollectionClient
5+
from tests.http.conftest import DummyCollectionClientBase
66

77

88
@pytest.fixture
@@ -16,7 +16,7 @@ def sample_rql_query():
1616

1717

1818
def test_init_defaults(http_client):
19-
collection_client = DummyCollectionClient(client=http_client)
19+
collection_client = DummyCollectionClientBase(http_client=http_client)
2020

2121
assert collection_client.query_rql is None
2222
assert collection_client.query_order_by is None
@@ -25,8 +25,8 @@ def test_init_defaults(http_client):
2525

2626

2727
def test_init_with_filter(http_client, sample_rql_query):
28-
collection_client = DummyCollectionClient(
29-
client=http_client,
28+
collection_client = DummyCollectionClientBase(
29+
http_client=http_client,
3030
query_rql=sample_rql_query,
3131
)
3232

tests/http/collection/test_collection_client.py renamed to tests/http/collection/test_collection_mixin.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,19 @@ def test_url(collection_client) -> None:
6767
"&select=-audit,product.agreements,-product.agreements.product"
6868
"&eq(status,active)"
6969
)
70+
71+
72+
def test_clone(collection_client) -> None:
73+
configured = (
74+
collection_client
75+
.filter(RQLQuery(status="active"))
76+
.order_by("created", "-name")
77+
.select("agreement", "-product")
78+
)
79+
80+
cloned = configured.clone(configured)
81+
82+
assert cloned is not configured
83+
assert isinstance(cloned, configured.__class__)
84+
assert cloned.http_client is configured.http_client
85+
assert str(cloned.query_rql) == str(configured.query_rql)

tests/http/conftest.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
22

33
from mpt_api_client.http.client import HTTPClient, HTTPClientAsync
4-
from mpt_api_client.http.collection import CollectionBaseClient
4+
from mpt_api_client.http.collection import CollectionClientBase
55
from mpt_api_client.http.resource import ResourceBaseClient
66
from mpt_api_client.models import Collection
77
from tests.conftest import DummyResource
@@ -12,7 +12,7 @@ class DummyResourceClient(ResourceBaseClient[DummyResource]):
1212
_resource_class = DummyResource
1313

1414

15-
class DummyCollectionClient(CollectionBaseClient[DummyResource, DummyResourceClient]):
15+
class DummyCollectionClientBase(CollectionClientBase[DummyResource, DummyResourceClient]):
1616
_endpoint = "/api/v1/test"
1717
_resource_class = DummyResource
1818
_resource_client_class = DummyResourceClient
@@ -41,9 +41,9 @@ def http_client_async(api_url, api_token):
4141

4242
@pytest.fixture
4343
def resource_client(http_client):
44-
return DummyResourceClient(client=http_client, resource_id="RES-123")
44+
return DummyResourceClient(http_client=http_client, resource_id="RES-123")
4545

4646

4747
@pytest.fixture
48-
def collection_client(http_client) -> DummyCollectionClient:
49-
return DummyCollectionClient(client=http_client)
48+
def collection_client(http_client) -> DummyCollectionClientBase:
49+
return DummyCollectionClientBase(http_client=http_client)
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
from mpt_api_client.resources.order import OrderCollectionClient
1+
from mpt_api_client.resources.order import OrderCollectionClientBase
22

33

44
def test_order_collection_client(mpt_client):
5-
order_cc = OrderCollectionClient(client=mpt_client)
5+
order_cc = OrderCollectionClientBase(http_client=mpt_client)
66
assert order_cc.query_rql is None

0 commit comments

Comments
 (0)