from operator import is_not
from typing_extensions import Annotated
import sqlalchemy as sa
from sqlalchemy import ForeignKey
from sqlalchemy import func
from sqlalchemy import Integer
from sqlalchemy import MetaData
from sqlalchemy import schema
from sqlalchemy import select
from sqlalchemy import String
from sqlalchemy import testing
from sqlalchemy.orm import base as orm_base
from sqlalchemy.orm import class_mapper
from sqlalchemy.orm import clear_mappers
from sqlalchemy.orm import close_all_sessions
from sqlalchemy.orm import column_property
from sqlalchemy.orm import configure_mappers
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import declarative_mixin
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import deferred
from sqlalchemy.orm import events as orm_events
from sqlalchemy.orm import has_inherited_table
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import registry
from sqlalchemy.orm import relationship
from sqlalchemy.orm import synonym
from sqlalchemy.testing import assert_raises
from sqlalchemy.testing import assert_raises_message
from sqlalchemy.testing import eq_
from sqlalchemy.testing import expect_raises_message
from sqlalchemy.testing import expect_warnings
from sqlalchemy.testing import fixtures
from sqlalchemy.testing import is_
from sqlalchemy.testing import is_true
from sqlalchemy.testing import mock
from sqlalchemy.testing.fixtures import fixture_session
from sqlalchemy.testing.schema import Column
from sqlalchemy.testing.schema import mapped_column
from sqlalchemy.testing.schema import Table
from sqlalchemy.testing.util import gc_collect
from sqlalchemy.util import classproperty
Base = None
mapper_registry = None
class DeclarativeTestBase(
testing.AssertsCompiledSQL,
fixtures.TestBase,
testing.AssertsExecutionResults,
):
def setup_test(self):
global Base, mapper_registry
mapper_registry = registry(metadata=MetaData())
class Base(DeclarativeBase):
registry = mapper_registry
def teardown_test(self):
close_all_sessions()
clear_mappers()
with testing.db.begin() as conn:
Base.metadata.drop_all(conn)
class DeclarativeMixinTest(DeclarativeTestBase):
@testing.combinations("generate_base", "subclass", argnames="base_type")
def test_init_subclass_works(self, registry, base_type):
reg = registry
if base_type == "generate_base":
class Base:
def __init_subclass__(cls):
cls.id = Column(Integer, primary_key=True)
Base = registry.generate_base(cls=Base)
elif base_type == "subclass":
class Base(DeclarativeBase):
registry = reg
def __init_subclass__(cls):
cls.id = Column(Integer, primary_key=True)
super().__init_subclass__()
else:
assert False
class Foo(Base):
__tablename__ = "foo"
name = Column(String)
self.assert_compile(select(Foo), "SELECT foo.name, foo.id FROM foo")
@testing.variation("base_type", ["generate_base", "subclass"])
@testing.variation("attrname", ["table", "tablename"])
@testing.variation("position", ["base", "abstract"])
@testing.variation("assert_no_extra_cols", [True, False])
def test_declared_attr_on_base(
self, registry, base_type, attrname, position, assert_no_extra_cols
):
"""test #11509"""
if position.abstract:
if base_type.generate_base:
SuperBase = registry.generate_base()
class Base(SuperBase):
__abstract__ = True
if attrname.table:
@declared_attr.directive
def __table__(cls):
return Table(
cls.__name__,
cls.registry.metadata,
Column("id", Integer, primary_key=True),
)
elif attrname.tablename:
@declared_attr.directive
def __tablename__(cls):
return cls.__name__
else:
attrname.fail()
elif base_type.subclass:
class SuperBase(DeclarativeBase):
pass
class Base(SuperBase):
__abstract__ = True
if attrname.table:
@declared_attr.directive
def __table__(cls):
return Table(
cls.__name__,
cls.registry.metadata,
Column("id", Integer, primary_key=True),
)
elif attrname.tablename:
@declared_attr.directive
def __tablename__(cls):
return cls.__name__
else:
attrname.fail()
else:
base_type.fail()
else:
if base_type.generate_base:
class Base:
if attrname.table:
@declared_attr.directive
def __table__(cls):
return Table(
cls.__name__,
cls.registry.metadata,
Column("id", Integer, primary_key=True),
)
elif attrname.tablename:
@declared_attr.directive
def __tablename__(cls):
return cls.__name__
else:
attrname.fail()
Base = registry.generate_base(cls=Base)
elif base_type.subclass:
class Base(DeclarativeBase):
if attrname.table:
@declared_attr.directive
def __table__(cls):
return Table(
cls.__name__,
cls.registry.metadata,
Column("id", Integer, primary_key=True),
)
elif attrname.tablename:
@declared_attr.directive
def __tablename__(cls):
return cls.__name__
else:
attrname.fail()
else:
base_type.fail()
if attrname.table and assert_no_extra_cols:
with expect_raises_message(
sa.exc.ArgumentError,
"Can't add additional column 'data' when specifying __table__",
):
class MyNopeClass(Base):
data = Column(String)
return
class MyClass(Base):
if attrname.tablename:
id = Column(Integer, primary_key=True)
class MyOtherClass(Base):
if attrname.tablename:
id = Column(Integer, primary_key=True)
t = Table(
"my_override",
Base.metadata,
Column("id", Integer, primary_key=True),
)
class MyOverrideClass(Base):
__table__ = t
Base.registry.configure()
assert isinstance(MyClass.__dict__["__table__"], schema.Table)
assert isinstance(MyOtherClass.__dict__["__table__"], schema.Table)
eq_(MyClass.__table__.name, "MyClass")
eq_(MyClass.__table__.c.keys(), ["id"])
eq_(MyOtherClass.__table__.name, "MyOtherClass")
eq_(MyOtherClass.__table__.c.keys(), ["id"])
is_(MyOverrideClass.__table__, t)
def test_simple_wbase(self):
class MyMixin:
id = Column(
Integer, primary_key=True, test_needs_autoincrement=True
)
def foo(self):
return "bar" + str(self.id)
class MyModel(Base, MyMixin):
__tablename__ = "test"
name = Column(String(100), nullable=False, index=True)
Base.metadata.create_all(testing.db)
session = fixture_session()
session.add(MyModel(name="testing"))
session.flush()
session.expunge_all()
obj = session.query(MyModel).one()
eq_(obj.id, 1)
eq_(obj.name, "testing")
eq_(obj.foo(), "bar1")
def test_simple_wdecorator(self):
class MyMixin:
id = Column(
Integer, primary_key=True, test_needs_autoincrement=True
)
def foo(self):
return "bar" + str(self.id)
@mapper_registry.mapped
class MyModel(MyMixin):
__tablename__ = "test"
name = Column(String(100), nullable=False, index=True)
Base.metadata.create_all(testing.db)
session = fixture_session()
session.add(MyModel(name="testing"))
session.flush()
session.expunge_all()
obj = session.query(MyModel).one()
eq_(obj.id, 1)
eq_(obj.name, "testing")
eq_(obj.foo(), "bar1")
def test_declarative_mixin_decorator(self):
@declarative_mixin
class MyMixin:
id = Column(
Integer, primary_key=True, test_needs_autoincrement=True
)
def foo(self):
return "bar" + str(self.id)
@mapper_registry.mapped
class MyModel(MyMixin):
__tablename__ = "test"
name = Column(String(100), nullable=False, index=True)
Base.metadata.create_all(testing.db)
session = fixture_session()
session.add(MyModel(name="testing"))
session.flush()
session.expunge_all()
obj = session.query(MyModel).one()
eq_(obj.id, 1)
eq_(obj.name, "testing")
eq_(obj.foo(), "bar1")
@testing.combinations(Column, mapped_column, argnames="_column")
def test_unique_column(self, _column):
class MyMixin:
id = _column(Integer, primary_key=True)
value = _column(String, unique=True)
class MyModel(Base, MyMixin):
__tablename__ = "test"
assert MyModel.__table__.c.value.unique
@testing.combinations(Column, mapped_column, argnames="_column")
def test_hierarchical_bases_wbase(self, _column):
class MyMixinParent:
id = _column(
Integer, primary_key=True, test_needs_autoincrement=True
)
def foo(self):
return "bar" + str(self.id)
class MyMixin(MyMixinParent):
baz = _column(String(100), nullable=False, index=True)
class MyModel(Base, MyMixin):
__tablename__ = "test"
name = _column(String(100), nullable=False, index=True)
Base.metadata.create_all(testing.db)
session = fixture_session()
session.add(MyModel(name="testing", baz="fu"))
session.flush()
session.expunge_all()
obj = session.query(MyModel).one()
eq_(obj.id, 1)
eq_(obj.name, "testing")
eq_(obj.foo(), "bar1")
eq_(obj.baz, "fu")
@testing.combinations(Column, mapped_column, argnames="_column")
def test_hierarchical_bases_wdecorator(self, _column):
class MyMixinParent:
id = _column(
Integer, primary_key=True, test_needs_autoincrement=True
)
def foo(self):
return "bar" + str(self.id)
class MyMixin(MyMixinParent):
baz = _column(String(100), nullable=False, index=True)
@mapper_registry.mapped
class MyModel(MyMixin):
__tablename__ = "test"
name = Column(String(100), nullable=False, index=True)
Base.metadata.create_all(testing.db)
session = fixture_session()
session.add(MyModel(name="testing", baz="fu"))
session.flush()
session.expunge_all()
obj = session.query(MyModel).one()
eq_(obj.id, 1)
eq_(obj.name, "testing")
eq_(obj.foo(), "bar1")
eq_(obj.baz, "fu")
@testing.combinations(Column, mapped_column, argnames="_column")
def test_mixin_overrides_wbase(self, _column):
"""test a mixin that overrides a column on a superclass."""
class MixinA:
foo = _column(String(50))
class MixinB(MixinA):
foo = _column(Integer)
class MyModelA(Base, MixinA):
__tablename__ = "testa"
id = _column(Integer, primary_key=True)
class MyModelB(Base, MixinB):
__tablename__ = "testb"
id = _column(Integer, primary_key=True)
eq_(MyModelA.__table__.c.foo.type.__class__, String)
eq_(MyModelB.__table__.c.foo.type.__class__, Integer)
def test_mixin_overrides_wdecorator(self):
"""test a mixin that overrides a column on a superclass."""
class MixinA:
foo = Column(String(50))
class MixinB(MixinA):
foo = Column(Integer)
@mapper_registry.mapped
class MyModelA(MixinA):
__tablename__ = "testa"
id = Column(Integer, primary_key=True)
@mapper_registry.mapped
class MyModelB(MixinB):
__tablename__ = "testb"
id = Column(Integer, primary_key=True)
eq_(MyModelA.__table__.c.foo.type.__class__, String)
eq_(MyModelB.__table__.c.foo.type.__class__, Integer)
def test_same_base_multiple_times(self):
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
surname = Column(String)
class SpecialUser(User):
__abstract__ = True
class ConvenienceStuff(User):
__abstract__ = True
def fullname(self):
return self.name + " " + self.surname
class Manager(SpecialUser, ConvenienceStuff, User):
__tablename__ = "manager"
id = Column(Integer, ForeignKey("user.id"), primary_key=True)
title = Column(String)
eq_(Manager.__table__.name, "manager")
def test_same_base_multiple_metadata(self):
m1 = MetaData()
m2 = MetaData()
class B1(Base):
__abstract__ = True
metadata = m1
class B2(Base):
__abstract__ = True
metadata = m2
def fullname(self):
return self.name + " " + self.surname
class User(B1):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
surname = Column(String)
class AD(B1):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
class OtherUser(B2):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
username = Column(String)
class BUser(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
login = Column(String)
eq_(set(m1.tables), {"user", "address"})
eq_(set(m2.tables), {"user"})
eq_(set(Base.registry.metadata.tables), {"user"})
eq_(Base.registry.metadata.tables["user"].c.keys(), ["id", "login"])
eq_(m1.tables["user"].c.keys(), ["id", "name", "surname"])
eq_(m2.tables["user"].c.keys(), ["id", "username"])
def test_same_registry_multiple_metadata(self):
m1 = MetaData()
m2 = MetaData()
reg = registry()
class B1:
metadata = m1
class B2:
metadata = m2
def fullname(self):
return self.name + " " + self.surname
@reg.mapped
class User(B1):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String)
surname = Column(String)
@reg.mapped
class AD(B1):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
@reg.mapped
class OtherUser(B2):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
username = Column(String)
@reg.mapped
class BUser:
__tablename__ = "user"
id = Column(Integer, primary_key=True)
login = Column(String)
eq_(set(m1.tables), {"user", "address"})
eq_(set(m2.tables), {"user"})
eq_(set(reg.metadata.tables), {"user"})
eq_(reg.metadata.tables["user"].c.keys(), ["id", "login"])
eq_(m1.tables["user"].c.keys(), ["id", "name", "surname"])
eq_(m2.tables["user"].c.keys(), ["id", "username"])
@testing.combinations(Column, mapped_column, argnames="_column")
@testing.combinations("strname", "colref", "objref", argnames="fk_type")
def test_fk_mixin(self, decl_base, fk_type, _column):
class Bar(decl_base):
__tablename__ = "bar"
id = _column(Integer, primary_key=True)
if fk_type == "strname":
fk = ForeignKey("bar.id")
elif fk_type == "colref":
fk = ForeignKey(Bar.__table__.c.id)
elif fk_type == "objref":
fk = ForeignKey(Bar.id)
else:
assert False
class MyMixin:
foo = _column(Integer, fk)
class A(MyMixin, decl_base):
__tablename__ = "a"
id = _column(Integer, primary_key=True)
class B(MyMixin, decl_base):
__tablename__ = "b"
id = _column(Integer, primary_key=True)
is_true(A.__table__.c.foo.references(Bar.__table__.c.id))
is_true(B.__table__.c.foo.references(Bar.__table__.c.id))
fka = list(A.__table__.c.foo.foreign_keys)[0]
fkb = list(A.__table__.c.foo.foreign_keys)[0]
is_not(fka, fkb)
@testing.combinations(Column, mapped_column, argnames="_column")
def test_fk_mixin_self_referential_error(self, decl_base, _column):
class MyMixin:
id = _column(Integer, primary_key=True)
foo = _column(Integer, ForeignKey(id))
with expect_raises_message(
sa.exc.InvalidRequestError,
"Columns with foreign keys to non-table-bound columns "
"must be declared as @declared_attr",
):
class A(MyMixin, decl_base):
__tablename__ = "a"
@testing.combinations(Column, mapped_column, argnames="_column")
def test_fk_mixin_self_referential_declared_attr(self, decl_base, _column):
class MyMixin:
id = _column(Integer, primary_key=True)
@declared_attr
def foo(cls):
return _column(Integer, ForeignKey(cls.id))
class A(MyMixin, decl_base):
__tablename__ = "a"
class B(MyMixin, decl_base):
__tablename__ = "b"
is_true(A.__table__.c.foo.references(A.__table__.c.id))
is_true(B.__table__.c.foo.references(B.__table__.c.id))
fka = list(A.__table__.c.foo.foreign_keys)[0]
fkb = list(A.__table__.c.foo.foreign_keys)[0]
is_not(fka, fkb)
is_true(A.__table__.c.foo.references(A.__table__.c.id))
is_true(B.__table__.c.foo.references(B.__table__.c.id))
fka = list(A.__table__.c.foo.foreign_keys)[0]
fkb = list(A.__table__.c.foo.foreign_keys)[0]
is_not(fka, fkb)
def test_not_allowed(self):
class MyRelMixin:
foo = relationship("Bar")
def go():
class MyModel(Base, MyRelMixin):
__tablename__ = "foo"
assert_raises(sa.exc.InvalidRequestError, go)
class MyDefMixin:
foo = deferred(Column("foo", String))
def go():
class MyModel(Base, MyDefMixin):
__tablename__ = "foo"
assert_raises(sa.exc.InvalidRequestError, go)
class MyCPropMixin:
foo = column_property(Column("foo", String))
def go():
class MyModel(Base, MyCPropMixin):
__tablename__ = "foo"
assert_raises(sa.exc.InvalidRequestError, go)
def test_table_name_inherited(self):
class MyMixin:
@declared_attr
def __tablename__(cls):
return cls.__name__.lower()
id = Column(Integer, primary_key=True)
class MyModel(Base, MyMixin):
pass
eq_(MyModel.__table__.name, "mymodel")
def test_classproperty_still_works(self):
class MyMixin:
@classproperty
def __tablename__(cls):
return cls.__name__.lower()
id = Column(Integer, primary_key=True)
class MyModel(Base, MyMixin):
__tablename__ = "overridden"
eq_(MyModel.__table__.name, "overridden")
def test_table_name_not_inherited(self):
class MyMixin:
@declared_attr
def __tablename__(cls):
return cls.__name__.lower()
id = Column(Integer, primary_key=True)
class MyModel(Base, MyMixin):
__tablename__ = "overridden"
eq_(MyModel.__table__.name, "overridden")
def test_table_name_inheritance_order(self):
class MyMixin1:
@declared_attr
def __tablename__(cls):
return cls.__name__.lower() + "1"
class MyMixin2:
@declared_attr
def __tablename__(cls):
return cls.__name__.lower() + "2"
class MyModel(Base, MyMixin1, MyMixin2):
id = Column(Integer, primary_key=True)
eq_(MyModel.__table__.name, "mymodel1")
def test_table_name_dependent_on_subclass(self):
class MyHistoryMixin:
@declared_attr
def __tablename__(cls):
return cls.parent_name + "_changelog"
class MyModel(Base, MyHistoryMixin):
parent_name = "foo"
id = Column(Integer, primary_key=True)
eq_(MyModel.__table__.name, "foo_changelog")
def test_table_args_inherited(self):
class MyMixin:
__table_args__ = {"mysql_engine": "InnoDB"}
class MyModel(Base, MyMixin):
__tablename__ = "test"
id = Column(Integer, primary_key=True)
eq_(MyModel.__table__.kwargs, {"mysql_engine": "InnoDB"})
def test_table_args_inherited_descriptor(self):
class MyMixin:
@declared_attr
def __table_args__(cls):
return {"info": cls.__name__}
class MyModel(Base, MyMixin):
__tablename__ = "test"
id = Column(Integer, primary_key=True)
eq_(MyModel.__table__.info, "MyModel")
def test_table_args_inherited_single_table_inheritance(self):
class MyMixin:
__table_args__ = {"mysql_engine": "InnoDB"}
class General(Base, MyMixin):
__tablename__ = "test"
id = Column(Integer, primary_key=True)
type_ = Column(String(50))
__mapper__args = {"polymorphic_on": type_}
class Specific(General):
__mapper_args__ = {"polymorphic_identity": "specific"}
assert Specific.__table__ is General.__table__
eq_(General.__table__.kwargs, {"mysql_engine": "InnoDB"})
def test_columns_single_table_inheritance(self):
"""Test a column on a mixin with an alternate attribute name,
mapped to a superclass and single-table inheritance subclass.
The superclass table gets the column, the subclass shares
the MapperProperty.
"""
class MyMixin:
foo = Column("foo", Integer)
bar = Column("bar_newname", Integer)
class General(Base, MyMixin):
__tablename__ = "test"
id = Column(Integer, primary_key=True)
type_ = Column(String(50))
__mapper__args = {"polymorphic_on": type_}
class Specific(General):
__mapper_args__ = {"polymorphic_identity": "specific"}
assert General.bar.prop.columns[0] is General.__table__.c.bar_newname
assert len(General.bar.prop.columns) == 1
assert Specific.bar.prop is General.bar.prop
@testing.skip_if(
lambda: testing.against("oracle"),
"Test has an empty insert in it at the moment",
)
@testing.combinations(Column, mapped_column, argnames="_column")
def test_columns_single_inheritance_conflict_resolution(self, _column):
"""Test that a declared_attr can return the existing column and it will
be ignored. this allows conditional columns to be added.
See [ticket:2472].
"""
class Person(Base):
__tablename__ = "person"
id = _column(Integer, primary_key=True)
class Mixin:
@declared_attr
def target_id(cls):
return cls.__table__.c.get(
"target_id", _column(Integer, ForeignKey("other.id"))
)
@declared_attr
def target(cls):
return relationship("Other")
class Engineer(Mixin, Person):
"""single table inheritance"""
class Manager(Mixin, Person):
"""single table inheritance"""
class Other(Base):
__tablename__ = "other"
id = _column(Integer, primary_key=True)
is_(
Engineer.target_id.property.columns[0],
Person.__table__.c.target_id,
)
is_(
Manager.target_id.property.columns[0], Person.__table__.c.target_id
)
Base.metadata.create_all(testing.db)
session = fixture_session()
o1, o2 = Other(), Other()
session.add_all(
[Engineer(target=o1), Manager(target=o2), Manager(target=o1)]
)
session.commit()
eq_(session.query(Engineer).first().target, o1)
@testing.combinations(Column, mapped_column, argnames="_column")
def test_columns_joined_table_inheritance(self, _column):
"""Test a column on a mixin with an alternate attribute name,
mapped to a superclass and joined-table inheritance subclass.
Both tables get the column, in the case of the subclass the two
columns are joined under one MapperProperty.
"""
class MyMixin:
foo = _column("foo", Integer)
bar = _column("bar_newname", Integer)
class General(Base, MyMixin):
__tablename__ = "test"
id = _column(Integer, primary_key=True)
type_ = _column(String(50))
__mapper_args__ = {"polymorphic_on": type_}
class Specific(General):
__tablename__ = "sub"
id = _column(Integer, ForeignKey("test.id"), primary_key=True)
__mapper_args__ = {"polymorphic_identity": "specific"}
assert General.bar.prop.columns[0] is General.__table__.c.bar_newname
assert len(General.bar.prop.columns) == 1
assert Specific.bar.prop is General.bar.prop
eq_(len(Specific.bar.prop.columns), 1)
assert Specific.bar.prop.columns[0] is General.__table__.c.bar_newname
def test_column_join_checks_superclass_type(self):
"""Test that the logic which joins subclass props to those
of the superclass checks that the superclass property is a column.
"""
class General(Base):
__tablename__ = "test"
id = Column(Integer, primary_key=True)
general_id = Column(Integer, ForeignKey("test.id"))
type_ = relationship("General")
class Specific(General):
__tablename__ = "sub"
id = Column(Integer, ForeignKey("test.id"), primary_key=True)
type_ = Column("foob", String(50))
assert isinstance(General.type_.property, sa.orm.RelationshipProperty)
assert Specific.type_.property.columns[0] is Specific.__table__.c.foob
def test_column_join_checks_subclass_type(self):
"""Test that the logic which joins subclass props to those
of the superclass checks that the subclass property is a column.
"""
def go():
class General(Base):
__tablename__ = "test"
id = Column(Integer, primary_key=True)
type_ = Column("foob", Integer)
class Specific(General):
__tablename__ = "sub"
id = Column(Integer, ForeignKey("test.id"), primary_key=True)
specific_id = Column(Integer, ForeignKey("sub.id"))
type_ = relationship("Specific")
assert_raises_message(
sa.exc.ArgumentError, "column 'foob' conflicts with property", go
)
def test_table_args_overridden(self):
class MyMixin:
__table_args__ = {"mysql_engine": "Foo"}
class MyModel(Base, MyMixin):
__tablename__ = "test"
__table_args__ = {"mysql_engine": "InnoDB"}
id = Column(Integer, primary_key=True)
eq_(MyModel.__table__.kwargs, {"mysql_engine": "InnoDB"})
@testing.teardown_events(orm_events.MapperEvents)
def test_declare_first_mixin(self):
canary = mock.Mock()
class MyMixin:
@classmethod
def __declare_first__(cls):
canary.declare_first__(cls)
@classmethod
def __declare_last__(cls):
canary.declare_last__(cls)
class MyModel(Base, MyMixin):
__tablename__ = "test"
id = Column(Integer, primary_key=True)
configure_mappers()
eq_(
canary.mock_calls,
[
mock.call.declare_first__(MyModel),
mock.call.declare_last__(MyModel),
],
)
@testing.teardown_events(orm_events.MapperEvents)
def test_declare_first_base(self):
canary = mock.Mock()
class MyMixin:
@classmethod
def __declare_first__(cls):
canary.declare_first__(cls)
@classmethod
def __declare_last__(cls):
canary.declare_last__(cls)
class Base(MyMixin):
pass
Base = declarative_base(cls=Base)
class MyModel(Base):
__tablename__ = "test"
id = Column(Integer, primary_key=True)
configure_mappers()
eq_(
canary.mock_calls,
[
mock.call.declare_first__(MyModel),
mock.call.declare_last__(MyModel),
],
)
@testing.teardown_events(orm_events.MapperEvents)
def test_declare_first_direct(self):
canary = mock.Mock()
class MyOtherModel(Base):
__tablename__ = "test2"
id = Column(Integer, primary_key=True)
@classmethod
def __declare_first__(cls):
canary.declare_first__(cls)
@classmethod
def __declare_last__(cls):
canary.declare_last__(cls)
configure_mappers()
eq_(
canary.mock_calls,
[
mock.call.declare_first__(MyOtherModel),
mock.call.declare_last__(MyOtherModel),
],
)
def test_mapper_args_declared_attr(self):
class ComputedMapperArgs:
@declared_attr
def __mapper_args__(cls):
if cls.__name__ == "Person":
return {"polymorphic_on": cls.discriminator}
else:
return {"polymorphic_identity": cls.__name__}
class Person(Base, ComputedMapperArgs):
__tablename__ = "people"
id = Column(Integer, primary_key=True)
discriminator = Column("type", String(50))
class Engineer(Person):
pass
configure_mappers()
assert class_mapper(Person).polymorphic_on is Person.__table__.c.type
eq_(class_mapper(Engineer).polymorphic_identity, "Engineer")
def test_mapper_args_declared_attr_two(self):
class ComputedMapperArgs:
@declared_attr
def __mapper_args__(cls):
if cls.__name__ == "Person":
return {"polymorphic_on": cls.discriminator}
else:
return {"polymorphic_identity": cls.__name__}
class Person(Base, ComputedMapperArgs):
__tablename__ = "people"
id = Column(Integer, primary_key=True)
discriminator = Column("type", String(50))
class Engineer(Person, ComputedMapperArgs):
pass
configure_mappers()
assert class_mapper(Person).polymorphic_on is Person.__table__.c.type
eq_(class_mapper(Engineer).polymorphic_identity, "Engineer")
def test_table_args_composite(self):
class MyMixin1:
__table_args__ = {"info": {"baz": "bob"}}
class MyMixin2:
__table_args__ = {"info": {"foo": "bar"}}
class MyModel(Base, MyMixin1, MyMixin2):
__tablename__ = "test"
@declared_attr
def __table_args__(self):
info = {}
args = dict(info=info)
info.update(MyMixin1.__table_args__["info"])
info.update(MyMixin2.__table_args__["info"])
return args
id = Column(Integer, primary_key=True)
eq_(MyModel.__table__.info, {"foo": "bar", "baz": "bob"})
def test_mapper_args_inherited(self):
class MyMixin:
__mapper_args__ = {"always_refresh": True}
class MyModel(Base, MyMixin):
__tablename__ = "test"
id = Column(Integer, primary_key=True)
eq_(MyModel.__mapper__.always_refresh, True)
def test_mapper_args_inherited_descriptor(self):
class MyMixin:
@declared_attr
def __mapper_args__(cls):
if cls.__name__ == "MyModel":
return dict(always_refresh=True)
else:
return dict(always_refresh=False)
class MyModel(Base, MyMixin):
__tablename__ = "test"
id = Column(Integer, primary_key=True)
eq_(MyModel.__mapper__.always_refresh, True)
def test_mapper_args_polymorphic_on_inherited(self):
class MyMixin:
type_ = Column(String(50))
__mapper_args__ = {"polymorphic_on": type_}
class MyModel(Base, MyMixin):
__tablename__ = "test"
id = Column(Integer, primary_key=True)
col = MyModel.__mapper__.polymorphic_on
eq_(col.name, "type_")
assert col.table is not None
def test_mapper_args_overridden(self):
class MyMixin:
__mapper_args__ = dict(always_refresh=True)
class MyModel(Base, MyMixin):
__tablename__ = "test"
__mapper_args__ = dict(always_refresh=False)
id = Column(Integer, primary_key=True)
eq_(MyModel.__mapper__.always_refresh, False)
def test_mapper_args_composite(self):
class MyMixin1:
type_ = Column(String(50))
__mapper_args__ = {"polymorphic_on": type_}
class MyMixin2:
__mapper_args__ = {"always_refresh": True}
class MyModel(Base, MyMixin1, MyMixin2):
__tablename__ = "test"
@declared_attr
def __mapper_args__(cls):
args = {}
args.update(MyMixin1.__mapper_args__)
args.update(MyMixin2.__mapper_args__)
if cls.__name__ != "MyModel":
args.pop("polymorphic_on")
args["polymorphic_identity"] = cls.__name__
return args
id = Column(Integer, primary_key=True)
class MySubModel(MyModel):
pass
eq_(MyModel.__mapper__.polymorphic_on.name, "type_")
assert MyModel.__mapper__.polymorphic_on.table is not None
eq_(MyModel.__mapper__.always_refresh, True)
eq_(MySubModel.__mapper__.always_refresh, True)
eq_(MySubModel.__mapper__.polymorphic_identity, "MySubModel")
def test_mapper_args_property(self):
class MyModel(Base):
@declared_attr
def __tablename__(cls):
return cls.__name__.lower()
@declared_attr
def __table_args__(cls):
return {"mysql_engine": "InnoDB"}
@declared_attr
def __mapper_args__(cls):
args = {}
args["polymorphic_identity"] = cls.__name__
return args
id = Column(Integer, primary_key=True)
class MySubModel(MyModel):
id = Column(Integer, ForeignKey("mymodel.id"), primary_key=True)
class MySubModel2(MyModel):
__tablename__ = "sometable"
id = Column(Integer, ForeignKey("mymodel.id"), primary_key=True)
eq_(MyModel.__mapper__.polymorphic_identity, "MyModel")
eq_(MySubModel.__mapper__.polymorphic_identity, "MySubModel")
eq_(MyModel.__table__.kwargs["mysql_engine"], "InnoDB")
eq_(MySubModel.__table__.kwargs["mysql_engine"], "InnoDB")
eq_(MySubModel2.__table__.kwargs["mysql_engine"], "InnoDB")
eq_(MyModel.__table__.name, "mymodel")
eq_(MySubModel.__table__.name, "mysubmodel")
def test_mapper_args_custom_base(self):
"""test the @declared_attr approach from a custom base."""
class Base:
@declared_attr
def __tablename__(cls):
return cls.__name__.lower()
@declared_attr
def __table_args__(cls):
return {"mysql_engine": "InnoDB"}
@declared_attr
def id(self):
return Column(Integer, primary_key=True)
Base = declarative_base(cls=Base)
class MyClass(Base):
pass
class MyOtherClass(Base):
pass
eq_(MyClass.__table__.kwargs["mysql_engine"], "InnoDB")
eq_(MyClass.__table__.name, "myclass")
eq_(MyOtherClass.__table__.name, "myotherclass")
assert MyClass.__table__.c.id.table is MyClass.__table__
assert MyOtherClass.__table__.c.id.table is MyOtherClass.__table__
def test_single_table_no_propagation(self):
class IdColumn:
id = Column(Integer, primary_key=True)
class Generic(Base, IdColumn):
__tablename__ = "base"
discriminator = Column("type", String(50))
__mapper_args__ = dict(polymorphic_on=discriminator)
value = Column(Integer())
class Specific(Generic):
__mapper_args__ = dict(polymorphic_identity="specific")
assert Specific.__table__ is Generic.__table__
eq_(list(Generic.__table__.c.keys()), ["type", "value", "id"])
assert (
class_mapper(Specific).polymorphic_on is Generic.__table__.c.type
)
eq_(class_mapper(Specific).polymorphic_identity, "specific")
def test_joined_table_propagation(self):
class CommonMixin:
@declared_attr
def __tablename__(cls):
return cls.__name__.lower()
__table_args__ = {"mysql_engine": "InnoDB"}
timestamp = mapped_column(Integer)
id = Column(Integer, primary_key=True)
class Generic(Base, CommonMixin):
discriminator = Column("python_type", String(50))
__mapper_args__ = dict(polymorphic_on=discriminator)
class Specific(Generic):
__mapper_args__ = dict(polymorphic_identity="specific")
id = Column(Integer, ForeignKey("generic.id"), primary_key=True)
eq_(Generic.__table__.name, "generic")
eq_(Specific.__table__.name, "specific")
eq_(
list(Generic.__table__.c.keys()),
["python_type", "timestamp", "id"],
)
eq_(list(Specific.__table__.c.keys()), ["id"])
eq_(Generic.__table__.kwargs, {"mysql_engine": "InnoDB"})
eq_(Specific.__table__.kwargs, {"mysql_engine": "InnoDB"})
def test_some_propagation(self):
class CommonMixin:
@declared_attr
def __tablename__(cls):
return cls.__name__.lower()
__table_args__ = {"mysql_engine": "InnoDB"}
timestamp = Column(Integer)
class BaseType(Base, CommonMixin):
discriminator = Column("type", String(50))
__mapper_args__ = dict(polymorphic_on=discriminator)
id = Column(Integer, primary_key=True)
value = Column(Integer())
class Single(BaseType):
__tablename__ = None
__mapper_args__ = dict(polymorphic_identity="type1")
class Joined(BaseType):
__mapper_args__ = dict(polymorphic_identity="type2")
id = Column(Integer, ForeignKey("basetype.id"), primary_key=True)
eq_(BaseType.__table__.name, "basetype")
eq_(
list(BaseType.__table__.c.keys()),
["type", "id", "value", "timestamp"],
)
eq_(BaseType.__table__.kwargs, {"mysql_engine": "InnoDB"})
assert Single.__table__ is BaseType.__table__
eq_(Joined.__table__.name, "joined")
eq_(list(Joined.__table__.c.keys()), ["id"])
eq_(Joined.__table__.kwargs, {"mysql_engine": "InnoDB"})
def test_col_copy_vs_declared_attr_joined_propagation(self):
class Mixin:
a = Column(Integer)
@declared_attr
def b(cls):
return Column(Integer)
class A(Mixin, Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
class B(A):
__tablename__ = "b"
id = Column(Integer, ForeignKey("a.id"), primary_key=True)
assert "a" in A.__table__.c
assert "b" in A.__table__.c
assert "a" not in B.__table__.c
assert "b" not in B.__table__.c
def test_col_copy_vs_declared_attr_joined_propagation_newname(self):
class Mixin:
a = Column("a1", Integer)
@declared_attr
def b(cls):
return Column("b1", Integer)
class A(Mixin, Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
class B(A):
__tablename__ = "b"
id = Column(Integer, ForeignKey("a.id"), primary_key=True)
assert "a1" in A.__table__.c
assert "b1" in A.__table__.c
assert "a1" not in B.__table__.c
assert "b1" not in B.__table__.c
def test_col_copy_vs_declared_attr_single_propagation(self):
class Mixin:
a = Column(Integer)
@declared_attr
def b(cls):
return Column(Integer)
class A(Mixin, Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
class B(A):
pass
assert "a" in A.__table__.c
assert "b" in A.__table__.c
def test_non_propagating_mixin(self):
class NoJoinedTableNameMixin:
@declared_attr
def __tablename__(cls):
if has_inherited_table(cls):
return None
return cls.__name__.lower()
class BaseType(Base, NoJoinedTableNameMixin):
discriminator = Column("type", String(50))
__mapper_args__ = dict(polymorphic_on=discriminator)
id = Column(Integer, primary_key=True)
value = Column(Integer())
class Specific(BaseType):
__mapper_args__ = dict(polymorphic_identity="specific")
eq_(BaseType.__table__.name, "basetype")
eq_(list(BaseType.__table__.c.keys()), ["type", "id", "value"])
assert Specific.__table__ is BaseType.__table__
assert (
class_mapper(Specific).polymorphic_on is BaseType.__table__.c.type
)
eq_(class_mapper(Specific).polymorphic_identity, "specific")
def test_non_propagating_mixin_used_for_joined(self):
class TableNameMixin:
@declared_attr
def __tablename__(cls):
if (
has_inherited_table(cls)
and TableNameMixin not in cls.__bases__
):
return None
return cls.__name__.lower()
class BaseType(Base, TableNameMixin):
discriminator = Column("type", String(50))
__mapper_args__ = dict(polymorphic_on=discriminator)
id = Column(Integer, primary_key=True)
value = Column(Integer())
class Specific(BaseType, TableNameMixin):
__mapper_args__ = dict(polymorphic_identity="specific")
id = Column(Integer, ForeignKey("basetype.id"), primary_key=True)
eq_(BaseType.__table__.name, "basetype")
eq_(list(BaseType.__table__.c.keys()), ["type", "id", "value"])
eq_(Specific.__table__.name, "specific")
eq_(list(Specific.__table__.c.keys()), ["id"])
def test_single_back_propagate(self):
class ColumnMixin:
timestamp = Column(Integer)
class BaseType(Base):
__tablename__ = "foo"
discriminator = Column("type", String(50))
__mapper_args__ = dict(polymorphic_on=discriminator)
id = Column(Integer, primary_key=True)
class Specific(BaseType, ColumnMixin):
__mapper_args__ = dict(polymorphic_identity="specific")
eq_(list(BaseType.__table__.c.keys()), ["type", "id", "timestamp"])
def test_table_in_model_and_same_column_in_mixin(self):
class ColumnMixin:
data = Column(Integer)
class Model(Base, ColumnMixin):
__table__ = Table(
"foo",
Base.metadata,
Column("data", Integer),
Column("id", Integer, primary_key=True),
)
model_col = Model.__table__.c.data
mixin_col = ColumnMixin.data
assert model_col is not mixin_col
eq_(model_col.name, "data")
assert model_col.type.__class__ is mixin_col.type.__class__
def test_table_in_model_and_different_named_column_in_mixin(self):
class ColumnMixin:
tada = Column(Integer)
def go():
class Model(Base, ColumnMixin):
__table__ = Table(
"foo",
Base.metadata,
Column("data", Integer),
Column("id", Integer, primary_key=True),
)
foo = relationship("Dest")
assert_raises_message(
sa.exc.ArgumentError,
"Can't add additional column 'tada' when specifying __table__",
go,
)
def test_table_in_model_and_different_named_alt_key_column_in_mixin(self):
class ColumnMixin:
tada = Column("foobar", Integer)
def go():
class Model(Base, ColumnMixin):
__table__ = Table(
"foo",
Base.metadata,
Column("data", Integer),
Column("tada", Integer),
Column("id", Integer, primary_key=True),
)
foo = relationship("Dest")
assert_raises_message(
sa.exc.ArgumentError,
"Can't add additional column 'foobar' when "
"specifying __table__",
go,
)
def test_table_in_model_overrides_different_typed_column_in_mixin(self):
class ColumnMixin:
data = Column(String)
class Model(Base, ColumnMixin):
__table__ = Table(
"foo",
Base.metadata,
Column("data", Integer),
Column("id", Integer, primary_key=True),
)
model_col = Model.__table__.c.data
mixin_col = ColumnMixin.data
assert model_col is not mixin_col
eq_(model_col.name, "data")
assert model_col.type.__class__ is Integer
def test_mixin_column_ordering(self):
class Foo:
col1 = Column(Integer)
col3 = Column(Integer)
class Bar:
col2 = Column(Integer)
col4 = Column(Integer)
class Model(Base, Foo, Bar):
id = Column(Integer, primary_key=True)
__tablename__ = "model"
eq_(
list(Model.__table__.c.keys()),
["id", "col1", "col3", "col2", "col4"],
)
def test_honor_class_mro_one(self):
class HasXMixin:
@declared_attr
def x(self):
return Column(Integer)
class Parent(HasXMixin, Base):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
class Child(Parent):
__tablename__ = "child"
id = Column(Integer, ForeignKey("parent.id"), primary_key=True)
assert "x" not in Child.__table__.c
def test_honor_class_mro_two(self):
class HasXMixin:
@declared_attr
def x(self):
return Column(Integer)
class Parent(HasXMixin, Base):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
def x(self):
return "hi"
class C(Parent):
__tablename__ = "c"
id = Column(Integer, ForeignKey("parent.id"), primary_key=True)
assert C().x() == "hi"
def test_arbitrary_attrs_one(self):
class HasMixin:
@declared_attr
def some_attr(cls):
return cls.__name__ + "SOME ATTR"
class Mapped(HasMixin, Base):
__tablename__ = "t"
id = Column(Integer, primary_key=True)
eq_(Mapped.some_attr, "MappedSOME ATTR")
eq_(Mapped.__dict__["some_attr"], "MappedSOME ATTR")
def test_arbitrary_attrs_two(self):
from sqlalchemy.ext.associationproxy import association_proxy
class FilterA(Base):
__tablename__ = "filter_a"
id = Column(Integer(), primary_key=True)
parent_id = Column(Integer(), ForeignKey("type_a.id"))
filter = Column(String())
def __init__(self, filter_, **kw):
self.filter = filter_
class FilterB(Base):
__tablename__ = "filter_b"
id = Column(Integer(), primary_key=True)
parent_id = Column(Integer(), ForeignKey("type_b.id"))
filter = Column(String())
def __init__(self, filter_, **kw):
self.filter = filter_
class FilterMixin:
@declared_attr
def _filters(cls):
return relationship(
cls.filter_class, cascade="all,delete,delete-orphan"
)
@declared_attr
def filters(cls):
return association_proxy("_filters", "filter")
class TypeA(Base, FilterMixin):
__tablename__ = "type_a"
filter_class = FilterA
id = Column(Integer(), primary_key=True)
class TypeB(Base, FilterMixin):
__tablename__ = "type_b"
filter_class = FilterB
id = Column(Integer(), primary_key=True)
TypeA(filters=["foo"])
TypeB(filters=["foo"])
def test_arbitrary_attrs_three(self):
class Mapped(Base):
__tablename__ = "t"
id = Column(Integer, primary_key=True)
@declared_attr
def some_attr(cls):
return cls.__name__ + "SOME ATTR"
eq_(Mapped.some_attr, "MappedSOME ATTR")
eq_(Mapped.__dict__["some_attr"], "MappedSOME ATTR")
def test_arbitrary_attrs_doesnt_apply_to_abstract_declared_attr(self):
names = ["name1", "name2", "name3"]
class SomeAbstract(Base):
__abstract__ = True
@declared_attr
def some_attr(cls):
return names.pop(0)
class M1(SomeAbstract):
__tablename__ = "t1"
id = Column(Integer, primary_key=True)
class M2(SomeAbstract):
__tablename__ = "t2"
id = Column(Integer, primary_key=True)
eq_(M1.__dict__["some_attr"], "name1")
eq_(M2.__dict__["some_attr"], "name2")
def test_arbitrary_attrs_doesnt_apply_to_prepare_nocascade(self):
names = ["name1", "name2", "name3"]
class SomeAbstract(Base):
__tablename__ = "t0"
__no_table__ = True
_sa_decl_prepare_nocascade = True
id = Column(Integer, primary_key=True)
@declared_attr
def some_attr(cls):
return names.pop(0)
class M1(SomeAbstract):
__tablename__ = "t1"
id = Column(Integer, primary_key=True)
class M2(SomeAbstract):
__tablename__ = "t2"
id = Column(Integer, primary_key=True)
eq_(M1.some_attr, "name2")
eq_(M2.some_attr, "name3")
eq_(M1.__dict__["some_attr"], "name2")
eq_(M2.__dict__["some_attr"], "name3")
assert isinstance(SomeAbstract.__dict__["some_attr"], declared_attr)
class DeclarativeMixinPropertyTest(
DeclarativeTestBase, testing.AssertsCompiledSQL
):
def test_column_property(self):
class MyMixin:
@declared_attr
def prop_hoho(cls):
return column_property(Column("prop", String(50)))
class MyModel(Base, MyMixin):
__tablename__ = "test"
id = Column(
Integer, primary_key=True, test_needs_autoincrement=True
)
class MyOtherModel(Base, MyMixin):
__tablename__ = "othertest"
id = Column(
Integer, primary_key=True, test_needs_autoincrement=True
)
assert MyModel.__table__.c.prop is not None
assert MyOtherModel.__table__.c.prop is not None
assert MyModel.__table__.c.prop is not MyOtherModel.__table__.c.prop
assert MyModel.prop_hoho.property.columns == [MyModel.__table__.c.prop]
assert MyOtherModel.prop_hoho.property.columns == [
MyOtherModel.__table__.c.prop
]
assert (
MyModel.prop_hoho.property is not MyOtherModel.prop_hoho.property
)
Base.metadata.create_all(testing.db)
sess = fixture_session()
m1, m2 = MyModel(prop_hoho="foo"), MyOtherModel(prop_hoho="bar")
sess.add_all([m1, m2])
sess.flush()
eq_(sess.query(MyModel).filter(MyModel.prop_hoho == "foo").one(), m1)
eq_(
sess.query(MyOtherModel)
.filter(MyOtherModel.prop_hoho == "bar")
.one(),
m2,
)
@testing.combinations(
"anno",
"anno_w_clsmeth",
"pep593",
"nonanno",
"legacy",
argnames="clstype",
)
def test_column_property_col_ref(self, decl_base, clstype):
if clstype == "anno":
class SomethingMixin:
x: Mapped[int]
y: Mapped[int] = mapped_column()
@declared_attr
def x_plus_y(cls) -> Mapped[int]:
return column_property(cls.x + cls.y)
elif clstype == "anno_w_clsmeth":
class SomethingMixin:
x: Mapped[int]
y: Mapped[int] = mapped_column()
@declared_attr
@classmethod
def x_plus_y(cls) -> Mapped[int]:
return column_property(cls.x + cls.y)
elif clstype == "nonanno":
class SomethingMixin:
x = mapped_column(Integer)
y = mapped_column(Integer)
@declared_attr
def x_plus_y(cls) -> Mapped[int]:
return column_property(cls.x + cls.y)
elif clstype == "pep593":
myint = Annotated[int, mapped_column(Integer)]
class SomethingMixin:
x: Mapped[myint]
y: Mapped[myint]
@declared_attr
def x_plus_y(cls) -> Mapped[int]:
return column_property(cls.x + cls.y)
elif clstype == "legacy":
class SomethingMixin:
x = Column(Integer)
y = Column(Integer)
@declared_attr
def x_plus_y(cls) -> Mapped[int]:
return column_property(cls.x + cls.y)
else:
assert False
class Something(SomethingMixin, Base):
__tablename__ = "something"
id: Mapped[int] = mapped_column(primary_key=True)
class SomethingElse(SomethingMixin, Base):
__tablename__ = "something_else"
id: Mapped[int] = mapped_column(primary_key=True)
self.assert_compile(
select(Something.x_plus_y),
"SELECT something.x + something.y AS anon_1 FROM something",
)
self.assert_compile(
select(SomethingElse.x_plus_y),
"SELECT something_else.x + something_else.y AS anon_1 "
"FROM something_else",
)
def test_doc(self):
"""test documentation transfer.
the documentation situation with @declared_attr is problematic.
at least see if mapped subclasses get the doc.
"""
class MyMixin:
@declared_attr
def type_(cls):
"""this is a document."""
return Column(String(50))
@declared_attr
def t2(cls):
"""this is another document."""
return column_property(Column(String(50)))
class MyModel(Base, MyMixin):
__tablename__ = "test"
id = Column(Integer, primary_key=True)
configure_mappers()
eq_(MyModel.type_.__doc__, """this is a document.""")
eq_(MyModel.t2.__doc__, """this is another document.""")
def test_correct_for_proxies(self):
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy import inspect
class Mixin:
@hybrid_property
def hp1(cls):
return 42
@declared_attr
def hp2(cls):
@hybrid_property
def hp2(self):
return 42
return hp2
class Base(declarative_base(), Mixin):
__tablename__ = "test"
id = Column(String, primary_key=True)
class Derived(Base):
pass
assert Base.hp1._is_internal_proxy
assert Base.hp2._is_internal_proxy
assert Derived.hp1._is_internal_proxy
assert Derived.hp2._is_internal_proxy
b1 = inspect(Base)
d1 = inspect(Derived)
is_(b1.all_orm_descriptors["hp1"], d1.all_orm_descriptors["hp1"])
is_(b1.all_orm_descriptors["hp2"], d1.all_orm_descriptors["hp2"])
def test_correct_for_proxies_doesnt_impact_synonyms(self):
from sqlalchemy import inspect
class Mixin:
@declared_attr
def data_syn(cls):
return synonym("data")
class Base(declarative_base(), Mixin):
__tablename__ = "test"
id = Column(String, primary_key=True)
data = Column(String)
type = Column(String)
__mapper_args__ = {
"polymorphic_on": type,
"polymorphic_identity": "base",
}
class Derived(Base):
__mapper_args__ = {"polymorphic_identity": "derived"}
assert Base.data_syn._is_internal_proxy
assert Derived.data_syn._is_internal_proxy
b1 = inspect(Base)
d1 = inspect(Derived)
is_(b1.attrs["data_syn"], d1.attrs["data_syn"])
s = fixture_session()
self.assert_compile(
s.query(Base.data_syn).filter(Base.data_syn == "foo"),
"SELECT test.data AS test_data FROM test "
"WHERE test.data = :data_1",
dialect="default",
)
self.assert_compile(
s.query(Derived.data_syn).filter(Derived.data_syn == "foo"),
"SELECT test.data AS test_data FROM test WHERE test.data = "
":data_1 AND test.type IN (__[POSTCOMPILE_type_1])",
dialect="default",
checkparams={"type_1": ["derived"], "data_1": "foo"},
)
def test_column_in_mapper_args(self):
class MyMixin:
@declared_attr
def type_(cls):
return Column(String(50))
__mapper_args__ = {"polymorphic_on": type_}
class MyModel(Base, MyMixin):
__tablename__ = "test"
id = Column(Integer, primary_key=True)
configure_mappers()
col = MyModel.__mapper__.polymorphic_on
eq_(col.name, "type_")
assert col.table is not None
def test_column_in_mapper_args_used_multiple_times(self):
class MyMixin:
version_id = Column(Integer)
__mapper_args__ = {"version_id_col": version_id}
class ModelOne(Base, MyMixin):
__tablename__ = "m1"
id = Column(Integer, primary_key=True)
class ModelTwo(Base, MyMixin):
__tablename__ = "m2"
id = Column(Integer, primary_key=True)
is_(
ModelOne.__mapper__.version_id_col, ModelOne.__table__.c.version_id
)
is_(
ModelTwo.__mapper__.version_id_col, ModelTwo.__table__.c.version_id
)
def test_deferred(self):
class MyMixin:
@declared_attr
def data(cls):
return deferred(Column("data", String(50)))
class MyModel(Base, MyMixin):
__tablename__ = "test"
id = Column(
Integer, primary_key=True, test_needs_autoincrement=True
)
Base.metadata.create_all(testing.db)
sess = fixture_session()
sess.add_all([MyModel(data="d1"), MyModel(data="d2")])
sess.flush()
sess.expunge_all()
d1, d2 = sess.query(MyModel).order_by(MyModel.data)
assert "data" not in d1.__dict__
assert d1.data == "d1"
assert "data" in d1.__dict__
def _test_relationship(self, usestring):
class RefTargetMixin:
@declared_attr
def target_id(cls):
return Column("target_id", ForeignKey("target.id"))
if usestring:
@declared_attr
def target(cls):
return relationship(
"Target",
primaryjoin="Target.id==%s.target_id" % cls.__name__,
)
else:
@declared_attr
def target(cls):
return relationship("Target")
class Foo(Base, RefTargetMixin):
__tablename__ = "foo"
id = Column(
Integer, primary_key=True, test_needs_autoincrement=True
)
class Bar(Base, RefTargetMixin):
__tablename__ = "bar"
id = Column(
Integer, primary_key=True, test_needs_autoincrement=True
)
class Target(Base):
__tablename__ = "target"
id = Column(
Integer, primary_key=True, test_needs_autoincrement=True
)
Base.metadata.create_all(testing.db)
sess = fixture_session()
t1, t2 = Target(), Target()
f1, f2, b1 = Foo(target=t1), Foo(target=t2), Bar(target=t1)
sess.add_all([f1, f2, b1])
sess.flush()
eq_(sess.query(Foo).filter(Foo.target == t2).one(), f2)
eq_(sess.query(Bar).filter(Bar.target == t2).first(), None)
sess.expire_all()
eq_(f1.target, t1)
def test_relationship(self):
self._test_relationship(False)
def test_relationship_primryjoin(self):
self._test_relationship(True)
class DeclaredAttrTest(DeclarativeTestBase, testing.AssertsCompiledSQL):
__dialect__ = "default"
def test_singleton_behavior_within_decl(self):
counter = mock.Mock()
class Mixin:
@declared_attr
def my_prop(cls):
counter(cls)
return Column("x", Integer)
class A(Base, Mixin):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
@declared_attr
def my_other_prop(cls):
return column_property(cls.my_prop + 5)
eq_(counter.mock_calls, [mock.call(A)])
class B(Base, Mixin):
__tablename__ = "b"
id = Column(Integer, primary_key=True)
@declared_attr
def my_other_prop(cls):
return column_property(cls.my_prop + 5)
eq_(counter.mock_calls, [mock.call(A), mock.call(B)])
a_col = A.my_other_prop.__clause_element__().element.left
b_col = B.my_other_prop.__clause_element__().element.left
is_(a_col.table, A.__table__)
is_(b_col.table, B.__table__)
is_(a_col, A.__table__.c.x)
is_(b_col, B.__table__.c.x)
s = fixture_session()
self.assert_compile(
s.query(A),
"SELECT a.x + :x_1 AS anon_1, a.id AS a_id, a.x AS a_x FROM a",
)
self.assert_compile(
s.query(B),
"SELECT b.x + :x_1 AS anon_1, b.id AS b_id, b.x AS b_x FROM b",
)
@testing.requires.predictable_gc
def test_singleton_gc(self):
counter = mock.Mock()
class Mixin:
@declared_attr
def my_prop(cls):
counter(cls.__name__)
return Column("x", Integer)
class A(Base, Mixin):
__tablename__ = "b"
id = Column(Integer, primary_key=True)
@declared_attr
def my_other_prop(cls):
return column_property(cls.my_prop + 5)
eq_(counter.mock_calls, [mock.call("A")])
del A
gc_collect()
from sqlalchemy.orm.clsregistry import _key_is_empty
assert _key_is_empty(
"A",
Base.registry._class_registry,
lambda cls: hasattr(cls, "my_other_prop"),
)
def test_can_we_access_the_mixin_straight(self):
class Mixin:
@declared_attr
def my_prop(cls):
return Column("x", Integer)
with expect_warnings(
"Unmanaged access of declarative attribute my_prop "
"from non-mapped class Mixin"
):
Mixin.my_prop
def test_can_we_access_the_mixin_straight_special_names(self):
class Mixin:
@declared_attr.directive
def __table_args__(cls):
return (1, 2, 3)
@declared_attr.directive
def __arbitrary__(cls):
return (4, 5, 6)
eq_(Mixin.__table_args__, (1, 2, 3))
eq_(Mixin.__arbitrary__, (4, 5, 6))
def test_non_decl_access(self):
counter = mock.Mock()
class Mixin:
@declared_attr.directive
def __tablename__(cls):
counter(cls)
return "foo"
class Foo(Mixin, Base):
id = Column(Integer, primary_key=True)
@declared_attr.directive
def x(cls):
cls.__tablename__
@declared_attr.directive
def y(cls):
cls.__tablename__
eq_(counter.mock_calls, [mock.call(Foo)])
eq_(Foo.__tablename__, "foo")
eq_(Foo.__tablename__, "foo")
eq_(
counter.mock_calls,
[mock.call(Foo), mock.call(Foo), mock.call(Foo)],
)
def test_property_noncascade(self):
counter = mock.Mock()
class Mixin:
@declared_attr
def my_prop(cls):
counter(cls)
return column_property(cls.x + 2)
class A(Base, Mixin):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
x = Column(Integer)
class B(A):
pass
eq_(counter.mock_calls, [mock.call(A)])
def test_property_cascade_mixin(self):
counter = mock.Mock()
class Mixin:
@declared_attr.cascading
def my_prop(cls):
counter(cls)
return column_property(cls.x + 2)
class A(Base, Mixin):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
x = Column(Integer)
class B(A):
pass
eq_(counter.mock_calls, [mock.call(A), mock.call(B)])
def test_property_cascade_mixin_override(self):
counter = mock.Mock()
class Mixin:
@declared_attr.cascading
def my_prop(cls):
counter(cls)
return column_property(cls.x + 2)
class A(Base, Mixin):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
x = Column(Integer)
with expect_warnings(
"Attribute 'my_prop' on class .*B.* "
"cannot be processed due to @declared_attr.cascading; "
"skipping"
):
class B(A):
my_prop = Column("foobar", Integer)
eq_(counter.mock_calls, [mock.call(A), mock.call(B)])
def test_property_cascade_abstract(self):
counter = mock.Mock()
class Abs(Base):
__abstract__ = True
@declared_attr.cascading
def my_prop(cls):
counter(cls)
return column_property(cls.x + 2)
class A(Abs):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
x = Column(Integer)
class B(A):
pass
eq_(counter.mock_calls, [mock.call(A), mock.call(B)])
def test_warn_cascading_used_w_tablename(self):
class Mixin:
@declared_attr.cascading
def __tablename__(cls):
return "foo"
with expect_warnings(
"@declared_attr.cascading is not supported on the "
"__tablename__ attribute on class .*A."
):
class A(Mixin, Base):
id = Column(Integer, primary_key=True)
eq_(A.__table__.name, "foo")
def test_col_prop_attrs_associated_w_class_for_mapper_args(self):
from sqlalchemy import Column
import collections
asserted = collections.defaultdict(set)
class Mixin:
@declared_attr.cascading
def my_attr(cls):
if has_inherited_table(cls):
id_ = Column(ForeignKey("a.my_attr"), primary_key=True)
asserted["b"].add(id_)
else:
id_ = Column(Integer, primary_key=True)
asserted["a"].add(id_)
return id_
class A(Base, Mixin):
__tablename__ = "a"
@declared_attr
def __mapper_args__(cls):
asserted["a"].add(cls.my_attr)
return {}
class B(A):
__tablename__ = "b"
@declared_attr
def __mapper_args__(cls):
asserted["b"].add(cls.my_attr)
return {}
eq_(
asserted,
{
"a": {A.my_attr.property.columns[0]},
"b": {B.my_attr.property.columns[0]},
},
)
def test_column_pre_map(self):
counter = mock.Mock()
class Mixin:
@declared_attr
def my_col(cls):
counter(cls)
assert not orm_base._mapper_or_none(cls)
return Column("x", Integer)
class A(Base, Mixin):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
eq_(counter.mock_calls, [mock.call(A)])
def test_mixin_attr_refers_to_column_copies(self):
counter = mock.Mock()
class HasAddressCount:
id = Column(Integer, primary_key=True)
@declared_attr
def address_count(cls):
counter(cls.id)
return column_property(
select(func.count(Address.id))
.where(Address.user_id == cls.id)
.scalar_subquery()
)
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
user_id = Column(ForeignKey("user.id"))
class User(Base, HasAddressCount):
__tablename__ = "user"
eq_(counter.mock_calls, [mock.call(User.id)])
sess = fixture_session()
self.assert_compile(
sess.query(User).having(User.address_count > 5),
"SELECT (SELECT count(address.id) AS "
'count_1 FROM address WHERE address.user_id = "user".id) '
'AS anon_1, "user".id AS user_id FROM "user" '
"HAVING (SELECT count(address.id) AS "
'count_1 FROM address WHERE address.user_id = "user".id) '
"> :param_1",
)
def test_multilevel_mixin_attr_refers_to_column_copies(self):
"""test #8190.
This test is the same idea as test_mixin_attr_refers_to_column_copies
but tests the column copies from superclasses.
"""
counter = mock.Mock()
class SomeOtherMixin:
status = Column(String)
class HasAddressCount(SomeOtherMixin):
id = Column(Integer, primary_key=True)
@declared_attr
def address_count(cls):
counter(cls.id)
counter(cls.status)
return column_property(
select(func.count(Address.id))
.where(Address.user_id == cls.id)
.where(cls.status == "some status")
.scalar_subquery()
)
class Address(Base):
__tablename__ = "address"
id = Column(Integer, primary_key=True)
user_id = Column(ForeignKey("user.id"))
class User(Base, HasAddressCount):
__tablename__ = "user"
eq_(counter.mock_calls, [mock.call(User.id), mock.call(User.status)])
sess = fixture_session()
self.assert_compile(
sess.query(User).having(User.address_count > 5),
"SELECT (SELECT count(address.id) AS count_1 FROM address "
'WHERE address.user_id = "user".id AND "user".status = :param_1) '
'AS anon_1, "user".id AS user_id, "user".status AS user_status '
'FROM "user" HAVING (SELECT count(address.id) AS count_1 '
'FROM address WHERE address.user_id = "user".id '
'AND "user".status = :param_1) > :param_2',
)
class AbstractTest(DeclarativeTestBase):
def test_abstract_boolean(self):
class A(Base):
__abstract__ = True
__tablename__ = "x"
id = Column(Integer, primary_key=True)
class B(Base):
__abstract__ = False
__tablename__ = "y"
id = Column(Integer, primary_key=True)
class C(Base):
__abstract__ = False
__tablename__ = "z"
id = Column(Integer, primary_key=True)
class D(Base):
__tablename__ = "q"
id = Column(Integer, primary_key=True)
eq_(set(Base.metadata.tables), {"y", "z", "q"})
def test_middle_abstract_attributes(self):
class A(Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
name = Column(String)
class B(A):
__abstract__ = True
data = Column(String)
class C(B):
c_value = Column(String)
eq_(sa.inspect(C).attrs.keys(), ["id", "name", "c_value", "data"])
def test_implicit_abstract_viadecorator(self):
@mapper_registry.mapped
class A:
__tablename__ = "a"
id = Column(Integer, primary_key=True)
name = Column(String)
class B(A):
data = Column(String)
@mapper_registry.mapped
class C(B):
c_value = Column(String)
eq_(sa.inspect(C).attrs.keys(), ["id", "name", "c_value", "data"])
def test_middle_abstract_inherits(self):
class A(Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
class AAbs(A):
__abstract__ = True
class B1(A):
__tablename__ = "b1"
id = Column(ForeignKey("a.id"), primary_key=True)
class B2(AAbs):
__tablename__ = "b2"
id = Column(ForeignKey("a.id"), primary_key=True)
assert B1.__mapper__.inherits is A.__mapper__
assert B2.__mapper__.inherits is A.__mapper__