Skip to content

Commit

Permalink
Refactored TableauServer for better modularity (#70)
Browse files Browse the repository at this point in the history
Refactored TableauServer for better modularity
  • Loading branch information
JustinGrilli authored Feb 29, 2024
1 parent 94bca91 commit 8e7ed08
Show file tree
Hide file tree
Showing 20 changed files with 838 additions and 735 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def main():
# Create a Tableau Connection
ts = TableauServer(**tableau_creds)
# Download a Datasource
datasource_path = ts.download_datasource(datasource_id=datasource_id)
datasource_path = ts.download.datasource(datasource_id=datasource_id)
# Define a Datasource object from the datasource_path
datasource = Datasource(datasource_path)
# Define a new folder
Expand All @@ -62,7 +62,7 @@ def main():
# Save changes to the Datasource
datasource.save()
# Publish & Overwrite the Datasource
ts.publish_datasource(datasource_path, datasource_id=datasource_id)
ts.publish.datasource(datasource_path, datasource_id=datasource_id)


if __name__ == '__main__':
Expand Down
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,3 @@ requests
pandas
tabulate
pytest

20 changes: 10 additions & 10 deletions scripts/list_datasources.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@

def main():
args = parser.parse_args()

url = f'https://{args.server}.online.tableau.com'
ts = TableauServer(
user=args.user,
password=args.password,
personal_access_token_name=args.token_name,
personal_access_token_secret=args.token_secret,
host=url,
site=args.site
)
ts.get.datasources(print_info=True)

ts = TableauServer(user=args.user,
password=args.password,
token_name=args.token_name,
token_secret=args.token_secret,
url=url,
site=args.site)

ts.list_datasources(print_info=True)

if __name__ == '__main__':
main()
main()
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
long_description=readme,
long_description_content_type='text/markdown',
name="tableau_utilities",
version="2.0.69",
version="2.1.0",
packages=[
'tableau_utilities',
'tableau_utilities.general',
Expand Down
3 changes: 2 additions & 1 deletion tableau_utilities/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .scripts import cli
from .tableau_file.tableau_file import TableauFileError, Datasource
from .tableau_file import tableau_file_objects
from .tableau_server.tableau_server import TableauConnectionError, TableauServer
from .tableau_server.static import TableauConnectionError
from .tableau_server.tableau_server import TableauServer
from .tableau_server import tableau_server_objects
4 changes: 2 additions & 2 deletions tableau_utilities/fetch_all_cols.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ def all_columns_all_datasources(server):
tmp_folder = 'tmp_tdsx'
os.makedirs(tmp_folder, exist_ok=True)
os.chdir(tmp_folder)
datasource_list = [d for d in server.get_datasources()]
datasource_list = [d for d in server.get.datasources()]
rows = dict()
for datasource in datasource_list:
print(datasource.project_name, (datasource.id, datasource.name))
datasource_path = server.download_datasource(datasource.id, include_extract=False)
datasource_path = server.download.datasource(datasource.id, include_extract=False)
columns = [c.dict() for c in Datasource(datasource_path).columns]
rows.setdefault(datasource.name, [])
rows[datasource.name].extend(columns)
Expand Down
70 changes: 37 additions & 33 deletions tableau_utilities/scripted_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@
import shutil
import xml.etree.ElementTree as ET
from pprint import pprint, isreadable
from time import time
from tableau_utilities import Datasource, TableauServer
from tableau_utilities import tableau_file_objects as tfo


if __name__ == '__main__':
with open('settings.yaml') as f:
settings = yaml.safe_load(f)
start = time()
with open('../settings.yaml') as f:
creds = yaml.safe_load(f)['tableau_login']
personal_access_token_name = creds['token_name']
personal_access_token_secret = creds['token_secret']
site = creds['site']
host = f'https://{creds["server"]}.online.tableau.com'
api_version = creds['api_version']
tmp_folder = 'tmp_downloads'
# Cleanup lingering files, if there are any
# shutil.rmtree(tmp_folder, ignore_errors=True)
Expand All @@ -24,53 +31,50 @@
# datasource.unzip()
# datasource = Datasource('../resources/no_folder.tdsx')
# datasource.unzip()
datasource = Datasource('test_data_source.tds')
# datasource = Datasource('test_data_source.tds')
# datasource = Datasource('no_folder (local copy).tds')
# folder = tfo.Folder('Test')
# datasource.folders_common.add(folder)
# datasource.save()
# datasource = Datasource('test_data_source.tds')
# datasource = Datasource('no_folder (local copy).tds')
pprint(datasource.folders_common)
# pprint(datasource.folders_common)
# datasource.save()
# datasource.unzip()
# Connect to Tableau Server
# ts = TableauServer(**settings['tableau_login'])
# ts.get_datasource(datasource_id='Jobs')
# datasources = [(d.id, d.name) for d in ts.get_datasources()]
# pprint(datasources)

# Loop through the datasources
# for d in datasources:
# for path in os.listdir():
# if path != 'Jobs.tdsx':
# continue
# path = ts.download_datasource(datasource_id=d.id, include_extract=True)
# print('Downloaded:', path)
# datasource = Datasource(path)
# pprint(datasource.folders_common.folder)
# print(datasource.columns['job_id'].caption)
# datasource.columns['job_id'].caption = 'Job ID RENAMED'
# datasource._tree.write(path.replace('.tdsx', '.tds'))
# datasource.save()
# datasource = Datasource(path)
# print(datasource.columns['job_id'].caption)
# column = tfo.Column(
# name='FRIENDLY_NAME',
# caption='Friendly Name',
# datatype='string',
# type='ordinal',
# role='dimension',
# desc='Nice and friendly',
# )
# datasource.enforce_column(column, remote_name='ORG_ID', folder_name='Org')
# print(datasource.folders.get('Org'))
# print(datasource.datasource_metadata.get('ORG_ID'))
# print(datasource.extract_metadata.get('ORG_ID'))
# print(datasource.datasource_mapping_cols.get(column.name))
# print(datasource.extract_mapping_cols.get(column.name))
# path = ts.download_datasource(datasource_id=d.id, include_extract=True)
# print('Downloaded:', path)
# datasource = Datasource(path)
# pprint(datasource.folders_common.folder)
# print(datasource.columns['job_id'].caption)
# datasource.columns['job_id'].caption = 'Job ID RENAMED'
# datasource._tree.write(path.replace('.tdsx', '.tds'))
# datasource.save()
# datasource = Datasource(path)
# print(datasource.columns['job_id'].caption)
# column = tfo.Column(
# name='FRIENDLY_NAME',
# caption='Friendly Name',
# datatype='string',
# type='ordinal',
# role='dimension',
# desc='Nice and friendly',
# )
# datasource.enforce_column(column, remote_name='ORG_ID', folder_name='Org')
# print(datasource.folders.get('Org'))
# print(datasource.datasource_metadata.get('ORG_ID'))
# print(datasource.extract_metadata.get('ORG_ID'))
# print(datasource.datasource_mapping_cols.get(column.name))
# print(datasource.extract_mapping_cols.get(column.name))

# ### ### ### ### ### ### ###

# Cleanup lingering files, if there are any
# os.chdir('..')
# shutil.rmtree(tmp_folder)
print(f'Script finished in {round(time() - start)} seconds')
4 changes: 2 additions & 2 deletions tableau_utilities/scripts/datasource.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ def datasource(args, server=None):
# Downloads the datasource from Tableau Server if the datasource is not local
if location == 'online':
print(f'{color.fg_cyan}...Downloading {datasource_name}...{color.reset}')
d = server.get_datasource(datasource_id, datasource_name, project_name)
datasource_path = server.download_datasource(d.id, include_extract=include_extract)
d = server.get.datasource(datasource_id, datasource_name, project_name)
datasource_path = server.download.datasource(d.id, include_extract=include_extract)
print(f'{color.fg_green}{symbol.success} Downloaded Datasource:', f'{color.fg_yellow}{datasource_path}{color.reset}', '\n')

datasource_file_name = os.path.basename(datasource_path)
Expand Down
4 changes: 2 additions & 2 deletions tableau_utilities/scripts/gen_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,14 +282,14 @@ def generate_config(args, server: TableauServer = None):
datasource_name = Path(datasource_path).stem
# Download the datasouce and set values for
elif location == 'online':
obj = server.get_datasource(id, datasource_name, project_name)
obj = server.get.datasource(id, datasource_name, project_name)
id = obj.id
datasource_name = obj.name
print(f'{color.fg_yellow}GETTING DATASOURCE {symbol.arrow_r} '
f'{color.fg_grey}ID: {id} {symbol.sep} '
f'NAME: {datasource_name} {symbol.sep} '
f'INCLUDE EXTRACT: false{color.reset}')
datasource_path = server.download_datasource(id, include_extract=False)
datasource_path = server.download.datasource(id, include_extract=False)

print(f'{color.fg_yellow}BUILDING CONFIG {symbol.arrow_r} '
f'{color.fg_grey}{datasource_name} {symbol.sep} {datasource_path}{color.reset}')
Expand Down
2 changes: 1 addition & 1 deletion tableau_utilities/scripts/server_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def server_info(args, server):
if list_object:
print(f'{color.fg_cyan}{list_object.title()}s:{color.reset}')
# Get server objects, and convert them to dict
object_list = [o.__dict__ for o in getattr(server, f'get_{list_object.lower()}s')()]
object_list = [o.__dict__ for o in getattr(server.get, f'{list_object.lower()}s')()]
sorted_records = sorted(object_list, key=lambda d: d[sort_field])
if format == 'names':
for record in sorted_records:
Expand Down
12 changes: 6 additions & 6 deletions tableau_utilities/scripts/server_operate.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def server_operate(args, server):

# Download all objects, and return early if all objects have been downloaded
if all_objects and download:
object_list = [o for o in getattr(server, f'get_{object_type}s')()]
object_list = [o for o in getattr(server.get, f'{object_type}s')()]
for o in object_list:
print(
f'{color.fg_yellow}DOWNLOADING {object_type} {symbol.arrow_r} {color.fg_grey}'
Expand All @@ -49,12 +49,12 @@ def server_operate(args, server):
f'PROJECT: {o.project_name} {symbol.sep} '
f'INCLUDE EXTRACT: {include_extract}{color.reset}'
)
res = getattr(server, f'download_{object_type}')(o.id, include_extract=include_extract)
res = getattr(server.download, f'{object_type}')(o.id, include_extract=include_extract)
color_print(f'{symbol.success} {res}', fg='green')
return f'Successfully downloaded all {object_type}s'

# Gets the ID, name, and project from the object in Tableau Server
obj = getattr(server, f'get_{object_type}')(object_id, object_name, project_name)
obj = getattr(server.get, object_type)(object_id, object_name, project_name)
object_id = obj.id or object_id
object_name = obj.name or object_name
project_name = obj.project_name or project_name
Expand All @@ -67,7 +67,7 @@ def server_operate(args, server):
f'PROJECT: {project_name} {symbol.sep} '
f'INCLUDE EXTRACT: {include_extract}{color.reset}'
)
res: str = getattr(server, f'download_{object_type}')(object_id, include_extract=include_extract)
res: str = getattr(server.download, object_type)(object_id, include_extract=include_extract)
color_print(f'{symbol.success} {res}', fg='green')
elif publish:
print(
Expand All @@ -76,7 +76,7 @@ def server_operate(args, server):
f'NAME: {object_name} {symbol.sep} '
f'PROJECT NAME: {project_name}{color.reset}'
)
res: Datasource | Workbook = getattr(server, f'publish_{object_type}')(
res: Datasource | Workbook = getattr(server.publish, object_type)(
file_path, object_id, object_name, project_name,
connection=connection
)
Expand All @@ -100,5 +100,5 @@ def server_operate(args, server):
f'NAME: {object_name} {symbol.sep} '
f'PROJECT NAME: {project_name}{color.reset}'
)
res: Job = getattr(server, f'refresh_{object_type}')(object_id)
res: Job = getattr(server.refresh, object_type)(object_id)
color_print(f'{symbol.success} {res}', fg='green')
68 changes: 68 additions & 0 deletions tableau_utilities/tableau_server/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from requests import Session
from tableau_utilities.tableau_server.static import validate_response


class Base:
def __init__(self, parent):
self.session: Session = parent.session
self.user: str = parent.user
self._pw: str = parent._pw
self._personal_access_token_secret: str = parent._personal_access_token_secret
self.personal_access_token_name: str = parent.personal_access_token_name
self.host: str = parent.host
self.site: str = parent.site
self.api: float = parent.api
self._auth_token = parent._auth_token
self.url: str = parent.url
self.get = parent.get if hasattr(parent, 'get') else None

def _get(self, url, headers=None, **params):
""" GET request for the Tableau REST API
Args:
url (str): URL endpoint for GET call
headers (dict): GET call header
Returns: The response content as a JSON dict
"""

res = self.session.get(url, headers=headers, **params)
return validate_response(res)

def _post(self, url, json=None, headers=None, **params):
""" POST request for the Tableau REST API
Args:
url (str): URL endpoint for POST call
json (dict): The POST call JSON payload
headers (dict): POST call header
Returns: The response content as a JSON dict
"""
res = self.session.post(url, json=json, headers=headers, **params)
return validate_response(res)

def _put(self, url, json=None, headers=None, **params):
""" PUT request for the Tableau REST API
Args:
url (str): URL endpoint for PUT call
json (dict): The PUT call JSON payload
headers (dict): PUT call header
Returns: The response content as a JSON dict
"""
res = self.session.put(url, json=json, headers=headers, **params)
return validate_response(res)

def _delete(self, url, headers=None, **params):
""" DELETE request for the Tableau REST API
Args:
url (str): URL endpoint for DELETE call
headers (dict): DELETE call header
Returns: The response content as a JSON dict
"""
res = self.session.delete(url, headers=headers, **params)
return validate_response(res)
43 changes: 43 additions & 0 deletions tableau_utilities/tableau_server/create.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from requests import Session
from tableau_utilities.tableau_server.base import Base


class Create(Base):
def __init__(self, parent):
super().__init__(parent)

def project(self, name, description='', content_permissions='LockedToProject'):
""" Creates a project.
Args:
name (str): The name of the project
description (str): The description of the project
content_permissions (str): The content permissions, e.g. LockedToProject
"""
self._post(
f'{self.url}/projects',
{
'project': {
'name': name,
'description': description,
'contentPermissions': content_permissions
}
}
)

def group(self, name, minimum_site_role='Viewer'):
""" Creates a group.
Args:
name (str): The name of the Group
minimum_site_role (str): The minimum site role of the group, e.g. Viewer
"""
self._post(
f'{self.url}/groups',
{
'group': {
'name': name,
'minimumSiteRole': minimum_site_role
}
}
)
Loading

0 comments on commit 8e7ed08

Please sign in to comment.