1# Copyright (c) 2011 Mitch Garnaat http://garnaat.org/
2# Copyright (c) 2011 Amazon.com, Inc. or its affiliates.  All Rights Reserved
3#
4# Permission is hereby granted, free of charge, to any person obtaining a
5# copy of this software and associated documentation files (the
6# "Software"), to deal in the Software without restriction, including
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
9# persons to whom the Software is furnished to do so, subject to the fol-
10# lowing conditions:
11#
12# The above copyright notice and this permission notice shall be included
13# in all copies or substantial portions of the Software.
14#
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-
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,
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
21# IN THE SOFTWARE.
22#
23from boto.dynamodb.layer1 import Layer1
24from boto.dynamodb.table import Table
25from boto.dynamodb.schema import Schema
26from boto.dynamodb.item import Item
27from boto.dynamodb.batch import BatchList, BatchWriteList
28from boto.dynamodb.types import get_dynamodb_type, Dynamizer, \
29        LossyFloatDynamizer, NonBooleanDynamizer
30
31
32class TableGenerator(object):
33    """
34    This is an object that wraps up the table_generator function.
35    The only real reason to have this is that we want to be able
36    to accumulate and return the ConsumedCapacityUnits element that
37    is part of each response.
38
39    :ivar last_evaluated_key: A sequence representing the key(s)
40        of the item last evaluated, or None if no additional
41        results are available.
42
43    :ivar remaining: The remaining quantity of results requested.
44
45    :ivar table: The table to which the call was made.
46    """
47
48    def __init__(self, table, callable, remaining, item_class, kwargs):
49        self.table = table
50        self.callable = callable
51        self.remaining = -1 if remaining is None else remaining
52        self.item_class = item_class
53        self.kwargs = kwargs
54        self._consumed_units = 0.0
55        self.last_evaluated_key = None
56        self._count = 0
57        self._scanned_count = 0
58        self._response = None
59
60    @property
61    def count(self):
62        """
63        The total number of items retrieved thus far.  This value changes with
64        iteration and even when issuing a call with count=True, it is necessary
65        to complete the iteration to assert an accurate count value.
66        """
67        self.response
68        return self._count
69
70    @property
71    def scanned_count(self):
72        """
73        As above, but representing the total number of items scanned by
74        DynamoDB, without regard to any filters.
75        """
76        self.response
77        return self._scanned_count
78
79    @property
80    def consumed_units(self):
81        """
82        Returns a float representing the ConsumedCapacityUnits accumulated.
83        """
84        self.response
85        return self._consumed_units
86
87    @property
88    def response(self):
89        """
90        The current response to the call from DynamoDB.
91        """
92        return self.next_response() if self._response is None else self._response
93
94    def next_response(self):
95        """
96        Issue a call and return the result.  You can invoke this method
97        while iterating over the TableGenerator in order to skip to the
98        next "page" of results.
99        """
100        # preserve any existing limit in case the user alters self.remaining
101        limit = self.kwargs.get('limit')
102        if (self.remaining > 0 and (limit is None or limit > self.remaining)):
103            self.kwargs['limit'] = self.remaining
104        self._response = self.callable(**self.kwargs)
105        self.kwargs['limit'] = limit
106        self._consumed_units += self._response.get('ConsumedCapacityUnits', 0.0)
107        self._count += self._response.get('Count', 0)
108        self._scanned_count += self._response.get('ScannedCount', 0)
109        # at the expense of a possibly gratuitous dynamize, ensure that
110        # early generator termination won't result in bad LEK values
111        if 'LastEvaluatedKey' in self._response:
112            lek = self._response['LastEvaluatedKey']
113            esk = self.table.layer2.dynamize_last_evaluated_key(lek)
114            self.kwargs['exclusive_start_key'] = esk
115            lektuple = (lek['HashKeyElement'],)
116            if 'RangeKeyElement' in lek:
117                lektuple += (lek['RangeKeyElement'],)
118            self.last_evaluated_key = lektuple
119        else:
120            self.last_evaluated_key = None
121        return self._response
122
123    def __iter__(self):
124        while self.remaining != 0:
125            response = self.response
126            for item in response.get('Items', []):
127                self.remaining -= 1
128                yield self.item_class(self.table, attrs=item)
129                if self.remaining == 0:
130                    break
131                if response is not self._response:
132                    break
133            else:
134                if self.last_evaluated_key is not None:
135                    self.next_response()
136                    continue
137                break
138            if response is not self._response:
139                continue
140            break
141
142
143class Layer2(object):
144
145    def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
146                 is_secure=True, port=None, proxy=None, proxy_port=None,
147                 debug=0, security_token=None, region=None,
148                 validate_certs=True, dynamizer=LossyFloatDynamizer,
149                 profile_name=None):
150        self.layer1 = Layer1(aws_access_key_id, aws_secret_access_key,
151                             is_secure, port, proxy, proxy_port,
152                             debug, security_token, region,
153                             validate_certs=validate_certs,
154                             profile_name=profile_name)
155        self.dynamizer = dynamizer()
156
157    def use_decimals(self, use_boolean=False):
158        """
159        Use the ``decimal.Decimal`` type for encoding/decoding numeric types.
160
161        By default, ints/floats are used to represent numeric types
162        ('N', 'NS') received from DynamoDB.  Using the ``Decimal``
163        type is recommended to prevent loss of precision.
164
165        """
166        # Eventually this should be made the default dynamizer.
167        self.dynamizer = Dynamizer() if use_boolean else NonBooleanDynamizer()
168
169    def dynamize_attribute_updates(self, pending_updates):
170        """
171        Convert a set of pending item updates into the structure
172        required by Layer1.
173        """
174        d = {}
175        for attr_name in pending_updates:
176            action, value = pending_updates[attr_name]
177            if value is None:
178                # DELETE without an attribute value
179                d[attr_name] = {"Action": action}
180            else:
181                d[attr_name] = {"Action": action,
182                                "Value": self.dynamizer.encode(value)}
183        return d
184
185    def dynamize_item(self, item):
186        d = {}
187        for attr_name in item:
188            d[attr_name] = self.dynamizer.encode(item[attr_name])
189        return d
190
191    def dynamize_range_key_condition(self, range_key_condition):
192        """
193        Convert a layer2 range_key_condition parameter into the
194        structure required by Layer1.
195        """
196        return range_key_condition.to_dict()
197
198    def dynamize_scan_filter(self, scan_filter):
199        """
200        Convert a layer2 scan_filter parameter into the
201        structure required by Layer1.
202        """
203        d = None
204        if scan_filter:
205            d = {}
206            for attr_name in scan_filter:
207                condition = scan_filter[attr_name]
208                d[attr_name] = condition.to_dict()
209        return d
210
211    def dynamize_expected_value(self, expected_value):
212        """
213        Convert an expected_value parameter into the data structure
214        required for Layer1.
215        """
216        d = None
217        if expected_value:
218            d = {}
219            for attr_name in expected_value:
220                attr_value = expected_value[attr_name]
221                if attr_value is True:
222                    attr_value = {'Exists': True}
223                elif attr_value is False:
224                    attr_value = {'Exists': False}
225                else:
226                    val = self.dynamizer.encode(expected_value[attr_name])
227                    attr_value = {'Value': val}
228                d[attr_name] = attr_value
229        return d
230
231    def dynamize_last_evaluated_key(self, last_evaluated_key):
232        """
233        Convert a last_evaluated_key parameter into the data structure
234        required for Layer1.
235        """
236        d = None
237        if last_evaluated_key:
238            hash_key = last_evaluated_key['HashKeyElement']
239            d = {'HashKeyElement': self.dynamizer.encode(hash_key)}
240            if 'RangeKeyElement' in last_evaluated_key:
241                range_key = last_evaluated_key['RangeKeyElement']
242                d['RangeKeyElement'] = self.dynamizer.encode(range_key)
243        return d
244
245    def build_key_from_values(self, schema, hash_key, range_key=None):
246        """
247        Build a Key structure to be used for accessing items
248        in Amazon DynamoDB.  This method takes the supplied hash_key
249        and optional range_key and validates them against the
250        schema.  If there is a mismatch, a TypeError is raised.
251        Otherwise, a Python dict version of a Amazon DynamoDB Key
252        data structure is returned.
253
254        :type hash_key: int|float|str|unicode|Binary
255        :param hash_key: The hash key of the item you are looking for.
256            The type of the hash key should match the type defined in
257            the schema.
258
259        :type range_key: int|float|str|unicode|Binary
260        :param range_key: The range key of the item your are looking for.
261            This should be supplied only if the schema requires a
262            range key.  The type of the range key should match the
263            type defined in the schema.
264        """
265        dynamodb_key = {}
266        dynamodb_value = self.dynamizer.encode(hash_key)
267        if list(dynamodb_value.keys())[0] != schema.hash_key_type:
268            msg = 'Hashkey must be of type: %s' % schema.hash_key_type
269            raise TypeError(msg)
270        dynamodb_key['HashKeyElement'] = dynamodb_value
271        if range_key is not None:
272            dynamodb_value = self.dynamizer.encode(range_key)
273            if list(dynamodb_value.keys())[0] != schema.range_key_type:
274                msg = 'RangeKey must be of type: %s' % schema.range_key_type
275                raise TypeError(msg)
276            dynamodb_key['RangeKeyElement'] = dynamodb_value
277        return dynamodb_key
278
279    def new_batch_list(self):
280        """
281        Return a new, empty :class:`boto.dynamodb.batch.BatchList`
282        object.
283        """
284        return BatchList(self)
285
286    def new_batch_write_list(self):
287        """
288        Return a new, empty :class:`boto.dynamodb.batch.BatchWriteList`
289        object.
290        """
291        return BatchWriteList(self)
292
293    def list_tables(self, limit=None):
294        """
295        Return a list of the names of all tables associated with the
296        current account and region.
297
298        :type limit: int
299        :param limit: The maximum number of tables to return.
300        """
301        tables = []
302        start_table = None
303        while not limit or len(tables) < limit:
304            this_round_limit = None
305            if limit:
306                this_round_limit = limit - len(tables)
307                this_round_limit = min(this_round_limit, 100)
308            result = self.layer1.list_tables(limit=this_round_limit, start_table=start_table)
309            tables.extend(result.get('TableNames', []))
310            start_table = result.get('LastEvaluatedTableName', None)
311            if not start_table:
312                break
313        return tables
314
315    def describe_table(self, name):
316        """
317        Retrieve information about an existing table.
318
319        :type name: str
320        :param name: The name of the desired table.
321
322        """
323        return self.layer1.describe_table(name)
324
325    def table_from_schema(self, name, schema):
326        """
327        Create a Table object from a schema.
328
329        This method will create a Table object without
330        making any API calls.  If you know the name and schema
331        of the table, you can use this method instead of
332        ``get_table``.
333
334        Example usage::
335
336            table = layer2.table_from_schema(
337                'tablename',
338                Schema.create(hash_key=('foo', 'N')))
339
340        :type name: str
341        :param name: The name of the table.
342
343        :type schema: :class:`boto.dynamodb.schema.Schema`
344        :param schema: The schema associated with the table.
345
346        :rtype: :class:`boto.dynamodb.table.Table`
347        :return: A Table object representing the table.
348
349        """
350        return Table.create_from_schema(self, name, schema)
351
352    def get_table(self, name):
353        """
354        Retrieve the Table object for an existing table.
355
356        :type name: str
357        :param name: The name of the desired table.
358
359        :rtype: :class:`boto.dynamodb.table.Table`
360        :return: A Table object representing the table.
361        """
362        response = self.layer1.describe_table(name)
363        return Table(self, response)
364
365    lookup = get_table
366
367    def create_table(self, name, schema, read_units, write_units):
368        """
369        Create a new Amazon DynamoDB table.
370
371        :type name: str
372        :param name: The name of the desired table.
373
374        :type schema: :class:`boto.dynamodb.schema.Schema`
375        :param schema: The Schema object that defines the schema used
376            by this table.
377
378        :type read_units: int
379        :param read_units: The value for ReadCapacityUnits.
380
381        :type write_units: int
382        :param write_units: The value for WriteCapacityUnits.
383
384        :rtype: :class:`boto.dynamodb.table.Table`
385        :return: A Table object representing the new Amazon DynamoDB table.
386        """
387        response = self.layer1.create_table(name, schema.dict,
388                                            {'ReadCapacityUnits': read_units,
389                                             'WriteCapacityUnits': write_units})
390        return Table(self,  response)
391
392    def update_throughput(self, table, read_units, write_units):
393        """
394        Update the ProvisionedThroughput for the Amazon DynamoDB Table.
395
396        :type table: :class:`boto.dynamodb.table.Table`
397        :param table: The Table object whose throughput is being updated.
398
399        :type read_units: int
400        :param read_units: The new value for ReadCapacityUnits.
401
402        :type write_units: int
403        :param write_units: The new value for WriteCapacityUnits.
404        """
405        response = self.layer1.update_table(table.name,
406                                            {'ReadCapacityUnits': read_units,
407                                             'WriteCapacityUnits': write_units})
408        table.update_from_response(response)
409
410    def delete_table(self, table):
411        """
412        Delete this table and all items in it.  After calling this
413        the Table objects status attribute will be set to 'DELETING'.
414
415        :type table: :class:`boto.dynamodb.table.Table`
416        :param table: The Table object that is being deleted.
417        """
418        response = self.layer1.delete_table(table.name)
419        table.update_from_response(response)
420
421    def create_schema(self, hash_key_name, hash_key_proto_value,
422                      range_key_name=None, range_key_proto_value=None):
423        """
424        Create a Schema object used when creating a Table.
425
426        :type hash_key_name: str
427        :param hash_key_name: The name of the HashKey for the schema.
428
429        :type hash_key_proto_value: int|long|float|str|unicode|Binary
430        :param hash_key_proto_value: A sample or prototype of the type
431            of value you want to use for the HashKey.  Alternatively,
432            you can also just pass in the Python type (e.g. int, float, etc.).
433
434        :type range_key_name: str
435        :param range_key_name: The name of the RangeKey for the schema.
436            This parameter is optional.
437
438        :type range_key_proto_value: int|long|float|str|unicode|Binary
439        :param range_key_proto_value: A sample or prototype of the type
440            of value you want to use for the RangeKey.  Alternatively,
441            you can also pass in the Python type (e.g. int, float, etc.)
442            This parameter is optional.
443        """
444        hash_key = (hash_key_name, get_dynamodb_type(hash_key_proto_value))
445        if range_key_name and range_key_proto_value is not None:
446            range_key = (range_key_name,
447                         get_dynamodb_type(range_key_proto_value))
448        else:
449            range_key = None
450        return Schema.create(hash_key, range_key)
451
452    def get_item(self, table, hash_key, range_key=None,
453                 attributes_to_get=None, consistent_read=False,
454                 item_class=Item):
455        """
456        Retrieve an existing item from the table.
457
458        :type table: :class:`boto.dynamodb.table.Table`
459        :param table: The Table object from which the item is retrieved.
460
461        :type hash_key: int|long|float|str|unicode|Binary
462        :param hash_key: The HashKey of the requested item.  The
463            type of the value must match the type defined in the
464            schema for the table.
465
466        :type range_key: int|long|float|str|unicode|Binary
467        :param range_key: The optional RangeKey of the requested item.
468            The type of the value must match the type defined in the
469            schema for the table.
470
471        :type attributes_to_get: list
472        :param attributes_to_get: A list of attribute names.
473            If supplied, only the specified attribute names will
474            be returned.  Otherwise, all attributes will be returned.
475
476        :type consistent_read: bool
477        :param consistent_read: If True, a consistent read
478            request is issued.  Otherwise, an eventually consistent
479            request is issued.
480
481        :type item_class: Class
482        :param item_class: Allows you to override the class used
483            to generate the items. This should be a subclass of
484            :class:`boto.dynamodb.item.Item`
485        """
486        key = self.build_key_from_values(table.schema, hash_key, range_key)
487        response = self.layer1.get_item(table.name, key,
488                                        attributes_to_get, consistent_read,
489                                        object_hook=self.dynamizer.decode)
490        item = item_class(table, hash_key, range_key, response['Item'])
491        if 'ConsumedCapacityUnits' in response:
492            item.consumed_units = response['ConsumedCapacityUnits']
493        return item
494
495    def batch_get_item(self, batch_list):
496        """
497        Return a set of attributes for a multiple items in
498        multiple tables using their primary keys.
499
500        :type batch_list: :class:`boto.dynamodb.batch.BatchList`
501        :param batch_list: A BatchList object which consists of a
502            list of :class:`boto.dynamoddb.batch.Batch` objects.
503            Each Batch object contains the information about one
504            batch of objects that you wish to retrieve in this
505            request.
506        """
507        request_items = batch_list.to_dict()
508        return self.layer1.batch_get_item(request_items,
509                                          object_hook=self.dynamizer.decode)
510
511    def batch_write_item(self, batch_list):
512        """
513        Performs multiple Puts and Deletes in one batch.
514
515        :type batch_list: :class:`boto.dynamodb.batch.BatchWriteList`
516        :param batch_list: A BatchWriteList object which consists of a
517            list of :class:`boto.dynamoddb.batch.BatchWrite` objects.
518            Each Batch object contains the information about one
519            batch of objects that you wish to put or delete.
520        """
521        request_items = batch_list.to_dict()
522        return self.layer1.batch_write_item(request_items,
523                                            object_hook=self.dynamizer.decode)
524
525    def put_item(self, item, expected_value=None, return_values=None):
526        """
527        Store a new item or completely replace an existing item
528        in Amazon DynamoDB.
529
530        :type item: :class:`boto.dynamodb.item.Item`
531        :param item: The Item to write to Amazon DynamoDB.
532
533        :type expected_value: dict
534        :param expected_value: A dictionary of name/value pairs that you expect.
535            This dictionary should have name/value pairs where the name
536            is the name of the attribute and the value is either the value
537            you are expecting or False if you expect the attribute not to
538            exist.
539
540        :type return_values: str
541        :param return_values: Controls the return of attribute
542            name-value pairs before then were changed.  Possible
543            values are: None or 'ALL_OLD'. If 'ALL_OLD' is
544            specified and the item is overwritten, the content
545            of the old item is returned.
546        """
547        expected_value = self.dynamize_expected_value(expected_value)
548        response = self.layer1.put_item(item.table.name,
549                                        self.dynamize_item(item),
550                                        expected_value, return_values,
551                                        object_hook=self.dynamizer.decode)
552        if 'ConsumedCapacityUnits' in response:
553            item.consumed_units = response['ConsumedCapacityUnits']
554        return response
555
556    def update_item(self, item, expected_value=None, return_values=None):
557        """
558        Commit pending item updates to Amazon DynamoDB.
559
560        :type item: :class:`boto.dynamodb.item.Item`
561        :param item: The Item to update in Amazon DynamoDB.  It is expected
562            that you would have called the add_attribute, put_attribute
563            and/or delete_attribute methods on this Item prior to calling
564            this method.  Those queued changes are what will be updated.
565
566        :type expected_value: dict
567        :param expected_value: A dictionary of name/value pairs that you
568            expect.  This dictionary should have name/value pairs where the
569            name is the name of the attribute and the value is either the
570            value you are expecting or False if you expect the attribute
571            not to exist.
572
573        :type return_values: str
574        :param return_values: Controls the return of attribute name/value pairs
575            before they were updated. Possible values are: None, 'ALL_OLD',
576            'UPDATED_OLD', 'ALL_NEW' or 'UPDATED_NEW'. If 'ALL_OLD' is
577            specified and the item is overwritten, the content of the old item
578            is returned. If 'ALL_NEW' is specified, then all the attributes of
579            the new version of the item are returned. If 'UPDATED_NEW' is
580            specified, the new versions of only the updated attributes are
581            returned.
582
583        """
584        expected_value = self.dynamize_expected_value(expected_value)
585        key = self.build_key_from_values(item.table.schema,
586                                         item.hash_key, item.range_key)
587        attr_updates = self.dynamize_attribute_updates(item._updates)
588
589        response = self.layer1.update_item(item.table.name, key,
590                                           attr_updates,
591                                           expected_value, return_values,
592                                           object_hook=self.dynamizer.decode)
593        item._updates.clear()
594        if 'ConsumedCapacityUnits' in response:
595            item.consumed_units = response['ConsumedCapacityUnits']
596        return response
597
598    def delete_item(self, item, expected_value=None, return_values=None):
599        """
600        Delete the item from Amazon DynamoDB.
601
602        :type item: :class:`boto.dynamodb.item.Item`
603        :param item: The Item to delete from Amazon DynamoDB.
604
605        :type expected_value: dict
606        :param expected_value: A dictionary of name/value pairs that you expect.
607            This dictionary should have name/value pairs where the name
608            is the name of the attribute and the value is either the value
609            you are expecting or False if you expect the attribute not to
610            exist.
611
612        :type return_values: str
613        :param return_values: Controls the return of attribute
614            name-value pairs before then were changed.  Possible
615            values are: None or 'ALL_OLD'. If 'ALL_OLD' is
616            specified and the item is overwritten, the content
617            of the old item is returned.
618        """
619        expected_value = self.dynamize_expected_value(expected_value)
620        key = self.build_key_from_values(item.table.schema,
621                                         item.hash_key, item.range_key)
622        return self.layer1.delete_item(item.table.name, key,
623                                       expected=expected_value,
624                                       return_values=return_values,
625                                       object_hook=self.dynamizer.decode)
626
627    def query(self, table, hash_key, range_key_condition=None,
628              attributes_to_get=None, request_limit=None,
629              max_results=None, consistent_read=False,
630              scan_index_forward=True, exclusive_start_key=None,
631              item_class=Item, count=False):
632        """
633        Perform a query on the table.
634
635        :type table: :class:`boto.dynamodb.table.Table`
636        :param table: The Table object that is being queried.
637
638        :type hash_key: int|long|float|str|unicode|Binary
639        :param hash_key: The HashKey of the requested item.  The
640            type of the value must match the type defined in the
641            schema for the table.
642
643        :type range_key_condition: :class:`boto.dynamodb.condition.Condition`
644        :param range_key_condition: A Condition object.
645            Condition object can be one of the following types:
646
647            EQ|LE|LT|GE|GT|BEGINS_WITH|BETWEEN
648
649            The only condition which expects or will accept two
650            values is 'BETWEEN', otherwise a single value should
651            be passed to the Condition constructor.
652
653        :type attributes_to_get: list
654        :param attributes_to_get: A list of attribute names.
655            If supplied, only the specified attribute names will
656            be returned.  Otherwise, all attributes will be returned.
657
658        :type request_limit: int
659        :param request_limit: The maximum number of items to retrieve
660            from Amazon DynamoDB on each request.  You may want to set
661            a specific request_limit based on the provisioned throughput
662            of your table.  The default behavior is to retrieve as many
663            results as possible per request.
664
665        :type max_results: int
666        :param max_results: The maximum number of results that will
667            be retrieved from Amazon DynamoDB in total.  For example,
668            if you only wanted to see the first 100 results from the
669            query, regardless of how many were actually available, you
670            could set max_results to 100 and the generator returned
671            from the query method will only yeild 100 results max.
672
673        :type consistent_read: bool
674        :param consistent_read: If True, a consistent read
675            request is issued.  Otherwise, an eventually consistent
676            request is issued.
677
678        :type scan_index_forward: bool
679        :param scan_index_forward: Specified forward or backward
680            traversal of the index.  Default is forward (True).
681
682        :type count: bool
683        :param count: If True, Amazon DynamoDB returns a total
684            number of items for the Query operation, even if the
685            operation has no matching items for the assigned filter.
686            If count is True, the actual items are not returned and
687            the count is accessible as the ``count`` attribute of
688            the returned object.
689
690        :type exclusive_start_key: list or tuple
691        :param exclusive_start_key: Primary key of the item from
692            which to continue an earlier query.  This would be
693            provided as the LastEvaluatedKey in that query.
694
695        :type item_class: Class
696        :param item_class: Allows you to override the class used
697            to generate the items. This should be a subclass of
698            :class:`boto.dynamodb.item.Item`
699
700        :rtype: :class:`boto.dynamodb.layer2.TableGenerator`
701        """
702        if range_key_condition:
703            rkc = self.dynamize_range_key_condition(range_key_condition)
704        else:
705            rkc = None
706        if exclusive_start_key:
707            esk = self.build_key_from_values(table.schema,
708                                             *exclusive_start_key)
709        else:
710            esk = None
711        kwargs = {'table_name': table.name,
712                  'hash_key_value': self.dynamizer.encode(hash_key),
713                  'range_key_conditions': rkc,
714                  'attributes_to_get': attributes_to_get,
715                  'limit': request_limit,
716                  'count': count,
717                  'consistent_read': consistent_read,
718                  'scan_index_forward': scan_index_forward,
719                  'exclusive_start_key': esk,
720                  'object_hook': self.dynamizer.decode}
721        return TableGenerator(table, self.layer1.query,
722                              max_results, item_class, kwargs)
723
724    def scan(self, table, scan_filter=None,
725             attributes_to_get=None, request_limit=None, max_results=None,
726             exclusive_start_key=None, item_class=Item, count=False):
727        """
728        Perform a scan of DynamoDB.
729
730        :type table: :class:`boto.dynamodb.table.Table`
731        :param table: The Table object that is being scanned.
732
733        :type scan_filter: A dict
734        :param scan_filter: A dictionary where the key is the
735            attribute name and the value is a
736            :class:`boto.dynamodb.condition.Condition` object.
737            Valid Condition objects include:
738
739             * EQ - equal (1)
740             * NE - not equal (1)
741             * LE - less than or equal (1)
742             * LT - less than (1)
743             * GE - greater than or equal (1)
744             * GT - greater than (1)
745             * NOT_NULL - attribute exists (0, use None)
746             * NULL - attribute does not exist (0, use None)
747             * CONTAINS - substring or value in list (1)
748             * NOT_CONTAINS - absence of substring or value in list (1)
749             * BEGINS_WITH - substring prefix (1)
750             * IN - exact match in list (N)
751             * BETWEEN - >= first value, <= second value (2)
752
753        :type attributes_to_get: list
754        :param attributes_to_get: A list of attribute names.
755            If supplied, only the specified attribute names will
756            be returned.  Otherwise, all attributes will be returned.
757
758        :type request_limit: int
759        :param request_limit: The maximum number of items to retrieve
760            from Amazon DynamoDB on each request.  You may want to set
761            a specific request_limit based on the provisioned throughput
762            of your table.  The default behavior is to retrieve as many
763            results as possible per request.
764
765        :type max_results: int
766        :param max_results: The maximum number of results that will
767            be retrieved from Amazon DynamoDB in total.  For example,
768            if you only wanted to see the first 100 results from the
769            query, regardless of how many were actually available, you
770            could set max_results to 100 and the generator returned
771            from the query method will only yeild 100 results max.
772
773        :type count: bool
774        :param count: If True, Amazon DynamoDB returns a total
775            number of items for the Scan operation, even if the
776            operation has no matching items for the assigned filter.
777            If count is True, the actual items are not returned and
778            the count is accessible as the ``count`` attribute of
779            the returned object.
780
781        :type exclusive_start_key: list or tuple
782        :param exclusive_start_key: Primary key of the item from
783            which to continue an earlier query.  This would be
784            provided as the LastEvaluatedKey in that query.
785
786        :type item_class: Class
787        :param item_class: Allows you to override the class used
788            to generate the items. This should be a subclass of
789            :class:`boto.dynamodb.item.Item`
790
791        :rtype: :class:`boto.dynamodb.layer2.TableGenerator`
792        """
793        if exclusive_start_key:
794            esk = self.build_key_from_values(table.schema,
795                                             *exclusive_start_key)
796        else:
797            esk = None
798        kwargs = {'table_name': table.name,
799                  'scan_filter': self.dynamize_scan_filter(scan_filter),
800                  'attributes_to_get': attributes_to_get,
801                  'limit': request_limit,
802                  'count': count,
803                  'exclusive_start_key': esk,
804                  'object_hook': self.dynamizer.decode}
805        return TableGenerator(table, self.layer1.scan,
806                              max_results, item_class, kwargs)
807