# -*- coding: utf-8 -*-
"""
Using KVS (like TokyoTyrant) as a model
This time, we will handle Django's cache
Although it is called a model, it does not provide relations or searches,
but allows field definitions like models.Model.
(In essence, it automatically assigns the key names of the dictionary (storage) to the variable names.)
class Soldier(KvsModel):
hp = KvsModel.Field(default=0)
mp = KvsModel.Field(default=0)
cain = Soldier('Cain')
cain.hp = 100
cain.mp = 50
cain.save()
abel = Soldier('Abel')
abel.hp = 50
abel.mp = 100
abel.save()
cain = Soldier('Cain')
cain.hp -= 80
cain.save()
abel = Soldier('Abel')
abel.mp -= 20
abel.save()
cain = Soldier('Cain')
cain.hp = min(cain.hp + 50, 100)
cain.save()
cain = Soldier('Cain')
cain.hp
Out: 70
cain.dump()
Out: "[Soldier] dump: kvs_key=Soldier::Cain, storage={'hp': 70, 'mp': 50}"
abel.dump()
Out: "[Soldier] dump: kvs_key=Soldier::Abel, storage={'hp': 50, 'mp': 80}"
"""
import msgpack #pip install msgpack-python
from django.core.cache import cache as kvs_backend
class KvsModel(object):
"""
Base class to use TokyoTyrant etc. as a dictionary
Please use by inheriting
"""
packer = msgpack.Packer()
def __new__(cls, *args, **kwargs):
for k, v in vars(cls).items():
if isinstance(v, KvsModel.Field):
v.set_field_name(k)
return object.__new__(cls)
def __init__(self, key_object):
"""
@param key_object An instance of a Django model, etc.
When overriding and there is no need to call super's __init__,
directly call self.after_init(key_part)
"""
self.key_object = key_object
key_part = getattr(key_object, 'pk', str(key_object))
self.after_init(key_part)
def after_init(self, key_part):
"""
The latter half of the initialization process
@param (str)key_part A unique string that forms the basis of the key generation
"""
self.kvs_key = '%s::%s' % (self.__class__.__name__, key_part)
bulk_storage = kvs_backend.get(self.kvs_key, None)
if bulk_storage:
self.storage = msgpack.unpackb(bulk_storage)
else:
self.storage = {}
def get(self, k, default=None):
return self.storage.get(k, default)
def set(self, k, value):
self.storage[k] = value
def save(self):
"""
Save storage
"""
kvs_backend.set(self.kvs_key, self.packer.pack(self.storage))
def delete(self):
"""
Delete storage
"""
kvs_backend.delete(self.kvs_key)
self.storage = {}
def dump(self):
return "[%s] dump: kvs_key=%s, storage=%r" % (self.__class__.__name__, self.kvs_key, self.storage)
class Field(object):
"""
Field usable in KvsModel
Can be used like score = KvsModel.Field(default=0).
"""
def __init__(self, key_name=None, default=None):
self._key_name = key_name
self.default = default
def __get__(self, inst, type=None):
assert isinstance(inst, KvsModel), '%s use only KvsModel class.' % self.__class__.__name__
return inst.get(self.key_name, self.default)
def __set__(self, inst, value):
assert isinstance(inst, KvsModel), '%s use only KvsModel class.' % self.__class__.__name__
inst.set(self.key_name, value)
def set_field_name(self, field_name):
# Called from TokyoTyrantStorage.__new__
self.field_name = field_name
@property
def key_name(self):
return self._key_name or self.field_name
Comments