2010-03-29 16 views
69

Próbuję dowiedzieć się, jak iterować na liście kolumn zdefiniowanych w modelu SqlAlchemy. Chcę tego do napisania kilku metod serializacji i kopiowania do kilku modeli. Nie mogę po prostu powtórzyć tego obiektu. dict ponieważ zawiera wiele elementów specyficznych dla SA.metoda iterowania kolumn zdefiniowanych w modelu sqlalchemy?

Ktoś wie, w jaki sposób uzyskać nazwy identyfikatorów i desc od następujących?

class JobStatus(Base): 
    __tablename__ = 'jobstatus' 

    id = Column(Integer, primary_key=True) 
    desc = Column(Unicode(20)) 

W tym małym wypadku mógłbym łatwo utworzyć:

def logme(self): 
    return {'id': self.id, 'desc': self.desc} 

ale wolałbym coś, co było automatycznego generowania większych obiektów.

Dzięki za pomoc.

Odpowiedz

53

można użyć następujących funkcji:

def __unicode__(self): 
    return "[%s(%s)]" % (self.__class__.__name__, ', '.join('%s=%s' % (k, self.__dict__[k]) for k in sorted(self.__dict__) if '_sa_' != k[:4])) 

To wykluczy SA magię atrybuty, ale nie wyklucza relacje. Więc zasadniczo może załadować zależności, rodziców, dzieci itp., Co zdecydowanie nie jest pożądane.

Ale to jest rzeczywiście dużo łatwiejsze, ponieważ jeśli dziedziczą Base masz atrybut __table__, tak, że można zrobić:

for c in JobStatus.__table__.columns: 
    print c 

for c in JobStatus.__table__.foreign_keys: 
    print c 

Zobacz How to discover table properties from SQLAlchemy mapped object - podobne pytanie.

Edytuj według Mike'a: Zobacz funkcje, takie jak Mapper.c i Mapper.mapped_table. Jeśli używasz 0.8 i wyżej, zobacz także Mapper.attrs i powiązane funkcje.

Przykład Mapper.attrs:

from sqlalchemy import inspect 
mapper = inspect(JobStatus) 
for column in mapper.attrs: 
    print column.key 
+13

Zwróć uwagę, że '__table__.columns' podaje nazwy pól SQL, a nie nazwy atrybutów używanych w definicjach ORM (jeśli są one różne). –

+8

Czy mogę zalecić zmianę ''_sa_'! = K [: 4]' na 'not k.startswith ('_ sa _')'? –

+4

Nie trzeba pętli z inspect: 'inspect (JobStatus) .columns.keys()' – kirpit

53

Możesz otrzymać listę zdefiniowanych właściwości od programu odwzorowującego. Dla Twojej sprawy interesują Cię tylko obiekty ColumnProperty.

from sqlalchemy.orm import class_mapper 
import sqlalchemy 

def attribute_names(cls): 
    return [prop.key for prop in class_mapper(cls).iterate_properties 
     if isinstance(prop, sqlalchemy.orm.ColumnProperty)] 
+4

Dzięki, to niech mi stworzyć metodę __todict__ na bazie której następnie użyć do „wyrzucenia” instancji zewnątrz do Mogę wtedy przejść przez odpowiedź dekoratora jonów z pylonu. Próbowałem umieścić więcej szczegółów notatki z przykładem kodu w moim oryginalnym pytaniu, ale powoduje to, że stackoverflow powoduje błąd podczas przesyłania. – Rick

+4

btw, 'class_mapper' musi być zaimportowany z' sqlalchemy.orm' – priestc

+1

Podczas gdy jest to uzasadniona odpowiedź, po wersji 0.8 sugeruje się użycie 'inspect()', która zwraca dokładnie ten sam obiekt odwzorowujący co 'class_mapper () '. http://docs.sqlalchemy.org/en/latest/core/inspection.html – kirpit

22

Zdaję sobie sprawę, że jest to stara sprawa, ale ja po prostu natknąć samego wymogu i pragnie zaoferować alternatywne rozwiązanie dla przyszłych czytelników.

Jak zauważa Josh, SQL pełne nazwy pól zostaną zwrócone przez JobStatus.__table__.columns, więc raczej niż oryginalna nazwa pola id, dostaniesz jobstatus.id. Nie tak przydatne, jak mogłoby być.

Rozwiązaniem problemu z uzyskaniem listy nazw pól, które zostały pierwotnie zdefiniowane, jest przejrzenie atrybutu _data w obiekcie kolumny, który zawiera pełne dane.Jeśli spojrzymy na JobStatus.__table__.columns._data, wygląda to tak:

