summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorDmytro Starosud <d.starosud@gmail.com>2019-05-29 16:47:13 +0300
committerMike Bayer <mike_mp@zzzcomputing.com>2019-05-29 17:07:35 -0400
commit5ac10699e1111283ae848f9d3a6dcc4e09d8c1ef (patch)
tree2cee514317fc01465e4a61ff5ea51392a2727197 /lib/sqlalchemy
parent142c94348ad4a341f7cbff3d76bf0df397fa782f (diff)
downloadsqlalchemy-5ac10699e1111283ae848f9d3a6dcc4e09d8c1ef.tar.gz
Rework AliasedClass __getattr__ to use top-level getattr()
Reworked the attribute mechanics used by :class:`.AliasedClass` to no longer rely upon calling ``__getattribute__`` on the MRO of the wrapped class, and to instead resolve the attribute normally on the wrapped class using getattr(), and then unwrap/adapt that. This allows a greater range of attribute styles on the mapped class including special ``__getattr__()`` schemes; but it also makes the code simpler and more resilient in general. Fixes: #4694 Co-authored-by: Mike Bayer <mike_mp@zzzcomputing.com> Change-Id: I28901e2472d3c21e881fe5cafa3b1d3af704fad8
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/orm/attributes.py8
-rw-r--r--lib/sqlalchemy/orm/util.py51
2 files changed, 29 insertions, 30 deletions
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py
index 321ab7d6f..74ebe40f6 100644
--- a/lib/sqlalchemy/orm/attributes.py
+++ b/lib/sqlalchemy/orm/attributes.py
@@ -346,10 +346,14 @@ def create_proxied_attribute(descriptor):
)
def __get__(self, instance, owner):
- if instance is None:
+ retval = self.descriptor.__get__(instance, owner)
+ # detect if this is a plain Python @property, which just returns
+ # itself for class level access. If so, then return us.
+ # Otherwise, return the object returned by the descriptor.
+ if retval is self.descriptor and instance is None:
return self
else:
- return self.descriptor.__get__(instance, owner)
+ return retval
def __str__(self):
return "%s.%s" % (self.class_.__name__, self.key)
diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py
index e57418106..529766fc1 100644
--- a/lib/sqlalchemy/orm/util.py
+++ b/lib/sqlalchemy/orm/util.py
@@ -7,6 +7,7 @@
import re
+import types
from . import attributes # noqa
from .base import _class_to_mapper # noqa
@@ -495,34 +496,28 @@ class AliasedClass(object):
except KeyError:
raise AttributeError()
else:
- for base in _aliased_insp._target.__mro__:
- try:
- attr = object.__getattribute__(base, key)
- except AttributeError:
- continue
- else:
- break
- else:
- raise AttributeError(key)
-
- if isinstance(attr, PropComparator):
- ret = attr.adapt_to_entity(_aliased_insp)
- setattr(self, key, ret)
- return ret
- elif hasattr(attr, "func_code"):
- is_method = getattr(_aliased_insp._target, key, None)
- if is_method and is_method.__self__ is not None:
- return util.types.MethodType(attr.__func__, self, self)
- else:
- return None
- elif hasattr(attr, "__get__"):
- ret = attr.__get__(None, self)
- if isinstance(ret, PropComparator):
- return ret.adapt_to_entity(_aliased_insp)
- else:
- return ret
- else:
- return attr
+ target = _aliased_insp._target
+ # maintain all getattr mechanics
+ attr = getattr(target, key)
+
+ # attribute is a method, that will be invoked against a
+ # "self"; so just return a new method with the same function and
+ # new self
+ if hasattr(attr, "__call__") and hasattr(attr, "__self__"):
+ return types.MethodType(attr.__func__, self)
+
+ # attribute is a descriptor, that will be invoked against a
+ # "self"; so invoke the descriptor against this self
+ if hasattr(attr, "__get__"):
+ attr = attr.__get__(None, self)
+
+ # attributes within the QueryableAttribute system will want this
+ # to be invoked so the object can be adapted
+ if hasattr(attr, "adapt_to_entity"):
+ attr = attr.adapt_to_entity(_aliased_insp)
+ setattr(self, key, attr)
+
+ return attr
def __repr__(self):
return "<AliasedClass at 0x%x; %s>" % (