diff --git a/.travis.yml b/.travis.yml index 33e133203..01ad30886 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,4 +15,4 @@ script: # Tests - python setup.py test # pep8 - disabled for now until we can scrub the files to make sure we pass before turning it on - - pycodestyle tableauserverclient test + - pycodestyle tableauserverclient test samples diff --git a/CHANGELOG.md b/CHANGELOG.md index 12a95d31b..c4fece541 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ -## 0.5 (11 Sept 2017) +## 0.5.1 (21 Sept 2017 + +* Fix a critical issue caused by #224 that was the result of lack of test coverage (#226) + +## 0.5 (20 Sept 2017) * Added revision settings to update site (#187) * Added support for certified data sources (#189) diff --git a/samples/create_project.py b/samples/create_project.py index c68b992a9..744b056d4 100644 --- a/samples/create_project.py +++ b/samples/create_project.py @@ -24,6 +24,7 @@ def create_project(server, project_item): print('We have already created this project: %s' % project_item.name) sys.exit(1) + def main(): parser = argparse.ArgumentParser(description='Get all of the refresh tasks available on a server') parser.add_argument('--server', '-s', required=True, help='server address') @@ -64,5 +65,6 @@ def main(): grand_child_project = TSC.ProjectItem(name='Grand Child Project', parent_id=child_project.id) grand_child_project = create_project(server, grand_child_project) + if __name__ == '__main__': main() diff --git a/samples/download_view_image.py b/samples/download_view_image.py index 6038bd337..2da232061 100644 --- a/samples/download_view_image.py +++ b/samples/download_view_image.py @@ -64,5 +64,6 @@ def main(): print("View image saved to {0}".format(args.filepath)) + if __name__ == '__main__': main() diff --git a/samples/explore_datasource.py b/samples/explore_datasource.py index b5fe68390..e740d60f1 100644 --- a/samples/explore_datasource.py +++ b/samples/explore_datasource.py @@ -79,5 +79,6 @@ def main(): sample_datasource.tags = original_tag_set server.datasources.update(sample_datasource) + if __name__ == '__main__': main() diff --git a/samples/filter_sort_groups.py b/samples/filter_sort_groups.py index 6ed6fc773..3b585327d 100644 --- a/samples/filter_sort_groups.py +++ b/samples/filter_sort_groups.py @@ -87,5 +87,6 @@ def main(): for group in matching_groups: print(group.name) + if __name__ == '__main__': main() diff --git a/samples/list.py b/samples/list.py new file mode 100644 index 000000000..ec2ff9a6b --- /dev/null +++ b/samples/list.py @@ -0,0 +1,51 @@ +#### +# This script demonstrates how to list all of the workbooks or datasources +# +# To run the script, you must have installed Python 2.7.X or 3.3 and later. +#### + +import argparse +import getpass +import logging + +import tableauserverclient as TSC + + +def main(): + parser = argparse.ArgumentParser(description='Get all of the refresh tasks available on a 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('--site', '-S', default=None) + parser.add_argument('-p', default=None) + + parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', + help='desired logging level (set to error by default)') + + parser.add_argument('resource_type', choices=['workbook', 'datasource']) + + args = parser.parse_args() + + if args.p is None: + password = getpass.getpass("Password: ") + else: + password = args.p + + # Set logging level based on user input, or error by default + logging_level = getattr(logging, args.logging_level.upper()) + logging.basicConfig(level=logging_level) + + # SIGN IN + tableau_auth = TSC.TableauAuth(args.username, password, args.site) + server = TSC.Server(args.server, use_server_version=True) + with server.auth.sign_in(tableau_auth): + endpoint = { + 'workbook': server.workbooks, + 'datasource': server.datasources + }.get(args.resource_type) + + for resource in TSC.Pager(endpoint.get): + print(resource.id, resource.name) + + +if __name__ == '__main__': + main() diff --git a/samples/refresh.py b/samples/refresh.py new file mode 100644 index 000000000..dd39bc6f6 --- /dev/null +++ b/samples/refresh.py @@ -0,0 +1,54 @@ +#### +# This script demonstrates how to use trigger a refresh on a datasource or workbook +# +# To run the script, you must have installed Python 2.7.X or 3.3 and later. +#### + +import argparse +import getpass +import logging + +import tableauserverclient as TSC + + +def main(): + parser = argparse.ArgumentParser(description='Get all of the refresh tasks available on a 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('--site', '-S', default=None) + parser.add_argument('-p', default=None) + + parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', + help='desired logging level (set to error by default)') + + parser.add_argument('resource_type', choices=['workbook', 'datasource']) + parser.add_argument('resource_id') + + args = parser.parse_args() + + if args.p is None: + password = getpass.getpass("Password: ") + else: + password = args.p + + # Set logging level based on user input, or error by default + logging_level = getattr(logging, args.logging_level.upper()) + logging.basicConfig(level=logging_level) + + # SIGN IN + tableau_auth = TSC.TableauAuth(args.username, password, args.site) + server = TSC.Server(args.server, use_server_version=True) + with server.auth.sign_in(tableau_auth): + endpoint = { + 'workbook': server.workbooks, + 'datasource': server.datasources + }.get(args.resource_type) + + refresh_func = endpoint.refresh + resource = endpoint.get_by_id(args.resource_id) + + print(refresh_func(resource)) + + +if __name__ == '__main__': + main() diff --git a/tableauserverclient/__init__.py b/tableauserverclient/__init__.py index 7e94583df..c5840d7b6 100644 --- a/tableauserverclient/__init__.py +++ b/tableauserverclient/__init__.py @@ -1,6 +1,6 @@ from .namespace import NEW_NAMESPACE as DEFAULT_NAMESPACE from .models import ConnectionCredentials, ConnectionItem, DatasourceItem,\ - GroupItem, PaginationItem, ProjectItem, ScheduleItem, \ + GroupItem, JobItem, PaginationItem, ProjectItem, ScheduleItem, \ SiteItem, TableauAuth, UserItem, ViewItem, WorkbookItem, UnpopulatedPropertyError, \ HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval, IntervalItem, TaskItem, \ SubscriptionItem diff --git a/tableauserverclient/models/__init__.py b/tableauserverclient/models/__init__.py index a593b9d56..1ff6be869 100644 --- a/tableauserverclient/models/__init__.py +++ b/tableauserverclient/models/__init__.py @@ -4,6 +4,7 @@ from .exceptions import UnpopulatedPropertyError from .group_item import GroupItem from .interval_item import IntervalItem, DailyInterval, WeeklyInterval, MonthlyInterval, HourlyInterval +from .job_item import JobItem from .pagination_item import PaginationItem from .project_item import ProjectItem from .schedule_item import ScheduleItem diff --git a/tableauserverclient/models/job_item.py b/tableauserverclient/models/job_item.py new file mode 100644 index 000000000..cc53765ed --- /dev/null +++ b/tableauserverclient/models/job_item.py @@ -0,0 +1,60 @@ +import xml.etree.ElementTree as ET +from .target import Target + + +class JobItem(object): + def __init__(self, id_, job_type, created_at, started_at=None, completed_at=None, finish_code=0): + self._id = id_ + self._type = job_type + self._created_at = created_at + self._started_at = started_at + self._completed_at = completed_at + self._finish_code = finish_code + + @property + def id(self): + return self._id + + @property + def type(self): + return self._type + + @property + def created_at(self): + return self._created_at + + @property + def started_at(self): + return self._started_at + + @property + def completed_at(self): + return self._completed_at + + @property + def finish_code(self): + return self._finish_code + + def __repr__(self): + return "".format(**self.__dict__) + + @classmethod + def from_response(cls, xml, ns): + parsed_response = ET.fromstring(xml) + all_tasks_xml = parsed_response.findall( + './/t:job', namespaces=ns) + + all_tasks = [JobItem._parse_element(x, ns) for x in all_tasks_xml] + + return all_tasks + + @classmethod + def _parse_element(cls, element, ns): + id_ = element.get('id', None) + type_ = element.get('type', None) + created_at = element.get('createdAt', None) + started_at = element.get('startedAt', None) + completed_at = element.get('completedAt', None) + finish_code = element.get('finishCode', -1) + return cls(id_, type_, created_at, started_at, completed_at, finish_code) diff --git a/tableauserverclient/server/__init__.py b/tableauserverclient/server/__init__.py index 9dde13324..12a640723 100644 --- a/tableauserverclient/server/__init__.py +++ b/tableauserverclient/server/__init__.py @@ -2,7 +2,7 @@ from .request_options import ImageRequestOptions, PDFRequestOptions, RequestOptions from .filter import Filter from .sort import Sort -from .. import ConnectionItem, DatasourceItem,\ +from .. import ConnectionItem, DatasourceItem, JobItem, \ GroupItem, PaginationItem, ProjectItem, ScheduleItem, SiteItem, TableauAuth,\ UserItem, ViewItem, WorkbookItem, TaskItem, SubscriptionItem from .endpoint import Auth, Datasources, Endpoint, Groups, Projects, Schedules, \ diff --git a/tableauserverclient/server/endpoint/__init__.py b/tableauserverclient/server/endpoint/__init__.py index 58f603b54..c75fe8519 100644 --- a/tableauserverclient/server/endpoint/__init__.py +++ b/tableauserverclient/server/endpoint/__init__.py @@ -3,6 +3,7 @@ from .endpoint import Endpoint from .exceptions import ServerResponseError, MissingRequiredFieldError, ServerInfoEndpointNotFoundError from .groups_endpoint import Groups +from .jobs_endpoint import Jobs from .projects_endpoint import Projects from .schedules_endpoint import Schedules from .server_info_endpoint import ServerInfo diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 882001f18..5abfeb075 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -5,6 +5,7 @@ from .. import RequestFactory, DatasourceItem, PaginationItem, ConnectionItem from ...filesys_helpers import to_filename from ...models.tag_item import TagItem +from ...models.job_item import JobItem import os import logging import copy @@ -128,6 +129,13 @@ def update(self, datasource_item): updated_datasource = copy.copy(datasource_item) return updated_datasource._parse_common_elements(server_response.content, self.parent_srv.namespace) + def refresh(self, datasource_item): + url = "{0}/{1}/refresh".format(self.baseurl, datasource_item.id) + empty_req = RequestFactory.Empty.empty_req() + server_response = self.post_request(url, empty_req) + new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0] + return new_job + # Publish datasource @api(version="2.0") def publish(self, datasource_item, file_path, mode, connection_credentials=None): diff --git a/tableauserverclient/server/endpoint/endpoint.py b/tableauserverclient/server/endpoint/endpoint.py index cdc52e4ce..db5c6c1d2 100644 --- a/tableauserverclient/server/endpoint/endpoint.py +++ b/tableauserverclient/server/endpoint/endpoint.py @@ -10,7 +10,7 @@ logger = logging.getLogger('tableau.endpoint') -Success_codes = [200, 201, 204] +Success_codes = [200, 201, 202, 204] class Endpoint(object): diff --git a/tableauserverclient/server/endpoint/jobs_endpoint.py b/tableauserverclient/server/endpoint/jobs_endpoint.py new file mode 100644 index 000000000..243b04d63 --- /dev/null +++ b/tableauserverclient/server/endpoint/jobs_endpoint.py @@ -0,0 +1,19 @@ +from .endpoint import Endpoint, api +from .. import JobItem +import logging + +logger = logging.getLogger('tableau.endpoint.jobs') + + +class Jobs(Endpoint): + @property + def baseurl(self): + return "{0}/sites/{1}/jobs".format(self.parent_srv.baseurl, self.parent_srv.site_id) + + @api(version='2.6') + def get(self, job_id): + logger.info('Query for information about job ' + job_id) + url = "{0}/{1}".format(self.baseurl, job_id) + server_response = self.get_request(url) + new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0] + return new_job diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index ec5cedc22..f9ddd6f37 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -4,6 +4,7 @@ from .resource_tagger import _ResourceTagger from .. import RequestFactory, WorkbookItem, ConnectionItem, ViewItem, PaginationItem from ...models.tag_item import TagItem +from ...models.job_item import JobItem from ...filesys_helpers import to_filename import os @@ -50,6 +51,14 @@ def get_by_id(self, workbook_id): server_response = self.get_request(url) return WorkbookItem.from_response(server_response.content, self.parent_srv.namespace)[0] + @api(version="2.8") + def refresh(self, workbook_id): + url = "{0}/{1}/refresh".format(self.baseurl, workbook_id) + empty_req = RequestFactory.Empty.empty_req() + server_response = self.post_request(url, empty_req) + new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0] + return new_job + # Delete 1 workbook by id @api(version="2.0") def delete(self, workbook_id): diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index 464971472..a3f8fbbc2 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -377,9 +377,16 @@ def create_req(self, subscription_item): return ET.tostring(xml_request) +class EmptyRequest(object): + @_tsrequest_wrapped + def empty_req(xml_request): + pass + + class RequestFactory(object): Auth = AuthRequest() Datasource = DatasourceRequest() + Empty = EmptyRequest() Fileupload = FileuploadRequest() Group = GroupRequest() Permission = PermissionRequest() diff --git a/tableauserverclient/server/server.py b/tableauserverclient/server/server.py index eee37f483..0c2b4f1c2 100644 --- a/tableauserverclient/server/server.py +++ b/tableauserverclient/server/server.py @@ -3,7 +3,7 @@ from .exceptions import NotSignedInError from ..namespace import Namespace from .endpoint import Sites, Views, Users, Groups, Workbooks, Datasources, Projects, Auth, \ - Schedules, ServerInfo, Tasks, ServerInfoEndpointNotFoundError, Subscriptions + Schedules, ServerInfo, Tasks, ServerInfoEndpointNotFoundError, Subscriptions, Jobs import requests @@ -36,6 +36,7 @@ def __init__(self, server_address, use_server_version=False): self.users = Users(self) self.sites = Sites(self) self.groups = Groups(self) + self.jobs = Jobs(self) self.workbooks = Workbooks(self) self.datasources = Datasources(self) self.projects = Projects(self)