Skip to content
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

Add connection credentials #80

Merged
merged 12 commits into from Oct 27, 2016
2 changes: 1 addition & 1 deletion tableauserverclient/__init__.py
@@ -1,5 +1,5 @@
from .namespace import NAMESPACE
from .models import ConnectionItem, DatasourceItem,\
from .models import ConnectionCredentials, ConnectionItem, DatasourceItem,\
GroupItem, PaginationItem, ProjectItem, ScheduleItem, \
SiteItem, TableauAuth, UserItem, ViewItem, WorkbookItem, UnpopulatedPropertyError, \
HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval, IntervalItem
Expand Down
1 change: 1 addition & 0 deletions tableauserverclient/models/__init__.py
@@ -1,3 +1,4 @@
from .connection_credentials import ConnectionCredentials
from .connection_item import ConnectionItem
from .datasource_item import DatasourceItem
from .exceptions import UnpopulatedPropertyError
Expand Down
24 changes: 24 additions & 0 deletions tableauserverclient/models/connection_credentials.py
@@ -0,0 +1,24 @@
from .property_decorators import property_is_boolean


class ConnectionCredentials(object):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a docstring on this class that tells users they should delete this object when they're done using it?

"""Connection Credentials for Workbooks and Datasources publish request.

Consider removing this object and other variables holding secrets
as soon as possible after use to avoid them hanging around in memory.

"""

def __init__(self, name, password, embed=True):
self.name = name
self.password = password
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Reorder these to match the order in the function signature

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if I fix order of property set in credentials do you want me to fix auth while I am doing it as I copied the code for this class from auth. New code would be

class TableauAuth(object):
    def __init__(self, username, password, site='', user_id_to_impersonate=None):
        self.username = username
        self.password = password
        self.site = site
        self.user_id_to_impersonate = user_id_to_impersonate

self.embed = embed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make this an explicit property and add the property_is_boolean decorator


@property
def embed(self):
return self._embed

@embed.setter
@property_is_boolean
def embed(self, value):
self._embed = value
8 changes: 5 additions & 3 deletions tableauserverclient/server/endpoint/datasources_endpoint.py
Expand Up @@ -94,7 +94,7 @@ def update(self, datasource_item):
return updated_datasource._parse_common_tags(server_response.content)

# Publish datasource
def publish(self, datasource_item, file_path, mode):
def publish(self, datasource_item, file_path, mode, connection_credentials=None):
if not os.path.isfile(file_path):
error = "File path does not lead to an existing file."
raise IOError(error)
Expand Down Expand Up @@ -122,14 +122,16 @@ def publish(self, datasource_item, file_path, mode):
logger.info('Publishing {0} to server with chunking method (datasource over 64MB)'.format(filename))
upload_session_id = Fileuploads.upload_chunks(self.parent_srv, file_path)
url = "{0}&uploadSessionId={1}".format(url, upload_session_id)
xml_request, content_type = RequestFactory.Datasource.publish_req_chunked(datasource_item)
xml_request, content_type = RequestFactory.Datasource.publish_req_chunked(datasource_item,
connection_credentials)
else:
logger.info('Publishing {0} to server'.format(filename))
with open(file_path, 'rb') as f:
file_contents = f.read()
xml_request, content_type = RequestFactory.Datasource.publish_req(datasource_item,
filename,
file_contents)
file_contents,
connection_credentials)
server_response = self.post_request(url, xml_request, content_type)
new_datasource = DatasourceItem.from_response(server_response.content)[0]
logger.info('Published {0} (ID: {1})'.format(filename, new_datasource.id))
Expand Down
8 changes: 5 additions & 3 deletions tableauserverclient/server/endpoint/workbooks_endpoint.py
Expand Up @@ -140,7 +140,7 @@ def populate_preview_image(self, workbook_item):
logger.info('Populated preview image for workbook (ID: {0})'.format(workbook_item.id))

# Publishes workbook. Chunking method if file over 64MB
def publish(self, workbook_item, file_path, mode):
def publish(self, workbook_item, file_path, mode, connection_credentials=None):
if not os.path.isfile(file_path):
error = "File path does not lead to an existing file."
raise IOError(error)
Expand Down Expand Up @@ -171,14 +171,16 @@ def publish(self, workbook_item, file_path, mode):
logger.info('Publishing {0} to server with chunking method (workbook over 64MB)'.format(filename))
upload_session_id = Fileuploads.upload_chunks(self.parent_srv, file_path)
url = "{0}&uploadSessionId={1}".format(url, upload_session_id)
xml_request, content_type = RequestFactory.Workbook.publish_req_chunked(workbook_item)
xml_request, content_type = RequestFactory.Workbook.publish_req_chunked(workbook_item,
connection_credentials)
else:
logger.info('Publishing {0} to server'.format(filename))
with open(file_path, 'rb') as f:
file_contents = f.read()
xml_request, content_type = RequestFactory.Workbook.publish_req(workbook_item,
filename,
file_contents)
file_contents,
connection_credentials)
server_response = self.post_request(url, xml_request, content_type)
new_workbook = WorkbookItem.from_response(server_response.content)[0]
logger.info('Published {0} (ID: {1})'.format(filename, new_workbook.id))
Expand Down
30 changes: 20 additions & 10 deletions tableauserverclient/server/request_factory.py
Expand Up @@ -30,12 +30,17 @@ def signin_req(self, auth_item):


class DatasourceRequest(object):
def _generate_xml(self, datasource_item):
def _generate_xml(self, datasource_item, connection_credentials=None):
xml_request = ET.Element('tsRequest')
datasource_element = ET.SubElement(xml_request, 'datasource')
datasource_element.attrib['name'] = datasource_item.name
project_element = ET.SubElement(datasource_element, 'project')
project_element.attrib['id'] = datasource_item.project_id
if connection_credentials:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(not a shipblocker)
Since this is common code, we might consider pulling this into its own helper method.

credentials_element = ET.SubElement(datasource_element, 'connectionCredentials')
credentials_element.attrib['name'] = connection_credentials.name
credentials_element.attrib['password'] = connection_credentials.password
credentials_element.attrib['embed'] = 'true' if connection_credentials.embed else 'false'
return ET.tostring(xml_request)

def update_req(self, datasource_item):
Expand All @@ -49,15 +54,15 @@ def update_req(self, datasource_item):
owner_element.attrib['id'] = datasource_item.owner_id
return ET.tostring(xml_request)

def publish_req(self, datasource_item, filename, file_contents):
xml_request = self._generate_xml(datasource_item)
def publish_req(self, datasource_item, filename, file_contents, connection_credentials=None):
xml_request = self._generate_xml(datasource_item, connection_credentials)

parts = {'request_payload': ('', xml_request, 'text/xml'),
'tableau_datasource': (filename, file_contents, 'application/octet-stream')}
return _add_multipart(parts)

def publish_req_chunked(self, datasource_item):
xml_request = self._generate_xml(datasource_item)
def publish_req_chunked(self, datasource_item, connection_credentials=None):
xml_request = self._generate_xml(datasource_item, connection_credentials)

parts = {'request_payload': ('', xml_request, 'text/xml')}
return _add_multipart(parts)
Expand Down Expand Up @@ -260,14 +265,19 @@ def add_req(self, user_item):


class WorkbookRequest(object):
def _generate_xml(self, workbook_item):
def _generate_xml(self, workbook_item, connection_credentials=None):
xml_request = ET.Element('tsRequest')
workbook_element = ET.SubElement(xml_request, 'workbook')
workbook_element.attrib['name'] = workbook_item.name
if workbook_item.show_tabs:
workbook_element.attrib['showTabs'] = str(workbook_item.show_tabs).lower()
project_element = ET.SubElement(workbook_element, 'project')
project_element.attrib['id'] = workbook_item.project_id
if connection_credentials:
credentials_element = ET.SubElement(workbook_element, 'connectionCredentials')
credentials_element.attrib['name'] = connection_credentials.name
credentials_element.attrib['password'] = connection_credentials.password
credentials_element.attrib['embed'] = 'true' if connection_credentials.embed else 'false'
return ET.tostring(xml_request)

def update_req(self, workbook_item):
Expand All @@ -283,15 +293,15 @@ def update_req(self, workbook_item):
owner_element.attrib['id'] = workbook_item.owner_id
return ET.tostring(xml_request)

def publish_req(self, workbook_item, filename, file_contents):
xml_request = self._generate_xml(workbook_item)
def publish_req(self, workbook_item, filename, file_contents, connection_credentials=None):
xml_request = self._generate_xml(workbook_item, connection_credentials)

parts = {'request_payload': ('', xml_request, 'text/xml'),
'tableau_workbook': (filename, file_contents, 'application/octet-stream')}
return _add_multipart(parts)

def publish_req_chunked(self, workbook_item):
xml_request = self._generate_xml(workbook_item)
def publish_req_chunked(self, workbook_item, connection_credentials=None):
xml_request = self._generate_xml(workbook_item, connection_credentials)

parts = {'request_payload': ('', xml_request, 'text/xml')}
return _add_multipart(parts)
Expand Down