8000 Type hint views by jorwoods · Pull Request #949 · tableau/server-client-python · GitHub
[go: up one dir, main page]

Skip to content

Type hint views #949

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 6 commits into from
Jan 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 41 additions & 35 deletions tableauserverclient/models/view_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,32 @@
from .tag_item import TagItem
import copy

from typing import ByteString, Callable, Iterable, List, Optional, Set, TYPE_CHECKING

if TYPE_CHECKING:
from datetime import datetime
from .permissions_item import PermissionsRule


class ViewItem(object):
def __init__(self):
self._content_url = None
self._created_at = None
self._id = None
self._image = None
self._initial_tags = set()
self._name = None
self._owner_id = None
self._preview_image = None
self._project_id = None
self._pdf = None
self._csv = None
self._total_views = None
self._sheet_type = None
self._updated_at = None
self._workbook_id = None
self._permissions = None
self.tags = set()
def __init__(self) -> None:
self._content_url: Optional[str] = None
self._created_at: Optional["datetime"] = None
self._id: Optional[str] = None
self._image: Optional[Callable[[], bytes]] = None
self._initial_tags: Set[str] = set()
self._name: Optional[str] = None
self._owner_id: Optional[str] = None
self._preview_image: Optional[Callable[[], bytes]] = None
self._project_id: Optional[str] = None
self._pdf: Optional[Callable[[], bytes]] = None
self._csv: Optional[Callable[[], Iterable[bytes]]] = None
self._total_views: Optional[int] = None
self._sheet_type: Optional[str] = None
self._updated_at: Optional["datetime"] = None
self._workbook_id: Optional[str] = None
self._permissions: Optional[Callable[[], List["PermissionsRule"]]] = None
self.tags: Set[str] = set()

def _set_preview_image(self, preview_image):
self._preview_image = preview_image
Expand All @@ -38,59 +44,59 @@ def _set_csv(self, csv):
self._csv = csv

@property
def content_url(self):
def content_url(self) -> Optional[str]:
return self._content_url

@property
def created_at(self):
def created_at(self) -> Optional["datetime"]:
return self._created_at

@property
def id(self):
def id(self) -> Optional[str]:
return self._id

@property
def image(self):
def image(self) -> bytes:
if self._image is None:
error = "View item must be populated with its png image first."
raise UnpopulatedPropertyError(error)
return self._image()

@property
def name(self):
def name(self) -> Optional[str]:
return self._name

@property
def owner_id(self):
def owner_id(self) -> Optional[str]:
return self._owner_id

@property
def preview_image(self):
def preview_image(self) -> bytes:
if self._preview_image is None:
error = "View item must be populated with its preview image first."
raise UnpopulatedPropertyError(error)
return self._preview_image()

@property
def project_id(self):
def project_id(self) -> Optional[str]:
return self._project_id

@property
def pdf(self):
def pdf(self) -> bytes:
if self._pdf is None:
error = "View item must be populated with its pdf first."
raise UnpopulatedPropertyError(error)
return self._pdf()

@property
def csv(self):
def csv(self) -> Iterable[bytes]:
if self._csv is None:
error = "View item must be populated with its csv first."
raise UnpopulatedPropertyError(error)
return self._csv()

@property
def sheet_type(self):
def sheet_type(self) -> Optional[str]:
return self._sheet_type

@property
Expand All @@ -101,29 +107,29 @@ def total_views(self):
return self._total_views

@property
def updated_at(self):
def updated_at(self) -> Optional["datetime"]:
return self._updated_at

@property
def workbook_id(self):
def workbook_id(self) -> Optional[str]:
return self._workbook_id

@property
def permissions(self):
def permissions(self) -> List["PermissionsRule"]:
if self._permissions is None:
error = "View item must be populated with permissions first."
raise UnpopulatedPropertyError(error)
return self._permissions()

def _set_permissions(self, permissions):
def _set_permissions(self, permissions: Callable[[], List["PermissionsRule"]]) -> None:
self._permissions = permissions

