SQLAlchemy:映射器是否会更改我的对象?

ssc*_*ssc 9 python orm sqlalchemy

我试图使用SQLAlchemy版本将我的对象存储在数据库中.我有一个save(...)功能:

#!/usr/bin/env python
# encoding: utf-8

from sqlalchemy     import Column, Integer, MetaData, String, Table, create_engine
from sqlalchemy.orm import mapper, sessionmaker

class MyClass(object):
    def __init__(self, title):
        self.title = title
    def __str__(self):
        return '%s' % (self.title)

def save(object_list):
    metadata = MetaData()
    my_class_table = Table('my_class',
                            metadata,
                            Column('id',    Integer,     primary_key=True),
                            Column('title', String(255), nullable=False))

    # everything is OK here, output:
    # some title
    # another title
    # yet another title

    for obj in object_list:
        print obj

    mapper(MyClass, my_class_table)

    # on Linux / SQLAlchemy 0.6.8, this fails with
    # Traceback (most recent call last):
    #   File "./test.py", line 64, in <module>
    #     save(my_objects)
    #   File "./test.py", line 57, in save
    #     print obj
    #   File "./test.py", line 11, in __str__
    #     return '%s' % (self.title)
    #   File "/usr/lib/python2.7/dist-packages/sqlalchemy/orm/attributes.py", line 167, in __get__
    #     return self.impl.get(instance_state(instance),
    # AttributeError: 'NoneType' object has no attribute 'get'

    # on Mac OSX / SQLAlchemy 0.7.5, this fails with
    # Traceback (most recent call last):
    #   File "./test.py", line 64, in <module>
    #     save(my_objects)
    #   File "./test.py", line 57, in save
    #     print obj
    #   File "./test.py", line 11, in __str__
    #     return '%s' % (self.title)
    #   File "/Library/Python/2.7/site-packages/sqlalchemy/orm/attributes.py", line 165, in __get__
    #     if self._supports_population and self.key in dict_:
    #   File "/Library/Python/2.7/site-packages/sqlalchemy/orm/attributes.py", line 139, in __getattr__
    #     key)
    # AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object has an attribute '_supports_population'

    for obj in object_list:
        print obj

    # (more code to set up engine and session...)


if __name__ == '__main__':
    my_objects = [MyClass('some title'), MyClass('another title'), MyClass('yet another title')]
    save(my_objects)
Run Code Online (Sandbox Code Playgroud)

在我看来,当我创建映射器并且我不能再使用它时,映射器会隐式地对我的对象执行某些操作.从我在类似问题中读到的内容来看,这并不是完全未知的.

这种行为有望吗?
我在这里遇到了一些基本错误吗?
映射和存储对象的正确方法是什么?


附加信息:我在Mac OSX 10.7.3 Lion上使用SQLAlchemy 0.7.5和系统默认的Python 2.7.1,在Kubuntu 11.10虚拟机上使用系统默认的Python 2.7.2+的SQLAlchemy 0.6.8.


更新: 似乎SQLAlchemy映射器将对象更改为"SQLAlchemy needs".链接文章中的解决方案是在mapper(...)调用后创建对象.
我已经有了有效的对象 - 但我不能再使用它们了...

如何让SQLAlchemy存储我的对象?

更新2: 我得到的印象是我误解了ORM的一些基本原因:
我认为SQLAlchemy映射器概念为我提供了一种方法来定义我的对象并在我的应用程序中使用它与任何数据库分离 - 只有一次我想坚持它们,我引入了SQLAlchemy,让它完成将类对象映射到数据库表的繁重工作.另外,我看不出为什么在我的代码中的其他任何地方都应该提到SQLAlchemy的架构原因而不是持久性函数save(...)load(...).

