When I tried to version tables from Kotti project using SQLAlchemy-Continuum extension to SQLAlchemy I encountered sqlalchemy.exc.InvalidRequestError: Implicitly combining column(...)
error. These tables model inheritance using joined table inheritance approach. Based on the original code from Kotti I created minimal test case showing the problem (test.py
below). The error can be seen in traceback coming after the file's content and looks like this:
sqlalchemy.exc.InvalidRequestError: Implicitly combining column contents_version.transaction_id with column nodes_version.transaction_id under attribute 'transaction_id'. Please configure one or more attributes for these same-named columns explicitly.
As the last debug line says
INFO:sqlalchemy.orm.mapper.Mapper:(DocumentVersion|documents_version) _configure_property(transaction_id, Column)
it's clear that the error happens during configuration of transaction_id
attribute of DocumentVersion
model. This model is being automatically created by SQLAlchemy-Continuum extension to track changes in the original Document
model. I guess that SQLAlchemy-Continuum extension does not handle this scenario (joined table inheritance) correctly but I have no idea how I could fix this. I've read SQLAlchemy's FAQ entry titled I’m getting a warning or error about “Implicitly combining column X under attribute Y” but given the error comes from the extension I still don't know where to look to fix this.
I raised this issue on SQLAlchemy-Continuum's tracker here and on mailing list of SQLAlchemy here but with no replies yet.
SQLAlchemy-Continuum 1.2.0, SQLAlchemy 1.0.8
test.py
:
import logging
from sqlalchemy import (Column, ForeignKey, Integer, String)
from sqlalchemy.ext.declarative import (declarative_base, declared_attr)
from sqlalchemy.orm import configure_mappers
from sqlalchemy.util import classproperty
from sqlalchemy_continuum import make_versioned
logging.basicConfig()
logging.getLogger('sqlalchemy.orm').setLevel(logging.INFO)
make_versioned(user_cls=None)
class Node(declarative_base()):
__versioned__ = {}
__mapper_args__ = dict(polymorphic_on='type',
polymorphic_identity='node',
with_polymorphic='*')
@declared_attr
def __tablename__(cls):
return '{0}s'.format(cls.__name__.lower())
id = Column(Integer(), primary_key=True)
type = Column(String(30), nullable=False)
class Content(Node):
__versioned__ = {}
@classproperty
def __mapper_args__(cls):
return dict(polymorphic_identity=cls.__name__.lower())
id = Column(Integer, ForeignKey('nodes.id'), primary_key=True)
class Document(Content):
__versioned__ = {}
id = Column(Integer(), ForeignKey('contents.id'), primary_key=True)
configure_mappers()
Output and traceback:
INFO:sqlalchemy.orm.mapper.Mapper:(Node|nodes) _configure_property(id, Column)
INFO:sqlalchemy.orm.mapper.Mapper:(Node|nodes) _configure_property(type, Column)
INFO:sqlalchemy.orm.mapper.Mapper:(Node|nodes) Identified primary key columns: ColumnSet([Column('id', Integer(), table=<nodes>, primary_key=True, nullable=False)])
INFO:sqlalchemy.orm.mapper.Mapper:(Node|nodes) constructed
INFO:sqlalchemy.orm.mapper.Mapper:(Content|contents) _configure_property(id, ColumnProperty)
INFO:sqlalchemy.orm.mapper.Mapper:(Content|contents) _configure_property(type, ColumnProperty)
INFO:sqlalchemy.orm.mapper.Mapper:(Content|contents) _configure_property(id, Column)
INFO:sqlalchemy.orm.mapper.Mapper:(Content|contents) inserting column to existing list in properties.ColumnProperty id
INFO:sqlalchemy.orm.mapper.Mapper:(Content|contents) constructed
INFO:sqlalchemy.orm.mapper.Mapper:(Document|documents) _configure_property(id, ColumnProperty)
INFO:sqlalchemy.orm.mapper.Mapper:(Document|documents) _configure_property(type, ColumnProperty)
INFO:sqlalchemy.orm.mapper.Mapper:(Document|documents) _configure_property(id, Column)
INFO:sqlalchemy.orm.mapper.Mapper:(Document|documents) inserting column to existing list in properties.ColumnProperty id
INFO:sqlalchemy.orm.mapper.Mapper:(Document|documents) constructed
INFO:sqlalchemy.orm.mapper.Mapper:(Node|nodes) _post_configure_properties() started
INFO:sqlalchemy.orm.mapper.Mapper:(Node|nodes) initialize prop id
INFO:sqlalchemy.orm.mapper.Mapper:(Node|nodes) initialize prop type
INFO:sqlalchemy.orm.mapper.Mapper:(Node|nodes) _post_configure_properties() complete
INFO:sqlalchemy.orm.mapper.Mapper:(Document|documents) _post_configure_properties() started
INFO:sqlalchemy.orm.mapper.Mapper:(Document|documents) initialize prop id
INFO:sqlalchemy.orm.mapper.Mapper:(Document|documents) initialize prop type
INFO:sqlalchemy.orm.mapper.Mapper:(Document|documents) _post_configure_properties() complete
INFO:sqlalchemy.orm.mapper.Mapper:(Content|contents) _post_configure_properties() started
INFO:sqlalchemy.orm.mapper.Mapper:(Content|contents) initialize prop id
INFO:sqlalchemy.orm.mapper.Mapper:(Content|contents) initialize prop type
INFO:sqlalchemy.orm.mapper.Mapper:(Content|contents) _post_configure_properties() complete
INFO:sqlalchemy.orm.mapper.Mapper:(Transaction|transaction) _configure_property(issued_at, Column)
INFO:sqlalchemy.orm.mapper.Mapper:(Transaction|transaction) _configure_property(id, Column)
INFO:sqlalchemy.orm.mapper.Mapper:(Transaction|transaction) _configure_property(remote_addr, Column)
INFO:sqlalchemy.orm.mapper.Mapper:(Transaction|transaction) Identified primary key columns: ColumnSet([Column('id', BigInteger(), table=<transaction>, primary_key=True, nullable=False)])
INFO:sqlalchemy.orm.mapper.Mapper:(Transaction|transaction) constructed
INFO:sqlalchemy.orm.mapper.Mapper:(NodeVersion|nodes_version) _configure_property(id, Column)
INFO:sqlalchemy.orm.mapper.Mapper:(NodeVersion|nodes_version) _configure_property(type, Column)
INFO:sqlalchemy.orm.mapper.Mapper:(NodeVersion|nodes_version) _configure_property(transaction_id, Column)
INFO:sqlalchemy.orm.mapper.Mapper:(NodeVersion|nodes_version) _configure_property(end_transaction_id, Column)
INFO:sqlalchemy.orm.mapper.Mapper:(NodeVersion|nodes_version) _configure_property(operation_type, Column)
INFO:sqlalchemy.orm.mapper.Mapper:(NodeVersion|nodes_version) Identified primary key columns: ColumnSet([Column('id', Integer(), table=<nodes_version>, primary_key=True, nullable=False), Column('transaction_id', BigInteger(), table=<nodes_version>, primary_key=True, nullable=False)])
INFO:sqlalchemy.orm.mapper.Mapper:(NodeVersion|nodes_version) constructed
INFO:sqlalchemy.orm.mapper.Mapper:(Node|nodes) _configure_property(versions, RelationshipProperty)
INFO:sqlalchemy.orm.mapper.Mapper:(Content|contents) _configure_property(versions, RelationshipProperty)
INFO:sqlalchemy.orm.mapper.Mapper:(Document|documents) _configure_property(versions, RelationshipProperty)
INFO:sqlalchemy.orm.relationships.RelationshipProperty:Node.versions setup primary join nodes.id = nodes_version.id
INFO:sqlalchemy.orm.relationships.RelationshipProperty:Node.versions setup secondary join None
INFO:sqlalchemy.orm.relationships.RelationshipProperty:Node.versions synchronize pairs [(nodes.id => nodes_version.id)]
INFO:sqlalchemy.orm.relationships.RelationshipProperty:Node.versions secondary synchronize pairs []
INFO:sqlalchemy.orm.relationships.RelationshipProperty:Node.versions local/remote pairs [(nodes.id / nodes_version.id)]
INFO:sqlalchemy.orm.relationships.RelationshipProperty:Node.versions remote columns [nodes_version.id]
INFO:sqlalchemy.orm.relationships.RelationshipProperty:Node.versions local columns [nodes.id]
INFO:sqlalchemy.orm.relationships.RelationshipProperty:Node.versions relationship direction symbol('ONETOMANY')
INFO:sqlalchemy.orm.mapper.Mapper:(NodeVersion|nodes_version) _configure_property(version_parent, RelationshipProperty)
INFO:sqlalchemy.orm.relationships.RelationshipProperty:NodeVersion.version_parent setup primary join nodes.id = nodes_version.id
INFO:sqlalchemy.orm.relationships.RelationshipProperty:NodeVersion.version_parent setup secondary join None
INFO:sqlalchemy.orm.relationships.RelationshipProperty:NodeVersion.version_parent synchronize pairs [(nodes.id => nodes_version.id)]
INFO:sqlalchemy.orm.relationships.RelationshipProperty:NodeVersion.version_parent secondary synchronize pairs []
INFO:sqlalchemy.orm.relationships.RelationshipProperty:NodeVersion.version_parent local/remote pairs [(nodes_version.id / nodes.id)]
INFO:sqlalchemy.orm.relationships.RelationshipProperty:NodeVersion.version_parent remote columns [nodes.id]
INFO:sqlalchemy.orm.relationships.RelationshipProperty:NodeVersion.version_parent local columns [nodes_version.id]
INFO:sqlalchemy.orm.relationships.RelationshipProperty:NodeVersion.version_parent relationship direction symbol('MANYTOONE')
INFO:sqlalchemy.orm.strategies.LazyLoader:NodeVersion.version_parent lazy loading clause nodes.id = :param_1
INFO:sqlalchemy.orm.strategies.LazyLoader:NodeVersion.version_parent will use query.get() to optimize instance loads
INFO:sqlalchemy.orm.strategies.LazyLoader:Node.versions lazy loading clause :param_1 = nodes_version.id
INFO:sqlalchemy.orm.mapper.Mapper:(NodeVersion|nodes_version) _configure_property(transaction, RelationshipProperty)
INFO:sqlalchemy.orm.mapper.Mapper:(ContentVersion|contents_version) _configure_property(operation_type, ColumnProperty)
INFO:sqlalchemy.orm.mapper.Mapper:(ContentVersion|contents_version) _configure_property(transaction_id, ColumnProperty)
INFO:sqlalchemy.orm.mapper.Mapper:(ContentVersion|contents_version) _configure_property(end_transaction_id, ColumnProperty)
INFO:sqlalchemy.orm.mapper.Mapper:(ContentVersion|contents_version) _configure_property(id, ColumnProperty)
INFO:sqlalchemy.orm.mapper.Mapper:(ContentVersion|contents_version) _configure_property(type, ColumnProperty)
INFO:sqlalchemy.orm.mapper.Mapper:(ContentVersion|contents_version) _configure_property(version_parent, RelationshipProperty)
INFO:sqlalchemy.orm.mapper.Mapper:(ContentVersion|contents_version) _configure_property(transaction, RelationshipProperty)
INFO:sqlalchemy.orm.mapper.Mapper:(ContentVersion|contents_version) _configure_property(id, Column)
INFO:sqlalchemy.orm.mapper.Mapper:(ContentVersion|contents_version) inserting column to existing list in properties.ColumnProperty id
INFO:sqlalchemy.orm.mapper.Mapper:(ContentVersion|contents_version) constructed
INFO:sqlalchemy.orm.mapper.Mapper:(DocumentVersion|documents_version) _configure_property(operation_type, ColumnProperty)
INFO:sqlalchemy.orm.mapper.Mapper:(DocumentVersion|documents_version) _configure_property(transaction_id, ColumnProperty)
INFO:sqlalchemy.orm.mapper.Mapper:(DocumentVersion|documents_version) _configure_property(end_transaction_id, ColumnProperty)
INFO:sqlalchemy.orm.mapper.Mapper:(DocumentVersion|documents_version) _configure_property(id, ColumnProperty)
INFO:sqlalchemy.orm.mapper.Mapper:(DocumentVersion|documents_version) _configure_property(type, ColumnProperty)
INFO:sqlalchemy.orm.mapper.Mapper:(DocumentVersion|documents_version) _configure_property(version_parent, RelationshipProperty)
INFO:sqlalchemy.orm.mapper.Mapper:(DocumentVersion|documents_version) _configure_property(transaction, RelationshipProperty)
INFO:sqlalchemy.orm.mapper.Mapper:(DocumentVersion|documents_version) _configure_property(transaction_id, Column)
Traceback (most recent call last):
File "/home/piotr/projects/sqlalchemy-continuum/test.py", line 38, in <module>
configure_mappers()
File "/home/piotr/projects/sqlalchemy-continuum/env/lib/python2.7/site-packages/sqlalchemy/orm/mapper.py", line 2736, in configure_mappers
Mapper.dispatch._for_class(Mapper).after_configured()
File "/home/piotr/projects/sqlalchemy-continuum/env/lib/python2.7/site-packages/sqlalchemy/event/attr.py", line 218, in __call__
fn(*args, **kw)
File "/home/piotr/projects/sqlalchemy-continuum/env/lib/python2.7/site-packages/sqlalchemy/orm/events.py", line 550, in wrap
fn(*arg, **kw)
File "/home/piotr/projects/sqlalchemy-continuum/sqlalchemy_continuum/builder.py", line 165, in configure_versioned_classes
self.build_models()
File "/home/piotr/projects/sqlalchemy-continuum/sqlalchemy_continuum/builder.py", line 87, in build_models
self.manager.transaction_cls
File "/home/piotr/projects/sqlalchemy-continuum/sqlalchemy_continuum/model_builder.py", line 263, in __call__
self.version_class = self.build_model(table)
File "/home/piotr/projects/sqlalchemy-continuum/sqlalchemy_continuum/model_builder.py", line 250, in build_model
args
File "/home/piotr/projects/sqlalchemy-continuum/env/lib/python2.7/site-packages/sqlalchemy/ext/declarative/api.py", line 55, in __init__
_as_declarative(cls, classname, cls.__dict__)
File "/home/piotr/projects/sqlalchemy-continuum/env/lib/python2.7/site-packages/sqlalchemy/ext/declarative/base.py", line 88, in _as_declarative
_MapperConfig.setup_mapping(cls, classname, dict_)
File "/home/piotr/projects/sqlalchemy-continuum/env/lib/python2.7/site-packages/sqlalchemy/ext/declarative/base.py", line 103, in setup_mapping
cfg_cls(cls_, classname, dict_)
File "/home/piotr/projects/sqlalchemy-continuum/env/lib/python2.7/site-packages/sqlalchemy/ext/declarative/base.py", line 135, in __init__
self._early_mapping()
File "/home/piotr/projects/sqlalchemy-continuum/env/lib/python2.7/site-packages/sqlalchemy/ext/declarative/base.py", line 138, in _early_mapping
self.map()
File "/home/piotr/projects/sqlalchemy-continuum/env/lib/python2.7/site-packages/sqlalchemy/ext/declarative/base.py", line 529, in map
**self.mapper_args
File "<string>", line 2, in mapper
File "/home/piotr/projects/sqlalchemy-continuum/env/lib/python2.7/site-packages/sqlalchemy/orm/mapper.py", line 627, in __init__
self._configure_properties()
File "/home/piotr/projects/sqlalchemy-continuum/env/lib/python2.7/site-packages/sqlalchemy/orm/mapper.py", line 1318, in _configure_properties
setparent=True)
File "/home/piotr/projects/sqlalchemy-continuum/env/lib/python2.7/site-packages/sqlalchemy/orm/mapper.py", line 1525, in _configure_property
prop = self._property_from_column(key, prop)
File "/home/piotr/projects/sqlalchemy-continuum/env/lib/python2.7/site-packages/sqlalchemy/orm/mapper.py", line 1650, in _property_from_column
raise sa_exc.InvalidRequestError(msg)
sqlalchemy.exc.InvalidRequestError: Implicitly combining column contents_version.transaction_id with column nodes_version.transaction_id under attribute 'transaction_id'. Please configure one or more attributes for these same-named columns explicitly.
Process finished with exit code 1