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 |