OLD | NEW |
(Empty) | |
| 1 # Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/ |
| 2 # Copyright (c) 2010 Chris Moyer http://coredumped.org/ |
| 3 # |
| 4 # Permission is hereby granted, free of charge, to any person obtaining a |
| 5 # copy of this software and associated documentation files (the |
| 6 # "Software"), to deal in the Software without restriction, including |
| 7 # without limitation the rights to use, copy, modify, merge, publish, dis- |
| 8 # tribute, sublicense, and/or sell copies of the Software, and to permit |
| 9 # persons to whom the Software is furnished to do so, subject to the fol- |
| 10 # lowing conditions: |
| 11 # |
| 12 # The above copyright notice and this permission notice shall be included |
| 13 # in all copies or substantial portions of the Software. |
| 14 # |
| 15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| 16 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- |
| 17 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT |
| 18 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
| 19 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| 21 # IN THE SOFTWARE. |
| 22 import boto |
| 23 import re |
| 24 from boto.utils import find_class |
| 25 import uuid |
| 26 from boto.sdb.db.key import Key |
| 27 from boto.sdb.db.model import Model |
| 28 from boto.sdb.db.blob import Blob |
| 29 from boto.sdb.db.property import ListProperty, MapProperty |
| 30 from datetime import datetime, date, time |
| 31 from boto.exception import SDBPersistenceError, S3ResponseError |
| 32 |
| 33 ISO8601 = '%Y-%m-%dT%H:%M:%SZ' |
| 34 |
| 35 |
| 36 class TimeDecodeError(Exception): |
| 37 pass |
| 38 |
| 39 |
| 40 class SDBConverter(object): |
| 41 """ |
| 42 Responsible for converting base Python types to format compatible |
| 43 with underlying database. For SimpleDB, that means everything |
| 44 needs to be converted to a string when stored in SimpleDB and from |
| 45 a string when retrieved. |
| 46 |
| 47 To convert a value, pass it to the encode or decode method. The |
| 48 encode method will take a Python native value and convert to DB |
| 49 format. The decode method will take a DB format value and convert |
| 50 it to Python native format. To find the appropriate method to |
| 51 call, the generic encode/decode methods will look for the |
| 52 type-specific method by searching for a method |
| 53 called"encode_<type name>" or "decode_<type name>". |
| 54 """ |
| 55 def __init__(self, manager): |
| 56 self.manager = manager |
| 57 self.type_map = {bool: (self.encode_bool, self.decode_bool), |
| 58 int: (self.encode_int, self.decode_int), |
| 59 long: (self.encode_long, self.decode_long), |
| 60 float: (self.encode_float, self.decode_float), |
| 61 Model: (self.encode_reference, self.decode_reference), |
| 62 Key: (self.encode_reference, self.decode_reference), |
| 63 datetime: (self.encode_datetime, self.decode_datetime), |
| 64 date: (self.encode_date, self.decode_date), |
| 65 time: (self.encode_time, self.decode_time), |
| 66 Blob: (self.encode_blob, self.decode_blob), |
| 67 str: (self.encode_string, self.decode_string), |
| 68 } |
| 69 |
| 70 def encode(self, item_type, value): |
| 71 try: |
| 72 if Model in item_type.mro(): |
| 73 item_type = Model |
| 74 except: |
| 75 pass |
| 76 if item_type in self.type_map: |
| 77 encode = self.type_map[item_type][0] |
| 78 return encode(value) |
| 79 return value |
| 80 |
| 81 def decode(self, item_type, value): |
| 82 if item_type in self.type_map: |
| 83 decode = self.type_map[item_type][1] |
| 84 return decode(value) |
| 85 return value |
| 86 |
| 87 def encode_list(self, prop, value): |
| 88 if value in (None, []): |
| 89 return [] |
| 90 if not isinstance(value, list): |
| 91 # This is a little trick to avoid encoding when it's just a single v
alue, |
| 92 # since that most likely means it's from a query |
| 93 item_type = getattr(prop, "item_type") |
| 94 return self.encode(item_type, value) |
| 95 # Just enumerate(value) won't work here because |
| 96 # we need to add in some zero padding |
| 97 # We support lists up to 1,000 attributes, since |
| 98 # SDB technically only supports 1024 attributes anyway |
| 99 values = {} |
| 100 for k, v in enumerate(value): |
| 101 values["%03d" % k] = v |
| 102 return self.encode_map(prop, values) |
| 103 |
| 104 def encode_map(self, prop, value): |
| 105 import urllib |
| 106 if value == None: |
| 107 return None |
| 108 if not isinstance(value, dict): |
| 109 raise ValueError('Expected a dict value, got %s' % type(value)) |
| 110 new_value = [] |
| 111 for key in value: |
| 112 item_type = getattr(prop, "item_type") |
| 113 if Model in item_type.mro(): |
| 114 item_type = Model |
| 115 encoded_value = self.encode(item_type, value[key]) |
| 116 if encoded_value != None: |
| 117 new_value.append('%s:%s' % (urllib.quote(key), encoded_value)) |
| 118 return new_value |
| 119 |
| 120 def encode_prop(self, prop, value): |
| 121 if isinstance(prop, ListProperty): |
| 122 return self.encode_list(prop, value) |
| 123 elif isinstance(prop, MapProperty): |
| 124 return self.encode_map(prop, value) |
| 125 else: |
| 126 return self.encode(prop.data_type, value) |
| 127 |
| 128 def decode_list(self, prop, value): |
| 129 if not isinstance(value, list): |
| 130 value = [value] |
| 131 if hasattr(prop, 'item_type'): |
| 132 item_type = getattr(prop, "item_type") |
| 133 dec_val = {} |
| 134 for val in value: |
| 135 if val != None: |
| 136 k, v = self.decode_map_element(item_type, val) |
| 137 try: |
| 138 k = int(k) |
| 139 except: |
| 140 k = v |
| 141 dec_val[k] = v |
| 142 value = dec_val.values() |
| 143 return value |
| 144 |
| 145 def decode_map(self, prop, value): |
| 146 if not isinstance(value, list): |
| 147 value = [value] |
| 148 ret_value = {} |
| 149 item_type = getattr(prop, "item_type") |
| 150 for val in value: |
| 151 k, v = self.decode_map_element(item_type, val) |
| 152 ret_value[k] = v |
| 153 return ret_value |
| 154 |
| 155 def decode_map_element(self, item_type, value): |
| 156 """Decode a single element for a map""" |
| 157 import urllib |
| 158 key = value |
| 159 if ":" in value: |
| 160 key, value = value.split(':', 1) |
| 161 key = urllib.unquote(key) |
| 162 if Model in item_type.mro(): |
| 163 value = item_type(id=value) |
| 164 else: |
| 165 value = self.decode(item_type, value) |
| 166 return (key, value) |
| 167 |
| 168 def decode_prop(self, prop, value): |
| 169 if isinstance(prop, ListProperty): |
| 170 return self.decode_list(prop, value) |
| 171 elif isinstance(prop, MapProperty): |
| 172 return self.decode_map(prop, value) |
| 173 else: |
| 174 return self.decode(prop.data_type, value) |
| 175 |
| 176 def encode_int(self, value): |
| 177 value = int(value) |
| 178 value += 2147483648 |
| 179 return '%010d' % value |
| 180 |
| 181 def decode_int(self, value): |
| 182 try: |
| 183 value = int(value) |
| 184 except: |
| 185 boto.log.error("Error, %s is not an integer" % value) |
| 186 value = 0 |
| 187 value = int(value) |
| 188 value -= 2147483648 |
| 189 return int(value) |
| 190 |
| 191 def encode_long(self, value): |
| 192 value = long(value) |
| 193 value += 9223372036854775808 |
| 194 return '%020d' % value |
| 195 |
| 196 def decode_long(self, value): |
| 197 value = long(value) |
| 198 value -= 9223372036854775808 |
| 199 return value |
| 200 |
| 201 def encode_bool(self, value): |
| 202 if value == True or str(value).lower() in ("true", "yes"): |
| 203 return 'true' |
| 204 else: |
| 205 return 'false' |
| 206 |
| 207 def decode_bool(self, value): |
| 208 if value.lower() == 'true': |
| 209 return True |
| 210 else: |
| 211 return False |
| 212 |
| 213 def encode_float(self, value): |
| 214 """ |
| 215 See http://tools.ietf.org/html/draft-wood-ldapext-float-00. |
| 216 """ |
| 217 s = '%e' % value |
| 218 l = s.split('e') |
| 219 mantissa = l[0].ljust(18, '0') |
| 220 exponent = l[1] |
| 221 if value == 0.0: |
| 222 case = '3' |
| 223 exponent = '000' |
| 224 elif mantissa[0] != '-' and exponent[0] == '+': |
| 225 case = '5' |
| 226 exponent = exponent[1:].rjust(3, '0') |
| 227 elif mantissa[0] != '-' and exponent[0] == '-': |
| 228 case = '4' |
| 229 exponent = 999 + int(exponent) |
| 230 exponent = '%03d' % exponent |
| 231 elif mantissa[0] == '-' and exponent[0] == '-': |
| 232 case = '2' |
| 233 mantissa = '%f' % (10 + float(mantissa)) |
| 234 mantissa = mantissa.ljust(18, '0') |
| 235 exponent = exponent[1:].rjust(3, '0') |
| 236 else: |
| 237 case = '1' |
| 238 mantissa = '%f' % (10 + float(mantissa)) |
| 239 mantissa = mantissa.ljust(18, '0') |
| 240 exponent = 999 - int(exponent) |
| 241 exponent = '%03d' % exponent |
| 242 return '%s %s %s' % (case, exponent, mantissa) |
| 243 |
| 244 def decode_float(self, value): |
| 245 case = value[0] |
| 246 exponent = value[2:5] |
| 247 mantissa = value[6:] |
| 248 if case == '3': |
| 249 return 0.0 |
| 250 elif case == '5': |
| 251 pass |
| 252 elif case == '4': |
| 253 exponent = '%03d' % (int(exponent) - 999) |
| 254 elif case == '2': |
| 255 mantissa = '%f' % (float(mantissa) - 10) |
| 256 exponent = '-' + exponent |
| 257 else: |
| 258 mantissa = '%f' % (float(mantissa) - 10) |
| 259 exponent = '%03d' % abs((int(exponent) - 999)) |
| 260 return float(mantissa + 'e' + exponent) |
| 261 |
| 262 def encode_datetime(self, value): |
| 263 if isinstance(value, str) or isinstance(value, unicode): |
| 264 return value |
| 265 if isinstance(value, datetime): |
| 266 return value.strftime(ISO8601) |
| 267 else: |
| 268 return value.isoformat() |
| 269 |
| 270 def decode_datetime(self, value): |
| 271 """Handles both Dates and DateTime objects""" |
| 272 if value is None: |
| 273 return value |
| 274 try: |
| 275 if "T" in value: |
| 276 if "." in value: |
| 277 # Handle true "isoformat()" dates, which may have a microsec
ond on at the end of them |
| 278 return datetime.strptime(value.split(".")[0], "%Y-%m-%dT%H:%
M:%S") |
| 279 else: |
| 280 return datetime.strptime(value, ISO8601) |
| 281 else: |
| 282 value = value.split("-") |
| 283 return date(int(value[0]), int(value[1]), int(value[2])) |
| 284 except Exception, e: |
| 285 return None |
| 286 |
| 287 def encode_date(self, value): |
| 288 if isinstance(value, str) or isinstance(value, unicode): |
| 289 return value |
| 290 return value.isoformat() |
| 291 |
| 292 def decode_date(self, value): |
| 293 try: |
| 294 value = value.split("-") |
| 295 return date(int(value[0]), int(value[1]), int(value[2])) |
| 296 except: |
| 297 return None |
| 298 |
| 299 encode_time = encode_date |
| 300 |
| 301 def decode_time(self, value): |
| 302 """ converts strings in the form of HH:MM:SS.mmmmmm |
| 303 (created by datetime.time.isoformat()) to |
| 304 datetime.time objects. |
| 305 |
| 306 Timzone-aware strings ("HH:MM:SS.mmmmmm+HH:MM") won't |
| 307 be handled right now and will raise TimeDecodeError. |
| 308 """ |
| 309 if '-' in value or '+' in value: |
| 310 # TODO: Handle tzinfo |
| 311 raise TimeDecodeError("Can't handle timezone aware objects: %r" % va
lue) |
| 312 tmp = value.split('.') |
| 313 arg = map(int, tmp[0].split(':')) |
| 314 if len(tmp) == 2: |
| 315 arg.append(int(tmp[1])) |
| 316 return time(*arg) |
| 317 |
| 318 def encode_reference(self, value): |
| 319 if value in (None, 'None', '', ' '): |
| 320 return None |
| 321 if isinstance(value, str) or isinstance(value, unicode): |
| 322 return value |
| 323 else: |
| 324 return value.id |
| 325 |
| 326 def decode_reference(self, value): |
| 327 if not value or value == "None": |
| 328 return None |
| 329 return value |
| 330 |
| 331 def encode_blob(self, value): |
| 332 if not value: |
| 333 return None |
| 334 if isinstance(value, str): |
| 335 return value |
| 336 |
| 337 if not value.id: |
| 338 bucket = self.manager.get_blob_bucket() |
| 339 key = bucket.new_key(str(uuid.uuid4())) |
| 340 value.id = "s3://%s/%s" % (key.bucket.name, key.name) |
| 341 else: |
| 342 match = re.match("^s3:\/\/([^\/]*)\/(.*)$", value.id) |
| 343 if match: |
| 344 s3 = self.manager.get_s3_connection() |
| 345 bucket = s3.get_bucket(match.group(1), validate=False) |
| 346 key = bucket.get_key(match.group(2)) |
| 347 else: |
| 348 raise SDBPersistenceError("Invalid Blob ID: %s" % value.id) |
| 349 |
| 350 if value.value != None: |
| 351 key.set_contents_from_string(value.value) |
| 352 return value.id |
| 353 |
| 354 def decode_blob(self, value): |
| 355 if not value: |
| 356 return None |
| 357 match = re.match("^s3:\/\/([^\/]*)\/(.*)$", value) |
| 358 if match: |
| 359 s3 = self.manager.get_s3_connection() |
| 360 bucket = s3.get_bucket(match.group(1), validate=False) |
| 361 try: |
| 362 key = bucket.get_key(match.group(2)) |
| 363 except S3ResponseError, e: |
| 364 if e.reason != "Forbidden": |
| 365 raise |
| 366 return None |
| 367 else: |
| 368 return None |
| 369 if key: |
| 370 return Blob(file=key, id="s3://%s/%s" % (key.bucket.name, key.name)) |
| 371 else: |
| 372 return None |
| 373 |
| 374 def encode_string(self, value): |
| 375 """Convert ASCII, Latin-1 or UTF-8 to pure Unicode""" |
| 376 if not isinstance(value, str): |
| 377 return value |
| 378 try: |
| 379 return unicode(value, 'utf-8') |
| 380 except: |
| 381 # really, this should throw an exception. |
| 382 # in the interest of not breaking current |
| 383 # systems, however: |
| 384 arr = [] |
| 385 for ch in value: |
| 386 arr.append(unichr(ord(ch))) |
| 387 return u"".join(arr) |
| 388 |
| 389 def decode_string(self, value): |
| 390 """Decoding a string is really nothing, just |
| 391 return the value as-is""" |
| 392 return value |
| 393 |
| 394 |
| 395 class SDBManager(object): |
| 396 |
| 397 def __init__(self, cls, db_name, db_user, db_passwd, |
| 398 db_host, db_port, db_table, ddl_dir, enable_ssl, |
| 399 consistent=None): |
| 400 self.cls = cls |
| 401 self.db_name = db_name |
| 402 self.db_user = db_user |
| 403 self.db_passwd = db_passwd |
| 404 self.db_host = db_host |
| 405 self.db_port = db_port |
| 406 self.db_table = db_table |
| 407 self.ddl_dir = ddl_dir |
| 408 self.enable_ssl = enable_ssl |
| 409 self.s3 = None |
| 410 self.bucket = None |
| 411 self.converter = SDBConverter(self) |
| 412 self._sdb = None |
| 413 self._domain = None |
| 414 if consistent == None and hasattr(cls, "__consistent__"): |
| 415 consistent = cls.__consistent__ |
| 416 self.consistent = consistent |
| 417 |
| 418 @property |
| 419 def sdb(self): |
| 420 if self._sdb is None: |
| 421 self._connect() |
| 422 return self._sdb |
| 423 |
| 424 @property |
| 425 def domain(self): |
| 426 if self._domain is None: |
| 427 self._connect() |
| 428 return self._domain |
| 429 |
| 430 def _connect(self): |
| 431 args = dict(aws_access_key_id=self.db_user, |
| 432 aws_secret_access_key=self.db_passwd, |
| 433 is_secure=self.enable_ssl) |
| 434 try: |
| 435 region = [x for x in boto.sdb.regions() if x.endpoint == self.db_hos
t][0] |
| 436 args['region'] = region |
| 437 except IndexError: |
| 438 pass |
| 439 self._sdb = boto.connect_sdb(**args) |
| 440 # This assumes that the domain has already been created |
| 441 # It's much more efficient to do it this way rather than |
| 442 # having this make a roundtrip each time to validate. |
| 443 # The downside is that if the domain doesn't exist, it breaks |
| 444 self._domain = self._sdb.lookup(self.db_name, validate=False) |
| 445 if not self._domain: |
| 446 self._domain = self._sdb.create_domain(self.db_name) |
| 447 |
| 448 def _object_lister(self, cls, query_lister): |
| 449 for item in query_lister: |
| 450 obj = self.get_object(cls, item.name, item) |
| 451 if obj: |
| 452 yield obj |
| 453 |
| 454 def encode_value(self, prop, value): |
| 455 if value == None: |
| 456 return None |
| 457 if not prop: |
| 458 return str(value) |
| 459 return self.converter.encode_prop(prop, value) |
| 460 |
| 461 def decode_value(self, prop, value): |
| 462 return self.converter.decode_prop(prop, value) |
| 463 |
| 464 def get_s3_connection(self): |
| 465 if not self.s3: |
| 466 self.s3 = boto.connect_s3(self.db_user, self.db_passwd) |
| 467 return self.s3 |
| 468 |
| 469 def get_blob_bucket(self, bucket_name=None): |
| 470 s3 = self.get_s3_connection() |
| 471 bucket_name = "%s-%s" % (s3.aws_access_key_id, self.domain.name) |
| 472 bucket_name = bucket_name.lower() |
| 473 try: |
| 474 self.bucket = s3.get_bucket(bucket_name) |
| 475 except: |
| 476 self.bucket = s3.create_bucket(bucket_name) |
| 477 return self.bucket |
| 478 |
| 479 def load_object(self, obj): |
| 480 if not obj._loaded: |
| 481 a = self.domain.get_attributes(obj.id, consistent_read=self.consiste
nt) |
| 482 if '__type__' in a: |
| 483 for prop in obj.properties(hidden=False): |
| 484 if prop.name in a: |
| 485 value = self.decode_value(prop, a[prop.name]) |
| 486 value = prop.make_value_from_datastore(value) |
| 487 try: |
| 488 setattr(obj, prop.name, value) |
| 489 except Exception, e: |
| 490 boto.log.exception(e) |
| 491 obj._loaded = True |
| 492 |
| 493 def get_object(self, cls, id, a=None): |
| 494 obj = None |
| 495 if not a: |
| 496 a = self.domain.get_attributes(id, consistent_read=self.consistent) |
| 497 if '__type__' in a: |
| 498 if not cls or a['__type__'] != cls.__name__: |
| 499 cls = find_class(a['__module__'], a['__type__']) |
| 500 if cls: |
| 501 params = {} |
| 502 for prop in cls.properties(hidden=False): |
| 503 if prop.name in a: |
| 504 value = self.decode_value(prop, a[prop.name]) |
| 505 value = prop.make_value_from_datastore(value) |
| 506 params[prop.name] = value |
| 507 obj = cls(id, **params) |
| 508 obj._loaded = True |
| 509 else: |
| 510 s = '(%s) class %s.%s not found' % (id, a['__module__'], a['__ty
pe__']) |
| 511 boto.log.info('sdbmanager: %s' % s) |
| 512 return obj |
| 513 |
| 514 def get_object_from_id(self, id): |
| 515 return self.get_object(None, id) |
| 516 |
| 517 def query(self, query): |
| 518 query_str = "select * from `%s` %s" % (self.domain.name, self._build_fil
ter_part(query.model_class, query.filters, query.sort_by, query.select)) |
| 519 if query.limit: |
| 520 query_str += " limit %s" % query.limit |
| 521 rs = self.domain.select(query_str, max_items=query.limit, next_token = q
uery.next_token) |
| 522 query.rs = rs |
| 523 return self._object_lister(query.model_class, rs) |
| 524 |
| 525 def count(self, cls, filters, quick=True, sort_by=None, select=None): |
| 526 """ |
| 527 Get the number of results that would |
| 528 be returned in this query |
| 529 """ |
| 530 query = "select count(*) from `%s` %s" % (self.domain.name, self._build_
filter_part(cls, filters, sort_by, select)) |
| 531 count = 0 |
| 532 for row in self.domain.select(query): |
| 533 count += int(row['Count']) |
| 534 if quick: |
| 535 return count |
| 536 return count |
| 537 |
| 538 def _build_filter(self, property, name, op, val): |
| 539 if name == "__id__": |
| 540 name = 'itemName()' |
| 541 if name != "itemName()": |
| 542 name = '`%s`' % name |
| 543 if val == None: |
| 544 if op in ('is', '='): |
| 545 return "%(name)s is null" % {"name": name} |
| 546 elif op in ('is not', '!='): |
| 547 return "%s is not null" % name |
| 548 else: |
| 549 val = "" |
| 550 if property.__class__ == ListProperty: |
| 551 if op in ("is", "="): |
| 552 op = "like" |
| 553 elif op in ("!=", "not"): |
| 554 op = "not like" |
| 555 if not(op in ["like", "not like"] and val.startswith("%")): |
| 556 val = "%%:%s" % val |
| 557 return "%s %s '%s'" % (name, op, val.replace("'", "''")) |
| 558 |
| 559 def _build_filter_part(self, cls, filters, order_by=None, select=None): |
| 560 """ |
| 561 Build the filter part |
| 562 """ |
| 563 import types |
| 564 query_parts = [] |
| 565 |
| 566 order_by_filtered = False |
| 567 |
| 568 if order_by: |
| 569 if order_by[0] == "-": |
| 570 order_by_method = "DESC" |
| 571 order_by = order_by[1:] |
| 572 else: |
| 573 order_by_method = "ASC" |
| 574 |
| 575 if select: |
| 576 if order_by and order_by in select: |
| 577 order_by_filtered = True |
| 578 query_parts.append("(%s)" % select) |
| 579 |
| 580 if isinstance(filters, str) or isinstance(filters, unicode): |
| 581 query = "WHERE %s AND `__type__` = '%s'" % (filters, cls.__name__) |
| 582 if order_by in ["__id__", "itemName()"]: |
| 583 query += " ORDER BY itemName() %s" % order_by_method |
| 584 elif order_by != None: |
| 585 query += " ORDER BY `%s` %s" % (order_by, order_by_method) |
| 586 return query |
| 587 |
| 588 for filter in filters: |
| 589 filter_parts = [] |
| 590 filter_props = filter[0] |
| 591 if not isinstance(filter_props, list): |
| 592 filter_props = [filter_props] |
| 593 for filter_prop in filter_props: |
| 594 (name, op) = filter_prop.strip().split(" ", 1) |
| 595 value = filter[1] |
| 596 property = cls.find_property(name) |
| 597 if name == order_by: |
| 598 order_by_filtered = True |
| 599 if types.TypeType(value) == types.ListType: |
| 600 filter_parts_sub = [] |
| 601 for val in value: |
| 602 val = self.encode_value(property, val) |
| 603 if isinstance(val, list): |
| 604 for v in val: |
| 605 filter_parts_sub.append(self._build_filter(prope
rty, name, op, v)) |
| 606 else: |
| 607 filter_parts_sub.append(self._build_filter(property,
name, op, val)) |
| 608 filter_parts.append("(%s)" % (" OR ".join(filter_parts_sub))
) |
| 609 else: |
| 610 val = self.encode_value(property, value) |
| 611 if isinstance(val, list): |
| 612 for v in val: |
| 613 filter_parts.append(self._build_filter(property, nam
e, op, v)) |
| 614 else: |
| 615 filter_parts.append(self._build_filter(property, name, o
p, val)) |
| 616 query_parts.append("(%s)" % (" or ".join(filter_parts))) |
| 617 |
| 618 |
| 619 type_query = "(`__type__` = '%s'" % cls.__name__ |
| 620 for subclass in self._get_all_decendents(cls).keys(): |
| 621 type_query += " or `__type__` = '%s'" % subclass |
| 622 type_query += ")" |
| 623 query_parts.append(type_query) |
| 624 |
| 625 order_by_query = "" |
| 626 |
| 627 if order_by: |
| 628 if not order_by_filtered: |
| 629 query_parts.append("`%s` LIKE '%%'" % order_by) |
| 630 if order_by in ["__id__", "itemName()"]: |
| 631 order_by_query = " ORDER BY itemName() %s" % order_by_method |
| 632 else: |
| 633 order_by_query = " ORDER BY `%s` %s" % (order_by, order_by_metho
d) |
| 634 |
| 635 if len(query_parts) > 0: |
| 636 return "WHERE %s %s" % (" AND ".join(query_parts), order_by_query) |
| 637 else: |
| 638 return "" |
| 639 |
| 640 |
| 641 def _get_all_decendents(self, cls): |
| 642 """Get all decendents for a given class""" |
| 643 decendents = {} |
| 644 for sc in cls.__sub_classes__: |
| 645 decendents[sc.__name__] = sc |
| 646 decendents.update(self._get_all_decendents(sc)) |
| 647 return decendents |
| 648 |
| 649 def query_gql(self, query_string, *args, **kwds): |
| 650 raise NotImplementedError("GQL queries not supported in SimpleDB") |
| 651 |
| 652 def save_object(self, obj, expected_value=None): |
| 653 if not obj.id: |
| 654 obj.id = str(uuid.uuid4()) |
| 655 |
| 656 attrs = {'__type__': obj.__class__.__name__, |
| 657 '__module__': obj.__class__.__module__, |
| 658 '__lineage__': obj.get_lineage()} |
| 659 del_attrs = [] |
| 660 for property in obj.properties(hidden=False): |
| 661 value = property.get_value_for_datastore(obj) |
| 662 if value is not None: |
| 663 value = self.encode_value(property, value) |
| 664 if value == []: |
| 665 value = None |
| 666 if value == None: |
| 667 del_attrs.append(property.name) |
| 668 continue |
| 669 attrs[property.name] = value |
| 670 if property.unique: |
| 671 try: |
| 672 args = {property.name: value} |
| 673 obj2 = obj.find(**args).next() |
| 674 if obj2.id != obj.id: |
| 675 raise SDBPersistenceError("Error: %s must be unique!" %
property.name) |
| 676 except(StopIteration): |
| 677 pass |
| 678 # Convert the Expected value to SDB format |
| 679 if expected_value: |
| 680 prop = obj.find_property(expected_value[0]) |
| 681 v = expected_value[1] |
| 682 if v is not None and not isinstance(v, bool): |
| 683 v = self.encode_value(prop, v) |
| 684 expected_value[1] = v |
| 685 self.domain.put_attributes(obj.id, attrs, replace=True, expected_value=e
xpected_value) |
| 686 if len(del_attrs) > 0: |
| 687 self.domain.delete_attributes(obj.id, del_attrs) |
| 688 return obj |
| 689 |
| 690 def delete_object(self, obj): |
| 691 self.domain.delete_attributes(obj.id) |
| 692 |
| 693 def set_property(self, prop, obj, name, value): |
| 694 setattr(obj, name, value) |
| 695 value = prop.get_value_for_datastore(obj) |
| 696 value = self.encode_value(prop, value) |
| 697 if prop.unique: |
| 698 try: |
| 699 args = {prop.name: value} |
| 700 obj2 = obj.find(**args).next() |
| 701 if obj2.id != obj.id: |
| 702 raise SDBPersistenceError("Error: %s must be unique!" % prop
.name) |
| 703 except(StopIteration): |
| 704 pass |
| 705 self.domain.put_attributes(obj.id, {name: value}, replace=True) |
| 706 |
| 707 def get_property(self, prop, obj, name): |
| 708 a = self.domain.get_attributes(obj.id, consistent_read=self.consistent) |
| 709 |
| 710 # try to get the attribute value from SDB |
| 711 if name in a: |
| 712 value = self.decode_value(prop, a[name]) |
| 713 value = prop.make_value_from_datastore(value) |
| 714 setattr(obj, prop.name, value) |
| 715 return value |
| 716 raise AttributeError('%s not found' % name) |
| 717 |
| 718 def set_key_value(self, obj, name, value): |
| 719 self.domain.put_attributes(obj.id, {name: value}, replace=True) |
| 720 |
| 721 def delete_key_value(self, obj, name): |
| 722 self.domain.delete_attributes(obj.id, name) |
| 723 |
| 724 def get_key_value(self, obj, name): |
| 725 a = self.domain.get_attributes(obj.id, name, consistent_read=self.consis
tent) |
| 726 if name in a: |
| 727 return a[name] |
| 728 else: |
| 729 return None |
| 730 |
| 731 def get_raw_item(self, obj): |
| 732 return self.domain.get_item(obj.id) |
OLD | NEW |