如何迭代遍历SQLAlchemy模型中定义的列?

127

我一直在尝试弄清楚如何迭代SQLAlchemy模型中定义的列列表。 我想为几个模型编写一些序列化和复制方法。 我不能只是迭代obj.__dict__,因为它包含了很多SA特定的项目。

有人知道一种方法可以从以下内容中获取iddesc名称吗?

class JobStatus(Base):
    __tablename__ = 'jobstatus'

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

在这个小例子中,我可以轻松地创建一个:

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

但我更希望有一些自动生成 dict 的东西(对于较大的对象)。

11个回答

113
您可以使用以下函数:
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]))

它将排除SA magic 属性,但不会排除关系。所以基本上它可能会加载依赖项、父级、子级等,这肯定是不可取的。

但事实上这更容易,因为如果您从Base继承,您就有一个__table__属性,因此您可以执行以下操作:

for c in JobStatus.__table__.columns:
    print c

for c in JobStatus.__table__.foreign_keys:
    print c

请参见如何从SQLAlchemy映射的对象中发现表属性 - 类似的问题。

Mike编辑:请查看诸如Mapper.cMapper.mapped_table等函数。如果使用版本为0.8及更高版本,请参见Mapper.attrs和相关函数。

Mapper.attrs的示例:

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

25
请注意,__table__.columns会给出SQL字段名,而不是您在ORM定义中使用的属性名(如果两者不同)。 - Josh Kelley
11
我建议将'_sa_' != k[:4]更改为not k.startswith('_sa_'),这样更易懂。 - Mu Mind
16
无需使用inspect进行循环:inspect(JobStatus).columns.keys() - kirpit
1
如果您使用 model.__dict__[column] 设置值,SQLAlchemy 将无法检测到更改。 - Sebi2020
如果你想要知道比名称更多的内容,你必须使用mapper.attrs方法。我需要知道一个字符串列的长度,但是用常规的__table__.column无法获得。我认为你应该把这作为自己的答案,我会点赞的。 - Sean DiZazzo

66

您可以从映射器获取定义的属性列表。 对于您的情况,您只对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
谢谢,这让我在Base类中创建了一个__todict__方法,我可以使用它将实例“dump”到一个字典中,然后通过pylon的jsonify装饰器响应传递。 我试图在我的原始问题中添加更详细的注释和代码示例,但这导致stackoverflow提交时出错。 - Rick
4
顺便提一句,需要从 sqlalchemy.orm 中导入 class_mapper - priestc
3
虽然这是一个合法的答案,但自版本0.8以后,建议使用inspect()函数,该函数返回与class_mapper()相同的映射器对象。详情请参阅:http://docs.sqlalchemy.org/en/latest/core/inspection.html。 - kirpit
1
这对我非常有帮助,可以将SQLAlchemy模型属性名称映射到底层列名称。 - FearlessFuture

37

我知道这是一个老问题,但我刚遇到了同样的需求,并想为未来的读者提供另一种解决方案。

正如Josh所指出的那样,JobStatus.__table__.columns将返回完整的SQL字段名称,因此您将得到jobstatus.id而不是原始字段名称id。 这并不像它本应该有用。

获取字段名称列表的解决方案是查看列对象上的_data属性,其中包含完整数据。 如果我们查看JobStatus.__table__.columns._data,它看起来像这样:

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

从这里您可以简单地调用JobStatus.__table__.columns.keys(),它会给您一个漂亮、干净的列表:

['id', 'desc']

2
很好!使用这种方法是否有办法获取关系呢? - shroud
5
不需要"_data"属性,只需使用"..columns.keys()"即可解决。 - Humoyun Ahmad
2
是的,在可能的情况下应避免使用私有的_data属性,@Humoyun更正确。 - Ng Oon-Ee
属性错误:__data - user2738183
我还想检索关系。 - Konrad

22

这是简单的答案。 - stgrmks

14

self.__table__.columns只会给出该类别中定义的列,不包括继承的列。如果需要全部列,请使用self.__mapper__.columns。在您的示例中,我可能会使用以下内容:

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)

11
为了在我所有的类中获取一个as_dict方法,我使用了一个Mixin类,该类使用了Ants Aasma所描述的技术。请注意保留HTML标签。
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

然后在你的类中像这样使用它

class MyClass(BaseMixin, Base):
    pass

那么你可以在 MyClass 的实例上调用以下内容。
> myclass = MyClass()
> myclass.as_dict()

