Skip to content

Instantly share code, notes, and snippets.

@seancribbs
Last active December 22, 2015 02:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save seancribbs/6406939 to your computer and use it in GitHub Desktop.
Save seancribbs/6406939 to your computer and use it in GitHub Desktop.
WIP Client-side Riak CRDT API for Python
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
Copy link

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. :)

@seancribbs
Copy link
Author

@danostrowski It might be sufficient to have "inventory_set" or "name_register" for keys, but that could also get ugly.

@seancribbs
Copy link
Author

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