但是,通过查看下面的第一个响应,我知道在使用任何对象之前必须将类映射到数据库表.这将是我计划的最开始.也许我在这里遇到了一些错误,但这似乎是SQLAlchemy部分的一个非常严格的设计限制 - 所有松耦合都消失了.考虑到这一点,我也没有看到首先拥有一个mapper的好处,因为我想要的'后期耦合'在技术上似乎不可能与SQLAlchemy一起使用,我可能只是做这个'声明风格'并混合使用将业务逻辑与持久性代码混合:-(

更新3: 我回到原点,再次阅读SQLAlchemy.它在头版上说:

SQLAlchemy以其对象关系映射器(ORM)而闻名,ORM是一个提供数据映射器模式的可选组件,其中类可以以开放式,多种方式映射到数据库 - 允许对象模型和数据库模式在从一开始就干净地脱钩.

但是,根据我迄今为止的经验,SQLAlchemy似乎根本没有兑现承诺,因为它迫使我从一开始就将对象模型和数据库模式结合起来.毕竟我听说过SQLAlchemy,我真的很难相信这是真的,我宁愿假设我还没有理解基本的东西.

我究竟做错了什么 ?

更新4:

为了完整起见,我想在创建任何业务对象之前添加执行所有映射的工作示例代码:

#!/usr/bin/env python
# encoding: utf-8

from sqlalchemy     import Column, Integer, MetaData, String, Table, create_engine
from sqlalchemy.orm import mapper, sessionmaker

class MyClass(object):
    def __init__(self, title):
        self.title = title
    def __str__(self):
        return '%s' % (self.title)

def save(object_list, metadata):

    engine  = create_engine('sqlite:///:memory:')
    metadata.create_all(engine)
    Session = sessionmaker(bind=engine)
    session = Session()

    for obj in object_list:
        try:
            session.add(obj)
            session.commit()
        except Exception as e:
            print e
            session.rollback()

    # query objects to test they're in there
    all_objs = session.query(MyClass).all()
    for obj in all_objs:
        print 'object id   :', obj.id
        print 'object title:', obj.title


if __name__ == '__main__':

    metadata = MetaData()
    my_class_table = Table('my_class',
                            metadata,
                            Column('id',    Integer,     primary_key=True),
                            Column('title', String(255), nullable=False))
    mapper(MyClass, my_class_table)

    my_objects = [MyClass('some title'), MyClass('another title'), MyClass('yet another title')]
    save(my_objects, metadata)
Run Code Online (Sandbox Code Playgroud)

在我自己的(非样本)代码中,我MyClass从其自己的模块导入并在单独的"对象存储库"类中进行映射,该类MetaData在其__init__(...)方法中创建并将其存储在成员中,因此save(...)load(...)持久性方法可以根据需要访问它.所有主要应用程序需要做的是在一开始就创建存储库对象; 这包含SQLAlchemy对一个类的设计的影响,但也将业务对象和映射的表定义分发到代码中的不同位置.不确定我是否会长期使用,但它似乎暂时有效.

像我这样的SQLAlchemy noobs的最后快速提示:您必须始终使用同一个元数据对象,否则您将获得像no such table或的异常class not mapped.

Fra*_*ila 5

SQLAlchemy 绝对会修改映射的类。SQLAlchemy 将此称为“检测”

观察:

>>> print(MyClass.__dict__)
{'__module__': '__main__', '__str__': <function __str__ at 0x106d50398>, '__dict__':
<attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 
'MyClass' objects>, '__doc__': None, '__init__': <function __init__ at 0x106d4b848>}

>>> mapper(MyClass, my_class_table)
Mapper|MyClass|my_class
>>> print(MyClass.__dict__)
{'__module__': '__main__', '_sa_class_manager': <ClassManager of <class 
'__main__.MyClass'> at 7febea6a7f40>, 'title': 
<sqlalchemy.orm.attributes.InstrumentedAttribute object at 0x10fba4f90>, '__str__': 
<function __str__ at 0x10fbab398>, 'id': <sqlalchemy.orm.attributes.InstrumentedAttribute 
object at 0x10fba4e90>, '__dict__': <attribute '__dict__' of 'MyClass' objects>, 
'__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': None, '__init__':
 <function __init__ at 0x10fbab410>}
Run Code Online (Sandbox Code Playgroud)

MyClass同样,普通实例和检测实例之间也存在差异MyClass

>>> prem = MyClass('premapper')
>>> mapper(MyClass, my_class_table)
Mapper|MyClass|my_class
>>> postm = MyClass('postmapper')
>>> print prem
{'title': 'premapper'}
>>> print postm
{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x10e5b6d50>, 'title': 'postmapper'}
Run Code Online (Sandbox Code Playgroud)

您的NoneType错误是因为现在已检测MyClass.title(已替换为InstrumentedAttribute描述符)正在尝试_sa_instance_state通过 获取属性instance_state(instance)_sa_instance_state是由对象MyClass创建时的检测对象创建的,而不是由未检测对象创建的MyClass

仪器仪表是必不可少的。这样做是为了可以使用描述符将属性访问、分配和其他重要的对象状态更改传达给映射器。(例如,如果不修改类来监视对象中的属性访问,如何实现延迟列加载甚至集合访问?)MyClass对象不绑定到表,但它们确实需要绑定到映射器。您可以更改映射选项和表,而无需更改域对象的代码,但不一定要更改域对象本身。(毕竟,您可以将单个对象连接到多个映射器和多个表。)

将检测视为以主题-观察者模式透明地将“主题”实现添加到“观察者”映射器的映射类。显然,您不能在任意对象上实现主题-观察者模式——您需要使观察到的对象符合主题接口。

我想可以通过_sa_instance_state为其创建一个对象来就地检测实例(我不知道如何操作——我必须阅读代码mapper()),但我不明白为什么这是必要的。如果您的对象有可能被 SQLAlchemy 持久化,那么只需在创建该对象的任何实例之前定义该持久化的映射和表即可。您根本不需要创建引擎或会话或拥有任何数据库来定义映射。

您甚至可以摆脱使用完全未检测的应用程序对象的唯一方法是,如果您的用例非常琐碎,例如相当于一个pickleunpickle多个字典。例如,如果没有检测,您永远无法在集合属性中加载或保存相关对象。你真的从来没有打算这样做吗?如果是这样,也许您会更高兴sqlalchemy.orm根本不使用而仅使用底层Expression API来持久化您的实例?