{'desc': Column('desc', Unicode(length=20), table=<jobstatus>), 
'id': Column('id', Integer(), table=<jobstatus>, primary_key=True, nullable=False)} 

Stąd można po prostu zadzwonić JobStatus.__table__.columns._data.keys() co daje ładny, czysty listę:

['id', 'desc'] 
+1

Nice! Czy istnieje sposób na zastosowanie tej metody, aby uzyskać również relacje? – shroud

+2

nie ma potrzeby _data attr, po prostu ..columns.keys() zrób to, co należy zrobić – Humoyun

+0

Tak, należy unikać używania atrybutu private _data, jeśli to możliwe, @Humoyun jest bardziej poprawny. –

10

self.__table__.columns będzie „tylko” daje kolumny określone w tej konkretnej klasie, tj. bez dziedziczenia. jeśli potrzebujesz wszystkich, użyj self.__mapper__.columns. w przykładzie I prawdopodobnie używać coś takiego:

class JobStatus(Base): 

    ... 

    def __iter__(self): 
     values = vars(self) 
     for attr in self.__mapper__.columns.keys(): 
      if attr in values: 
       yield attr, values[attr] 

    def logme(self): 
     return dict(self) 
7

Aby uzyskać metodę as_dict na wszystkich moich klas użyłem Mixin klasę która wykorzystuje techniki opisane przez Ants Aasma.

class BaseMixin(object):                                            
    def as_dict(self):                                            
     result = {}                                             
     for prop in class_mapper(self.__class__).iterate_properties:                                 
      if isinstance(prop, ColumnProperty):                                      
       result[prop.key] = getattr(self, prop.key)                                   
     return result 

a następnie używać go w ten sposób w swoich klasach

class MyClass(BaseMixin, Base): 
    pass 

ten sposób można wywołać następujące na wystąpienie MyClass.

> myclass = MyClass() 
> myclass.as_dict() 

Mam nadzieję, że to pomoże.


Grałem arround z tym nieco dalej, tak naprawdę potrzebne, aby uczynić moje instancji jako dict jako postaci HAL object z jego linki do pokrewnych przedmiotów. Dodałem tutaj tę małą magię, która będzie indeksować wszystkie właściwości klasy takie same jak wyżej, z tą różnicą, że będę się wczołgać głębiej w właściwości Relaionship i automatycznie wygeneruję dla nich links.

Należy pamiętać, że to będzie działać tylko dla relacji mieć jeden klucz podstawowy

from sqlalchemy.orm import class_mapper, ColumnProperty 
from functools import reduce 


def deepgetattr(obj, attr): 
    """Recurses through an attribute chain to get the ultimate value.""" 
    return reduce(getattr, attr.split('.'), obj) 


class BaseMixin(object): 
    def as_dict(self): 
     IgnoreInstrumented = (
      InstrumentedList, InstrumentedDict, InstrumentedSet 
     ) 
     result = {} 
     for prop in class_mapper(self.__class__).iterate_properties: 
      if isinstance(getattr(self, prop.key), IgnoreInstrumented): 
       # All reverse relations are assigned to each related instances 
       # we don't need to link these, so we skip 
       continue 
      if isinstance(prop, ColumnProperty): 
       # Add simple property to the dictionary with its value 
       result[prop.key] = getattr(self, prop.key) 
      if isinstance(prop, RelationshipProperty): 
       # Construct links relaions 
       if 'links' not in result: 
        result['links'] = {} 

       # Get value using nested class keys 
       value = (
        deepgetattr(
         self, prop.key + "." + prop.mapper.primary_key[0].key 
        ) 
       ) 
       result['links'][prop.key] = {} 
       result['links'][prop.key]['href'] = (
        "/{}/{}".format(prop.key, value) 
       ) 
     return result 
+0

Proszę dodać 'from sqlalchemy.orm import class_mapper, ColumnProperty' na szczycie fragmentu kodu – JVK

+0

Dzięki za komentarz! Dodałem brakujący import – flazzarini

-1

wiem, jest to stara sprawa, ale co:

class JobStatus(Base): 

    ... 

    def columns(self): 
     return [col for col in dir(self) if isinstance(col, db.Column)] 

Następnie, aby uzyskać nazwy kolumn: jobStatus.columns()

To by zwróciło ['id', 'desc']

Następnie można pętli i robić rzeczy z kolumnami i wartościami:

for col in jobStatus.colums(): 
    doStuff(getattr(jobStatus, col)) 
+0

Nie można zrobić isinstance (col, Column) na ciągu znaków – Muposat

+0

Dochodzimy z dołu, ponieważ kolumny __table __. Columns i __mapper __. Dają te dane bez ponownego wynajdowania koła. –

Powiązane problemy