Python Boto3和亚马逊DynamoDB编程教程

585 阅读10分钟

Python Boto3和亚马逊DynamoDB编程教程

DynamoDB是AWS(亚马逊网络服务)提供的一种快速而灵活的NoSQL数据库服务。DynamoDB非常适合于移动应用、网络应用、物联网设备和游戏。Python对DynamoDB有很好的支持。在本教程中,我们将使用AWS PythonSDK(Boto3)对DynamoDB进行CRUD(创建、读取、更新、删除)操作。

前提条件

在学习本教程之前,你必须有DynamoDB的知识。

要开始学习本教程,你需要以下条件。

  • DynamoDB本地:下载并配置DynamoDB。这个版本的DynamoDB仅用于开发目的。
  • Python:下载并安装2.7或更高版本的Python。最新版本的Python可以在[官方网站]上下载。
  • [集成开发环境]:使用你选择的IDE或代码编辑器。VS Code是一个不错的选择。

DynamoDB SDK介绍

AWS提供了一个与DynamoDB交互的SDK。SDK工具可用于不同的编程语言。

在本教程中,我们将学习如何使用AWS SDK for Python(Boto3)与DynamoDB交互。Boto3允许Python开发人员创建、配置和管理不同的AWS产品。

将AWS Python SDK (Boto3)与DynamoDB连接起来

在继续前进之前,请确保你满足先决条件。通过运行下面的命令安装最新版本的Boto3。这将安装Boto3 Python依赖项,这是我们的代码运行所需要的。

python -m pip install boto3

现在,我们将使用Python连接到DynamoDB的本地实例。我们将使用下面的代码来完成这个任务。注意endpoint_url

dynamodb = boto3.resource('dynamodb', endpoint_url="http://localhost:8000")

注意:为了使Python代码工作,我们必须使用下面的代码在我们的脚本中导入Boto3依赖。

import boto3

Boto3也可以用来连接AWS DynamoDB的在线实例(生产版本)。

使用Python SDK进行DynamoDB操作

在这个阶段,我们已经导入了Boto3库,并有一个本地版本的DynamoDB在运行。因此,我们可以编写Python脚本来对DynamoDB进行操作。第一步将是在我们的DynamoDB上创建一个表。在运行任何脚本之前,确保DynamoDB的本地实例正在你的电脑上运行。

创建表

我们将使用create_table 方法创建一个名为Devices 的表。该表的属性device_id 作为分区键,datacount 作为排序键。创建一个脚本并将其命名为create_table.py 。将下面的代码粘贴到脚本中。

import boto3  # import Boto3


def create_devices_table(dynamodb=None):
    dynamodb = boto3.resource(
        'dynamodb', endpoint_url="http://localhost:8000")
    # Table defination
    table = dynamodb.create_table(
        TableName='Devices',
        KeySchema=[
            {
                'AttributeName': 'device_id',
                'KeyType': 'HASH'  # Partition key
            },
            {
                'AttributeName': 'datacount',
                'KeyType': 'RANGE'  # Sort key
            }
        ],
        AttributeDefinitions=[
            {
                'AttributeName': 'device_id',
                # AttributeType defines the data type. 'S' is string type and 'N' is number type
                'AttributeType': 'S'
            },
            {
                'AttributeName': 'datacount',
                'AttributeType': 'N'
            },
        ],
        ProvisionedThroughput={
            # ReadCapacityUnits set to 10 strongly consistent reads per second
            'ReadCapacityUnits': 10,
            'WriteCapacityUnits': 10  # WriteCapacityUnits set to 10 writes per second
        }
    )
    return table


if __name__ == '__main__':
    device_table = create_devices_table()
    # Print tablle status
    print("Status:", device_table.table_status)

在上面的脚本中,首先要导入boto3的依赖关系。在每个连接到DynamoDB的脚本中都要导入这个依赖关系。我们也要连接到DynamoDB本地服务器。在脚本中,我们将定义表的结构。

只有分区键和排序键是必需的。请注意AttributeTypeProvisionedThroughputAttributeType 定义了数据类型。ProvisionedThroughput 是一个应用程序在表上可以消耗的最大读写容量。

AWS API文档中了解更多关于ProvisionedThroughput 。要运行该脚本,请输入以下命令。

python create_table.py

加载样本数据

让我们用一些数据来填充这个表。我们将通过使用函数put_item ,从一个JSON文件加载数据来实现这一目标。数据应该是JSON格式的,如下图所示。在JSONLint上验证数据是否为有效的JSON格式。将下面的数据保存在一个文件中,并将其命名为data.json

[
  {
    "device_id": "10001",
    "datacount": 1,
    "info": {
      "info_timestamp": "1612519200",
      "temperature1": 37.2,
      "temperature2": 21.31,
      "temperature3": 25.6,
      "temperature4": 22.96,
      "temperature5": 24.69
    }
  },
  {
    "device_id": "10001",
    "datacount": 2,
    "info": {
      "info_timestamp": "1612521000",
      "temperature1": 24.34,
      "temperature2": 24.59,
      "temperature3": 19.2,
      "temperature4": 29.11,
      "temperature5": 23.18
    }
  },
  {
    "device_id": "10002",
    "datacount": 1,
    "info": {
      "info_timestamp": "1612519200",
      "temperature1": 14.34,
      "temperature2": 17.59,
      "temperature3": 11.2,
      "temperature4": 15.95,
      "temperature5": 16.17
    }
  },
  {
    "device_id": "10002",
    "datacount": 2,
    "info": {
      "info_timestamp": "1612521000",
      "temperature1": 13.04,
      "temperature2": 15.01,
      "temperature3": 18.91,
      "temperature4": 16.45,
      "temperature5": 16.21
    }
  },
  {
    "device_id": "10003",
    "datacount": 1,
    "info": {
      "info_timestamp": "1612519200",
      "temperature1": 34.23,
      "temperature2": 36.21,
      "temperature3": 31.24,
      "temperature4": 32.02,
      "temperature5": 29.54
    }
  },
  {
    "device_id": "10003",
    "datacount": 2,
    "info": {
      "info_timestamp": "1612521000",
      "temperature1": 34.55,
      "temperature2": 33.13,
      "temperature3": 32.62,
      "temperature4": 39.32,
      "temperature5": 38.87
    }
  }
]

创建一个名为load_data.py 的脚本并添加以下代码。该代码从JSON文件data.json 中加载数据,并将其插入到Devices 表中。

import json  # module for converting Python objects to JSON
# decimal module support correctly-rounded decimal floating point arithmetic.
from decimal import Decimal
import boto3  # import Boto3


def load_data(devices, dynamodb=None):
    dynamodb = boto3.resource(
        'dynamodb', endpoint_url="http://localhost:8000")

    devices_table = dynamodb.Table('Devices')
    # Loop through all the items and load each
    for device in devices:
        device_id = (device['device_id'])
        datacount = device['datacount']
        # Print device info
        print("Loading Devices Data:", device_id, datacount)
        devices_table.put_item(Item=device)


if __name__ == '__main__':
    # open file and read all the data in it
    with open("data.json") as json_file:
        device_list = json.load(json_file, parse_float=Decimal)
    load_data(device_list)

要执行该脚本,运行下面的命令。

python load_data.py

下面是数据加载过程成功后的预期响应。

Adding Device Data: 10001 1
Adding Device Data: 10001 2
Adding Device Data: 10002 1
Adding Device Data: 10002 2
Adding Device Data: 10003 1
Adding Device Data: 10003 2

创建项目

我们使用put_item 方法在我们的表中插入项目。我们将创建一个脚本,在表Devices 中插入/创建一个新项目。创建一个名为create_item.py 的脚本并粘贴下面的代码。

