summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/util
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2022-10-19 09:41:44 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2022-10-19 15:00:39 -0400
commit05d5f90d184494782d3ed6a24f6dd6b48bb31946 (patch)
tree2f251efe24180b3400fcd84e080f9d6cc36bb436 /lib/sqlalchemy/util
parent3c5ac045fcc551674c38a813478977e736b2e8a9 (diff)
downloadsqlalchemy-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.py49
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.