I'm developing a model for web applications. The database that I use is Redis.
I created special DbField
objects like HashDbField
, SortedSetDbField
, ScalarDbField
and so on, for each data type in Redis. This objects provides a convenient way to work with redis keys. Each of it takes target key name in constructor
The second type of objects are DbObject
s that represents objects in database. DbObject
s (UserDbObject
, PostDbObject
) consist of DbField
s. There are class fields, for some global registers and instance fields for values of certain objects.
Each dbObject
has a DbObjectName
class field which contains the current object name ('user', 'post', 'comment', and so on...)
DbObject
class looks like:
class DbObjectType(type):
def __init__(cls, name, bases, dct):
super(DbObjectType, cls).__init__(name, bases, dct)
cls._TypeInit()
class DbObject(metaclass=DbObjectType):
DbObjectName = DB_OBJECT_NAME
@classmethod
def _TypeInit(cls):
cls._SelfDbObjectSerializator = DbObjectSerializator(cls)
cls.LastId = ScalarDbField(cls.DbObjectName, None, R_LAST_ID)
cls.Reg = SortedSetDbField(cls.DbObjectName, None, R_REG, cls._SelfDbObjectSerializator, DateTimeSerializator)
cls._Reg_raw = SortedSetDbField(cls.DbObjectName, None, R_REG)
@classmethod
def Create(cls):
newDbObjectId = cls.LastId.increment()
dbObject = cls(newDbObjectId)
dbObject.createDate.set(datetime.now())
return dbObject
@classmethod
def Get(cls, dbObjectId):
assert dbObjectId
if not cls._Reg_raw.contains(dbObjectId): return None
return cls(dbObjectId)
def delete(self):
for value in self.__dict__.values():
if isinstance(value, DbField):
dbField = value
dbField.destroy()
def __init__(self, dbObjectId):
assert dbObjectId
self._dbObjectId = str(dbObjectId)
self.createDate = ScoreDbField(self.DbObjectName, self._dbObjectId, F_CREATE_DATE, self.Reg, self, DateTimeSerializator)
self.tag = ScalarDbField(self.DbObjectName, self._dbObjectId, F_TAG)
def __hash__(self):
return hash(self._dbObjectId)
def __str__(self):
return self._dbObjectId
def __eq__(self, other):
if type(self) != type(other): return False
return (self.DbObjectName == other.DbObjectName) and (self._dbObjectId == other._dbObjectId)
def __ne__(self, other):
return not self.__eq__(other)
id = property(lambda self: self._dbObjectId)
As you can see, class fields defining in TypeInit
, which is called right after class creates. This trick provides inheritance support, so DbObject
's derivative classes will contain LastId
and Reg
but for its own DbObjectName
.
Is there way to make this more pythonic? I think this can be implemented with decorators, but I'm not sure it will be better.
Here are some DbField
s:
class DbField():
_Redis = None
@classmethod
def Connect(cls, host=None, port=None):
cls._Redis = Redis(host or 'localhost', port or 6379, decode_responses =True)
def __init__(self, dbObjectName, dbObjectId, fieldName, valueSerializator=None):
assert dbObjectName
assert fieldName
self._dbObjectName = dbObjectName
self._dbObjectId = dbObjectId
self._fieldName = fieldName
if self._dbObjectId:
self._key = "%s:%s:%s" % (self._dbObjectName, self._dbObjectId, self._fieldName)
else:
self._key = "%s:%s" % (self._dbObjectName, self._fieldName)
self._valueSerializator = valueSerializator or Serializator
def exists(self):
return self._Redis.exists(self._key)
def destroy(self):
return self._Redis.delete(self._key)
class HashDbField(DbField):
def __init__(self, dbObjectName, dbObjectId, fieldName, valueSerializator=None, nameSerializator=None):
super(HashDbField, self).__init__(dbObjectName, dbObjectId, fieldName, valueSerializator)
self._nameSerializator = nameSerializator or Serializator
def set(self, name, value, overwrite=True):
return (self._Redis.hset if overwrite else self._Redis.hsetnx)(
self._key, self._nameSerializator.serialize(name), self._valueSerializator.serialize(value)
)
def get(self, name):
return self._valueSerializator.restore(
self._Redis.hget(self._key, self._nameSerializator.serialize(name))
)
def delete(self, name):
return self._Redis.hdel(self._key, self._nameSerializator.serialize(name))
class SortedSetDbField(DbField):
def __init__(self, dbObjectName, dbObjectId, fieldName, valueSerializator=None, scoreSerializator=None):
super(SortedSetDbField, self).__init__(dbObjectName, dbObjectId, fieldName, valueSerializator)
self._scoreSerializator = scoreSerializator or Serializator
def add(self, value, score):
self._Redis.zadd(self._key, self._valueSerializator.serialize(value), self._scoreSerializator.serialize(score))
def getRange(self, start=0, end=-1, desc=False):
return map(self._valueSerializator.restore, self._Redis.zrange(self._key, start=start, end=end, desc=desc))
def getScore(self, value):
return self._scoreSerializator.restore(self._Redis.zscore(self._key, self._valueSerializator.serialize(value)))
def setScore(self, value, score):
self.add(value, score)
def indexOf(self, value):
return self._Redis.zrank(self._key, self._valueSerializator.serialize(value))
def contains(self, value):
return self.indexOf(value) is not None
def delete(self, value):
self._Redis.zrem(self._key, self._valueSerializator.serialize(value))
def count(self):
return self._Redis.zcard(self._key)
def union(self, sortedSetDbField):
assert isinstance(sortedSetDbField, SortedSetDbField)
self._Redis.zunionstore(self._key, (self._key, sortedSetDbField._key), aggregate='max')
def subtract(self, sortedSetDbField):
assert isinstance(sortedSetDbField, SortedSetDbField)
self._Redis.zunionstore(self._key, {self._key: 1, sortedSetDbField._key: -1}, aggregate='min')
self._Redis.zremrangebyscore(self._key, '-inf', 0)
class ScalarDbField(DbField):
def set(self, value, overwrite=True):
return (self._Redis.set if overwrite else self._Redis.setnx)(self._key, self._valueSerializator.serialize(value))
def get(self):
return self._valueSerializator.restore(self._Redis.get(self._key))
def increment(self, amount=1):
return self._Redis.incr(self._key, amount)
class RefValueDbField(ScalarDbField):
def __init__(self, dbObjectName, dbObjectId, fieldName, targetHashDbField, targetHashDbFieldValue, valueSerializator=None):
super(RefValueDbField, self).__init__(dbObjectName, dbObjectId, fieldName, valueSerializator)
self._targetHashDbField = targetHashDbField
self._targetHashDbFieldValue = targetHashDbFieldValue
def set(self, name, overwrite=False):
if not self._targetHashDbField.set(name, self._targetHashDbFieldValue, overwrite):
return False
oldName = self.get()
super(RefValueDbField, self).set(name)
if oldName is not None: self._targetHashDbField.delete(oldName)
return True
def destroy(self):
name = self.get()
if name is not None: self._targetHashDbField.delete(name)
super(RefValueDbField, self).destroy()
class ScoreDbField(ScalarDbField):
def __init__(self, dbObjectName, dbObjectId, fieldName, targetSortedSetDbField, targetSortedSetDbFieldValue, valueSerializator=None):
assert isinstance(targetSortedSetDbField, SortedSetDbField)
super(ScoreDbField, self).__init__(dbObjectName, dbObjectId, fieldName, valueSerializator)
self._targetSortedSetDbField = targetSortedSetDbField
self._targetSortedSetDbFieldValue = targetSortedSetDbFieldValue
def set(self, score):
self._targetSortedSetDbField.setScore(self._targetSortedSetDbFieldValue, score)
super(ScoreDbField, self).set(score)
def exists(self):
return self._targetSortedSetDbField.contains(self._targetSortedSetDbFieldValue)
def destroy(self):
self._targetSortedSetDbField.delete(self._targetSortedSetDbFieldValue)
super(ScoreDbField, self).destroy()
I'm in trouble with ScalarDbField
because I have to call get/set methods to get/set values. I can't use it like descriptor to get/set values like in regular variable because it mostly used like instance field, and I don't like to use properties with lambdas:
nick = property(lambda self: self._nick.get(), lambda self, value: self._nick.set(value))
Here is UserDbObject
:
class UserDbObject(DbObject):
DbObjectName = DB_OBJECT_NAME
@classmethod
def _TypeInit(cls):
super(cls, cls)._TypeInit()
cls.Nicks = HashDbField(cls.DbObjectName, None, R_NICKS, cls._SelfDbObjectSerializator, nameSerializator=IgnoreCaseSerializator)
cls.Emails = HashDbField(cls.DbObjectName, None, R_EMAILS, cls._SelfDbObjectSerializator, nameSerializator=IgnoreCaseSerializator)
cls.AuthKeys = HashDbField(cls.DbObjectName, None, R_AUTH_KEYS, cls._SelfDbObjectSerializator)
cls.LastLoginDates = SortedSetDbField(cls.DbObjectName, None, R_LAST_LOGIN_DATES, cls._SelfDbObjectSerializator, DateTimeSerializator)
cls.LastActiveDates = SortedSetDbField(cls.DbObjectName, None, R_LAST_ACTIVE_DATES, cls._SelfDbObjectSerializator, DateTimeSerializator)
@classmethod
def Create(cls, nick, email, password, name, surname):
assert nick
assert email
assert password
userDbObject = super(UserDbObject, cls).Create()
def setRefFields():
if not userDbObject.nick.set(nick): return False
if not userDbObject.email.set(email): return False
return True
if not setRefFields():
userDbObject.delete()
return None
userDbObject.generateNewAuthKey()
userDbObject.setPassword(password)
if name: userDbObject.name.set(name)
if surname: userDbObject.surname.set(surname)
return userDbObject
@classmethod
def Get(cls, dbObjectId=None, nick=None, email=None, authKey=None):
if dbObjectId:
return super(UserDbObject, cls).Get(dbObjectId)
if nick:
return cls.Nicks.get(nick)
if email:
return cls.Emails.get(email)
if authKey:
return cls.AuthKeys.get(authKey)
return None
def setPassword(self, password):
salt = generateRandomKey(SALT_LENGTH)
self._passwordHash.set(countCoolHash(password, salt))
self._salt.set(salt)
def checkPassword(self, password):
return self._passwordHash.get() == countCoolHash(password, self._salt.get())
def generateNewAuthKey(self):
while True:
if self.authKey.set(generateRandomKey(AUTH_KEY_LENGTH)): break
def __init__(self, dbObjectId):
super(UserDbObject, self).__init__(dbObjectId)
self.nick = RefValueDbField(self.DbObjectName, self._dbObjectId, F_NICK, self.Nicks, self)
self.email = RefValueDbField(self.DbObjectName, self._dbObjectId, F_EMAIL, self.Emails, self)
self.authKey = RefValueDbField(self.DbObjectName, self._dbObjectId, F_AUTH_KEY, self.AuthKeys, self)
self._passwordHash = ScalarDbField(self.DbObjectName, self._dbObjectId, F_PASSWORD_HASH)
self._salt = ScalarDbField(self.DbObjectName, self._dbObjectId, F_SALT)
self.name = ScalarDbField(self.DbObjectName, self._dbObjectId, F_NAME)
self.surname = ScalarDbField(self.DbObjectName, self._dbObjectId, F_SURNAME)
self.lastLoginDate = ScoreDbField(self.DbObjectName, self._dbObjectId, F_LAST_LOGIN_DATE, self.LastLoginDates, self, DateTimeSerializator)
self.lastActiveDate = ScoreDbField(self.DbObjectName, self._dbObjectId, F_LAST_ACTIVE_DATE, self.LastActiveDates, self, DateTimeSerializator)
self.posts = SortedSetDbField(self.DbObjectName, self._dbObjectId, F_POSTS, self._PostDbObjectSerializator, DateTimeSerializator)
self.followers = SortedSetDbField(self.DbObjectName, self._dbObjectId, F_FOLLOWERS, self._UserDbObjectSerializator, DateTimeSerializator)
self.followingUsers = SortedSetDbField(self.DbObjectName, self._dbObjectId, F_FOLLOWING_USERS, self._UserDbObjectSerializator, DateTimeSerializator)
self.feed = SortedSetDbField(self.DbObjectName, self._dbObjectId, F_FEED, self._PostDbObjectSerializator, DateTimeSerializator)
How can you help me improve this code?
SortedSetDbField.get_score
andUserDbObject.create
. \$\endgroup\$