33
44from httpx import Response
55
6- from mpt_api_client .http .client import HTTPClient
6+ from mpt_api_client .http .client import HTTPClient , HTTPClientAsync
77from mpt_api_client .models import Resource
88
99
10- class ResourceBaseClient [ ResourceModel : Resource ]( ABC ): # noqa: WPS214
11- """Client for RESTful resources ."""
10+ class ResourceMixin :
11+ """Mixin for resource clients ."""
1212
1313 _endpoint : str
14- _resource_class : type [ResourceModel ]
14+ _resource_class : type [Any ]
1515 _safe_attributes : ClassVar [set [str ]] = {"http_client_" , "resource_id_" , "resource_" }
1616
17- def __init__ (self , http_client : HTTPClient , resource_id : str ) -> None :
18- self .http_client_ = http_client # noqa: WPS120
19- self .resource_id_ = resource_id # noqa: WPS120
20- self .resource_ : Resource | None = None # noqa: WPS120
17+ def __init__ (
18+ self , http_client : HTTPClient | HTTPClientAsync , resource_id : str , resource : Resource | None
19+ ) -> None :
20+ self .http_client_ = http_client
21+ self .resource_id_ = resource_id
22+ self .resource_ : Resource | None = resource
2123
2224 def __getattr__ (self , attribute : str ) -> Any :
2325 """Returns the resource data."""
24- self ._ensure_resource_is_fetched ()
26+ self ._assert_resource_is_set ()
2527 return self .resource_ .__getattr__ (attribute ) # type: ignore[union-attr]
2628
2729 @property
@@ -34,9 +36,32 @@ def __setattr__(self, attribute: str, attribute_value: Any) -> None:
3436 if attribute in self ._safe_attributes :
3537 object .__setattr__ (self , attribute , attribute_value )
3638 return
37- self ._ensure_resource_is_fetched ()
39+ self ._assert_resource_is_set ()
3840 self .resource_ .__setattr__ (attribute , attribute_value )
3941
42+ def _assert_resource_is_set (self ) -> None :
43+ if not self .resource_ :
44+ raise RuntimeError (
45+ f"Resource data not available. Call fetch() method first to retrieve" # noqa: WPS237
46+ f" the resource `{ self ._resource_class .__name__ } `"
47+ )
48+
49+
50+ class ResourceBaseClient [ResourceModel : Resource ](ABC , ResourceMixin ): # noqa: WPS214
51+ """Client for RESTful resources."""
52+
53+ _endpoint : str
54+ _resource_class : type [ResourceModel ]
55+ _safe_attributes : ClassVar [set [str ]] = {"http_client_" , "resource_id_" , "resource_" }
56+
57+ def __init__ (
58+ self , http_client : HTTPClient , resource_id : str , resource : Resource | None = None
59+ ) -> None :
60+ self .http_client_ : HTTPClient = http_client # type: ignore[mutable-override]
61+ ResourceMixin .__init__ (
62+ self , http_client = http_client , resource_id = resource_id , resource = resource
63+ )
64+
4065 def fetch (self ) -> ResourceModel :
4166 """Fetch a specific resource using `GET /endpoint/{resource_id}`.
4267
@@ -47,7 +72,7 @@ def fetch(self) -> ResourceModel:
4772 """
4873 response = self .do_action ("GET" )
4974
50- self .resource_ = self ._resource_class .from_response (response ) # noqa: WPS120
75+ self .resource_ = self ._resource_class .from_response (response )
5176 return self .resource_
5277
5378 def resource_action (
@@ -58,7 +83,7 @@ def resource_action(
5883 ) -> ResourceModel :
5984 """Perform an action on a specific resource using `HTTP_METHOD /endpoint/{resource_id}`."""
6085 response = self .do_action (method , url , json = json )
61- self .resource_ = self ._resource_class .from_response (response ) # noqa: WPS120
86+ self .resource_ = self ._resource_class .from_response (response )
6287 return self .resource_
6388
6489 def do_action (
@@ -97,7 +122,7 @@ def update(self, resource_data: dict[str, Any]) -> ResourceModel:
97122
98123 """
99124 response = self .do_action ("PUT" , json = resource_data )
100- self .resource_ = self ._resource_class .from_response (response ) # noqa: WPS120
125+ self .resource_ = self ._resource_class .from_response (response )
101126 return self .resource_
102127
103128 def save (self ) -> Self :
@@ -111,9 +136,8 @@ def save(self) -> Self:
111136 contact.save()
112137
113138 """
114- if not self .resource_ :
115- raise ValueError ("Unable to save resource that has not been set." )
116- self .update (self .resource_ .to_dict ())
139+ self ._assert_resource_is_set ()
140+ self .update (self .resource_ .to_dict ()) # type: ignore[union-attr]
117141 return self
118142
119143 def delete (self ) -> None :
@@ -128,8 +152,110 @@ def delete(self) -> None:
128152 response = self .do_action ("DELETE" )
129153 response .raise_for_status ()
130154
131- self .resource_ = None # noqa: WPS120
155+ self .resource_ = None
132156
133- def _ensure_resource_is_fetched (self ) -> None :
134- if not self .resource_ :
135- self .fetch ()
157+
158+ class AsyncResourceBaseClient [ResourceModel : Resource ](ABC , ResourceMixin ): # noqa: WPS214
159+ """Client for RESTful resources."""
160+
161+ _endpoint : str
162+ _resource_class : type [ResourceModel ]
163+ _safe_attributes : ClassVar [set [str ]] = {"http_client_" , "resource_id_" , "resource_" }
164+
165+ def __init__ (
166+ self , http_client : HTTPClientAsync , resource_id : str , resource : Resource | None = None
167+ ) -> None :
168+ self .http_client_ : HTTPClientAsync = http_client # type: ignore[mutable-override]
169+ ResourceMixin .__init__ (
170+ self , http_client = http_client , resource_id = resource_id , resource = resource
171+ )
172+
173+ async def fetch (self ) -> ResourceModel :
174+ """Fetch a specific resource using `GET /endpoint/{resource_id}`.
175+
176+ It fetches and caches the resource.
177+
178+ Returns:
179+ The fetched resource.
180+ """
181+ response = await self .do_action ("GET" )
182+
183+ self .resource_ = self ._resource_class .from_response (response )
184+ return self .resource_
185+
186+ async def resource_action (
187+ self ,
188+ method : str = "GET" ,
189+ url : str | None = None ,
190+ json : dict [str , Any ] | list [Any ] | None = None , # noqa: WPS221
191+ ) -> ResourceModel :
192+ """Perform an action on a specific resource using `HTTP_METHOD /endpoint/{resource_id}`."""
193+ response = await self .do_action (method , url , json = json )
194+ self .resource_ = self ._resource_class .from_response (response )
195+ return self .resource_
196+
197+ async def do_action (
198+ self ,
199+ method : str = "GET" ,
200+ url : str | None = None ,
201+ json : dict [str , Any ] | list [Any ] | None = None , # noqa: WPS221
202+ ) -> Response :
203+ """Perform an action on a specific resource using `HTTP_METHOD /endpoint/{resource_id}`.
204+
205+ Args:
206+ method: The HTTP method to use.
207+ url: The action name to use.
208+ json: The updated resource data.
209+
210+ Raises:
211+ HTTPError: If the action fails.
212+ """
213+ url = f"{ self .resource_url } /{ url } " if url else self .resource_url
214+ response = await self .http_client_ .request (method , url , json = json )
215+ response .raise_for_status ()
216+ return response
217+
218+ async def update (self , resource_data : dict [str , Any ]) -> ResourceModel :
219+ """Update a specific in the API and catches the result as a current resource.
220+
221+ Args:
222+ resource_data: The updated resource data.
223+
224+ Returns:
225+ The updated resource.
226+
227+ Examples:
228+ updated_contact = contact.update({"name": "New Name"})
229+
230+
231+ """
232+ return await self .resource_action ("PUT" , json = resource_data )
233+
234+ async def save (self ) -> Self :
235+ """Save the current state of the resource to the api using the update method.
236+
237+ Raises:
238+ ValueError: If the resource has not been set.
239+
240+ Examples:
241+ contact.name = "New Name"
242+ contact.save()
243+
244+ """
245+ self ._assert_resource_is_set ()
246+ await self .update (self .resource_ .to_dict ()) # type: ignore[union-attr]
247+ return self
248+
249+ async def delete (self ) -> None :
250+ """Delete the resource using `DELETE /endpoint/{resource_id}`.
251+
252+ Raises:
253+ HTTPStatusError: If the deletion fails.
254+
255+ Examples:
256+ contact.delete()
257+ """
258+ response = await self .do_action ("DELETE" )
259+ response .raise_for_status ()
260+
261+ self .resource_ = None
0 commit comments