summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2022-10-21 00:11:10 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2022-10-21 11:48:35 -0400
commit8370edb4ccca2055635107855b58c795fccbeaf1 (patch)
tree5f63704726762dbf9b3a1496ce18240a91afd905 /lib/sqlalchemy
parentbfe36f11fb423f2318166162d95941beef3cbaaa (diff)
downloadsqlalchemy-8370edb4ccca2055635107855b58c795fccbeaf1.tar.gz
allow legacy forms with __allow_unmapped__
Improved support for legacy 1.4 mappings that use annotations which don't include ``Mapped[]``, by ensuring the ``__allow_unmapped__`` attribute can be used to allow such legacy annotations to pass through Annotated Declarative without raising an error and without being interpreted in an ORM runtime context. Additionally improved the error message generated when this condition is detected, and added more documentation for how this situation should be handled. Unfortunately the 1.4 WARN_SQLALCHEMY_20 migration warning cannot detect this particular configurational issue at runtime with its current architecture. Fixes: #8692 Change-Id: I5c642bcc1ebb7816f9470ec9bb0951550b6d55f1
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/orm/decl_base.py36
-rw-r--r--lib/sqlalchemy/orm/util.py48
2 files changed, 43 insertions, 41 deletions
diff --git a/lib/sqlalchemy/orm/decl_base.py b/lib/sqlalchemy/orm/decl_base.py
index ef2c2f3c9..4e79ecc6f 100644
--- a/lib/sqlalchemy/orm/decl_base.py
+++ b/lib/sqlalchemy/orm/decl_base.py
@@ -1199,6 +1199,11 @@ class _ClassScanMapperConfig(_MapperConfig):
cls, "_sa_decl_prepare_nocascade", strict=True
)
+ expect_annotations_wo_mapped = (
+ self.allow_unmapped_annotations
+ or self.is_dataclass_prior_to_mapping
+ )
+
for k in list(collected_attributes):
if k in ("__table__", "__tablename__", "__mapper_args__"):
@@ -1275,15 +1280,28 @@ class _ClassScanMapperConfig(_MapperConfig):
) = self.collected_annotations.get(
k, (None, None, None, False, None)
)
- value.declarative_scan(
- self.registry,
- cls,
- k,
- mapped_container,
- annotation,
- extracted_mapped_annotation,
- is_dataclass,
- )
+
+ # issue #8692 - don't do any annotation interpretation if
+ # an annotation were present and a container such as
+ # Mapped[] etc. were not used. If annotation is None,
+ # do declarative_scan so that the property can raise
+ # for required
+ if mapped_container is not None or annotation is None:
+ value.declarative_scan(
+ self.registry,
+ cls,
+ k,
+ mapped_container,
+ annotation,
+ extracted_mapped_annotation,
+ is_dataclass,
+ )
+ else:
+ # assert that we were expecting annotations
+ # without Mapped[] were going to be passed.
+ # otherwise an error should have been raised
+ # by util._extract_mapped_subtype before we got here.
+ assert expect_annotations_wo_mapped
if (
isinstance(value, (MapperProperty, _MapsColumns))
diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py
index b9d1b50e7..481a71f8e 100644
--- a/lib/sqlalchemy/orm/util.py
+++ b/lib/sqlalchemy/orm/util.py
@@ -2090,15 +2090,6 @@ def _extract_mapped_subtype(
if not hasattr(annotated, "__origin__") or not is_origin_of_cls(
annotated, _MappedAnnotationBase
):
- anno_name = (
- getattr(annotated, "__name__", None)
- if not isinstance(annotated, str)
- else None
- )
- if anno_name is None:
- our_annotated_str = repr(annotated)
- else:
- our_annotated_str = anno_name
if expect_mapped:
if getattr(annotated, "__origin__", None) is typing.ClassVar:
@@ -2107,29 +2098,22 @@ def _extract_mapped_subtype(
if not raiseerr:
return None
- if attr_cls.__name__ == our_annotated_str or attr_cls is type(
- None
- ):
- raise sa_exc.ArgumentError(
- f'Type annotation for "{cls.__name__}.{key}" '
- "should use the "
- f'syntax "Mapped[{our_annotated_str}]". To leave '
- f"the attribute unmapped, use "
- f"ClassVar[{our_annotated_str}], assign a value to "
- f"the attribute, or "
- f"set __allow_unmapped__ = True on the class."
- )
- else:
- raise sa_exc.ArgumentError(
- f'Type annotation for "{cls.__name__}.{key}" '
- "should use the "
- f'syntax "Mapped[{our_annotated_str}]" or '
- f'"{attr_cls.__name__}[{our_annotated_str}]". To '
- f"leave the attribute unmapped, use "
- f"ClassVar[{our_annotated_str}], assign a value to "
- f"the attribute, or "
- f"set __allow_unmapped__ = True on the class."
- )
+ raise sa_exc.ArgumentError(
+ f'Type annotation for "{cls.__name__}.{key}" '
+ "can't be correctly interpreted for "
+ "Annotated Declarative Table form. ORM annotations "
+ "should normally make use of the ``Mapped[]`` generic "
+ "type, or other ORM-compatible generic type, as a "
+ "container for the actual type, which indicates the "
+ "intent that the attribute is mapped. "
+ "Class variables that are not intended to be mapped "
+ "by the ORM should use ClassVar[]. "
+ "To allow Annotated Declarative to disregard legacy "
+ "annotations which don't use Mapped[] to pass, set "
+ '"__allow_unmapped__ = True" on the class or a '
+ "superclass this class.",
+ code="zlpr",
+ )
else:
return annotated, None