from pprint import pprint  # import pprint, a module that enable to “pretty-print”
import boto3  # import Boto3


def put_device(device_id, datacount, timestamp, temperature1, temperature2, temperature3, temperature4, temperature5, dynamodb=None):
    dynamodb = boto3.resource(
        'dynamodb', endpoint_url="http://localhost:8000")
    # Specify the table
    devices_table = dynamodb.Table('Devices')
    response = devices_table.put_item(
        # Data to be inserted
        Item={
            'device_id': device_id,
            'datacount': datacount,
            'info': {
                'info_timestamp': timestamp,
                'temperature1': temperature1,
                'temperature2': temperature2,
                'temperature3': temperature3,
                'temperature4': temperature4,
                'temperature5': temperature5
            }
        }
    )
    return response


if __name__ == '__main__':
    device_resp = put_device("10001", 3, "1612522800",
                             "23.74", "32.56", "12.43", "44.74", "12.74")
    print("Create item successful.")
    # Print response
    pprint(device_resp)

运行下面的命令来执行脚本create_item.py

python create_item.py

我们刚刚添加了下面这个项目。

{
  "device_id": "10001",
  "datacount": 3,
  "info": {
    "info_timestamp": "1612522800",
    "temperature1": 23.74,
    "temperature2": 23.74,
    "temperature3": 12.43,
    "temperature4": 44.74,
    "temperature5": 12.74
  }
}

读取项目

我们将使用get_item 方法读取我们刚刚创建的项目。我们需要指定我们要读取的项目的主键。在这种情况下,Devices 表的主键是一个分区键和一个排序键的组合。主键是device_id ,而排序键是datacount

# import Boto3 exceptions and error handling module
from botocore.exceptions import ClientError
import boto3  # import Boto3


def get_device(device_id, datacount, dynamodb=None):
    dynamodb = boto3.resource(
        'dynamodb', endpoint_url="http://localhost:8000")
    # Specify the table to read from
    devices_table = dynamodb.Table('Devices')

    try:
        response = devices_table.get_item(
            Key={'device_id': device_id, 'datacount': datacount})
    except ClientError as e:
        print(e.response['Error']['Message'])
    else:
        return response['Item']


if __name__ == '__main__':
    device = get_device("10001", 3,)
    if device:
        print("Get Device Data Done:")
        # Print the data read
        print(device)

运行下面的命令来执行脚本read_item.py

python read_item.py

下面是预期的输出。你可以确认响应的项目是我们之前创建的项目。使用特定的主键,我们可以检索到一个特定的项目。

Get Device Data Done:
{'datacount': Decimal('3'),
 'device_id': '10001',
 'info': {'info_timestamp': '1612522800',
          'temperature1': '23.74',
          'temperature2': '32.56',
          'temperature3': '12.43',
          'temperature4': '44.74',
          'temperature5': '12.74'}}

条件

DynamoDB有一个使用条件的规定。在更新或删除项目时,可以应用条件。我们可以提供一个ConditionExpression

如果ConditionExpression ,评估为真,那么就会执行该动作。熟悉一下不同的DynamoDB条件

更新

更新是指通过更新现有属性值、删除属性或添加新属性来修改先前创建的项目。在本教程中,我们将更新现有属性的值。下面是原始项目和更新后的项目。

原始项目

{
  "device_id": "10001",
  "datacount": 3,
  "info": {
    "info_timestamp": "1612522800",
    "temperature1": 23.74,
    "temperature2": 23.74,
    "temperature3": 12.43,
    "temperature4": 44.74,
    "temperature5": 12.74
  }
}

更新的项目

{
  "device_id": "10001",
  "datacount": 3,
  "info": {
    "info_timestamp": "1612522800",
    "temperature1": 33.74,
    "temperature2": 23.74,
    "temperature3": 25.2,
    "temperature4": 22.0,
    "temperature5": 25.0
  }
}

