diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-01-02 17:35:43 -0500 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-01-12 10:29:30 -0500 |
commit | 43f6ae639ca0186f4802255861acdc20f19e702f (patch) | |
tree | 311d908ba5b72b0fcb751d682f56ccd73710d41b /lib/sqlalchemy/util/langhelpers.py | |
parent | a869dc8fe3cd579ed9bab665d215a6c3e3d8a4ca (diff) | |
download | sqlalchemy-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.py | 116 |
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): |