Skip to content

Commit f09527e

Browse files
author
Pietro Albini
committed
[INCOMPLETE!] Use custom proxies for the shared memory
[ci skip]
1 parent e3aa658 commit f09527e

2 files changed

Lines changed: 255 additions & 11 deletions

File tree

botogram/shared/drivers.py

Lines changed: 123 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88

99
import builtins
1010
import threading
11+
import uuid
12+
import functools
13+
14+
15+
# This is used as the default argument for some methods of DictProxy
16+
_None = object()
1117

1218

1319
class dict(builtins.dict):
@@ -16,24 +22,117 @@ class dict(builtins.dict):
1622
pass
1723

1824

25+
class SharedObject:
26+
27+
def __init__(self, type):
28+
self.type = type
29+
self.children = {}
30+
31+
if type == "dict":
32+
self.value = dict()
33+
34+
def add_children(self, object):
35+
"""Register a new children"""
36+
self.children.add(object)
37+
38+
1939
class LocalDriver:
2040
"""Local driver for the shared memory"""
2141

2242
def __init__(self):
23-
self._memories = {}
43+
self._objects = {}
2444
self._locks = {}
2545

2646
def __reduce__(self):
2747
return rebuild_local_driver, (self.export_data(),)
2848

29-
def get(self, component):
30-
# Create the shared memory if it doesn't exist
31-
new = False
32-
if component not in self._memories:
33-
self._memories[component] = dict()
34-
new = True
35-
36-
return self._memories[component], new
49+
def create_object(self, type, id=None, parent=None):
50+
"""Create a new object"""
51+
if id is None:
52+
id = uuid.uuid4()
53+
54+
self._objects[id] = SharedObject(type)
55+
if parent is not None:
56+
self._objects[parent].add_children(id)
57+
58+
return self._objects[id]
59+
60+
def delete_object(self, id):
61+
"""Delete an object"""
62+
obj = self._objects.pop(id)
63+
64+
# Recursively delete every children
65+
for child in obj.children:
66+
self.delete_object(child)
67+
68+
def _ensure_object(type):
69+
"""Ensure the object exists and it's of that type"""
70+
def decorator(f):
71+
@functools.wraps(f)
72+
def wrapper(self, object_id, *args, **kwargs):
73+
if object_id not in self._objects:
74+
raise ValueError("Object doesn't exist: %s" % object_id)
75+
76+
obj = self._objects[object_id]
77+
if obj.type != type:
78+
raise TypeError("Operation not supported on the %s type" %
79+
obj.type)
80+
81+
return f(self, obj.value, *args, **kwargs)
82+
return wrapper
83+
return decorator
84+
85+
############################
86+
# dicts implementation #
87+
############################
88+
89+
@_ensure_object("dict")
90+
def dict_length(self, obj):
91+
return len(obj)
92+
93+
@_ensure_object("dict")
94+
def dict_item_get(self, obj, key):
95+
return obj[key]
96+
97+
@_ensure_object("dict")
98+
def dict_item_set(self, obj, key, value):
99+
obj[key] = value
100+
101+
@_ensure_object("dict")
102+
def dict_item_delete(self, obj, key):
103+
del obj[key]
104+
105+
@_ensure_object("dict")
106+
def dict_contains(self, obj, key):
107+
return key in obj
108+
109+
@_ensure_object("dict")
110+
def dict_keys(self, obj):
111+
return tuple(obj.keys())
112+
113+
@_ensure_object("dict")
114+
def dict_values(self, obj):
115+
return tuple(obj.values())
116+
117+
@_ensure_object("dict")
118+
def dict_items(self, obj):
119+
return tuple(obj.items())
120+
121+
@_ensure_object("dict")
122+
def dict_clear(self, obj):
123+
obj.clear()
124+
125+
@_ensure_object("dict")
126+
def dict_pop(self, obj, key=_None):
127+
# If no keys are provided pop a random item
128+
if key is _None:
129+
return obj.popitem()
130+
else:
131+
return obj.pop(key)
132+
133+
############################
134+
# Locks implementation #
135+
############################
37136

38137
def lock_acquire(self, lock_id):
39138
# Create a new lock if it doesn't exist yet
@@ -56,17 +155,30 @@ def lock_status(self, lock_id):
56155

57156
return self._locks[lock_id]["acquired"]
58157

158+
###############################
159+
# Importing and exporting #
160+
###############################
161+
59162
def import_data(self, data):
60-
self._memories = dict(data["storage"])
163+
# Rebuild the objects
164+
self._objects = {}
165+
for id, value in data["objects"]:
166+
if type(value) == dict:
167+
self._objects[id] = SharedObject("dict")
168+
self._objects[id].value = value
169+
else:
170+
raise ValueError("Unsupported type: %s" % type(value))
61171

62172
# Rebuild the locks
63173
self._locks = {}
64174
for lock_id in data["locks"]:
65175
self.lock_acquire(lock_id)
66176

67177
def export_data(self):
178+
objects = {id: obj.value for id, obj in self._objects.items()}
68179
locks = [lock_id for lock_id, d in self._locks if not d["acquired"]]
69-
return {"storage": self._memories.copy(), "locks": locks}
180+
181+
return {"storage": objects, "locks": locks}
70182

71183

72184
def rebuild_local_driver(memories):

botogram/shared/proxies.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
"""
2+
botogram.shared.proxies
3+
Object proxies for the botogram's shared memory
4+
5+
Copyright (c) 2016 Pietro Albini <[email protected]>
6+
Released under the MIT license
7+
"""
8+
9+
import copy
10+
11+
12+
# This is used as the default argument for some methods of DictProxy
13+
_None = object()
14+
15+
16+
class DictProxy:
17+
"""A proxy for dictionaries"""
18+
19+
def __init__(self, object_id, driver):
20+
self._object_id = object_id
21+
self._driver = driver
22+
23+
def __str__(self):
24+
return str(dict(self.items()))
25+
26+
def __repr__(self):
27+
return '<DictProxy with %s>' % self
28+
29+
def __len__(self):
30+
return self._driver.dict_length(self._object_id)
31+
32+
def __getitem__(self, key):
33+
return self._driver.dict_item_get(self._object_id, key)
34+
35+
def __setitem__(self, key, value):
36+
self._driver.dict_item_set(self._object_id, key, value)
37+
38+
def __delitem__(self, key):
39+
self._driver.dict_item_delete(self._object_id, key)
40+
41+
def __contains__(self, key):
42+
return self._driver.dict_contains(self._object_id, key)
43+
44+
def __iter__(self):
45+
return iter(self._driver.dict_keys(self._object_id))
46+
47+
def clear(self):
48+
"""Remove all items from the dictionary."""
49+
self._driver.dict_clear(self._object_id)
50+
51+
def copy(self):
52+
"""Return a shallow copy of the dictionary."""
53+
return dict(self._driver.dict_items(self._object_id))
54+
55+
def get(self, key, default=_None):
56+
"""Return the value for key if key is in the dictionary, else default.
57+
If default is not given, it defaults to None, so that this method never
58+
raises a KeyError.
59+
"""
60+
try:
61+
return self._driver.dict_item_get(self._object_id, key)
62+
except KeyError:
63+
# Return a default value if provided
64+
if default is _None:
65+
raise
66+
return default
67+
68+
def keys(self):
69+
"""Return the dictionary's keys."""
70+
return self._driver.dict_keys(self._object_id)
71+
72+
def values(self):
73+
"""Return the dictionary's values."""
74+
return self._driver.dict_values(self._object_id)
75+
76+
def items(self):
77+
"""Return the dictionary's items."""
78+
return self._driver.dict_items(self._object_id)
79+
80+
def pop(self, key, default=_None):
81+
"""If key is in the dictionary, remove it and return its value, else
82+
return default. If default is not given and key is not in the
83+
dictionary, a KeyError is raised.
84+
"""
85+
try:
86+
return self._driver.dict_pop(self._object_id, key)
87+
except KeyError:
88+
# Return a default value if provided
89+
if default is _None:
90+
raise
91+
return default
92+
93+
def popitem(self):
94+
"""Remove and return an arbitrary (key, value) pair from the
95+
dictionary.
96+
97+
popitem() is useful to destructively iterate over a dictionary, as
98+
often used in set algorithms. If the dictionary is empty, calling
99+
popitem() raises a KeyError.
100+
"""
101+
return self._driver.dict_pop(self._object_id)
102+
103+
def setdefault(self, key, default=None):
104+
"""If key is in the dictionary, return its value. If not, insert key
105+
with a value of default and return default. default defaults to None.
106+
"""
107+
if key in self:
108+
return self[key]
109+
else:
110+
self[key] = default
111+
return default
112+
113+
def update(self, other=_None, **kwargs)
114+
"""Update the dictionary with the key/value pairs from other,
115+
overwriting existing keys. Return None.
116+
117+
update() accepts either another dictionary object or an iterable of
118+
key/value pairs (as tuples or other iterables of length two). If
119+
keyword arguments are specified, the dictionary is then updated with
120+
those key/value pairs: d.update(red=1, blue=2).
121+
"""
122+
# Support providing data as a keyword argument
123+
if other is _None:
124+
other = kwargs
125+
126+
# Accept also non-dict objects
127+
if type(other) == dict:
128+
other = dict.items()
129+
130+
# Add every value to our dictionary
131+
for key, value in other:
132+
self[key] = value

0 commit comments

Comments
 (0)