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
Changes from all commits
03e8ce3
312a1c7
b32a632
0adb03f
66e1793
0342bed
bb9ff8f
34457b3
f4490fb
79271a1
b710d9d
46cc1d0
fd17154
371b6f9
28ebb9e
c330687
d7add3a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This feels cleaner to me because it hide the I then don't need to have a bunch of these in
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)] |
There was a problem hiding this comment.
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.