Skip to content

Commit dc8aaba

Browse files
committed
Merge pull request #3 from rightlag/release-0.0.2
Release 0.0.2
2 parents 8219b48 + 3cc69cc commit dc8aaba

File tree

4 files changed

+92
-59
lines changed

4 files changed

+92
-59
lines changed

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# timely
22

3-
timely 0.0.1
3+
timely 0.0.2
44

55
Released: 2-Sep-2015
66

@@ -39,3 +39,15 @@ All commits are tested with [Travis CI](https://travis-ci.org/) and *also* requi
3939
>>> timely = Timely(verbose=True)
4040
>>> timely.check()
4141
Stopping instance: i-6dc5bc92
42+
43+
### Using timezones
44+
45+
By default, `timely` sets the default timezone to `US/Eastern`. However, this can be changed upon instantiation of the `timely` class or via the `set_tz` method. For example:
46+
47+
>>> from timely import Timely
48+
>>> timely = Timely(tz='US/Pacific')
49+
>>> timely.set_tz('US/Mountain')
50+
51+
If the timezone specified does not exist, then `UTC` is used.
52+
53+
When assigning the timezone, the `set` method will create a tag `tz` for EC2 containers with the timezone that was specified. Whenever the `check` method is called, the `tz` tag that is assigned to the EC2 container is used to determine whether or not it should be running.

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
boto==2.38.0
22
nose==1.3.7
3+
pytz==2015.6
34
wheel==0.24.0

tests/test_timely.py

Lines changed: 35 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import boto.ec2
22
import datetime
3+
import pytz
4+
import sys
35
import unittest
46

57
from time import sleep
@@ -10,18 +12,19 @@ class TimelyTestCase(unittest.TestCase):
1012
def setUp(self):
1113
self.timely = Timely(verbose=True)
1214
self.conn = boto.ec2.connect_to_region('us-east-1')
13-
self.now = datetime.datetime.now()
15+
self.now = datetime.datetime.now(tz=pytz.timezone('US/Eastern'))
1416

15-
def test_times_tag_is_created(self):
17+
def test_times_and_tz_tags_are_created(self):
18+
"""Assert that both the `times` and `tz` tags are created for
19+
instances."""
1620
self.timely.set(weekdays=['*'])
1721
instances = self.conn.get_only_instances()
1822
for instance in instances:
19-
if instance.state != 'terminated':
20-
self.assertIn('times', instance.tags)
21-
else:
22-
continue
23+
self.assertIn('times', instance.tags)
24+
self.assertIn('tz', instance.tags)
2325

2426
def test_times_tag_has_length_of_7(self):
27+
"""Assert that the length of the times tag is 7 elements."""
2528
self.timely.set(weekdays=['*'])
2629
instances = self.conn.get_only_instances()
2730
for instance in instances:
@@ -31,6 +34,7 @@ def test_times_tag_has_length_of_7(self):
3134
self.assertEqual(len(times), 7)
3235

3336
def test_time_is_set_for_weekday(self):
37+
"""Assert that a time is set for the current weekday."""
3438
weekday = self.timely.weekdays[self.now.weekday()]
3539
self.timely.set(weekdays=[weekday], start_time='9:00 AM',
3640
end_time='5:00 PM')
@@ -40,6 +44,9 @@ def test_time_is_set_for_weekday(self):
4044
self.assertNotEqual(times[self.now.weekday()], str(None))
4145

4246
def test_exception_if_start_time_is_greater_than_equal_to_end_time(self):
47+
"""If the start time is greater than or equal to the end time
48+
a `ValueError` should be raised.
49+
"""
4350
with self.assertRaises(ValueError):
4451
# Greater
4552
self.timely.set(weekdays=['*'], start_time='9:00 AM',
@@ -49,19 +56,18 @@ def test_exception_if_start_time_is_greater_than_equal_to_end_time(self):
4956
end_time='9:00 AM')
5057

5158
def test_unset_method(self):
59+
"""Assert that the times are set to `None` for all weekdays."""
5260
self.timely.set(weekdays=['*'], start_time='9:00 AM',
5361
end_time='5:00 PM')
54-
try:
55-
instance = self.conn.get_only_instances()[0]
56-
times = self.timely.all()[instance.id]
57-
# First - set times for all days of the week
62+
instances = self.conn.get_only_instances()
63+
for instance in instances:
64+
times = instance.tags['times'].split(';')
5865
self.assertEqual(len(times), 7)
59-
# Second - unset times for all days of the week
60-
self.timely.unset(weekdays=['*'])
61-
times = self.timely.all()[instance.id]
62-
self.assertEqual(len(times), 0)
63-
except IndexError:
64-
pass
66+
self.timely.unset(weekdays=['*'])
67+
for instance in instances:
68+
instance.update()
69+
times = instance.tags['times']
70+
self.assertEqual(times, ';'.join([str(None)] * 7))
6571

6672
def test_check_method_stops_instance_if_should_not_be_running(self):
6773
"""Check to ensure that an instance is stopped if it SHOULD NOT
@@ -70,36 +76,29 @@ def test_check_method_stops_instance_if_should_not_be_running(self):
7076
try:
7177
instance = self.conn.get_only_instances()[0]
7278
if instance.state == 'stopped':
79+
running = False
7380
# Start the instance to ensure it is running
81+
sys.stdout.write('starting instance: {0}\n'
82+
.format(instance.id))
7483
instance.start()
75-
running = False
7684
while not running:
77-
try:
78-
instance = self.conn.get_only_instances()[0]
79-
if instance.state == 'running':
80-
running = True
81-
else:
82-
sleep(1)
83-
except IndexError:
84-
pass
85+
instance.update()
86+
if instance.state == 'running':
87+
running = True
88+
else:
89+
sleep(1)
8590
weekday = self.timely.weekdays[self.now.weekday()]
8691
# Automatically sets `start_time` and `end_time` to `None`
8792
self.timely.set(weekdays=[weekday])
8893
# Ensure that the instance is being stopped
8994
self.timely.check()
9095
stopped = False
91-
instance = None
9296
while not stopped:
93-
try:
94-
# Need to remake a connection to AWS to get the updated
95-
# instance status
96-
instance = self.conn.get_only_instances()[0]
97-
if instance.state == 'stopped':
98-
stopped = True
99-
else:
100-
sleep(1)
101-
except IndexError:
102-
pass
97+
instance.update()
98+
if instance.state == 'stopped':
99+
stopped = True
100+
else:
101+
sleep(1)
103102
self.assertEqual(instance.state, 'stopped')
104103
except IndexError:
105104
pass

timely/timely.py

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import boto.ec2
22
import collections
3+
import pytz
34
import sys
45

56
from datetime import datetime
67

78

89
class Timely(object):
9-
def __init__(self, region='us-east-1', iso=False, verbose=False):
10+
def __init__(self, region='us-east-1', iso=False, verbose=False,
11+
tz='US/Eastern'):
1012
self.conn = boto.ec2.connect_to_region(region)
1113
self.iso = iso
1214
# Accommodate for ISO weekday - remove first element if `iso` is False
@@ -27,6 +29,7 @@ def __init__(self, region='us-east-1', iso=False, verbose=False):
2729
weekdays.pop(0)
2830
self.weekdays = weekdays
2931
self.verbose = verbose
32+
self.set_tz(tz)
3033

3134
def use_verbose(self):
3235
self.verbose = True
@@ -37,6 +40,15 @@ def use_iso(self):
3740
def set_region(self, region):
3841
self.conn = boto.ec2.connect_to_region(region)
3942

43+
def set_tz(self, tz):
44+
try:
45+
self.tz = pytz.timezone(tz)
46+
except pytz.exceptions.UnknownTimeZoneError:
47+
self.tz = pytz.utc
48+
49+
def _verbose_message(self, action, instance):
50+
sys.stdout.write('{0} instance: {1}\n'.format(action, instance.id))
51+
4052
def all(self, instance_ids=None):
4153
"""Read weekday run times for all or specific EC2 instances.
4254
@@ -60,8 +72,12 @@ def all(self, instance_ids=None):
6072
end_time = datetime.strptime(end_time, '%H:%M')
6173
except ValueError:
6274
continue
63-
start_time = start_time.strftime('%H:%M')
64-
end_time = end_time.strftime('%H:%M')
75+
tz = instance.tags.get('tz', pytz.utc)
76+
tz = pytz.timezone(tz)
77+
start_time = tz.localize(start_time)
78+
end_time = tz.localize(end_time)
79+
start_time = start_time.strftime('%I:%M %p %Z%z')
80+
end_time = end_time.strftime('%I:%M %p %Z%z')
6581
weekday = (self.weekdays[i + 1]
6682
if self.iso else self.weekdays[i])
6783
data[instance.id].append(
@@ -105,10 +121,14 @@ def set(self, instance_ids=None, weekdays=None, start_time=None,
105121
continue
106122
times = instance.tags.get('times')
107123
if not times:
108-
# No `times` tag - set default
124+
# No `times` tag - set defaults
109125
times = ';'.join([str(None)] * 7)
126+
tags = {
127+
'times': times,
128+
'tz': self.tz,
129+
}
110130
try:
111-
instance.add_tag('times', times)
131+
instance.add_tags(tags)
112132
except self.conn.ResponseError, e:
113133
raise e
114134
times = times.split(';')
@@ -135,9 +155,13 @@ def set(self, instance_ids=None, weekdays=None, start_time=None,
135155
# Remove first element `None` from `times` object
136156
times.pop(0)
137157
times = ';'.join([str(time) for time in times])
158+
tags = {
159+
'times': times,
160+
'tz': self.tz,
161+
}
138162
try:
139163
# Overwrite existing `times` tag with new value
140-
instance.add_tag('times', times)
164+
instance.add_tags(tags)
141165
except self.conn.ResponseError, e:
142166
raise e
143167

@@ -180,7 +204,13 @@ def check(self, instance_ids=None):
180204
"""
181205
instances = self.conn.get_only_instances(instance_ids=instance_ids)
182206
for instance in instances:
183-
now = datetime.now()
207+
tz = instance.tags.get('tz')
208+
if not tz:
209+
# Needs to be a timezone to compare current time to
210+
# `start_time` and `end_time` - otherwise continue
211+
continue
212+
tz = pytz.timezone(tz)
213+
now = datetime.now(tz=tz)
184214
if self.iso:
185215
weekday = now.isoweekday()
186216
else:
@@ -206,34 +236,25 @@ def check(self, instance_ids=None):
206236
day=now.day)
207237
end_time = end_time.replace(year=now.year,
208238
month=now.month, day=now.day)
239+
# http://www.saltycrane.com/blog/2009/05/converting-time-zones-datetime-objects-python/#add-timezone-localize
240+
start_time = tz.localize(start_time)
241+
end_time = tz.localize(end_time)
209242
if start_time <= now <= end_time:
210243
if instance.state == 'stopped':
211244
if self.verbose:
212-
sys.stdout.write(
213-
'Starting instance: {0}\r\n'.format(
214-
instance.id
215-
)
216-
)
245+
self._verbose_message('starting', instance)
217246
instance.start()
218247
else:
219248
if instance.state == 'running':
220249
if self.verbose:
221-
sys.stdout.write(
222-
'Stopping instance: {0}\r\n'.format(
223-
instance.id
224-
)
225-
)
250+
self._verbose_message('stopping', instance)
226251
instance.stop()
227252
else:
228253
# If the time is `None` check to see if the instance is
229254
# running - if it is, then stop it by default
230255
if instance.state == 'running':
231256
if self.verbose:
232-
sys.stdout.write(
233-
'Stopping instance: {0}\r\n'.format(
234-
instance.id
235-
)
236-
)
257+
self._verbose_message('stopping', instance)
237258
instance.stop()
238259

239260
def __str__(self):

0 commit comments

Comments
 (0)