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