annotation,
is_dataclass_field,
) in local_attributes_for_class():
-
if re.match(r"^__.+__$", name):
if name == "__mapper_args__":
check_decl = _check_declared_props_nocascade(
"not applying to subclass %s."
% (base.__name__, name, base, cls)
)
+
continue
elif base is not cls:
# we're a mixin, abstract base, or something that is
_AttributeOptions._get_arguments_for_make_dataclass(
key,
anno,
+ mapped_container,
self.collected_attributes.get(key, _NoArg.NO_ARG),
)
- for key, anno in (
- (key, mapped_anno if mapped_anno else raw_anno)
+ for key, anno, mapped_container in (
+ (
+ key,
+ mapped_anno if mapped_anno else raw_anno,
+ mapped_container,
+ )
for key, (
raw_anno,
mapped_container,
) in self.collected_annotations.items()
)
]
-
annotations = {}
defaults = {}
for item in field_list:
# copy mixin columns to the mapped class
for name, obj, annotation, is_dataclass in attributes_for_class():
-
if (
not fixed_table
and obj is None
elif isinstance(obj, (Column, MappedColumn)):
- obj = self._collect_annotation(name, annotation, True, obj)
-
if attribute_is_overridden(name, obj):
# if column has been overridden
# (like by the InstrumentedAttribute of the
- # superclass), skip
+ # superclass), skip. don't collect the annotation
+ # either (issue #8718)
continue
- elif name not in dict_ and not (
+
+ obj = self._collect_annotation(name, annotation, True, obj)
+
+ if name not in dict_ and not (
"__table__" in dict_
and (getattr(obj, "name", None) or name)
in dict_["__table__"].c
@classmethod
def _get_arguments_for_make_dataclass(
- cls, key: str, annotation: Type[Any], elem: _T
+ cls,
+ key: str,
+ annotation: Type[Any],
+ mapped_container: Optional[Any],
+ elem: _T,
) -> Union[
Tuple[str, Type[Any]], Tuple[str, Type[Any], dataclasses.Field[Any]]
]:
elif elem is not _NoArg.NO_ARG:
# why is typing not erroring on this?
return (key, annotation, elem)
+ elif mapped_container is not None:
+ # it's Mapped[], but there's no "element", which means declarative
+ # did not actually do anything for this field. this shouldn't
+ # happen.
+ # previously, this would occur because _scan_attributes would
+ # skip a field that's on an already mapped superclass, but it
+ # would still include it in the annotations, leading
+ # to issue #8718
+
+ assert False, "Mapped[] received without a mapping declaration"
+
else:
+ # plain dataclass field, not mapped. Is only possible
+ # if __allow_unmapped__ is set up. I can see this mode causing
+ # problems...
return (key, annotation)
},
)
+ def test_allow_unmapped_fields_wo_mapped_or_dc_w_inherits(
+ self, dc_decl_base: Type[MappedAsDataclass]
+ ):
+ class A(dc_decl_base):
+ __tablename__ = "a"
+ __allow_unmapped__ = True
+
+ id: Mapped[int] = mapped_column(primary_key=True, init=False)
+ data: str
+ ctrl_one: str = dataclasses.field()
+ some_field: int = dataclasses.field(default=5)
+
+ class B(A):
+ b_data: Mapped[str] = mapped_column(default="bd")
+
+ b1 = B(data="data", ctrl_one="ctrl_one", some_field=5, b_data="x")
+ eq_(
+ dataclasses.asdict(b1),
+ {
+ "ctrl_one": "ctrl_one",
+ "data": "data",
+ "id": None,
+ "some_field": 5,
+ "b_data": "x",
+ },
+ )
+
def test_integrated_dc(self, dc_decl_base: Type[MappedAsDataclass]):
"""We will be telling users "this is a dataclass that is also
mapped". Therefore, they will want *any* kind of attribute to do what
eq_(prop._attribute_options, exp)
+class MixinColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL):
+ """tests for #8718"""
+
+ __dialect__ = "default"
+
+ @testing.fixture
+ def model(self):
+ def go(use_mixin, use_inherits, mad_setup):
+
+ if use_mixin:
+
+ if mad_setup == "dc, mad":
+
+ class BaseEntity(DeclarativeBase, MappedAsDataclass):
+ pass
+
+ elif mad_setup == "mad, dc":
+
+ class BaseEntity(MappedAsDataclass, DeclarativeBase):
+ pass
+
+ elif mad_setup == "subclass":
+
+ class BaseEntity(DeclarativeBase):
+ pass
+
+ class IdMixin:
+ id: Mapped[int] = mapped_column(
+ primary_key=True, init=False
+ )
+
+ if mad_setup == "subclass":
+
+ class A(IdMixin, MappedAsDataclass, BaseEntity):
+ __mapper_args__ = {
+ "polymorphic_on": "type",
+ "polymorphic_identity": "a",
+ }
+
+ __tablename__ = "a"
+ type: Mapped[str] = mapped_column(String, init=False)
+ data: Mapped[str] = mapped_column(String, init=False)
+
+ else:
+
+ class A(IdMixin, BaseEntity):
+ __mapper_args__ = {
+ "polymorphic_on": "type",
+ "polymorphic_identity": "a",
+ }
+
+ __tablename__ = "a"
+ type: Mapped[str] = mapped_column(String, init=False)
+ data: Mapped[str] = mapped_column(String, init=False)
+
+ else:
+
+ if mad_setup == "dc, mad":
+
+ class BaseEntity(DeclarativeBase, MappedAsDataclass):
+ id: Mapped[int] = mapped_column(
+ primary_key=True, init=False
+ )
+
+ elif mad_setup == "mad, dc":
+
+ class BaseEntity(MappedAsDataclass, DeclarativeBase):
+ id: Mapped[int] = mapped_column(
+ primary_key=True, init=False
+ )
+
+ elif mad_setup == "subclass":
+
+ class BaseEntity(DeclarativeBase):
+ id: Mapped[int] = mapped_column(
+ primary_key=True, init=False
+ )
+
+ if mad_setup == "subclass":
+
+ class A(MappedAsDataclass, BaseEntity):
+ __mapper_args__ = {
+ "polymorphic_on": "type",
+ "polymorphic_identity": "a",
+ }
+
+ __tablename__ = "a"
+ type: Mapped[str] = mapped_column(String, init=False)
+ data: Mapped[str] = mapped_column(String, init=False)
+
+ else:
+
+ class A(BaseEntity):
+ __mapper_args__ = {
+ "polymorphic_on": "type",
+ "polymorphic_identity": "a",
+ }
+
+ __tablename__ = "a"
+ type: Mapped[str] = mapped_column(String, init=False)
+ data: Mapped[str] = mapped_column(String, init=False)
+
+ if use_inherits:
+
+ class B(A):
+ __mapper_args__ = {
+ "polymorphic_identity": "b",
+ }
+ b_data: Mapped[str] = mapped_column(String, init=False)
+
+ return B
+ else:
+ return A
+
+ yield go
+
+ @testing.combinations("inherits", "plain", argnames="use_inherits")
+ @testing.combinations("mixin", "base", argnames="use_mixin")
+ @testing.combinations(
+ "mad, dc", "dc, mad", "subclass", argnames="mad_setup"
+ )
+ def test_mapping(self, model, use_inherits, use_mixin, mad_setup):
+ target_cls = model(
+ use_inherits=use_inherits == "inherits",
+ use_mixin=use_mixin == "mixin",
+ mad_setup=mad_setup,
+ )
+
+ obj = target_cls()
+ assert "id" not in obj.__dict__
+
+
class CompositeTest(fixtures.TestBase, testing.AssertsCompiledSQL):
__dialect__ = "default"