|
| 1 | +from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Type |
| 2 | + |
1 | 3 | from .base import ObjectBase |
2 | 4 |
|
| 5 | +if TYPE_CHECKING: |
| 6 | + from ..client import Client |
| 7 | + from ..resources.base import ResourceBase |
| 8 | + |
3 | 9 |
|
4 | 10 | class UnknownObject(ObjectBase): |
5 | 11 | """Mock object for empty lists.""" |
@@ -97,3 +103,87 @@ def get_previous(self): |
97 | 103 | resource = self.object_type.get_resource_class(self.client) |
98 | 104 | resp = resource.perform_api_call(resource.REST_READ, url) |
99 | 105 | return ObjectList(resp, self.object_type, self.client) |
| 106 | + |
| 107 | + |
| 108 | +class ResultListIterator: |
| 109 | + """ |
| 110 | + An iterator for result lists from the API. |
| 111 | +
|
| 112 | + You can iterate through the results. If the initial result indocates pagination, |
| 113 | + a new result page is automatically fetched from the API when the current result page |
| 114 | + is exhausted. |
| 115 | +
|
| 116 | + Note: This iterator should preferably replace the ObjectList as the default |
| 117 | + return value for the Resource.list() method in the future. |
| 118 | + """ |
| 119 | + |
| 120 | + _last: int |
| 121 | + _client: "Client" |
| 122 | + next_uri: str |
| 123 | + list_data: List[Dict[str, Any]] |
| 124 | + result_class: Type[ObjectBase] |
| 125 | + resource_class: Type["ResourceBase"] |
| 126 | + |
| 127 | + def __init__( |
| 128 | + self, |
| 129 | + client: "Client", |
| 130 | + data: Dict[str, Any], |
| 131 | + resource_class: Type["ResourceBase"], |
| 132 | + ) -> None: |
| 133 | + self._client = client |
| 134 | + self.resource_class = resource_class |
| 135 | + |
| 136 | + # Next line is a bit klunky |
| 137 | + self.result_class = self.resource_class(self._client).get_resource_object({}).__class__ |
| 138 | + self.list_data, self.next_uri = self._parse_data(data) |
| 139 | + |
| 140 | + self._last = -1 |
| 141 | + |
| 142 | + def __iter__(self): |
| 143 | + """Return the iterator.""" |
| 144 | + return self |
| 145 | + |
| 146 | + def __next__(self) -> ObjectBase: |
| 147 | + """ |
| 148 | + Return the next result. |
| 149 | +
|
| 150 | + If the result data is exhausted, but a link to further paginated results |
| 151 | + is available, we fetch that and return the first result of that. |
| 152 | + """ |
| 153 | + current = self._last + 1 |
| 154 | + try: |
| 155 | + object_data = self.list_data[current] |
| 156 | + self._last = current |
| 157 | + except IndexError: |
| 158 | + if self.next_uri: |
| 159 | + self._reinit_from_uri(self.next_uri) |
| 160 | + return next(self) |
| 161 | + else: |
| 162 | + raise StopIteration |
| 163 | + |
| 164 | + return self.result_class(object_data, self._client) |
| 165 | + |
| 166 | + def _parse_data(self, data: Dict[str, Any]) -> Tuple[List[Dict[str, Any]], str]: |
| 167 | + """ |
| 168 | + Extract useful data from the payload. |
| 169 | +
|
| 170 | + We are interested in the following parts: |
| 171 | + - the actual list data, unwrapped |
| 172 | + - links to next results, when results are paginated |
| 173 | + """ |
| 174 | + try: |
| 175 | + next_uri = data["_links"]["next"]["href"] |
| 176 | + except TypeError: |
| 177 | + next_uri = "" |
| 178 | + |
| 179 | + resource_name = self.result_class.get_object_name() |
| 180 | + list_data = data["_embedded"][resource_name] |
| 181 | + |
| 182 | + return list_data, next_uri |
| 183 | + |
| 184 | + def _reinit_from_uri(self, uri: str) -> None: |
| 185 | + """Fetch additional results from the API, and feed the iterator with the data.""" |
| 186 | + |
| 187 | + result = self.resource_class(self._client).perform_api_call(self.resource_class.REST_READ, uri) |
| 188 | + self.list_data, self.next_uri = self._parse_data(result) |
| 189 | + self._last = -1 |
0 commit comments