sync_struct: Factor action strings out into enum and document them [nfc]

This commit is contained in:
David Nadlinger 2018-03-08 19:53:33 +00:00
parent c213ab13ba
commit e165a9a352

View File

@ -4,13 +4,15 @@ modified by one process (the *publisher*) with copies of it (the
Synchronization is achieved by sending a full copy of the structure to each Synchronization is achieved by sending a full copy of the structure to each
subscriber upon connection (*initialization*), followed by dictionaries subscriber upon connection (*initialization*), followed by dictionaries
describing each modification made to the structure (*mods*). describing each modification made to the structure (*mods*, see
:class:`ModAction`).
Structures must be PYON serializable and contain only lists, dicts, and Structures must be PYON serializable and contain only lists, dicts, and
immutable types. Lists and dicts can be nested arbitrarily. immutable types. Lists and dicts can be nested arbitrarily.
""" """
import asyncio import asyncio
from enum import Enum, unique
from operator import getitem from operator import getitem
from functools import partial from functools import partial
@ -22,23 +24,66 @@ from artiq.protocols.asyncio_server import AsyncioServer
_protocol_banner = b"ARTIQ sync_struct\n" _protocol_banner = b"ARTIQ sync_struct\n"
@unique
class ModAction(Enum):
"""Describes the type of incremental modification.
`Mods` are represented by a dictionary ``m``. ``m["action"]`` describes
the type of modification, as per this enum, serialized as a string if
required.
The path (member field) the change applies to is given in
``m["path"]`` as a list; elements give successive levels of indexing. (There
is no ``path`` on initial initialization.)
Details on the modification are stored in additional data fields specific
to each type.
For example, this represents appending the value ``42`` to an array
``data.counts[0]``: ::
{
"action": "append",
"path": ["data", "counts", 0],
"x": 42
}
"""
#: A full copy of the data is sent in `struct`; no `path` given.
init = "init"
#: Appends `x` to target list.
append = "append"
#: Inserts `x` into target list at index `i`.
insert = "insert"
#: Removes index `i` from target list.
pop = "pop"
#: Sets target's `key` to `value`.
setitem = "setitem"
#: Removes target's `key`.
delitem = "delitem"
# Handlers to apply a given mod to a target dict, invoked with (target, mod).
_mod_appliers = {
ModAction.append: lambda t, m: t.append(m["x"]),
ModAction.insert: lambda t, m: t.insert(m["i"], m["x"]),
ModAction.pop: lambda t, m: t.pop(m["i"]),
ModAction.setitem: lambda t, m: t.__setitem__(m["key"], m["value"]),
ModAction.delitem: lambda t, m: t.__delitem__(m["key"])
}
def process_mod(target, mod): def process_mod(target, mod):
"""Apply a *mod* to the target, mutating it.""" """Apply a *mod* to the target, mutating it."""
for key in mod["path"]: for key in mod["path"]:
target = getitem(target, key) target = getitem(target, key)
action = mod["action"]
if action == "append": _mod_appliers[ModAction(mod["action"])](target, mod)
target.append(mod["x"])
elif action == "insert":
target.insert(mod["i"], mod["x"])
elif action == "pop":
target.pop(mod["i"])
elif action == "setitem":
target.__setitem__(mod["key"], mod["value"])
elif action == "delitem":
target.__delitem__(mod["key"])
else:
raise ValueError
class Subscriber: class Subscriber:
@ -155,7 +200,7 @@ class Notifier:
"""Append to a list.""" """Append to a list."""
self._backing_struct.append(x) self._backing_struct.append(x)
if self.root.publish is not None: if self.root.publish is not None:
self.root.publish({"action": "append", self.root.publish({"action": ModAction.append.value,
"path": self._path, "path": self._path,
"x": x}) "x": x})
@ -163,7 +208,7 @@ class Notifier:
"""Insert an element into a list.""" """Insert an element into a list."""
self._backing_struct.insert(i, x) self._backing_struct.insert(i, x)
if self.root.publish is not None: if self.root.publish is not None:
self.root.publish({"action": "insert", self.root.publish({"action": ModAction.insert.value,
"path": self._path, "path": self._path,
"i": i, "x": x}) "i": i, "x": x})
@ -173,7 +218,7 @@ class Notifier:
tracked.""" tracked."""
r = self._backing_struct.pop(i) r = self._backing_struct.pop(i)
if self.root.publish is not None: if self.root.publish is not None:
self.root.publish({"action": "pop", self.root.publish({"action": ModAction.pop.value,
"path": self._path, "path": self._path,
"i": i}) "i": i})
return r return r
@ -181,7 +226,7 @@ class Notifier:
def __setitem__(self, key, value): def __setitem__(self, key, value):
self._backing_struct.__setitem__(key, value) self._backing_struct.__setitem__(key, value)
if self.root.publish is not None: if self.root.publish is not None:
self.root.publish({"action": "setitem", self.root.publish({"action": ModAction.setitem.value,
"path": self._path, "path": self._path,
"key": key, "key": key,
"value": value}) "value": value})
@ -189,7 +234,7 @@ class Notifier:
def __delitem__(self, key): def __delitem__(self, key):
self._backing_struct.__delitem__(key) self._backing_struct.__delitem__(key)
if self.root.publish is not None: if self.root.publish is not None:
self.root.publish({"action": "delitem", self.root.publish({"action": ModAction.delitem.value,
"path": self._path, "path": self._path,
"key": key}) "key": key})
@ -252,7 +297,7 @@ class Publisher(AsyncioServer):
except KeyError: except KeyError:
return return
obj = {"action": "init", "struct": notifier.raw_view} obj = {"action": ModAction.init.value, "struct": notifier.raw_view}
line = pyon.encode(obj) + "\n" line = pyon.encode(obj) + "\n"
writer.write(line.encode()) writer.write(line.encode())