From 5f0af2574fae2527366011e12c6d7a02db4def5b Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Sat, 23 Oct 2021 14:23:44 -0400 Subject: [PATCH 1/4] Type hint permissions --- .../models/permissions_item.py | 63 ++++++++++--------- .../server/endpoint/permissions_endpoint.py | 19 ++++-- 2 files changed, 48 insertions(+), 34 deletions(-) diff --git a/tableauserverclient/models/permissions_item.py b/tableauserverclient/models/permissions_item.py index 113e8525e..b6becd929 100644 --- a/tableauserverclient/models/permissions_item.py +++ b/tableauserverclient/models/permissions_item.py @@ -7,54 +7,59 @@ logger = logging.getLogger("tableau.models.permissions_item") +from typing import Dict, List, Mapping, Optional, TYPE_CHECKING + +if TYPE_CHECKING: + from .reference_item import ResourceReference + class Permission: class Mode: - Allow = "Allow" - Deny = "Deny" + Allow: str = "Allow" + Deny: str = "Deny" class Capability: - AddComment = "AddComment" - ChangeHierarchy = "ChangeHierarchy" - ChangePermissions = "ChangePermissions" - Connect = "Connect" - Delete = "Delete" - Execute = "Execute" - ExportData = "ExportData" - ExportImage = "ExportImage" - ExportXml = "ExportXml" - Filter = "Filter" - ProjectLeader = "ProjectLeader" - Read = "Read" - ShareView = "ShareView" - ViewComments = "ViewComments" - ViewUnderlyingData = "ViewUnderlyingData" - WebAuthoring = "WebAuthoring" - Write = "Write" + AddComment: str = "AddComment" + ChangeHierarchy: str = "ChangeHierarchy" + ChangePermissions: str = "ChangePermissions" + Connect: str = "Connect" + Delete: str = "Delete" + Execute: str = "Execute" + ExportData: str = "ExportData" + ExportImage: str = "ExportImage" + ExportXml: str = "ExportXml" + Filter: str = "Filter" + ProjectLeader: str = "ProjectLeader" + Read: str = "Read" + ShareView: str = "ShareView" + ViewComments: str = "ViewComments" + ViewUnderlyingData: str = "ViewUnderlyingData" + WebAuthoring: str = "WebAuthoring" + Write: str = "Write" class Resource: - Workbook = "workbook" - Datasource = "datasource" - Flow = "flow" - Table = "table" - Database = "database" - View = "view" + Workbook: str = "workbook" + Datasource: str = "datasource" + Flow: str = "flow" + Table: str = "table" + Database: str = "database" + View: str = "view" class PermissionsRule(object): - def __init__(self, grantee, capabilities): + def __init__(self, grantee: "ResourceReference", capabilities: Dict[Optional[str], Optional[str]]) -> None: self.grantee = grantee self.capabilities = capabilities @classmethod - def from_response(cls, resp, ns=None): + def from_response(cls, resp, ns=None) -> List["PermissionsRule"]: parsed_response = ET.fromstring(resp) rules = [] permissions_rules_list_xml = parsed_response.findall(".//t:granteeCapabilities", namespaces=ns) for grantee_capability_xml in permissions_rules_list_xml: - capability_dict = {} + capability_dict: Dict[Optional[str], Optional[str]] = {} grantee = PermissionsRule._parse_grantee_element(grantee_capability_xml, ns) @@ -70,7 +75,7 @@ def from_response(cls, resp, ns=None): return rules @staticmethod - def _parse_grantee_element(grantee_capability_xml, ns): + def _parse_grantee_element(grantee_capability_xml: ET.Element, ns: Optional[Dict[str, str]]) -> "ResourceReference": """Use Xpath magic and some string splitting to get the right object type from the xml""" # Get the first element in the tree with an 'id' attribute diff --git a/tableauserverclient/server/endpoint/permissions_endpoint.py b/tableauserverclient/server/endpoint/permissions_endpoint.py index 5013a0bef..384451560 100644 --- a/tableauserverclient/server/endpoint/permissions_endpoint.py +++ b/tableauserverclient/server/endpoint/permissions_endpoint.py @@ -8,6 +8,15 @@ logger = logging.getLogger(__name__) +from typing import Callable, TYPE_CHECKING, List, Union + +if TYPE_CHECKING: + from ...models import DatasourceItem, ProjectItem, WorkbookItem, ViewItem + from ..server import Server + from ..request_options import RequestOptions + +TableauItem = Union["DatasourceItem", "ProjectItem", "WorkbookItem", "ViewItem"] + class _PermissionsEndpoint(Endpoint): """Adds permission model to another endpoint @@ -18,7 +27,7 @@ class _PermissionsEndpoint(Endpoint): parent endpoint which has these supported endpoints """ - def __init__(self, parent_srv, owner_baseurl): + def __init__(self, parent_srv: "Server", owner_baseurl: Callable[[], str]) -> None: super(_PermissionsEndpoint, self).__init__(parent_srv) # owner_baseurl is the baseurl of the parent. The MUST be a lambda @@ -26,7 +35,7 @@ def __init__(self, parent_srv, owner_baseurl): # populated without, we will get a sign-in error self.owner_baseurl = owner_baseurl - def update(self, resource, permissions): + def update(self, resource: TableauItem, permissions: List[PermissionsRule]) -> List[PermissionsRule]: url = "{0}/{1}/permissions".format(self.owner_baseurl(), resource.id) update_req = RequestFactory.Permission.add_req(permissions) response = self.put_request(url, update_req) @@ -35,7 +44,7 @@ def update(self, resource, permissions): return permissions - def delete(self, resource, rules): + def delete(self, resource: TableauItem, rules: Union[PermissionsRule, List[PermissionsRule]]): # Delete is the only endpoint that doesn't take a list of rules # so let's fake it to keep it consistent # TODO that means we need error handling around the call @@ -62,7 +71,7 @@ def delete(self, resource, rules): "Deleted permission for {0} {1} item {2}".format(rule.grantee.tag_name, rule.grantee.id, resource.id) ) - def populate(self, item): + def populate(self, item: TableauItem): if not item.id: error = "Server item is missing ID. Item must be retrieved from server first." raise MissingRequiredFieldError(error) @@ -73,7 +82,7 @@ def permission_fetcher(): item._set_permissions(permission_fetcher) logger.info("Populated permissions for item (ID: {0})".format(item.id)) - def _get_permissions(self, item, req_options=None): + def _get_permissions(self, item: TableauItem, req_options: "RequestOptions" = None): url = "{0}/{1}/permissions".format(self.owner_baseurl(), item.id) server_response = self.get_request(url, req_options) permissions = PermissionsRule.from_response(server_response.content, self.parent_srv.namespace) From e0539b6d0db53b03dcfd7c2248773421156b2652 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Sat, 23 Oct 2021 22:00:06 -0500 Subject: [PATCH 2/4] Type hint default permissions --- .../endpoint/default_permissions_endpoint.py | 32 +++++++++++++++---- tableauserverclient/server/request_factory.py | 4 ++- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/tableauserverclient/server/endpoint/default_permissions_endpoint.py b/tableauserverclient/server/endpoint/default_permissions_endpoint.py index 1cfa41733..93b3be110 100644 --- a/tableauserverclient/server/endpoint/default_permissions_endpoint.py +++ b/tableauserverclient/server/endpoint/default_permissions_endpoint.py @@ -9,6 +9,22 @@ logger = logging.getLogger(__name__) +from typing import TYPE_CHECKING, Callable, List, Optional, Sequence, Union + +if TYPE_CHECKING: + from ...models import ( + DatasourceItem, + FlowItem, + ProjectItem, + ViewItem, + WorkbookItem, + ) + + from ..server import Server + from ..request_options import RequestOptions + + TableauItem = Union[DatasourceItem, FlowItem, ProjectItem, ViewItem, WorkbookItem] + class _DefaultPermissionsEndpoint(Endpoint): """Adds default-permission model to another endpoint @@ -19,7 +35,7 @@ class _DefaultPermissionsEndpoint(Endpoint): has these supported endpoints """ - def __init__(self, parent_srv, owner_baseurl): + def __init__(self, parent_srv: "Server", owner_baseurl: Callable[[], str]) -> None: super(_DefaultPermissionsEndpoint, self).__init__(parent_srv) # owner_baseurl is the baseurl of the parent. The MUST be a lambda @@ -27,7 +43,9 @@ def __init__(self, parent_srv, owner_baseurl): # populated without, we will get a sign-in error self.owner_baseurl = owner_baseurl - def update_default_permissions(self, resource, permissions, content_type): + def update_default_permissions( + self, resource: "TableauItem", permissions: Sequence[PermissionsRule], content_type: str + ) -> List[PermissionsRule]: url = "{0}/{1}/default-permissions/{2}".format(self.owner_baseurl(), resource.id, content_type + "s") update_req = RequestFactory.Permission.add_req(permissions) response = self.put_request(url, update_req) @@ -36,7 +54,7 @@ def update_default_permissions(self, resource, permissions, content_type): return permissions - def delete_default_permission(self, resource, rule, content_type): + def delete_default_permission(self, resource: "TableauItem", rule: PermissionsRule, content_type: str) -> None: for capability, mode in rule.capabilities.items(): # Made readability better but line is too long, will make this look better url = ( @@ -60,18 +78,20 @@ def delete_default_permission(self, resource, rule, content_type): "Deleted permission for {0} {1} item {2}".format(rule.grantee.tag_name, rule.grantee.id, resource.id) ) - def populate_default_permissions(self, item, content_type): + def populate_default_permissions(self, item: "ProjectItem", content_type: str) -> None: if not item.id: error = "Server item is missing ID. Item must be retrieved from server first." raise MissingRequiredFieldError(error) - def permission_fetcher(): + def permission_fetcher() -> List[PermissionsRule]: return self._get_default_permissions(item, content_type) item._set_default_permissions(permission_fetcher, content_type) logger.info("Populated {0} permissions for item (ID: {1})".format(item.id, content_type)) - def _get_default_permissions(self, item, content_type, req_options=None): + def _get_default_permissions( + self, item: "TableauItem", content_type: str, req_options: Optional["RequestOptions"] = None + ) -> List[PermissionsRule]: url = "{0}/{1}/default-permissions/{2}".format(self.owner_baseurl(), item.id, content_type + "s") server_response = self.get_request(url, req_options) permissions = PermissionsRule.from_response(server_response.content, self.parent_srv.namespace) diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index 1e0c357dc..12eeb7dee 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -5,6 +5,8 @@ from ..models import TaskItem, UserItem, GroupItem, PermissionsRule, FavoriteItem +from typing import Iterable + def _add_multipart(parts): mime_multipart_parts = list() @@ -376,7 +378,7 @@ def update_req(self, group_item, default_site_role=None): class PermissionRequest(object): - def add_req(self, rules): + def add_req(self, rules: Iterable[PermissionsRule]) -> bytes: xml_request = ET.Element("tsRequest") permissions_element = ET.SubElement(xml_request, "permissions") From 7bddec3082c6559bd4f86e1e263a361e7b23b2e6 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Wed, 10 Nov 2021 10:25:20 -0600 Subject: [PATCH 3/4] Remove hints from enums --- .../models/permissions_item.py | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/tableauserverclient/models/permissions_item.py b/tableauserverclient/models/permissions_item.py index b6becd929..870cfb698 100644 --- a/tableauserverclient/models/permissions_item.py +++ b/tableauserverclient/models/permissions_item.py @@ -15,35 +15,35 @@ class Permission: class Mode: - Allow: str = "Allow" - Deny: str = "Deny" + Allow = "Allow" + Deny = "Deny" class Capability: - AddComment: str = "AddComment" - ChangeHierarchy: str = "ChangeHierarchy" - ChangePermissions: str = "ChangePermissions" - Connect: str = "Connect" - Delete: str = "Delete" - Execute: str = "Execute" - ExportData: str = "ExportData" - ExportImage: str = "ExportImage" - ExportXml: str = "ExportXml" - Filter: str = "Filter" - ProjectLeader: str = "ProjectLeader" - Read: str = "Read" - ShareView: str = "ShareView" - ViewComments: str = "ViewComments" - ViewUnderlyingData: str = "ViewUnderlyingData" - WebAuthoring: str = "WebAuthoring" - Write: str = "Write" + AddComment = "AddComment" + ChangeHierarchy = "ChangeHierarchy" + ChangePermissions = "ChangePermissions" + Connect = "Connect" + Delete = "Delete" + Execute = "Execute" + ExportData = "ExportData" + ExportImage = "ExportImage" + ExportXml = "ExportXml" + Filter = "Filter" + ProjectLeader = "ProjectLeader" + Read = "Read" + ShareView = "ShareView" + ViewComments = "ViewComments" + ViewUnderlyingData = "ViewUnderlyingData" + WebAuthoring = "WebAuthoring" + Write = "Write" class Resource: - Workbook: str = "workbook" - Datasource: str = "datasource" - Flow: str = "flow" - Table: str = "table" - Database: str = "database" - View: str = "view" + Workbook = "workbook" + Datasource = "datasource" + Flow = "flow" + Table = "table" + Database = "database" + View = "view" class PermissionsRule(object): From bca147c92b550452237c58d07b242dd60b50bf33 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Wed, 10 Nov 2021 10:25:43 -0600 Subject: [PATCH 4/4] Remove unused import --- tableauserverclient/models/permissions_item.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tableauserverclient/models/permissions_item.py b/tableauserverclient/models/permissions_item.py index 870cfb698..5498ede42 100644 --- a/tableauserverclient/models/permissions_item.py +++ b/tableauserverclient/models/permissions_item.py @@ -7,7 +7,7 @@ logger = logging.getLogger("tableau.models.permissions_item") -from typing import Dict, List, Mapping, Optional, TYPE_CHECKING +from typing import Dict, List, Optional, TYPE_CHECKING if TYPE_CHECKING: from .reference_item import ResourceReference