Skip to content

Commit

Permalink
Merge branch 'release/0.1.4'
Browse files Browse the repository at this point in the history
  • Loading branch information
romaryd committed May 22, 2018
2 parents f202a06 + 4e98705 commit 5128389
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 21 deletions.
2 changes: 1 addition & 1 deletion jsonrepo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
Copyright (C) 2017 Romary Dupuis
"""

__version__ = '0.1.3'
__version__ = '0.1.4'
__all__ = ['repository', 'mixin', 'record']
6 changes: 5 additions & 1 deletion jsonrepo/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@

class Backend(object):
""" Basic backend class """
def __init__(self, prefix):
def __init__(self, prefix, secondary_indexes):
self._prefix = prefix
self._secondary_indexes = secondary_indexes

def prefixed(self, key):
""" build a prefixed key """
Expand All @@ -26,3 +27,6 @@ def delete(self, key, sort_key):

def history(self, key, _from='-', _to='+', _desc=True):
raise NotImplementedError

def find(self, index, value):
raise NotImplementedError
27 changes: 25 additions & 2 deletions jsonrepo/backends/dynamodb.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
import boto3
from boto3.dynamodb.conditions import Key
from loggingmixin import LoggingMixin
Expand All @@ -9,10 +10,13 @@ class DynamoDBBackend(Backend, LoggingMixin):
"""
Backend based on DynamoDB
"""
def __init__(self, prefix, key, sort_key):

def __init__(self, prefix, key, sort_key,
secondary_indexes):
self._prefix = prefix
self._key = key
self._sort_key = sort_key
self._secondary_indexes = secondary_indexes

@memoized
def dynamodb_server(self):
Expand All @@ -23,6 +27,7 @@ def get(self, key, sort_key):
self.logger.debug('Storage - get {}'.format(self.prefixed(key)))
res = self.dynamodb_server.get_item(Key={
self._key: self.prefixed(key),
self._sort_key: sort_key
})
if 'Item' in res and 'value' in res['Item']:
return res['Item']['value']
Expand All @@ -35,6 +40,12 @@ def set(self, key, sort_key, value):
self._key: self.prefixed(key),
'value': value
}
obj = json.loads(value)
for index in self._secondary_indexes:
if obj.get(index, None) not in ['', None]:
item.update({
index: obj[index]
})
if sort_key is not None:
item.update({
self._sort_key: sort_key
Expand All @@ -44,7 +55,8 @@ def set(self, key, sort_key, value):
def delete(self, key, sort_key):
self.logger.debug('Storage - delete {}'.format(self.prefixed(key)))
return self.dynamodb_server.delete_item(Key={
self._key: self.prefixed(key)
self._key: self.prefixed(key),
self._sort_key: sort_key
})

def history(self, key, _from='-', _to='+', _desc=True):
Expand Down Expand Up @@ -83,3 +95,14 @@ def latest(self, key):
return response['Items'][0]['value']
else:
return None

def find(self, index, value):
res = self.dynamodb_server.query(
KeyConditionExpression=Key(index).eq(value),
IndexName='{}-index'.format(index)
)
self.logger.debug('{}'.format(res))
return {
'count': res['Count'],
'items': [item['value'] for item in res['Items']]
}
47 changes: 46 additions & 1 deletion jsonrepo/backends/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Author: Romary Dupuis <romary@me.com>
Copyright (C) 2017 Romary Dupuis
"""
import json
from loggingmixin import LoggingMixin
from awesomedecorators import memoized
from jsonrepo.backend import Backend
Expand All @@ -28,10 +29,19 @@ def get(self, key, sort_key):
return None
return self.cache[key]

def init_secondary_indexes(self):
if 'secondary_indexes' not in self.cache:
self.cache['secondary_indexes'] = {}

def init_secondary_index(self, index):
if 'secondary_indexes' not in self.cache:
return
if index not in self.cache['secondary_indexes']:
self.cache['secondary_indexes'][index] = {}

def set(self, key, sort_key, value):
primary_key = key
key = self.prefixed('{}:{}'.format(key, sort_key))
""" Set an element in dictionary """
self.logger.debug('Storage - set value {} for {}'.format(value, key))
if (self.prefixed(primary_key) not in self.cache.keys() and
sort_key is not None):
Expand All @@ -40,6 +50,23 @@ def set(self, key, sort_key, value):
self.cache[self.prefixed(primary_key)].append(sort_key)
self.cache[self.prefixed(primary_key)] = sorted(
self.cache[self.prefixed(primary_key)])
p_value = {}
if key in self.cache.keys():
p_value = json.loads(self.cache[key])
for index in self._secondary_indexes:
if index in p_value.keys():
self.cache['secondary_indexes'][index][p_value[index]].remove(
key)
obj = json.loads(value)
if index in obj.keys():
self.init_secondary_indexes()
self.init_secondary_index(index)
if obj[index] not in self.cache['secondary_indexes'][index]:
self.cache['secondary_indexes'][index][obj[index]] = [
key]
else:
self.cache['secondary_indexes'][index][obj[index]].append(
key)
self.cache[key] = value
return self.cache[key] is value

