diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-02-18 10:05:12 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-05-20 14:19:02 -0400 |
| commit | a463b1109abb60fc85f8356f30c0351a4e2ed71e (patch) | |
| tree | de8f96b7bce319fc0f19f56b302202ea3e4e91db /lib/sqlalchemy/orm/util.py | |
| parent | 9e7bed9df601ead02fd96bf2fc787b23b536d2d6 (diff) | |
| download | sqlalchemy-a463b1109abb60fc85f8356f30c0351a4e2ed71e.tar.gz | |
implement dataclass_transforms
Implement a new means of creating a mapped dataclass where
instead of applying the `@dataclass` decorator distinctly,
the declarative process itself can create the dataclass.
MapperProperty and MappedColumn objects themselves take
the place of the dataclasses.Field object when constructing
the class.
The overall approach is made possible at the typing level
using pep-681 dataclass transforms [1].
This new approach should be able to completely supersede the
previous "dataclasses" approach of embedding metadata into
Field() objects, which remains a mutually exclusive declarative
setup style (mixing them introduces new issues that are not worth
solving).
[1] https://peps.python.org/pep-0681/#transform-descriptor-types-example
Fixes: #7642
Change-Id: I6ba88a87c5df38270317b4faf085904d91c8a63c
Diffstat (limited to 'lib/sqlalchemy/orm/util.py')
| -rw-r--r-- | lib/sqlalchemy/orm/util.py | 45 |
1 files changed, 27 insertions, 18 deletions
diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index c50cc5bac..520c95672 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -1927,7 +1927,7 @@ def _getitem(iterable_query: Query[Any], item: Any) -> Any: def _is_mapped_annotation( - raw_annotation: Union[type, str], cls: Type[Any] + raw_annotation: _AnnotationScanType, cls: Type[Any] ) -> bool: annotated = de_stringify_annotation(cls, raw_annotation) return is_origin_of(annotated, "Mapped", module="sqlalchemy.orm") @@ -1969,9 +1969,14 @@ def _extract_mapped_subtype( attr_cls: Type[Any], required: bool, is_dataclass_field: bool, - superclasses: Optional[Tuple[Type[Any], ...]] = None, + expect_mapped: bool = True, ) -> Optional[Union[type, str]]: + """given an annotation, figure out if it's ``Mapped[something]`` and if + so, return the ``something`` part. + Includes error raise scenarios and other options. + + """ if raw_annotation is None: if required: @@ -1989,25 +1994,29 @@ def _extract_mapped_subtype( if is_dataclass_field: return annotated else: - # TODO: there don't seem to be tests for the failure - # conditions here - if not hasattr(annotated, "__origin__") or ( - not issubclass( - annotated.__origin__, # type: ignore - superclasses if superclasses else attr_cls, - ) - and not issubclass(attr_cls, annotated.__origin__) # type: ignore + if not hasattr(annotated, "__origin__") or not is_origin_of( + annotated, "Mapped", module="sqlalchemy.orm" ): - our_annotated_str = ( - annotated.__name__ + anno_name = ( + getattr(annotated, "__name__", None) if not isinstance(annotated, str) - else repr(annotated) - ) - 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}]".' + else None ) + if anno_name is None: + our_annotated_str = repr(annotated) + else: + our_annotated_str = anno_name + + if expect_mapped: + 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}]".' + ) + + else: + return annotated if len(annotated.__args__) != 1: # type: ignore raise sa_exc.ArgumentError( |