我们将使用update_item 方法,如下面的代码所示。创建一个名为update_item.py 的脚本,添加下面的代码。

from pprint import pprint  # import pprint, a module that enable to “pretty-print”
import boto3  # import Boto3


def update_device(device_id, datacount, info_timestamp, temperature1, temperature2, temperature3, temperature4, temperature5, dynamodb=None):
    dynamodb = boto3.resource(
        'dynamodb', endpoint_url="http://localhost:8000")
    # Specify the table
    devices_table = dynamodb.Table('Devices')

    response = devices_table.update_item(
        Key={
            'device_id': device_id,
            'datacount': datacount
        },
        UpdateExpression="set info.info_timestamp=:time, info.temperature1=:t1, info.temperature2=:t2, info.temperature3=:t3, info.temperature4=:t4, info.temperature5=:t5",
        ExpressionAttributeValues={
            ':time': info_timestamp,
            ':t1': temperature1,
            ':t2': temperature2,
            ':t3': temperature3,
            ':t4': temperature4,
            ':t5': temperature5
        },
        ReturnValues="UPDATED_NEW"
    )
    return response


if __name__ == '__main__':
    update_response = update_device(
        "10001", 3, "1612522800", "33.74", "23.74", "25.20", "22.00", "25.00")
    print("Device Updated")
    # Print response
    pprint(update_response)

运行下面的命令来执行脚本update_item.py

python update_item.py

下面是预期的输出。

{'Attributes': {'info': {'info_timestamp': '1612522800',
                         'temperature1': '33.74',
                         'temperature2': '23.74',
                         'temperature3': '25.20',
                         'temperature4': '22.00',
                         'temperature5': '25.00'}},
 'ResponseMetadata': {'HTTPHeaders': {'content-length': '212',
                                      'content-type': 'application/x-amz-json-1.0',
                                      'date': 'Fri, 05 Feb 2021 11:27:43 GMT',
                                      'server': 'Jetty(9.4.18.v20190429)',
                                      'x-amz-crc32': '1118861638',
                                      'x-amzn-requestid': 'a6a8201d-dc10-4837-be6a-7de03ee9b24f'},
                      'HTTPStatusCode': 200,
                      'RequestId': 'a6a8201d-dc10-4837-be6a-7de03ee9b24f',
                      'RetryAttempts': 0}}

删除项目

要删除一个项目,我们使用delete_item 方法。我们可以指定要删除的项目的主键或者提供一个ConditionExpression 。如果我们使用一个ConditionExpression ,除非条件被评估为True,否则该项目将不会被删除。

在这个例子中,我们将为要删除的项目提供一个主键,并提供一个ConditionExpression 。如果满足ConditionExpression ,该项目将被删除。

在这个例子中,条件是。

ConditionExpression="info.info_timestamp >= :val"

我们将删除下面这个项目。

{
  "device_id": "10001",
  "datacount": 3,
  "info": {
    "info_timestamp": "1612522800",
    "temperature1": 33.74,
    "temperature2": 23.74,
    "temperature3": 25.2,
    "temperature4": 22.0,
    "temperature5": 25.0
  }
}

如果info_timestamp 的值大于或等于所提供的值,该项目将被删除。创建一个名为delete_item.py 的脚本,并粘贴下面的代码。

# import Boto3 exceptions and error handling module
from botocore.exceptions import ClientError
from pprint import pprint  # import pprint, a module that enable to “pretty-print”
import boto3  # import Boto3


def delete_device(device_id, datacount, info_timestamp, dynamodb=None):
    dynamodb = boto3.resource(
        'dynamodb', endpoint_url="http://localhost:8000")
    # Specify the table to delete from
    devices_table = dynamodb.Table('Devices')

    try:
        response = devices_table.delete_item(
            Key={
                'device_id': device_id,
                'datacount': datacount
            },
            # Conditional request
            ConditionExpression="info.info_timestamp <= :value",
            ExpressionAttributeValues={
                ":value": info_timestamp
            }
        )
    except ClientError as er:
        if er.response['Error']['Code'] == "ConditionalCheckFailedException":
            print(er.response['Error']['Message'])
        else:
            raise
    else:
        return response


