# object.py
#
#
"""
An Object adds 'dotted access' to dicts to allow for better readable code.
Object's also allow for saving JSON to disk to allow it to be restored at a later time.
"""
from meds.errors import EBORDER, ESET, ENOJSON, ESIGNATURE, ENOTSET, EISMETHOD
from meds.utils.signature import make_signature, verify_signature
from meds.utils.misc import locked, headertxt
from meds.utils.getters import path as dopath
from meds.utils.getters import urled, slice
from meds.utils.trace import get_strace
from meds.utils.json import dumps
from meds.utils.tijd import rtime
from meds.utils.file import cdir
from meds.utils.join import j
from meds.utils.name import name, naam
import threading
import logging
import inspect
import fcntl
import json
import time
import os
[docs]class Object(dict):
"""
Core Object of the MEDS bot, provides saving to JSON files,
using timestamps in the filename so collections of object in a
period of time is possible.
constructing an Object:
>>> obj = Object()
>>> print(obj)
{}
setting attributes on an Object:
>>> obj.key1 = "value1"
>>> obj["key2"] = "value2"
accessing attributes can also be 'dotted' or with a dict key:
>>> v1 = obj.key1
>>> v2 = obj["key2"]
saving is easy, the save() method will use the current time as timestamp:
>>> path = obj.save()
syncing to an already saved file is done with the sync() method:
>>> path = obj.sync()
the json() method can be used to get a JSON string of the object:
>>> s = obj.json()
nice() will give you a more readable presentation:
>>> s = obj.nice()
use the load() method to read a file into an Object:
>>> path = obj._path
>>> test = Object()
>>> obj = test.load(path)
"""
def __str__(self): return self.nice()
def __repr__(self):
return '<%s.%s at %s>' % (
self.__class__.__module__,
self.__class__.__name__,
hex(id(self))
)
def __getattribute__(self, name):
if name == "url": return urled(self)
try: val = super().__getattribute__(name)
except AttributeError:
try: val = self[name]
except KeyError as ex: raise AttributeError(name)
return val
def __getattr__(self, name):
if name == "_resume": self._resume = Object()
if name == "_connected": self._connected = Object()
if name == "_funcs": self._funcs = []
if name == "_ready": self._ready = threading.Event()
if name == "_result": self._result = []
if name == "_thrs": self._thrs = []
if name not in self: raise AttributeError(name)
return self.__getitem__(name)
def __setattr__(self, name, value):
return self.__setitem__(name, value)
def __dir__(self): return self.keys()
def __contains__(self, key):
if key in self.keys(): return True
return False
[docs] def consume(self, value):
nr = 0
for x in value:
self["key%s" % str(nr)] = x
nr += 1
[docs] def grep(self, val):
o = Object()
for key, value in self.items():
if val in str(value) and value not in o: o[key] = value
return o
[docs] def json(self, *args, **kwargs): return dumps(self, *args, **kwargs)
[docs] def load(self, path="", force=True):
from meds.core import cfg
if not path: path = self._path
if cfg.workdir not in path: path = j(cfg.workdir, path)
ondisk = self.read(path)
fromdisk = json.loads(ondisk)
if "signature" in fromdisk:
if not verify_signature(fromdisk["data"], fromdisk["signature"]) and not force: raise ESIGNATURE
if "data" in fromdisk: self.update(fromdisk["data"])
else: self.update(fromdisk)
if "saved" in fromdisk: self._saved = fromdisk["saved"]
self._path = path
return self
[docs] def merge(self, obj):
for key in obj:
if key not in self:
self[key] = obj[key]
[docs] def nice(self, *args, **kwargs): return dumps(self, indent=4, sort_keys=True)
[docs] def prepare(self):
todisk = Object()
todisk.data = slice(self)
todisk.data.type = self.__class__.__name__
if "saved" not in todisk.data:
todisk.data.saved = time.ctime(time.time())
try: todisk.signature = make_signature(todisk["data"])
except: pass
try: result = dumps(todisk, indent=4, ensure_ascii=False, sort_keys=True)
except TypeError: raise ENOJSON()
return result
[docs] def read(self, path):
f = open(path, "r", encoding="utf-8")
res = ""
for line in f:
if not line.strip().startswith("#"): res += line
if not res.strip(): return "{}"
f.close()
return res
[docs] def register(self, key, val, force=False):
if key in self and not force: raise ESET(key)
self[key] = val
[docs] def save(self, stime=""):
if not stime: stime = rtime()
path = j(dopath(self), stime)
return self.sync(path)
[docs] def search(self, name):
o = Object()
for key, value in self.items():
if key.startswith("_"): continue
if key in name: o[key] = value
elif name in key.split(".")[-1]: o[key] = value
return o
@locked
def sync(self, path=""):
from meds.core import kernel
if not path:
try: path = self._path
except AttributeError: pass
if not path: path = j(dopath(self), rtime())
self._path = os.path.abspath(path)
if kernel._cfg.workdir not in self._path: raise EBORDER(self._path)
logging.info("! sync %s" % path)
d, fn = os.path.split(path)
cdir(d)
todisk = self.prepare()
datafile = open(os.path.abspath(path) + ".tmp", 'w')
fcntl.flock(datafile, fcntl.LOCK_EX | fcntl.LOCK_NB)
datafile.write(headertxt % "%s characters" % len(todisk))
datafile.write(todisk)
datafile.write("\n")
fcntl.flock(datafile, fcntl.LOCK_UN)
datafile.flush()
datafile.close()
os.rename(path + ".tmp", path)
return path
[docs] def clear(self):
self._ready.clear()
[docs] def isSet(self):
return self._ready.isSet()
[docs] def ready(self):
self._ready.set()
[docs] def wait(self, sec=None):
self._ready.wait(sec)
for thr in self._thrs:
logging.info("! join %s" % naam(thr))
thr.join(1.0)
[docs]class OOL(Object):
[docs] def register(self, key, val):
if key not in self: self[key] = []
self[key].append(val)
[docs] def find(self, txt=None):
for key, value in self.items():
if txt and txt in key: yield value
else: yield value