def test_ExpressionAttributeNames_appends_references(): mock_request = immutable(attributes=immutable({ 'keys': [], 'values': [], 'conditions': [] }), conditions=immutable({'references': ['state']})) result = args.ExpressionAttributeNames(mock_request) assert result == {'#state': 'state'}
def test_AttributeDefinitions_does_not_duplicate_keys(): mock_request = immutable( hash_key={ 'name': 'id', 'type': 'S' }, range_key={ 'name': 'contry', 'type': 'S' }, lsi=[dict(name='gs_index', range_key={ 'name': 'type', 'type': 'S' })], gsi=[ dict(name='gs_index', hash_key={ 'name': 'country', 'type': 'S' }, range_key={ 'name': 'status', 'type': 'S' }) ]) result = [ item.get('AttributeName') for item in args.AttributeDefinitions(mock_request) ] assert len([1 for key in result if key == 'country']) == 1
def test_LocalSecondaryIndexes_result(): mock_request = immutable( hash_key={ 'name': 'main_hash_key', 'type': 'S' }, lsi=[dict( name='gs_index', range_key={ 'name': 'type', 'type': 'S' }, )]) expected = [{ 'IndexName': 'gs_index', 'KeySchema': [{ 'AttributeName': 'main_hash_key', 'KeyType': 'HASH' }, { 'AttributeName': 'type', 'KeyType': 'RANGE' }], 'Projection': { 'ProjectionType': 'ALL' } }] result = args.LocalSecondaryIndexes(mock_request) assertObjectsEqual(result, expected)
def Condition(expression, attributes, references=None): references = references or [] return immutable({ 'expression': expression, 'attributes': attributes, 'references': references })
def test_update_handles_deep_objects(): original = immutable({'alpha': {'color': 23}, 'beta': 2, 'dalet': 4}) result = update(original, alpha={'color': 10}) assert original is not result assert result.get('alpha').get('color') == 10
def test_update_returns_with_updates(): original = immutable({'alpha': 1, 'beta': 2, 'dalet': 4}) result = update(original, alpha=10) assert original is not result assert result.get('alpha') == 10
def test_update_returns_new_instance(): original = immutable({'alpha': 1, 'beta': 2, 'dalet': 4}) result = update(original) assert original == result assert original is not result
def test_db_passes_args(): mock_client = {} mock_runner = lambda r, x: r mock_operation = immutable(name='mock', description={}, runner=mock_runner) db = dynofunc.db(mock_client) db(mock_operation)
def test_condition_composition(): cond = cand( attr('username').equals('sunshie'), cor(cand(attr('rank').gt(12), attr('rank').lt(20)), cand(attr('kills').gt_or_eq(100), attr('kills').lt_or_eq(1000)))) mock_attributes = [ immutable({ 'original': 'rank', 'key': ':rank', 'value': { "N": 12 }, 'alias': 'rank', 'func': None }), immutable({ 'original': 'kills', 'key': ':kills', 'value': { "N": 300 }, 'alias': 'kills', 'func': None }), immutable({ 'original': 'username', 'key': ':username', 'value': { "S": "sunshie" }, 'alias': 'username', 'func': None }) ] expected = '(username = :username) AND (((rank > :rank) AND (rank < :rank)) OR ((kills >= :kills) AND (kills <= :kills)))' result = cond.expression(mock_attributes) assert result == expected
def Attr(equals, gt, lt, lt_or_eq, gt_or_eq, between, begins_with): return immutable({ 'equals': equals, 'gt': gt, 'lt': lt, 'lt_or_eq': lt_or_eq, 'gt_or_eq': gt_or_eq, 'between': between, 'begins_with': begins_with })
def RequestTree(attributes, table_name, index_name, hash_key, range_key, conditions, gsi, lsi): return immutable({ 'attributes': attributes, 'table_name': table_name, 'index_name': index_name, 'hash_key': hash_key, 'range_key': range_key, 'conditions': conditions, 'gsi': gsi, 'lsi': lsi })
def test_immutable_raises_when_set(): obj = immutable(x=2, y=23) with pytest.raises(TypeError): obj['x'] = 5 with pytest.raises(TypeError): obj.x = 5 with pytest.raises(TypeError): del obj['x'] with pytest.raises(TypeError): del obj.x
def table(db, table_name): def wrap_op(op): def call_op(*args, **kwargs): return db(op(table_name, *args, **kwargs)) return call_op return immutable( add=wrap_op(add), create=wrap_op(create), delete=wrap_op(delete), describe=wrap_op(describe), find=wrap_op(find), query=wrap_op(query), update=wrap_op(update), scan=wrap_op(scan))
def ExpressionAttributeNames(request: RequestTree): all_attributes = [ *request.attributes.keys, *request.attributes.values, *request.attributes.conditions ] aliased_attributes = [attr for attr in all_attributes if attr.alias[0] == '#'] if request.conditions is not None and request.conditions.references is not None: for ref in request.conditions.references: aliased_attributes.append(immutable({ 'alias': get_safe_alias(ref), 'original': ref })) return { attr.alias: attr.original for attr in aliased_attributes }
def test_condition_begins_with(): cond = attr('state').begins_with('Ca') mock_attributes = [ immutable({ 'original': 'state', 'key': ':state', 'value': { "S": 'California' }, 'alias': 'state', 'func': None }) ] result = cond.expression(mock_attributes) assert result == 'begins_with(state, :state)'
def test_attribute_equals_condition(): cond = attr('username').equals('sunshie') mock_attributes = [ immutable({ 'original': 'username', 'key': ':username', 'value': { "S": "sunshie" }, 'alias': 'username', 'func': None }) ] result = cond.expression(mock_attributes) # username = :username # { ":username": { "S": "sunshie" } } assert result == 'username = :username'
def Attribute(original, key, value, alias, func): """ Parameters ---------- original : str The orignial name the client passed us for the attribute. Should never change for later references Ex. { 'item': 'a' } => 'item' key : str The key we should use in expressions to refer to the value of this attribute Ex. { 'item': 'a' } => ':item' value : dict The typed value of the attribute. NOTE: The attribute name should be stripped out so only the object defining the value/type is stored here. Different operations will use this and some will need to set a custom key so here were only storing the value of the value/type tree Ex. { 'item': 'a' } => { 'S': 'a' } alias : str The name we should use when refering to the dynamodb _column_. Typically this will be the same as `original`. However, in the case of reserved attr names it will be updated to something dynamodb friendly Ex. { 'item': 'a' } => '#item' func : Function A func from `dynofunc.funcs` that should be called to modify the attr """ return immutable({ 'original': original, 'key': key, 'value': value, 'alias': alias, 'func': func })
def test_KeySchema_uses_hash_and_range(): mock_request = immutable(hash_key={ 'name': 'id', 'type': 'S' }, range_key={ 'name': 'type', 'type': 'S' }) expected = [{ 'AttributeName': 'id', 'KeyType': 'HASH' }, { 'AttributeName': 'type', 'KeyType': 'RANGE' }] result = args.KeySchema(mock_request) assertObjectsEqual(result, expected)
def test_AttributeDefinitions_gets_all_keys(): mock_request = immutable( hash_key={ 'name': 'id', 'type': 'S' }, range_key={ 'name': 'country', 'type': 'S' }, lsi=[dict( name='gs_index', range_key={ 'name': 'type', 'type': 'S' }, )], gsi=[ dict(name='gs_index', hash_key={ 'name': 'username', 'type': 'S' }, range_key={ 'name': 'status', 'type': 'S' }) ]) expected = ['id', 'type', 'username', 'status', 'country'] result = [ item.get('AttributeName') for item in args.AttributeDefinitions(mock_request) ] assert set(expected) == set(result)
def test_GlobalSecondaryIndexes_result(): mock_request = immutable(gsi=[ dict( name='gs_index', hash_key={ 'name': 'other', 'type': 'S' }, range_key={ 'name': 'type', 'type': 'S' }, ) ]) expected = [{ 'IndexName': 'gs_index', 'KeySchema': [{ 'AttributeName': 'other', 'KeyType': 'HASH' }, { 'AttributeName': 'type', 'KeyType': 'RANGE' }], 'Projection': { 'ProjectionType': 'ALL' }, 'ProvisionedThroughput': { 'ReadCapacityUnits': 10, 'WriteCapacityUnits': 10 } }] result = args.GlobalSecondaryIndexes(mock_request) assertObjectsEqual(result, expected)
def response(res): def get_items(): items = res.get('Items', None) if items is not None and len(items) > 0: return [destructure_type_tree(item) for item in items] return None return immutable({ 'retries': lambda: res.get('ResponseMetadata', {}).get('RetryAttempts', None), 'success': lambda: res.get('ResponseMetadata', {}).get('HTTPStatusCode', 0) == 200, 'item': lambda: destructure_type_tree(res.get('Item', None)), 'items': get_items, 'count': lambda: res.get('Count', None), 'scanned_count': lambda: res.get('ScannedCount', None), 'raw': lambda: res })
def Operation(description, runner): return immutable({'description': description, 'runner': runner})
def test_immutable_none_comparison(): obj = immutable(x=2, y=23) isEqual = obj == None assert isEqual is not True
def test_immutable_repr_dumps_json(): obj = immutable(x=2, y=23) r = repr(obj) json.loads(r)
def test_immutable_str_dumps_json(): obj = immutable(x=2, y=23) s = str(obj) json.loads(s)
def Function(expression, value): return immutable({'expression': expression, 'value': value})
def AttributeGroup(keys, values, conditions): return immutable({ 'keys': keys, 'values': values, 'conditions': conditions })