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
5 changes: 5 additions & 0 deletions tableauserverclient/models/connection_credentials.py
@@ -0,0 +1,5 @@
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?

def __init__(self, name, password, embed=True):
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

self.name = name
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'] = str(connection_credentials.embed).lower()
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'] = str(connection_credentials.embed).lower()
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 do a more explicit assignment instead of relying on str(True).lower() == 'true'?

credentials_element.attrib['embed'] = 'true' if connection_credentials.embed else 'false'

Or even more explicit

if connection_credentials.embed:
    credentials_element.attrib['embed'] = 'true'
else:
    credentials_element.attrib['embed'] = '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