) -> None:
if mapper.non_primary:
return
+ _APPLIED_KEY = "_ext_mutable_listener_applied"
+
for prop in mapper.column_attrs:
if (
- schema_event_check
- and hasattr(prop.expression, "info")
- and prop.expression.info.get("_ext_mutable_orig_type") # type: ignore # noqa: E501 # TODO: https://github.com/python/mypy/issues/1424#issuecomment-1272354487
- is sqltype
- ) or (prop.columns[0].type is sqltype):
- cls.associate_with_attribute(getattr(class_, prop.key))
+ # all Mutable types refer to a Column that's mapped,
+ # since this is the only kind of Core target the ORM can
+ # "mutate"
+ isinstance(prop.expression, Column)
+ and (
+ (
+ schema_event_check
+ and prop.expression.info.get(
+ "_ext_mutable_orig_type"
+ )
+ is sqltype
+ )
+ or prop.expression.type is sqltype
+ )
+ ):
+ if not prop.expression.info.get(_APPLIED_KEY, False):
+ prop.expression.info[_APPLIED_KEY] = True
+ cls.associate_with_attribute(getattr(class_, prop.key))
event.listen(Mapper, "mapper_configured", listen_for_type)
"""Subclasses should call this method whenever change events occur."""
for parent, key in self._parents.items():
-
prop = parent.mapper.get_property(key)
for value, attr_name in zip(
prop._composite_values_from_instance(self),
self.changed()
if TYPE_CHECKING:
-
# from https://github.com/python/mypy/issues/14858
@overload
+from __future__ import annotations
+
import copy
import dataclasses
import pickle
+from typing import Any
+from typing import Dict
from sqlalchemy import event
from sqlalchemy import ForeignKey
from sqlalchemy.orm import column_property
from sqlalchemy.orm import composite
from sqlalchemy.orm import declarative_base
+from sqlalchemy.orm import Mapped
+from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import Session
from sqlalchemy.orm.instrumentation import ClassManager
from sqlalchemy.orm.mapper import Mapper
registry.metadata.create_all(connection)
with Session(connection) as sess:
-
data = dict(
j1={"a": 1},
j2={"b": 2},
is_true(inspect(t1_merged).attrs.data.history.added)
+ def test_no_duplicate_reg_w_inheritance(self, decl_base):
+ """test #9676"""
+
+ class A(decl_base):
+ __tablename__ = "a"
+
+ id: Mapped[int] = mapped_column(primary_key=True)
+
+ json: Mapped[Dict[str, Any]] = mapped_column(
+ MutableDict.as_mutable(JSON())
+ )
+
+ class B(A):
+ pass
+
+ class C(B):
+ pass
+
+ decl_base.registry.configure()
+
+ # the event hook itself doesnt do anything for repeated calls
+ # already, so there's really nothing else to assert other than there's
+ # only one "set" event listener
+
+ eq_(len(A.json.dispatch.set), 1)
+ eq_(len(B.json.dispatch.set), 1)
+ eq_(len(C.json.dispatch.set), 1)
+
class _MutableDictTestBase(_MutableDictTestFixture):
run_define_tables = "each"
):
@classmethod
def define_tables(cls, metadata):
-
Table(
"foo",
metadata,
class CustomMutableAssociationScalarJSONTest(
_MutableDictTestBase, fixtures.MappedTest
):
-
CustomMutableDict = None
@classmethod
@classmethod
def _type_fixture(cls):
-
return Point
class MutableDCCompositeColumnDefaultTest(MutableCompositeColumnDefaultTest):
@classmethod
def _type_fixture(cls):
-
return DCPoint
class MutableDCCompositesUnpickleTest(MutableCompositesUnpickleTest):
@classmethod
def _type_fixture(cls):
-
return DCPoint
class MutableDCCompositesTest(MutableCompositesTest):
@classmethod
def _type_fixture(cls):
-
return DCPoint
):
@classmethod
def _type_fixture(cls):
-
return MyPoint
@classmethod
class MutableDCCompositeCustomCoerceTest(MutableCompositeCustomCoerceTest):
@classmethod
def _type_fixture(cls):
-
return MyDCPoint
class MutableInheritedDCCompositesTest(MutableInheritedCompositesTest):
@classmethod
def _type_fixture(cls):
-
return DCPoint