2014-05-08 7 views
7

Jak przekonwertować wynik obiektu SQLAlchemy orm do formatu JSON?Konwersja SqlAlchemy wynik orm do dyktowania

Obecnie używam odbicie sqlalchemy do odzwierciedlenia tabel z DB. Zastanów się, czy mam tabelę użytkownika i tabelę adresów. Zastanawiam się nad DB. Jednostka użytkownika ma relację jeden do jednego z encją adresu. Poniżej znajduje się kod odzwierciedlający tabelę z bazy danych i wykorzystujący klasę mapper do odwzorowania relacji.

from sqlalchemy import Table 
from sqlalchemy.orm import mapper, relationship 
user_reflection = Table('user', metadata, autoload=True, autoload_with=engine) 
class User(object): 
    def __init__(self, id, name, dob): 
     self.id = id 
     self.name = name 
     self.dob = dob 
address_reflection = Table('address', metadata, autoload=True, autoload_with=engine) 
mapper(User, 
     user_reflection, 
     properties={ 
      'address': relationship(SourceAddress, uselist=False) 
     } 
) 

Teraz kiedy kwerendy obiektu przy sqlalchemy orm

user = session.query(User).first() 
user_dict = object_to_dict(user) 

Teraz, gdy chcę, aby przekształcić obiekt użytkownika do dict użyć poniższego sposobu

def object_to_dict(obj): 
    columns = [column.key for column in class_mapper(obj.__class__).columns] 
    get_key_value = lambda c: (c, getattr(obj, c).isoformat()) if isinstance(getattr(obj, c), datetime) else (c, getattr(obj, c)) 
    return dict(map(get_key_value, columns)) 

jednak metody object_to_dict działają poprawnie i zwracają prawidłowy obiekt dic, jeśli zwrócono obiekt użytkownika, który nie ma relacji z inną tabelą. Jeśli obiekt użytkownika ma relację, metoda object_to_dict nie powoduje automatycznego rozwinięcia obiektu relacji i przekonwertowania go na dict.

Czy ktoś mógłby zasugerować mi, w jaki sposób mógłbym automatycznie określić, czy zwracany obiekt użytkownika ma relację i rozwinąć obiekt relacji do dyktatury, czy ma taki i tak dalej dla dowolnej liczby obiektów podrzędnych.

Odpowiedz

9

Możesz użyć właściwości relacji programu odwzorowującego. Wybór kodu zależy od tego, jak chcesz mapować dane i jak wyglądają twoje relacje. Jeśli masz wiele relacji rekurencyjnych, możesz użyć licznika max_depth. Mój przykład poniżej wykorzystuje zestaw relacji, aby zapobiec pętli rekursywnej. Możesz całkowicie wyeliminować rekurencję, jeśli planujesz tylko dogłębnie, ale powiedziałeś "i tak dalej".

def object_to_dict(obj, found=None): 
    if found is None: 
     found = set() 
    mapper = class_mapper(obj.__class__) 
    columns = [column.key for column in mapper.columns] 
    get_key_value = lambda c: (c, getattr(obj, c).isoformat()) if isinstance(getattr(obj, c), datetime) else (c, getattr(obj, c)) 
    out = dict(map(get_key_value, columns)) 
    for name, relation in mapper.relationships.items(): 
     if relation not in found: 
      found.add(relation) 
      related_obj = getattr(obj, name) 
      if related_obj is not None: 
       if relation.uselist: 
        out[name] = [object_to_dict(child, found) for child in related_obj] 
       else: 
        out[name] = object_to_dict(related_obj, found) 
    return out 

Należy również pamiętać, że należy wziąć pod uwagę problemy z wydajnością. Możesz chcieć użyć opcji takich jak linkedload lub subqueryload, aby zapobiec wykonywaniu nadmiernej liczby zapytań SQL.

3

Pomimo „adibies doog” Odpowiedź została zaakceptowana i upvoted go, ponieważ był bardzo pomocny, istnieje kilka problemów znanych w algorytmie:

  1. Sub-serializacji relacji zatrzymuje się na pierwszym dziecko (z powodu przedwczesnego oprócz „found”)
  2. serializes również powrót relacje, które w większości przypadków nie są desiderable (jeśli masz Father obiekt o stosunku do Son ze skonfigurowanym backref, będzie generować dodatkowy Father węzeł dla każdego syna w nim, z same dane, że głównym Father obiekt zapewnia już!)

Aby rozwiązać te problemy, ja zdefiniowany inny set() do śledzenia relacji niepożądanych pleców i przeniosłem śledzenie odwiedzanych dzieci później w kodzie. Zmieniłem też nazwy zmiennych, aby wyjaśnić (oczywiście IMO), co one reprezentują i jak działa algorytm, i zastąpić map() czystszym zrozumieniem słownika.

Poniżej moja faktyczna realizacja pracy, który został przetestowany przed zagnieżdżonych obiektów 4 wymiarach (User -> UserProject -> UserProjectEntity -> UserProjectEntityField):

def model_to_dict(obj, visited_children=None, back_relationships=None): 
    if visited_children is None: 
     visited_children = set() 
    if back_relationships is None: 
     back_relationships = set() 
    serialized_data = {c.key: getattr(obj, c.key) for c in obj.__table__.columns} 
    relationships = class_mapper(obj.__class__).relationships 
    visitable_relationships = [(name, rel) for name, rel in relationships.items() if name not in back_relationships] 
    for name, relation in visitable_relationships: 
     if relation.backref: 
      back_relationships.add(relation.backref) 
     relationship_children = getattr(obj, name) 
     if relationship_children is not None: 
      if relation.uselist: 
       children = [] 
       for child in [c for c in relationship_children if c not in visited_children]: 
        visited_children.add(child) 
        children.append(model_to_dict(child, visited_children, back_relationships)) 
       serialized_data[name] = children 
      else: 
       serialized_data[name] = model_to_dict(relationship_children, visited_children, back_relationships) 
    return serialized_data 
+0

istnieje bug na ten kod, należy dodaj "name" na back_relationships, a nie "relation.backref" (poprawiłem Twój post, wystarczy zatwierdzić). – iuridiniz

+0

Jak uniknąć niepotrzebnych kolumn? –

Powiązane problemy