1# -------------------------------------------------------------------------- 2# 3# Copyright (c) Microsoft Corporation. All rights reserved. 4# 5# The MIT License (MIT) 6# 7# Permission is hereby granted, free of charge, to any person obtaining a copy 8# of this software and associated documentation files (the ""Software""), to 9# deal in the Software without restriction, including without limitation the 10# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11# sell copies of the Software, and to permit persons to whom the Software is 12# furnished to do so, subject to the following conditions: 13# 14# The above copyright notice and this permission notice shall be included in 15# all copies or substantial portions of the Software. 16# 17# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23# IN THE SOFTWARE. 24# 25# -------------------------------------------------------------------------- 26import sys 27try: 28 from collections.abc import Iterator 29 xrange = range 30except ImportError: 31 from collections import Iterator 32 33from typing import Dict, Any, List, Callable, Optional, TYPE_CHECKING # pylint: disable=unused-import 34 35from .serialization import Deserializer 36from .pipeline import ClientRawResponse 37 38if TYPE_CHECKING: 39 from .universal_http import ClientResponse # pylint: disable=unused-import 40 from .serialization import Model # pylint: disable=unused-import 41 42if sys.version_info >= (3, 5, 2): 43 # Not executed on old Python, no syntax error 44 from .async_paging import AsyncPagedMixin # type: ignore 45else: 46 class AsyncPagedMixin(object): # type: ignore 47 pass 48 49class Paged(AsyncPagedMixin, Iterator): 50 """A container for paged REST responses. 51 52 :param ClientResponse response: server response object. 53 :param callable command: Function to retrieve the next page of items. 54 :param dict classes: A dictionary of class dependencies for 55 deserialization. 56 :param dict raw_headers: A dict of raw headers to add if "raw" is called 57 """ 58 _validation = {} # type: Dict[str, Dict[str, Any]] 59 _attribute_map = {} # type: Dict[str, Dict[str, Any]] 60 61 def __init__(self, command, classes, raw_headers=None, **kwargs): 62 # type: (Callable[[str], ClientResponse], Dict[str, Model], Dict[str, str], Any) -> None 63 super(Paged, self).__init__(**kwargs) # type: ignore 64 # Sets next_link, current_page, and _current_page_iter_index. 65 self.next_link = "" 66 self._current_page_iter_index = 0 67 self.reset() 68 self._derserializer = Deserializer(classes) 69 self._get_next = command 70 self._response = None # type: Optional[ClientResponse] 71 self._raw_headers = raw_headers 72 73 def __iter__(self): 74 """Return 'self'.""" 75 # Since iteration mutates this object, consider it an iterator in-and-of 76 # itself. 77 return self 78 79 @classmethod 80 def _get_subtype_map(cls): 81 """Required for parity to Model object for deserialization.""" 82 return {} 83 84 @property 85 def raw(self): 86 # type: () -> ClientRawResponse 87 """Get current page as ClientRawResponse. 88 89 :rtype: ClientRawResponse 90 """ 91 raw = ClientRawResponse(self.current_page, self._response) 92 if self._raw_headers: 93 raw.add_headers(self._raw_headers) 94 return raw 95 96 def get(self, url): 97 # type: (str) -> List[Model] 98 """Get an arbitrary page. 99 100 This resets the iterator and then fully consumes it to return the 101 specific page **only**. 102 103 :param str url: URL to arbitrary page results. 104 """ 105 self.reset() 106 self.next_link = url 107 return self.advance_page() 108 109 def reset(self): 110 # type: () -> None 111 """Reset iterator to first page.""" 112 self.next_link = "" 113 self.current_page = [] # type: List[Model] 114 self._current_page_iter_index = 0 115 116 def advance_page(self): 117 # type: () -> List[Model] 118 """Force moving the cursor to the next azure call. 119 120 This method is for advanced usage, iterator protocol is prefered. 121 122 :raises: StopIteration if no further page 123 :return: The current page list 124 :rtype: list 125 """ 126 if self.next_link is None: 127 raise StopIteration("End of paging") 128 self._current_page_iter_index = 0 129 self._response = self._get_next(self.next_link) 130 self._derserializer(self, self._response) 131 return self.current_page 132 133 def __next__(self): 134 """Iterate through responses.""" 135 # Storing the list iterator might work out better, but there's no 136 # guarantee that some code won't replace the list entirely with a copy, 137 # invalidating an list iterator that might be saved between iterations. 138 if self.current_page and self._current_page_iter_index < len(self.current_page): 139 response = self.current_page[self._current_page_iter_index] 140 self._current_page_iter_index += 1 141 return response 142 else: 143 self.advance_page() 144 return self.__next__() 145 146 next = __next__ # Python 2 compatibility. 147