PageRenderTime 43ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/boto-2.5.2/boto/sdb/domain.py

#
Python | 377 lines | 327 code | 12 blank | 38 comment | 5 complexity | 848558d51dcc078e823bd482cce725b1 MD5 | raw file
  1. # Copyright (c) 2006,2007 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. Represents an SDB Domain
  23. """
  24. from boto.sdb.queryresultset import SelectResultSet
  25. class Domain:
  26. def __init__(self, connection=None, name=None):
  27. self.connection = connection
  28. self.name = name
  29. self._metadata = None
  30. def __repr__(self):
  31. return 'Domain:%s' % self.name
  32. def __iter__(self):
  33. return iter(self.select("SELECT * FROM `%s`" % self.name))
  34. def startElement(self, name, attrs, connection):
  35. return None
  36. def endElement(self, name, value, connection):
  37. if name == 'DomainName':
  38. self.name = value
  39. else:
  40. setattr(self, name, value)
  41. def get_metadata(self):
  42. if not self._metadata:
  43. self._metadata = self.connection.domain_metadata(self)
  44. return self._metadata
  45. def put_attributes(self, item_name, attributes,
  46. replace=True, expected_value=None):
  47. """
  48. Store attributes for a given item.
  49. :type item_name: string
  50. :param item_name: The name of the item whose attributes are being stored.
  51. :type attribute_names: dict or dict-like object
  52. :param attribute_names: The name/value pairs to store as attributes
  53. :type expected_value: list
  54. :param expected_value: If supplied, this is a list or tuple consisting
  55. of a single attribute name and expected value. The list can be
  56. of the form:
  57. * ['name', 'value']
  58. In which case the call will first verify that the attribute
  59. "name" of this item has a value of "value". If it does, the delete
  60. will proceed, otherwise a ConditionalCheckFailed error will be
  61. returned. The list can also be of the form:
  62. * ['name', True|False]
  63. which will simply check for the existence (True) or non-existence
  64. (False) of the attribute.
  65. :type replace: bool
  66. :param replace: Whether the attribute values passed in will replace
  67. existing values or will be added as addition values.
  68. Defaults to True.
  69. :rtype: bool
  70. :return: True if successful
  71. """
  72. return self.connection.put_attributes(self, item_name, attributes,
  73. replace, expected_value)
  74. def batch_put_attributes(self, items, replace=True):
  75. """
  76. Store attributes for multiple items.
  77. :type items: dict or dict-like object
  78. :param items: A dictionary-like object. The keys of the dictionary are
  79. the item names and the values are themselves dictionaries
  80. of attribute names/values, exactly the same as the
  81. attribute_names parameter of the scalar put_attributes
  82. call.
  83. :type replace: bool
  84. :param replace: Whether the attribute values passed in will replace
  85. existing values or will be added as addition values.
  86. Defaults to True.
  87. :rtype: bool
  88. :return: True if successful
  89. """
  90. return self.connection.batch_put_attributes(self, items, replace)
  91. def get_attributes(self, item_name, attribute_name=None,
  92. consistent_read=False, item=None):
  93. """
  94. Retrieve attributes for a given item.
  95. :type item_name: string
  96. :param item_name: The name of the item whose attributes are being retrieved.
  97. :type attribute_names: string or list of strings
  98. :param attribute_names: An attribute name or list of attribute names. This
  99. parameter is optional. If not supplied, all attributes
  100. will be retrieved for the item.
  101. :rtype: :class:`boto.sdb.item.Item`
  102. :return: An Item mapping type containing the requested attribute name/values
  103. """
  104. return self.connection.get_attributes(self, item_name, attribute_name,
  105. consistent_read, item)
  106. def delete_attributes(self, item_name, attributes=None,
  107. expected_values=None):
  108. """
  109. Delete attributes from a given item.
  110. :type item_name: string
  111. :param item_name: The name of the item whose attributes are being deleted.
  112. :type attributes: dict, list or :class:`boto.sdb.item.Item`
  113. :param attributes: Either a list containing attribute names which will cause
  114. all values associated with that attribute name to be deleted or
  115. a dict or Item containing the attribute names and keys and list
  116. of values to delete as the value. If no value is supplied,
  117. all attribute name/values for the item will be deleted.
  118. :type expected_value: list
  119. :param expected_value: If supplied, this is a list or tuple consisting
  120. of a single attribute name and expected value. The list can be of
  121. the form:
  122. * ['name', 'value']
  123. In which case the call will first verify that the attribute "name"
  124. of this item has a value of "value". If it does, the delete
  125. will proceed, otherwise a ConditionalCheckFailed error will be
  126. returned. The list can also be of the form:
  127. * ['name', True|False]
  128. which will simply check for the existence (True) or
  129. non-existence (False) of the attribute.
  130. :rtype: bool
  131. :return: True if successful
  132. """
  133. return self.connection.delete_attributes(self, item_name, attributes,
  134. expected_values)
  135. def batch_delete_attributes(self, items):
  136. """
  137. Delete multiple items in this domain.
  138. :type items: dict or dict-like object
  139. :param items: A dictionary-like object. The keys of the dictionary are
  140. the item names and the values are either:
  141. * dictionaries of attribute names/values, exactly the
  142. same as the attribute_names parameter of the scalar
  143. put_attributes call. The attribute name/value pairs
  144. will only be deleted if they match the name/value
  145. pairs passed in.
  146. * None which means that all attributes associated
  147. with the item should be deleted.
  148. :rtype: bool
  149. :return: True if successful
  150. """
  151. return self.connection.batch_delete_attributes(self, items)
  152. def select(self, query='', next_token=None, consistent_read=False, max_items=None):
  153. """
  154. Returns a set of Attributes for item names within domain_name that match the query.
  155. The query must be expressed in using the SELECT style syntax rather than the
  156. original SimpleDB query language.
  157. :type query: string
  158. :param query: The SimpleDB query to be performed.
  159. :rtype: iter
  160. :return: An iterator containing the results. This is actually a generator
  161. function that will iterate across all search results, not just the
  162. first page.
  163. """
  164. return SelectResultSet(self, query, max_items=max_items, next_token=next_token,
  165. consistent_read=consistent_read)
  166. def get_item(self, item_name, consistent_read=False):
  167. """
  168. Retrieves an item from the domain, along with all of its attributes.
  169. :param string item_name: The name of the item to retrieve.
  170. :rtype: :class:`boto.sdb.item.Item` or ``None``
  171. :keyword bool consistent_read: When set to true, ensures that the most
  172. recent data is returned.
  173. :return: The requested item, or ``None`` if there was no match found
  174. """
  175. item = self.get_attributes(item_name, consistent_read=consistent_read)
  176. if item:
  177. item.domain = self
  178. return item
  179. else:
  180. return None
  181. def new_item(self, item_name):
  182. return self.connection.item_cls(self, item_name)
  183. def delete_item(self, item):
  184. self.delete_attributes(item.name)
  185. def to_xml(self, f=None):
  186. """Get this domain as an XML DOM Document
  187. :param f: Optional File to dump directly to
  188. :type f: File or Stream
  189. :return: File object where the XML has been dumped to
  190. :rtype: file
  191. """
  192. if not f:
  193. from tempfile import TemporaryFile
  194. f = TemporaryFile()
  195. print >> f, '<?xml version="1.0" encoding="UTF-8"?>'
  196. print >> f, '<Domain id="%s">' % self.name
  197. for item in self:
  198. print >> f, '\t<Item id="%s">' % item.name
  199. for k in item:
  200. print >> f, '\t\t<attribute id="%s">' % k
  201. values = item[k]
  202. if not isinstance(values, list):
  203. values = [values]
  204. for value in values:
  205. print >> f, '\t\t\t<value><![CDATA[',
  206. if isinstance(value, unicode):
  207. value = value.encode('utf-8', 'replace')
  208. else:
  209. value = unicode(value, errors='replace').encode('utf-8', 'replace')
  210. f.write(value)
  211. print >> f, ']]></value>'
  212. print >> f, '\t\t</attribute>'
  213. print >> f, '\t</Item>'
  214. print >> f, '</Domain>'
  215. f.flush()
  216. f.seek(0)
  217. return f
  218. def from_xml(self, doc):
  219. """Load this domain based on an XML document"""
  220. import xml.sax
  221. handler = DomainDumpParser(self)
  222. xml.sax.parse(doc, handler)
  223. return handler
  224. def delete(self):
  225. """
  226. Delete this domain, and all items under it
  227. """
  228. return self.connection.delete_domain(self)
  229. class DomainMetaData:
  230. def __init__(self, domain=None):
  231. self.domain = domain
  232. self.item_count = None
  233. self.item_names_size = None
  234. self.attr_name_count = None
  235. self.attr_names_size = None
  236. self.attr_value_count = None
  237. self.attr_values_size = None
  238. def startElement(self, name, attrs, connection):
  239. return None
  240. def endElement(self, name, value, connection):
  241. if name == 'ItemCount':
  242. self.item_count = int(value)
  243. elif name == 'ItemNamesSizeBytes':
  244. self.item_names_size = int(value)
  245. elif name == 'AttributeNameCount':
  246. self.attr_name_count = int(value)
  247. elif name == 'AttributeNamesSizeBytes':
  248. self.attr_names_size = int(value)
  249. elif name == 'AttributeValueCount':
  250. self.attr_value_count = int(value)
  251. elif name == 'AttributeValuesSizeBytes':
  252. self.attr_values_size = int(value)
  253. elif name == 'Timestamp':
  254. self.timestamp = value
  255. else:
  256. setattr(self, name, value)
  257. import sys
  258. from xml.sax.handler import ContentHandler
  259. class DomainDumpParser(ContentHandler):
  260. """
  261. SAX parser for a domain that has been dumped
  262. """
  263. def __init__(self, domain):
  264. self.uploader = UploaderThread(domain)
  265. self.item_id = None
  266. self.attrs = {}
  267. self.attribute = None
  268. self.value = ""
  269. self.domain = domain
  270. def startElement(self, name, attrs):
  271. if name == "Item":
  272. self.item_id = attrs['id']
  273. self.attrs = {}
  274. elif name == "attribute":
  275. self.attribute = attrs['id']
  276. elif name == "value":
  277. self.value = ""
  278. def characters(self, ch):
  279. self.value += ch
  280. def endElement(self, name):
  281. if name == "value":
  282. if self.value and self.attribute:
  283. value = self.value.strip()
  284. attr_name = self.attribute.strip()
  285. if attr_name in self.attrs:
  286. self.attrs[attr_name].append(value)
  287. else:
  288. self.attrs[attr_name] = [value]
  289. elif name == "Item":
  290. self.uploader.items[self.item_id] = self.attrs
  291. # Every 20 items we spawn off the uploader
  292. if len(self.uploader.items) >= 20:
  293. self.uploader.start()
  294. self.uploader = UploaderThread(self.domain)
  295. elif name == "Domain":
  296. # If we're done, spawn off our last Uploader Thread
  297. self.uploader.start()
  298. from threading import Thread
  299. class UploaderThread(Thread):
  300. """Uploader Thread"""
  301. def __init__(self, domain):
  302. self.db = domain
  303. self.items = {}
  304. Thread.__init__(self)
  305. def run(self):
  306. try:
  307. self.db.batch_put_attributes(self.items)
  308. except:
  309. print "Exception using batch put, trying regular put instead"
  310. for item_name in self.items:
  311. self.db.put_attributes(item_name, self.items[item_name])
  312. print ".",
  313. sys.stdout.flush()