Last active
December 22, 2015 02:58
-
-
Save seancribbs/6406939 to your computer and use it in GitHub Desktop.
WIP Client-side Riak CRDT API for Python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from collections import MutableSet, MutableMapping, Mapping | |
class DataType(object): | |
def __init__(self, value=None, context=None): | |
if value is not None: | |
self._check_value(value) | |
self.value = value | |
if context: | |
self.context = context | |
@property | |
def value(self): | |
return self.value | |
@property | |
def context(self): | |
return self.context | |
def to_op(self): | |
raise NotImplementedError | |
@classmethod | |
def _check_value(value): | |
raise NotImplementedError | |
class Counter(DataType): | |
value = 0 | |
_increment = 0 | |
def increment(self, amount=1): | |
self.value += amount | |
self._increment += amount | |
def decrement(self, amount=1): | |
self.increment(-amount) | |
def to_op(self): | |
if self._increment != 0: | |
return ('increment', self._increment) | |
@classmethod | |
def _check_value(value): | |
if not isinstance(value, int): | |
raise TypeError("Counter values must be integers") | |
class Register(DataType): | |
_changed = False | |
def set(self, value): | |
self._check_value(value) | |
if value != self.value: | |
self._changed = True | |
self.value = value | |
def to_op(self): | |
if self._changed: | |
return self.value | |
@classmethod | |
def _check_value(value): | |
if not isinstance(value, basestring): | |
raise TypeError("Register values must be strings") | |
class Flag(DataType): | |
value = False | |
_changed = False | |
def enable(self): | |
self.value = True | |
self._changed = not self._changed | |
def disable(self): | |
self.value = False | |
self._changed = not self._changed | |
def to_op(self): | |
if self._changed: | |
if self.value: | |
return 'enable' | |
else: | |
return 'disable' | |
@classmethod | |
def _check_value(value): | |
if not isinstance(value, bool): | |
raise TypeError("Flag values must be bools") | |
class Set(MutableSet, DataType): | |
value = set() | |
_adds = set() | |
_removes = set() | |
# DataType API | |
@classmethod | |
def _check_value(value): | |
for element in value: | |
if not isinstance(element, basestring): | |
raise TypeError("Set elements must be strings") | |
def to_op(self): | |
if self._adds or self._removes: | |
{'adds': list(self._adds), | |
'removes': list(self._removes)} | |
# MutableSet API | |
def __contains__(self, element): | |
return element in self.value | |
def __iter__(self): | |
return iter(self.value) | |
def __len__(self): | |
return len(self.value) | |
def add(self, element): | |
if not isinstance(element, basestring): | |
raise TypeError("Set elements must be strings") | |
if element in self._removes: | |
self._removes.discard(element) | |
else: | |
self._adds.add(element) | |
self.value.add(element) | |
def discard(self, element): | |
if element in self._adds: | |
self._adds.discard(element) | |
else: | |
self._removes.add(element) | |
self.value.discard(element) | |
_type_mappings = { | |
'COUNTER': Counter, | |
'FLAG': Flag, | |
'REGISTER': Register, | |
'SET': Set, | |
'MAP': Map | |
} |
@danostrowski It might be sufficient to have "inventory_set" or "name_register" for keys, but that could also get ugly.
So, the main case I can think of for same-name/different-type is in the course of a data-layout migration. i.e. changing the type of the field (say for example, allowing someone to have multiple emails in their profile instead of just one). You want that still to converge, without defining a meta-lattice of types on the server-side.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Keying on (name, type) complicates the API, especially if it's supposed to be a generic container. What's the trade off?
Well, theoretically, chance_of_fail becomes (chance_of_fail * 0.2) ... but you still have to code around the chance to fail, so I'm not sure if that gains much.
Furthermore, I'm not sure that it's actually *= 0.2 because generally programmers think in terms of "1 name, 1 type." I can't think of a time in 15 years of programming where I've thought sticking something in a container more than once with the same name but different type was a good idea or what I wanted to do, generally you have "foo_int" and "foo_str" or even "foo_val" and "foo_len" or some-such. So if you have multiple clients, I think odds are good they're probably going to be trying to write the same (value, name) pair.
However, I haven't thought about this much and maybe I'm missing some really obvious use cases. :)