Skip to content

Commit

Permalink
Merge pull request tableau#128 from tableau/development
Browse files Browse the repository at this point in the history
Release 0.3
  • Loading branch information
Russell Hay authored Jan 11, 2017
2 parents 05974c3 + aa56523 commit 8e6c907
Show file tree
Hide file tree
Showing 33 changed files with 500 additions and 87 deletions.
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ python:
- "3.3"
- "3.4"
- "3.5"
- "3.6"
- "pypy"
# command to install dependencies
install:
Expand All @@ -14,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 .
- pycodestyle tableauserverclient test
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
## 0.3 (11 January 2017)

* Return DateTime objects instead of strings (#102)
* UserItem now is compatible with Pager (#107, #109)
* Deprecated site in favor of site_id (#97)
* Improved handling of large downloads (#105, #111)
* Added support for oAuth when publishing (#117)
* Added Testing against Py36 (#122, #123)
* Added Version Checking to use highest supported REST api version (#100)
* Added Infrastructure for throwing error if trying to do something that is not supported by REST api version (#124)
* Various Code Cleanup
* Added Documentation (#98)
* Improved Test Infrastructure (#91)

## 0.2 (02 November 2016)

* Added Initial Schedules Support (#48)
Expand Down
3 changes: 3 additions & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ The following people have contributed to this project to make it possible, and w
## Contributors

* [geordielad](https://github.com/geordielad)
* [Hugo Stijns)(https://github.com/hugoboos)
* [kovner](https://github.com/kovner)


Expand All @@ -14,3 +15,5 @@ The following people have contributed to this project to make it possible, and w
* [lgraber](https://github.com/lgraber)
* [t8y8](https://github.com/t8y8)
* [RussTheAerialist](https://github.com/RussTheAerialist)
* [Ben Lower](https://github.com/benlower)
* [Jared Dominguez](https://github.com/jdomingu)
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ This repository contains Python source code and sample files.
For more information on installing and using TSC, see the documentation:

<https://tableau.github.io/server-client-python/docs/>

107 changes: 107 additions & 0 deletions samples/initialize_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
####
# This script sets up a server. It uploads datasources and workbooks from the local filesystem.
#
# By default, all content is published to the Default project on the Default site.
####

import tableauserverclient as TSC
import argparse
import getpass
import logging
import glob


def main():
parser = argparse.ArgumentParser(description='Initialize a server with content.')
parser.add_argument('--server', '-s', required=True, help='server address')
parser.add_argument('--datasources-folder', '-df', required=True, help='folder containing datasources')
parser.add_argument('--workbooks-folder', '-wf', required=True, help='folder containing workbooks')
parser.add_argument('--site', '-si', required=False, default='Default', help='site to use')
parser.add_argument('--project', '-p', required=False, default='Default', help='project to use')
parser.add_argument('--username', '-u', required=True, help='username to sign into server')
parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error',
help='desired logging level (set to error by default)')
args = parser.parse_args()

password = getpass.getpass("Password: ")

# Set logging level based on user input, or error by default
logging_level = getattr(logging, args.logging_level.upper())
logging.basicConfig(level=logging_level)

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

with server.auth.sign_in(tableau_auth):

################################################################################
# Step 2: Create the site we need only if it doesn't exist
################################################################################
print("Checking to see if we need to create the site...")

all_sites, _ = server.sites.get()
existing_site = next((s for s in all_sites if s.name == args.site), None)

# Create the site if it doesn't exist
if existing_site is None:
print("Site not found: {0} Creating it...").format(args.site)
new_site = TSC.SiteItem(name=args.site, content_url=args.site.replace(" ", ""),
admin_mode=TSC.SiteItem.AdminMode.ContentAndUsers)
server.sites.create(new_site)
else:
print("Site {0} exists. Moving on...").format(args.site)

################################################################################
# Step 3: Sign-in to our target site
################################################################################
print("Starting our content upload...")
server_upload = TSC.Server(args.server)
tableau_auth.site = args.site

with server_upload.auth.sign_in(tableau_auth):

################################################################################
# Step 4: Create the project we need only if it doesn't exist
################################################################################
all_projects, _ = server_upload.projects.get()
project = next((p for p in all_projects if p.name == args.project), None)

# Create our project if it doesn't exist
if project is None:
print("Project not found: {0} Creating it...").format(args.project)
new_project = TSC.ProjectItem(name=args.project)
project = server_upload.projects.create(new_project)

################################################################################
# Step 5: Set up our content
# Publish datasources to our site and project
# Publish workbooks to our site and project
################################################################################
publish_datasources_to_site(server_upload, project, args.datasources_folder)
publish_workbooks_to_site(server_upload, project, args.workbooks_folder)


def publish_datasources_to_site(server_object, project, folder):
path = folder + '/*.tds*'

for fname in glob.glob(path):
new_ds = TSC.DatasourceItem(project.id)
new_ds = server_object.datasources.publish(new_ds, fname, server_object.PublishMode.Overwrite)
print("Datasource published. ID: {0}".format(new_ds.id))


def publish_workbooks_to_site(server_object, project, folder):
path = folder + '/*.twb*'

for fname in glob.glob(path):
new_workbook = TSC.WorkbookItem(project.id)
new_workbook.show_tabs = True
new_workbook = server_object.workbooks.publish(new_workbook, fname, server_object.PublishMode.Overwrite)
print("Workbook published. ID: {0}".format(new_workbook.id))


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions samples/pagination_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,6 @@ def main():
# >>> request_options = TSC.RequestOptions(pagesize=1000)
# >>> all_workbooks = list(TSC.Pager(server.workbooks, request_options))


if __name__ == '__main__':
main()
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setup(
name='tableauserverclient',
version='0.2',
version='0.3',
author='Tableau',
author_email='[email protected]',
url='https://github.com/tableau/server-client-python',
Expand Down
37 changes: 37 additions & 0 deletions tableauserverclient/datetime_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import datetime


# This code below is from the python documentation for tzinfo: https://docs.python.org/2.3/lib/datetime-tzinfo.html
ZERO = datetime.timedelta(0)
HOUR = datetime.timedelta(hours=1)

# A UTC class.


class UTC(datetime.tzinfo):
"""UTC"""

def utcoffset(self, dt):
return ZERO

def tzname(self, dt):
return "UTC"

def dst(self, dt):
return ZERO


utc = UTC()

TABLEAU_DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"


def parse_datetime(date):
if date is None:
return None

return datetime.datetime.strptime(date, TABLEAU_DATE_FORMAT).replace(tzinfo=utc)


def format_datetime(date):
return date.astimezone(tz=utc).strftime(TABLEAU_DATE_FORMAT)
12 changes: 11 additions & 1 deletion tableauserverclient/models/connection_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ class ConnectionCredentials(object):
"""

def __init__(self, name, password, embed=True):
def __init__(self, name, password, embed=True, oauth=False):
self.name = name
self.password = password
self.embed = embed
self.oauth = oauth

@property
def embed(self):
Expand All @@ -22,3 +23,12 @@ def embed(self):
@property_is_boolean
def embed(self, value):
self._embed = value

@property
def oauth(self):
return self._oauth

@oauth.setter
@property_is_boolean
def oauth(self, value):
self._oauth = value
5 changes: 3 additions & 2 deletions tableauserverclient/models/datasource_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .property_decorators import property_not_nullable
from .tag_item import TagItem
from .. import NAMESPACE
from ..datetime_helpers import parse_datetime


class DatasourceItem(object):
Expand Down Expand Up @@ -118,8 +119,8 @@ def _parse_element(datasource_xml):
name = datasource_xml.get('name', None)
datasource_type = datasource_xml.get('type', None)
content_url = datasource_xml.get('contentUrl', None)
created_at = datasource_xml.get('createdAt', None)
updated_at = datasource_xml.get('updatedAt', None)
created_at = parse_datetime(datasource_xml.get('createdAt', None))
updated_at = parse_datetime(datasource_xml.get('updatedAt', None))

tags = None
tags_elem = datasource_xml.find('.//t:tags', namespaces=NAMESPACE)
Expand Down
29 changes: 29 additions & 0 deletions tableauserverclient/models/property_decorators.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import datetime
import re
from functools import wraps
from ..datetime_helpers import parse_datetime
try:
basestring
except NameError:
# In case we are in python 3 the string check is different
basestring = str


def property_is_enum(enum_type):
Expand Down Expand Up @@ -99,3 +106,25 @@ def validate_regex_decorator(self, value):
return func(self, value)
return validate_regex_decorator
return wrapper


def property_is_datetime(func):
""" Takes the following datetime format and turns it into a datetime object:
2016-08-18T18:25:36Z
Because we return everything with Z as the timezone, we assume everything is in UTC and create
a timezone aware datetime.
"""

@wraps(func)
def wrapper(self, value):
if isinstance(value, datetime.datetime):
return func(self, value)
if not isinstance(value, basestring):
raise ValueError("Cannot convert {} into a datetime, cannot update {}".format(value.__class__.__name__,
func.__name__))

dt = parse_datetime(value)
return func(self, dt)
return wrapper
9 changes: 5 additions & 4 deletions tableauserverclient/models/schedule_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .interval_item import IntervalItem, HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval
from .property_decorators import property_is_enum, property_not_nullable, property_is_int
from .. import NAMESPACE
from ..datetime_helpers import parse_datetime


class ScheduleItem(object):
Expand Down Expand Up @@ -208,12 +209,12 @@ def _parse_element(schedule_xml):
id = schedule_xml.get('id', None)
name = schedule_xml.get('name', None)
state = schedule_xml.get('state', None)
created_at = schedule_xml.get('createdAt', None)
updated_at = schedule_xml.get('updatedAt', None)
created_at = parse_datetime(schedule_xml.get('createdAt', None))
updated_at = parse_datetime(schedule_xml.get('updatedAt', None))
schedule_type = schedule_xml.get('type', None)
frequency = schedule_xml.get('frequency', None)
next_run_at = schedule_xml.get('nextRunAt', None)
end_schedule_at = schedule_xml.get('endScheduleAt', None)
next_run_at = parse_datetime(schedule_xml.get('nextRunAt', None))
end_schedule_at = parse_datetime(schedule_xml.get('endScheduleAt', None))
execution_order = schedule_xml.get('executionOrder', None)

priority = schedule_xml.get('priority', None)
Expand Down
7 changes: 7 additions & 0 deletions tableauserverclient/models/tableau_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,10 @@ def site(self):
warnings.warn('TableauAuth.site is deprecated, use TableauAuth.site_id instead.',
DeprecationWarning)
return self.site_id

@site.setter
def site(self, value):
import warnings
warnings.warn('TableauAuth.site is deprecated, use TableauAuth.site_id instead.',
DeprecationWarning)
self.site_id = value
7 changes: 4 additions & 3 deletions tableauserverclient/models/user_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from .exceptions import UnpopulatedPropertyError
from .property_decorators import property_is_enum, property_not_empty, property_not_nullable
from .. import NAMESPACE
from ..datetime_helpers import parse_datetime


class UserItem(object):
Expand Down Expand Up @@ -118,7 +119,7 @@ def _set_values(self, id, name, site_role, last_login,

@classmethod
def from_response(cls, resp):
all_user_items = set()
all_user_items = []
parsed_response = ET.fromstring(resp)
all_user_xml = parsed_response.findall('.//t:user', namespaces=NAMESPACE)
for user_xml in all_user_xml:
Expand All @@ -127,15 +128,15 @@ def from_response(cls, resp):
user_item = cls(name, site_role)
user_item._set_values(id, name, site_role, last_login, external_auth_user_id,
fullname, email, auth_setting, domain_name)
all_user_items.add(user_item)
all_user_items.append(user_item)
return all_user_items

@staticmethod
def _parse_element(user_xml):
id = user_xml.get('id', None)
name = user_xml.get('name', None)
site_role = user_xml.get('siteRole', None)
last_login = user_xml.get('lastLogin', None)
last_login = parse_datetime(user_xml.get('lastLogin', None))
external_auth_user_id = user_xml.get('externalAuthUserId', None)
fullname = user_xml.get('fullName', None)
email = user_xml.get('email', None)
Expand Down
5 changes: 3 additions & 2 deletions tableauserverclient/models/workbook_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .tag_item import TagItem
from .view_item import ViewItem
from .. import NAMESPACE
from ..datetime_helpers import parse_datetime
import copy


Expand Down Expand Up @@ -163,8 +164,8 @@ def _parse_element(workbook_xml):
id = workbook_xml.get('id', None)
name = workbook_xml.get('name', None)
content_url = workbook_xml.get('contentUrl', None)
created_at = workbook_xml.get('createdAt', None)
updated_at = workbook_xml.get('updatedAt', None)
created_at = parse_datetime(workbook_xml.get('createdAt', None))
updated_at = parse_datetime(workbook_xml.get('updatedAt', None))

size = workbook_xml.get('size', None)
if size:
Expand Down
2 changes: 1 addition & 1 deletion tableauserverclient/server/endpoint/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from .auth_endpoint import Auth
from .datasources_endpoint import Datasources
from .endpoint import Endpoint
from .exceptions import ServerResponseError, MissingRequiredFieldError
from .exceptions import ServerResponseError, MissingRequiredFieldError, ServerInfoEndpointNotFoundError
from .groups_endpoint import Groups
from .projects_endpoint import Projects
from .schedules_endpoint import Schedules
Expand Down
Loading

0 comments on commit 8e6c907

Please sign in to comment.