summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/util/langhelpers.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2022-01-02 17:35:43 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2022-01-12 10:29:30 -0500
commit43f6ae639ca0186f4802255861acdc20f19e702f (patch)
tree311d908ba5b72b0fcb751d682f56ccd73710d41b /lib/sqlalchemy/util/langhelpers.py
parenta869dc8fe3cd579ed9bab665d215a6c3e3d8a4ca (diff)
downloadsqlalchemy-43f6ae639ca0186f4802255861acdc20f19e702f.tar.gz
initial reorganize for static typing
start applying foundational annotations to key elements. two main elements addressed here: 1. removal of public_factory() and replacement with explicit functions. this just works much better with typing. 2. typing support for column expressions and operators. The biggest part of this involves stubbing out all the ColumnOperators methods under ColumnElement in a TYPE_CHECKING section. Took me a while to see this method vs. much more complicated things I thought I needed. Also for this version implementing #7519, ColumnElement types against the Python type and not TypeEngine. it is hoped this leads to easier transferrence between ORM/Core as well as eventual support for result set typing. Not clear yet how well this approach will work and what new issues it may introduce. given the current approach we now get full, rich typing for scenarios like this: from sqlalchemy import column, Integer, String, Boolean c1 = column('a', String) c2 = column('a', Integer) expr1 = c2.in_([1, 2, 3]) expr2 = c2 / 5 expr3 = -c2 expr4_a = ~(c2 == 5) expr4_b = ~column('q', Boolean) expr5 = c1 + 'x' expr6 = c2 + 10 Fixes: #7519 Fixes: #6810 Change-Id: I078d9f57955549f6f7868314287175f6c61c44cb
Diffstat (limited to 'lib/sqlalchemy/util/langhelpers.py')
-rw-r--r--lib/sqlalchemy/util/langhelpers.py116
1 files changed, 21 insertions, 95 deletions
diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py
index 66c530867..d85b1261b 100644
--- a/lib/sqlalchemy/util/langhelpers.py
+++ b/lib/sqlalchemy/util/langhelpers.py
@@ -36,6 +36,11 @@ from . import typing as compat_typing
from .. import exc
_T = TypeVar("_T")
+_F = TypeVar("_F", bound=Callable[..., Any])
+_MP = TypeVar("_MP", bound="memoized_property[Any]")
+_MA = TypeVar("_MA", bound="HasMemoized.memoized_attribute[Any]")
+_HP = TypeVar("_HP", bound="hybridproperty")
+_HM = TypeVar("_HM", bound="hybridmethod")
def md5_hex(x):
@@ -234,103 +239,12 @@ def _exec_code_in_env(code, env, fn_name):
return env[fn_name]
+_PF = TypeVar("_PF")
_TE = TypeVar("_TE")
_P = compat_typing.ParamSpec("_P")
-def public_factory(
- target: typing.Callable[_P, _TE],
- location: str,
- class_location: Optional[str] = None,
-) -> typing.Callable[_P, _TE]:
- """Produce a wrapping function for the given cls or classmethod.
-
- Rationale here is so that the __init__ method of the
- class can serve as documentation for the function.
-
- """
-
- if isinstance(target, type):
- fn = target.__init__
- callable_ = target
- doc = (
- "Construct a new :class:`%s` object. \n\n"
- "This constructor is mirrored as a public API function; "
- "see :func:`sqlalchemy%s` "
- "for a full usage and argument description."
- % (
- class_location if class_location else ".%s" % target.__name__,
- location,
- )
- )
- else:
- fn = callable_ = target
- doc = (
- "This function is mirrored; see :func:`sqlalchemy%s` "
- "for a description of arguments." % location
- )
-
- location_name = location.split(".")[-1]
- spec = compat.inspect_getfullargspec(fn)
- del spec[0][0]
- metadata = format_argspec_plus(spec, grouped=False)
- metadata["name"] = location_name
- code = (
- """\
-def %(name)s%(grouped_args)s:
- return cls(%(apply_kw)s)
-"""
- % metadata
- )
- env = {
- "cls": callable_,
- "symbol": symbol,
- "__name__": callable_.__module__,
- }
- exec(code, env)
-
- decorated = env[location_name]
-
- if hasattr(fn, "_linked_to"):
- linked_to, linked_to_location = fn._linked_to
- linked_to_doc = linked_to.__doc__
- if class_location is None:
- class_location = "%s.%s" % (target.__module__, target.__name__)
-
- linked_to_doc = inject_docstring_text(
- linked_to_doc,
- ".. container:: inherited_member\n\n "
- "This documentation is inherited from :func:`sqlalchemy%s`; "
- "this constructor, :func:`sqlalchemy%s`, "
- "creates a :class:`sqlalchemy%s` object. See that class for "
- "additional details describing this subclass."
- % (linked_to_location, location, class_location),
- 1,
- )
- decorated.__doc__ = linked_to_doc
- else:
- decorated.__doc__ = fn.__doc__
-
- decorated.__module__ = "sqlalchemy" + location.rsplit(".", 1)[0]
- if decorated.__module__ not in sys.modules:
- raise ImportError(
- "public_factory location %s is not in sys.modules"
- % (decorated.__module__,)
- )
-
- if hasattr(fn, "__func__"):
- fn.__func__.__doc__ = doc
- if not hasattr(fn.__func__, "_linked_to"):
- fn.__func__._linked_to = (decorated, location)
- else:
- fn.__doc__ = doc
- if not hasattr(fn, "_linked_to"):
- fn._linked_to = (decorated, location)
-
- return decorated
-
-
class PluginLoader:
def __init__(self, group, auto_fn=None):
self.group = group
@@ -1182,14 +1096,26 @@ class HasMemoized:
self.__dict__[key] = value
self._memoized_keys |= {key}
- class memoized_attribute:
+ class memoized_attribute(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__
+ @overload
+ def __get__(self: _MA, obj: None, cls: Any) -> _MA:
+ ...
+
+ @overload
+ def __get__(self, obj: Any, cls: Any) -> _T:
+ ...
+
def __get__(self, obj, cls):
if obj is None:
return self
@@ -1198,7 +1124,7 @@ class HasMemoized:
return result
@classmethod
- def memoized_instancemethod(cls, fn):
+ def memoized_instancemethod(cls, fn: Any) -> Any:
"""Decorate a method memoize its return value."""
def oneshot(self, *args, **kw):