Finalize implementation of DynamoDB Streams
This commit is contained in:
parent
519899f74f
commit
0f6086f708
5 changed files with 166 additions and 33 deletions
|
|
@ -1,10 +1,11 @@
|
|||
from __future__ import unicode_literals
|
||||
from collections import defaultdict
|
||||
from collections import defaultdict, namedtuple
|
||||
import copy
|
||||
import datetime
|
||||
import decimal
|
||||
import json
|
||||
import re
|
||||
import uuid
|
||||
|
||||
import boto3
|
||||
from moto.compat import OrderedDict
|
||||
|
|
@ -292,6 +293,44 @@ class Item(BaseModel):
|
|||
'ADD not supported for %s' % ', '.join(update_action['Value'].keys()))
|
||||
|
||||
|
||||
class StreamRecord(BaseModel):
|
||||
def __init__(self, table, stream_type, event_name, old, new, seq):
|
||||
old_a = old.to_json()['Attributes'] if old is not None else {}
|
||||
new_a = new.to_json()['Attributes'] if new is not None else {}
|
||||
|
||||
rec = old if old is not None else new
|
||||
keys = {table.hash_key_attr: rec.hash_key.to_json()}
|
||||
if table.range_key_attr is not None:
|
||||
keys[table.range_key_attr] = rec.range_key.to_json()
|
||||
|
||||
self.record = {
|
||||
'eventID': uuid.uuid4().hex,
|
||||
'eventName': event_name,
|
||||
'eventSource': 'aws:dynamodb',
|
||||
'eventVersion': '1.0',
|
||||
'awsRegion': 'us-east-1',
|
||||
'dynamodb': {
|
||||
'StreamViewType': stream_type,
|
||||
'ApproximateCreationDateTime': datetime.datetime.utcnow().isoformat(),
|
||||
'SequenceNumber': seq,
|
||||
'SizeBytes': 1,
|
||||
'Keys': keys
|
||||
}
|
||||
}
|
||||
|
||||
if stream_type in ('NEW_IMAGE', 'NEW_AND_OLD_IMAGES'):
|
||||
self.record['dynamodb']['NewImage'] = new_a
|
||||
if stream_type in ('OLD_IMAGE', 'NEW_AND_OLD_IMAGES'):
|
||||
self.record['dynamodb']['OldImage'] = old_a
|
||||
|
||||
# This is a substantial overestimate but it's the easiest to do now
|
||||
self.record['dynamodb']['SizeBytes'] = len(
|
||||
json.dumps(self.record['dynamodb']))
|
||||
|
||||
def to_json(self):
|
||||
return self.record
|
||||
|
||||
|
||||
class StreamShard(BaseModel):
|
||||
def __init__(self, table):
|
||||
self.table = table
|
||||
|
|
@ -310,15 +349,22 @@ class StreamShard(BaseModel):
|
|||
|
||||
def add(self, old, new):
|
||||
t = self.table.stream_specification['StreamViewType']
|
||||
if t == 'KEYS_ONLY':
|
||||
self.items.append(new.key)
|
||||
elif t == 'NEW_IMAGE':
|
||||
self.items.append(new)
|
||||
elif t == 'OLD_IMAGE':
|
||||
self.items.append(old)
|
||||
elif t == 'NEW_AND_OLD_IMAGES':
|
||||
self.items.append((old, new))
|
||||
|
||||
if old is None:
|
||||
event_name = 'INSERT'
|
||||
elif new is None:
|
||||
event_name = 'DELETE'
|
||||
else:
|
||||
event_name = 'MODIFY'
|
||||
seq = len(self.items) + self.starting_sequence_number
|
||||
self.items.append(
|
||||
StreamRecord(self.table, t, event_name, old, new, seq))
|
||||
|
||||
def get(self, start, quantity):
|
||||
start -= self.starting_sequence_number
|
||||
assert start >= 0
|
||||
end = start + quantity
|
||||
return [i.to_json() for i in self.items[start:end]]
|
||||
|
||||
|
||||
class Table(BaseModel):
|
||||
|
||||
|
|
@ -428,22 +474,22 @@ class Table(BaseModel):
|
|||
else:
|
||||
range_value = None
|
||||
|
||||
if expected is None:
|
||||
expected = {}
|
||||
lookup_range_value = range_value
|
||||
else:
|
||||
expected_range_value = expected.get(
|
||||
self.range_key_attr, {}).get("Value")
|
||||
if(expected_range_value is None):
|
||||
lookup_range_value = range_value
|
||||
else:
|
||||
lookup_range_value = DynamoType(expected_range_value)
|
||||
current = self.get_item(hash_value, lookup_range_value)
|
||||
|
||||
item = Item(hash_value, self.hash_key_type, range_value,
|
||||
self.range_key_type, item_attrs)
|
||||
|
||||
if not overwrite:
|
||||
if expected is None:
|
||||
expected = {}
|
||||
lookup_range_value = range_value
|
||||
else:
|
||||
expected_range_value = expected.get(
|
||||
self.range_key_attr, {}).get("Value")
|
||||
if(expected_range_value is None):
|
||||
lookup_range_value = range_value
|
||||
else:
|
||||
lookup_range_value = DynamoType(expected_range_value)
|
||||
|
||||
if current is None:
|
||||
current_attr = {}
|
||||
elif hasattr(current, 'attrs'):
|
||||
|
|
@ -508,9 +554,14 @@ class Table(BaseModel):
|
|||
def delete_item(self, hash_key, range_key):
|
||||
try:
|
||||
if range_key:
|
||||
return self.items[hash_key].pop(range_key)
|
||||
item = self.items[hash_key].pop(range_key)
|
||||
else:
|
||||
return self.items.pop(hash_key)
|
||||
item = self.items.pop(hash_key)
|
||||
|
||||
if self.stream_shard is not None:
|
||||
self.stream_shard.add(item, None)
|
||||
|
||||
return item
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue