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