This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# util/langhelpers.py
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2023 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -60,85 +60,7 @@ _HP = TypeVar("_HP", bound="hybridproperty[Any]")
|
||||
_HM = TypeVar("_HM", bound="hybridmethod[Any]")
|
||||
|
||||
|
||||
if compat.py314:
|
||||
# vendor a minimal form of get_annotations per
|
||||
# https://github.com/python/cpython/issues/133684#issuecomment-2863841891
|
||||
|
||||
from annotationlib import call_annotate_function # type: ignore
|
||||
from annotationlib import Format
|
||||
|
||||
def _get_and_call_annotate(obj, format): # noqa: A002
|
||||
annotate = getattr(obj, "__annotate__", None)
|
||||
if annotate is not None:
|
||||
ann = call_annotate_function(annotate, format, owner=obj)
|
||||
if not isinstance(ann, dict):
|
||||
raise ValueError(f"{obj!r}.__annotate__ returned a non-dict")
|
||||
return ann
|
||||
return None
|
||||
|
||||
# this is ported from py3.13.0a7
|
||||
_BASE_GET_ANNOTATIONS = type.__dict__["__annotations__"].__get__ # type: ignore # noqa: E501
|
||||
|
||||
def _get_dunder_annotations(obj):
|
||||
if isinstance(obj, type):
|
||||
try:
|
||||
ann = _BASE_GET_ANNOTATIONS(obj)
|
||||
except AttributeError:
|
||||
# For static types, the descriptor raises AttributeError.
|
||||
return {}
|
||||
else:
|
||||
ann = getattr(obj, "__annotations__", None)
|
||||
if ann is None:
|
||||
return {}
|
||||
|
||||
if not isinstance(ann, dict):
|
||||
raise ValueError(
|
||||
f"{obj!r}.__annotations__ is neither a dict nor None"
|
||||
)
|
||||
return dict(ann)
|
||||
|
||||
def _vendored_get_annotations(
|
||||
obj: Any, *, format: Format # noqa: A002
|
||||
) -> Mapping[str, Any]:
|
||||
"""A sparse implementation of annotationlib.get_annotations()"""
|
||||
|
||||
try:
|
||||
ann = _get_dunder_annotations(obj)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
if ann is not None:
|
||||
return dict(ann)
|
||||
|
||||
# But if __annotations__ threw a NameError, we try calling __annotate__
|
||||
ann = _get_and_call_annotate(obj, format)
|
||||
if ann is None:
|
||||
# If that didn't work either, we have a very weird object:
|
||||
# evaluating
|
||||
# __annotations__ threw NameError and there is no __annotate__.
|
||||
# In that case,
|
||||
# we fall back to trying __annotations__ again.
|
||||
ann = _get_dunder_annotations(obj)
|
||||
|
||||
if ann is None:
|
||||
if isinstance(obj, type) or callable(obj):
|
||||
return {}
|
||||
raise TypeError(f"{obj!r} does not have annotations")
|
||||
|
||||
if not ann:
|
||||
return {}
|
||||
|
||||
return dict(ann)
|
||||
|
||||
def get_annotations(obj: Any) -> Mapping[str, Any]:
|
||||
# FORWARDREF has the effect of giving us ForwardRefs and not
|
||||
# actually trying to evaluate the annotations. We need this so
|
||||
# that the annotations act as much like
|
||||
# "from __future__ import annotations" as possible, which is going
|
||||
# away in future python as a separate mode
|
||||
return _vendored_get_annotations(obj, format=Format.FORWARDREF)
|
||||
|
||||
elif compat.py310:
|
||||
if compat.py310:
|
||||
|
||||
def get_annotations(obj: Any) -> Mapping[str, Any]:
|
||||
return inspect.get_annotations(obj)
|
||||
@@ -252,11 +174,10 @@ def string_or_unprintable(element: Any) -> str:
|
||||
return "unprintable element %r" % element
|
||||
|
||||
|
||||
def clsname_as_plain_name(
|
||||
cls: Type[Any], use_name: Optional[str] = None
|
||||
) -> str:
|
||||
name = use_name or cls.__name__
|
||||
return " ".join(n.lower() for n in re.findall(r"([A-Z][a-z]+|SQL)", name))
|
||||
def clsname_as_plain_name(cls: Type[Any]) -> str:
|
||||
return " ".join(
|
||||
n.lower() for n in re.findall(r"([A-Z][a-z]+|SQL)", cls.__name__)
|
||||
)
|
||||
|
||||
|
||||
def method_is_overridden(
|
||||
@@ -328,30 +249,10 @@ def decorator(target: Callable[..., Any]) -> Callable[[_Fn], _Fn]:
|
||||
if not inspect.isfunction(fn) and not inspect.ismethod(fn):
|
||||
raise Exception("not a decoratable function")
|
||||
|
||||
# Python 3.14 defer creating __annotations__ until its used.
|
||||
# We do not want to create __annotations__ now.
|
||||
annofunc = getattr(fn, "__annotate__", None)
|
||||
if annofunc is not None:
|
||||
fn.__annotate__ = None # type: ignore[union-attr]
|
||||
try:
|
||||
spec = compat.inspect_getfullargspec(fn)
|
||||
finally:
|
||||
fn.__annotate__ = annofunc # type: ignore[union-attr]
|
||||
else:
|
||||
spec = compat.inspect_getfullargspec(fn)
|
||||
spec = compat.inspect_getfullargspec(fn)
|
||||
env: Dict[str, Any] = {}
|
||||
|
||||
# Do not generate code for annotations.
|
||||
# update_wrapper() copies the annotation from fn to decorated.
|
||||
# We use dummy defaults for code generation to avoid having
|
||||
# copy of large globals for compiling.
|
||||
# We copy __defaults__ and __kwdefaults__ from fn to decorated.
|
||||
empty_defaults = (None,) * len(spec.defaults or ())
|
||||
empty_kwdefaults = dict.fromkeys(spec.kwonlydefaults or ())
|
||||
spec = spec._replace(
|
||||
annotations={},
|
||||
defaults=empty_defaults,
|
||||
kwonlydefaults=empty_kwdefaults,
|
||||
)
|
||||
spec = _update_argspec_defaults_into_env(spec, env)
|
||||
|
||||
names = (
|
||||
tuple(cast("Tuple[str, ...]", spec[0]))
|
||||
@@ -396,21 +297,41 @@ def decorator(target: Callable[..., Any]) -> Callable[[_Fn], _Fn]:
|
||||
% metadata
|
||||
)
|
||||
|
||||
env: Dict[str, Any] = {
|
||||
targ_name: target,
|
||||
fn_name: fn,
|
||||
"__name__": fn.__module__,
|
||||
}
|
||||
mod = sys.modules[fn.__module__]
|
||||
env.update(vars(mod))
|
||||
env.update({targ_name: target, fn_name: fn, "__name__": fn.__module__})
|
||||
|
||||
decorated = cast(
|
||||
types.FunctionType,
|
||||
_exec_code_in_env(code, env, fn.__name__),
|
||||
)
|
||||
decorated.__defaults__ = fn.__defaults__
|
||||
decorated.__kwdefaults__ = fn.__kwdefaults__ # type: ignore
|
||||
return update_wrapper(decorated, fn) # type: ignore[return-value]
|
||||
decorated.__defaults__ = getattr(fn, "__func__", fn).__defaults__
|
||||
|
||||
return update_wrapper(decorate, target) # type: ignore[return-value]
|
||||
decorated.__wrapped__ = fn # type: ignore
|
||||
return cast(_Fn, update_wrapper(decorated, fn))
|
||||
|
||||
return update_wrapper(decorate, target)
|
||||
|
||||
|
||||
def _update_argspec_defaults_into_env(spec, env):
|
||||
"""given a FullArgSpec, convert defaults to be symbol names in an env."""
|
||||
|
||||
if spec.defaults:
|
||||
new_defaults = []
|
||||
i = 0
|
||||
for arg in spec.defaults:
|
||||
if type(arg).__module__ not in ("builtins", "__builtin__"):
|
||||
name = "x%d" % i
|
||||
env[name] = arg
|
||||
new_defaults.append(name)
|
||||
i += 1
|
||||
else:
|
||||
new_defaults.append(arg)
|
||||
elem = list(spec)
|
||||
elem[3] = tuple(new_defaults)
|
||||
return compat.FullArgSpec(*elem)
|
||||
else:
|
||||
return spec
|
||||
|
||||
|
||||
def _exec_code_in_env(
|
||||
@@ -463,9 +384,6 @@ class PluginLoader:
|
||||
|
||||
self.impls[name] = load
|
||||
|
||||
def deregister(self, name: str) -> None:
|
||||
del self.impls[name]
|
||||
|
||||
|
||||
def _inspect_func_args(fn):
|
||||
try:
|
||||
@@ -493,13 +411,15 @@ def get_cls_kwargs(
|
||||
*,
|
||||
_set: Optional[Set[str]] = None,
|
||||
raiseerr: Literal[True] = ...,
|
||||
) -> Set[str]: ...
|
||||
) -> Set[str]:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def get_cls_kwargs(
|
||||
cls: type, *, _set: Optional[Set[str]] = None, raiseerr: bool = False
|
||||
) -> Optional[Set[str]]: ...
|
||||
) -> Optional[Set[str]]:
|
||||
...
|
||||
|
||||
|
||||
def get_cls_kwargs(
|
||||
@@ -743,9 +663,7 @@ def format_argspec_init(method, grouped=True):
|
||||
"""format_argspec_plus with considerations for typical __init__ methods
|
||||
|
||||
Wraps format_argspec_plus with error handling strategies for typical
|
||||
__init__ cases:
|
||||
|
||||
.. sourcecode:: text
|
||||
__init__ cases::
|
||||
|
||||
object.__init__ -> (self)
|
||||
other unreflectable (usually C) -> (self, *args, **kwargs)
|
||||
@@ -800,9 +718,7 @@ def create_proxy_methods(
|
||||
def getargspec_init(method):
|
||||
"""inspect.getargspec with considerations for typical __init__ methods
|
||||
|
||||
Wraps inspect.getargspec with error handling for typical __init__ cases:
|
||||
|
||||
.. sourcecode:: text
|
||||
Wraps inspect.getargspec with error handling for typical __init__ cases::
|
||||
|
||||
object.__init__ -> (self)
|
||||
other unreflectable (usually C) -> (self, *args, **kwargs)
|
||||
@@ -1176,19 +1092,23 @@ class generic_fn_descriptor(Generic[_T_co]):
|
||||
self.__name__ = fget.__name__
|
||||
|
||||
@overload
|
||||
def __get__(self: _GFD, obj: None, cls: Any) -> _GFD: ...
|
||||
def __get__(self: _GFD, obj: None, cls: Any) -> _GFD:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __get__(self, obj: object, cls: Any) -> _T_co: ...
|
||||
def __get__(self, obj: object, cls: Any) -> _T_co:
|
||||
...
|
||||
|
||||
def __get__(self: _GFD, obj: Any, cls: Any) -> Union[_GFD, _T_co]:
|
||||
raise NotImplementedError()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
def __set__(self, instance: Any, value: Any) -> None: ...
|
||||
def __set__(self, instance: Any, value: Any) -> None:
|
||||
...
|
||||
|
||||
def __delete__(self, instance: Any) -> None: ...
|
||||
def __delete__(self, instance: Any) -> None:
|
||||
...
|
||||
|
||||
def _reset(self, obj: Any) -> None:
|
||||
raise NotImplementedError()
|
||||
@@ -1327,10 +1247,12 @@ class HasMemoized:
|
||||
self.__name__ = fget.__name__
|
||||
|
||||
@overload
|
||||
def __get__(self: _MA, obj: None, cls: Any) -> _MA: ...
|
||||
def __get__(self: _MA, obj: None, cls: Any) -> _MA:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __get__(self, obj: Any, cls: Any) -> _T: ...
|
||||
def __get__(self, obj: Any, cls: Any) -> _T:
|
||||
...
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
if obj is None:
|
||||
@@ -1676,9 +1598,9 @@ class hybridmethod(Generic[_T]):
|
||||
class symbol(int):
|
||||
"""A constant symbol.
|
||||
|
||||
>>> symbol("foo") is symbol("foo")
|
||||
>>> symbol('foo') is symbol('foo')
|
||||
True
|
||||
>>> symbol("foo")
|
||||
>>> symbol('foo')
|
||||
<symbol 'foo>
|
||||
|
||||
A slight refinement of the MAGICCOOKIE=object() pattern. The primary
|
||||
@@ -1744,8 +1666,6 @@ class _IntFlagMeta(type):
|
||||
items: List[symbol]
|
||||
cls._items = items = []
|
||||
for k, v in dict_.items():
|
||||
if re.match(r"^__.*__$", k):
|
||||
continue
|
||||
if isinstance(v, int):
|
||||
sym = symbol(k, canonical=v)
|
||||
elif not k.startswith("_"):
|
||||
@@ -2039,15 +1959,12 @@ NoneType = type(None)
|
||||
|
||||
|
||||
def attrsetter(attrname):
|
||||
code = "def set(obj, value): obj.%s = value" % attrname
|
||||
code = "def set(obj, value):" " obj.%s = value" % attrname
|
||||
env = locals().copy()
|
||||
exec(code, env)
|
||||
return env["set"]
|
||||
|
||||
|
||||
_dunders = re.compile("^__.+__$")
|
||||
|
||||
|
||||
class TypingOnly:
|
||||
"""A mixin class that marks a class as 'typing only', meaning it has
|
||||
absolutely no methods, attributes, or runtime functionality whatsoever.
|
||||
@@ -2058,9 +1975,15 @@ class TypingOnly:
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
if TypingOnly in cls.__bases__:
|
||||
remaining = {
|
||||
name for name in cls.__dict__ if not _dunders.match(name)
|
||||
}
|
||||
remaining = set(cls.__dict__).difference(
|
||||
{
|
||||
"__module__",
|
||||
"__doc__",
|
||||
"__slots__",
|
||||
"__orig_bases__",
|
||||
"__annotations__",
|
||||
}
|
||||
)
|
||||
if remaining:
|
||||
raise AssertionError(
|
||||
f"Class {cls} directly inherits TypingOnly but has "
|
||||
@@ -2293,11 +2216,3 @@ def has_compiled_ext(raise_=False):
|
||||
)
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class _Missing(enum.Enum):
|
||||
Missing = enum.auto()
|
||||
|
||||
|
||||
Missing = _Missing.Missing
|
||||
MissingOr = Union[_T, Literal[_Missing.Missing]]
|
||||
|
||||
Reference in New Issue
Block a user