Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recursion problem with circuits model references and Model.__repr__ #39

Open
prologic opened this issue Aug 20, 2014 · 1 comment
Open

Comments

@prologic
Copy link

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'):
@prologic
Copy link
Author

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.

@prologic prologic changed the title Recursion problem with ListField of ReferenceField Recursion problem with circuits model references Aug 20, 2014
@prologic prologic changed the title Recursion problem with circuits model references Recursion problem with circuits model references and Model.__repr__ Aug 20, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant