Skip to content

Commit

Permalink
Testing updates to fix some type checking in PublishedContent
Browse files Browse the repository at this point in the history
  • Loading branch information
Bryant Howell committed Jan 3, 2020
1 parent 2621913 commit 169f423
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 73 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,10 @@ The Logger class by default only logs Requests but not Responses. If you need to
`Logger.enable_debug_level()`b


### 0.3 TableauBase class
Many classes within the tableau_tools package inherit from the TableauBase class. TableauBase implements the `enable_logging(Logger)` method, along with other a `.log()` method that calls to `Logger.log()`. It also has many static methods, mapping dicts, and helper classes related to Tableau in general.
### 0.3 TableauRestXml class
There is a class called TableauRestXml which holds static methods and properties that are useful on any Tableau REST XML request or response.

It should never be necessary to use TableauBase by itself.
TableauServerRest and TableauRestApiConnection both inherit from this class so you can call any of the methods from one of those objects rather than calling it directly.

### 0.4 tableau_exceptions
The tableau_exceptions file defines a variety of Exceptions that are specific to Tableau, particularly the REST API. They are not very complex, and most simply include a msg property that will clarify the problem if logged
Expand Down
1 change: 0 additions & 1 deletion examples/test_suite_all_querying_tableau_server_rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ def run_tests(server_url: str, username: str, password: str, site_content_url: s
site_content_url=site_content_url)
t.signin()
t.enable_logging(rest_request_log_obj)

# Server info and methods
server_info = t.query_server_info()
server_version = t.query_server_version()
Expand Down
55 changes: 10 additions & 45 deletions tableau_documents/tableau_datasource.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,23 +98,11 @@ def __init__(self, datasource_xml: Optional[ET.Element] = None, logger_obj: Opti
"Hyper": 'hyper',
}

# Create from new or from existing object
# Create from new. Only doing 10.5 style for now.
if datasource_xml is None:
if ds_version is None:
raise InvalidOptionException('When creating Datasource from scratch, must declare a ds_version')
self._ds_version = ds_version
version_split = self._ds_version.split('.')
if version_split[0] == '10':
if int(version_split[1]) < 5:
self.ds_version_type = '10'
else:
self.ds_version_type = '10.5'
elif version_split[0] == '9':
self.ds_version_type = '9'
else:
raise InvalidOptionException('Datasource being created with wrong version type')
self.ds_version_type = '10.5'
self.xml = self.create_new_datasource_xml(ds_version)

