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

时间:2012-03-02 14:34:29

标签: 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)

在我看来,当我创建映射器并且我不能再使用它时,映射器会隐式地对我的对象执行某些操作。从我读到的in similar questions来看,这并不完全是未知的。

预计会出现这种情况吗? 我在这里得到了一些基本的错误吗? 映射和存储对象的正确方法是什么?


其他信息:我在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。 / p>


更新 似乎SQLAlchemy映射器是well known to change objects到'SQLAlchemy需要'。链接文章中的解决方案是在调用mapper(...)之后创建对象。
虽然我已经有了有效的对象 - 但我不能再使用它们了...

如何让SQLAlchemy存储我的对象?

更新2: 我得到的印象是我误解了有关ORM的一些基本要素:
我认为SQLAlchemy映射器概念为我提供了一种方法来定义我的对象并在我的应用程序中与任何数据库分离 - 并且只有一次我想要持久化它们,我引入SQLAlchemy并让它完成所有繁重的映射工作类对象到数据库表。另外,我认为在我的代码中,除了持久性函数save(...)load(...)之外,我甚至不能在其他任何地方提到SQLAlchemy的架构原因。
但是,通过查看下面的第一个响应,我知道在使用任何对象之前必须将类映射到数据库表。这将是我计划的最开始。也许我在这里遇到了一些错误,但这似乎是SQLAlchemy部分的一个非常严格的设计限制 - 所有松耦合都消失了。考虑到这一点,我也没有看到首先拥有一个mapper的好处,因为我想要的'后期耦合'在技术上似乎不可能与SQLAlchemy一起使用,我不妨只做这个'声明风格'并混合使用将业务逻辑与持久性代码混合: - (

更新3: 我回到原点,再次阅读SQLAlchemy。它正好在front page

  

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)

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

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

2 个答案:

答案 0 :(得分:2)

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>}

同样,普通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'}

您的NoneType错误是因为现在已检测的MyClass.title(已被InstrumentedAttribute描述符替换)试图通过{{1}获取_sa_instance_state属性}。 instance_state(instance)由有关对象创建的已检测_sa_instance_state创建,但未由未经检测的MyClass创建。

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

将检测视为透明地将“Subject”实现添加到主题 - 观察者模式中的“Observer”映射器的映射类中。显然,您无法在任意对象上实现主题 - 观察者模式 - 您需要将观察到的对象与Subject主界面相符。

我认为可以通过为它创建一个MyClass对象来就地设置实例(我不知道如何 - 我必须阅读_sa_instance_state代码),但我不明白为什么这是必要的。如果SQLAlchemy可能会保留您的对象,那么在创建对象的任何实例之前,只需为该持久性定义映射和表。您不需要创建引擎或会话,也不需要任何数据库来定义映射。

使用完全未检测的应用程序对象甚至可以逃脱的唯一方法是,如果您的用例非常简单,类似于mapper()pickle的dicts。例如,在没有检测的情况下,您永远不能在集合属性中加载或保存相关对象。你真的不打算这样做吗?如果是这样,也许您会更乐意不使用unpickle并仅使用基础Expression API来保留您的实例?

答案 1 :(得分:0)

您必须在创建MyClass对象之前执行映射,可以在“if”的开头或之前。

在决定映射到哪个表之前,是否需要创建实例?