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 id
i desc
nazwiska?
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).
źródło
__table__.columns
otrzymasz 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ą).'_sa_' != k[:4]
nanot k.startswith('_sa_')
?inspect(JobStatus).columns.keys()
model.__dict__[column]
sqlalchemy, nie wykryje zmian.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)]
źródło
class_mapper
musi być importowany zsqlalchemy.orm
inspect()
, która zwraca dokładnie ten sam obiekt mapowania, coclass_mapper()
. docs.sqlalchemy.org/en/latest/core/inspection.htmlZdaję 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
_data
atrybut na obiekcie kolumny, który zawiera pełne dane. Jeśli spojrzymyJobStatus.__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']
źródło
self.__table__.columns
poda „tylko” kolumny zdefiniowane w tej konkretnej klasie, tj. bez dziedziczonych. jeśli potrzebujesz wszystkiego, użyjself.__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)
źródło
Zakładając, że używasz mapowania deklaratywnego SQLAlchemy, możesz użyć
__mapper__
atrybutu, aby uzyskać mapowanie klas. Aby uzyskać wszystkie zmapowane atrybuty (w tym relacje):Jeśli chcesz mieć ściśle nazwy kolumn, użyj
obj.__mapper__.column_attrs.keys()
. Zobacz dokumentację dla innych widoków.https://docs.sqlalchemy.org/en/latest/orm/mapping_api.html#sqlalchemy.orm.mapper.Mapper.attrs
źródło
Aby uzyskać
as_dict
metodę na wszystkich moich zajęciach, użyłemMixin
klasy, 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
.Mam nadzieję że to pomoże.
Bawiłem się tym trochę dalej, właściwie musiałem renderować moje instancje jako
dict
formę 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 doRelaionship
właściwości i generowaćlinks
je 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
źródło
from sqlalchemy.orm import class_mapper, ColumnProperty
u góry fragmentu koduzwraca 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ć :)
źródło
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))
źródło