Ошибка «Класс уже определен первичным преобразователем» с SQLAlchemy - PullRequest
4 голосов
/ 19 мая 2011

В октябре 2010 года я разместил этот вопрос в списке пользователей Sqlalchemy. В то время я просто использовал обходной путь clear_mappers, упомянутый в сообщении, и не пытался выяснить, в чем проблема. Это было очень непослушно с моей стороны. Сегодня я снова столкнулся с этой ошибкой и решил создать минимальный пример, который показан ниже. Майкл также обратился к тому, что , вероятно, та же проблема , еще в 2006 году. Я решил продолжить здесь, чтобы дать Майклу отдохнуть от моих глупых вопросов.

Таким образом, в результате получается, что для данного определения класса не может быть определено более одного сопоставителя. В моем случае у меня есть класс Pheno, объявленный в области видимости модуля (я предполагаю, что здесь это область верхнего уровня), и каждый раз, когда make_tables выполняется, он пытается определить другой преобразователь.

Майк писал: «Исходя из описания проблемы, приведенной выше, вы должны убедиться, что ваши классы Python объявлены в той же области, что и ваши преобразователи. Получаемое сообщение об ошибке предполагает, что« Pheno »объявлен на уровне модуля». «. Это решило бы проблему, но как мне справиться с этим, не меняя мою нынешнюю структуру? Какие еще есть варианты, если они есть? Очевидно, у mapper нет опции типа «если маппер уже определен, выйдите без выполнения каких-либо действий», что бы хорошо позаботиться об этом. Полагаю, я мог бы определить функцию-обертку, но это было бы довольно уродливо.

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')

Далее следует сообщение об ошибке. Протестировано с SQLAlchemy 0.6.3-3 на сжатие Debian.

$ 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: API mapper () , я мог бы заменить mapper(Pheno, pheno_table) выше на

from sqlalchemy.orm.exc import UnmappedClassError

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

Если картограф не определен для 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')

Однако внешне похожий

# 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 в обоих случаях не входит в какую-то локальную область видимости?

Ответы [ 2 ]

2 голосов
/ 19 мая 2011

Вы пытаетесь сопоставить один и тот же класс Pheno с двумя разными таблицами.SQLAlchemy разрешает только один первичный преобразователь для каждого класса, чтобы он знал, какую таблицу использовать для session.query(Pheno).Не ясно, что вы хотите получить от своего вопроса, поэтому я не могу предложить решение.Существует 2 очевидных варианта:

  • определить отдельный класс для сопоставления со второй таблицей,
  • создать неосновное сопоставление для второй таблицы путем передачи параметра 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
0 голосов
/ 09 мая 2013

возможно, я не совсем понял, что вы хотите, но этот рецепт создает идентичные столбцы в разных __tablename __

class TBase(object):
    """Base class is a 'mixin'.
    Guidelines for declarative mixins is at:

    http://www.sqlalchemy.org/docs/orm/extensions/declarative.html#mixin-classes

    """
    id = Column(Integer, primary_key=True)
    data = Column(String(50))

    def __repr__(self):
        return "%s(data=%r)" % (
            self.__class__.__name__, self.data
        )

class T1Foo(TBase, Base):
    __tablename__ = 't1'

class T2Foo(TBase, Base):
    __tablename__ = 't2'

engine = create_engine('sqlite:///foo.db', echo=True)

Base.metadata.create_all(engine)

sess = sessionmaker(engine)()

sess.add_all([T1Foo(data='t1'), T1Foo(data='t2'), T2Foo(data='t3'),
         T1Foo(data='t4')])

print sess.query(T1Foo).all()
print sess.query(T2Foo).all()
sess.commit()

информация в примере sqlalchemy

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...