OLD | NEW |
(Empty) | |
| 1 # Copyright (c) 2010 Chris Moyer http://coredumped.org/ |
| 2 # |
| 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- |
| 9 # lowing conditions: |
| 10 # |
| 11 # The above copyright notice and this permission notice shall be included |
| 12 # in all copies or substantial portions of the Software. |
| 13 # |
| 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 |
| 20 # IN THE SOFTWARE. |
| 21 |
| 22 from boto.exception import SDBResponseError |
| 23 |
| 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". |
| 30 |
| 31 If you set "rollover" to True in the above example, passing |
| 32 in "C" would give you "A" again. |
| 33 |
| 34 The Sequence string can be a string or any iterable |
| 35 that has the "index" function and is indexable. |
| 36 """ |
| 37 __name__ = "SequenceGenerator" |
| 38 |
| 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. |
| 42 |
| 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 |
| 46 |
| 47 :param rollover: Rollover instead of incrementing when |
| 48 we hit the end of the sequence |
| 49 :type rollover: bool |
| 50 """ |
| 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) |
| 56 |
| 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_va
lue)) |
| 67 else: |
| 68 val = "%s%s" % (val[:-self.sequence_length], self._inc(last_value)) |
| 69 return val |
| 70 |
| 71 def _inc(self, val): |
| 72 """Increment a single value""" |
| 73 assert(len(val) == self.sequence_length) |
| 74 return self.sequence_string[(self.sequence_string.index(val)+1) % len(se
lf.sequence_string)] |
| 75 |
| 76 |
| 77 |
| 78 # |
| 79 # Simple Sequence Functions |
| 80 # |
| 81 def increment_by_one(cv=None, lv=None): |
| 82 if cv == None: |
| 83 return 0 |
| 84 return cv + 1 |
| 85 |
| 86 def double(cv=None, lv=None): |
| 87 if cv == None: |
| 88 return 1 |
| 89 return cv * 2 |
| 90 |
| 91 def fib(cv=1, lv=0): |
| 92 """The fibonacci sequence, this incrementer uses the |
| 93 last value""" |
| 94 if cv == None: |
| 95 cv = 1 |
| 96 if lv == None: |
| 97 lv = 0 |
| 98 return cv + lv |
| 99 |
| 100 increment_string = SequenceGenerator("ABCDEFGHIJKLMNOPQRSTUVWXYZ") |
| 101 |
| 102 |
| 103 |
| 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""" |
| 108 |
| 109 |
| 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 |
| 116 environment |
| 117 |
| 118 :param id: Optional ID (name) for this counter |
| 119 :type id: str |
| 120 |
| 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 |
| 124 |
| 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 |
| 129 |
| 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 functi
on that uses |
| 132 strings instead of ints to increment |
| 133 """ |
| 134 self._db = None |
| 135 self._value = None |
| 136 self.last_value = None |
| 137 self.domain_name = domain_name |
| 138 self.id = id |
| 139 if init_val == None: |
| 140 init_val = fnc(init_val) |
| 141 |
| 142 if self.id == None: |
| 143 import uuid |
| 144 self.id = str(uuid.uuid4()) |
| 145 |
| 146 self.item_type = type(fnc(None)) |
| 147 self.timestamp = None |
| 148 # Allow us to pass in a full name to a function |
| 149 if isinstance(fnc, str): |
| 150 from boto.utils import find_class |
| 151 fnc = find_class(fnc) |
| 152 self.fnc = fnc |
| 153 |
| 154 # Bootstrap the value last |
| 155 if not self.val: |
| 156 self.val = init_val |
| 157 |
| 158 def set(self, val): |
| 159 """Set the value""" |
| 160 import time |
| 161 now = time.time() |
| 162 expected_value = [] |
| 163 new_val = {} |
| 164 new_val['timestamp'] = now |
| 165 if self._value != None: |
| 166 new_val['last_value'] = self._value |
| 167 expected_value = ['current_value', str(self._value)] |
| 168 new_val['current_value'] = val |
| 169 try: |
| 170 self.db.put_attributes(self.id, new_val, expected_value=expected_val
ue) |
| 171 self.timestamp = new_val['timestamp'] |
| 172 except SDBResponseError, e: |
| 173 if e.status == 409: |
| 174 raise ValueError("Sequence out of sync") |
| 175 else: |
| 176 raise |
| 177 |
| 178 |
| 179 def get(self): |
| 180 """Get the value""" |
| 181 val = self.db.get_attributes(self.id, consistent_read=True) |
| 182 if val: |
| 183 if 'timestamp' in val: |
| 184 self.timestamp = val['timestamp'] |
| 185 if 'current_value' in val: |
| 186 self._value = self.item_type(val['current_value']) |
| 187 if "last_value" in val and val['last_value'] != None: |
| 188 self.last_value = self.item_type(val['last_value']) |
| 189 return self._value |
| 190 |
| 191 val = property(get, set) |
| 192 |
| 193 def __repr__(self): |
| 194 return "%s('%s', '%s', '%s.%s', '%s')" % ( |
| 195 self.__class__.__name__, |
| 196 self.id, |
| 197 self.domain_name, |
| 198 self.fnc.__module__, self.fnc.__name__, |
| 199 self.val) |
| 200 |
| 201 |
| 202 def _connect(self): |
| 203 """Connect to our domain""" |
| 204 if not self._db: |
| 205 import boto |
| 206 sdb = boto.connect_sdb() |
| 207 if not self.domain_name: |
| 208 self.domain_name = boto.config.get("DB", "sequence_db", boto.con
fig.get("DB", "db_name", "default")) |
| 209 try: |
| 210 self._db = sdb.get_domain(self.domain_name) |
| 211 except SDBResponseError, e: |
| 212 if e.status == 400: |
| 213 self._db = sdb.create_domain(self.domain_name) |
| 214 else: |
| 215 raise |
| 216 return self._db |
| 217 |
| 218 db = property(_connect) |
| 219 |
| 220 def next(self): |
| 221 self.val = self.fnc(self.val, self.last_value) |
| 222 return self.val |
| 223 |
| 224 def delete(self): |
| 225 """Remove this sequence""" |
| 226 self.db.delete_attributes(self.id) |
OLD | NEW |