FRTc@sddlZddlmZddlmZmZmZmZmZm Z m Z m Z ddl m Z ddlmZddlmZmZddlmZmZmZmZddlmZd efd YZd efd YZdS( iN(t exceptions(tHashKeytRangeKeytAllIndext KeysOnlyIndext IncludeIndextGlobalAllIndextGlobalKeysOnlyIndextGlobalIncludeIndex(tItem(tDynamoDBConnection(t ResultSettBatchGetResultSet(t DynamizertFILTER_OPERATORStQUERY_OPERATORStSTRING(tJSONResponseErrortTablec BseZdZdZddddddZedddddZddZdZ dZ ddZ dZ d Z edd Zd Zd Zd ZedZddZddZdddZdZdZedZddeedddZddeedddddZdeddeddZddeedddddd ZdddddddZ dddddddZ!eddZ"eddZ#dZ$RS(s Interacts & models the behavior of a DynamoDB table. The ``Table`` object represents a set (or rough categorization) of records within DynamoDB. The important part is that all records within the table, while largely-schema-free, share the same schema & are essentially namespaced for use in your application. For example, you might have a ``users`` table or a ``forums`` table. idcCs||_||_idd6dd6|_||_||_||_|jdkrbt|_n|dk rz||_nt|_ dS(s Sets up a new in-memory ``Table``. This is useful if the table already exists within DynamoDB & you simply want to use it for additional interactions. The only required parameter is the ``table_name``. However, under the hood, the object will call ``describe_table`` to determine the schema/indexes/throughput. You can avoid this extra call by passing in ``schema`` & ``indexes``. **IMPORTANT** - If you're creating a new ``Table`` for the first time, you should use the ``Table.create`` method instead, as it will persist the table structure to DynamoDB. Requires a ``table_name`` parameter, which should be a simple string of the name of the table. Optionally accepts a ``schema`` parameter, which should be a list of ``BaseSchemaField`` subclasses representing the desired schema. Optionally accepts a ``throughput`` parameter, which should be a dictionary. If provided, it should specify a ``read`` & ``write`` key, both of which should have an integer value associated with them. Optionally accepts a ``indexes`` parameter, which should be a list of ``BaseIndexField`` subclasses representing the desired indexes. Optionally accepts a ``global_indexes`` parameter, which should be a list of ``GlobalBaseIndexField`` subclasses representing the desired indexes. Optionally accepts a ``connection`` parameter, which should be a ``DynamoDBConnection`` instance (or subclass). This is primarily useful for specifying alternate connection parameters. Example:: # The simple, it-already-exists case. >>> conn = Table('users') # The full, minimum-extra-calls case. >>> from boto import dynamodb2 >>> users = Table('users', schema=[ ... HashKey('username'), ... RangeKey('date_joined', data_type=NUMBER) ... ], throughput={ ... 'read':20, ... 'write': 10, ... }, indexes=[ ... KeysOnlyIndex('MostRecentlyJoined', parts=[ ... HashKey('username') ... RangeKey('date_joined') ... ]), ... ], global_indexes=[ ... GlobalAllIndex('UsersByZipcode', parts=[ ... HashKey('zipcode'), ... RangeKey('username'), ... ], ... throughput={ ... 'read':10, ... 'write":10, ... }), ... ], connection=dynamodb2.connect_to_region('us-west-2', ... aws_access_key_id='key', ... aws_secret_access_key='key', ... )) itreadtwriteN( t table_namet connectiont throughputtschematindexestglobal_indexestNoneR R t _dynamizer(tselfRRRRRR((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pyt__init__sE        c Cs|d|d|}||_|dk r6||_n|dk rN||_n|dk rf||_ng}g} t} xG|jD]<} |j| j| j| j| j| j qWit |jdd6t |jdd6} i} idd6d d 6}xdD]}t ||}|rg}xj|D]b}|j|jxF|j D];} | j| kr_| j| j| j| j q_q_Wq<W|| ||>> users = Table.create('users', schema=[ ... HashKey('username'), ... RangeKey('date_joined', data_type=NUMBER) ... ], throughput={ ... 'read':20, ... 'write': 10, ... }, indexes=[ ... KeysOnlyIndex('MostRecentlyJoined', parts=[ ... RangeKey('date_joined') ... ]), global_indexes=[ ... GlobalAllIndex('UsersByZipcode', parts=[ ... HashKey('zipcode'), ... RangeKey('username'), ... ], ... throughput={ ... 'read':10, ... 'write':10, ... }), ... ]) RRRtReadCapacityUnitsRtWriteCapacityUnitstlocal_secondary_indexesRtglobal_secondary_indexesRtattribute_definitionst key_schematprovisioned_throughputN(sindexessglobal_indexes(RRRRRtsettappendtaddtnamet definitiontinttgetattrtpartsRt create_tableR(tclsRRRRRRttablet raw_schemat attr_defst seen_attrstfieldtraw_throughputtkwargst kwarg_mapt index_attrt table_indexest raw_indexest index_field((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pytcreatersPA             cCsg}i}|r8x#|D]}|d||d>> users.describe() { # Lots of keys here... } >>> len(users.schema) 2 RtProvisionedThroughputRRR RRPtAttributeDefinitionstLocalSecondaryIndexes( Rtdescribe_tableRR+RRRCRGRRS(RtresultR5R1RER:((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pytdescribe,s  cCs||_it|jdd6t|jdd6}d }|rg}xa|jD]P\}}|jii|d6it|dd6t|dd6d6d6qVWn|jj|jd|d |tS( s Updates table attributes in DynamoDB. Currently, the only thing you can modify about a table after it has been created is the throughput. Requires a ``throughput`` parameter, which should be a dictionary. If provided, it should specify a ``read`` & ``write`` key, both of which should have an integer value associated with them. Returns ``True`` on success. Example:: # For a read-heavier application... >>> users.update(throughput={ ... 'read': 20, ... 'write': 10, ... }) True # To also update the global index(es) throughput. >>> users.update(throughput={ ... 'read': 20, ... 'write': 10, ... }, ... global_secondary_indexes={ ... 'TheIndexNameHere': { ... 'read': 15, ... 'write': 5, ... } ... }) True RRRR RORTtUpdateR%tglobal_secondary_index_updatesN( RR+RtitemsR'Rt update_tableRtTrue(RRRtdatatgsi_datatgsi_nametgsi_throughput((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pytupdateYs&$  $  cCs|jj|jtS(s Deletes a table in DynamoDB. **IMPORTANT** - Be careful when using this method, there is no undo. Returns ``True`` on success. Example:: >>> users.delete() True (Rt delete_tableRR^(R((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pytdeletescCs=i}x0|jD]"\}}|jj|||>> john = users.get_item(username='johndoe') >>> john['first_name'] 'John' # A complex hash+range key. >>> john = users.get_item(username='johndoe', last_name='Doe') >>> john['first_name'] 'John' # A consistent read (assuming the data might have just changed). >>> john = users.get_item(username='johndoe', consistent=True) >>> john['first_name'] 'Johann' # With a key that is an invalid variable name in Python. # Also, assumes a different schema than previous examples. >>> john = users.get_item(**{ ... 'date-joined': 127549192, ... }) >>> john['first_name'] 'John' tattributes_to_gettconsistent_readR sItem %s couldn't be found.(RkRtget_itemRRt ItemNotFoundR tload(Rt consistentt attributesR6Rht item_datatitem((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pyRns.     cKs3y|j|Wnttjfk r.tSXtS(s Return whether an item (record) exists within a table in DynamoDB. To specify the key of the item you'd like to get, you can specify the key attributes as kwargs. Optionally accepts a ``consistent`` parameter, which should be a boolean. If you provide ``True``, it will perform a consistent (but more expensive) read from DynamoDB. (Default: ``False``) Optionally accepts an ``attributes`` parameter, which should be a list of fieldnames to fetch. (Default: ``None``, which means all fields should be fetched) Returns ``True`` if an ``Item`` is present, ``False`` if not. Example:: # Simple, just hash-key schema. >>> users.has_item(username='johndoe') True # Complex schema, item not present. >>> users.has_item( ... username='johndoe', ... date_joined='2014-01-07' ... ) False (RnRRRotFalseR^(RR6((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pythas_items cOsj|js|jnx.t|D] \}}|||j|j>> user = users.lookup(username) >>> user = users.lookup(username, consistent=True) >>> app = apps.lookup('my_customer_id', 'my_app_id') N(RRYt enumerateR)RnRgR(RtargsR6txtargtret((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pytlookup's   cGs]|js|jni}x.t|D] \}}|||j|j>> users.put_item(data={ ... 'username': 'jane', ... 'first_name': 'Jane', ... 'last_name': 'Doe', ... 'date_joined': 126478915, ... }) True R_t overwrite(R tsave(RR_R~Rt((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pytput_itemMscCs<i}|dk r||d__=``. **IMPORTANT** - Be careful when using this method, there is no undo. To specify the key of the item you'd like to get, you can specify the key attributes as kwargs. Optionally accepts an ``expected`` parameter which is a dictionary of expected attribute value conditions. Optionally accepts a ``conditional_operator`` which applies to the expected attribute value conditions: + `AND` - If all of the conditions evaluate to true (default) + `OR` - True if at least one condition evaluates to true Returns ``True`` on success, ``False`` on failed conditional delete. Example:: # A simple hash key. >>> users.delete_item(username='johndoe') True # A complex hash+range key. >>> users.delete_item(username='jane', last_name='Doe') True # With a key that is an invalid variable name in Python. # Also, assumes a different schema than previous examples. >>> users.delete_item(**{ ... 'date-joined': 127549192, ... }) True # Conditional delete >>> users.delete_item(username='johndoe', ... expected={'balance__eq': 0}) True tusingRtconditional_operator( t_build_filtersRRkRt delete_itemRRtConditionalCheckFailedExceptionRuR^(RRRR6Rh((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pyRs4 cCs3|js|jng|jD]}|j^q S(s Returns the fields necessary to make a key for a table. If the ``Table`` does not already have a populated ``schema``, this will request it via a ``Table.describe`` call. Returns a list of fieldnames (strings). Example:: # A simple hash key. >>> users.get_key_fields() ['username'] # A complex hash+range key. >>> users.get_key_fields() ['username', 'last_name'] (RRYR)(RR4((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pytget_key_fieldss  cCs t|S(s Allows the batching of writes to DynamoDB. Since each write/delete call to DynamoDB has a cost associated with it, when loading lots of data, it makes sense to batch them, creating as few calls as possible. This returns a context manager that will transparently handle creating these batches. The object you get back lightly-resembles a ``Table`` object, sharing just the ``put_item`` & ``delete_item`` methods (which are all that DynamoDB can batch in terms of writing data). DynamoDB's maximum batch size is 25 items per request. If you attempt to put/delete more than that, the context manager will batch as many as it can up to that number, then flush them to DynamoDB & continue batching as more calls come in. Example:: # Assuming a table with one record... >>> with users.batch_write() as batch: ... batch.put_item(data={ ... 'username': 'johndoe', ... 'first_name': 'John', ... 'last_name': 'Doe', ... 'owner': 1, ... }) ... # Nothing across the wire yet. ... batch.delete_item(username='bob') ... # Still no requests sent. ... batch.put_item(data={ ... 'username': 'jane', ... 'first_name': 'Jane', ... 'last_name': 'Doe', ... 'date_joined': 127436192, ... }) ... # Nothing yet, but once we leave the context, the ... # put/deletes will be sent. (t BatchTable(R((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pyt batch_writes*c Cs|dkrdSi}x|jD]\}}|jd}dj|d }y||d}Wn.tk rtjd|d|fnXigd6|d6} |ddkr| d=|tkrd| d__=``. Query filters are specified in the same way. Optionally accepts a ``limit`` parameter, which should be an integer count of the total number of items to return. (Default: ``None`` - all results) Optionally accepts an ``index`` parameter, which should be a string of name of the local secondary index you want to query against. (Default: ``None``) Optionally accepts a ``reverse`` parameter, which will present the results in reverse order. (Default: ``False`` - normal order) Optionally accepts a ``consistent`` parameter, which should be a boolean. If you provide ``True``, it will force a consistent read of the data (more expensive). (Default: ``False`` - use eventually consistent reads) Optionally accepts a ``attributes`` parameter, which should be a tuple. If you provide any attributes only these will be fetched from DynamoDB. This uses the ``AttributesToGet`` and set's ``Select`` to ``SPECIFIC_ATTRIBUTES`` API. Optionally accepts a ``max_page_size`` parameter, which should be an integer count of the maximum number of items to retrieve **per-request**. This is useful in making faster requests & prevent the scan from drowning out other queries. (Default: ``None`` - fetch as many as DynamoDB will return) Optionally accepts a ``query_filter`` which is a dictionary of filter conditions against any arbitrary field in the returned data. Optionally accepts a ``conditional_operator`` which applies to the query filter conditions: + `AND` - True if all filter conditions evaluate to true (default) + `OR` - True if at least one filter condition evaluates to true Returns a ``ResultSet``, which transparently handles the pagination of results you get back. Example:: # Look for last names equal to "Doe". >>> results = users.query(last_name__eq='Doe') >>> for res in results: ... print res['first_name'] 'John' 'Jane' # Look for last names beginning with "D", in reverse order, limit 3. >>> results = users.query( ... last_name__beginswith='D', ... reverse=True, ... limit=3 ... ) >>> for res in results: ... print res['first_name'] 'Alice' 'Jane' 'John' # Use an LSI & a consistent read. >>> results = users.query( ... date_joined__gte=1236451000, ... owner__eq=1, ... index='DateJoinedIndex', ... consistent=True ... ) >>> for res in results: ... print res['first_name'] 'Alice' 'Bob' 'John' 'Fred' # Filter by non-indexed field(s) >>> results = users.query( ... last_name__eq='Doe', ... reverse=True, ... query_filter={ ... 'first_name__beginswith': 'A' ... } ... ) >>> for res in results: ... print res['first_name'] + ' ' + res['last_name'] 'Alice Doe' is0You must specify more than one key to filter on.tSPECIFIC_ATTRIBUTESRRRRRqtselectRlt query_filterRN( RRRRt QueryErrorRR tcopyRctto_callt_query( RRRRRqRrRRRRRtresultsR6((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pyRas.j       c Ks|j|dt}|j|dt} |jj|jd|d|ddd|d| d|d |d |} t| jd d S( s Queries the exact count of matching items in a DynamoDB table. Queries can be performed against a hash key, a hash+range key or against any data stored in your local secondary indexes. Query filters can be used to filter on arbitrary fields. To specify the filters of the items you'd like to get, you can specify the filters as kwargs. Each filter kwarg should follow the pattern ``__=``. Query filters are specified in the same way. Optionally accepts an ``index`` parameter, which should be a string of name of the local secondary index you want to query against. (Default: ``None``) Optionally accepts a ``consistent`` parameter, which should be a boolean. If you provide ``True``, it will force a consistent read of the data (more expensive). (Default: ``False`` - use eventually consistent reads) Optionally accepts a ``query_filter`` which is a dictionary of filter conditions against any arbitrary field in the returned data. Optionally accepts a ``conditional_operator`` which applies to the query filter conditions: + `AND` - True if all filter conditions evaluate to true (default) + `OR` - True if at least one filter condition evaluates to true Returns an integer which represents the exact amount of matched items. :type scan_index_forward: boolean :param scan_index_forward: Specifies ascending (true) or descending (false) traversal of the index. DynamoDB returns results reflecting the requested order determined by the range key. If the data type is Number, the results are returned in numeric order. For String, the results are returned in order of ASCII character code values. For Binary, DynamoDB treats each byte of the binary data as unsigned when it compares binary values. If ScanIndexForward is not specified, the results are returned in ascending order. :type limit: integer :param limit: The maximum number of items to evaluate (not necessarily the number of matching items). Example:: # Look for last names equal to "Doe". >>> users.query_count(last_name__eq='Doe') 5 # Use an LSI & a consistent read. >>> users.query_count( ... date_joined__gte=1236451000, ... owner__eq=1, ... index='DateJoinedIndex', ... consistent=True ... ) 2 Rt index_nameRmRtCOUNTtkey_conditionsRRRtscan_index_forwardtCounti(RRRRRRR+RC( RRRqRRRRRRtbuilt_query_filtert raw_results((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pyt query_counts"D     c Ksi|d6|d6|d6|d6|d6| d6} |rCt| d__=``. Optionally accepts a ``limit`` parameter, which should be an integer count of the total number of items to return. (Default: ``None`` - all results) Optionally accepts a ``segment`` parameter, which should be an integer of the segment to retrieve on. Please see the documentation about Parallel Scans (Default: ``None`` - no segments) Optionally accepts a ``total_segments`` parameter, which should be an integer count of number of segments to divide the table into. Please see the documentation about Parallel Scans (Default: ``None`` - no segments) Optionally accepts a ``max_page_size`` parameter, which should be an integer count of the maximum number of items to retrieve **per-request**. This is useful in making faster requests & prevent the scan from drowning out other queries. (Default: ``None`` - fetch as many as DynamoDB will return) Optionally accepts an ``attributes`` parameter, which should be a tuple. If you provide any attributes only these will be fetched from DynamoDB. This uses the ``AttributesToGet`` and set's ``Select`` to ``SPECIFIC_ATTRIBUTES`` API. Returns a ``ResultSet``, which transparently handles the pagination of results you get back. Example:: # All results. >>> everything = users.scan() # Look for last names beginning with "D". >>> results = users.scan(last_name__beginswith='D') >>> for res in results: ... print res['first_name'] 'Alice' 'John' 'Jane' # Use an ``IN`` filter & limit. >>> results = users.scan( ... age__in=[25, 26, 27, 28, 29], ... limit=1 ... ) >>> for res in results: ... print res['first_name'] 'Alice' RRtsegmentttotal_segmentsRrR(R RRcRt_scan( RRRRRRrRRRR6((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pytscans@    cKs^i|d6|d6|d6|d6|d6}|rsi|d>> results = users.batch_get(keys=[ ... { ... 'username': 'johndoe', ... }, ... { ... 'username': 'jane', ... }, ... { ... 'username': 'fred', ... }, ... ]) >>> for res in results: ... print res['first_name'] 'John' 'Jane' 'Fred' Rgt max_batch_getRqRr(R RRt _batch_get(RRgRqRrR((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pyt batch_gets(cCsiigd6|j6}|r1t||jd>> users.count() 6 Rt ItemCounti(RYRC(Rtinfo((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pytcountks N(%t__name__t __module__t__doc__RRRt classmethodR<RGRSRYRcReRkRuRnRvR|R}RRRRRRRRRRR^RRRRRRR(((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pyRsR  Vx  ! - @  ; '  !  @  , @    Z  < M 4,7RcBsbeZdZdZdZdZedZdZdZ dZ dZ d Z RS( s Used by ``Table`` as the context manager for batch writes. You likely don't want to try to use this object directly. cCs(||_g|_g|_g|_dS(N(R0t_to_putt _to_deletet _unprocessed(RR0((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pyRs   cCs|S(N((R((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pyt __enter__scCs9|js|jr|jn|jr5|jndS(N(RRtflushRtresend_unprocessed(RttypeRjt traceback((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pyt__exit__s  cCs-|jj||jr)|jndS(N(RR't should_flushR(RR_R~((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pyRs cKs-|jj||jr)|jndS(N(RR'RR(RR6((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pyRs cCs*t|jt|jdkr&tStS(Ni(RRRR^Ru(R((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pyRs"cCsig|jj6}xQ|jD]F}t|jd|}||jjjii|jd6d6qWxB|jD]7}||jjjii|jj|d6d6qqW|jjj |}|j |g|_g|_t S(NR_R t PutRequesttKeyt DeleteRequest( R0RRR R't prepare_fullRRkRtbatch_write_itemthandle_unprocessedR^(Rt batch_datatputRtRetresp((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pyRs    cCsqt|jdgrm|jj}|dj|g}d}tjj|t||jj|ndS(NtUnprocessedItemss-%s items were unprocessed. Storing for later.( RRCR0RtbototlogRRtextend(RRRt unprocessedtmsg((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pyRs  cCstjjdt|jxt|jr|jd }|jd|_i||jj6}tjjdt||jjj|}|j |tjjdt|jq WdS(Ns Re-sending %s unprocessed items.isSending %s itemss%s unprocessed items left( RRRRRR0RRRR(Rt to_resendRR((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pyRs    ( RRRRRRRuRRRRRR(((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pyR|s       (Rtboto.dynamodb2Rtboto.dynamodb2.fieldsRRRRRRRRtboto.dynamodb2.itemsR tboto.dynamodb2.layer1R tboto.dynamodb2.resultsR R tboto.dynamodb2.typesR RRRtboto.exceptionRtobjectRR(((sA/opt/freeware/lib/python2.7/site-packages/boto/dynamodb2/table.pyts :"r