Skip to content

Commit

Permalink
Updated the samples to show the new get_permissions_obj() method acro…
Browse files Browse the repository at this point in the history
…ss the board. Also added a TableauRestXml class to hold static info and methods that various other objects might want to access. This is similar to the old TableauBase class but there is not not a need for everything to descend from it
  • Loading branch information
Bryant Howell committed Dec 11, 2019
1 parent 5d55b87 commit 3635a1c
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 25 deletions.
24 changes: 12 additions & 12 deletions examples/create_site_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,23 +46,23 @@ def tableau_rest_api_connection_version():


# Add in any default permissions you'd like at this point
admin_perms = default_proj.create_project_permissions_object_for_group(group_name_or_luid='Administrators',
admin_perms = default_proj.get_permissions_obj(group_name_or_luid='Administrators',
role='Project Leader')
default_proj.set_permissions_by_permissions_obj_list([admin_perms, ])
default_proj.set_permissions([admin_perms, ])

admin_perms = default_proj.create_workbook_permissions_object_for_group(group_name_or_luid='Administrators',
admin_perms = default_proj.workbook_defaults.get_permissions_obj(group_name_or_luid='Administrators',
role='Editor')
admin_perms.set_capability(capability_name='Download Full Data', mode='Deny')
default_proj.workbook_defaults.set_permissions_by_permissions_obj_list([admin_perms, ])
default_proj.workbook_defaults.set_permissions([admin_perms, ])

admin_perms = default_proj.create_datasource_permissions_object_for_group(group_name_or_luid='Administrators',
admin_perms = default_proj.datasource_defaults.get_permissions_obj(group_name_or_luid='Administrators',
role='Editor')
default_proj.datasource_defaults.set_permissions_by_permissions_obj_list([admin_perms, ])
default_proj.datasource_defaults.set_permissions([admin_perms, ])

# Change one of these
new_perms = default_proj.create_project_permissions_object_for_group(group_name_or_luid='Administrators',
new_perms = default_proj.get_permissions_obj(group_name_or_luid='Administrators',
role='Publisher')
default_proj.set_permissions_by_permissions_obj_list([new_perms, ])
default_proj.set_permissions([new_perms, ])

# Create Additional Projects
projects_to_create = ['Sandbox', 'Data Source Definitions', 'UAT', 'Finance', 'Real Financials']
Expand Down Expand Up @@ -117,21 +117,21 @@ def tableau_server_rest_version():
default_proj.clear_all_permissions() # This clears all, including the defaults

# Add in any default permissions you'd like at this point
admin_perms = default_proj.create_project_permissions_object_for_group(group_name_or_luid='Administrators',
admin_perms = default_proj.get_permissions_obj(group_name_or_luid='Administrators',
role='Project Leader')
default_proj.set_permissions_by_permissions_obj_list([admin_perms, ])

admin_perms = default_proj.create_workbook_permissions_object_for_group(group_name_or_luid='Administrators',
admin_perms = default_proj.workbook_defaults.get_permissions_obj(group_name_or_luid='Administrators',
role='Editor')
admin_perms.set_capability(capability_name='Download Full Data', mode='Deny')
default_proj.workbook_defaults.set_permissions_by_permissions_obj_list([admin_perms, ])

admin_perms = default_proj.create_datasource_permissions_object_for_group(group_name_or_luid='Administrators',
admin_perms = default_proj.datasource_defaults.get_permissions_obj(group_name_or_luid='Administrators',
role='Editor')
default_proj.datasource_defaults.set_permissions_by_permissions_obj_list([admin_perms, ])

# Change one of these
new_perms = default_proj.create_project_permissions_object_for_group(group_name_or_luid='Administrators',
new_perms = default_proj.get_permissions_obj(group_name_or_luid='Administrators',
role='Publisher')
default_proj.set_permissions_by_permissions_obj_list([new_perms, ])

Expand Down
4 changes: 2 additions & 2 deletions examples/user_sync_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@
for user in users_dict:
proj_obj = t.projects.create_project("My Saved Reports - {}".format(user))
user_luid = users_dict[user]
perms_obj = proj_obj.create_project_permissions_object_for_user(username_or_luid=user_luid, role='Publisher')
proj_obj.set_permissions_by_permissions_obj_list([perms_obj, ])
perms_obj = proj_obj.get_permissions_obj(username_or_luid=user_luid, role='Publisher')
proj_obj.set_permissions([perms_obj, ])


# Reset back to beginning to reuse query
Expand Down
2 changes: 1 addition & 1 deletion tableau_documents/tableau_datasource.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ def published_ds_content_url(self, new_content_url: str):

# It seems some databases like Oracle and Teradata need this as well to swap a database
def update_tables_with_new_database_or_schema(self, original_db_or_schema: str, new_db_or_schema: str):
for relation in self.table_relations:
for relation in self._tables_relations:
if relation.get('type') == "table":
relation.set('table', relation.get('table').replace("[{}]".format(original_db_or_schema),
"[{}]".format(new_db_or_schema)))
Expand Down
2 changes: 2 additions & 0 deletions tableau_documents/tableau_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,8 @@ def save_new_file(self, new_filename_no_extension: str):

return save_filename

# TFL files are actually JSON rather than XML. I don't think there are actually any XML calls except
# possibly when creating the TableauDocument file.
class TFL(TableauXmlFile):

@property
Expand Down
2 changes: 1 addition & 1 deletion tableau_rest_api/methods/workbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def __getattr__(self, attr):
def get_published_workbook_object(self, workbook_name_or_luid: str,
project_name_or_luid: Optional[str] = None) -> Workbook:
luid = self.query_workbook_luid(workbook_name_or_luid, project_name_or_luid)
wb_obj = Workbook(luid=luid, tableau_rest_api_obj=self,
wb_obj = Workbook(luid=luid, tableau_rest_api_obj=self.rest_api_base,
default=False, logger_obj=self.logger)
return wb_obj

Expand Down
15 changes: 6 additions & 9 deletions tableau_rest_api/published_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import copy
from typing import Union, Any, Optional, List, Dict, TYPE_CHECKING

from ..tableau_rest_xml import TableauRestXml

if TYPE_CHECKING:
from tableau_tools.logging_methods import LoggingMethods
from tableau_tools.logger import Logger
Expand Down Expand Up @@ -86,7 +88,7 @@ def get_permissions_obj(self, group_name_or_luid: Optional[str] = None, username
# Copy Permissions for users or group
def _copy_permissions_obj(self, perms_obj, user_or_group, name_or_luid):
self.start_log_block()
if self.is_luid(name_or_luid):
if TableauRestXml.is_luid(name_or_luid):
luid = name_or_luid
else:
if user_or_group == 'group':
Expand Down Expand Up @@ -339,12 +341,7 @@ def are_capabilities_obj_dicts_identical(new_obj_dict: Dict, dest_obj_dict: Dict
return False

# Dict { capability_name : mode } into XML with checks for validity. Set type to 'workbook' or 'datasource'
def build_capabilities_xml_from_dict(self, capabilities_dict, obj_type):
"""
:type capabilities_dict: dict
:type obj_type: unicode
:return: ET.Element
"""
def build_capabilities_xml_from_dict(self, capabilities_dict: Dict, obj_type: str) -> ET.Element:
if obj_type not in self.permissionable_objects:
error_text = 'objtype can only be "project", "workbook" or "datasource", was given {}'
raise InvalidOptionException(error_text.format('obj_type'))
Expand Down Expand Up @@ -760,6 +757,7 @@ def get_permissions_obj(self, group_name_or_luid: Optional[str] = None, username
role=role)



class Project(PublishedContent):
def __init__(self, luid, tableau_rest_api_obj, logger_obj=None,
content_xml_obj=None):
Expand All @@ -782,7 +780,7 @@ def luid(self):

@luid.setter
def luid(self, name_or_luid):
if self.is_luid(name_or_luid):
if TableauRestXml.is_luid(name_or_luid):
luid = name_or_luid
else:
luid = self.t_rest_api.query_project_luid(name_or_luid)
Expand Down Expand Up @@ -925,7 +923,6 @@ def create_datasource_permissions_object_for_user(self, username_or_luid: str,
return self._get_permissions_object(username_or_luid=username_or_luid, role=role,
permissions_class_override=DatasourcePermissions)


@property
def workbook_defaults(self) -> Workbook:
return self._workbook_defaults
Expand Down
93 changes: 93 additions & 0 deletions tableau_rest_xml.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# This is intended to be full of static helper methods
import xml.etree.ElementTree as ET
from typing import Union, Optional, List, Dict, Tuple
import re

class TableauRestXml:
tableau_namespace = 'http://tableau.com/api'
ns_map = {'t': 'http://tableau.com/api'}
ns_prefix = '{' + ns_map['t'] + '}'
# ET.register_namespace('t', ns_map['t'])
# Generic method for XML lists for the "query" actions to name -> id dict
@staticmethod
def convert_xml_list_to_name_id_dict(xml_obj: ET.Element) -> Dict:
d = {}
for element in xml_obj:
e_id = element.get("id")
# If list is collection, have to run one deeper
if e_id is None:
for list_element in element:
e_id = list_element.get("id")
name = list_element.get("name")
d[name] = e_id
else:
name = element.get("name")
d[name] = e_id
return d

# Repeat of above method with shorter name
@staticmethod
def xml_list_to_dict(xml_obj: ET.Element) -> Dict:
d = {}
for element in xml_obj:
e_id = element.get("id")
# If list is collection, have to run one deeper
if e_id is None:
for list_element in element:
e_id = list_element.get("id")
name = list_element.get("name")
d[name] = e_id
else:
name = element.get("name")
d[name] = e_id
return d

@staticmethod
def luid_name_dict_from_xml(xml_obj: ET.Element) -> Dict:
d = {}
for element in xml_obj:
e_id = element.get("id")
# If list is collection, have to run one deeper
if e_id is None:
for list_element in element:
e_id = list_element.get("id")
name = list_element.get("name")
d[e_id] = name
else:
name = element.get("name")
d[e_id] = name
return d

@staticmethod
def luid_content_url_dict_from_xml(xml_obj: ET.Element) -> Dict:
d = {}
for element in xml_obj:
e_id = element.get("id")
# If list is collection, have to run one deeper
if e_id is None:
for list_element in element:
e_id = list_element.get("id")
name = list_element.get("contentUrl")
d[e_id] = name
else:
name = element.get("contentUrl")
d[e_id] = name
return d

# This corrects for the first element in any response by the plural collection tag, which leads to differences
# with the XPath search currently
@staticmethod
def make_xml_list_iterable(xml_obj: ET.Element) -> List[ET.Element]:
pass

# 32 hex characters with 4 dashes
@staticmethod
def is_luid(val: str) -> bool:
luid_pattern = r"[0-9a-fA-F]*-[0-9a-fA-F]*-[0-9a-fA-F]*-[0-9a-fA-F]*-[0-9a-fA-F]*"
if len(val) == 36:
if re.match(luid_pattern, val) is not None:
return True
else:
return False
else:
return False

0 comments on commit 3635a1c

Please sign in to comment.