希望这可以帮助到您。
我进一步尝试了一下,实际上需要将我的实例呈现为字典形式,作为一个HAL对象,并链接到相关对象。因此,我添加了下面这个小魔术,它将遍历类的所有属性,与上述相同,不同之处在于我会更深入地遍历Relaionship属性,并自动生成这些属性的链接
请注意,这仅适用于具有单个主键的关系。
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

请在您的代码片段顶部添加 from sqlalchemy.orm import class_mapper, ColumnProperty - JVK
谢谢您的评论!我已经添加了缺失的导入。 - flazzarini
这是SQLAlchemy的声明性基础,请在此处阅读更多信息:https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/basic_use.html - flazzarini

2
self.__dict__

返回一个字典,其中键是属性名,值为对象的值。

/!\ 有一个附加属性:'_sa_instance_state',但您可以处理它 :)

最初的回答:

该函数返回一个字典,其中包含对象的属性名和对应的属性值。

/!\ 注意,还有一个额外的属性:'_sa_instance_state',但你可以处理它 :)


只有在设置属性时。 - stgrmks

2

虽然 row._asdict() 对大多数情况都有效,但我需要一些方法在对象创建过程之后也能使用(例如db.session.add等)。我的想法是创建一个名为to_dict的方法,在表对象上访问列,并使用标准的getattr

class Inventory(db.Model):
    __tablename__ = 'inventory'

    id = db.Column('id', db.Integer(), primary_key=True)
    date = db.Column('date', db.DateTime, nullable=False, default=datetime.utcnow)
    item = db.Column('item', db.String(100))

    def to_dict(self):
        return {
            column.name: getattr(self, column.name, None)
            for column in Inventory.__table__.columns
        }



record = Inventory(item="gloves")
db.session.add(record)
db.session.commit()

# print(record._asdict()) # << that doesn't work
print(record.to_dict()) # << that works as intended

这个解决方案将生成仅包含列的字典 - 没有任何元属性或其他需要在下一个大型更新之后手动清理的内容(如果有的话)。
PS。我使用flask-sqlalchemy,但它并不改变这个想法。

0

为了将一个从SQLAlchemy中获取的模型映射到JSON格式,考虑到关联关系,我使用了以下代码:

from sqlalchemy.orm import class_mapper
from sqlalchemy.ext.declarative import DeclarativeMeta
from sqlalchemy.orm import ColumnProperty
from sqlalchemy.orm import RelationshipProperty


class BaseMixin(object):
    """BaseMixin"""

    __repr_hide = ["created_at", "updated_at"]
    __insert_hide = []

    @property
    def _repr_hide(self):
        return self.__repr_hide

    @_repr_hide.setter
    def _repr_hide(self, k):
        self.__repr_hide.append(k)

    @property
    def _insert_hide(self):
        return self.__insert_hide

    @_insert_hide.setter
    def _insert_hide(self, k):
        self.__insert_hide.append(k)

    def serialize(self, obj):
        """serialize from json"""
        for k, v in obj.items():
            if k in self.__repr_hide:
                continue
            if k in self.__insert_hide:
                continue
            if k in self.__table__.c.keys():
                setattr(self, k, v)
        return self

    def deserialize(self, backref=None):
        """deserialize to json"""
        res = dict()

        for prop in class_mapper(self.__class__).iterate_properties:
            if prop.key in self.__repr_hide:
                continue
            if isinstance(prop, ColumnProperty):
                res[prop.key] = getattr(self, prop.key)

        for prop in class_mapper(self.__class__).iterate_properties:
            if prop.key in self.__repr_hide:
                continue
            if isinstance(prop, RelationshipProperty):
                if prop.key == str(backref):
                    continue
                key, value = prop.key, getattr(self, prop.key)
                if value is None:
                    res[key] = None
                elif isinstance(value.__class__, DeclarativeMeta):
                    res[key] = value.deserialize(backref=self.__table__)
                else:
                    res[key] = [i.deserialize(backref=self.__table__) for i in value]
        return res

    def __iter__(self):
        return iter(self.deserialize().items())

    def __repr__(self):
        vals = ", ".join(
            "%s=%r" % (n, getattr(self, n))
            for n in self.__table__.c.keys()
            if n not in self._repr_hide
        )

        return "<%s={%s}>" % (self.__class__.__name__, vals)

0
我想动态获取模型特定实例的数据。我使用了这段代码。
def to_json(instance):
    # get columns data
    data = {}
    columns = list(instance.__table__.columns)
    for column in columns:
        data[column.name] = instance.__dict__[column.name]
    return data

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接