OLD | NEW |
(Empty) | |
| 1 # Copyright (c) 2006,2007,2008 Mitch Garnaat http://garnaat.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.sdb.db.manager import get_manager |
| 23 from boto.sdb.db.property import Property |
| 24 from boto.sdb.db.key import Key |
| 25 from boto.sdb.db.query import Query |
| 26 import boto |
| 27 |
| 28 class ModelMeta(type): |
| 29 "Metaclass for all Models" |
| 30 |
| 31 def __init__(cls, name, bases, dict): |
| 32 super(ModelMeta, cls).__init__(name, bases, dict) |
| 33 # Make sure this is a subclass of Model - mainly copied from django Mode
lBase (thanks!) |
| 34 cls.__sub_classes__ = [] |
| 35 try: |
| 36 if filter(lambda b: issubclass(b, Model), bases): |
| 37 for base in bases: |
| 38 base.__sub_classes__.append(cls) |
| 39 cls._manager = get_manager(cls) |
| 40 # look for all of the Properties and set their names |
| 41 for key in dict.keys(): |
| 42 if isinstance(dict[key], Property): |
| 43 property = dict[key] |
| 44 property.__property_config__(cls, key) |
| 45 prop_names = [] |
| 46 props = cls.properties() |
| 47 for prop in props: |
| 48 if not prop.__class__.__name__.startswith('_'): |
| 49 prop_names.append(prop.name) |
| 50 setattr(cls, '_prop_names', prop_names) |
| 51 except NameError: |
| 52 # 'Model' isn't defined yet, meaning we're looking at our own |
| 53 # Model class, defined below. |
| 54 pass |
| 55 |
| 56 class Model(object): |
| 57 __metaclass__ = ModelMeta |
| 58 __consistent__ = False # Consistent is set off by default |
| 59 id = None |
| 60 |
| 61 @classmethod |
| 62 def get_lineage(cls): |
| 63 l = [c.__name__ for c in cls.mro()] |
| 64 l.reverse() |
| 65 return '.'.join(l) |
| 66 |
| 67 @classmethod |
| 68 def kind(cls): |
| 69 return cls.__name__ |
| 70 |
| 71 @classmethod |
| 72 def _get_by_id(cls, id, manager=None): |
| 73 if not manager: |
| 74 manager = cls._manager |
| 75 return manager.get_object(cls, id) |
| 76 |
| 77 @classmethod |
| 78 def get_by_id(cls, ids=None, parent=None): |
| 79 if isinstance(ids, list): |
| 80 objs = [cls._get_by_id(id) for id in ids] |
| 81 return objs |
| 82 else: |
| 83 return cls._get_by_id(ids) |
| 84 |
| 85 get_by_ids = get_by_id |
| 86 |
| 87 @classmethod |
| 88 def get_by_key_name(cls, key_names, parent=None): |
| 89 raise NotImplementedError("Key Names are not currently supported") |
| 90 |
| 91 @classmethod |
| 92 def find(cls, limit=None, next_token=None, **params): |
| 93 q = Query(cls, limit=limit, next_token=next_token) |
| 94 for key, value in params.items(): |
| 95 q.filter('%s =' % key, value) |
| 96 return q |
| 97 |
| 98 @classmethod |
| 99 def all(cls, limit=None, next_token=None): |
| 100 return cls.find(limit=limit, next_token=next_token) |
| 101 |
| 102 @classmethod |
| 103 def get_or_insert(key_name, **kw): |
| 104 raise NotImplementedError("get_or_insert not currently supported") |
| 105 |
| 106 @classmethod |
| 107 def properties(cls, hidden=True): |
| 108 properties = [] |
| 109 while cls: |
| 110 for key in cls.__dict__.keys(): |
| 111 prop = cls.__dict__[key] |
| 112 if isinstance(prop, Property): |
| 113 if hidden or not prop.__class__.__name__.startswith('_'): |
| 114 properties.append(prop) |
| 115 if len(cls.__bases__) > 0: |
| 116 cls = cls.__bases__[0] |
| 117 else: |
| 118 cls = None |
| 119 return properties |
| 120 |
| 121 @classmethod |
| 122 def find_property(cls, prop_name): |
| 123 property = None |
| 124 while cls: |
| 125 for key in cls.__dict__.keys(): |
| 126 prop = cls.__dict__[key] |
| 127 if isinstance(prop, Property): |
| 128 if not prop.__class__.__name__.startswith('_') and prop_name
== prop.name: |
| 129 property = prop |
| 130 if len(cls.__bases__) > 0: |
| 131 cls = cls.__bases__[0] |
| 132 else: |
| 133 cls = None |
| 134 return property |
| 135 |
| 136 @classmethod |
| 137 def get_xmlmanager(cls): |
| 138 if not hasattr(cls, '_xmlmanager'): |
| 139 from boto.sdb.db.manager.xmlmanager import XMLManager |
| 140 cls._xmlmanager = XMLManager(cls, None, None, None, |
| 141 None, None, None, None, False) |
| 142 return cls._xmlmanager |
| 143 |
| 144 @classmethod |
| 145 def from_xml(cls, fp): |
| 146 xmlmanager = cls.get_xmlmanager() |
| 147 return xmlmanager.unmarshal_object(fp) |
| 148 |
| 149 def __init__(self, id=None, **kw): |
| 150 self._loaded = False |
| 151 # first try to initialize all properties to their default values |
| 152 for prop in self.properties(hidden=False): |
| 153 try: |
| 154 setattr(self, prop.name, prop.default_value()) |
| 155 except ValueError: |
| 156 pass |
| 157 if 'manager' in kw: |
| 158 self._manager = kw['manager'] |
| 159 self.id = id |
| 160 for key in kw: |
| 161 if key != 'manager': |
| 162 # We don't want any errors populating up when loading an object, |
| 163 # so if it fails we just revert to it's default value |
| 164 try: |
| 165 setattr(self, key, kw[key]) |
| 166 except Exception, e: |
| 167 boto.log.exception(e) |
| 168 |
| 169 def __repr__(self): |
| 170 return '%s<%s>' % (self.__class__.__name__, self.id) |
| 171 |
| 172 def __str__(self): |
| 173 return str(self.id) |
| 174 |
| 175 def __eq__(self, other): |
| 176 return other and isinstance(other, Model) and self.id == other.id |
| 177 |
| 178 def _get_raw_item(self): |
| 179 return self._manager.get_raw_item(self) |
| 180 |
| 181 def load(self): |
| 182 if self.id and not self._loaded: |
| 183 self._manager.load_object(self) |
| 184 |
| 185 def reload(self): |
| 186 if self.id: |
| 187 self._loaded = False |
| 188 self._manager.load_object(self) |
| 189 |
| 190 def put(self, expected_value=None): |
| 191 """ |
| 192 Save this object as it is, with an optional expected value |
| 193 |
| 194 :param expected_value: Optional tuple of Attribute, and Value that |
| 195 must be the same in order to save this object. If this |
| 196 condition is not met, an SDBResponseError will be raised with a |
| 197 Confict status code. |
| 198 :type expected_value: tuple or list |
| 199 :return: This object |
| 200 :rtype: :class:`boto.sdb.db.model.Model` |
| 201 """ |
| 202 self._manager.save_object(self, expected_value) |
| 203 return self |
| 204 |
| 205 save = put |
| 206 |
| 207 def put_attributes(self, attrs): |
| 208 """ |
| 209 Save just these few attributes, not the whole object |
| 210 |
| 211 :param attrs: Attributes to save, key->value dict |
| 212 :type attrs: dict |
| 213 :return: self |
| 214 :rtype: :class:`boto.sdb.db.model.Model` |
| 215 """ |
| 216 assert(isinstance(attrs, dict)), "Argument must be a dict of key->values
to save" |
| 217 for prop_name in attrs: |
| 218 value = attrs[prop_name] |
| 219 prop = self.find_property(prop_name) |
| 220 assert(prop), "Property not found: %s" % prop_name |
| 221 self._manager.set_property(prop, self, prop_name, value) |
| 222 self.reload() |
| 223 return self |
| 224 |
| 225 def delete_attributes(self, attrs): |
| 226 """ |
| 227 Delete just these attributes, not the whole object. |
| 228 |
| 229 :param attrs: Attributes to save, as a list of string names |
| 230 :type attrs: list |
| 231 :return: self |
| 232 :rtype: :class:`boto.sdb.db.model.Model` |
| 233 """ |
| 234 assert(isinstance(attrs, list)), "Argument must be a list of names of ke
ys to delete." |
| 235 self._manager.domain.delete_attributes(self.id, attrs) |
| 236 self.reload() |
| 237 return self |
| 238 |
| 239 save_attributes = put_attributes |
| 240 |
| 241 def delete(self): |
| 242 self._manager.delete_object(self) |
| 243 |
| 244 def key(self): |
| 245 return Key(obj=self) |
| 246 |
| 247 def set_manager(self, manager): |
| 248 self._manager = manager |
| 249 |
| 250 def to_dict(self): |
| 251 props = {} |
| 252 for prop in self.properties(hidden=False): |
| 253 props[prop.name] = getattr(self, prop.name) |
| 254 obj = {'properties' : props, |
| 255 'id' : self.id} |
| 256 return {self.__class__.__name__ : obj} |
| 257 |
| 258 def to_xml(self, doc=None): |
| 259 xmlmanager = self.get_xmlmanager() |
| 260 doc = xmlmanager.marshal_object(self, doc) |
| 261 return doc |
| 262 |
| 263 @classmethod |
| 264 def find_subclass(cls, name): |
| 265 """Find a subclass with a given name""" |
| 266 if name == cls.__name__: |
| 267 return cls |
| 268 for sc in cls.__sub_classes__: |
| 269 r = sc.find_subclass(name) |
| 270 if r != None: |
| 271 return r |
| 272 |
| 273 class Expando(Model): |
| 274 |
| 275 def __setattr__(self, name, value): |
| 276 if name in self._prop_names: |
| 277 object.__setattr__(self, name, value) |
| 278 elif name.startswith('_'): |
| 279 object.__setattr__(self, name, value) |
| 280 elif name == 'id': |
| 281 object.__setattr__(self, name, value) |
| 282 else: |
| 283 self._manager.set_key_value(self, name, value) |
| 284 object.__setattr__(self, name, value) |
| 285 |
| 286 def __getattr__(self, name): |
| 287 if not name.startswith('_'): |
| 288 value = self._manager.get_key_value(self, name) |
| 289 if value: |
| 290 object.__setattr__(self, name, value) |
| 291 return value |
| 292 raise AttributeError |
| 293 |
| 294 |
OLD | NEW |