-
Notifications
You must be signed in to change notification settings - Fork 3.9k
/
split_mongo_kvs.py
193 lines (161 loc) · 7.96 KB
/
split_mongo_kvs.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# lint-amnesty, pylint: disable=missing-module-docstring
import copy
from collections import namedtuple
from xblock.core import XBlockAside
from xblock.exceptions import InvalidScopeError
from xblock.fields import Scope
from xmodule.modulestore.inheritance import InheritanceKeyValueStore
from .definition_lazy_loader import DefinitionLazyLoader
# id is a BlockUsageLocator, def_id is the definition's guid
SplitMongoKVSid = namedtuple('SplitMongoKVSid', 'id, def_id')
class SplitMongoKVS(InheritanceKeyValueStore):
"""
A KeyValueStore that maps keyed data access to one of the 3 data areas
known to the MongoModuleStore (data, children, and metadata)
"""
VALID_SCOPES = (Scope.parent, Scope.children, Scope.settings, Scope.content)
def __init__(self, definition, initial_values, default_values, parent, aside_fields=None, field_decorator=None):
"""
:param definition: either a lazyloader or definition id for the definition
:param initial_values: a dictionary of the locally set values
:param default_values: any Scope.settings field defaults that are set locally
(copied from a template block with copy_from_template)
"""
# deepcopy so that manipulations of fields does not pollute the source
super().__init__(copy.deepcopy(initial_values))
self._definition = definition # either a DefinitionLazyLoader or the db id of the definition.
# if the db id, then the definition is presumed to be loaded into _fields
self._defaults = default_values
# a decorator function for field values (to be called when a field is accessed)
if field_decorator is None:
self.field_decorator = lambda x: x
else:
self.field_decorator = field_decorator
self.parent = parent
self.aside_fields = aside_fields if aside_fields else {}
def get(self, key):
if key.block_family == XBlockAside.entry_point: # lint-amnesty, pylint: disable=no-else-raise
if key.scope not in self.VALID_SCOPES:
raise InvalidScopeError(key, self.VALID_SCOPES)
if key.block_scope_id.block_type not in self.aside_fields:
# load the definition to see if it has the aside_fields
self._load_definition()
if key.block_scope_id.block_type not in self.aside_fields:
raise KeyError()
aside_fields = self.aside_fields[key.block_scope_id.block_type]
# load the field, if needed
if key.field_name not in aside_fields:
self._load_definition()
if key.field_name in aside_fields:
return self.field_decorator(aside_fields[key.field_name])
raise KeyError()
else:
# load the field, if needed
if key.field_name not in self._fields:
if key.scope == Scope.parent:
return self.parent
if key.scope == Scope.children: # lint-amnesty, pylint: disable=no-else-raise
# didn't find children in _fields; so, see if there's a default
raise KeyError()
elif key.scope == Scope.settings:
# get default which may be the inherited value
raise KeyError()
elif key.scope == Scope.content:
if isinstance(self._definition, DefinitionLazyLoader):
self._load_definition()
else:
raise KeyError()
else:
raise InvalidScopeError(key)
if key.field_name in self._fields:
field_value = self._fields[key.field_name]
# return the "decorated" field value
return self.field_decorator(field_value)
return None
def set(self, key, value):
# handle any special cases
if key.scope not in self.VALID_SCOPES:
raise InvalidScopeError(key, self.VALID_SCOPES)
if key.scope == Scope.content:
self._load_definition()
if key.block_family == XBlockAside.entry_point:
if key.scope == Scope.children:
raise InvalidScopeError(key)
self.aside_fields.setdefault(key.block_scope_id.block_type, {})[key.field_name] = value
else:
# set the field
self._fields[key.field_name] = value
# This function is currently incomplete: it doesn't handle side effects.
# To complete this function, here is some pseudocode for what should happen:
#
# if key.scope == Scope.children:
# remove inheritance from any exchildren
# add inheritance to any new children
# if key.scope == Scope.settings:
# if inheritable, push down to children
def delete(self, key):
# handle any special cases
if key.scope not in self.VALID_SCOPES:
raise InvalidScopeError(key, self.VALID_SCOPES)
if key.scope == Scope.content:
self._load_definition()
if key.block_family == XBlockAside.entry_point:
if key.scope == Scope.children:
raise InvalidScopeError(key)
if key.block_scope_id.block_type in self.aside_fields \
and key.field_name in self.aside_fields[key.block_scope_id.block_type]:
del self.aside_fields[key.block_scope_id.block_type][key.field_name]
else:
# delete the field value
if key.field_name in self._fields:
del self._fields[key.field_name]
def has(self, key):
"""
Is the given field explicitly set in this kvs (neither inherited nor default)
"""
# handle any special cases
if key.scope not in self.VALID_SCOPES:
return False
if key.scope == Scope.content:
self._load_definition()
elif key.scope == Scope.parent:
return True
if key.block_family == XBlockAside.entry_point:
if key.scope == Scope.children:
return False
b_type = key.block_scope_id.block_type
return b_type in self.aside_fields and key.field_name in self.aside_fields[b_type]
else:
# it's not clear whether inherited values should return True. Right now they don't
# if someone changes it so that they do, then change any tests of field.name in xx._field_data
return key.field_name in self._fields
def has_default_value(self, field_name):
"""
Is the given field has default value in this kvs
"""
return field_name in self._defaults
def default(self, key):
"""
Check to see if the default should be from the template's defaults (if any)
rather than the global default or inheritance.
"""
if self._defaults and key.field_name in self._defaults:
return self._defaults[key.field_name]
# If not, try inheriting from a parent, then use the XBlock type's normal default value:
return super().default(key)
def _load_definition(self):
"""
Update fields w/ the lazily loaded definitions
"""
if isinstance(self._definition, DefinitionLazyLoader):
persisted_definition = self._definition.fetch()
if persisted_definition is not None:
fields = self._definition.field_converter(persisted_definition.get('fields'))
self._fields.update(fields)
aside_fields_p = persisted_definition.get('aside_fields')
if aside_fields_p:
aside_fields = self._definition.field_converter(aside_fields_p)
for aside_type, fields in aside_fields.items():
self.aside_fields.setdefault(aside_type, {}).update(fields)
# do we want to cache any of the edit_info?
self._definition = None # already loaded