OLD | NEW |
(Empty) | |
| 1 .. dynamodb_tut: |
| 2 |
| 3 ============================================ |
| 4 An Introduction to boto's DynamoDB interface |
| 5 ============================================ |
| 6 |
| 7 This tutorial focuses on the boto interface to AWS' DynamoDB_. This tutorial |
| 8 assumes that you have boto already downloaded and installed. |
| 9 |
| 10 .. _DynamoDB: http://aws.amazon.com/dynamodb/ |
| 11 |
| 12 |
| 13 Creating a Connection |
| 14 --------------------- |
| 15 |
| 16 The first step in accessing DynamoDB is to create a connection to the service. |
| 17 To do so, the most straight forward way is the following:: |
| 18 |
| 19 >>> import boto |
| 20 >>> conn = boto.connect_dynamodb( |
| 21 aws_access_key_id='<YOUR_AWS_KEY_ID>', |
| 22 aws_secret_access_key='<YOUR_AWS_SECRET_KEY>') |
| 23 >>> conn |
| 24 <boto.dynamodb.layer2.Layer2 object at 0x3fb3090> |
| 25 |
| 26 Bear in mind that if you have your credentials in boto config in your home |
| 27 directory, the two keyword arguments in the call above are not needed. More |
| 28 details on configuration can be found in :doc:`boto_config_tut`. |
| 29 |
| 30 The :py:func:`boto.connect_dynamodb` functions returns a |
| 31 :py:class:`boto.dynamodb.layer2.Layer2` instance, which is a high-level API |
| 32 for working with DynamoDB. Layer2 is a set of abstractions that sit atop |
| 33 the lower level :py:class:`boto.dynamodb.layer1.Layer1` API, which closely |
| 34 mirrors the Amazon DynamoDB API. For the purpose of this tutorial, we'll |
| 35 just be covering Layer2. |
| 36 |
| 37 |
| 38 Listing Tables |
| 39 -------------- |
| 40 |
| 41 Now that we have a DynamoDB connection object, we can then query for a list of |
| 42 existing tables in that region:: |
| 43 |
| 44 >>> conn.list_tables() |
| 45 ['test-table', 'another-table'] |
| 46 |
| 47 |
| 48 Creating Tables |
| 49 --------------- |
| 50 |
| 51 DynamoDB tables are created with the |
| 52 :py:meth:`Layer2.create_table <boto.dynamodb.layer2.Layer2.create_table>` |
| 53 method. While DynamoDB's items (a rough equivalent to a relational DB's row) |
| 54 don't have a fixed schema, you do need to create a schema for the table's |
| 55 hash key element, and the optional range key element. This is explained in |
| 56 greater detail in DynamoDB's `Data Model`_ documentation. |
| 57 |
| 58 We'll start by defining a schema that has a hash key and a range key that |
| 59 are both keys:: |
| 60 |
| 61 >>> message_table_schema = conn.create_schema( |
| 62 hash_key_name='forum_name', |
| 63 hash_key_proto_value=str, |
| 64 range_key_name='subject', |
| 65 range_key_proto_value=str |
| 66 ) |
| 67 |
| 68 The next few things to determine are table name and read/write throughput. We'll |
| 69 defer explaining throughput to the DynamoDB's `Provisioned Throughput`_ docs. |
| 70 |
| 71 We're now ready to create the table:: |
| 72 |
| 73 >>> table = conn.create_table( |
| 74 name='messages', |
| 75 schema=message_table_schema, |
| 76 read_units=10, |
| 77 write_units=10 |
| 78 ) |
| 79 >>> table |
| 80 Table(messages) |
| 81 |
| 82 This returns a :py:class:`boto.dynamodb.table.Table` instance, which provides |
| 83 simple ways to create (put), update, and delete items. |
| 84 |
| 85 |
| 86 Getting a Table |
| 87 --------------- |
| 88 |
| 89 To retrieve an existing table, use |
| 90 :py:meth:`Layer2.get_table <boto.dynamodb.layer2.Layer2.get_table>`:: |
| 91 |
| 92 >>> conn.list_tables() |
| 93 ['test-table', 'another-table', 'messages'] |
| 94 >>> table = conn.get_table('messages') |
| 95 >>> table |
| 96 Table(messages) |
| 97 |
| 98 :py:meth:`Layer2.get_table <boto.dynamodb.layer2.Layer2.get_table>`, like |
| 99 :py:meth:`Layer2.create_table <boto.dynamodb.layer2.Layer2.create_table>`, |
| 100 returns a :py:class:`boto.dynamodb.table.Table` instance. |
| 101 |
| 102 Keep in mind that :py:meth:`Layer2.get_table <boto.dynamodb.layer2.Layer2.get_ta
ble>` |
| 103 will make an API call to retrieve various attributes of the table including the |
| 104 creation time, the read and write capacity, and the table schema. If you |
| 105 already know the schema, you can save an API call and create a |
| 106 :py:class:`boto.dynamodb.table.Table` object without making any calls to |
| 107 Amazon DynamoDB:: |
| 108 |
| 109 >>> table = conn.table_from_schema( |
| 110 name='messages', |
| 111 schema=message_table_schema) |
| 112 |
| 113 If you do this, the following fields will have ``None`` values: |
| 114 |
| 115 * create_time |
| 116 * status |
| 117 * read_units |
| 118 * write_units |
| 119 |
| 120 In addition, the ``item_count`` and ``size_bytes`` will be 0. |
| 121 If you create a table object directly from a schema object and |
| 122 decide later that you need to retrieve any of these additional |
| 123 attributes, you can use the |
| 124 :py:meth:`Table.refresh <boto.dynamodb.table.Table.refresh>` method:: |
| 125 |
| 126 >>> from boto.dynamodb.schema import Schema |
| 127 >>> table = conn.table_from_schema( |
| 128 name='messages', |
| 129 schema=Schema.create(hash_key=('forum_name', 'S'), |
| 130 range_key=('subject', 'S'))) |
| 131 >>> print table.write_units |
| 132 None |
| 133 >>> # Now we decide we need to know the write_units: |
| 134 >>> table.refresh() |
| 135 >>> print table.write_units |
| 136 10 |
| 137 |
| 138 |
| 139 The recommended best practice is to retrieve a table object once and |
| 140 use that object for the duration of your application. So, for example, |
| 141 instead of this:: |
| 142 |
| 143 class Application(object): |
| 144 def __init__(self, layer2): |
| 145 self._layer2 = layer2 |
| 146 |
| 147 def retrieve_item(self, table_name, key): |
| 148 return self._layer2.get_table(table_name).get_item(key) |
| 149 |
| 150 You can do something like this instead:: |
| 151 |
| 152 class Application(object): |
| 153 def __init__(self, layer2): |
| 154 self._layer2 = layer2 |
| 155 self._tables_by_name = {} |
| 156 |
| 157 def retrieve_item(self, table_name, key): |
| 158 table = self._tables_by_name.get(table_name) |
| 159 if table is None: |
| 160 table = self._layer2.get_table(table_name) |
| 161 self._tables_by_name[table_name] = table |
| 162 return table.get_item(key) |
| 163 |
| 164 |
| 165 Describing Tables |
| 166 ----------------- |
| 167 |
| 168 To get a complete description of a table, use |
| 169 :py:meth:`Layer2.describe_table <boto.dynamodb.layer2.Layer2.describe_table>`:: |
| 170 |
| 171 >>> conn.list_tables() |
| 172 ['test-table', 'another-table', 'messages'] |
| 173 >>> conn.describe_table('messages') |
| 174 { |
| 175 'Table': { |
| 176 'CreationDateTime': 1327117581.624, |
| 177 'ItemCount': 0, |
| 178 'KeySchema': { |
| 179 'HashKeyElement': { |
| 180 'AttributeName': 'forum_name', |
| 181 'AttributeType': 'S' |
| 182 }, |
| 183 'RangeKeyElement': { |
| 184 'AttributeName': 'subject', |
| 185 'AttributeType': 'S' |
| 186 } |
| 187 }, |
| 188 'ProvisionedThroughput': { |
| 189 'ReadCapacityUnits': 10, |
| 190 'WriteCapacityUnits': 10 |
| 191 }, |
| 192 'TableName': 'messages', |
| 193 'TableSizeBytes': 0, |
| 194 'TableStatus': 'ACTIVE' |
| 195 } |
| 196 } |
| 197 |
| 198 |
| 199 Adding Items |
| 200 ------------ |
| 201 |
| 202 Continuing on with our previously created ``messages`` table, adding an:: |
| 203 |
| 204 >>> table = conn.get_table('messages') |
| 205 >>> item_data = { |
| 206 'Body': 'http://url_to_lolcat.gif', |
| 207 'SentBy': 'User A', |
| 208 'ReceivedTime': '12/9/2011 11:36:03 PM', |
| 209 } |
| 210 >>> item = table.new_item( |
| 211 # Our hash key is 'forum' |
| 212 hash_key='LOLCat Forum', |
| 213 # Our range key is 'subject' |
| 214 range_key='Check this out!', |
| 215 # This has the |
| 216 attrs=item_data |
| 217 ) |
| 218 |
| 219 The |
| 220 :py:meth:`Table.new_item <boto.dynamodb.table.Table.new_item>` method creates |
| 221 a new :py:class:`boto.dynamodb.item.Item` instance with your specified |
| 222 hash key, range key, and attributes already set. |
| 223 :py:class:`Item <boto.dynamodb.item.Item>` is a :py:class:`dict` sub-class, |
| 224 meaning you can edit your data as such:: |
| 225 |
| 226 item['a_new_key'] = 'testing' |
| 227 del item['a_new_key'] |
| 228 |
| 229 After you are happy with the contents of the item, use |
| 230 :py:meth:`Item.put <boto.dynamodb.item.Item.put>` to commit it to DynamoDB:: |
| 231 |
| 232 >>> item.put() |
| 233 |
| 234 |
| 235 Retrieving Items |
| 236 ---------------- |
| 237 |
| 238 Now, let's check if it got added correctly. Since DynamoDB works under an |
| 239 'eventual consistency' mode, we need to specify that we wish a consistent read, |
| 240 as follows:: |
| 241 |
| 242 >>> table = conn.get_table('messages') |
| 243 >>> item = table.get_item( |
| 244 # Your hash key was 'forum_name' |
| 245 hash_key='LOLCat Forum', |
| 246 # Your range key was 'subject' |
| 247 range_key='Check this out!' |
| 248 ) |
| 249 >>> item |
| 250 { |
| 251 # Note that this was your hash key attribute (forum_name) |
| 252 'forum_name': 'LOLCat Forum', |
| 253 # This is your range key attribute (subject) |
| 254 'subject': 'Check this out!' |
| 255 'Body': 'http://url_to_lolcat.gif', |
| 256 'ReceivedTime': '12/9/2011 11:36:03 PM', |
| 257 'SentBy': 'User A', |
| 258 } |
| 259 |
| 260 |
| 261 Updating Items |
| 262 -------------- |
| 263 |
| 264 To update an item's attributes, simply retrieve it, modify the value, then |
| 265 :py:meth:`Item.put <boto.dynamodb.item.Item.put>` it again:: |
| 266 |
| 267 >>> table = conn.get_table('messages') |
| 268 >>> item = table.get_item( |
| 269 hash_key='LOLCat Forum', |
| 270 range_key='Check this out!' |
| 271 ) |
| 272 >>> item['SentBy'] = 'User B' |
| 273 >>> item.put() |
| 274 |
| 275 Working with Decimals |
| 276 --------------------- |
| 277 |
| 278 To avoid the loss of precision, you can stipulate that the |
| 279 ``decimal.Decimal`` type be used for numeric values:: |
| 280 |
| 281 >>> import decimal |
| 282 >>> conn.use_decimals() |
| 283 >>> table = conn.get_table('messages') |
| 284 >>> item = table.new_item( |
| 285 hash_key='LOLCat Forum', |
| 286 range_key='Check this out!' |
| 287 ) |
| 288 >>> item['decimal_type'] = decimal.Decimal('1.12345678912345') |
| 289 >>> item.put() |
| 290 >>> print table.get_item('LOLCat Forum', 'Check this out!') |
| 291 {u'forum_name': 'LOLCat Forum', u'decimal_type': Decimal('1.12345678912345')
, |
| 292 u'subject': 'Check this out!'} |
| 293 |
| 294 You can enable the usage of ``decimal.Decimal`` by using either the ``use_decima
ls`` |
| 295 method, or by passing in the |
| 296 :py:class:`Dynamizer <boto.dynamodb.types.Dynamizer>` class for |
| 297 the ``dynamizer`` param:: |
| 298 |
| 299 >>> from boto.dynamodb.types import Dynamizer |
| 300 >>> conn = boto.connect_dynamodb(dynamizer=Dynamizer) |
| 301 |
| 302 This mechanism can also be used if you want to customize the encoding/decoding |
| 303 process of DynamoDB types. |
| 304 |
| 305 |
| 306 Deleting Items |
| 307 -------------- |
| 308 |
| 309 To delete items, use the |
| 310 :py:meth:`Item.delete <boto.dynamodb.item.Item.delete>` method:: |
| 311 |
| 312 >>> table = conn.get_table('messages') |
| 313 >>> item = table.get_item( |
| 314 hash_key='LOLCat Forum', |
| 315 range_key='Check this out!' |
| 316 ) |
| 317 >>> item.delete() |
| 318 |
| 319 |
| 320 Deleting Tables |
| 321 --------------- |
| 322 |
| 323 .. WARNING:: |
| 324 Deleting a table will also **permanently** delete all of its contents without
prompt. Use carefully. |
| 325 |
| 326 There are two easy ways to delete a table. Through your top-level |
| 327 :py:class:`Layer2 <boto.dynamodb.layer2.Layer2>` object:: |
| 328 |
| 329 >>> conn.delete_table(table) |
| 330 |
| 331 Or by getting the table, then using |
| 332 :py:meth:`Table.delete <boto.dynamodb.table.Table.delete>`:: |
| 333 |
| 334 >>> table = conn.get_table('messages') |
| 335 >>> table.delete() |
| 336 |
| 337 |
| 338 .. _Data Model: http://docs.amazonwebservices.com/amazondynamodb/latest/develope
rguide/DataModel.html |
| 339 .. _Provisioned Throughput: http://docs.amazonwebservices.com/amazondynamodb/lat
est/developerguide/ProvisionedThroughputIntro.html |
OLD | NEW |