Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1005)

Side by Side Diff: third_party/gsutil/boto/dynamodb/layer1.py

Issue 12042069: Scripts to download files from google storage based on sha1 sums (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: Removed gsutil/tests and gsutil/docs Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 # Copyright (c) 2012 Mitch Garnaat http://garnaat.org/
2 # Copyright (c) 2012 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 #
23 import time
24 from binascii import crc32
25
26 import boto
27 from boto.connection import AWSAuthConnection
28 from boto.exception import DynamoDBResponseError
29 from boto.provider import Provider
30 from boto.dynamodb import exceptions as dynamodb_exceptions
31 from boto.compat import json
32
33 #
34 # To get full debug output, uncomment the following line and set the
35 # value of Debug to be 2
36 #
37 #boto.set_stream_logger('dynamodb')
38 Debug = 0
39
40
41 class Layer1(AWSAuthConnection):
42 """
43 This is the lowest-level interface to DynamoDB. Methods at this
44 layer map directly to API requests and parameters to the methods
45 are either simple, scalar values or they are the Python equivalent
46 of the JSON input as defined in the DynamoDB Developer's Guide.
47 All responses are direct decoding of the JSON response bodies to
48 Python data structures via the json or simplejson modules.
49
50 :ivar throughput_exceeded_events: An integer variable that
51 keeps a running total of the number of ThroughputExceeded
52 responses this connection has received from Amazon DynamoDB.
53 """
54
55 DefaultRegionName = 'us-east-1'
56 """The default region name for DynamoDB API."""
57
58 ServiceName = 'DynamoDB'
59 """The name of the Service"""
60
61 Version = '20111205'
62 """DynamoDB API version."""
63
64 ThruputError = "ProvisionedThroughputExceededException"
65 """The error response returned when provisioned throughput is exceeded"""
66
67 SessionExpiredError = 'com.amazon.coral.service#ExpiredTokenException'
68 """The error response returned when session token has expired"""
69
70 ConditionalCheckFailedError = 'ConditionalCheckFailedException'
71 """The error response returned when a conditional check fails"""
72
73 ValidationError = 'ValidationException'
74 """The error response returned when an item is invalid in some way"""
75
76 ResponseError = DynamoDBResponseError
77
78 NumberRetries = 10
79 """The number of times an error is retried."""
80
81 def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
82 is_secure=True, port=None, proxy=None, proxy_port=None,
83 debug=0, security_token=None, region=None,
84 validate_certs=True, validate_checksums=True):
85 if not region:
86 region_name = boto.config.get('DynamoDB', 'region',
87 self.DefaultRegionName)
88 for reg in boto.dynamodb.regions():
89 if reg.name == region_name:
90 region = reg
91 break
92
93 self.region = region
94 AWSAuthConnection.__init__(self, self.region.endpoint,
95 aws_access_key_id,
96 aws_secret_access_key,
97 is_secure, port, proxy, proxy_port,
98 debug=debug, security_token=security_token,
99 validate_certs=validate_certs)
100 self.throughput_exceeded_events = 0
101 self._validate_checksums = boto.config.getbool(
102 'DynamoDB', 'validate_checksums', validate_checksums)
103
104 def _get_session_token(self):
105 self.provider = Provider(self._provider_type)
106 self._auth_handler.update_provider(self.provider)
107
108 def _required_auth_capability(self):
109 return ['hmac-v4']
110
111 def make_request(self, action, body='', object_hook=None):
112 """
113 :raises: ``DynamoDBExpiredTokenError`` if the security token expires.
114 """
115 headers = {'X-Amz-Target': '%s_%s.%s' % (self.ServiceName,
116 self.Version, action),
117 'Host': self.region.endpoint,
118 'Content-Type': 'application/x-amz-json-1.0',
119 'Content-Length': str(len(body))}
120 http_request = self.build_base_http_request('POST', '/', '/',
121 {}, headers, body, None)
122 start = time.time()
123 response = self._mexe(http_request, sender=None,
124 override_num_retries=self.NumberRetries,
125 retry_handler=self._retry_handler)
126 elapsed = (time.time() - start) * 1000
127 request_id = response.getheader('x-amzn-RequestId')
128 boto.log.debug('RequestId: %s' % request_id)
129 boto.perflog.debug('%s: id=%s time=%sms',
130 headers['X-Amz-Target'], request_id, int(elapsed))
131 response_body = response.read()
132 boto.log.debug(response_body)
133 return json.loads(response_body, object_hook=object_hook)
134
135 def _retry_handler(self, response, i, next_sleep):
136 status = None
137 if response.status == 400:
138 response_body = response.read()
139 boto.log.debug(response_body)
140 data = json.loads(response_body)
141 if self.ThruputError in data.get('__type'):
142 self.throughput_exceeded_events += 1
143 msg = "%s, retry attempt %s" % (self.ThruputError, i)
144 next_sleep = self._exponential_time(i)
145 i += 1
146 status = (msg, i, next_sleep)
147 elif self.SessionExpiredError in data.get('__type'):
148 msg = 'Renewing Session Token'
149 self._get_session_token()
150 status = (msg, i + self.num_retries - 1, 0)
151 elif self.ConditionalCheckFailedError in data.get('__type'):
152 raise dynamodb_exceptions.DynamoDBConditionalCheckFailedError(
153 response.status, response.reason, data)
154 elif self.ValidationError in data.get('__type'):
155 raise dynamodb_exceptions.DynamoDBValidationError(
156 response.status, response.reason, data)
157 else:
158 raise self.ResponseError(response.status, response.reason,
159 data)
160 expected_crc32 = response.getheader('x-amz-crc32')
161 if self._validate_checksums and expected_crc32 is not None:
162 boto.log.debug('Validating crc32 checksum for body: %s',
163 response.read())
164 actual_crc32 = crc32(response.read()) & 0xffffffff
165 expected_crc32 = int(expected_crc32)
166 if actual_crc32 != expected_crc32:
167 msg = ("The calculated checksum %s did not match the expected "
168 "checksum %s" % (actual_crc32, expected_crc32))
169 status = (msg, i + 1, self._exponential_time(i))
170 return status
171
172 def _exponential_time(self, i):
173 if i == 0:
174 next_sleep = 0
175 else:
176 next_sleep = 0.05 * (2 ** i)
177 return next_sleep
178
179 def list_tables(self, limit=None, start_table=None):
180 """
181 Returns a dictionary of results. The dictionary contains
182 a **TableNames** key whose value is a list of the table names.
183 The dictionary could also contain a **LastEvaluatedTableName**
184 key whose value would be the last table name returned if
185 the complete list of table names was not returned. This
186 value would then be passed as the ``start_table`` parameter on
187 a subsequent call to this method.
188
189 :type limit: int
190 :param limit: The maximum number of tables to return.
191
192 :type start_table: str
193 :param start_table: The name of the table that starts the
194 list. If you ran a previous list_tables and not
195 all results were returned, the response dict would
196 include a LastEvaluatedTableName attribute. Use
197 that value here to continue the listing.
198 """
199 data = {}
200 if limit:
201 data['Limit'] = limit
202 if start_table:
203 data['ExclusiveStartTableName'] = start_table
204 json_input = json.dumps(data)
205 return self.make_request('ListTables', json_input)
206
207 def describe_table(self, table_name):
208 """
209 Returns information about the table including current
210 state of the table, primary key schema and when the
211 table was created.
212
213 :type table_name: str
214 :param table_name: The name of the table to describe.
215 """
216 data = {'TableName': table_name}
217 json_input = json.dumps(data)
218 return self.make_request('DescribeTable', json_input)
219
220 def create_table(self, table_name, schema, provisioned_throughput):
221 """
222 Add a new table to your account. The table name must be unique
223 among those associated with the account issuing the request.
224 This request triggers an asynchronous workflow to begin creating
225 the table. When the workflow is complete, the state of the
226 table will be ACTIVE.
227
228 :type table_name: str
229 :param table_name: The name of the table to create.
230
231 :type schema: dict
232 :param schema: A Python version of the KeySchema data structure
233 as defined by DynamoDB
234
235 :type provisioned_throughput: dict
236 :param provisioned_throughput: A Python version of the
237 ProvisionedThroughput data structure defined by
238 DynamoDB.
239 """
240 data = {'TableName': table_name,
241 'KeySchema': schema,
242 'ProvisionedThroughput': provisioned_throughput}
243 json_input = json.dumps(data)
244 response_dict = self.make_request('CreateTable', json_input)
245 return response_dict
246
247 def update_table(self, table_name, provisioned_throughput):
248 """
249 Updates the provisioned throughput for a given table.
250
251 :type table_name: str
252 :param table_name: The name of the table to update.
253
254 :type provisioned_throughput: dict
255 :param provisioned_throughput: A Python version of the
256 ProvisionedThroughput data structure defined by
257 DynamoDB.
258 """
259 data = {'TableName': table_name,
260 'ProvisionedThroughput': provisioned_throughput}
261 json_input = json.dumps(data)
262 return self.make_request('UpdateTable', json_input)
263
264 def delete_table(self, table_name):
265 """
266 Deletes the table and all of it's data. After this request
267 the table will be in the DELETING state until DynamoDB
268 completes the delete operation.
269
270 :type table_name: str
271 :param table_name: The name of the table to delete.
272 """
273 data = {'TableName': table_name}
274 json_input = json.dumps(data)
275 return self.make_request('DeleteTable', json_input)
276
277 def get_item(self, table_name, key, attributes_to_get=None,
278 consistent_read=False, object_hook=None):
279 """
280 Return a set of attributes for an item that matches
281 the supplied key.
282
283 :type table_name: str
284 :param table_name: The name of the table containing the item.
285
286 :type key: dict
287 :param key: A Python version of the Key data structure
288 defined by DynamoDB.
289
290 :type attributes_to_get: list
291 :param attributes_to_get: A list of attribute names.
292 If supplied, only the specified attribute names will
293 be returned. Otherwise, all attributes will be returned.
294
295 :type consistent_read: bool
296 :param consistent_read: If True, a consistent read
297 request is issued. Otherwise, an eventually consistent
298 request is issued.
299 """
300 data = {'TableName': table_name,
301 'Key': key}
302 if attributes_to_get:
303 data['AttributesToGet'] = attributes_to_get
304 if consistent_read:
305 data['ConsistentRead'] = True
306 json_input = json.dumps(data)
307 response = self.make_request('GetItem', json_input,
308 object_hook=object_hook)
309 if 'Item' not in response:
310 raise dynamodb_exceptions.DynamoDBKeyNotFoundError(
311 "Key does not exist."
312 )
313 return response
314
315 def batch_get_item(self, request_items, object_hook=None):
316 """
317 Return a set of attributes for a multiple items in
318 multiple tables using their primary keys.
319
320 :type request_items: dict
321 :param request_items: A Python version of the RequestItems
322 data structure defined by DynamoDB.
323 """
324 # If the list is empty, return empty response
325 if not request_items:
326 return {}
327 data = {'RequestItems': request_items}
328 json_input = json.dumps(data)
329 return self.make_request('BatchGetItem', json_input,
330 object_hook=object_hook)
331
332 def batch_write_item(self, request_items, object_hook=None):
333 """
334 This operation enables you to put or delete several items
335 across multiple tables in a single API call.
336
337 :type request_items: dict
338 :param request_items: A Python version of the RequestItems
339 data structure defined by DynamoDB.
340 """
341 data = {'RequestItems': request_items}
342 json_input = json.dumps(data)
343 return self.make_request('BatchWriteItem', json_input,
344 object_hook=object_hook)
345
346 def put_item(self, table_name, item,
347 expected=None, return_values=None,
348 object_hook=None):
349 """
350 Create a new item or replace an old item with a new
351 item (including all attributes). If an item already
352 exists in the specified table with the same primary
353 key, the new item will completely replace the old item.
354 You can perform a conditional put by specifying an
355 expected rule.
356
357 :type table_name: str
358 :param table_name: The name of the table in which to put the item.
359
360 :type item: dict
361 :param item: A Python version of the Item data structure
362 defined by DynamoDB.
363
364 :type expected: dict
365 :param expected: A Python version of the Expected
366 data structure defined by DynamoDB.
367
368 :type return_values: str
369 :param return_values: Controls the return of attribute
370 name-value pairs before then were changed. Possible
371 values are: None or 'ALL_OLD'. If 'ALL_OLD' is
372 specified and the item is overwritten, the content
373 of the old item is returned.
374 """
375 data = {'TableName': table_name,
376 'Item': item}
377 if expected:
378 data['Expected'] = expected
379 if return_values:
380 data['ReturnValues'] = return_values
381 json_input = json.dumps(data)
382 return self.make_request('PutItem', json_input,
383 object_hook=object_hook)
384
385 def update_item(self, table_name, key, attribute_updates,
386 expected=None, return_values=None,
387 object_hook=None):
388 """
389 Edits an existing item's attributes. You can perform a conditional
390 update (insert a new attribute name-value pair if it doesn't exist,
391 or replace an existing name-value pair if it has certain expected
392 attribute values).
393
394 :type table_name: str
395 :param table_name: The name of the table.
396
397 :type key: dict
398 :param key: A Python version of the Key data structure
399 defined by DynamoDB which identifies the item to be updated.
400
401 :type attribute_updates: dict
402 :param attribute_updates: A Python version of the AttributeUpdates
403 data structure defined by DynamoDB.
404
405 :type expected: dict
406 :param expected: A Python version of the Expected
407 data structure defined by DynamoDB.
408
409 :type return_values: str
410 :param return_values: Controls the return of attribute
411 name-value pairs before then were changed. Possible
412 values are: None or 'ALL_OLD'. If 'ALL_OLD' is
413 specified and the item is overwritten, the content
414 of the old item is returned.
415 """
416 data = {'TableName': table_name,
417 'Key': key,
418 'AttributeUpdates': attribute_updates}
419 if expected:
420 data['Expected'] = expected
421 if return_values:
422 data['ReturnValues'] = return_values
423 json_input = json.dumps(data)
424 return self.make_request('UpdateItem', json_input,
425 object_hook=object_hook)
426
427 def delete_item(self, table_name, key,
428 expected=None, return_values=None,
429 object_hook=None):
430 """
431 Delete an item and all of it's attributes by primary key.
432 You can perform a conditional delete by specifying an
433 expected rule.
434
435 :type table_name: str
436 :param table_name: The name of the table containing the item.
437
438 :type key: dict
439 :param key: A Python version of the Key data structure
440 defined by DynamoDB.
441
442 :type expected: dict
443 :param expected: A Python version of the Expected
444 data structure defined by DynamoDB.
445
446 :type return_values: str
447 :param return_values: Controls the return of attribute
448 name-value pairs before then were changed. Possible
449 values are: None or 'ALL_OLD'. If 'ALL_OLD' is
450 specified and the item is overwritten, the content
451 of the old item is returned.
452 """
453 data = {'TableName': table_name,
454 'Key': key}
455 if expected:
456 data['Expected'] = expected
457 if return_values:
458 data['ReturnValues'] = return_values
459 json_input = json.dumps(data)
460 return self.make_request('DeleteItem', json_input,
461 object_hook=object_hook)
462
463 def query(self, table_name, hash_key_value, range_key_conditions=None,
464 attributes_to_get=None, limit=None, consistent_read=False,
465 scan_index_forward=True, exclusive_start_key=None,
466 object_hook=None):
467 """
468 Perform a query of DynamoDB. This version is currently punting
469 and expecting you to provide a full and correct JSON body
470 which is passed as is to DynamoDB.
471
472 :type table_name: str
473 :param table_name: The name of the table to query.
474
475 :type hash_key_value: dict
476 :param key: A DynamoDB-style HashKeyValue.
477
478 :type range_key_conditions: dict
479 :param range_key_conditions: A Python version of the
480 RangeKeyConditions data structure.
481
482 :type attributes_to_get: list
483 :param attributes_to_get: A list of attribute names.
484 If supplied, only the specified attribute names will
485 be returned. Otherwise, all attributes will be returned.
486
487 :type limit: int
488 :param limit: The maximum number of items to return.
489
490 :type consistent_read: bool
491 :param consistent_read: If True, a consistent read
492 request is issued. Otherwise, an eventually consistent
493 request is issued.
494
495 :type scan_index_forward: bool
496 :param scan_index_forward: Specified forward or backward
497 traversal of the index. Default is forward (True).
498
499 :type exclusive_start_key: list or tuple
500 :param exclusive_start_key: Primary key of the item from
501 which to continue an earlier query. This would be
502 provided as the LastEvaluatedKey in that query.
503 """
504 data = {'TableName': table_name,
505 'HashKeyValue': hash_key_value}
506 if range_key_conditions:
507 data['RangeKeyCondition'] = range_key_conditions
508 if attributes_to_get:
509 data['AttributesToGet'] = attributes_to_get
510 if limit:
511 data['Limit'] = limit
512 if consistent_read:
513 data['ConsistentRead'] = True
514 if scan_index_forward:
515 data['ScanIndexForward'] = True
516 else:
517 data['ScanIndexForward'] = False
518 if exclusive_start_key:
519 data['ExclusiveStartKey'] = exclusive_start_key
520 json_input = json.dumps(data)
521 return self.make_request('Query', json_input,
522 object_hook=object_hook)
523
524 def scan(self, table_name, scan_filter=None,
525 attributes_to_get=None, limit=None,
526 count=False, exclusive_start_key=None,
527 object_hook=None):
528 """
529 Perform a scan of DynamoDB. This version is currently punting
530 and expecting you to provide a full and correct JSON body
531 which is passed as is to DynamoDB.
532
533 :type table_name: str
534 :param table_name: The name of the table to scan.
535
536 :type scan_filter: dict
537 :param scan_filter: A Python version of the
538 ScanFilter data structure.
539
540 :type attributes_to_get: list
541 :param attributes_to_get: A list of attribute names.
542 If supplied, only the specified attribute names will
543 be returned. Otherwise, all attributes will be returned.
544
545 :type limit: int
546 :param limit: The maximum number of items to return.
547
548 :type count: bool
549 :param count: If True, Amazon DynamoDB returns a total
550 number of items for the Scan operation, even if the
551 operation has no matching items for the assigned filter.
552
553 :type exclusive_start_key: list or tuple
554 :param exclusive_start_key: Primary key of the item from
555 which to continue an earlier query. This would be
556 provided as the LastEvaluatedKey in that query.
557 """
558 data = {'TableName': table_name}
559 if scan_filter:
560 data['ScanFilter'] = scan_filter
561 if attributes_to_get:
562 data['AttributesToGet'] = attributes_to_get
563 if limit:
564 data['Limit'] = limit
565 if count:
566 data['Count'] = True
567 if exclusive_start_key:
568 data['ExclusiveStartKey'] = exclusive_start_key
569 json_input = json.dumps(data)
570 return self.make_request('Scan', json_input, object_hook=object_hook)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698