summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/util/typing.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2022-11-04 11:04:13 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2022-11-04 13:38:06 -0400
commitc0b8bdcaf020e8d043b9f9bce3e53d19e4fb79a0 (patch)
treeceeb184a053a61c93428de120052f3a0d27a879d /lib/sqlalchemy/util/typing.py
parentb96321ae79a0366c33ca739e6e67aaf5f4420db4 (diff)
downloadsqlalchemy-c0b8bdcaf020e8d043b9f9bce3e53d19e4fb79a0.tar.gz
support renamed symbols in annotation scans
Added support in ORM declarative annotations for class names specified for :func:`_orm.relationship`, as well as the name of the :class:`_orm.Mapped` symbol itself, to be different names than their direct class name, to support scenarios such as where :class:`_orm.Mapped` is imported as ``from sqlalchemy.orm import Mapped as M``, or where related class names are imported with an alternate name in a similar fashion. Additionally, a target class name given as the lead argument for :func:`_orm.relationship` will always supersede the name given in the left hand annotation, so that otherwise un-importable names that also don't match the class name can still be used in annotations. Fixes: #8759 Change-Id: I74a00de7e1a45bf62dad50fd385bb75cf343f9f3
Diffstat (limited to 'lib/sqlalchemy/util/typing.py')
-rw-r--r--lib/sqlalchemy/util/typing.py67
1 files changed, 53 insertions, 14 deletions
diff --git a/lib/sqlalchemy/util/typing.py b/lib/sqlalchemy/util/typing.py
index b5d918a74..e4674a44c 100644
--- a/lib/sqlalchemy/util/typing.py
+++ b/lib/sqlalchemy/util/typing.py
@@ -85,7 +85,7 @@ def de_stringify_annotation(
cls: Type[Any],
annotation: _AnnotationScanType,
originating_module: str,
- str_cleanup_fn: Optional[Callable[[str], str]] = None,
+ str_cleanup_fn: Optional[Callable[[str, str], str]] = None,
) -> Type[Any]:
"""Resolve annotations that may be string based into real objects.
@@ -109,26 +109,65 @@ def de_stringify_annotation(
if isinstance(annotation, str):
if str_cleanup_fn:
- annotation = str_cleanup_fn(annotation)
- base_globals: "Dict[str, Any]" = getattr(
- sys.modules.get(originating_module, None), "__dict__", {}
- )
-
- try:
- annotation = eval(annotation, base_globals, None)
- except NameError as err:
- # breakpoint()
- raise NameError(
- f"Could not de-stringify annotation {annotation}"
- ) from err
+ annotation = str_cleanup_fn(annotation, originating_module)
+
+ annotation = eval_expression(annotation, originating_module)
return annotation # type: ignore
+def eval_expression(expression: str, module_name: str) -> Any:
+ try:
+ base_globals: Dict[str, Any] = sys.modules[module_name].__dict__
+ except KeyError as ke:
+ raise NameError(
+ f"Module {module_name} isn't present in sys.modules; can't "
+ f"evaluate expression {expression}"
+ ) from ke
+ try:
+ annotation = eval(expression, base_globals, None)
+ except Exception as err:
+ raise NameError(
+ f"Could not de-stringify annotation {expression}"
+ ) from err
+ else:
+ return annotation
+
+
+def eval_name_only(name: str, module_name: str) -> Any:
+
+ try:
+ base_globals: Dict[str, Any] = sys.modules[module_name].__dict__
+ except KeyError as ke:
+ raise NameError(
+ f"Module {module_name} isn't present in sys.modules; can't "
+ f"resolve name {name}"
+ ) from ke
+
+ # name only, just look in globals. eval() works perfectly fine here,
+ # however we are seeking to have this be faster, as this occurs for
+ # every Mapper[] keyword, etc. depending on configuration
+ try:
+ return base_globals[name]
+ except KeyError as ke:
+ raise NameError(
+ f"Could not locate name {name} in module {module_name}"
+ ) from ke
+
+
+def resolve_name_to_real_class_name(name: str, module_name: str) -> str:
+ try:
+ obj = eval_name_only(name, module_name)
+ except NameError:
+ return name
+ else:
+ return getattr(obj, "__name__", name)
+
+
def de_stringify_union_elements(
cls: Type[Any],
annotation: _AnnotationScanType,
originating_module: str,
- str_cleanup_fn: Optional[Callable[[str], str]] = None,
+ str_cleanup_fn: Optional[Callable[[str, str], str]] = None,
) -> Type[Any]:
return make_union_type(
*[