PageRenderTime 56ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/boto-2.5.2/boto/sdb/db/property.py

#
Python | 703 lines | 664 code | 17 blank | 22 comment | 6 complexity | 52016ea2491f0956f43ee647b001fcc4 MD5 | raw file
  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. import datetime
  22. from key import Key
  23. from boto.utils import Password
  24. from boto.sdb.db.query import Query
  25. import re
  26. import boto
  27. import boto.s3.key
  28. from boto.sdb.db.blob import Blob
  29. class Property(object):
  30. data_type = str
  31. type_name = ''
  32. name = ''
  33. verbose_name = ''
  34. def __init__(self, verbose_name=None, name=None, default=None,
  35. required=False, validator=None, choices=None, unique=False):
  36. self.verbose_name = verbose_name
  37. self.name = name
  38. self.default = default
  39. self.required = required
  40. self.validator = validator
  41. self.choices = choices
  42. if self.name:
  43. self.slot_name = '_' + self.name
  44. else:
  45. self.slot_name = '_'
  46. self.unique = unique
  47. def __get__(self, obj, objtype):
  48. if obj:
  49. obj.load()
  50. return getattr(obj, self.slot_name)
  51. else:
  52. return None
  53. def __set__(self, obj, value):
  54. self.validate(value)
  55. # Fire off any on_set functions
  56. try:
  57. if obj._loaded and hasattr(obj, "on_set_%s" % self.name):
  58. fnc = getattr(obj, "on_set_%s" % self.name)
  59. value = fnc(value)
  60. except Exception:
  61. boto.log.exception("Exception running on_set_%s" % self.name)
  62. setattr(obj, self.slot_name, value)
  63. def __property_config__(self, model_class, property_name):
  64. self.model_class = model_class
  65. self.name = property_name
  66. self.slot_name = '_' + self.name
  67. def default_validator(self, value):
  68. if isinstance(value, basestring) or value == self.default_value():
  69. return
  70. if not isinstance(value, self.data_type):
  71. raise TypeError('Validation Error, %s.%s expecting %s, got %s' % (self.model_class.__name__, self.name, self.data_type, type(value)))
  72. def default_value(self):
  73. return self.default
  74. def validate(self, value):
  75. if self.required and value == None:
  76. raise ValueError('%s is a required property' % self.name)
  77. if self.choices and value and not value in self.choices:
  78. raise ValueError('%s not a valid choice for %s.%s' % (value, self.model_class.__name__, self.name))
  79. if self.validator:
  80. self.validator(value)
  81. else:
  82. self.default_validator(value)
  83. return value
  84. def empty(self, value):
  85. return not value
  86. def get_value_for_datastore(self, model_instance):
  87. return getattr(model_instance, self.name)
  88. def make_value_from_datastore(self, value):
  89. return value
  90. def get_choices(self):
  91. if callable(self.choices):
  92. return self.choices()
  93. return self.choices
  94. def validate_string(value):
  95. if value == None:
  96. return
  97. elif isinstance(value, str) or isinstance(value, unicode):
  98. if len(value) > 1024:
  99. raise ValueError('Length of value greater than maxlength')
  100. else:
  101. raise TypeError('Expecting String, got %s' % type(value))
  102. class StringProperty(Property):
  103. type_name = 'String'
  104. def __init__(self, verbose_name=None, name=None, default='',
  105. required=False, validator=validate_string,
  106. choices=None, unique=False):
  107. Property.__init__(self, verbose_name, name, default, required,
  108. validator, choices, unique)
  109. class TextProperty(Property):
  110. type_name = 'Text'
  111. def __init__(self, verbose_name=None, name=None, default='',
  112. required=False, validator=None, choices=None,
  113. unique=False, max_length=None):
  114. Property.__init__(self, verbose_name, name, default, required,
  115. validator, choices, unique)
  116. self.max_length = max_length
  117. def validate(self, value):
  118. value = super(TextProperty, self).validate(value)
  119. if not isinstance(value, str) and not isinstance(value, unicode):
  120. raise TypeError('Expecting Text, got %s' % type(value))
  121. if self.max_length and len(value) > self.max_length:
  122. raise ValueError('Length of value greater than maxlength %s' % self.max_length)
  123. class PasswordProperty(StringProperty):
  124. """
  125. Hashed property whose original value can not be
  126. retrieved, but still can be compared.
  127. Works by storing a hash of the original value instead
  128. of the original value. Once that's done all that
  129. can be retrieved is the hash.
  130. The comparison
  131. obj.password == 'foo'
  132. generates a hash of 'foo' and compares it to the
  133. stored hash.
  134. Underlying data type for hashing, storing, and comparing
  135. is boto.utils.Password. The default hash function is
  136. defined there ( currently sha512 in most cases, md5
  137. where sha512 is not available )
  138. It's unlikely you'll ever need to use a different hash
  139. function, but if you do, you can control the behavior
  140. in one of two ways:
  141. 1) Specifying hashfunc in PasswordProperty constructor
  142. import hashlib
  143. class MyModel(model):
  144. password = PasswordProperty(hashfunc=hashlib.sha224)
  145. 2) Subclassing Password and PasswordProperty
  146. class SHA224Password(Password):
  147. hashfunc=hashlib.sha224
  148. class SHA224PasswordProperty(PasswordProperty):
  149. data_type=MyPassword
  150. type_name="MyPassword"
  151. class MyModel(Model):
  152. password = SHA224PasswordProperty()
  153. """
  154. data_type = Password
  155. type_name = 'Password'
  156. def __init__(self, verbose_name=None, name=None, default='', required=False,
  157. validator=None, choices=None, unique=False, hashfunc=None):
  158. """
  159. The hashfunc parameter overrides the default hashfunc in boto.utils.Password.
  160. The remaining parameters are passed through to StringProperty.__init__"""
  161. StringProperty.__init__(self, verbose_name, name, default, required,
  162. validator, choices, unique)
  163. self.hashfunc = hashfunc
  164. def make_value_from_datastore(self, value):
  165. p = self.data_type(value, hashfunc=self.hashfunc)
  166. return p
  167. def get_value_for_datastore(self, model_instance):
  168. value = StringProperty.get_value_for_datastore(self, model_instance)
  169. if value and len(value):
  170. return str(value)
  171. else:
  172. return None
  173. def __set__(self, obj, value):
  174. if not isinstance(value, self.data_type):
  175. p = self.data_type(hashfunc=self.hashfunc)
  176. p.set(value)
  177. value = p
  178. Property.__set__(self, obj, value)
  179. def __get__(self, obj, objtype):
  180. return self.data_type(StringProperty.__get__(self, obj, objtype), hashfunc=self.hashfunc)
  181. def validate(self, value):
  182. value = Property.validate(self, value)
  183. if isinstance(value, self.data_type):
  184. if len(value) > 1024:
  185. raise ValueError('Length of value greater than maxlength')
  186. else:
  187. raise TypeError('Expecting %s, got %s' % (type(self.data_type), type(value)))
  188. class BlobProperty(Property):
  189. data_type = Blob
  190. type_name = "blob"
  191. def __set__(self, obj, value):
  192. if value != self.default_value():
  193. if not isinstance(value, Blob):
  194. oldb = self.__get__(obj, type(obj))
  195. id = None
  196. if oldb:
  197. id = oldb.id
  198. b = Blob(value=value, id=id)
  199. value = b
  200. Property.__set__(self, obj, value)
  201. class S3KeyProperty(Property):
  202. data_type = boto.s3.key.Key
  203. type_name = 'S3Key'
  204. validate_regex = "^s3:\/\/([^\/]*)\/(.*)$"
  205. def __init__(self, verbose_name=None, name=None, default=None,
  206. required=False, validator=None, choices=None, unique=False):
  207. Property.__init__(self, verbose_name, name, default, required,
  208. validator, choices, unique)
  209. def validate(self, value):
  210. value = super(S3KeyProperty, self).validate(value)
  211. if value == self.default_value() or value == str(self.default_value()):
  212. return self.default_value()
  213. if isinstance(value, self.data_type):
  214. return
  215. match = re.match(self.validate_regex, value)
  216. if match:
  217. return
  218. raise TypeError('Validation Error, expecting %s, got %s' % (self.data_type, type(value)))
  219. def __get__(self, obj, objtype):
  220. value = Property.__get__(self, obj, objtype)
  221. if value:
  222. if isinstance(value, self.data_type):
  223. return value
  224. match = re.match(self.validate_regex, value)
  225. if match:
  226. s3 = obj._manager.get_s3_connection()
  227. bucket = s3.get_bucket(match.group(1), validate=False)
  228. k = bucket.get_key(match.group(2))
  229. if not k:
  230. k = bucket.new_key(match.group(2))
  231. k.set_contents_from_string("")
  232. return k
  233. else:
  234. return value
  235. def get_value_for_datastore(self, model_instance):
  236. value = Property.get_value_for_datastore(self, model_instance)
  237. if value:
  238. return "s3://%s/%s" % (value.bucket.name, value.name)
  239. else:
  240. return None
  241. class IntegerProperty(Property):
  242. data_type = int
  243. type_name = 'Integer'
  244. def __init__(self, verbose_name=None, name=None, default=0, required=False,
  245. validator=None, choices=None, unique=False, max=2147483647, min=-2147483648):
  246. Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
  247. self.max = max
  248. self.min = min
  249. def validate(self, value):
  250. value = int(value)
  251. value = Property.validate(self, value)
  252. if value > self.max:
  253. raise ValueError('Maximum value is %d' % self.max)
  254. if value < self.min:
  255. raise ValueError('Minimum value is %d' % self.min)
  256. return value
  257. def empty(self, value):
  258. return value is None
  259. def __set__(self, obj, value):
  260. if value == "" or value == None:
  261. value = 0
  262. return Property.__set__(self, obj, value)
  263. class LongProperty(Property):
  264. data_type = long
  265. type_name = 'Long'
  266. def __init__(self, verbose_name=None, name=None, default=0, required=False,
  267. validator=None, choices=None, unique=False):
  268. Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
  269. def validate(self, value):
  270. value = long(value)
  271. value = Property.validate(self, value)
  272. min = -9223372036854775808
  273. max = 9223372036854775807
  274. if value > max:
  275. raise ValueError('Maximum value is %d' % max)
  276. if value < min:
  277. raise ValueError('Minimum value is %d' % min)
  278. return value
  279. def empty(self, value):
  280. return value is None
  281. class BooleanProperty(Property):
  282. data_type = bool
  283. type_name = 'Boolean'
  284. def __init__(self, verbose_name=None, name=None, default=False, required=False,
  285. validator=None, choices=None, unique=False):
  286. Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
  287. def empty(self, value):
  288. return value is None
  289. class FloatProperty(Property):
  290. data_type = float
  291. type_name = 'Float'
  292. def __init__(self, verbose_name=None, name=None, default=0.0, required=False,
  293. validator=None, choices=None, unique=False):
  294. Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
  295. def validate(self, value):
  296. value = float(value)
  297. value = Property.validate(self, value)
  298. return value
  299. def empty(self, value):
  300. return value is None
  301. class DateTimeProperty(Property):
  302. """This class handles both the datetime.datetime object
  303. And the datetime.date objects. It can return either one,
  304. depending on the value stored in the database"""
  305. data_type = datetime.datetime
  306. type_name = 'DateTime'
  307. def __init__(self, verbose_name=None, auto_now=False, auto_now_add=False, name=None,
  308. default=None, required=False, validator=None, choices=None, unique=False):
  309. Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
  310. self.auto_now = auto_now
  311. self.auto_now_add = auto_now_add
  312. def default_value(self):
  313. if self.auto_now or self.auto_now_add:
  314. return self.now()
  315. return Property.default_value(self)
  316. def validate(self, value):
  317. if value == None:
  318. return
  319. if isinstance(value, datetime.date):
  320. return value
  321. return super(DateTimeProperty, self).validate(value)
  322. def get_value_for_datastore(self, model_instance):
  323. if self.auto_now:
  324. setattr(model_instance, self.name, self.now())
  325. return Property.get_value_for_datastore(self, model_instance)
  326. def now(self):
  327. return datetime.datetime.utcnow()
  328. class DateProperty(Property):
  329. data_type = datetime.date
  330. type_name = 'Date'
  331. def __init__(self, verbose_name=None, auto_now=False, auto_now_add=False, name=None,
  332. default=None, required=False, validator=None, choices=None, unique=False):
  333. Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
  334. self.auto_now = auto_now
  335. self.auto_now_add = auto_now_add
  336. def default_value(self):
  337. if self.auto_now or self.auto_now_add:
  338. return self.now()
  339. return Property.default_value(self)
  340. def validate(self, value):
  341. value = super(DateProperty, self).validate(value)
  342. if value == None:
  343. return
  344. if not isinstance(value, self.data_type):
  345. raise TypeError('Validation Error, expecting %s, got %s' % (self.data_type, type(value)))
  346. def get_value_for_datastore(self, model_instance):
  347. if self.auto_now:
  348. setattr(model_instance, self.name, self.now())
  349. val = Property.get_value_for_datastore(self, model_instance)
  350. if isinstance(val, datetime.datetime):
  351. val = val.date()
  352. return val
  353. def now(self):
  354. return datetime.date.today()
  355. class TimeProperty(Property):
  356. data_type = datetime.time
  357. type_name = 'Time'
  358. def __init__(self, verbose_name=None, name=None,
  359. default=None, required=False, validator=None, choices=None, unique=False):
  360. Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
  361. def validate(self, value):
  362. value = super(TimeProperty, self).validate(value)
  363. if value is None:
  364. return
  365. if not isinstance(value, self.data_type):
  366. raise TypeError('Validation Error, expecting %s, got %s' % (self.data_type, type(value)))
  367. class ReferenceProperty(Property):
  368. data_type = Key
  369. type_name = 'Reference'
  370. def __init__(self, reference_class=None, collection_name=None,
  371. verbose_name=None, name=None, default=None, required=False, validator=None, choices=None, unique=False):
  372. Property.__init__(self, verbose_name, name, default, required, validator, choices, unique)
  373. self.reference_class = reference_class
  374. self.collection_name = collection_name
  375. def __get__(self, obj, objtype):
  376. if obj:
  377. value = getattr(obj, self.slot_name)
  378. if value == self.default_value():
  379. return value
  380. # If the value is still the UUID for the referenced object, we need to create
  381. # the object now that is the attribute has actually been accessed. This lazy
  382. # instantiation saves unnecessary roundtrips to SimpleDB
  383. if isinstance(value, str) or isinstance(value, unicode):
  384. value = self.reference_class(value)
  385. setattr(obj, self.name, value)
  386. return value
  387. def __set__(self, obj, value):
  388. """Don't allow this object to be associated to itself
  389. This causes bad things to happen"""
  390. if value != None and (obj.id == value or (hasattr(value, "id") and obj.id == value.id)):
  391. raise ValueError("Can not associate an object with itself!")
  392. return super(ReferenceProperty, self).__set__(obj, value)
  393. def __property_config__(self, model_class, property_name):
  394. Property.__property_config__(self, model_class, property_name)
  395. if self.collection_name is None:
  396. self.collection_name = '%s_%s_set' % (model_class.__name__.lower(), self.name)
  397. if hasattr(self.reference_class, self.collection_name):
  398. raise ValueError('duplicate property: %s' % self.collection_name)
  399. setattr(self.reference_class, self.collection_name,
  400. _ReverseReferenceProperty(model_class, property_name, self.collection_name))
  401. def check_uuid(self, value):
  402. # This does a bit of hand waving to "type check" the string
  403. t = value.split('-')
  404. if len(t) != 5:
  405. raise ValueError
  406. def check_instance(self, value):
  407. try:
  408. obj_lineage = value.get_lineage()
  409. cls_lineage = self.reference_class.get_lineage()
  410. if obj_lineage.startswith(cls_lineage):
  411. return
  412. raise TypeError('%s not instance of %s' % (obj_lineage, cls_lineage))
  413. except:
  414. raise ValueError('%s is not a Model' % value)
  415. def validate(self, value):
  416. if self.validator:
  417. self.validator(value)
  418. if self.required and value == None:
  419. raise ValueError('%s is a required property' % self.name)
  420. if value == self.default_value():
  421. return
  422. if not isinstance(value, str) and not isinstance(value, unicode):
  423. self.check_instance(value)
  424. class _ReverseReferenceProperty(Property):
  425. data_type = Query
  426. type_name = 'query'
  427. def __init__(self, model, prop, name):
  428. self.__model = model
  429. self.__property = prop
  430. self.collection_name = prop
  431. self.name = name
  432. self.item_type = model
  433. def __get__(self, model_instance, model_class):
  434. """Fetches collection of model instances of this collection property."""
  435. if model_instance is not None:
  436. query = Query(self.__model)
  437. if isinstance(self.__property, list):
  438. props = []
  439. for prop in self.__property:
  440. props.append("%s =" % prop)
  441. return query.filter(props, model_instance)
  442. else:
  443. return query.filter(self.__property + ' =', model_instance)
  444. else:
  445. return self
  446. def __set__(self, model_instance, value):
  447. """Not possible to set a new collection."""
  448. raise ValueError('Virtual property is read-only')
  449. class CalculatedProperty(Property):
  450. def __init__(self, verbose_name=None, name=None, default=None,
  451. required=False, validator=None, choices=None,
  452. calculated_type=int, unique=False, use_method=False):
  453. Property.__init__(self, verbose_name, name, default, required,
  454. validator, choices, unique)
  455. self.calculated_type = calculated_type
  456. self.use_method = use_method
  457. def __get__(self, obj, objtype):
  458. value = self.default_value()
  459. if obj:
  460. try:
  461. value = getattr(obj, self.slot_name)
  462. if self.use_method:
  463. value = value()
  464. except AttributeError:
  465. pass
  466. return value
  467. def __set__(self, obj, value):
  468. """Not possible to set a new AutoID."""
  469. pass
  470. def _set_direct(self, obj, value):
  471. if not self.use_method:
  472. setattr(obj, self.slot_name, value)
  473. def get_value_for_datastore(self, model_instance):
  474. if self.calculated_type in [str, int, bool]:
  475. value = self.__get__(model_instance, model_instance.__class__)
  476. return value
  477. else:
  478. return None
  479. class ListProperty(Property):
  480. data_type = list
  481. type_name = 'List'
  482. def __init__(self, item_type, verbose_name=None, name=None, default=None, **kwds):
  483. if default is None:
  484. default = []
  485. self.item_type = item_type
  486. Property.__init__(self, verbose_name, name, default=default, required=True, **kwds)
  487. def validate(self, value):
  488. if self.validator:
  489. self.validator(value)
  490. if value is not None:
  491. if not isinstance(value, list):
  492. value = [value]
  493. if self.item_type in (int, long):
  494. item_type = (int, long)
  495. elif self.item_type in (str, unicode):
  496. item_type = (str, unicode)
  497. else:
  498. item_type = self.item_type
  499. for item in value:
  500. if not isinstance(item, item_type):
  501. if item_type == (int, long):
  502. raise ValueError('Items in the %s list must all be integers.' % self.name)
  503. else:
  504. raise ValueError('Items in the %s list must all be %s instances' %
  505. (self.name, self.item_type.__name__))
  506. return value
  507. def empty(self, value):
  508. return value is None
  509. def default_value(self):
  510. return list(super(ListProperty, self).default_value())
  511. def __set__(self, obj, value):
  512. """Override the set method to allow them to set the property to an instance of the item_type instead of requiring a list to be passed in"""
  513. if self.item_type in (int, long):
  514. item_type = (int, long)
  515. elif self.item_type in (str, unicode):
  516. item_type = (str, unicode)
  517. else:
  518. item_type = self.item_type
  519. if isinstance(value, item_type):
  520. value = [value]
  521. elif value == None: # Override to allow them to set this to "None" to remove everything
  522. value = []
  523. return super(ListProperty, self).__set__(obj, value)
  524. class MapProperty(Property):
  525. data_type = dict
  526. type_name = 'Map'
  527. def __init__(self, item_type=str, verbose_name=None, name=None, default=None, **kwds):
  528. if default is None:
  529. default = {}
  530. self.item_type = item_type
  531. Property.__init__(self, verbose_name, name, default=default, required=True, **kwds)
  532. def validate(self, value):
  533. value = super(MapProperty, self).validate(value)
  534. if value is not None:
  535. if not isinstance(value, dict):
  536. raise ValueError('Value must of type dict')
  537. if self.item_type in (int, long):
  538. item_type = (int, long)
  539. elif self.item_type in (str, unicode):
  540. item_type = (str, unicode)
  541. else:
  542. item_type = self.item_type
  543. for key in value:
  544. if not isinstance(value[key], item_type):
  545. if item_type == (int, long):
  546. raise ValueError('Values in the %s Map must all be integers.' % self.name)
  547. else:
  548. raise ValueError('Values in the %s Map must all be %s instances' %
  549. (self.name, self.item_type.__name__))
  550. return value
  551. def empty(self, value):
  552. return value is None
  553. def default_value(self):
  554. return {}