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

Adding Schedules Support Round 2 #48

Merged
merged 17 commits into from Oct 7, 2016
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
67 changes: 67 additions & 0 deletions samples/create_schedules.py
@@ -0,0 +1,67 @@
####
# This script demonstrates how to create schedules using the Tableau
# Server Client.
#
# To run the script, you must have installed Python 2.7.9 or later.
####


import argparse
import getpass
import logging

from datetime import time

import tableauserverclient as TSC


def main():

parser = argparse.ArgumentParser(description='Creates sample schedules for each type of frequency.')
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('--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)

tableau_auth = TSC.TableauAuth(args.username, password)
server = TSC.Server(args.server)
with server.auth.sign_in(tableau_auth):
# Hourly Schedule
hourly_interval = TSC.IntervalItem.create_hourly(time(2, 30), time(23, 0), TSC.IntervalItem.Occurrence.Hours, 2)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yikes, I need to update these now :)

(I wonder if we can find a way to run these, either in a local task, or in travis somehow)

hourly_schedule = TSC.ScheduleItem("Hourly-Schedule", 50, TSC.ScheduleItem.Type.Extract,
TSC.ScheduleItem.ExecutionOrder.Parallel, hourly_interval)
hourly_schedule = server.schedules.create(hourly_schedule)
print("Hourly schedule created (ID: {}).".format(hourly_schedule.id))

# Daily Schedule
daily_interval = TSC.IntervalItem.create_daily(time(5))
daily_schedule = TSC.ScheduleItem("Daily-Schedule", 60, TSC.ScheduleItem.Type.Subscription,
TSC.ScheduleItem.ExecutionOrder.Serial, daily_interval)
daily_schedule = server.schedules.create(daily_schedule)
print("Daily schedule created (ID: {}).".format(daily_schedule.id))

# Weekly Schedule
weekly_interval = TSC.IntervalItem.create_weekly(time(19, 15), TSC.IntervalItem.Day.Monday,
TSC.IntervalItem.Day.Wednesday, TSC.IntervalItem.Day.Friday)
weekly_schedule = TSC.ScheduleItem("Weekly-Schedule", 70, TSC.ScheduleItem.Type.Extract,
TSC.ScheduleItem.ExecutionOrder.Serial, weekly_interval)
weekly_schedule = server.schedules.create(weekly_schedule)
print("Weekly schedule created (ID: {}).".format(weekly_schedule.id))

# Monthly Schedule
monthly_interval = TSC.IntervalItem.create_monthly(time(23, 30), 15)
monthly_schedule = TSC.ScheduleItem("Monthly-Schedule", 80, TSC.ScheduleItem.Type.Subscription,
TSC.ScheduleItem.ExecutionOrder.Parallel, monthly_interval)
monthly_schedule = server.schedules.create(monthly_schedule)
print("Monthly schedule created (ID: {}).".format(monthly_schedule.id))


if __name__ == '__main__':
main()
3 changes: 2 additions & 1 deletion tableauserverclient/__init__.py
@@ -1,9 +1,10 @@
from .namespace import NAMESPACE
from .models import ConnectionItem, DatasourceItem,\
GroupItem, PaginationItem, ProjectItem, \
GroupItem, PaginationItem, ProjectItem, ScheduleItem, \
SiteItem, TableauAuth, UserItem, ViewItem, WorkbookItem, UnpopulatedPropertyError
from .server import RequestOptions, Filter, Sort, Server, ServerResponseError,\
MissingRequiredFieldError, NotSignedInError
from .models.interval_item import *
Copy link
Contributor

Choose a reason for hiding this comment

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

Russell didn't like it when I imported *

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

aww, but I'm lazy. ok done.


__version__ = '0.0.1'
__VERSION__ = __version__
2 changes: 2 additions & 0 deletions tableauserverclient/models/__init__.py
Expand Up @@ -2,8 +2,10 @@
from .datasource_item import DatasourceItem
from .exceptions import UnpopulatedPropertyError
from .group_item import GroupItem
from .interval_item import IntervalItem, DailyInterval
from .pagination_item import PaginationItem
from .project_item import ProjectItem
from .schedule_item import ScheduleItem
from .site_item import SiteItem
from .tableau_auth import TableauAuth
from .user_item import UserItem
Expand Down
126 changes: 126 additions & 0 deletions tableauserverclient/models/interval_item.py
@@ -0,0 +1,126 @@
import xml.etree.ElementTree as ET
from datetime import datetime
from .. import NAMESPACE


