Skip to content

Commit

Permalink
fix: handle 0 item response in querysets
Browse files Browse the repository at this point in the history
A flaw in the __iter__ logic introduced to handle scenarios
where a pagination element is not included in the response xml
resulted in an infinite loop. This PR introduces a few changes
to protect against this:

1. After running QuerySet._fetch_all(), if the result_cache is
empty, return instead of performing other comparisons.
2. Ensure that any non-None total_available is returned from
the PaginationItem's object.
3. In _fetch_all, check if there is a PaginationItem that has been
populated so as to not call the server side endpoint muliple times
before returning.
  • Loading branch information
jorwoods committed Oct 15, 2024
1 parent 9f59af1 commit b0da014
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 2 deletions.
6 changes: 4 additions & 2 deletions tableauserverclient/server/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ def __iter__(self: Self) -> Iterator[T]:
# up overrunning the total number of pages. Catch the
# error and break out of the loop.
raise StopIteration
if len(self._result_cache) == 0:
return
yield from self._result_cache
# If the length of the QuerySet is unknown, continue fetching until
# the result cache is empty.
Expand Down Expand Up @@ -150,7 +152,7 @@ def _fetch_all(self: Self) -> None:
"""
Retrieve the data and store result and pagination item in cache
"""
if not self._result_cache:
if not self._result_cache and self._pagination_item._page_number is None:
response = self.model.get(self.request_options)
if isinstance(response, tuple):
self._result_cache, self._pagination_item = response
Expand All @@ -159,7 +161,7 @@ def _fetch_all(self: Self) -> None:
self._pagination_item = PaginationItem()

def __len__(self: Self) -> int:
return self.total_available or sys.maxsize
return sys.maxsize if self.total_available is None else self.total_available

@property
def total_available(self: Self) -> int:
Expand Down
12 changes: 12 additions & 0 deletions test/test_pager.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import contextlib
import os
import unittest
import xml.etree.ElementTree as ET

import requests_mock

Expand Down Expand Up @@ -122,3 +123,14 @@ def test_pager_view(self) -> None:
m.get(self.server.views.baseurl, text=view_xml)
for view in TSC.Pager(self.server.views):
assert view.name is not None

def test_queryset_no_matches(self) -> None:
elem = ET.Element("tsResponse", xmlns="http://tableau.com/api")
ET.SubElement(elem, "pagination", totalAvailable="0")
ET.SubElement(elem, "groups")
xml = ET.tostring(elem).decode("utf-8")
with requests_mock.mock() as m:
m.get(self.server.groups.baseurl, text=xml)
all_groups = self.server.groups.all()
groups = list(all_groups)
assert len(groups) == 0

0 comments on commit b0da014

Please sign in to comment.