-
Notifications
You must be signed in to change notification settings - Fork 44
Add Page Iterator support #479
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
76 commits
Select commit
Hold shift + click to select a range
d292cbf
Initialize page_result class
shemogumbe 15664cd
add odata_next_link and value properties
shemogumbe 4bb29cf
add setters for value and odata_next_link
shemogumbe 257978d
add create_from discriminator_value and get_field_deserializer methods
shemogumbe fb9c548
added get_field_deserializers and serialize method
shemogumbe b5689ad
expose PageResult to be able to be imported
shemogumbe 2395bed
Add Page Iterator
shemogumbe c9bbff6
added fetch next link
shemogumbe c04af55
added next method to the page Iterator
shemogumbe d139e0b
added convert_to_page method of the Page Iterator
shemogumbe b7c66e6
Page iterator draft
shemogumbe c2a3ddd
Use request information, Parsable, headers, request adaoter and method
shemogumbe 3163da1
draft test for page_result model
shemogumbe 401b1ea
Fix set headers
shemogumbe 4a6d391
remove as next method in favor of has next attribute
shemogumbe 64e6a89
fix fetch next page
shemogumbe 86be040
format code
shemogumbe e3ff67a
remove env files
shemogumbe 9e17e46
remove headers collection import
shemogumbe 4e2fe4f
format code with yapf
shemogumbe fd532cb
fix linting issus on page iterator
shemogumbe 014146f
debug fetch naxt page
shemogumbe b76ae84
Fixe fetch next page
shemogumbe b54e681
Fix keep iterating and enumerate
shemogumbe d28c10d
code clean up
shemogumbe 7e1a960
fix typing errors
shemogumbe 0e0c802
fix typing issues
shemogumbe 88aca4e
fix typing issues on page iterator
shemogumbe cfb8458
update types in page iterator
shemogumbe 6b3d311
type check fixes for page iterator
shemogumbe 40eb018
Fix list assignment in page iterator
shemogumbe e3f4677
Fix list assignment in page iterator
shemogumbe 14ae080
Fix list assignment in page iterator
shemogumbe e2737e1
Fix list assignment in page iterator
shemogumbe b40ff3a
Fix list assignment in page iterator
shemogumbe 3b20e13
Fix list assignment in page iterator
shemogumbe 8f87d7f
Fix type checking for page iterator
shemogumbe 4f2677b
Fix type checking for page iterator
shemogumbe 1403cf7
add class and method docstrings
shemogumbe 5f59f94
fix linting issues
shemogumbe 2f49172
fix linting issues
shemogumbe fc3755e
fix linting issues
shemogumbe c66ce25
fix failing tests
shemogumbe 6ea49b2
fix failing tests
shemogumbe f5b5682
Fix unit tests
shemogumbe eb0d3a1
fix types for page result
shemogumbe 790ebee
fix import errors
shemogumbe c292706
Add auth credentials as secrets
shemogumbe 7b78122
remove debugging print statements
shemogumbe ebc53ee
remove debugging print statements
shemogumbe a0ef95c
Change value to generic type
shemogumbe 9957f03
remove set pause index
shemogumbe 4a13e52
add next link and delta link attributes to page iterator
shemogumbe 219bcef
add next link and delta link attributes to page iterator
shemogumbe 5ca5ed3
add bounds to T
shemogumbe 2f33739
add type annotations to page
shemogumbe 50b245c
add type annotations to page
shemogumbe 58ddea7
fixing linting issues
shemogumbe 4673ead
use type annotations for page result
shemogumbe 8e3d5f3
corect create from descriminator method call
shemogumbe 48e413f
return a headers collection object in set_headers
shemogumbe 0b8c58e
add null check for current page
shemogumbe 884fca4
convert PageResult to dataclass
shemogumbe d210000
add odata_delta link for last page of next link
shemogumbe 8a84a89
remove hard coded imports
shemogumbe 4db8354
remove multiple type-ignore for imports
shemogumbe 132097f
remove multiple type-ignore for imports
shemogumbe 1d61709
add type requests to dev dependencies
shemogumbe d66655f
Merge branch 'main' into shem/add-page-iterator
shemogumbe 0b8c586
remove set_value
shemogumbe 13e3878
Merge branch 'shem/add-page-iterator' of github.com:microsoftgraph/ms…
shemogumbe 2202c23
instantiate data class with data points
shemogumbe 0820ca1
Fix tests for changes on data class
shemogumbe 5165ecd
test pagination and iteration
shemogumbe e1d6313
added type hint in next method
shemogumbe 0eee03e
ignore type checking for page creation at next
shemogumbe File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"editor.formatOnSave": true | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from .page_result import PageResult | ||
|
||
__all__ = ['PageResult'] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
""" | ||
This module defines the PageResult class which represents a page of | ||
items in a paged response. | ||
|
||
The PageResult class provides methods to get and set the next link and | ||
the items in the page, create a PageResult from a discriminator value, set | ||
the value, get the field deserializers, and serialize the PageResult. | ||
|
||
Classes: | ||
PageResult: Represents a page of items in a paged response. | ||
""" | ||
from __future__ import annotations | ||
from typing import List, Optional, Dict, Callable | ||
from dataclasses import dataclass | ||
|
||
from kiota_abstractions.serialization.parsable import Parsable | ||
from kiota_abstractions.serialization.serialization_writer \ | ||
import SerializationWriter | ||
from kiota_abstractions.serialization.parse_node import ParseNode | ||
from typing import TypeVar, List, Optional | ||
|
||
T = TypeVar('T') | ||
|
||
|
||
@dataclass | ||
class PageResult(Parsable): | ||
odata_next_link: Optional[str] = None | ||
value: Optional[List[Parsable]] = None | ||
|
||
@staticmethod | ||
def create_from_discriminator_value(parse_node: Optional[ParseNode] = None) -> PageResult: | ||
""" | ||
Creates a new instance of the appropriate class based on discriminator value | ||
Args: | ||
parseNode: The parse node to use to read the discriminator value and create the object | ||
Returns: Attachment | ||
""" | ||
if not parse_node: | ||
raise TypeError("parse_node cannot be null") | ||
return PageResult() | ||
|
||
def get_field_deserializers(self) -> Dict[str, Callable[[ParseNode], None]]: | ||
"""Gets the deserialization information for this object. | ||
|
||
Returns: | ||
Dict[str, Callable[[ParseNode], None]]: The deserialization information for this | ||
object where each entry is a property key with its deserialization callback. | ||
""" | ||
return { | ||
"@odata.nextLink": lambda x: setattr(self, "odata_next_link", x.get_str_value()), | ||
"value": lambda x: setattr(self, "value", x.get_collection_of_object_values(Parsable)) | ||
} | ||
|
||
def serialize(self, writer: SerializationWriter) -> None: | ||
"""Writes the objects properties to the current writer. | ||
|
||
Args: | ||
writer (SerializationWriter): The writer to write to. | ||
""" | ||
if not writer: | ||
raise TypeError("Writer cannot be null") | ||
writer.write_str_value("@odata.nextLink", self.odata_next_link) | ||
writer.write_collection_of_object_values("value", self.value) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .page_iterator import PageIterator |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
""" | ||
This module contains the PageIterator class which is used to | ||
iterate over paged responses from a server. | ||
|
||
The PageIterator class provides methods to iterate over the items | ||
in the pages, fetch the next page, convert a response to a page, and | ||
fetch the next page from the server. | ||
|
||
The PageIterator class uses the Parsable interface to parse the responses | ||
from the server, the HttpxRequestAdapter class to send requests to the | ||
server, and the PageResult class to represent the pages. | ||
|
||
This module also imports the necessary types and exceptions from the | ||
typing, requests.exceptions, kiota_http.httpx_request_adapter, | ||
kiota_abstractions.method, kiota_abstractions.headers_collection, | ||
kiota_abstractions.request_information, kiota_abstractions.serialization.parsable, | ||
and models modules. | ||
""" | ||
|
||
from typing import Callable, Optional, Union, Dict, List | ||
|
||
from typing import TypeVar | ||
from requests.exceptions import InvalidURL | ||
|
||
from kiota_http.httpx_request_adapter import HttpxRequestAdapter | ||
from kiota_abstractions.method import Method | ||
from kiota_abstractions.headers_collection import HeadersCollection | ||
from kiota_abstractions.request_information import RequestInformation | ||
from kiota_abstractions.serialization.parsable import Parsable | ||
|
||
from msgraph_core.models.page_result import PageResult # pylint: disable=no-name-in-module, import-error | ||
|
||
T = TypeVar('T', bound=Parsable) | ||
|
||
|
||
class PageIterator: | ||
""" | ||
This class is used to iterate over paged responses from a server. | ||
|
||
The PageIterator class provides methods to iterate over the items in the pages, | ||
fetch the next page, and convert a response to a page. | ||
|
||
Attributes: | ||
request_adapter (HttpxRequestAdapter): The adapter used to send HTTP requests. | ||
pause_index (int): The index at which to pause iteration. | ||
headers (HeadersCollection): The headers to include in the HTTP requests. | ||
request_options (list): The options for the HTTP requests. | ||
current_page (PageResult): The current page of items. | ||
object_type (str): The type of the items in the pages. | ||
has_next (bool): Whether there are more pages to fetch. | ||
|
||
Methods: | ||
__init__(response: Union[T, list, object], request_adapter: HttpxRequestAdapter, | ||
constructor_callable: Optional[Callable] = None): Initializes a new instance of | ||
the PageIterator class. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
response: Union[T, list, object], | ||
request_adapter: HttpxRequestAdapter, | ||
constructor_callable: Optional[Callable] = None | ||
): | ||
self.request_adapter = request_adapter | ||
|
||
if isinstance(response, Parsable) and not constructor_callable: | ||
parsable_factory = type(response) | ||
elif constructor_callable is None: | ||
parsable_factory = PageResult | ||
self.parsable_factory = parsable_factory | ||
self.pause_index = 0 | ||
self.headers: HeadersCollection = HeadersCollection() | ||
self.request_options = [] # type: ignore | ||
self.current_page = self.convert_to_page(response) | ||
self.object_type = self.current_page.value[ | ||
0].__class__.__name__ if self.current_page.value else None | ||
page = self.current_page | ||
self._next_link = response.get('odata_next_link', '') if isinstance( | ||
response, dict | ||
) else getattr(response, 'odata_next_link', '') | ||
self._delta_link = response.get('@odata.deltaLink', '') if isinstance( | ||
response, dict | ||
) else getattr(response, '@odata.deltaLink', '') | ||
|
||
if page is not None: | ||
self.current_page = page | ||
self.has_next = bool(page.odata_next_link) | ||
|
||
def set_headers(self, headers: dict) -> HeadersCollection: | ||
""" | ||
Sets the headers for the HTTP requests. | ||
This method takes a dictionary of headers and adds them to the | ||
existing headers. | ||
Args: | ||
headers (dict): A dictionary of headers to add. The keys are the | ||
header names and the values are the header values. | ||
""" | ||
self.headers.add_all(**headers) | ||
|
||
@property | ||
def delta_link(self): | ||
return self._delta_link | ||
|
||
@property | ||
def next_link(self): | ||
return self._next_link | ||
|
||
def set_request_options(self, request_options: list) -> None: | ||
""" | ||
Sets the request options for the HTTP requests. | ||
Args: | ||
request_options (list): The request options to set. | ||
""" | ||
self.request_options = request_options | ||
|
||
async def iterate(self, callback: Callable) -> None: | ||
""" | ||
Iterates over the pages and applies a callback function to each item. | ||
The iteration stops when there are no more pages or the callback | ||
function returns False. | ||
Args: | ||
callback (Callable): The function to apply to each item. | ||
It should take one argument (the item) and return a boolean. | ||
""" | ||
while True: | ||
keep_iterating = self.enumerate(callback) | ||
if not keep_iterating: | ||
return | ||
next_page = await self.next() | ||
if not next_page: | ||
shemogumbe marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return | ||
self.current_page = next_page | ||
self.pause_index = 0 | ||
|
||
async def next(self) -> Optional[PageResult]: | ||
""" | ||
Fetches the next page of items. | ||
Returns: | ||
dict: The next page of items, or None if there are no more pages. | ||
""" | ||
if self.current_page is not None and not self.current_page.odata_next_link: | ||
return None | ||
response = await self.fetch_next_page() | ||
print(f"Response - {type(response)}") | ||
page: PageResult = PageResult(response.odata_next_link, response.value) # type: ignore | ||
return page | ||
|
||
@staticmethod | ||
def convert_to_page(response: Union[T, list, object]) -> PageResult: | ||
""" | ||
Converts a response to a PageResult. | ||
This method extracts the 'value' and 'odata_next_link' from the | ||
response and uses them to create a PageResult. | ||
Args: | ||
response (Union[T, list, object]): The response to convert. It can | ||
be a list, an object, or any other type. | ||
Returns: | ||
PageResult: The PageResult created from the response. | ||
Raises: | ||
ValueError: If the response is None or does not contain a 'value'. | ||
""" | ||
if not response: | ||
raise ValueError('Response cannot be null.') | ||
value = None | ||
if isinstance(response, list): | ||
value = response.value # type: ignore | ||
elif hasattr(response, 'value'): | ||
value = getattr(response, 'value') | ||
elif isinstance(response, object): | ||
value = getattr(response, 'value', []) | ||
if value is None: | ||
raise ValueError('The response does not contain a value.') | ||
parsable_page = response if isinstance(response, dict) else vars(response) | ||
next_link = parsable_page.get('odata_next_link', '') if isinstance( | ||
parsable_page, dict | ||
) else getattr(parsable_page, 'odata_next_link', '') | ||
|
||
page: PageResult = PageResult(next_link, value) | ||
return page | ||
|
||
async def fetch_next_page(self) -> List[Parsable]: | ||
""" | ||
Fetches the next page of items from the server. | ||
Returns: | ||
dict: The response from the server. | ||
Raises: | ||
ValueError: If the current page does not contain a next link. | ||
InvalidURL: If the next link URL could not be parsed. | ||
""" | ||
|
||
next_link = self.current_page.odata_next_link | ||
if not next_link: | ||
raise ValueError('The response does not contain a nextLink.') | ||
if not next_link.startswith('http'): | ||
raise InvalidURL('Could not parse nextLink URL.') | ||
request_info = RequestInformation() | ||
request_info.http_method = Method.GET | ||
request_info.url = next_link | ||
request_info.headers = self.headers | ||
if self.request_options: | ||
request_info.add_request_options(*self.request_options) | ||
error_map: Dict[str, int] = {} | ||
response = await self.request_adapter.send_async( | ||
request_info, self.parsable_factory, error_map | ||
) | ||
return response | ||
|
||
def enumerate(self, callback: Optional[Callable] = None) -> bool: | ||
""" | ||
Enumerates over the items in the current page and applies a | ||
callback function to each item. | ||
Args: | ||
callback (Callable, optional): The function to apply to each item. | ||
It should take one argument (the item) and return a boolean. | ||
Returns: | ||
bool: False if there are no items in the current page or the | ||
callback function returns False, True otherwise. | ||
""" | ||
keep_iterating = True | ||
page_items = self.current_page.value | ||
if not page_items: | ||
return False | ||
for i in range(self.pause_index, len(page_items)): | ||
keep_iterating = callback(page_items[i]) if callback is not None else True | ||
if not keep_iterating: | ||
self.pause_index = i + 1 | ||
break | ||
return keep_iterating |
Empty file.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.