class IntervalItem(object):
class Frequency:
Hourly = "Hourly"
Daily = "Daily"
Weekly = "Weekly"
Monthly = "Monthly"

class Occurrence:
Hours = "hours"
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The XSD actually specifies these as lower case, I'm not sure how it was working before to be honest.

But I found these by getting test failures after the refactor

Minutes = "minutes"
Copy link
Contributor

Choose a reason for hiding this comment

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

Minutes should come before hours.

WeekDay = "weekDay"
MonthDay = "monthDay"

class Day:
Sunday = "Sunday"
Monday = "Monday"
Tuesday = "Tuesday"
Wednesday = "Wednesday"
Thursday = "Thursday"
Friday = "Friday"
Saturday = "Saturday"
LastDay = "LastDay"

end_time = None
frequency = None
interval = None
start_time = None

@staticmethod
def _validate_time(t):
units_of_time = {"hour", "minute", "second"}

if not any(hasattr(t, unit) for unit in units_of_time):
error = "Invalid time object defined."
raise ValueError(error)

@classmethod
def from_response(cls, resp, frequency):
cls.from_xml_element(ET.fromstring(resp), frequency)

@classmethod
def from_xml_element(cls, parsed_response, frequency):
start_time = parsed_response.get("start", None)
start_time = datetime.strptime(start_time, "%H:%M:%S").time()
end_time = parsed_response.get("end", None)
if end_time is not None:
end_time = datetime.strptime(end_time, "%H:%M:%S").time()
interval_elems = parsed_response.findall(".//t:intervals/t:interval", namespaces=NAMESPACE)
interval = []
for interval_elem in interval_elems:
interval.extend(interval_elem.attrib.items())

# If statement of doom until I think of a better way

if frequency == IntervalItem.Frequency.Daily:
return DailyInterval(start_time)

if frequency == IntervalItem.Frequency.Hourly:
interval_occurrence, interval_value = interval.pop()
return HourlyInterval(start_time, end_time, interval_occurrence, interval_value)

if frequency == IntervalItem.Frequency.Weekly:
interval_values = [i[1] for i in interval]
return WeeklyInterval(start_time, *interval_values)

if frequency == IntervalItem.Frequency.Monthly:
interval_occurrence, interval_value = interval.pop()
return MonthlyInterval(start_time, interval_value)


class HourlyInterval(IntervalItem):
def __init__(self, start_time, end_time, interval_occurrence, interval_value):
self._validate_time(start_time)
self._validate_time(end_time)

if interval_occurrence != IntervalItem.Occurrence.Hours and \
interval_occurrence != IntervalItem.Occurrence.Minutes:
error = "Invalid interval type defined: {}.".format(interval_occurrence)
raise ValueError(error)
elif interval_occurrence == IntervalItem.Occurrence.Hours and int(interval_value) not in [1, 2, 4, 6, 8, 12]:
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Similarly I'm not sure how this worked before, parsed responses will be strings, found via test failures.

error = "Invalid hour value defined: {}.".format(interval_value)
raise ValueError(error)
elif interval_occurrence == IntervalItem.Occurrence.Minutes and int(interval_value) not in [15, 30]:
error = "Invalid minute value defined: {}".format(interval_value)
raise ValueError(error)

self.start_time = start_time
self.end_time = end_time
self.frequency = IntervalItem.Frequency.Hourly
self.interval = [(interval_occurrence.lower(), str(interval_value))]


class DailyInterval(IntervalItem):
def __init__(self, start_time, *args):
self._validate_time(start_time)

self.start_time = start_time
self.frequency = IntervalItem.Frequency.Daily


class WeeklyInterval(IntervalItem):
def __init__(self, start_time, *interval_values):
self._validate_time(start_time)
if not all(hasattr(IntervalItem.Day, day) for day in interval_values):
raise ValueError("Invalid week day defined " + str(interval_values))
self.start_time = start_time
self.frequency = IntervalItem.Frequency.Weekly
self.interval = [(IntervalItem.Occurrence.WeekDay, day) for day in interval_values]


class MonthlyInterval(IntervalItem):
def __init__(self, start_time, interval_value):
self._validate_time(start_time)

if (int(interval_value) < 1 or int(interval_value) > 31) and interval_value != IntervalItem.Day.LastDay:
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Again, not sure how this worked without converting to an int before

error = "Invalid interval value defined for a monthly frequency: {}.".format(interval_value)
raise ValueError(error)

self.start_time = start_time
self.frequency = IntervalItem.Frequency.Monthly
self.interval = [(IntervalItem.Occurrence.MonthDay, str(interval_value))]