SQLAlchemy 出现“类已定义主映射器”错误

Fah*_*tha 5 sqlalchemy

早在 2010 年 10 月,我就将这个问题发布到 Sqlalchemy 用户列表中。当时,我只是使用了clear_mappers消息中提到的解决方法,并没有尝试找出问题所在。我真是太淘气了。今天我再次遇到这个错误,并决定构建一个最小的示例,如下所示。Michael 在 2006 年也曾讨论过可能是同样的问题。我决定在这里跟进,让 Michael 从我愚蠢的问题中解脱出来。

因此,结果似乎是,对于给定的类定义,不能定义多个映射器。就我而言,我在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')
Run Code Online (Sandbox Code Playgroud)

错误消息如下。在 Debian scrape 上使用 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.
Run Code Online (Sandbox Code Playgroud)

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

from sqlalchemy.orm.exc import UnmappedClassError

try:
    class_mapper(Pheno)
except UnmappedClassError:
    mapper(Pheno, pheno_table)
Run Code Online (Sandbox Code Playgroud)

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

编辑2:根据丹尼斯的建议,以下工作:

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')
Run Code Online (Sandbox Code Playgroud)

然而,表面上相似

# 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')
Run Code Online (Sandbox Code Playgroud)

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

Den*_*ach 2

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

  • 定义单独的类来映射到第二个表,
  • 通过传递参数为第二个表创建非主映射器non_primary=True并将其(函数返回的值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
Run Code Online (Sandbox Code Playgroud)