GitLab is used only for code review, issue tracking and project management. Canonical locations for source code are still https://gitweb.torproject.org/ https://git.torproject.org/ and git-rw.torproject.org.

Commit 26409a3d authored by juga  's avatar juga

fix: state: Let json manage data types

Since state uses json and json will raise an error when it can't
decode/encode some datatype.
parent fe827ac1
...@@ -4,16 +4,12 @@ import json ...@@ -4,16 +4,12 @@ import json
class State: class State:
''' """
State allows one to atomically access and update a simple state file on `json` wrapper to read a json file every time it gets a key and to write
disk across threads and across processes. to the file every time a key is set.
To put it blunty, due to limited developer time and his inability to Every time a key is got or set, the file is locked, to atomically access
quickly find a way to safely access and update more complex data types and update the file across threads and across processes.
(namely, collections like list, set, and dict), you may only store simple
types of data as enumerated in _ALLOWED_TYPES. Keys must be strings.
Data is stored as JSON on disk in the provided file file.
>>> state = State('foo.state') >>> state = State('foo.state')
>>> # state == {} >>> # state == {}
...@@ -40,8 +36,8 @@ class State: ...@@ -40,8 +36,8 @@ class State:
>>> # We can do many of the same things with a State object as with a dict >>> # We can do many of the same things with a State object as with a dict
>>> for key in state: print(key) >>> for key in state: print(key)
>>> # Prints 'linux', 'age', and 'name' >>> # Prints 'linux', 'age', and 'name'
'''
_ALLOWED_TYPES = (int, float, str, bool, type(None)) """
def __init__(self, fname): def __init__(self, fname):
self._fname = fname self._fname = fname
...@@ -68,35 +64,19 @@ class State: ...@@ -68,35 +64,19 @@ class State:
Implements a dictionary ``get`` method reading and locking Implements a dictionary ``get`` method reading and locking
a json file. a json file.
""" """
if not isinstance(key, str):
raise TypeError(
'Keys must be strings. %s is a %s' % (key, type(key)))
self._state = self._read() self._state = self._read()
return self._state.get(key, d) return self._state.get(key, d)
def __getitem__(self, key): def __getitem__(self, key):
if not isinstance(key, str):
raise TypeError(
'Keys must be strings. %s is a %s' % (key, type(key)))
self._state = self._read() self._state = self._read()
return self._state.__getitem__(key) return self._state.__getitem__(key)
def __delitem__(self, key): def __delitem__(self, key):
if not isinstance(key, str):
raise TypeError(
'Keys must be strings. %s is a %s' % (key, type(key)))
self._state = self._read() self._state = self._read()
self._state.__delitem__(key) self._state.__delitem__(key)
self._write() self._write()
def __setitem__(self, key, value): def __setitem__(self, key, value):
if not isinstance(key, str):
raise TypeError(
'Keys must be strings. %s is a %s' % (key, type(key)))
if type(value) not in State._ALLOWED_TYPES:
raise TypeError(
'May only store value with type in %s, not %s' %
(State._ALLOWED_TYPES, type(value)))
# NOTE: important, read the file before setting the key, # NOTE: important, read the file before setting the key,
# otherwise if other instances are creating other keys, they're lost. # otherwise if other instances are creating other keys, they're lost.
self._state = self._read() self._state = self._read()
......
...@@ -11,26 +11,6 @@ def test_state_set_allowed_key_types(tmpdir): ...@@ -11,26 +11,6 @@ def test_state_set_allowed_key_types(tmpdir):
assert state[key] == 4 assert state[key] == 4
def test_state_set_bad_key_types(tmpdir):
state = State(os.path.join(str(tmpdir), 'statefoo'))
attempt_keys = (15983, None, True, -1.2, [], {}, set())
for key in attempt_keys:
try:
state[key] = 4
except TypeError:
pass
else:
assert None, 'Should not have been able to use %s %s as a key' %\
(key, type(key))
try:
state[key]
except TypeError:
pass
else:
assert None, '%s %s is not a valid key type, so should have got '\
'TypeError when giving it' % (key, type(key))
def test_state_set_allowed_value_types(tmpdir): def test_state_set_allowed_value_types(tmpdir):
state = State(os.path.join(str(tmpdir), 'statefoo')) state = State(os.path.join(str(tmpdir), 'statefoo'))
attempt_vals = (15983, None, True, -1.2, 'loooooool') attempt_vals = (15983, None, True, -1.2, 'loooooool')
...@@ -39,19 +19,6 @@ def test_state_set_allowed_value_types(tmpdir): ...@@ -39,19 +19,6 @@ def test_state_set_allowed_value_types(tmpdir):
assert state['foo'] == val assert state['foo'] == val
def test_state_set_bad_value_types(tmpdir):
state = State(os.path.join(str(tmpdir), 'statefoo'))
attempt_vals = ([], {}, set())
for val in attempt_vals:
try:
state['foo'] = val
except TypeError:
pass
else:
assert None, 'Should not have been able to use %s %s as a value' %\
(val, type(val))
def test_state_del(tmpdir): def test_state_del(tmpdir):
state = State(os.path.join(str(tmpdir), 'statefoo')) state = State(os.path.join(str(tmpdir), 'statefoo'))
d = {'a': 1, 'b': 2, 'c': 3, 'd': 4} d = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
...@@ -65,16 +32,6 @@ def test_state_del(tmpdir): ...@@ -65,16 +32,6 @@ def test_state_del(tmpdir):
for key in d: for key in d:
assert d[key] == state[key] assert d[key] == state[key]
attempt_keys = (15983, None, True, -1.2, [], {}, set())
for key in attempt_keys:
try:
del state[key]
except TypeError:
pass
else:
assert None, 'Should not have been allowed to delete %s %s '\
'because it is not a valid key type' % (key, type(key))
d['e'] = 5 d['e'] = 5
state['e'] = 5 state['e'] = 5
d['e'] = 5.5 d['e'] = 5.5
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment