diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-10-19 09:41:44 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-10-19 15:00:39 -0400 |
| commit | 05d5f90d184494782d3ed6a24f6dd6b48bb31946 (patch) | |
| tree | 2f251efe24180b3400fcd84e080f9d6cc36bb436 /lib/sqlalchemy/util | |
| parent | 3c5ac045fcc551674c38a813478977e736b2e8a9 (diff) | |
| download | sqlalchemy-05d5f90d184494782d3ed6a24f6dd6b48bb31946.tar.gz | |
more many-to-one typing
since we are typing centric, note this configuration
as we have just supported in #8668.
Note also I am just taking "backref" out of the basic
version of the docs here totally, this doc is already
a lot to read / take in without making it even more
confusing; backref still has an entirely dedicated
docs page which can have all the additional behaviors
of backref() described.
Additionally, get other "optional" forms to work including
``cls | None`` and ``Union[cls, None]``.
Fixes: #8668
Change-Id: I2b026f496a1710ddebfb4aa6cf8459b4892cbc54
Diffstat (limited to 'lib/sqlalchemy/util')
| -rw-r--r-- | lib/sqlalchemy/util/typing.py | 49 |
1 files changed, 47 insertions, 2 deletions
diff --git a/lib/sqlalchemy/util/typing.py b/lib/sqlalchemy/util/typing.py index 85ef4bb45..1d9344447 100644 --- a/lib/sqlalchemy/util/typing.py +++ b/lib/sqlalchemy/util/typing.py @@ -2,6 +2,7 @@ from __future__ import annotations +import re import sys import typing from typing import Any @@ -57,13 +58,15 @@ if compat.py310: else: NoneType = type(None) # type: ignore +NoneFwd = ForwardRef("None") + typing_get_args = get_args typing_get_origin = get_origin # copied from TypeShed, required in order to implement # MutableMapping.update() -_AnnotationScanType = Union[Type[Any], str] +_AnnotationScanType = Union[Type[Any], str, ForwardRef] class SupportsKeysAndGetItem(Protocol[_KT, _VT_co]): @@ -151,6 +154,13 @@ def de_optionalize_union_types(type_: Type[Any]) -> Type[Any]: ... +@overload +def de_optionalize_union_types( + type_: _AnnotationScanType, +) -> _AnnotationScanType: + ... + + def de_optionalize_union_types( type_: _AnnotationScanType, ) -> _AnnotationScanType: @@ -158,10 +168,14 @@ def de_optionalize_union_types( to not include the ``NoneType``. """ - if is_optional(type_): + if is_fwd_ref(type_): + return de_optionalize_fwd_ref_union_types(cast(ForwardRef, type_)) + + elif is_optional(type_): typ = set(type_.__args__) # type: ignore typ.discard(NoneType) + typ.discard(NoneFwd) return make_union_type(*typ) @@ -169,6 +183,37 @@ def de_optionalize_union_types( return type_ +def de_optionalize_fwd_ref_union_types( + type_: ForwardRef, +) -> _AnnotationScanType: + """return the non-optional type for Optional[], Union[None, ...], x|None, + etc. without de-stringifying forward refs. + + unfortunately this seems to require lots of hardcoded heuristics + + """ + + annotation = type_.__forward_arg__ + + mm = re.match(r"^(.+?)\[(.+)\]$", annotation) + if mm: + if mm.group(1) == "Optional": + return ForwardRef(mm.group(2)) + elif mm.group(1) == "Union": + elements = re.split(r",\s*", mm.group(2)) + return make_union_type( + *[ForwardRef(elem) for elem in elements if elem != "None"] + ) + else: + return type_ + + pipe_tokens = re.split(r"\s*\|\s*", annotation) + if "None" in pipe_tokens: + return ForwardRef("|".join(p for p in pipe_tokens if p != "None")) + + return type_ + + def make_union_type(*types: _AnnotationScanType) -> Type[Any]: """Make a Union type. |
