metoda iteracji po kolumnach zdefiniowanych w modelu sqlalchemy?

96

Próbowałem dowiedzieć się, jak iterować po liście kolumn zdefiniowanych w modelu SQLAlchemy. Chcę go do pisania metod serializacji i kopiowania do kilku modeli. Nie mogę po prostu iterować, obj.__dict__ponieważ zawiera wiele elementów specyficznych dla SA.

Czy ktoś wie, jak uzyskać następujące nazwy idi descnazwiska?

class JobStatus(Base):
    __tablename__ = 'jobstatus'

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

W tym małym przypadku mógłbym łatwo stworzyć:

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

ale wolałbym coś, co automatycznie generuje dict(dla większych obiektów).

Stóg
źródło

Odpowiedzi:

84

Możesz użyć następującej 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]))

Wykluczy magiczne atrybuty SA , ale nie wykluczy relacji. Zasadniczo może to ładować zależności, rodziców, dzieci itp., Co zdecydowanie nie jest pożądane.

Ale w rzeczywistości jest to o wiele łatwiejsze, ponieważ jeśli odziedziczysz po Base, masz __table__atrybut, dzięki czemu możesz:

for c in JobStatus.__table__.columns:
    print c

for c in JobStatus.__table__.foreign_keys:
    print c

Zobacz Jak wykryć właściwości tabeli z obiektu mapowanego SQLAlchemy - podobne pytanie.

Edycja autorstwa Mike'a: Zobacz funkcje takie jak Mapper.c i Mapper.mapped_table . Jeśli używasz wersji 0.8 i nowszej, zobacz także Mapper.attrs i powiązane funkcje.

Przykład dla Mapper.attrs :

from sqlalchemy import inspect
mapper = inspect(JobStatus)
for column in mapper.attrs:
    print column.key
awangarda
źródło
21
Zauważ, że __table__.columnsotrzymasz nazwy pól SQL, a nie nazwy atrybutów, których użyłeś w swoich definicjach ORM (jeśli te dwa się różnią).
Josh Kelley,
11
Czy mogę polecić zmianę '_sa_' != k[:4]na not k.startswith('_sa_')?
Mu Mind
12
Nie ma potrzeby inspect(JobStatus).columns.keys()
zapętlania
jeśli ustawisz wartości za pomocą model.__dict__[column]sqlalchemy, nie wykryje zmian.
Sebi2020
63

Listę zdefiniowanych właściwości można pobrać z programu mapującego. W Twoim przypadku 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)]
Ants Aasma
źródło
4
Dzięki, to pozwoliło mi stworzyć metodę todict w Base, której używam do „zrzucenia” instancji do dyktu , przez który mogę przejść, aby uzyskać odpowiedź dekoratora jsonify pylon. Próbowałem umieścić więcej szczegółów z przykładowym kodem w moim pierwotnym pytaniu, ale powoduje to błąd przepełnienia stosu podczas przesyłania.
Rick
4
btw, class_mappermusi być importowany zsqlalchemy.orm
ksiądz
3
Chociaż jest to uzasadniona odpowiedź, sugeruje się użycie po wersji 0.8 inspect(), która zwraca dokładnie ten sam obiekt mapowania, co class_mapper(). docs.sqlalchemy.org/en/latest/core/inspection.html
kirpit,
1
Bardzo mi to pomogło w mapowaniu nazw właściwości modelu SQLAlchemy na nazwy bazowych kolumn.
FearlessFuture
30

Zdaję sobie sprawę, że to stare pytanie, ale właśnie natknąłem się na ten sam wymóg i chciałbym zaoferować przyszłym czytelnikom alternatywne rozwiązanie.

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

Aby uzyskać listę nazw pól w takiej postaci, w jakiej zostały pierwotnie zdefiniowane, należy spojrzeć na _dataatrybut na obiekcie kolumny, który zawiera pełne dane. Jeśli spojrzymy 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żesz po prostu zadzwonić, JobStatus.__table__.columns._data.keys()co daje ładną, przejrzystą listę:

['id', 'desc']
morfika
źródło
2
Ładny! Czy za pomocą tej metody można również nawiązać relacje?
całun
3
nie ma potrzeby, aby atr _data, po prostu ..columns.keys () załatwiało
sprawę
1
Tak, w miarę możliwości należy unikać prywatnego atrybutu _data, @Humoyun jest bardziej poprawne.
Ng Oon-Ee
AttributeError: __data
13

self.__table__.columnspoda „tylko” kolumny zdefiniowane w tej konkretnej klasie, tj. bez dziedziczonych. jeśli potrzebujesz wszystkiego, użyj self.__mapper__.columns. w twoim przykładzie prawdopodobnie użyłbym czegoś 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)
Andi Zeidler
źródło
7

Aby uzyskać as_dictmetodę na wszystkich moich zajęciach, użyłem Mixinklasy, która używa technik opisanych 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 potem używaj tego w ten sposób na swoich zajęciach

class MyClass(BaseMixin, Base):
    pass

W ten sposób możesz wywołać następujące czynności na wystąpieniu MyClass.

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

Mam nadzieję że to pomoże.


Bawiłem się tym trochę dalej, właściwie musiałem renderować moje instancje jako dictformę obiektu HAL z linkami do powiązanych obiektów. Więc dodałem tutaj tę małą magię, która będzie przeszukiwać wszystkie właściwości tej samej klasy, tak samo jak powyżej, z tą różnicą, że będę indeksować głębiej do Relaionshipwłaściwości i generować linksje automatycznie.

Pamiętaj, że zadziała to tylko w przypadku relacji, które mają 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
flazzarini
źródło
Dodaj from sqlalchemy.orm import class_mapper, ColumnPropertyu góry fragmentu kodu
JVK
Dzięki za komentarz! Dodałem brakujące importy
flazzarini,
To deklaratywna podstawa sqlalchemy. Przeczytaj więcej na ten temat tutaj docs.sqlalchemy.org/en/13/orm/extensions/declarative/ ...
flazzarini
1
self.__dict__

zwraca dict, gdzie klucze są nazwami atrybutów, a wartościami wartościami obiektu.

/! \ jest dodatkowy atrybut: „_sa_instance_state”, ale możesz sobie z tym poradzić :)

Valentin Fabianski
źródło
Tylko wtedy, gdy ustawione są atrybuty.
stgrmks
-1

Wiem, że to stare pytanie, ale co z:

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 wróci ['id', 'desc']

Następnie możesz przechodzić przez pętlę i robić rzeczy z kolumnami i wartościami:

for col in jobStatus.colums():
    doStuff(getattr(jobStatus, col))
vktec
źródło
nie możesz zrobić instancji (kolumna, kolumna) na strunie
Muposat
Głosowanie negatywne, ponieważ tabele .columns i mapper .columns zapewniają te dane bez ponownego wymyślania koła.
David Watson