#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ---------------------------------------------------------------------
# Copyright (c) Merchise Autrement [~º/~] and Contributors
# All rights reserved.
#
# This is free software; you can do what the LICENCE file allows you to.
#
"""Base interfaces."""
from dataclasses import dataclass
[docs]@dataclass(frozen=True, order=True)
class Process:
"""Represents a process or node that holds replicated objects.
We require (for some CRDTs) that processes are uniquely named and totally
ordered across the cluster. So when adding/removing a process you should
take measures for not reusing old names.
"""
order: int
name: str
def __init__(self, name: str, order: int) -> None:
object.__setattr__(self, "name", name)
object.__setattr__(self, "order", order)
def __repr__(self):
return f"Process({self.name!r}, {self.order!r})"
def __eq__(self, other) -> bool:
if isinstance(other, Process):
return self.name == other.name
else:
return NotImplemented
[docs]class CvRDT:
"""Base class for Convergent Replicated Data Types.
Basically this documents the expectation of each CvRDT. Subclasses
**must** implement the following methods and attributes.
"""
def __init__(self, *, process: Process) -> None:
self.process = process
self.init()
[docs] def init(self) -> None:
"""Set the initial state of a newly create CRDT."""
[docs] def merge(self, other: "CvRDT") -> None:
"""Update the CvRDT to account for the another replica's state.
"""
raise NotImplementedError
@property
def value(self):
"""The current value that is managed by this CRDT.
This could be any type of value. But you *must* never assume changes
to the value return will be of any effect. Each CRDT implements
methods to properly update its value.
This is a read-only property.
"""
raise NotImplementedError
[docs] def reset(self) -> None:
"Reset the internal state of value, usually to the initial state."
raise NotImplementedError
[docs] def __le__(self, other):
"""Compares two replicas for '<=' in the semilattice.
This is **NOT** a relation of the `value`:any:.
"""
return NotImplemented
[docs] def __eq__(self, other) -> bool:
"""Compares two replicas for '==' in the semilattice.
This is **NOT** a relation of the `value`:any:.
"""
return NotImplemented
[docs]def get_state(crdt: CvRDT) -> bytes:
"""Dumps the crdt in a way that is amenable for transmission/storage.
"""
import pickle
return pickle.dumps(crdt)
[docs]def from_state(state: bytes) -> CvRDT:
"""Reconstruct the CRDT from its dumped state.
`state` should be the result of calling `get_state`:func:. The following
property should always hold::
assert crdt == from_state(get_state(crdt))
"""
import pickle
res = pickle.loads(state)
if not isinstance(res, CvRDT):
raise ValueError("Invalid state") # pragma: no cover
else:
return res