|
| 1 | +from collections import defaultdict |
1 | 2 | from ldclient.util import log |
2 | 3 | from ldclient.interfaces import FeatureStore |
3 | 4 | from ldclient.rwlock import ReadWriteLock |
4 | 5 |
|
5 | 6 |
|
6 | 7 | class InMemoryFeatureStore(FeatureStore): |
| 8 | + """ |
| 9 | + In-memory implementation of a store that holds feature flags and related data received from the streaming API. |
| 10 | + """ |
7 | 11 |
|
8 | 12 | def __init__(self): |
9 | 13 | self._lock = ReadWriteLock() |
10 | 14 | self._initialized = False |
11 | | - self._features = {} |
| 15 | + self._items = defaultdict(dict) |
12 | 16 |
|
13 | | - def get(self, key, callback): |
| 17 | + def get(self, kind, key, callback): |
14 | 18 | try: |
15 | 19 | self._lock.rlock() |
16 | | - f = self._features.get(key) |
17 | | - if f is None: |
18 | | - log.debug("Attempted to get missing feature: " + str(key) + " Returning None") |
| 20 | + itemsOfKind = self._items[kind] |
| 21 | + item = itemsOfKind.get(key) |
| 22 | + if item is None: |
| 23 | + log.debug("Attempted to get missing key %s in '%s', returning None", key, kind.namespace) |
19 | 24 | return callback(None) |
20 | | - if 'deleted' in f and f['deleted']: |
21 | | - log.debug("Attempted to get deleted feature: " + str(key) + " Returning None") |
| 25 | + if 'deleted' in item and item['deleted']: |
| 26 | + log.debug("Attempted to get deleted key %s in '%s', returning None", key, kind.namespace) |
22 | 27 | return callback(None) |
23 | | - return callback(f) |
| 28 | + return callback(item) |
24 | 29 | finally: |
25 | 30 | self._lock.runlock() |
26 | 31 |
|
27 | | - def all(self, callback): |
| 32 | + def all(self, kind, callback): |
28 | 33 | try: |
29 | 34 | self._lock.rlock() |
30 | | - return callback(dict((k, f) for k, f in self._features.items() if ('deleted' not in f) or not f['deleted'])) |
| 35 | + itemsOfKind = self._items[kind] |
| 36 | + return callback(dict((k, i) for k, i in itemsOfKind.items() if ('deleted' not in i) or not i['deleted'])) |
31 | 37 | finally: |
32 | 38 | self._lock.runlock() |
33 | 39 |
|
34 | | - def init(self, features): |
| 40 | + def init(self, all_data): |
35 | 41 | try: |
36 | | - self._lock.lock() |
37 | | - self._features = dict(features) |
| 42 | + self._lock.rlock() |
| 43 | + self._items.clear() |
| 44 | + self._items.update(all_data) |
38 | 45 | self._initialized = True |
39 | | - log.debug("Initialized feature store with " + str(len(features)) + " features") |
| 46 | + for k in all_data: |
| 47 | + log.debug("Initialized '%s' store with %d items", k.namespace, len(all_data[k])) |
40 | 48 | finally: |
41 | | - self._lock.unlock() |
| 49 | + self._lock.runlock() |
42 | 50 |
|
43 | 51 | # noinspection PyShadowingNames |
44 | | - def delete(self, key, version): |
| 52 | + def delete(self, kind, key, version): |
45 | 53 | try: |
46 | | - self._lock.lock() |
47 | | - f = self._features.get(key) |
48 | | - if f is not None and f['version'] < version: |
49 | | - f['deleted'] = True |
50 | | - f['version'] = version |
51 | | - elif f is None: |
52 | | - f = {'deleted': True, 'version': version} |
53 | | - self._features[key] = f |
| 54 | + self._lock.rlock() |
| 55 | + itemsOfKind = self._items[kind] |
| 56 | + i = itemsOfKind.get(key) |
| 57 | + if i is None or i['version'] < version: |
| 58 | + i = {'deleted': True, 'version': version} |
| 59 | + itemsOfKind[key] = i |
54 | 60 | finally: |
55 | | - self._lock.unlock() |
| 61 | + self._lock.runlock() |
56 | 62 |
|
57 | | - def upsert(self, key, feature): |
| 63 | + def upsert(self, kind, item): |
| 64 | + key = item['key'] |
58 | 65 | try: |
59 | | - self._lock.lock() |
60 | | - f = self._features.get(key) |
61 | | - if f is None or f['version'] < feature['version']: |
62 | | - self._features[key] = feature |
63 | | - log.debug("Updated feature {0} to version {1}".format(key, feature['version'])) |
| 66 | + self._lock.rlock() |
| 67 | + itemsOfKind = self._items[kind] |
| 68 | + i = itemsOfKind.get(key) |
| 69 | + if i is None or i['version'] < item['version']: |
| 70 | + itemsOfKind[key] = item |
| 71 | + log.debug("Updated %s in '%s' to version %d", key, kind.namespace, item['version']) |
64 | 72 | finally: |
65 | | - self._lock.unlock() |
| 73 | + self._lock.runlock() |
66 | 74 |
|
67 | 75 | @property |
68 | 76 | def initialized(self): |
|
0 commit comments