SQLAlchemy中的“类已经定义了主映射器”错误

早在2010年10月,我就将
this question发布到了Sqlalchemy用户列表中.

当时,我只是使用了消息中提到的clear_mappers解决方法,并没有试图找出问题所在.那对我很顽皮.今天我又遇到了这个bug,并决定构建一个最小的例子,如下所示.迈克尔也在2006年回顾了
probably the same issue年的问题.我决定跟进这里,让迈克尔从我愚蠢的问题中解脱出来.

因此,结果似乎是对于给定的类定义,您不能定义多个映射器.在我的例子中,我在模块范围中声明了Pheno类(我假设这是顶级范围),每次make_tables运行时,它都会尝试定义另一个映射器.

Mike写道:“根据上述问题的描述,你需要确保你的Python类在与映射器相同的范围内声明.你得到的错误信息表明’Pheno’是在模块级别声明的.”这会解决问题,但如何在不改变现有结构的情况下进行管理呢?我有什么其他选择,如果有的话?显然映射器没有像“如果映射器已经定义,退出而不做任何事情”这样的选项,这将很好地处理它.我想我可以定义一个包装函数,但那会非常难看.

from sqlalchemy import *
from sqlalchemy.orm import *

def make_pheno_table(meta, schema, name='pheno'):
    pheno_table = Table(
        name, meta,
        Column('patientid', String(60), primary_key=True),
        schema=schema,
        )
    return pheno_table

class Pheno(object):
    def __init__(self, patientid):
        self.patientid = patientid

def make_tables(schema):
    from sqlalchemy import MetaData
    meta = MetaData() 
    pheno_table = make_pheno_table(meta, schema)
    mapper(Pheno, pheno_table)
    table_dict = {'metadata': meta, 'pheno_table':pheno_table}
    return table_dict

table_dict = make_tables('foo')
table_dict = make_tables('bar')

错误信息如下.在Debian squeeze上使用SQLAlchemy 0.6.3-3进行测试.

$python test.py 
Traceback (most recent call last):
  File "test.py", line 25, in <module>
    table_dict = make_tables('bar')
  File "test.py", line 20, in make_tables
    mapper(Pheno, pheno_table)
  File "/usr/lib/python2.6/dist-packages/sqlalchemy/orm/__init__.py", line 818, in mapper
    return Mapper(class_, local_table, *args, **params)
  File "/usr/lib/python2.6/dist-packages/sqlalchemy/orm/mapper.py", line 209, in __init__
    self._configure_class_instrumentation()
  File "/usr/lib/python2.6/dist-packages/sqlalchemy/orm/mapper.py", line 381, in _configure_class_instrumentation
    self.class_)
sqlalchemy.exc.ArgumentError: Class '<class '__main__.Pheno'>' already has a primary mapper defined. Use non_primary=True to create a non primary Mapper.  clear_mappers() will remove *all* current mappers from all classes.

编辑:根据SQLAlchemy: The mapper() API中的文档,我可以替换上面的mapper(Pheno,pheno_table)

from sqlalchemy.orm.exc import UnmappedClassError

try:
    class_mapper(Pheno)
except UnmappedClassError:
    mapper(Pheno, pheno_table)

如果未为Pheno定义映射器,则会抛出UnmappedClassError.这至少不会在我的测试脚本中返回错误,但我还没有检查它是否真的有效.评论?

EDIT2:Per Denis的建议,以下作品:

class Tables(object):
    def make_tables(self, schema):
        class Pheno(object):
            def __init__(self, patientid):
                self.patientid = patientid

        from sqlalchemy import MetaData
        from sqlalchemy.orm.exc import UnmappedClassError
        meta = MetaData()
        pheno_table = make_pheno_table(meta, schema)
        mapper(Pheno, pheno_table)
        table_dict = {'metadata': meta, 'pheno_table':pheno_table, 'Pheno':Pheno}
        return table_dict

table_dict = Tables().make_tables('foo')
table_dict = Tables().make_tables('bar')

但是,表面上相似

# does not work                                                                                                                                                  
class Tables(object):
    class Pheno(object):
        def __init__(self, patientid):
            self.patientid = patientid

    def make_tables(self, schema):
        from sqlalchemy import MetaData
        from sqlalchemy.orm.exc import UnmappedClassError
        meta = MetaData()
        pheno_table = make_pheno_table(meta, schema)
        mapper(self.Pheno, pheno_table)
        table_dict = {'metadata': meta, 'pheno_table':pheno_table, 'Pheno':self.Pheno}
        return table_dict

table_dict = Tables().make_tables('foo')
table_dict = Tables().make_tables('bar')

才不是.我收到与以前相同的错误消息.
我不太了解范围问题,足以说出原因.
这两种情况下的Pheno类都不是在某种局部范围内吗?

最佳答案 您正尝试将同一类Pheno映射到2个不同的表. SQLAlchemy只允许每个类使用一个主映射器,以便它知道用于session.query(Pheno)的表.目前尚不清楚你希望从你的问题中得到什么,所以我无法提出解决方案.有两个明显的选择:

>定义单独的类以映射到第二个表,
>通过传递non_primary = True参数为第二个表创建non-primary mapper,并将它(mapper()函数返回的值)传递给session.query()而不是类.

更新:为每个表定义单独的类,您可以将其定义放入make_tables():

def make_tables(schema):
    from sqlalchemy import MetaData
    meta = MetaData() 
    pheno_table = make_pheno_table(meta, schema)
    class Pheno(object):
        def __init__(self, patientid):
            self.patientid = patientid    
    mapper(Pheno, pheno_table)
    table_dict = {'metadata': meta, 
                  'pheno_class': Pheno, 
                  'pheno_table':pheno_table}
    return table_dict
点赞