diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-04-05 19:00:19 -0400 |
|---|---|---|
| committer | mike bayer <mike_mp@zzzcomputing.com> | 2022-04-12 02:09:42 +0000 |
| commit | 98eae4e181cb2d1bbc67ec834bfad29dcba7f461 (patch) | |
| tree | fc000c3113a4a198b4ddd6bc81fe291dc9ef1ffb /lib/sqlalchemy/util | |
| parent | 15ef11e0ede82e44fb07f31b63d3db0712d8bf48 (diff) | |
| download | sqlalchemy-98eae4e181cb2d1bbc67ec834bfad29dcba7f461.tar.gz | |
use code generation for scoped_session
our decorator thing generates code in any case,
so point it at the file itself to generate real code
for the blocks rather than doing things dynamically.
this will allow typing tools to have no problem
whatsoever and we also reduce import time overhead.
file size will be a lot bigger though, shrugs.
syntax / dupe method / etc. checking will be accomplished
by our existing linting / typing / formatting tools.
As we are also using "from __future__ import annotations",
we also no longer have to apply quotes to generated
annotations.
Change-Id: I20962cb65bda63ff0fb67357ab346e9b1ef4f108
Diffstat (limited to 'lib/sqlalchemy/util')
| -rw-r--r-- | lib/sqlalchemy/util/compat.py | 8 | ||||
| -rw-r--r-- | lib/sqlalchemy/util/langhelpers.py | 106 |
2 files changed, 10 insertions, 104 deletions
diff --git a/lib/sqlalchemy/util/compat.py b/lib/sqlalchemy/util/compat.py index d30236dd9..aeaefa40e 100644 --- a/lib/sqlalchemy/util/compat.py +++ b/lib/sqlalchemy/util/compat.py @@ -139,17 +139,17 @@ def _formatannotation(annotation, base_module=None): """vendored from python 3.7""" if isinstance(annotation, str): - return f'"{annotation}"' + return annotation if getattr(annotation, "__module__", None) == "typing": - return f'"{repr(annotation).replace("typing.", "").replace("~", "")}"' + return repr(annotation).replace("typing.", "").replace("~", "") if isinstance(annotation, type): if annotation.__module__ in ("builtins", base_module): return repr(annotation.__qualname__) return annotation.__module__ + "." + annotation.__qualname__ elif isinstance(annotation, typing.TypeVar): - return f'"{repr(annotation).replace("~", "")}"' - return f'"{repr(annotation).replace("~", "")}"' + return repr(annotation).replace("~", "") + return repr(annotation).replace("~", "") def inspect_formatargspec( diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py index 8f0ec700f..3e89c72bb 100644 --- a/lib/sqlalchemy/util/langhelpers.py +++ b/lib/sqlalchemy/util/langhelpers.py @@ -678,111 +678,17 @@ def create_proxy_methods( methods=(), attributes=(), ): - """A class decorator that will copy attributes to a proxy class. + """A class decorator indicating attributes should refer to a proxy + class. - The class to be instrumented must define a single accessor "_proxied". + This decorator is now a "marker" that does nothing at runtime. Instead, + it is consumed by the tools/generate_proxy_methods.py script to + statically generate proxy methods and attributes that are fully + recognized by typing tools such as mypy. """ def decorate(cls): - def instrument(name, clslevel=False): - fn = cast(types.FunctionType, getattr(target_cls, name)) - spec = compat.inspect_getfullargspec(fn) - env = {"__name__": fn.__module__} - - spec = _update_argspec_defaults_into_env(spec, env) - caller_argspec = format_argspec_plus(spec, grouped=False) - - metadata = { - "name": fn.__name__, - "apply_pos_proxied": caller_argspec["apply_pos_proxied"], - "apply_kw_proxied": caller_argspec["apply_kw_proxied"], - "grouped_args": caller_argspec["grouped_args"], - "self_arg": caller_argspec["self_arg"], - } - - if clslevel: - code = ( - "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%(grouped_args)s:\n" - " return %(self_arg)s._proxied.%(name)s(%(apply_kw_proxied)s)" # noqa: E501 - % metadata - ) - - proxy_fn = cast( - types.FunctionType, _exec_code_in_env(code, env, fn.__name__) - ) - proxy_fn.__defaults__ = getattr(fn, "__func__", fn).__defaults__ - proxy_fn.__doc__ = inject_docstring_text( - fn.__doc__, - ".. container:: class_bases\n\n " - "Proxied for the %s class on behalf of the %s class." - % (target_cls_sphinx_name, proxy_cls_sphinx_name), - 1, - ) - - if clslevel: - return classmethod(proxy_fn) - else: - return proxy_fn - - def makeprop(name): - attr = target_cls.__dict__.get(name, None) - - if attr is not None: - doc = inject_docstring_text( - attr.__doc__, - ".. container:: class_bases\n\n " - "Proxied for the %s class on behalf of the %s class." - % ( - target_cls_sphinx_name, - proxy_cls_sphinx_name, - ), - 1, - ) - else: - doc = None - - code = ( - "def set_(self, attr):\n" - " self._proxied.%(name)s = attr\n" - "def get(self):\n" - " return self._proxied.%(name)s\n" - "get.__doc__ = doc\n" - "getset = property(get, set_)" - ) % {"name": name} - - getset = _exec_code_in_env(code, {"doc": doc}, "getset") - - return getset - - for meth in methods: - if hasattr(cls, meth): - raise TypeError( - "class %s already has a method %s" % (cls, meth) - ) - setattr(cls, meth, instrument(meth)) - - for prop in attributes: - if hasattr(cls, prop): - raise TypeError( - "class %s already has a method %s" % (cls, prop) - ) - setattr(cls, prop, makeprop(prop)) - - for prop in classmethods: - if hasattr(cls, prop): - raise TypeError( - "class %s already has a method %s" % (cls, prop) - ) - setattr(cls, prop, instrument(prop, clslevel=True)) - return cls return decorate |
