1
# Copyright (c) 2010 Chris Moyer http://coredumped.org/
3
# Permission is hereby granted, free of charge, to any person obtaining a
4
# copy of this software and associated documentation files (the
5
# "Software"), to deal in the Software without restriction, including
6
# without limitation the rights to use, copy, modify, merge, publish, dis-
7
# tribute, sublicense, and/or sell copies of the Software, and to permit
8
# persons to whom the Software is furnished to do so, subject to the fol-
11
# The above copyright notice and this permission notice shall be included
12
# in all copies or substantial portions of the Software.
14
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
16
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
17
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22
from boto.exception import SDBResponseError
24
class SequenceGenerator(object):
25
"""Generic Sequence Generator object, this takes a single
26
string as the "sequence" and uses that to figure out
27
what the next value in a string is. For example
28
if you give "ABC" and pass in "A" it will give you "B",
29
and if you give it "C" it will give you "AA".
31
If you set "rollover" to True in the above example, passing
32
in "C" would give you "A" again.
34
The Sequence string can be a string or any iterable
35
that has the "index" function and is indexable.
37
__name__ = "SequenceGenerator"
39
def __init__(self, sequence_string, rollover=False):
40
"""Create a new SequenceGenerator using the sequence_string
41
as how to generate the next item.
43
:param sequence_string: The string or list that explains
44
how to generate the next item in the sequence
45
:type sequence_string: str,iterable
47
:param rollover: Rollover instead of incrementing when
48
we hit the end of the sequence
51
self.sequence_string = sequence_string
52
self.sequence_length = len(sequence_string[0])
53
self.rollover = rollover
54
self.last_item = sequence_string[-1]
55
self.__name__ = "%s('%s')" % (self.__class__.__name__, sequence_string)
57
def __call__(self, val, last=None):
58
"""Get the next value in the sequence"""
59
# If they pass us in a string that's not at least
60
# the lenght of our sequence, then return the
61
# first element in our sequence
62
if val == None or len(val) < self.sequence_length:
63
return self.sequence_string[0]
64
last_value = val[-self.sequence_length:]
65
if (not self.rollover) and (last_value == self.last_item):
66
val = "%s%s" % (self(val[:-self.sequence_length]), self._inc(last_value))
68
val = "%s%s" % (val[:-self.sequence_length], self._inc(last_value))
72
"""Increment a single value"""
73
assert(len(val) == self.sequence_length)
74
return self.sequence_string[(self.sequence_string.index(val)+1) % len(self.sequence_string)]
79
# Simple Sequence Functions
81
def increment_by_one(cv=None, lv=None):
86
def double(cv=None, lv=None):
92
"""The fibonacci sequence, this incrementer uses the
100
increment_string = SequenceGenerator("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
104
class Sequence(object):
105
"""A simple Sequence using the new SDB "Consistent" features
106
Based largly off of the "Counter" example from mitch garnaat:
107
http://bitbucket.org/mitch/stupidbototricks/src/tip/counter.py"""
110
def __init__(self, id=None, domain_name=None, fnc=increment_by_one, init_val=None):
111
"""Create a new Sequence, using an optional function to
112
increment to the next number, by default we just increment by one.
113
Every parameter here is optional, if you don't specify any options
114
then you'll get a new SequenceGenerator with a random ID stored in the
115
default domain that increments by one and uses the default botoweb
118
:param id: Optional ID (name) for this counter
121
:param domain_name: Optional domain name to use, by default we get this out of the
122
environment configuration
123
:type domain_name:str
125
:param fnc: Optional function to use for the incrementation, by default we just increment by one
126
There are several functions defined in this module.
127
Your function must accept "None" to get the initial value
128
:type fnc: function, str
130
:param init_val: Initial value, by default this is the first element in your sequence,
131
but you can pass in any value, even a string if you pass in a function that uses
132
strings instead of ints to increment
136
self.last_value = None
137
self.domain_name = domain_name
141
self.id = str(uuid.uuid4())
143
init_val = fnc(init_val)
146
self.item_type = type(fnc(None))
147
self.timestamp = None
148
# Allow us to pass in a full name to a function
150
from boto.utils import find_class
151
fnc = find_class(fnc)
160
new_val['timestamp'] = now
161
if self._value != None:
162
new_val['last_value'] = self._value
163
expected_values = ['current_value', str(self._value)]
164
new_val['current_value'] = val
166
self.db.put_attributes(self.id, new_val, expected_values=expected_values)
167
self.timestamp = new_val['timestamp']
168
except SDBResponseError, e:
170
raise ValueError, "Sequence out of sync"
177
val = self.db.get_attributes(self.id, consistent_read=True)
178
if val and val.has_key('timestamp'):
179
self.timestamp = val['timestamp']
180
if val and val.has_key('current_value'):
181
self._value = self.item_type(val['current_value'])
182
if val.has_key("last_value") and val['last_value'] != None:
183
self.last_value = self.item_type(val['last_value'])
186
val = property(get, set)
189
return "%s('%s', '%s', '%s.%s', '%s')" % (
190
self.__class__.__name__,
193
self.fnc.__module__, self.fnc.__name__,
198
"""Connect to our domain"""
200
if not self.domain_name:
202
sdb = boto.connect_sdb()
203
self.domain_name = boto.config.get("DB", "sequence_db", boto.config.get("DB", "db_name", "default"))
205
self._db = sdb.get_domain(self.domain_name)
206
except SDBResponseError, e:
208
self._db = sdb.create_domain(self.domain_name)
213
db = property(_connect)
216
self.val = self.fnc(self.val, self.last_value)
220
"""Remove this sequence"""
221
self.db.delete_attributes(self.id)