9

If I have "a.py"

from google.appengine.ext import db
class A(db.Model):
    db.ReferenceProperty(b.B)
    ...other stuff

and another file "b.py"

from google.appengine.ext import db
class B(db.Model):
    db.ReferenceProperty(a.A)
    ...other stuff

It would appear that Python simply does not allow circular dependencies. Normally I guess you would alter the code such that the two classes actually can resolve themselves without importing one another directly. Perhaps by consolidating their reference to one another through a third intermediary? But I can't just use a normal intermediary class, as all classes would ultimately need to be persisted to the database? Is there any correct solution to structuring the above code such that it works?

I have a feeling that I am going to get a lot of "bad smelling code", "decouple", "bad design", etc comments. So I ask that if you say that, please illustrate what you would do with an actual example. Are there any solutions that would involve leaving the references, classes, and modules as they stand?

Thank you.

Stephen Cagle
  • 14,124
  • 16
  • 55
  • 86
  • As you say, bad code smell - why do you want to have circular references between classes outside of a common module, aside from natural programmer laze? – Mike Burton Dec 15 '09 at 23:37
  • 1
    You need this when you want a one to one relationship where two persistent objects are able to refer to one another. – Peter Recore Dec 16 '09 at 02:57
  • Well specifically, I have a Question (db.Model entity), that is supposed to have one or more Answers (db.Model entity). Each of these Answers may however point to another Question, used for further clarification, or to a Result (db.Model entity). – Stephen Cagle Dec 16 '09 at 02:58
  • 1
    +1 because there are actually situations where these circular references are needed for the best solution. – noio Jan 26 '12 at 16:30
  • This is like working in C, but not being allowed headers. – bobobobo Apr 09 '13 at 20:53

4 Answers4

7

The workaround is to have a ReferenceProperty in at least one of the models that doesn't restrict itself to a particular class, and then enforce only referencing that class in your own code.

e.g.,

class A(db.Model):
  b = db.ReferenceProperty()

class B(db.Model):
  a = db.ReferenceProperty(A)

You'll be able to assign any model instance to the b variable; just make sure you only assign actual Bs.

Wooble
  • 87,717
  • 12
  • 108
  • 131
  • 2
    That is what I have done. It is kind of lame, I mean I was hoping to maybe re-open one of the classes and then "inject" a reference attribute after they were both instantiated, but whatever. I suppose this will be the correct answer unless I see something better. – Stephen Cagle Dec 16 '09 at 06:35
1

What happens if you define both models in the same module? e.g. a_b.py

Chase Seibert
  • 15,703
  • 8
  • 51
  • 58
  • 3
    I did give that a try. Same thing though, the problem isn't that they are in separate files (I believe) it is that they reference one another. – Stephen Cagle Dec 16 '09 at 00:02
1

According to the documentation:

ReferenceProperty has another handy feature: back-references. When a model has a ReferenceProperty to another model, each referenced entity gets a property whose value is a Query that returns all of the entities of the first model that refer to it.

So you should probably be able to use the automatically added back-reference.

codeape
  • 97,830
  • 24
  • 159
  • 188
  • This is exactly what you need. – Peter Recore Dec 16 '09 at 02:55
  • Unfortunately I am trying to keep A and B in a sort of tree structure, so things can reference one another from the root to leaf direction, but not from the leaf to root direction. Your solution to have the "leaves" refer to the "roots" and then use the auto generated back-reference from the "roots" to the "leaves" might solve the circular dependency, but would intwine the tree. – Stephen Cagle Dec 16 '09 at 03:02
  • A backreference is not the same as a reference in the opposite direction. – Nick Johnson Dec 17 '09 at 09:55
0

Technically speaking, you can replace the circular dependency with an intersection table.

In my case, I have denormalized models, Player and Match.

The relationship between Matches and Players is many-to-many, (a Player has played one-or-more matches, and a Match references one-or-more players).

What I needed:

class Match(db.Model):
    p1 = db.ReferenceProperty( Player )
    p2 = db.ReferenceProperty( Player )

class Player(db.Model):
    # Remember what match player is currently in, for victor reporting
    currentMatch = db.ReferenceProperty( Match )

Option 0: What Wooble suggested

Option 1: Normalize it (use an intersection table)

class Match(db.Model):
    # ...

class Player(db.Model):
    # ...

# Every match has multiple entries here (as many 1 for
# each player-in-match entry).  This will make retrieval
# slower, but more-correct (it is "normalized" now)
class PlayersInMatches(db.Model):
    player=db.ReferenceProperty(Player)
    match=db.ReferenceProperty(Match)
    isCurrent=db.BooleanProperty()
Community
  • 1
  • 1
bobobobo
  • 64,917
  • 62
  • 258
  • 363