8000 v0.14.0 to master by shinchris · Pull Request #725 · tableau/server-client-python · GitHub
[go: up one dir, main page]

Skip to content

v0.14.0 to master #725

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Nov 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ target/
# pyenv
.python-version

# poetry
poetry.lock
pyproject.toml

# celery beat schedule file
celerybeat-schedule

Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
## 0.14.0 (6 Nov 2020)
* Added django-style filtering and sorting (#615)
* Added encoding tag-name before deleting (#687)
* Added 'Execute' Capability to permissions (#700)
* Added support for publishing workbook using file objects (#704)
* Added new fields to datasource_item (#705)
* Added all fields for users.get to get email and fullname (#713)
* Added support publishing datasource using file objects (#714)
* Improved request options by removing manual query param generation (#686)
* Improved publish_workbook sample to take in site (#694)
* Improved schedules.update() by removing constraint that required an interval (#711)
* Fixed site update/create not checking booleans properly (#723)

## 0.13 (1 Sept 2020)
* Added notes field to JobItem (#571)
* Added webpage_url field to WorkbookItem (#661)
Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ The following people have contributed to this project to make it possible, and w
* [Stephen Mitchell](https://github.com/scuml)
* [absentmoose](https://github.com/absentmoose)
* [Paul Vickers](https://github.com/paulvic)
* [Madhura Selvarajan](https://github.com/maddy-at-leisure)
* [Niklas Nevalainen](https://github.com/nnevalainen)

## Core Team

Expand Down
5 changes: 3 additions & 2 deletions samples/publish_workbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ def main():
parser = argparse.ArgumentParser(description='Publish a workbook to server.')
parser.add_argument('--server', '-s', required=True, help='server address')
parser.add_argument('--username', '-u', required=True, help='username to sign into server')
parser.add_argument('--filepath', '-f', required=True, help='filepath to the workbook to publish')
parser.add_argument('--filepath', '-f', required=True, help='computer filepath of the workbook to publish')
parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error',
help='desired logging level (set to error by default)')
parser.add_argument('--as-job', '-a', help='Publishing asynchronously', action='store_true')
parser.add_argument('--site', '-S', default='', help='id (contentUrl) of site to sign into')

args = parser.parse_args()

Expand All @@ -41,7 +42,7 @@ def main():
logging.basicConfig(level=logging_level)

# Step 1: Sign in to server.
tableau_auth = TSC.TableauAuth(args.username, password)
tableau_auth = TSC.TableauAuth(args.username, password, site_id=args.site)
server = TSC.Server(args.server)

overwrite_true = TSC.Server.PublishMode.Overwrite
Expand Down
40 changes: 40 additions & 0 deletions tableauserverclient/filesys_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,43 @@ def make_download_path(filepath, filename):
download_path = filepath + os.path.splitext(filename)[1]

return download_path


def get_file_object_size(file):
# Returns the size of a file object
file.seek(0, os.SEEK_END)
file_size = file.tell()
file.seek(0)
return file_size


def get_file_type(file):
# Tableau workbooks (twb) and data sources (tds) are both stored as xml files.
# Packaged workbooks (twbx) and data sources (tdsx) are zip files
# containing original files accompanied with supporting local files.

# This reference lists magic file signatures: https://www.garykessler.net/library/file_sigs.html
MAGIC_BYTES = {
'zip': bytes.fromhex("504b0304"),
'tde': bytes.fromhex("20020162"),
'xml': bytes.fromhex("3c3f786d6c20"),
'hyper': bytes.fromhex("487970657208000001000000")
}

# Peek first bytes of a file
first_bytes = file.read(32)

file_type = None
for ft, signature in MAGIC_BYTES.items():
if first_bytes.startswith(signature):
file_type = ft
break

# Return pointer back to start
file.seek(0)

if file_type is None:
error = "Unknown file type!"
raise ValueError(error)

return file_type
148 changes: 111 additions & 37 deletions tableauserverclient/models/datasource_item.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,50 @@
import xml.etree.ElementTree as ET
from .exceptions import UnpopulatedPropertyError
from .property_decorators import property_not_nullable, property_is_boolean
from .property_decorators import property_not_nullable, property_is_boolean, property_is_enum
from .tag_item import TagItem
from ..datetime_helpers import parse_datetime
import copy


class DatasourceItem(object):
class AskDataEnablement:
Enabled = 'Enabled'
Disabled = 'Disabled'
SiteDefault = 'SiteDefault'

def __init__(self, project_id, name=None):
self._ask_data_enablement = None
self._certified = None
self._certification_note = None
self._connections = None
self._content_url = None
self._created_at = None
self._datasource_type = None
self._encrypt_extracts = None
self._has_extracts = None
self._id = None
self._initial_tags = set()
self._project_name = None
self._updated_at = None
self._certified = None
self._certification_note = None
self._use_remote_query_agent = None
self._webpage_url = None
self.description = None
self.name = name
self.owner_id = None
self.project_id = project_id
self.tags = set()

self._permissions = None

@property
def ask_data_enablement(self):
return self._ask_data_enablement

@ask_data_enablement.setter
@property_is_enum(AskDataEnablement)
def ask_data_enablement(self, value):
self._ask_data_enablement = value

@property
def connections(self):
if self._connections is None:
Expand Down Expand Up @@ -65,6 +85,19 @@ def certification_note(self):
def certification_note(self, value):
self._certification_note = value

@property
def encrypt_extracts(self):
return self._encrypt_extracts

@encrypt_extracts.setter
@property_is_boolean
def encrypt_extracts(self, value):
self._encrypt_extracts = value

@property
def has_extracts(self):
return self._has_extracts

@property
def id(self):
return self._id
Expand All @@ -90,6 +123,19 @@ def datasource_type(self):
def updated_at(self):
return self._updated_at

@property
def use_remote_query_agent(self):
return self._use_remote_query_agent

@use_remote_query_agent.setter
@property_is_boolean
def use_remote_query_agent(self, value):
self._use_remote_query_agent = value

@pr 10000 operty
def webpage_url(self):
return self._webpage_url

def _set_connections(self, connections):
self._connections = connections

Expand All @@ -100,38 +146,53 @@ def _parse_common_elements(self, datasource_xml, ns):
if not isinstance(datasource_xml, ET.Element):
datasource_xml = ET.fromstring(datasource_xml).find('.//t:datasource', namespaces=ns)
if datasource_xml is not None:
(_, _, _, _, _, updated_at, _, project_id, project_name, owner_id,
certified, certification_note) = self._parse_element(datasource_xml, ns)
self._set_values(None, None, None, None, None, updated_at, None, project_id,
project_name, owner_id, certified, certification_note)
(ask_data_enablement, certified, certification_note, _, _, _, _, encrypt_extracts, has_extracts,
_, _, owner_id, project_id, project_name, _, updated_at, use_remote_query_agent,
webpage_url) = self._parse_element(datasource_xml, ns)
self._set_values(ask_data_enablement, certified, certification_note, None, None, None, None,
encrypt_extracts, has_extracts, None, None, owner_id, project_id, project_name, None,
updated_at, use_remote_query_agent, webpage_url)
return self

def _set_values(self, id, name, datasource_type, content_url, created_at,
updated_at, tags, project_id, project_name, owner_id, certified, certification_note):
if id is not None:
self._id = id
if name:
self.name = name
if datasource_type:
self._datasource_type = datasource_type
def _set_values(self, ask_data_enablement, certified, certification_note, content_url, created_at, datasource_type,
description, encrypt_extracts, has_extracts, id_, name, owner_id, project_id, project_name, tags,
updated_at, use_remote_query_agent, webpage_url):
if ask_data_enablement is not None:
self._ask_data_enablement = ask_data_enablement
if certification_note:
self.certification_note = certification_note
self.certified = certified # Always True/False, not conditional
if content_url:
self._content_url = content_url
if created_at:
self._created_at = created_at
if updated_at:
self._updated_at = updated_at
if tags:
self.tags = tags
self._initial_tags = copy.copy(tags)
if datasource_type:
self._datasource_type = datasource_type
if description:
self.description = description
if encrypt_extracts is not None:
self.encrypt_extracts = str(encrypt_extracts).lower() == 'true'
if has_extracts is not None:
self._has_extracts = str(has_extracts).lower() == 'true'
if id_ is not None:
self._id = id_
if name:
self.name = name
if owner_id:
self.owner_id = owner_id
if project_id:
self.project_id = project_id
if project_name:
self._project_name = project_name
if owner_id:
self.owner_id = owner_id
if certification_note:
self.certification_note = certification_note
self.certified = certified # Always True/False, not conditional
if tags:
self.tags = tags
self._initial_tags = copy.copy(tags)
if updated_at:
self._updated_at = updated_at
if use_remote_query_agent is not None:
self._use_remote_query_agent = str(use_remote_query_agent).lower() == 'true'
if webpage_url:
self._webpage_url = webpage_url

@classmethod
def from_response(cls, resp, ns):
Expand All @@ -140,25 +201,32 @@ def from_response(cls, resp, ns):
all_datasource_xml = parsed_response.findall('.//t:datasource', namespaces=ns)

for datasource_xml in all_datasource_xml:
(id_, name, datasource_type, content_url, created_at, updated_at,
tags, project_id, project_name, owner_id,
certified, certification_note) = cls._parse_element(datasource_xml, ns)
(ask_data_enablement, certified, certification_note, content_url, created_at, datasource_type,
description, encrypt_extracts, has_extracts, id_, name, owner_id, project_id, project_name, tags,
updated_at, use_remote_query_agent, webpage_url) = cls._parse_element(datasource_xml, ns)
datasource_item = cls(project_id)
datasource_item._set_values(id_, name, datasource_type, content_url, created_at, updated_at,
tags, None, project_name, owner_id, certified, certification_note)
datasource_item._set_values(ask_data_enablement, certified, certification_note, content_url,
created_at, datasource_type, description, encrypt_extracts,
has_extracts, id_, name, owner_id, None, project_name, tags, updated_at,
use_remote_query_agent, webpage_url)
all_datasource_items.append(datasource_item)
return all_datasource_items
F987
@staticmethod
def _parse_element(datasource_xml, ns):
id_ = datasource_xml.get('id', None)
name = datasource_xml.get('name', None)
datasource_type = datasource_xml.get('type', None)
certification_note = datasource_xml.get('certificationNote', None)
certified = str(datasource_xml.get('isCertified', None)).lower() == 'true'
content_url = datasource_xml.get('contentUrl', None)
created_at = parse_datetime(datasource_xml.get('createdAt', None))
datasource_type = datasource_xml.get('type', None)
description = datasource_xml.get('description', None)
encrypt_extracts = datasource_xml.get('encryptExtracts', None)
has_extracts = datasource_xml.get('hasExtracts', None)
id_ = datasource_xml.get('id', None)
name = datasource_xml.get('name', None)
updated_at = parse_datetime(datasource_xml.get('updatedAt', None))
certification_note = datasource_xml.get('certificationNote', None)
certified = str(datasource_xml.get('isCertified', None)).lower() == 'true'
use_remote_query_agent = datasource_xml.get('useRemoteQueryAgent', None)
webpage_url = datasource_xml.get('webpageUrl', None)

tags = None
tags_elem = datasource_xml.find('.//t:tags', namespaces=ns)
Expand All @@ -177,5 +245,11 @@ def _parse_element(datasource_xml, ns):
if owner_elem is not None:
owner_id = owner_elem.get('id', None)

return (id_, name, datasource_type, content_url, created_at, updated_at, tags, project_id,
project_name, owner_id, certified, certification_note)
ask_data_enablement = None
ask_data_elem = datasource_xml.find('.//t:askData', namespaces=ns)
if ask_data_elem is not None:
ask_data_enablement = ask_data_elem.get('enablement', None)

return (ask_data_enablement, certified, certification_note, content_url, created_at,
datasource_type, description, encrypt_extracts, has_extracts, id_, name, owner_id,
project_id, project_name, tags, updated_at, use_remote_query_agent, webpage_url)
1 change: 1 addition & 0 deletions tableauserverclient/models/permissions_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class Capability:
ChangePermissions = 'ChangePermissions'
Connect = 'Connect'
Delete = 'Delete'
Execute = 'Execute'
ExportData = 'ExportData'
ExportImage = 'ExportImage'
ExportXml = 'ExportXml'
Expand Down
3 changes: 2 additions & 1 deletion tableauserverclient/models/view_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from ..datetime_helpers import parse_datetime
from .exceptions import UnpopulatedPropertyError
from .tag_item import TagItem
import copy


class ViewItem(object):
Expand Down Expand Up @@ -158,7 +159,7 @@ def from_xml_element(cls, parsed_response, ns, workbook_id=''):
if tags_elem is not None:
tags = TagItem.from_xml_element(tags_elem, ns)
view_item.tags = tags
view_item._initial_tags = tags
view_item._initial_tags = copy.copy(tags)

all_view_items.append(view_item)
return all_view_items
6 changes: 6 additions & 0 deletions tableauserverclient/server/endpoint/auth_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,9 @@ def switch_site(self, site_item):
self.parent_srv._set_auth(site_id, user_id, auth_token)
logger.info('Signed into {0} as user with id {1}'.format(self.parent_srv.server_address, user_id))
return Auth.contextmgr(self.sign_out)

@api(version="3.10")
def revoke_all_server_admin_tokens(self):
url = "{0}/{1}".format(self.baseurl, 'revokeAllServerAdminTokens')
self.post_request(url, '')
logger.info('Revoked all tokens for all server admins')
Loading
0