在 SQLAlchemy 中是否可以从 Table 转储和加载(而不是从映射的 class)?

In SQLAlchemy is it possible to dump and load from a Table (not from a mapped class)?


TL;DR 使用 SQLAlchemy is it possible to do a dumps(…) and load(…) using a Table (not a mapped class)? Just to clarify, I wrote those two tiny tests (currently red) 来表达我的观点。


映射 class 和会话:

from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.serializer import loads, dumps
from sqlalchemy.orm import mapper, scoped_session
from sqlalchemy.orm.session import sessionmaker


engine = create_engine('sqlite:///:memory:', echo=False)
Base = declarative_base(bind=engine)
session = scoped_session(sessionmaker())



class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    email = Column(String(140), index=True, unique=True)

使用 User 映射 class 我可以转储和加载数据:

>>> # create a record
>>> Base.metadata.create_all()
>>> session.add(User(email='john@alchemydumps'))
>>> session.commit()
>>>
>>> # use dumps
>>> with open('dummy_file', 'wb') as file_handler:
...     file_handler.write(dumps(session.query(User).all()))
...
490
>>>
>>> # reset the database
>>> Base.metadata.drop_all()
>>> Base.metadata.create_all()
>>>
>>> # use loads
>>> with open('dummy_file', 'rb') as file_handler:
...     for row in loads(file_handler.read()):
...         session.merge(row)
...
<__main__.User object at 0x10977ea20>
>>> session.commit()
>>> 
>>> # assert data is back
>>> session.query(User).count()
1
>>>

但是使用 Table 代替 工作:

>>> …
>>>
>>> # use a table instead (User.__table__, instead of User)
>>> with open('dummy_file', 'wb') as file_handler:
...    file_handler.write(dumps(session.query(User.__table__).all()))
...
115
>>> Base.metadata.drop_all()
>>> Base.metadata.create_all()
>>>
>>> with open('dummy_file', 'rb') as file_handler:
...     for row in loads(file_handler.read()):
...         session.merge(row)
...

此代码因 sqlalchemy.orm.exc.UnmappedInstanceError:

而崩溃
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
File "/Users/cuducos/.virtualenvs/alchemydumps/lib/python3.5/site-packages/sqlalchemy/orm/scoping.py", line 157, in do
    return getattr(self.registry(), name)(*args, **kwargs)
File "/Users/cuducos/.virtualenvs/alchemydumps/lib/python3.5/site-packages/sqlalchemy/orm/session.py", line 1702, in merge
    object_mapper(instance)  # verify mapped
File "/Users/cuducos/.virtualenvs/alchemydumps/lib/python3.5/site-packages/sqlalchemy/orm/base.py", line 266, in object_mapper
    return object_state(instance).mapper
File "/Users/cuducos/.virtualenvs/alchemydumps/lib/python3.5/site-packages/sqlalchemy/orm/base.py", line 288, in object_state
    raise exc.UnmappedInstanceError(instance)
sqlalchemy.orm.exc.UnmappedInstanceError: Class 'sqlalchemy.util._collections.KeyedTuple' is not mapped

这条消息已经太长了,所以我避免过多谈论问题的背景。但是我确实有 a situation,我需要在其中转储和加载未映射的 classes……我正试图为此找到解决方案。 (免责声明:我在 2 年前写了这个包,当时我还是 Python 的新手;是的,代码看起来很糟糕 — 但我在过去几周一直在完全重构它。)

对这个 SQLAlchemy 新手有什么想法或建议吗?

在@univerio 的评论之后,我可以更好地了解情况并修复,耶!

问题在于,如果 SQLAlchemy dumps 收到一个 Table 实例,结果与 table 无关,它们纯粹是一个 KeyedTuple。所以我不得不:

  1. KeyedTuple 加载
  2. 时引入 映射 class
  3. transform the KeyedTuple into a dictionary(这很容易,因为 KeyedTuple 有一个 _asdict() 方法)
  4. 然后合并(因为这是我的初衷)

tests are now green,但总结一下这是我需要的:

with open('dummy_file', 'rb') as file_handler:
...     for row in loads(file_handler.read()):
...         if isinstance(row, KeyedTuple):
...             row = User(**row._asdict())
...         session.merge(row)
>>> session.commit()