CircularDependencyError, tried use_alter and inverse.

270 views
Skip to first unread message

Jakob D.

unread,
Feb 17, 2012, 4:44:10 AM2/17/12
to sqlal...@googlegroups.com
From what I've read these errors are usually solved by adding use_alter=True and inverse.

It doesn't work for me though.

I have ridiculously many relations between two tables. But that's how it is and I cannot refactor the model at this point. 

This is part of the model right now:
class Project(Entity):
id = Field(String(100), primary_key=True)
workers = OneToMany('User') 
client = ManyToOne('User')
creator = ManyToOne('User')
class User(Entity):
username = Field(String(80),  primary_key=True)
assigned_project = ManyToOne('Project', use_alter=True, inverse='user_account')
owned_projects = OneToMany('Project', use_alter=True, inverse='client')
created_projects = OneToMany('Project', inverse='creator')

The problem occurs when you both own and created the project and try to assign yourself to it:
  File "/usr/sbin/admin-web", line 25, in service
    Application.service(self)
  File "/usr/lib/python2.5/site-packages/smisk/mvc/__init__.py", line 879, in service
    rsp = self._call_leaf_and_handle_model_session(req_args, req_params)
  File "/usr/lib/python2.5/site-packages/smisk/mvc/__init__.py", line 706, in _call_leaf_and_handle_model_session
    rsp = self._call_leaf(req_args, req_params)
  File "/usr/lib/python2.5/site-packages/smisk/mvc/__init__.py", line 695, in _call_leaf
    return self._leaf_filter(*args, **params)
  File "/usr/lib/python2.5/site-packages/smisk/mvc/decorators.py", line 72, in f
    return filter(leaf, *va, **kw)
  File "/var/mysite/lib/mysite/admin_web/__init__.py", line 139, in add_authorized_user_to_rsp
    return leaf(*va, **kw)
  File "/usr/lib/python2.5/site-packages/smisk/mvc/__init__.py", line 298, in <lambda>
    self._leaf_filter = lambda *va, **kw: filter(self.destination)(*va, **kw)
  File "/usr/lib/python2.5/site-packages/smisk/mvc/decorators.py", line 72, in f
    return filter(leaf, *va, **kw)
  File "/var/mysite/lib/mysite/admin_web/__init__.py", line 139, in add_authorized_user_to_rsp
    return leaf(*va, **kw)
  File "/usr/lib/python2.5/site-packages/smisk/mvc/routing.py", line 65, in __call__
    return self._call_leaf(*args, **params)
  File "/usr/lib/python2.5/site-packages/smisk/mvc/routing.py", line 59, in _call_leaf
    return self.leaf(*args, **params)
  File "/var/mysite/lib/smisk117compat.py", line 15, in f
    return filter(leaf, *va, **kw)
  File "/var/mysite/lib/smisk117compat.py", line 81, in _require
    return leaf(*va, **kw)
  File "/var/mysite/lib/mysite/admin_web/__init__.py", line 1871, in assign_project
    elixir.session.commit()
  File "/var/lib/python-support/python2.5/sqlalchemy/orm/scoping.py", line 98, in do
    return getattr(self.registry(), name)(*args, **kwargs)
  File "/var/lib/python-support/python2.5/sqlalchemy/orm/session.py", line 557, in commit
    self.transaction.commit()
  File "/var/lib/python-support/python2.5/sqlalchemy/orm/session.py", line 262, in commit
    self._prepare_impl()
  File "/var/lib/python-support/python2.5/sqlalchemy/orm/session.py", line 246, in _prepare_impl
    self.session.flush()
  File "/var/lib/python-support/python2.5/sqlalchemy/orm/session.py", line 789, in flush
    self.uow.flush(self, objects)
  File "/var/lib/python-support/python2.5/sqlalchemy/orm/unitofwork.py", line 233, in flush
    flush_context.execute()
  File "/var/lib/python-support/python2.5/sqlalchemy/orm/unitofwork.py", line 442, in execute
    tasks = self._sort_dependencies()
  File "/var/lib/python-support/python2.5/sqlalchemy/orm/unitofwork.py", line 480, in _sort_dependencies
    for t in task._sort_circular_dependencies(self, [self.get_task_by_mapper(i) for i in cycles]):
  File "/var/lib/python-support/python2.5/sqlalchemy/orm/unitofwork.py", line 733, in _sort_circular_dependencies
    head = topological.sort_as_tree(tuples, allobjects)
  File "/var/lib/python-support/python2.5/sqlalchemy/topological.py", line 59, in sort_as_tree
    return _organize_as_tree(_sort(tuples, allitems, allow_cycles=with_cycles))
  File "/var/lib/python-support/python2.5/sqlalchemy/topological.py", line 216, in _sort
    raise CircularDependencyError("Circular dependency detected " + repr(edges) + repr(queue))
CircularDependencyError: Circular dependency detected [(<sqlalchemy.orm.attributes.InstanceState object at 0xa6fa84c>, <sqlalchemy.orm.attributes.InstanceState object at 0xa6fadcc>), (<sqlalchemy.orm.attributes.InstanceState object at 0xa6fadcc>, <sqlalchemy.orm.attributes.InstanceState object at 0xa5ae3ac>), (<sqlalchemy.orm.attributes.InstanceState object at 0xa6fadcc>, <sqlalchemy.orm.attributes.InstanceState object at 0xa6fa84c>)][]

I've tried removing the creator reference and only store a string and had a property that gets the object through a query but get the same error:
@Property
def creator():
def fget(self):
return User.get_by(username=self.creator_name)

def fset(self, value):
if isinstance(value, User):
self.creator_name = value.name

return locals()

Any ideas on what I can try next?

Thx, 
JD

Jakob D.

unread,
Feb 17, 2012, 9:30:08 AM2/17/12
to sqlal...@googlegroups.com
Finally found a link that wasn't broken: http://docs.sqlalchemy.org/en/rel_0_5/mappers.html#rows-that-point-to-themselves-mutually-dependent-rows

But I tried using post_update=True as well, but it gave the same error:

class Project(Entity):
id = Field(String(100), primary_key=True)
workers     = OneToMany('User') 
client = ManyToOne('User', post_update=True, inverse='owned_projects')
creator = ManyToOne('User', post_update=True, inverse='created_projects')
class User(Entity):
username            = Field(String(80),  primary_key=True)
assigned_project  = ManyToOne('Project', post_update=True, use_alter=True, inverse='workers')
owned_projects    = OneToMany('Project', use_alter=True, inverse='client')
created_projects   = OneToMany('Project', use_alter=True, inverse='creator')

Am I missing something?

Michael Bayer

unread,
Feb 17, 2012, 9:37:20 AM2/17/12
to sqlal...@googlegroups.com
I don't use Elixir but I think it's strange that you are creating both "inverse" as well as distinct OneToMany/ManyToOne's with the same name each time here ("owned_projects", "created_projects", "client", "creator").   I think you probably only have to specify "inverse", assuming that's the same as a backref.

Note the examples at http://docs.sqlalchemy.org/en/latest/orm/relationships.html#one-to-many and similar illustrate how "backref" replaces the usage of relationship().   Elixir is likely similar.

As far as how to specify use_alter and post_update with "inverse", I'm not sure.  In plain SQLAlchemy you use the backref() function.


--
You received this message because you are subscribed to the Google Groups "sqlalchemy" group.
To view this discussion on the web visit https://groups.google.com/d/msg/sqlalchemy/-/xDVBO-iBoRIJ.
To post to this group, send email to sqlal...@googlegroups.com.
To unsubscribe from this group, send email to sqlalchemy+...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/sqlalchemy?hl=en.

Jakob D.

unread,
Feb 17, 2012, 11:14:58 AM2/17/12
to sqlal...@googlegroups.com
The inverse thing was just something I tried to solve this.

But I removed them and tried different ways of specifying use_alter and post_update according to: http://elixir.ematia.de/apidocs/elixir.relationships.html

This is getting ridiculous though, I even removed all of the relationships except for client and I still get the same error:


class Project(Entity):
id = Field(String(100), primary_key=True)
client = ManyToOne('User', post_update=True, inverse='owned_projects')
creator_name = Field(String(80))
@Property
def creator():
def fget(self):
return User.get_by(username=self.creator_name)
def fset(self, value):
if isinstance(value, User):
self.creator_name = value.name
return locals()
class User(Entity):
username         = Field(String(80),  primary_key=True)
project_id = Field(String(100))


@Property
def assigned_project():

def fget(self):
return Project.get_by(id=self.project_id)

def fset(self, value):
if isinstance(value, Project):
self.project_id = value.id

return 

I wish I could try the SQLAlchemy way of doing this, but I'd have to redo the tables since I read that you cannot mix the both on the same table.

