summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/util
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2021-12-08 20:27:16 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2021-12-27 12:30:38 -0500
commit91501e06a17d873902114275d7149ba24973db6a (patch)
tree464d2c7167c30d92be13c851b52c0f4471645456 /lib/sqlalchemy/util
parent2bb6cfc7c9b8f09eaa4efeffc337a1162993979c (diff)
downloadsqlalchemy-91501e06a17d873902114275d7149ba24973db6a.tar.gz
factor out UnboundLoad and rearchitect strategy_options.py
The architecture of Load is mostly rewritten here. The change includes removal of the "pluggable" aspect of the loader options, which would patch new methods onto Load. This has been replaced by normal methods that respond normally to typing annotations. As part of this change, the bake_loaders() and unbake_loaders() options, which have no effect since 1.4 and were unlikely to be in any common use, have been removed. Additionally, to support annotations for methods that make use of @decorator, @generative etc., modified format_argspec_plus to no longer return "args", instead returns "grouped_args" which is always grouped and allows return annotations to format correctly. Fixes: #6986 Change-Id: I6117c642345cdde65a64389bba6057ddd5374427
Diffstat (limited to 'lib/sqlalchemy/util')
-rw-r--r--lib/sqlalchemy/util/compat.py2
-rw-r--r--lib/sqlalchemy/util/langhelpers.py56
2 files changed, 39 insertions, 19 deletions
diff --git a/lib/sqlalchemy/util/compat.py b/lib/sqlalchemy/util/compat.py
index dfa5fa825..37f157698 100644
--- a/lib/sqlalchemy/util/compat.py
+++ b/lib/sqlalchemy/util/compat.py
@@ -165,7 +165,7 @@ def _formatannotation(annotation, base_module=None):
return repr(annotation).replace("typing.", "")
if isinstance(annotation, type):
if annotation.__module__ in ("builtins", base_module):
- return annotation.__qualname__
+ return repr(annotation.__qualname__)
return annotation.__module__ + "." + annotation.__qualname__
return repr(annotation)
diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py
index b759490c5..780af2bfe 100644
--- a/lib/sqlalchemy/util/langhelpers.py
+++ b/lib/sqlalchemy/util/langhelpers.py
@@ -20,12 +20,22 @@ import re
import sys
import textwrap
import types
+from typing import Any
+from typing import Callable
+from typing import Generic
+from typing import Optional
+from typing import overload
+from typing import TypeVar
+from typing import Union
import warnings
from . import _collections
from . import compat
from .. import exc
+_T = TypeVar("_T")
+_MP = TypeVar("_MP", bound="memoized_property[Any]")
+
def md5_hex(x):
x = x.encode("utf-8")
@@ -179,7 +189,7 @@ def decorator(target):
metadata["name"] = fn.__name__
code = (
"""\
-def %(name)s(%(args)s):
+def %(name)s%(grouped_args)s:
return %(target)s(%(fn)s, %(apply_kw)s)
"""
% metadata
@@ -255,7 +265,7 @@ def public_factory(target, location, class_location=None):
metadata["name"] = location_name
code = (
"""\
-def %(name)s(%(args)s):
+def %(name)s%(grouped_args)s:
return cls(%(apply_kw)s)
"""
% metadata
@@ -501,7 +511,7 @@ def format_argspec_plus(fn, grouped=True):
Example::
>>> format_argspec_plus(lambda self, a, b, c=3, **d: 123)
- {'args': '(self, a, b, c=3, **d)',
+ {'grouped_args': '(self, a, b, c=3, **d)',
'self_arg': 'self',
'apply_kw': '(self, a, b, c=c, **d)',
'apply_pos': '(self, a, b, c, **d)'}
@@ -567,7 +577,7 @@ def format_argspec_plus(fn, grouped=True):
if grouped:
return dict(
- args=args,
+ grouped_args=args,
self_arg=self_arg,
apply_pos=apply_pos,
apply_kw=apply_kw,
@@ -576,7 +586,7 @@ def format_argspec_plus(fn, grouped=True):
)
else:
return dict(
- args=args[1:-1],
+ grouped_args=args,
self_arg=self_arg,
apply_pos=apply_pos[1:-1],
apply_kw=apply_kw[1:-1],
@@ -596,21 +606,19 @@ def format_argspec_init(method, grouped=True):
"""
if method is object.__init__:
+ grouped_args = "(self)"
args = "(self)" if grouped else "self"
proxied = "()" if grouped else ""
else:
try:
return format_argspec_plus(method, grouped=grouped)
except TypeError:
- args = (
- "(self, *args, **kwargs)"
- if grouped
- else "self, *args, **kwargs"
- )
+ grouped_args = "(self, *args, **kwargs)"
+ args = grouped_args if grouped else "self, *args, **kwargs"
proxied = "(*args, **kwargs)" if grouped else "*args, **kwargs"
return dict(
self_arg="self",
- args=args,
+ grouped_args=grouped_args,
apply_pos=args,
apply_kw=args,
apply_pos_proxied=proxied,
@@ -645,20 +653,20 @@ def create_proxy_methods(
"name": fn.__name__,
"apply_pos_proxied": caller_argspec["apply_pos_proxied"],
"apply_kw_proxied": caller_argspec["apply_kw_proxied"],
- "args": caller_argspec["args"],
+ "grouped_args": caller_argspec["grouped_args"],
"self_arg": caller_argspec["self_arg"],
}
if clslevel:
code = (
- "def %(name)s(%(args)s):\n"
+ "def %(name)s%(grouped_args)s:\n"
" return target_cls.%(name)s(%(apply_kw_proxied)s)"
% metadata
)
env["target_cls"] = target_cls
else:
code = (
- "def %(name)s(%(args)s):\n"
+ "def %(name)s%(grouped_args)s:\n"
" return %(self_arg)s._proxied.%(name)s(%(apply_kw_proxied)s)" # noqa E501
% metadata
)
@@ -1072,15 +1080,27 @@ def as_interface(obj, cls=None, methods=None, required=None):
)
-class memoized_property:
+class memoized_property(Generic[_T]):
"""A read-only @property that is only evaluated once."""
- def __init__(self, fget, doc=None):
+ fget: Callable[..., _T]
+ __doc__: Optional[str]
+ __name__: str
+
+ def __init__(self, fget: Callable[..., _T], doc: Optional[str] = None):
self.fget = fget
self.__doc__ = doc or fget.__doc__
self.__name__ = fget.__name__
- def __get__(self, obj, cls):
+ @overload
+ def __get__(self: _MP, obj: None, cls: Any) -> _MP:
+ ...
+
+ @overload
+ def __get__(self, obj: Any, cls: Any) -> _T:
+ ...
+
+ def __get__(self: _MP, obj: Any, cls: Any) -> Union[_MP, _T]:
if obj is None:
return self
obj.__dict__[self.__name__] = result = self.fget(obj)
@@ -1090,7 +1110,7 @@ class memoized_property:
memoized_property.reset(obj, self.__name__)
@classmethod
- def reset(cls, obj, name):
+ def reset(cls, obj: Any, name: str) -> None:
obj.__dict__.pop(name, None)