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 all 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
77 changes: 77 additions & 0 deletions samples/create_schedules.py
@@ -0,0 +1,77 @@
####
# 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
# This schedule will run every 2 hours between 2:30AM and 11:00PM
hourly_interval = TSC.HourlyInterval(start_time=time(2, 30),
end_time=time(23, 0),
interval_value=2)

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
# This schedule will run every day at 5AM
daily_interval = TSC.DailyInterval(start_time=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
# This schedule will wun every Monday, Wednesday, and Friday at 7:15PM
weekly_interval = TSC.WeeklyInterval(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
# This schedule will run on the 15th of every month at 11:30PM
monthly_interval = TSC.MonthlyInterval(start_time=time(23, 30),
interval_value=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()
5 changes: 3 additions & 2 deletions tableauserverclient/__init__.py
@@ -1,7 +1,8 @@
from .namespace import NAMESPACE
from .models import ConnectionItem, DatasourceItem,\
GroupItem, PaginationItem, ProjectItem, \
SiteItem, TableauAuth, UserItem, ViewItem, WorkbookItem, UnpopulatedPropertyError
GroupItem, PaginationItem, ProjectItem, ScheduleItem, \
SiteItem, TableauAuth, UserItem, ViewItem, WorkbookItem, UnpopulatedPropertyError, \
HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval, IntervalItem
from .server import RequestOptions, Filter, Sort, Server, ServerResponseError,\
MissingRequiredFieldError, NotSignedInError

Expand Down
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, WeeklyInterval, MonthlyInterval, HourlyInterval
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
183 changes: 183 additions & 0 deletions tableauserverclient/models/interval_item.py
@@ -0,0 +1,183 @@
from .property_decorators import property_is_valid_time, property_not_nullable


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

class Occurrence:
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.

Hours = "hours"
WeekDay = "weekDay"
MonthDay = "monthDay"

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


class HourlyInterval(object):
Copy link
Contributor

Choose a reason for hiding this comment

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

The properties should have an decorators if they cannot be none / empty

def __init__(self, start_time, end_time, interval_value):

self.start_time = start_time
Copy link
Contributor

Choose a reason for hiding this comment

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

For other code, it seems we start by setting the internal variable to None before calling the setter. Any reason for using that pattern versus not?

Copy link
Collaborator Author

@t8y8 t8y8 Oct 6, 2016

Choose a reason for hiding this comment

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

This feels cleaner to me because it hide the _blah versions as the implementation detail that they are and keeps the __init__ function clean.

I then don't need to have a bunch of these in __init__:

        # Invoke setter
        self.name = name

Copy link
Contributor

Choose a reason for hiding this comment

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

Russell?

self.end_time = end_time
self.interval = interval_value

@property
def _frequency(self):
return IntervalItem.Frequency.Hourly

@property
def start_time(self):
return self._start_time

@start_time.setter
@property_is_valid_time
@property_not_nullable
def start_time(self, value):
self._start_time = value

@property
def end_time(self):
return self._end_time

@end_time.setter
@property_is_valid_time
@property_not_nullable
def end_time(self, value):
self._end_time = value

@property
def interval(self):
return self._interval

@interval.setter
def interval(self, interval):
VALID_INTERVALS = {.25, .5, 1, 2, 4, 6, 8, 12}
if float(interval) not in VALID_INTERVALS:
error = "Invalid interval {} not in {}".format(interval, str(VALID_INTERVALS))
raise ValueError(error)

self._interval = interval

def _interval_type_pairs(self):

# We use fractional hours for the two minute-based intervals.
# Need to convert to minutes from hours here
if self.interval in {.25, .5}:
calculated_interval = int(self.interval * 60)
interval_type = IntervalItem.Occurrence.Minutes
else:
calculated_interval = self.interval
interval_type = IntervalItem.Occurrence.Hours

return [(interval_type, str(calculated_interval))]


class DailyInterval(object):
def __init__(self, start_time):
self.start_time = start_time

@property
def _frequency(self):
return IntervalItem.Frequency.Daily

@property
def start_time(self):
return self._start_time

@start_time.setter
@property_is_valid_time
@property_not_nullable
def start_time(self, value):
self._start_time = value


class WeeklyInterval(object):
def __init__(self, start_time, *interval_values):
self.start_time = start_time
self.interval = interval_values

@property
def _frequency(self):
return IntervalItem.Frequency.Weekly

@property
def start_time(self):
return self._start_time

@start_time.setter
@property_is_valid_time
@property_not_nullable
def start_time(self, value):
self._start_time = value

@property
def interval(self):
return self._interval

@interval.setter
def interval(self, interval_values):
if not all(hasattr(IntervalItem.Day, day) for day in interval_values):
raise ValueError("Invalid week day defined " + str(interval_values))

self._interval = interval_values

def _interval_type_pairs(self):
return [(IntervalItem.Occurrence.WeekDay, day) for day in self.interval]


class MonthlyInterval(object):
def __init__(self, start_time, interval_value):
self.start_time = start_time
self.interval = str(interval_value)

@property
def _frequency(self):
return IntervalItem.Frequency.Monthly

@property
def start_time(self):
return self._start_time

@start_time.setter
@property_is_valid_time
@property_not_nullable
def start_time(self, value):
self._start_time = value

@property
def interval(self):
return self._interval

@interval.setter
def interval(self, interval_value):
error = "Invalid interval value for a monthly frequency: {}.".format(interval_value)

# This is weird because the value could be a str or an int
# The only valid str is 'LastDay' so we check that first. If that's not it
# try to convert it to an int, if that fails because it's an incorrect string
# like 'badstring' we catch and re-raise. Otherwise we convert to int and check
# that it's in range 1-31

if interval_value != "LastDay":
try:
if not (1 <= int(interval_value) <= 31):
raise ValueError(error)
except ValueError as e:
if interval_value != "LastDay":
raise ValueError(error)

self._interval = str(interval_value)

def _interval_type_pairs(self):
return [(IntervalItem.Occurrence.MonthDay, self.interval)]
38 changes: 38 additions & 0 deletions tableauserverclient/models/property_decorators.py
Expand Up @@ -46,3 +46,41 @@ def wrapper(self, value):
return func(self, value)

return wrapper


def property_is_valid_time(func):
@wraps(func)
def wrapper(self, value):
units_of_time = {"hour", "minute", "second"}

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

return wrapper


def property_is_int(range):
def property_type_decorator(func):
@wraps(func)
def wrapper(self, value):
error = "Invalid priority defined: {}.".format(value)

if range is None:
if isinstance(value, int):
return func(self, value)
else:
raise ValueError(error)

min, max = range

if value < min or value > max:

raise ValueError(error)

return func(self, value)

return wrapper

return property_type_decorator