@classmethod
def from_response(cls, resp, ns, workbook_id=""):
def from_response(cls, resp, ns, workbook_id="") -> List["ViewItem"]:
return cls.from_xml_element(ET.fromstring(resp), ns, workbook_id)

@classmethod
def from_xml_element(cls, parsed_response, ns, workbook_id=""):
def from_xml_element(cls, parsed_response, ns, workbook_id="") -> List["ViewItem"]:
all_view_items = list()
all_view_xml = parsed_response.findall(".//t:view", namespaces=ns)
for view_xml in all_view_xml:
Expand Down
35 changes: 21 additions & 14 deletions tableauserverclient/server/endpoint/views_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@

logger = logging.getLogger("tableau.endpoint.views")

from typing import Iterable, List, Optional, Tuple, TYPE_CHECKING

if TYPE_CHECKING:
from ..request_options import RequestOptions, CSVRequestOptions, PDFRequestOptions, ImageRequestOptions


class Views(QuerysetEndpoint):
def __init__(self, parent_srv):
Expand All @@ -18,15 +23,17 @@ def __init__(self, parent_srv):

# Used because populate_preview_image functionaliy requires workbook endpoint
@property
def siteurl(self):
def siteurl(self) -> str:
return "{0}/sites/{1}".format(self.parent_srv.baseurl, self.parent_srv.site_id)

@property
def baseurl(self):
def baseurl(self) -> str:
return "{0}/views".format(self.siteurl)

@api(version="2.2")
def get(self, req_options=None, usage=False):
def get(
self, req_options: Optional["RequestOptions"] = None, usage: bool = False
) -> Tuple[List[ViewItem], PaginationItem]:
logger.info("Querying all views on site")
url = self.baseurl
if usage:
Expand All @@ -37,7 +44,7 @@ def get(self, req_options=None, usage=False):
return all_view_items, pagination_item

@api(version="3.1")
def get_by_id(self, view_id):
def get_by_id(self, view_id: str) -> ViewItem:
if not view_id:
error = "View item missing ID."
raise MissingRequiredFieldError(error)
Expand All @@ -47,7 +54,7 @@ def get_by_id(self, view_id):
return ViewItem.from_response(server_response.content, self.parent_srv.namespace)[0]

@api(version="2.0")
def populate_preview_image(self, view_item):
def populate_preview_image(self, view_item: ViewItem) -> None:
if not view_item.id or not view_item.workbook_id:
error = "View item missing ID or workbook ID."
raise MissingRequiredFieldError(error)
Expand All @@ -58,14 +65,14 @@ def image_fetcher():
view_item._set_preview_image(image_fetcher)
logger.info("Populated preview image for view (ID: {0})".format(view_item.id))

def _get_preview_for_view(self, view_item):
def _get_preview_for_view(self, view_item: ViewItem) -> bytes:
url = "{0}/workbooks/{1}/views/{2}/previewImage".format(self.siteurl, view_item.workbook_id, view_item.id)
server_response = self.get_request(url)
image = server_response.content
return image

@api(version="2.5")
def populate_image(self, view_item, req_options=None):
def populate_image(self, view_item: ViewItem, req_options: Optional["ImageRequestOptions"] = None) -> None:
if not view_item.id:
error = "View item missing ID."
raise MissingRequiredFieldError(error)
Expand All @@ -76,14 +83,14 @@ def image_fetcher():
view_item._set_image(image_fetcher)
logger.info("Populated image for view (ID: {0})".format(view_item.id))

def _get_view_image(self, view_item, req_options):
def _get_view_image(self, view_item: ViewItem, req_options: Optional["ImageRequestOptions"]) -> bytes:
url = "{0}/{1}/image".format(self.baseurl, view_item.id)
server_response = self.get_request(url, req_options)
image = server_response.content
return image

@api(version="2.7")
def populate_pdf(self, view_item, req_options=None):
def populate_pdf(self, view_item: ViewItem, req_options: Optional["PDFRequestOptions"] = None) -> None:
if not view_item.id:
error = "View item missing ID."
raise MissingRequiredFieldError(error)
Expand All @@ -94,14 +101,14 @@ def pdf_fetcher():
view_item._set_pdf(pdf_fetcher)
logger.info("Populated pdf for view (ID: {0})".format(view_item.id))

def _get_view_pdf(self, view_item, req_options):
def _get_view_pdf(self, view_item: ViewItem, req_options: Optional["PDFRequestOptions"]) -> bytes:
url = "{0}/{1}/pdf".format(self.baseurl, view_item.id)
server_response = self.get_request(url, req_options)
pdf = server_response.content
return pdf

@api(version="2.7")
def populate_csv(self, view_item, req_options=None):
def populate_csv(self, view_item: ViewItem, req_options: Optional["CSVRequestOptions"] = None) -> None:
if not view_item.id:
error = "View item missing ID."
raise MissingRequiredFieldError(error)
Expand All @@ -112,15 +119,15 @@ def csv_fetcher():
view_item._set_csv(csv_fetcher)
logger.info("Populated csv for view (ID: {0})".format(view_item.id))

def _get_view_csv(self, view_item, req_options):
def _get_view_csv(self, view_item: ViewItem, req_options: Optional["CSVRequestOptions"]) -> Iterable[bytes]:
url = "{0}/{1}/data".format(self.baseurl, view_item.id)

with closing(self.get_request(url, request_object=req_options, parameters={"stream": True})) as server_response:
csv = server_response.iter_content(1024)
return csv

@api(version="3.2")
def populate_permissions(self, item):
def populate_permissions(self, item: ViewItem) -> None:
self._permissions.populate(item)

@api(version="3.2")
Expand All @@ -132,7 +139,7 @@ def delete_permission(self, item, capability_item):
return self._permissions.delete(item, capability_item)

# Update view. Currently only tags can be updated
def update(self, view_item):
def update(self, view_item: ViewItem) -> ViewItem:
if not view_item.id:
error = "View item missing ID. View must be retrieved from server first."
raise MissingRequiredFieldError(error)
Expand Down
1 change: 1 addition & 0 deletions test/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ def test_update_missing_id(self) -> None:
self.assertRaises(TSC.MissingRequiredFieldError, self.server.projects.update, single_project)

def test_create(self) -> None:

with open(CREATE_XML, "rb") as f:
response_xml = f.read().decode("utf-8")
with requests_mock.mock() as m:
Expand Down
8 changes: 4 additions & 4 deletions test/test_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def test_get_by_id(self):
self.assertEqual("2002-06-05T08:00:59Z", format_datetime(view.updated_at))
self.assertEqual("story", view.sheet_type)

def test_get_by_id_missing_id(self):
def test_get_by_id_missing_id(self) -> None:
self.assertRaises(TSC.MissingRequiredFieldError, self.server.views.get_by_id, None)

def test_get_with_usage(self):
Expand Down Expand Up @@ -117,7 +117,7 @@ def test_get_with_usage_and_filter(self):
self.assertEqual("Overview", all_views[1].name)
self.assertEqual(13, all_views[1].total_views)

def test_get_before_signin(self):
def test_get_before_signin(self) -> None:
self.server._auth_token = None
self.assertRaises(TSC.NotSignedInError, self.server.views.get)

Expand All @@ -136,7 +136,7 @@ def test_populate_preview_image(self):
self.server.views.populate_preview_image(single_view)
self.assertEqual(response, single_view.preview_image)

def test_populate_preview_image_missing_id(self):
def test_populate_preview_image_missing_id(self) -> None:
single_view = TSC.ViewItem()
single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
self.assertRaises(TSC.MissingRequiredFieldError, self.server.views.populate_preview_image, single_view)
Expand Down Expand Up @@ -211,7 +211,7 @@ def test_populate_csv_default_maxage(self):
csv_file = b"".join(single_view.csv)
self.assertEqual(response, csv_file)

def test_populate_image_missing_id(self):
def test_populate_image_missing_id(self) -> None:
single_view = TSC.ViewItem()
single_view._id = None
self.assertRaises(TSC.MissingRequiredFieldError, self.server.views.populate_image, single_view)
Expand Down
0