diff --git a/tableauserverclient/models/view_item.py b/tableauserverclient/models/view_item.py index f18acfc33..b8af8df88 100644 --- a/tableauserverclient/models/view_item.py +++ b/tableauserverclient/models/view_item.py @@ -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 @@ -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 @@ -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: diff --git a/tableauserverclient/server/endpoint/views_endpoint.py b/tableauserverclient/server/endpoint/views_endpoint.py index a00e7f145..f614f0dc4 100644 --- a/tableauserverclient/server/endpoint/views_endpoint.py +++ b/tableauserverclient/server/endpoint/views_endpoint.py @@ -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): @@ -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: @@ -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) @@ -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) @@ -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) @@ -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) @@ -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) @@ -112,7 +119,7 @@ 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: @@ -120,7 +127,7 @@ def _get_view_csv(self, view_item, req_options): 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") @@ -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) diff --git a/test/test_project.py b/test/test_project.py index 47bd60114..d371cb928 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -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: diff --git a/test/test_view.py b/test/test_view.py index ddb3871c4..668eb9c3a 100644 --- a/test/test_view.py +++ b/test/test_view.py @@ -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): @@ -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) @@ -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) @@ -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)