How to update an item in dynamodb where the value is in a deeper level?

Viewed 24

I have the a table in DynamoDB called "environments_protection" and it looks like so:

{
  "environment_name": {
   "S": "qa-devops-1"
  },
  "asg_c": {
   "M": {
    "desired": {
     "N": "4"
    },
    "max": {
     "N": "4"
    },
    "min": {
     "N": "1"
    },
    "context": {
     "S": "test-context"
    }
   }
  },
  "cp_sp_svc": {
   "M": {
    "desired": {
     "N": "2"
    },
    "context": {
     "S": "test-context"
    }
   }
  },
  "issuer": {
   "S": "itai"
  },
  "protected": {
   "BOOL": false
  },
  "services_task_count": {
   "L": [
    {
     "M": {
      "desired": {
       "N": "3"
      },
      "name": {
       "S": "service1"
      },
      "context": {
       "S": "test-context"
      }
     }
    },
    {
      "M": {
       "desired": {
        "N": "1"
       },
       "name": {
        "S": "service2"
       },
       "context": {
        "S": "test-context"
       }
      }
     },
     {
      "M": {
       "desired": {
        "N": "2"
       },
       "name": {
        "S": "service3"
       },
       "context": {
        "S": "test-context"
       }
      }
     }
   ]
  }
 }

I'm trying to update service1 "desired" value to 5 using boto3.

Here's the relevant part of my code:

tableName = 'environments_protection'
response = table.get_item(Key={'environment_name': 'qa-devops-1'})
item = response['Item']
for elem in item['services_task_count']:
    name = elem['name']
    desired = elem['desired']
    print(f"service name: {name}, desired: {desired}")
    table.update_item(
        Key={'environment_name': 'qa-devops-1'},
        UpdateExpression=f"set desired = :x",
        ExpressionAttributeValues={
            ':x': '5',
        },
        ReturnValues="UPDATED_NEW"
    )

I know i'm missing the part that aims to change only service1 but I didn't find a way to do it.

I've followed the answers in this SO question but couldn't get the update to work.

How do I change my update_item function so it updates the right value?

2 Answers

Here is great primer on how to access document paths in DynamoDB.

I tested this code successfully with your details above.

table.update_item(
    Key={'environment_name': 'qa-devops-1'},
    UpdateExpression='SET services_task_count[1].desired = :servicesVal'),
    ExpressionAttributeValues={
        ":servicesVal": 4
    },
    ReturnValues='UPDATED_NEW',
)

Edit: While it wasn't asked, you might want to know how to find the correct index in order to update the value. This is easily done with Python

search_item = "service2"
index_to_update = None
for index, service_task in enumerate(item["services_task_count"]):
    if service_task["name"] == search_item:
        index_to_update = index

table.update_item(
    Key={'environment_name': 'qa-devops-1'},
    UpdateExpression='SET services_task_count[{}].desired = :servicesVal'.format(index_to_update),
    ExpressionAttributeValues={
        ":servicesVal": 4
    },
    ReturnValues='UPDATED_NEW',
)

To answer your immediate question, here is how you would do it:

tableName = 'test'
table = boto3.resource('dynamodb').Table(tableName)

try:
    table.update_item(
                Key={'environment_name': 'qa-devops-1'},
                UpdateExpression="set #y[0].desired = :x",
                ExpressionAttributeValues={
                    ':x': 5,
                },
                ExpressionAttributeNames={
                    '#y': 'services_task_count',
                },
                ReturnValues="UPDATED_NEW"
            )
except Exception as e:
    print(e)

However, as you can see this requires me to know what position service1 is in the array. I prefer to use maps to better achieve this:

{
 "pk": {
  "S": "qa-devops-2"
 },
 "sk": {
  "S": "lhnng"
 },
 "asg_c": {
  "M": {
   "context": {
    "S": "test-context"
   },
   "desired": {
    "N": "4"
   },
   "max": {
    "N": "4"
   },
   "min": {
    "N": "1"
   }
  }
 },
 "cp_sp_svc": {
  "M": {
   "context": {
    "S": "test-context"
   },
   "desired": {
    "N": "2"
   }
  }
 },
 "issuer": {
  "S": "itai"
 },
 "protected": {
  "BOOL": false
 },
 "services_task_count": {
  "M": {
   "service1": {
    "M": {
     "context": {
      "S": "test-context"
     },
     "desired": {
      "S": "5"
     }
    }
   },
   "service2": {
    "M": {
     "context": {
      "S": "test-context"
     },
     "desired": {
      "N": "2"
     }
    }
   },
   "service3": {
    "M": {
     "context": {
      "S": "test-context"
     },
     "desired": {
      "N": "2"
     }
    }
   }
  }
 }
}

Now I no longer need to know the position of service1 in my list as service1 is now the key in my map, and I can update it blindly:

try:
    table.update_item(
                Key={'environment_name': 'qa-devops-1',},
                UpdateExpression="set #y.#z.desired = :x",
                ExpressionAttributeValues={
                    ':x': 10,
                },
                ExpressionAttributeNames={
                    '#y': 'services_task_count',
                    '#z': 'service1'
                },
                ReturnValues="UPDATED_NEW"
            )
except Exception as e:
    print(e)
Related