Expand All @@ -50,7 +77,13 @@ def delete(self, key, sort_key):
self.logger.debug('Storage - delete {}'.format(key))
if sort_key is not None:
self.cache[self.prefixed(primary_key)].remove(sort_key)
for index in self._secondary_indexes:
obj = json.loads(self.cache[key])
if index in obj.keys():
self.cache['secondary_indexes'][index][obj[index]].remove(
key)
del(self.cache[key])
return True

def history(self, key, _from='-', _to='+', _desc=True):
if _from == '-':
Expand All @@ -76,3 +109,15 @@ def latest(self, key):
if len(self.cache[self.prefixed(key)]) == 0:
return None
return self.get(key, self.cache[self.prefixed(key)][-1])

def find(self, index, value):
if ('secondary_indexes' in self.cache and
index in self.cache['secondary_indexes'] and
value in self.cache['secondary_indexes'][index]):
res = [self.cache[item]
for item in self.cache['secondary_indexes'][index][value]]
return {
'count': len(res),
'items': res
}
return {'count': 0, 'items': []}
49 changes: 49 additions & 0 deletions jsonrepo/backends/redis.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
Copyright (C) 2017 Romary Dupuis
"""
import os
import json
import redis
from loggingmixin import LoggingMixin
from awesomedecorators import memoized
Expand Down Expand Up @@ -46,6 +47,27 @@ def set(self, key, sort_key, value):
)))
if sort_key is not None:
self.redis_server.zadd(self.prefixed(key), 0.0, sort_key)
prev_value = self.get(key, sort_key)
prev_obj = None
if prev_value is not None:
prev_obj = json.loads(prev_value)
for sec_index in self._secondary_indexes:
if (prev_obj is not None and
sec_index in prev_obj.keys()):
self.redis_server.srem(
self.prefixed('secondary_indexes:{}:{}'.format(
sec_index, prev_obj[sec_index]
)),
self.prefixed('{}:{}'.format(key, sort_key))
)
obj = json.loads(value)
if sec_index in obj.keys():
self.redis_server.sadd(
self.prefixed('secondary_indexes:{}:{}'.format(
sec_index, obj[sec_index]
)),
self.prefixed('{}:{}'.format(key, sort_key))
)
return self.redis_server.set(self.prefixed(
'{}:{}'.format(key, sort_key)), value)

Expand All @@ -54,6 +76,19 @@ def delete(self, key, sort_key):
'{}:{}'.format(key, sort_key))))
if sort_key is not None:
self.redis_server.zrem(self.prefixed(key), sort_key)
prev_value = self.get(key, sort_key)
prev_obj = None
if prev_value is not None:
prev_obj = json.loads(prev_value)
for sec_index in self._secondary_indexes:
if (prev_obj is not None and
sec_index in prev_obj.keys()):
self.redis_server.srem(
self.prefixed('secondary_indexes:{}:{}'.format(
sec_index, prev_obj[sec_index]
)),
self.prefixed('{}:{}'.format(key, sort_key))
)
return self.redis_server.delete(self.prefixed(
'{}:{}'.format(key, sort_key)))

Expand Down Expand Up @@ -87,3 +122,17 @@ def latest(self, key):

def transaction(self, func, *watchs, **params):
return self.redis_server.transaction(func, *watchs, **params)

def find(self, index, value):
keys = self.redis_server.smembers(
self.prefixed('secondary_indexes:{}:{}'.format(
index, value
))
)
if keys is not None:
return {
'count': len(keys),
'items': [self.redis_server.get(key.decode('utf-8'))
for key in keys]
}
return {'count': 0, 'items': []}
7 changes: 4 additions & 3 deletions jsonrepo/mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ def storage(self):
Instantiates and returns a storage instance
"""
if self.backend == 'redis':
return RedisBackend(self.prefix)
return RedisBackend(self.prefix, self.secondary_indexes)
if self.backend == 'dynamodb':
return DynamoDBBackend(self.prefix, self.key, self.sort_key)
return DictBackend(self.prefix)
return DynamoDBBackend(self.prefix, self.key, self.sort_key,
self.secondary_indexes)
return DictBackend(self.prefix, self.secondary_indexes)
1 change: 1 addition & 0 deletions jsonrepo/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class Record(object):
"""
Definition of a JSON serializable record for a repository
"""
@classmethod
def from_json(cls, json_dump):
"""
JSON deserialization
Expand Down
18 changes: 18 additions & 0 deletions jsonrepo/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class Repository(StorageMixin, LoggingMixin):
klass = Record
key = ''
sort_key = ''
secondary_indexes = []

def storage_get(self, key, sort_key):
return self.storage.get(key, sort_key)
Expand All @@ -42,6 +43,12 @@ def save(self, key, sort_key, _object):
"""
return self.storage.set(key, sort_key, _object.to_json())

def delete(self, key, sort_key):
"""
Saves a context object
"""
return self.storage.delete(key, sort_key)

def history(self, key, _from='-', _to='+', _desc=True):
"""
Retrives a list of records according to a datetime range
Expand All @@ -54,3 +61,14 @@ def latest(self, key):
Get the most recent record for a specific key
"""
return self.klass.from_json(self.storage.latest(key))

def find(self, index, value):
"""
Find record according to the value of a secondary index
"""
res = self.storage.find(index, value)
return {
'count': res['count'],
'items': [self.klass.from_json(_object)
for _object in res['items']]
}

0 comments on commit 5128389

Please sign in to comment.