This repository has been archived by the owner on Aug 1, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 82
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The biggest part of this commit is a plugin storage subsystem, which at this point I'm fairly happy with. I've iterated on this a couple times, and the final result has a very clean/simple interface, is easy to extend to different data stores, and has a very few minimal number of grokable edge cases. - Storage subsytem - Fix command group abbreviations - Fix reconnecting in the GatewaySocket - Add pickle support to serializer
- Loading branch information
1 parent
e80dcec
commit 41f7126
Showing
17 changed files
with
300 additions
and
89 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,4 @@ build/ | |
dist/ | ||
disco*.egg-info/ | ||
docs/_build | ||
storage.db |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import inspect | ||
import importlib | ||
|
||
from .base import BaseProvider | ||
|
||
|
||
def load_provider(name): | ||
try: | ||
mod = importlib.import_module('disco.bot.providers.' + name) | ||
except ImportError: | ||
mod = importlib.import_module(name) | ||
|
||
for entry in filter(inspect.isclass, map(lambda i: getattr(mod, i), dir(mod))): | ||
if issubclass(entry, BaseProvider) and entry != BaseProvider: | ||
return entry |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
import six | ||
import pickle | ||
|
||
from six.moves import map | ||
|
||
from UserDict import UserDict | ||
|
||
|
||
ROOT_SENTINEL = u'\u200B' | ||
SEP_SENTINEL = u'\u200D' | ||
OBJ_SENTINEL = u'\u200C' | ||
CAST_SENTINEL = u'\u24EA' | ||
|
||
|
||
def join_key(*args): | ||
nargs = [] | ||
for arg in args: | ||
if not isinstance(arg, six.string_types): | ||
arg = CAST_SENTINEL + pickle.dumps(arg) | ||
nargs.append(arg) | ||
return SEP_SENTINEL.join(nargs) | ||
|
||
|
||
def true_key(key): | ||
key = key.rsplit(SEP_SENTINEL, 1)[-1] | ||
if key.startswith(CAST_SENTINEL): | ||
return pickle.loads(key) | ||
return key | ||
|
||
|
||
class BaseProvider(object): | ||
def __init__(self, config): | ||
self.config = config | ||
self.data = {} | ||
|
||
def exists(self, key): | ||
return key in self.data | ||
|
||
def keys(self, other): | ||
count = other.count(SEP_SENTINEL) + 1 | ||
for key in self.data.keys(): | ||
if key.startswith(other) and key.count(SEP_SENTINEL) == count: | ||
yield key | ||
|
||
def get_many(self, keys): | ||
for key in keys: | ||
yield key, self.get(key) | ||
|
||
def get(self, key): | ||
return self.data[key] | ||
|
||
def set(self, key, value): | ||
self.data[key] = value | ||
|
||
def delete(self, key): | ||
del self.data[key] | ||
|
||
def load(self): | ||
pass | ||
|
||
def save(self): | ||
pass | ||
|
||
def root(self): | ||
return StorageDict(self) | ||
|
||
|
||
class StorageDict(UserDict): | ||
def __init__(self, parent_or_provider, key=None): | ||
if isinstance(parent_or_provider, BaseProvider): | ||
self.provider = parent_or_provider | ||
self.parent = None | ||
else: | ||
self.parent = parent_or_provider | ||
self.provider = self.parent.provider | ||
self._key = key or ROOT_SENTINEL | ||
|
||
def keys(self): | ||
return map(true_key, self.provider.keys(self.key)) | ||
|
||
def values(self): | ||
for key in self.keys(): | ||
yield self.provider.get(key) | ||
|
||
def items(self): | ||
for key in self.keys(): | ||
yield (true_key(key), self.provider.get(key)) | ||
|
||
def ensure(self, key, typ=dict): | ||
if key not in self: | ||
self[key] = typ() | ||
return self[key] | ||
|
||
def update(self, obj): | ||
for k, v in six.iteritems(obj): | ||
self[k] = v | ||
|
||
@property | ||
def data(self): | ||
obj = {} | ||
|
||
for raw, value in self.provider.get_many(self.provider.keys(self.key)): | ||
key = true_key(raw) | ||
|
||
if value == OBJ_SENTINEL: | ||
value = self.__class__(self, key=key).data | ||
obj[key] = value | ||
return obj | ||
|
||
@property | ||
def key(self): | ||
if self.parent is not None: | ||
return join_key(self.parent.key, self._key) | ||
return self._key | ||
|
||
def __setitem__(self, key, value): | ||
if isinstance(value, dict): | ||
obj = self.__class__(self, key) | ||
obj.update(value) | ||
value = OBJ_SENTINEL | ||
|
||
self.provider.set(join_key(self.key, key), value) | ||
|
||
def __getitem__(self, key): | ||
res = self.provider.get(join_key(self.key, key)) | ||
|
||
if res == OBJ_SENTINEL: | ||
return self.__class__(self, key) | ||
|
||
return res | ||
|
||
def __delitem__(self, key): | ||
return self.provider.delete(join_key(self.key, key)) | ||
|
||
def __contains__(self, key): | ||
return self.provider.exists(join_key(self.key, key)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import os | ||
import gevent | ||
|
||
from disco.util.serializer import Serializer | ||
from .base import BaseProvider | ||
|
||
|
||
class DiskProvider(BaseProvider): | ||
def __init__(self, config): | ||
super(DiskProvider, self).__init__(config) | ||
self.format = config.get('format', 'pickle') | ||
self.path = config.get('path', 'storage') + '.' + self.format | ||
self.fsync = config.get('fsync', False) | ||
self.fsync_changes = config.get('fsync_changes', 1) | ||
|
||
self.change_count = 0 | ||
|
||
def autosave_loop(self, interval): | ||
while True: | ||
gevent.sleep(interval) | ||
self.save() | ||
|
||
def _on_change(self): | ||
if self.fsync: | ||
self.change_count += 1 | ||
|
||
if self.change_count >= self.fsync_changes: | ||
self.save() | ||
self.change_count = 0 | ||
|
||
def load(self): | ||
if not os.path.exists(self.path): | ||
return | ||
|
||
if self.config.get('autosave', True): | ||
self.autosave_task = gevent.spawn( | ||
self.autosave_loop, | ||
self.config.get('autosave_interval', 120)) | ||
|
||
with open(self.path, 'r') as f: | ||
self.data = Serializer.loads(self.format, f.read()) | ||
|
||
def save(self): | ||
with open(self.path, 'w') as f: | ||
f.write(Serializer.dumps(self.format, self.data)) | ||
|
||
def set(self, key, value): | ||
super(DiskProvider, self).set(key, value) | ||
self._on_change() | ||
|
||
def delete(self, key): | ||
super(DiskProvider, self).delete(key) | ||
self._on_change() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from .base import BaseProvider | ||
|
||
|
||
class MemoryProvider(BaseProvider): | ||
pass |
Oops, something went wrong.