2013-01-23 3 views
8

Aggiornamento:SQLAlchemy bidirezionale rapporto di associazione delega

Per chiunque abbia questo problema, con il very latest SQLAlchemy è stato risolto questo comportamento.

problema originale:

Sto avendo un problema con ottenere i proxy di associazione per aggiornare correttamente.

Utilizzando i modelli di esempio qui: http://docs.sqlalchemy.org/en/rel_0_7/orm/extensions/associationproxy.html#simplifying-association-objects

Ma cambiare UserKeyword con questa linea:

keyword = relationship("Keyword", backref=backref("user_keywords", cascade="all, delete-orphan")) 

e l'aggiunta di questo per parola chiave:

users = association_proxy('user_keywords', 'user') 

Così un'istanza parola chiave ha una lista di utenti.

le seguenti funzioni come previsto:

>>> rory = User("rory") 
>>> session.add(rory) 
>>> chicken = Keyword('chicken') 
>>> session.add(chicken) 
>>> rory.keywords.append(chicken) 
>>> chicken.users 
[<__main__.User object at 0x1f1c0d0>] 
>>> chicken.user_keywords 
[<__main__.UserKeyword object at 0x1f1c450>] 

Ma traslochi fanno cose strane. La rimozione dalle liste associazione procura in questo modo:

>>> rory.keywords.remove(chicken) 

causa un errore di integrità come SA tenta di impostare una delle colonne chiave esterna NULL.

Fare questo:

>>> rory.user_keywords.remove(rory.user_keywords[0]) 

I risultati in questo:

>>> chicken.users 
[None] 

ho mancato qualche cosa di evidente non ho?

risposta

7

UserKeyword richiede che sia associato ad entrambi uno Keyword e User allo stesso tempo, al fine di essere persistente. Quando lo associ a uno User e Keyword, ma lo rimuovi dalla raccolta User.user_keywords, è ancora associato allo Keyword.

>>> rory.keywords.remove(chicken) 

# empty as we expect 
>>> rory.user_keywords 
[] 

# but the other side, still populated. UserKeyword 
# has no User, but still has Keyword 
>>> chicken.user_keywords 
[<__main__.UserKeyword object at 0x101748d10>] 

# but the User on that UserKeyword is None 
>>> chicken.user_keywords[0].user is None 
True 

# hence accessing the "association" gives us None 
# as well 
>>> chicken.users 
[None] 

Quindi, se dovessimo flush() questo proprio ora, hai un UserKeyword oggetto pronto ad andare ma non ha User su di esso, in modo da ottenere quell'errore NULL. Al momento dell'INSERTO, l'oggetto non è considerato un "orfano" a meno che non sia associato a qualsiasi raccolta Keyword.user_keywordsoUser.user_keywords. Solo se dovessi dire, del chicken.user_keywords[0] o equivalente, vedresti che non viene generato INSERT e l'oggetto UserKeyword viene dimenticato.

Se si desidera scaricare l'oggetto nel database prima di rimuoverlo da "rory", le cose cambiano. Lo UserKeyword è ora persistente e quando si rimuove "chicken" da "rory".parole chiave", un "delete-orphan" generato l'evento off che fa eliminare il UserKeyword, anche se ancora è associato all'oggetto Keyword:

rory.keywords.append(chicken) 

session.flush() 

rory.keywords.remove(chicken) 

session.flush() 

si vede lo SQL:

INSERT INTO "user" (name) VALUES (%(name)s) RETURNING "user".id 
{'name': 'rory'} 

INSERT INTO keyword (keyword) VALUES (%(keyword)s) RETURNING keyword.id 
{'keyword': 'chicken'} 

INSERT INTO user_keyword (user_id, keyword_id, special_key) VALUES (%(user_id)s, %(keyword_id)s, %(special_key)s) 
{'keyword_id': 1, 'special_key': None, 'user_id': 1} 

DELETE FROM user_keyword WHERE user_keyword.user_id = %(user_id)s AND user_keyword.keyword_id = %(keyword_id)s 
{'keyword_id': 1, 'user_id': 1} 

Ora una persona ragionevole chiederebbe "non è incoerente?" E al momento direi "assolutamente". Devo esaminare i casi di test per vedere quale sia la ragione per questa differenza di comportamento, I ' Ho identificato nel codice perché si verifica in questo modo e sono abbastanza sicuro di questa differenza nel modo in cui un "orfano" è considerato eretto per oggetti "persistenti" o "persistenti" è intenzionale, ma in questa particolare permutazione ovviamente produce un risultato strano. Potrei fare una modifica in 0.8 per questo se riesco a trovarne uno che sia fattibile.

modifica: http://www.sqlalchemy.org/trac/ticket/2655 riassume il problema a cui dovrò pensare. Esiste un test per questo comportamento in particolare, è necessario risalire alla sua origine.

+0

Grazie heap zzzeek. –