redisco
redisco copied to clipboard
Recursion problem with circuits model references and Model.__repr__
I'm trying to use ListField of ReferenceField
to maintain a list of user channels of my User model
as well as a list of users in my Channel model.
Model:
# Module: models
# Date: 16th August 2014
# Author: James Mills, prologic at shortcircuit dot net dot au
"""Data Models"""
from circuits.protocols.irc import joinprefix
from redisco.models import Model
from redisco.models import (
Attribute, BooleanField, DateTimeField,
IntegerField, ListField, ReferenceField
)
class User(Model):
host = Attribute(default="")
port = IntegerField(default=0)
nick = Attribute(default=None)
away = BooleanField(default=False)
channels = ListField("Channel")
userinfo = ReferenceField("UserInfo")
registered = BooleanField(default=False)
signon = DateTimeField(auto_now_add=True)
@property
def prefix(self):
userinfo = self.userinfo
return joinprefix(self.nick, userinfo.user, userinfo.host)
class Meta:
indices = ("id", "nick",)
class UserInfo(Model):
user = Attribute(default=None)
host = Attribute(default=None)
server = Attribute(default=None)
name = Attribute(default=None)
def __nonzero__(self):
return all(x is not None for x in (self.user, self.host, self.name))
class Channel(Model):
name = Attribute(required=True, unique=True)
users = ListField("User")
class Meta:
indices = ("id", "name",)
This seems to work fine when there is one User and one Channel
object(s) in the database, but as soon as there are two different users
that are members of the same channel I get this error:
2014-08-16 22:02:51,218 - charla.main - ERROR - ERROR <handler[*.join] (Commands.join)> (<join[commands] (<socket._socketobject object at 0x7f956fb54c20>, ('', None, None), '#circuits' )>) (<type 'exceptions.RuntimeError'>): RuntimeError('maximum recursion depth exceeded while calling a Python object',)
File "/home/prologic/work/circuits/circuits/core/manager.py", line 603, in _dispatcher
value = handler(*eargs, **ekwargs)
File "/home/prologic/charla/charla/plugins/core.py", line 111, in join
user.save()
File "/home/prologic/.virtualenvs/charla/lib/python2.7/site-packages/redisco/models/base.py", line 202, in save
self._write(_new)
File "/home/prologic/.virtualenvs/charla/lib/python2.7/site-packages/redisco/models/base.py", line 345, in _write
self._update_indices(pipeline)
File "/home/prologic/.virtualenvs/charla/lib/python2.7/site-packages/redisco/models/base.py", line 413, in _update_indices
self._add_to_indices(pipeline)
File "/home/prologic/.virtualenvs/charla/lib/python2.7/site-packages/redisco/models/base.py", line 418, in _add_to_indices
self._add_to_index(att, pipeline=pipeline)
File "/home/prologic/.virtualenvs/charla/lib/python2.7/site-packages/redisco/models/base.py", line 426, in _add_to_index
index = self._index_key_for(att)
File "/home/prologic/.virtualenvs/charla/lib/python2.7/site-packages/redisco/models/base.py", line 474, in _index_key_for
return self._tuple_for_index_key_attr_list(att, value)
File "/home/prologic/.virtualenvs/charla/lib/python2.7/site-packages/redisco/models/base.py", line 492, in _tuple_for_index_key_attr_list
return ('list', [self._index_key_for_attr_val(att, e) for e in val])
File "/home/prologic/.virtualenvs/charla/lib/python2.7/site-packages/redisco/models/base.py", line 499, in _index_key_for_attr_val
return self._key[att][_encode_key(val)]
File "/home/prologic/.virtualenvs/charla/lib/python2.7/site-packages/redisco/models/utils.py", line 5, in _encode_key
return base64.b64encode(str(s)).replace("\n", "")
File "/home/prologic/.virtualenvs/charla/lib/python2.7/site-packages/redisco/models/base.py", line 516, in __repr__
return "<%s %s>" % (self.key(), self.attributes_dict)
File "/home/prologic/.virtualenvs/charla/lib/python2.7/site-packages/redisco/models/base.py", line 516, in __repr__
return "<%s %s>" % (self.key(), self.attributes_dict)
... repeated lines delete
File "/home/prologic/.virtualenvs/charla/lib/python2.7/site-packages/redisco/models/base.py", line 516, in __repr__
return "<%s %s>" % (self.key(), self.attributes_dict)
File "/home/prologic/.virtualenvs/charla/lib/python2.7/site-packages/redisco/models/base.py", line 247, in attributes_dict
h[k] = getattr(self, k)
File "/home/prologic/.virtualenvs/charla/lib/python2.7/site-packages/redisco/models/attributes.py", line 273, in __get__
val = List(key).members
File "/home/prologic/.virtualenvs/charla/lib/python2.7/site-packages/redisco/containers.py", line 34, in __getattribute__
return object.__getattribute__(self, att)
File "/home/prologic/.virtualenvs/charla/lib/python2.7/site-packages/redisco/containers.py", line 217, in all
return self.lrange(0, -1)
File "/home/prologic/.virtualenvs/charla/lib/python2.7/site-packages/redisco/containers.py", line 32, in __getattribute__
return partial(getattr(object.__getattribute__(self, 'db'), att), self.key)
File "/home/prologic/.virtualenvs/charla/lib/python2.7/site-packages/redisco/containers.py", line 39, in db
if self.pipeline:
File "/home/prologic/.virtualenvs/charla/lib/python2.7/site-packages/redisco/containers.py", line 31, in __getattribute__
if att in object.__getattribute__(self, 'DELEGATEABLE_METHODS'):
I seem to have worked around the problem
by overriding the Model.__repr__() replacing
circular model references by a simple list of strings.
Example:
# Module: models
# Date: 16th August 2014
# Author: James Mills, prologic at shortcircuit dot net dot au
"""Data Models"""
from operator import attrgetter
from socket import socket, error as SocketError
from bidict import bidict
from circuits.protocols.irc import joinprefix
from redisco.models import Model
from redisco.models import (
Attribute, BooleanField, DateTimeField,
IntegerField, ListField, ReferenceField
)
class SocketField(Attribute):
cache = bidict()
def typecast_for_read(self, value):
return self.cache[int(value)]
def typecast_for_storage(self, value):
if value is None:
return None
try:
fd = value.fileno()
self.cache[fd] = value
return fd
except SocketError:
try:
fd = self.cache[:value]
self.cache[fd] = value
return fd
except KeyError:
return None
def value_type(self):
return socket
def acceptable_types(self):
return self.value_type()
class User(Model):
sock = SocketField(required=True)
host = Attribute(default="")
port = IntegerField(default=0)
nick = Attribute(default=None)
away = Attribute(default=None)
channels = ListField("Channel")
userinfo = ReferenceField("UserInfo")
registered = BooleanField(default=False)
signon = DateTimeField(auto_now_add=True)
def __repr__(self):
attrs = self.attributes_dict.copy()
attrs["channels"] = map(attrgetter("name"), attrs["channels"])
if not self.is_new():
return "<%s %s>" % (self.key(), attrs)
return "<%s %s>" % (self.__class__.__name__, attrs)
@property
def prefix(self):
userinfo = self.userinfo
if userinfo is None:
return
return joinprefix(self.nick, userinfo.user, userinfo.host)
class Meta:
indices = ("id", "sock", "nick",)
class UserInfo(Model):
user = Attribute(default=None)
host = Attribute(default=None)
server = Attribute(default=None)
name = Attribute(default=None)
def __nonzero__(self):
return all(x is not None for x in (self.user, self.host, self.name))
class Channel(Model):
name = Attribute(required=True, unique=True)
users = ListField("User")
def __repr__(self):
attrs = self.attributes_dict.copy()
attrs["users"] = map(attrgetter("nick"), attrs["users"])
if not self.is_new():
return "<%s %s>" % (self.key(), attrs)
return "<%s %s>" % (self.__class__.__name__, attrs)
class Meta:
indices = ("id", "name",)
Note: The two custom __repr__ quite similar to that of Model.__repr__ but I replace the circular references.
At least in testing, the recursion loop was being caused by recursive repr()'s of the attributes which have circular references. I'm not sure if this can be solved in the general case and Model.__repr__ can be made to handle this case or not.