# Read existing data source
else:
self.xml = datasource_xml
if self.xml.get("caption"):
Expand Down Expand Up @@ -173,15 +161,6 @@ def __init__(self, datasource_xml: Optional[ET.Element] = None, logger_obj: Opti
else:
self.log('Found a Parameters datasource')


#self.repository_location = None

#if self.xml.find(u'repository-location') is not None:
# if len(self.xml.find(u'repository-location')) == 0:
# self._published = True
# repository_location_xml = self.xml.find(u'repository-location')
# self.repository_location = repository_location_xml

# Grab the extract filename if there is an extract section
if self.xml.find('extract') is not None:
e = self.xml.find('extract')
Expand Down Expand Up @@ -282,16 +261,21 @@ 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._tables_relations:
for relation in self.tables.table_relations:
if relation.get('type') == "table":
relation.set('table', relation.get('table').replace("[{}]".format(original_db_or_schema),
"[{}]".format(new_db_or_schema)))

# Start of data sources creation methods (from scratch)
# Need considerable review and testing

@staticmethod
def create_new_datasource_xml(version: str) -> ET.Element:
# nsmap = {u"user": u'http://www.tableausoftware.com/xml/user'}
# The most basic component is just a datasource element with a version.
ds_xml = ET.Element("datasource")
ds_xml.set('version', version)
# Unclear if this is even necessary. May only appear in TWBXs
ds_xml.set('inline', "true")
return ds_xml

Expand All @@ -300,9 +284,7 @@ def create_new_connection_xml(ds_version: str, ds_type: str, server: str, db_nam
authentication: Optional[str] = None,
initial_sql: Optional[str] = None) -> ET.Element:
connection = ET.Element("connection")
if ds_version == '9':
c = connection
elif ds_version in ['10', '10.5']:
if ds_version in ['10', '10.5']:
nc = ET.Element('named-connection')
nc.set('caption', 'Connection')
# Connection has a random number of 20 digits appended
Expand Down Expand Up @@ -427,23 +409,6 @@ def translate_columns(self, translation_dict: Dict):
self.columns.translate_captions(translation_dict=translation_dict)
self.end_log_block()

def add_extract(self, new_extract_filename: str):
self.log('add_extract called, checking if extract exists already')
# Test to see if extract exists already
e = self.xml.find('extract')
if e is not None:
self.log("Existing extract found, no need to add")
raise AlreadyExistsException("An extract already exists, can't add a new one", "")
else:
self.log('Extract doesnt exist')
new_extract_filename_start = new_extract_filename.split(".")[0]
if self.ds_version_type == '10.5':
final_extract_filename = "{}.hyper".format(new_extract_filename_start)
else:
final_extract_filename = "{}.tde".format(new_extract_filename_start)
self._extract_filename = final_extract_filename
self.log('Adding extract to the data source')

def generate_extract_section(self) -> Union[ET.Element, bool]:
# Short circuit if no extract had been set
if self._extract_filename is None:
Expand Down
34 changes: 21 additions & 13 deletions tableau_rest_api/methods/rest_api_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,20 +287,24 @@ def signin(self, user_luid_to_impersonate: Optional[str] = None):
self._request_obj.xml_request = tsr
self._request_obj.http_verb = 'post'
self.log('Login payload is\n {}'.format(ET.tostring(tsr)))
try:
self._request_obj.request_from_api(0)
# self.log(api.get_raw_response())
xml = self._request_obj.get_response()

self._request_obj.request_from_api(0)
# self.log(api.get_raw_response())
xml = self._request_obj.get_response()

credentials_element = xml.findall('.//t:credentials', self.ns_map)
self.token = credentials_element[0].get("token")
self.log("Token is " + self.token)
self._request_obj.token = self.token
self.site_luid = credentials_element[0].findall(".//t:site", self.ns_map)[0].get("id")
self.user_luid = credentials_element[0].findall(".//t:user", self.ns_map)[0].get("id")
self.log("Site ID is " + self.site_luid)
self._request_obj.url = None
self._request_obj.xml_request = None
credentials_element = xml.findall('.//t:credentials', self.ns_map)
self.token = credentials_element[0].get("token")
self.log("Token is " + self.token)
self._request_obj.token = self.token
self.site_luid = credentials_element[0].findall(".//t:site", self.ns_map)[0].get("id")
self.user_luid = credentials_element[0].findall(".//t:user", self.ns_map)[0].get("id")
self.log("Site ID is " + self.site_luid)
self._request_obj.url = None
self._request_obj.xml_request = None
except RecoverableHTTPException as e:
if e.tableau_error_code == '401001':
self.end_log_block()
raise NotSignedInException('Sign-in failed due to wrong credentials')
self.end_log_block()

def swap_token(self, site_luid: str, user_luid: str, token: str):
Expand Down Expand Up @@ -374,6 +378,8 @@ def query_resource(self, url_ending: str, server_level:bool = False, filters: Op
sorts: Optional[List[Sort]] = None, additional_url_ending: Optional[str] = None,
fields: Optional[List[str]] = None) -> ET.Element:
self.start_log_block()
if self.token == "":
raise NotSignedInException('Must use .signin() to create REST API session first')
url_endings = []
if filters is not None:
if len(filters) > 0:
Expand Down Expand Up @@ -525,6 +531,8 @@ def query_resource_json(self, url_ending: str, server_level: bool = False,
sorts: Optional[List[Sort]] = None, additional_url_ending: str = None,
fields: Optional[List[str]] = None, page_number: Optional[int] = None) -> Dict:
self.start_log_block()
if self.token == "":
raise NotSignedInException('Must use .signin() to create REST API session first')
url_endings = []
if filters is not None:
if len(filters) > 0:
Expand Down
18 changes: 9 additions & 9 deletions tableau_rest_api/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ def __init__(self, group_or_user: str, luid: str, content_type: Optional[str] =
'3.6': server_content_roles_3_5
}

self.__server_to_rest_capability_map = {
self.server_to_rest_capability_map = {
'Add Comment': 'AddComment',
'Move': 'ChangeHierarchy',
'Set Permissions': 'ChangePermissions',
Expand Down Expand Up @@ -365,15 +365,15 @@ def group_or_user(self, group_or_user):

# Just use the direct "to_allow" and "to_deny" methods
def set_capability(self, capability_name: str, mode: str):
if capability_name not in list(self.__server_to_rest_capability_map.values()):
if capability_name not in list(self.server_to_rest_capability_map.values()):
# If it's the Tableau UI naming, translate it over
if capability_name in self.__server_to_rest_capability_map:
if capability_name in self.server_to_rest_capability_map:
# InheritedProjectLeader (2.8+) is Read-Only
if capability_name == 'InheritedProjectLeader':
self.log('InheritedProjectLeader permission is read-only, skipping')
return
if capability_name != 'all':
capability_name = self.__server_to_rest_capability_map[capability_name]
capability_name = self.server_to_rest_capability_map[capability_name]
else:
raise InvalidOptionException('"{}" is not a capability in REST API or Server'.format(capability_name))
self.capabilities[capability_name] = mode
Expand All @@ -387,23 +387,23 @@ def set_capability_to_deny(self, capability_name: str):
def set_capability_to_unspecified(self, capability_name: str):
if capability_name not in self.capabilities:
# If it's the Tableau UI naming, translate it over
if capability_name in self.__server_to_rest_capability_map:
if capability_name in self.server_to_rest_capability_map:
if capability_name == 'InheritedProjectLeader':
self.log('InheritedProjectLeader permission is read-only, skipping')
return
if capability_name != 'all':
capability_name = self.__server_to_rest_capability_map[capability_name]
capability_name = self.server_to_rest_capability_map[capability_name]
else:
raise InvalidOptionException('"{}" is not a capability in REST API or Server'.format(capability_name))
self.capabilities[capability_name] = None

# This exists specifically to allow the setting of read-only permissions
def _set_capability_from_published_content(self, capability_name: str, mode: str):
if capability_name not in list(self.__server_to_rest_capability_map.values()):
if capability_name not in list(self.server_to_rest_capability_map.values()):
# If it's the Tableau UI naming, translate it over
if capability_name in self.__server_to_rest_capability_map:
if capability_name in self.server_to_rest_capability_map:
if capability_name != 'all':
capability_name = self.__server_to_rest_capability_map[capability_name]
capability_name = self.server_to_rest_capability_map[capability_name]
else:
raise InvalidOptionException('"{}" is not a capability in REST API or Server'.format(capability_name))
self.capabilities[capability_name] = mode
Expand Down
4 changes: 2 additions & 2 deletions tableau_rest_api/published_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -950,9 +950,9 @@ def are_permissions_locked(self) -> bool:
def lock_permissions(self):
self.start_log_block()
if self.permissions_locked is False:
if(isinstance(self.t_rest_api, TableauRestApiConnection)):
if(type(self.t_rest_api).__name__.contains('TableauRestApiConnection')):
self.t_rest_api.update_project(self.luid, locked_permissions=True)
if(isinstance(self.t_rest_api, TableauServerRest)):
if(type(self.t_rest_api).__name__.contains('TableauServerRest')):
self.t_rest_api.projects.update_project(self.luid, locked_permissions=True)
self.end_log_block()

Expand Down

0 comments on commit 169f423

Please sign in to comment.