if __name__ == '__main__':
    print("DynamoBD Conditional delete")
    # Provide device_id, datacount, info_timestamp
    delete_response = delete_device("10001", 3, "1712519200")
    if delete_response:
        print("Item Deleted:")
        # Print response
        pprint(delete_response)

运行下面的命令来执行脚本delete_item.py

python delete_item.py

如果不满足ConditionExpression ,预期的响应将如下所示。

Conditional delete
The conditional request failed

如果条件被删除或满足,那么该项目将被成功删除。

查询

查询返回所有符合分区键值的项目。在这个例子中,我们将查询一个特定分区键的所有数据。我们需要指定分区键值。

在这个例子中,分区键是device_id 。我们将查询所有device_id 等于10001的项目。

import boto3  # import Boto3
from boto3.dynamodb.conditions import Key  # import Boto3 conditions


def query_devices(device_id, dynamodb=None):
    dynamodb = boto3.resource(
        'dynamodb', endpoint_url="http://localhost:8000")
    # Specify the table to query
    devices_table = dynamodb.Table('Devices')
    response = devices_table.query(
        KeyConditionExpression=Key('device_id').eq(device_id)
    )
    return response['Items']


if __name__ == '__main__':
    query_id = "10001"
    print(f"Device Data from Device ID: {query_id}")
    devices_data = query_devices(query_id)
    # Print the items returned
    for device_data in devices_data:
        print(device_data['device_id'], ":", device_data['datacount'])

运行下面的命令来执行脚本query.py

python query.py

扫描

扫描操作读取并返回表中的所有项目。方法DynamoDB.Table.scan() 是用来扫描表的。使用filter_expression ,我们可以过滤要返回的项目。

然而,整个表将被扫描,不符合filter_expression 的项目将被丢弃。创建一个名为scan.py 的脚本,并粘贴下面的代码。

import boto3  # import Boto3


def scan_devices(display_devices_data, dynamodb=None):
    dynamodb = boto3.resource(
        'dynamodb', endpoint_url="http://localhost:8000")
    # Specify the table to scan
    devices_table = dynamodb.Table('Devices')
    done = False
    start_key = None
    while not done:
        if start_key:
            scan_kwargs['ExclusiveStartKey'] = start_key
        response = devices_table.scan()
        display_devices_data(response.get('Items', []))
        start_key = response.get('LastEvaluatedKey', None)
        done = start_key is None


if __name__ == '__main__':
    # A method for printing the items
    def print_devices(devices):
        for device in devices:
            print(f"\n{device['device_id']} : {device['datacount']}")
            print(device['info'])

    print(
        f"Scanning all devices data")
    # Print the items returned
    scan_devices(print_devices)

上面的脚本扫描了Devices 表,没有filter_expression 。运行下面的命令来执行脚本scan.py。输出将是Devices 表中的所有项目。

python scan.py

删除表

要删除一个表,我们使用方法DynamoDB.Table.delete() 。我们所需要的是指定表名。这个动作很少被执行。创建一个名为delete_table.py 的脚本,并添加下面的代码。

import boto3  # import Boto3


def delete_devices_table(dynamodb=None):
    dynamodb = boto3.resource(
        'dynamodb', endpoint_url="http://localhost:8000")
    # specify the table to be deleted
    devices_table = dynamodb.Table('Devices')
    devices_table.delete()


if __name__ == '__main__':
    delete_devices_table()
    print("Table deleted.")

运行下面的命令来执行该脚本delete_table.py

python delete_table.py

总结

我们已经学会了如何使用AWS SDK for Python、Boto3来编写与AWS DynamoDB交互的python脚本。