| OLD | NEW |
| 1 # Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/ | 1 # Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.org/ |
| 2 # Copyright (c) 2010 Chris Moyer http://coredumped.org/ | 2 # Copyright (c) 2010 Chris Moyer http://coredumped.org/ |
| 3 # | 3 # |
| 4 # Permission is hereby granted, free of charge, to any person obtaining a | 4 # Permission is hereby granted, free of charge, to any person obtaining a |
| 5 # copy of this software and associated documentation files (the | 5 # copy of this software and associated documentation files (the |
| 6 # "Software"), to deal in the Software without restriction, including | 6 # "Software"), to deal in the Software without restriction, including |
| 7 # without limitation the rights to use, copy, modify, merge, publish, dis- | 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 | 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- | 9 # persons to whom the Software is furnished to do so, subject to the fol- |
| 10 # lowing conditions: | 10 # lowing conditions: |
| 11 # | 11 # |
| 12 # The above copyright notice and this permission notice shall be included | 12 # The above copyright notice and this permission notice shall be included |
| 13 # in all copies or substantial portions of the Software. | 13 # in all copies or substantial portions of the Software. |
| 14 # | 14 # |
| 15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | 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- | 16 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- |
| 17 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT | 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, | 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, | 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 | 20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| 21 # IN THE SOFTWARE. | 21 # IN THE SOFTWARE. |
| 22 import boto | 22 import boto |
| 23 import re | 23 import re |
| 24 from boto.utils import find_class | 24 from boto.utils import find_class |
| 25 import uuid | 25 import uuid |
| 26 from boto.sdb.db.key import Key | 26 from boto.sdb.db.key import Key |
| 27 from boto.sdb.db.model import Model | 27 from boto.sdb.db.model import Model |
| 28 from boto.sdb.db.blob import Blob | 28 from boto.sdb.db.blob import Blob |
| 29 from boto.sdb.db.property import ListProperty, MapProperty | 29 from boto.sdb.db.property import ListProperty, MapProperty |
| 30 from datetime import datetime, date | 30 from datetime import datetime, date, time |
| 31 from boto.exception import SDBPersistenceError | 31 from boto.exception import SDBPersistenceError, S3ResponseError |
| 32 | 32 |
| 33 ISO8601 = '%Y-%m-%dT%H:%M:%SZ' | 33 ISO8601 = '%Y-%m-%dT%H:%M:%SZ' |
| 34 | 34 |
| 35 class TimeDecodeError(Exception): |
| 36 pass |
| 35 | 37 |
| 36 class SDBConverter: | 38 class SDBConverter(object): |
| 37 """ | 39 """ |
| 38 Responsible for converting base Python types to format compatible with under
lying | 40 Responsible for converting base Python types to format compatible with under
lying |
| 39 database. For SimpleDB, that means everything needs to be converted to a st
ring | 41 database. For SimpleDB, that means everything needs to be converted to a st
ring |
| 40 when stored in SimpleDB and from a string when retrieved. | 42 when stored in SimpleDB and from a string when retrieved. |
| 41 | 43 |
| 42 To convert a value, pass it to the encode or decode method. The encode meth
od | 44 To convert a value, pass it to the encode or decode method. The encode meth
od |
| 43 will take a Python native value and convert to DB format. The decode method
will | 45 will take a Python native value and convert to DB format. The decode method
will |
| 44 take a DB format value and convert it to Python native format. To find the
appropriate | 46 take a DB format value and convert it to Python native format. To find the
appropriate |
| 45 method to call, the generic encode/decode methods will look for the type-spe
cific | 47 method to call, the generic encode/decode methods will look for the type-spe
cific |
| 46 method by searching for a method called "encode_<type name>" or "decode_<typ
e name>". | 48 method by searching for a method called "encode_<type name>" or "decode_<typ
e name>". |
| 47 """ | 49 """ |
| 48 def __init__(self, manager): | 50 def __init__(self, manager): |
| 49 self.manager = manager | 51 self.manager = manager |
| 50 self.type_map = { bool : (self.encode_bool, self.decode_bool), | 52 self.type_map = { bool : (self.encode_bool, self.decode_bool), |
| 51 int : (self.encode_int, self.decode_int), | 53 int : (self.encode_int, self.decode_int), |
| 52 long : (self.encode_long, self.decode_long), | 54 long : (self.encode_long, self.decode_long), |
| 53 float : (self.encode_float, self.decode_float), | 55 float : (self.encode_float, self.decode_float), |
| 54 Model : (self.encode_reference, self.decode_reference)
, | 56 Model : (self.encode_reference, self.decode_reference)
, |
| 55 Key : (self.encode_reference, self.decode_reference), | 57 Key : (self.encode_reference, self.decode_reference), |
| 56 datetime : (self.encode_datetime, self.decode_datetime
), | 58 datetime : (self.encode_datetime, self.decode_datetime
), |
| 57 date : (self.encode_date, self.decode_date), | 59 date : (self.encode_date, self.decode_date), |
| 60 time : (self.encode_time, self.decode_time), |
| 58 Blob: (self.encode_blob, self.decode_blob), | 61 Blob: (self.encode_blob, self.decode_blob), |
| 62 str: (self.encode_string, self.decode_string), |
| 59 } | 63 } |
| 60 | 64 |
| 61 def encode(self, item_type, value): | 65 def encode(self, item_type, value): |
| 62 try: | 66 try: |
| 63 if Model in item_type.mro(): | 67 if Model in item_type.mro(): |
| 64 item_type = Model | 68 item_type = Model |
| 65 except: | 69 except: |
| 66 pass | 70 pass |
| 67 if item_type in self.type_map: | 71 if item_type in self.type_map: |
| 68 encode = self.type_map[item_type][0] | 72 encode = self.type_map[item_type][0] |
| (...skipping 17 matching lines...) Expand all Loading... |
| 86 # Just enumerate(value) won't work here because | 90 # Just enumerate(value) won't work here because |
| 87 # we need to add in some zero padding | 91 # we need to add in some zero padding |
| 88 # We support lists up to 1,000 attributes, since | 92 # We support lists up to 1,000 attributes, since |
| 89 # SDB technically only supports 1024 attributes anyway | 93 # SDB technically only supports 1024 attributes anyway |
| 90 values = {} | 94 values = {} |
| 91 for k,v in enumerate(value): | 95 for k,v in enumerate(value): |
| 92 values["%03d" % k] = v | 96 values["%03d" % k] = v |
| 93 return self.encode_map(prop, values) | 97 return self.encode_map(prop, values) |
| 94 | 98 |
| 95 def encode_map(self, prop, value): | 99 def encode_map(self, prop, value): |
| 100 import urllib |
| 96 if value == None: | 101 if value == None: |
| 97 return None | 102 return None |
| 98 if not isinstance(value, dict): | 103 if not isinstance(value, dict): |
| 99 raise ValueError, 'Expected a dict value, got %s' % type(value) | 104 raise ValueError, 'Expected a dict value, got %s' % type(value) |
| 100 new_value = [] | 105 new_value = [] |
| 101 for key in value: | 106 for key in value: |
| 102 item_type = getattr(prop, "item_type") | 107 item_type = getattr(prop, "item_type") |
| 103 if Model in item_type.mro(): | 108 if Model in item_type.mro(): |
| 104 item_type = Model | 109 item_type = Model |
| 105 encoded_value = self.encode(item_type, value[key]) | 110 encoded_value = self.encode(item_type, value[key]) |
| 106 if encoded_value != None: | 111 if encoded_value != None: |
| 107 new_value.append('%s:%s' % (key, encoded_value)) | 112 new_value.append('%s:%s' % (urllib.quote(key), encoded_value)) |
| 108 return new_value | 113 return new_value |
| 109 | 114 |
| 110 def encode_prop(self, prop, value): | 115 def encode_prop(self, prop, value): |
| 111 if isinstance(prop, ListProperty): | 116 if isinstance(prop, ListProperty): |
| 112 return self.encode_list(prop, value) | 117 return self.encode_list(prop, value) |
| 113 elif isinstance(prop, MapProperty): | 118 elif isinstance(prop, MapProperty): |
| 114 return self.encode_map(prop, value) | 119 return self.encode_map(prop, value) |
| 115 else: | 120 else: |
| 116 return self.encode(prop.data_type, value) | 121 return self.encode(prop.data_type, value) |
| 117 | 122 |
| (...skipping 19 matching lines...) Expand all Loading... |
| 137 value = [value] | 142 value = [value] |
| 138 ret_value = {} | 143 ret_value = {} |
| 139 item_type = getattr(prop, "item_type") | 144 item_type = getattr(prop, "item_type") |
| 140 for val in value: | 145 for val in value: |
| 141 k,v = self.decode_map_element(item_type, val) | 146 k,v = self.decode_map_element(item_type, val) |
| 142 ret_value[k] = v | 147 ret_value[k] = v |
| 143 return ret_value | 148 return ret_value |
| 144 | 149 |
| 145 def decode_map_element(self, item_type, value): | 150 def decode_map_element(self, item_type, value): |
| 146 """Decode a single element for a map""" | 151 """Decode a single element for a map""" |
| 152 import urllib |
| 147 key = value | 153 key = value |
| 148 if ":" in value: | 154 if ":" in value: |
| 149 key, value = value.split(':',1) | 155 key, value = value.split(':',1) |
| 156 key = urllib.unquote(key) |
| 150 if Model in item_type.mro(): | 157 if Model in item_type.mro(): |
| 151 value = item_type(id=value) | 158 value = item_type(id=value) |
| 152 else: | 159 else: |
| 153 value = self.decode(item_type, value) | 160 value = self.decode(item_type, value) |
| 154 return (key, value) | 161 return (key, value) |
| 155 | 162 |
| 156 def decode_prop(self, prop, value): | 163 def decode_prop(self, prop, value): |
| 157 if isinstance(prop, ListProperty): | 164 if isinstance(prop, ListProperty): |
| 158 return self.decode_list(prop, value) | 165 return self.decode_list(prop, value) |
| 159 elif isinstance(prop, MapProperty): | 166 elif isinstance(prop, MapProperty): |
| (...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 263 return value | 270 return value |
| 264 return value.isoformat() | 271 return value.isoformat() |
| 265 | 272 |
| 266 def decode_date(self, value): | 273 def decode_date(self, value): |
| 267 try: | 274 try: |
| 268 value = value.split("-") | 275 value = value.split("-") |
| 269 return date(int(value[0]), int(value[1]), int(value[2])) | 276 return date(int(value[0]), int(value[1]), int(value[2])) |
| 270 except: | 277 except: |
| 271 return None | 278 return None |
| 272 | 279 |
| 280 encode_time = encode_date |
| 281 |
| 282 def decode_time(self, value): |
| 283 """ converts strings in the form of HH:MM:SS.mmmmmm |
| 284 (created by datetime.time.isoformat()) to |
| 285 datetime.time objects. |
| 286 |
| 287 Timzone-aware strings ("HH:MM:SS.mmmmmm+HH:MM") won't |
| 288 be handled right now and will raise TimeDecodeError. |
| 289 """ |
| 290 if '-' in value or '+' in value: |
| 291 # TODO: Handle tzinfo |
| 292 raise TimeDecodeError("Can't handle timezone aware objects: %r" % va
lue) |
| 293 tmp = value.split('.') |
| 294 arg = map(int, tmp[0].split(':')) |
| 295 if len(tmp) == 2: |
| 296 arg.append(int(tmp[1])) |
| 297 return time(*arg) |
| 298 |
| 273 def encode_reference(self, value): | 299 def encode_reference(self, value): |
| 274 if value in (None, 'None', '', ' '): | 300 if value in (None, 'None', '', ' '): |
| 275 return None | 301 return None |
| 276 if isinstance(value, str) or isinstance(value, unicode): | 302 if isinstance(value, str) or isinstance(value, unicode): |
| 277 return value | 303 return value |
| 278 else: | 304 else: |
| 279 return value.id | 305 return value.id |
| 280 | 306 |
| 281 def decode_reference(self, value): | 307 def decode_reference(self, value): |
| 282 if not value or value == "None": | 308 if not value or value == "None": |
| (...skipping 24 matching lines...) Expand all Loading... |
| 307 return value.id | 333 return value.id |
| 308 | 334 |
| 309 | 335 |
| 310 def decode_blob(self, value): | 336 def decode_blob(self, value): |
| 311 if not value: | 337 if not value: |
| 312 return None | 338 return None |
| 313 match = re.match("^s3:\/\/([^\/]*)\/(.*)$", value) | 339 match = re.match("^s3:\/\/([^\/]*)\/(.*)$", value) |
| 314 if match: | 340 if match: |
| 315 s3 = self.manager.get_s3_connection() | 341 s3 = self.manager.get_s3_connection() |
| 316 bucket = s3.get_bucket(match.group(1), validate=False) | 342 bucket = s3.get_bucket(match.group(1), validate=False) |
| 317 key = bucket.get_key(match.group(2)) | 343 try: |
| 344 key = bucket.get_key(match.group(2)) |
| 345 except S3ResponseError, e: |
| 346 if e.reason != "Forbidden": |
| 347 raise |
| 348 return None |
| 318 else: | 349 else: |
| 319 return None | 350 return None |
| 320 if key: | 351 if key: |
| 321 return Blob(file=key, id="s3://%s/%s" % (key.bucket.name, key.name)) | 352 return Blob(file=key, id="s3://%s/%s" % (key.bucket.name, key.name)) |
| 322 else: | 353 else: |
| 323 return None | 354 return None |
| 324 | 355 |
| 356 def encode_string(self, value): |
| 357 """Convert ASCII, Latin-1 or UTF-8 to pure Unicode""" |
| 358 if not isinstance(value, str): return value |
| 359 try: |
| 360 return unicode(value, 'utf-8') |
| 361 except: # really, this should throw an exception. |
| 362 # in the interest of not breaking current |
| 363 # systems, however: |
| 364 arr = [] |
| 365 for ch in value: |
| 366 arr.append(unichr(ord(ch))) |
| 367 return u"".join(arr) |
| 368 |
| 369 def decode_string(self, value): |
| 370 """Decoding a string is really nothing, just |
| 371 return the value as-is""" |
| 372 return value |
| 373 |
| 325 class SDBManager(object): | 374 class SDBManager(object): |
| 326 | 375 |
| 327 def __init__(self, cls, db_name, db_user, db_passwd, | 376 def __init__(self, cls, db_name, db_user, db_passwd, |
| 328 db_host, db_port, db_table, ddl_dir, enable_ssl, consistent=Non
e): | 377 db_host, db_port, db_table, ddl_dir, enable_ssl, consistent=Non
e): |
| 329 self.cls = cls | 378 self.cls = cls |
| 330 self.db_name = db_name | 379 self.db_name = db_name |
| 331 self.db_user = db_user | 380 self.db_user = db_user |
| 332 self.db_passwd = db_passwd | 381 self.db_passwd = db_passwd |
| 333 self.db_host = db_host | 382 self.db_host = db_host |
| 334 self.db_port = db_port | 383 self.db_port = db_port |
| (...skipping 15 matching lines...) Expand all Loading... |
| 350 self._connect() | 399 self._connect() |
| 351 return self._sdb | 400 return self._sdb |
| 352 | 401 |
| 353 @property | 402 @property |
| 354 def domain(self): | 403 def domain(self): |
| 355 if self._domain is None: | 404 if self._domain is None: |
| 356 self._connect() | 405 self._connect() |
| 357 return self._domain | 406 return self._domain |
| 358 | 407 |
| 359 def _connect(self): | 408 def _connect(self): |
| 360 self._sdb = boto.connect_sdb(aws_access_key_id=self.db_user, | 409 args = dict(aws_access_key_id=self.db_user, |
| 361 aws_secret_access_key=self.db_passwd, | 410 aws_secret_access_key=self.db_passwd, |
| 362 is_secure=self.enable_ssl) | 411 is_secure=self.enable_ssl) |
| 412 try: |
| 413 region = [x for x in boto.sdb.regions() if x.endpoint == self.db_hos
t][0] |
| 414 args['region'] = region |
| 415 except IndexError: |
| 416 pass |
| 417 self._sdb = boto.connect_sdb(**args) |
| 363 # This assumes that the domain has already been created | 418 # This assumes that the domain has already been created |
| 364 # It's much more efficient to do it this way rather than | 419 # It's much more efficient to do it this way rather than |
| 365 # having this make a roundtrip each time to validate. | 420 # having this make a roundtrip each time to validate. |
| 366 # The downside is that if the domain doesn't exist, it breaks | 421 # The downside is that if the domain doesn't exist, it breaks |
| 367 self._domain = self._sdb.lookup(self.db_name, validate=False) | 422 self._domain = self._sdb.lookup(self.db_name, validate=False) |
| 368 if not self._domain: | 423 if not self._domain: |
| 369 self._domain = self._sdb.create_domain(self.db_name) | 424 self._domain = self._sdb.create_domain(self.db_name) |
| 370 | 425 |
| 371 def _object_lister(self, cls, query_lister): | 426 def _object_lister(self, cls, query_lister): |
| 372 for item in query_lister: | 427 for item in query_lister: |
| (...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 479 if not(op in ["like", "not like"] and val.startswith("%")): | 534 if not(op in ["like", "not like"] and val.startswith("%")): |
| 480 val = "%%:%s" % val | 535 val = "%%:%s" % val |
| 481 return "%s %s '%s'" % (name, op, val.replace("'", "''")) | 536 return "%s %s '%s'" % (name, op, val.replace("'", "''")) |
| 482 | 537 |
| 483 def _build_filter_part(self, cls, filters, order_by=None, select=None): | 538 def _build_filter_part(self, cls, filters, order_by=None, select=None): |
| 484 """ | 539 """ |
| 485 Build the filter part | 540 Build the filter part |
| 486 """ | 541 """ |
| 487 import types | 542 import types |
| 488 query_parts = [] | 543 query_parts = [] |
| 544 |
| 489 order_by_filtered = False | 545 order_by_filtered = False |
| 546 |
| 490 if order_by: | 547 if order_by: |
| 491 if order_by[0] == "-": | 548 if order_by[0] == "-": |
| 492 order_by_method = "DESC"; | 549 order_by_method = "DESC"; |
| 493 order_by = order_by[1:] | 550 order_by = order_by[1:] |
| 494 else: | 551 else: |
| 495 order_by_method = "ASC"; | 552 order_by_method = "ASC"; |
| 553 |
| 554 if select: |
| 555 if order_by and order_by in select: |
| 556 order_by_filtered = True |
| 557 query_parts.append("(%s)" % select) |
| 558 |
| 496 if isinstance(filters, str) or isinstance(filters, unicode): | 559 if isinstance(filters, str) or isinstance(filters, unicode): |
| 497 query = "WHERE `__type__` = '%s' AND %s" % (cls.__name__, filters) | 560 query = "WHERE %s AND `__type__` = '%s'" % (filters, cls.__name__) |
| 498 if order_by != None: | 561 if order_by in ["__id__", "itemName()"]: |
| 562 query += " ORDER BY itemName() %s" % order_by_method |
| 563 elif order_by != None: |
| 499 query += " ORDER BY `%s` %s" % (order_by, order_by_method) | 564 query += " ORDER BY `%s` %s" % (order_by, order_by_method) |
| 500 return query | 565 return query |
| 501 | 566 |
| 502 for filter in filters: | 567 for filter in filters: |
| 503 filter_parts = [] | 568 filter_parts = [] |
| 504 filter_props = filter[0] | 569 filter_props = filter[0] |
| 505 if type(filter_props) != list: | 570 if type(filter_props) != list: |
| 506 filter_props = [filter_props] | 571 filter_props = [filter_props] |
| 507 for filter_prop in filter_props: | 572 for filter_prop in filter_props: |
| 508 (name, op) = filter_prop.strip().split(" ", 1) | 573 (name, op) = filter_prop.strip().split(" ", 1) |
| (...skipping 21 matching lines...) Expand all Loading... |
| 530 query_parts.append("(%s)" % (" or ".join(filter_parts))) | 595 query_parts.append("(%s)" % (" or ".join(filter_parts))) |
| 531 | 596 |
| 532 | 597 |
| 533 type_query = "(`__type__` = '%s'" % cls.__name__ | 598 type_query = "(`__type__` = '%s'" % cls.__name__ |
| 534 for subclass in self._get_all_decendents(cls).keys(): | 599 for subclass in self._get_all_decendents(cls).keys(): |
| 535 type_query += " or `__type__` = '%s'" % subclass | 600 type_query += " or `__type__` = '%s'" % subclass |
| 536 type_query +=")" | 601 type_query +=")" |
| 537 query_parts.append(type_query) | 602 query_parts.append(type_query) |
| 538 | 603 |
| 539 order_by_query = "" | 604 order_by_query = "" |
| 605 |
| 540 if order_by: | 606 if order_by: |
| 541 if not order_by_filtered: | 607 if not order_by_filtered: |
| 542 query_parts.append("`%s` LIKE '%%'" % order_by) | 608 query_parts.append("`%s` LIKE '%%'" % order_by) |
| 543 order_by_query = " ORDER BY `%s` %s" % (order_by, order_by_method) | 609 if order_by in ["__id__", "itemName()"]: |
| 544 | 610 order_by_query = " ORDER BY itemName() %s" % order_by_method |
| 545 if select: | 611 else: |
| 546 query_parts.append("(%s)" % select) | 612 order_by_query = " ORDER BY `%s` %s" % (order_by, order_by_metho
d) |
| 547 | 613 |
| 548 if len(query_parts) > 0: | 614 if len(query_parts) > 0: |
| 549 return "WHERE %s %s" % (" AND ".join(query_parts), order_by_query) | 615 return "WHERE %s %s" % (" AND ".join(query_parts), order_by_query) |
| 550 else: | 616 else: |
| 551 return "" | 617 return "" |
| 552 | 618 |
| 553 | 619 |
| 554 def _get_all_decendents(self, cls): | 620 def _get_all_decendents(self, cls): |
| 555 """Get all decendents for a given class""" | 621 """Get all decendents for a given class""" |
| 556 decendents = {} | 622 decendents = {} |
| 557 for sc in cls.__sub_classes__: | 623 for sc in cls.__sub_classes__: |
| 558 decendents[sc.__name__] = sc | 624 decendents[sc.__name__] = sc |
| 559 decendents.update(self._get_all_decendents(sc)) | 625 decendents.update(self._get_all_decendents(sc)) |
| 560 return decendents | 626 return decendents |
| 561 | 627 |
| 562 def query_gql(self, query_string, *args, **kwds): | 628 def query_gql(self, query_string, *args, **kwds): |
| 563 raise NotImplementedError, "GQL queries not supported in SimpleDB" | 629 raise NotImplementedError, "GQL queries not supported in SimpleDB" |
| 564 | 630 |
| 565 def save_object(self, obj): | 631 def save_object(self, obj, expected_value=None): |
| 566 if not obj.id: | 632 if not obj.id: |
| 567 obj.id = str(uuid.uuid4()) | 633 obj.id = str(uuid.uuid4()) |
| 568 | 634 |
| 569 attrs = {'__type__' : obj.__class__.__name__, | 635 attrs = {'__type__' : obj.__class__.__name__, |
| 570 '__module__' : obj.__class__.__module__, | 636 '__module__' : obj.__class__.__module__, |
| 571 '__lineage__' : obj.get_lineage()} | 637 '__lineage__' : obj.get_lineage()} |
| 572 del_attrs = [] | 638 del_attrs = [] |
| 573 for property in obj.properties(hidden=False): | 639 for property in obj.properties(hidden=False): |
| 574 value = property.get_value_for_datastore(obj) | 640 value = property.get_value_for_datastore(obj) |
| 575 if value is not None: | 641 if value is not None: |
| 576 value = self.encode_value(property, value) | 642 value = self.encode_value(property, value) |
| 577 if value == []: | 643 if value == []: |
| 578 value = None | 644 value = None |
| 579 if value == None: | 645 if value == None: |
| 580 del_attrs.append(property.name) | 646 del_attrs.append(property.name) |
| 581 continue | 647 continue |
| 582 attrs[property.name] = value | 648 attrs[property.name] = value |
| 583 if property.unique: | 649 if property.unique: |
| 584 try: | 650 try: |
| 585 args = {property.name: value} | 651 args = {property.name: value} |
| 586 obj2 = obj.find(**args).next() | 652 obj2 = obj.find(**args).next() |
| 587 if obj2.id != obj.id: | 653 if obj2.id != obj.id: |
| 588 raise SDBPersistenceError("Error: %s must be unique!" %
property.name) | 654 raise SDBPersistenceError("Error: %s must be unique!" %
property.name) |
| 589 except(StopIteration): | 655 except(StopIteration): |
| 590 pass | 656 pass |
| 591 self.domain.put_attributes(obj.id, attrs, replace=True) | 657 # Convert the Expected value to SDB format |
| 658 if expected_value: |
| 659 prop = obj.find_property(expected_value[0]) |
| 660 v = expected_value[1] |
| 661 if v is not None and not type(v) == bool: |
| 662 v = self.encode_value(prop, v) |
| 663 expected_value[1] = v |
| 664 self.domain.put_attributes(obj.id, attrs, replace=True, expected_value=e
xpected_value) |
| 592 if len(del_attrs) > 0: | 665 if len(del_attrs) > 0: |
| 593 self.domain.delete_attributes(obj.id, del_attrs) | 666 self.domain.delete_attributes(obj.id, del_attrs) |
| 594 return obj | 667 return obj |
| 595 | 668 |
| 596 def delete_object(self, obj): | 669 def delete_object(self, obj): |
| 597 self.domain.delete_attributes(obj.id) | 670 self.domain.delete_attributes(obj.id) |
| 598 | 671 |
| 599 def set_property(self, prop, obj, name, value): | 672 def set_property(self, prop, obj, name, value): |
| 673 setattr(obj, name, value) |
| 600 value = prop.get_value_for_datastore(obj) | 674 value = prop.get_value_for_datastore(obj) |
| 601 value = self.encode_value(prop, value) | 675 value = self.encode_value(prop, value) |
| 602 if prop.unique: | 676 if prop.unique: |
| 603 try: | 677 try: |
| 604 args = {prop.name: value} | 678 args = {prop.name: value} |
| 605 obj2 = obj.find(**args).next() | 679 obj2 = obj.find(**args).next() |
| 606 if obj2.id != obj.id: | 680 if obj2.id != obj.id: |
| 607 raise SDBPersistenceError("Error: %s must be unique!" % prop
.name) | 681 raise SDBPersistenceError("Error: %s must be unique!" % prop
.name) |
| 608 except(StopIteration): | 682 except(StopIteration): |
| 609 pass | 683 pass |
| (...skipping 19 matching lines...) Expand all Loading... |
| 629 def get_key_value(self, obj, name): | 703 def get_key_value(self, obj, name): |
| 630 a = self.domain.get_attributes(obj.id, name,consistent_read=self.consist
ent) | 704 a = self.domain.get_attributes(obj.id, name,consistent_read=self.consist
ent) |
| 631 if a.has_key(name): | 705 if a.has_key(name): |
| 632 return a[name] | 706 return a[name] |
| 633 else: | 707 else: |
| 634 return None | 708 return None |
| 635 | 709 |
| 636 def get_raw_item(self, obj): | 710 def get_raw_item(self, obj): |
| 637 return self.domain.get_item(obj.id) | 711 return self.domain.get_item(obj.id) |
| 638 | 712 |
| OLD | NEW |