Skip to content

Commit ca93d0c

Browse files
authored
MPT-14533 Create collection mixins (#96)
- Moved collection related methods from Service to CollectionMixin. - Created Resource types mixins: ModifiableResourceMixin, ManagedResourceMixin - Updated Resources to use new mixins
2 parents db7cb11 + 465827a commit ca93d0c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+1188
-1252
lines changed

mpt_api_client/http/async_service.py

Lines changed: 1 addition & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
from collections.abc import AsyncIterator
21
from urllib.parse import urljoin
32

43
from mpt_api_client.http.async_client import AsyncHTTPClient
54
from mpt_api_client.http.base_service import ServiceBase
65
from mpt_api_client.http.types import QueryParam, Response
7-
from mpt_api_client.models import Collection, ResourceData
86
from mpt_api_client.models import Model as BaseModel
7+
from mpt_api_client.models import ResourceData
98
from mpt_api_client.models.collection import ResourceList
109

1110

@@ -21,72 +20,6 @@ class AsyncService[Model: BaseModel](ServiceBase[AsyncHTTPClient, Model]): # no
2120
2221
"""
2322

24-
async def fetch_page(self, limit: int = 100, offset: int = 0) -> Collection[Model]:
25-
"""Fetch one page of resources.
26-
27-
Returns:
28-
Collection of resources.
29-
"""
30-
response = await self._fetch_page_as_response(limit=limit, offset=offset)
31-
return self._create_collection(response)
32-
33-
async def fetch_one(self) -> Model:
34-
"""Fetch one resource, expect exactly one result.
35-
36-
Returns:
37-
One resource.
38-
39-
Raises:
40-
ValueError: If the total matching records are not exactly one.
41-
"""
42-
response = await self._fetch_page_as_response(limit=1, offset=0)
43-
resource_list = self._create_collection(response)
44-
total_records = len(resource_list)
45-
if resource_list.meta:
46-
total_records = resource_list.meta.pagination.total
47-
if total_records == 0:
48-
raise ValueError("Expected one result, but got zero results")
49-
if total_records > 1:
50-
raise ValueError(f"Expected one result, but got {total_records} results")
51-
52-
return resource_list[0]
53-
54-
async def iterate(self, batch_size: int = 100) -> AsyncIterator[Model]:
55-
"""Iterate over all resources, yielding GenericResource objects.
56-
57-
Args:
58-
batch_size: Number of resources to fetch per request
59-
60-
Returns:
61-
Iterator of resources.
62-
"""
63-
offset = 0
64-
limit = batch_size # Default page size
65-
66-
while True:
67-
response = await self._fetch_page_as_response(limit=limit, offset=offset)
68-
items_collection = self._create_collection(response)
69-
for resource in items_collection:
70-
yield resource
71-
72-
if not items_collection.meta:
73-
break
74-
if not items_collection.meta.pagination.has_next():
75-
break
76-
offset = items_collection.meta.pagination.next_offset()
77-
78-
async def _fetch_page_as_response(self, limit: int = 100, offset: int = 0) -> Response:
79-
"""Fetch one page of resources.
80-
81-
Returns:
82-
Response object.
83-
84-
Raises:
85-
HTTPStatusError: if the response status code is not 200.
86-
"""
87-
pagination_params: dict[str, int] = {"limit": limit, "offset": offset}
88-
return await self.http_client.request("get", self.build_path(pagination_params))
89-
9023
async def _resource_do_request( # noqa: WPS211
9124
self,
9225
resource_id: str,

mpt_api_client/http/base_service.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
from typing import Any
22

3-
from mpt_api_client.http.mixins import QueryableMixin
43
from mpt_api_client.http.query_state import QueryState
54
from mpt_api_client.http.types import Response
65
from mpt_api_client.models import Collection, Meta
76
from mpt_api_client.models import Model as BaseModel
87

98

10-
class ServiceBase[Client, Model: BaseModel](QueryableMixin): # noqa: WPS214
9+
class ServiceBase[Client, Model: BaseModel]: # noqa: WPS214
1110
"""Service base with agnostic HTTP client."""
1211

1312
_endpoint: str
@@ -43,7 +42,12 @@ def build_path(
4342
return f"{self.path}?{query}" if query else self.path
4443

4544
@classmethod
46-
def _create_collection(cls, response: Response) -> Collection[Model]:
45+
def make_collection(cls, response: Response) -> Collection[Model]:
46+
"""Builds a collection from a response.
47+
48+
Args:
49+
response: The response object.
50+
"""
4751
meta = Meta.from_response(response)
4852
return Collection(
4953
resources=[

mpt_api_client/http/mixins.py

Lines changed: 162 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import json
2+
from collections.abc import AsyncIterator, Iterator
23
from typing import Self
34
from urllib.parse import urljoin
45

56
from mpt_api_client.http.query_state import QueryState
67
from mpt_api_client.http.types import FileTypes, Response
7-
from mpt_api_client.models import FileModel, ResourceData
8+
from mpt_api_client.models import Collection, FileModel, ResourceData
9+
from mpt_api_client.models import Model as BaseModel
810
from mpt_api_client.rql import RQLQuery
911

1012

@@ -301,3 +303,162 @@ def _create_new_instance(
301303
query_state=query_state,
302304
endpoint_params=self.endpoint_params, # type: ignore[attr-defined]
303305
)
306+
307+
308+
class CollectionMixin[Model: BaseModel](QueryableMixin):
309+
"""Mixin providing collection functionality."""
310+
311+
def fetch_page(self, limit: int = 100, offset: int = 0) -> Collection[Model]:
312+
"""Fetch one page of resources.
313+
314+
Returns:
315+
Collection of resources.
316+
"""
317+
response = self._fetch_page_as_response(limit=limit, offset=offset)
318+
return self.make_collection(response) # type: ignore[attr-defined, no-any-return]
319+
320+
def fetch_one(self) -> Model:
321+
"""Fetch one resource, expect exactly one result.
322+
323+
Returns:
324+
One resource.
325+
326+
Raises:
327+
ValueError: If the total matching records are not exactly one.
328+
"""
329+
response = self._fetch_page_as_response(limit=1, offset=0)
330+
resource_list = self.make_collection(response) # type: ignore[attr-defined]
331+
total_records = len(resource_list)
332+
if resource_list.meta:
333+
total_records = resource_list.meta.pagination.total
334+
if total_records == 0:
335+
raise ValueError("Expected one result, but got zero results")
336+
if total_records > 1:
337+
raise ValueError(f"Expected one result, but got {total_records} results")
338+
339+
return resource_list[0] # type: ignore[no-any-return]
340+
341+
def iterate(self, batch_size: int = 100) -> Iterator[Model]:
342+
"""Iterate over all resources, yielding GenericResource objects.
343+
344+
Args:
345+
batch_size: Number of resources to fetch per request
346+
347+
Returns:
348+
Iterator of resources.
349+
"""
350+
offset = 0
351+
limit = batch_size # Default page size
352+
353+
while True:
354+
response = self._fetch_page_as_response(limit=limit, offset=offset)
355+
items_collection = self.make_collection(response) # type: ignore[attr-defined]
356+
yield from items_collection
357+
358+
if not items_collection.meta:
359+
break
360+
if not items_collection.meta.pagination.has_next():
361+
break
362+
offset = items_collection.meta.pagination.next_offset()
363+
364+
def _fetch_page_as_response(self, limit: int = 100, offset: int = 0) -> Response:
365+
"""Fetch one page of resources.
366+
367+
Returns:
368+
Response object.
369+
370+
Raises:
371+
HTTPStatusError: if the response status code is not 200.
372+
"""
373+
pagination_params: dict[str, int] = {"limit": limit, "offset": offset}
374+
return self.http_client.request("get", self.build_path(pagination_params)) # type: ignore[attr-defined, no-any-return]
375+
376+
377+
class AsyncCollectionMixin[Model: BaseModel](QueryableMixin):
378+
"""Async mixin providing collection functionality."""
379+
380+
async def fetch_page(self, limit: int = 100, offset: int = 0) -> Collection[Model]:
381+
"""Fetch one page of resources.
382+
383+
Returns:
384+
Collection of resources.
385+
"""
386+
response = await self._fetch_page_as_response(limit=limit, offset=offset)
387+
return self.make_collection(response) # type: ignore[no-any-return,attr-defined]
388+
389+
async def fetch_one(self) -> Model:
390+
"""Fetch one resource, expect exactly one result.
391+
392+
Returns:
393+
One resource.
394+
395+
Raises:
396+
ValueError: If the total matching records are not exactly one.
397+
"""
398+
response = await self._fetch_page_as_response(limit=1, offset=0)
399+
resource_list = self.make_collection(response) # type: ignore[attr-defined]
400+
total_records = len(resource_list)
401+
if resource_list.meta:
402+
total_records = resource_list.meta.pagination.total
403+
if total_records == 0:
404+
raise ValueError("Expected one result, but got zero results")
405+
if total_records > 1:
406+
raise ValueError(f"Expected one result, but got {total_records} results")
407+
408+
return resource_list[0] # type: ignore[no-any-return]
409+
410+
async def iterate(self, batch_size: int = 100) -> AsyncIterator[Model]:
411+
"""Iterate over all resources, yielding GenericResource objects.
412+
413+
Args:
414+
batch_size: Number of resources to fetch per request
415+
416+
Returns:
417+
Iterator of resources.
418+
"""
419+
offset = 0
420+
limit = batch_size # Default page size
421+
422+
while True:
423+
response = await self._fetch_page_as_response(limit=limit, offset=offset)
424+
items_collection = self.make_collection(response) # type: ignore[attr-defined]
425+
for resource in items_collection:
426+
yield resource
427+
428+
if not items_collection.meta:
429+
break
430+
if not items_collection.meta.pagination.has_next():
431+
break
432+
offset = items_collection.meta.pagination.next_offset()
433+
434+
async def _fetch_page_as_response(self, limit: int = 100, offset: int = 0) -> Response:
435+
"""Fetch one page of resources.
436+
437+
Returns:
438+
Response object.
439+
440+
Raises:
441+
HTTPStatusError: if the response status code is not 200.
442+
"""
443+
pagination_params: dict[str, int] = {"limit": limit, "offset": offset}
444+
return await self.http_client.request("get", self.build_path(pagination_params)) # type: ignore[attr-defined,no-any-return]
445+
446+
447+
class ModifiableResourceMixin[Model](GetMixin[Model], UpdateMixin[Model], DeleteMixin):
448+
"""Editable resource mixin allows to read and update a resource resources."""
449+
450+
451+
class AsyncModifiableResourceMixin[Model](
452+
AsyncGetMixin[Model], AsyncUpdateMixin[Model], AsyncDeleteMixin
453+
):
454+
"""Editable resource mixin allows to read and update a resource resources."""
455+
456+
457+
class ManagedResourceMixin[Model](CreateMixin[Model], ModifiableResourceMixin[Model]):
458+
"""Managed resource mixin allows to read, create, update and delete a resource resources."""
459+
460+
461+
class AsyncManagedResourceMixin[Model](
462+
AsyncCreateMixin[Model], AsyncModifiableResourceMixin[Model]
463+
):
464+
"""Managed resource mixin allows to read, create, update and delete a resource resources."""

mpt_api_client/http/service.py

Lines changed: 1 addition & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
from collections.abc import Iterator
21
from urllib.parse import urljoin
32

43
from mpt_api_client.http.base_service import ServiceBase
54
from mpt_api_client.http.client import HTTPClient
65
from mpt_api_client.http.types import QueryParam, Response
7-
from mpt_api_client.models import Collection, ResourceData
86
from mpt_api_client.models import Model as BaseModel
7+
from mpt_api_client.models import ResourceData
98
from mpt_api_client.models.collection import ResourceList
109

1110

@@ -21,71 +20,6 @@ class Service[Model: BaseModel](ServiceBase[HTTPClient, Model]): # noqa: WPS214
2120
2221
"""
2322

24-
def fetch_page(self, limit: int = 100, offset: int = 0) -> Collection[Model]:
25-
"""Fetch one page of resources.
26-
27-
Returns:
28-
Collection of resources.
29-
"""
30-
response = self._fetch_page_as_response(limit=limit, offset=offset)
31-
return self._create_collection(response)
32-
33-
def fetch_one(self) -> Model:
34-
"""Fetch one resource, expect exactly one result.
35-
36-
Returns:
37-
One resource.
38-
39-
Raises:
40-
ValueError: If the total matching records are not exactly one.
41-
"""
42-
response = self._fetch_page_as_response(limit=1, offset=0)
43-
resource_list = self._create_collection(response)
44-
total_records = len(resource_list)
45-
if resource_list.meta:
46-
total_records = resource_list.meta.pagination.total
47-
if total_records == 0:
48-
raise ValueError("Expected one result, but got zero results")
49-
if total_records > 1:
50-
raise ValueError(f"Expected one result, but got {total_records} results")
51-
52-
return resource_list[0]
53-
54-
def iterate(self, batch_size: int = 100) -> Iterator[Model]:
55-
"""Iterate over all resources, yielding GenericResource objects.
56-
57-
Args:
58-
batch_size: Number of resources to fetch per request
59-
60-
Returns:
61-
Iterator of resources.
62-
"""
63-
offset = 0
64-
limit = batch_size # Default page size
65-
66-
while True:
67-
response = self._fetch_page_as_response(limit=limit, offset=offset)
68-
items_collection = self._create_collection(response)
69-
yield from items_collection
70-
71-
if not items_collection.meta:
72-
break
73-
if not items_collection.meta.pagination.has_next():
74-
break
75-
offset = items_collection.meta.pagination.next_offset()
76-
77-
def _fetch_page_as_response(self, limit: int = 100, offset: int = 0) -> Response:
78-
"""Fetch one page of resources.
79-
80-
Returns:
81-
Response object.
82-
83-
Raises:
84-
HTTPStatusError: if the response status code is not 200.
85-
"""
86-
pagination_params: dict[str, int] = {"limit": limit, "offset": offset}
87-
return self.http_client.request("get", self.build_path(pagination_params))
88-
8923
def _resource_do_request( # noqa: WPS211
9024
self,
9125
resource_id: str,

0 commit comments

Comments
 (0)