Now I obviously only have one relation between the two tables, when setting the project for a user, I don't see how it would cause a CircularDependencyError.

I've checked different relations even though it shouldn't matter. And they seem ok, no circular dependencies. 

Any other ideas? 



Michael Bayer

unread,
Feb 17, 2012, 2:21:46 PM2/17/12
to sqlal...@googlegroups.com
You should at least create a small test of the same segment of the model using the declarative style, to narrow down what's going wrong and at least ensure it works structurally. 

Sent from my iPhone
--
You received this message because you are subscribed to the Google Groups "sqlalchemy" group.
To view this discussion on the web visit https://groups.google.com/d/msg/sqlalchemy/-/Z-kuaTHBrP8J.

Michael Bayer

unread,
Feb 17, 2012, 9:44:37 PM2/17/12
to sqlal...@googlegroups.com

On Feb 17, 2012, at 4:44 AM, Jakob D. wrote:

> From what I've read these errors are usually solved by adding use_alter=True and inverse.
>
> It doesn't work for me though.
>
> I have ridiculously many relations between two tables. But that's how it is and I cannot refactor the model at this point.
>

anyway, it's a little unclear what the difference between Project.user_account and Project.workers is....I created a sample declarative model that illustrates workers/client/creator in both directions, placing the post_updates on the many-to-ones to minimize the extra UPDATE statements and just putting use_alter on User.project_id, so you can see it working here. You'd have to figure out exactly how to make Elixir do the same thing:

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base

Base= declarative_base()


class Project(Base):
__tablename__ = 'project'
id = Column(String(100), primary_key=True)
client_id = Column(String(80), ForeignKey('user.username'))
creator_id = Column(String(80), ForeignKey('user.username'))
workers = relationship("User", backref="assigned_project",
primaryjoin="Project.id==User.project_id")
client = relationship("User", backref="owned_projects",
primaryjoin="Project.client_id==User.username",
post_update=True)
creator = relationship("User", backref="created_projects",
primaryjoin="Project.creator_id==User.username",
post_update=True)

class User(Base):
__tablename__ = 'user'
username = Column(String(80), primary_key=True)
project_id = Column(String(100), ForeignKey("project.id", use_alter=True,
name="fk_user_project_id"))

e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)
s = Session(e)

p1 = Project(id="someproject")
u1 = User(username="someuser")
p1.client = p1.creator = u1
u1.assigned_project = p1
s.add(u1)
s.commit()


Jakob D.

unread,
Feb 18, 2012, 7:30:53 AM2/18/12
to sqlal...@googlegroups.com
Thanks Michael. As always I appreciate the support here.

I'll try it out. It might actually be faster to redo these two tables in the model in plain SQLAlchemy than trying to figure out what's wrong with the current one.

And, uhm, there is no difference between user_account and workers. I just renamed a few fields so that it'd make more sense. But obviously I managed to make it so that it made less sense.

//JD

Michael Bayer

unread,
Feb 18, 2012, 8:49:02 AM2/18/12
to sqlal...@googlegroups.com
also in 0.8 I'm definitely going to make it so that "primaryjoin" thing isn't needed for this kind of case, foreign_keys="foo.id" will be enough, see http://www.sqlalchemy.org/trac/ticket/2411 .


--
You received this message because you are subscribed to the Google Groups "sqlalchemy" group.
To view this discussion on the web visit https://groups.google.com/d/msg/sqlalchemy/-/w3ngcBw7OjMJ.

Jakob D.

unread,
Feb 18, 2012, 11:36:18 AM2/18/12
to sqlal...@googlegroups.com
Awesome =)

Your example definitely works. 

But given my nature, I cannot leave things like this unanswered. So I took a few steps back and debugged some more.
I removed a few fields from the model and added them one by one with elixir. When using reverse I actually had to specify the field on the oposite site this time, it gave me an error otherwise. Last time I tried this I didn't. 
I found that the errors first occurred when I tried to access project.client. 
So I tried adding post_update=True on client this time and it worked. God knows I tried this once already but maybe in combination with other things that wouldn't solve the problem. Or perhaps it was one the OneToMany side if that matters.

So for future reference. You can in fact specify post_update and use_alter in elixir like: client = ManyToOne('UserAccount', post_update=True, use_alter=True)

Thanks again for your help.
//JD
Reply all
Reply to author
Forward
0 new messages