summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2020-08-06 15:53:17 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2020-08-06 17:12:58 -0400
commit9a3fee2cb6b608eb5c0263cf5a7e9085f74f2e73 (patch)
tree7cf047100c3961588c08d04fac863b250dfb4635 /lib/sqlalchemy
parentf1626c784efc800a912a815085b9bac609c0d1cb (diff)
downloadsqlalchemy-9a3fee2cb6b608eb5c0263cf5a7e9085f74f2e73.tar.gz
base all_orm_descriptors ordering on cls.__dict__ + cls.__mro__
Adjusted the workings of the :meth:`_orm.Mapper.all_orm_descriptors` accessor to represent the attributes in the order that they are located in a deterministic way, assuming the use of Python 3.6 or higher which maintains the sorting order of class attributes based on how they were declared. This sorting is not guaranteed to match the declared order of attributes in all cases however; see the method documentation for the exact scheme. Fixes: #5494 Change-Id: I6ee8d4ace3eb8b3f7c9c0f2a3d7e27b5f62abfd3
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/orm/instrumentation.py24
-rw-r--r--lib/sqlalchemy/orm/mapper.py15
-rw-r--r--lib/sqlalchemy/testing/requirements.py4
3 files changed, 37 insertions, 6 deletions
diff --git a/lib/sqlalchemy/orm/instrumentation.py b/lib/sqlalchemy/orm/instrumentation.py
index 43e380101..f64744083 100644
--- a/lib/sqlalchemy/orm/instrumentation.py
+++ b/lib/sqlalchemy/orm/instrumentation.py
@@ -158,12 +158,24 @@ class ClassManager(HasMemoized, dict):
:class:`.AssociationProxy`.
"""
- if exclude is None:
- exclude = set()
- for supercls in self.class_.__mro__:
- for key in set(supercls.__dict__).difference(exclude):
- exclude.add(key)
- val = supercls.__dict__[key]
+
+ found = {}
+
+ # constraints:
+ # 1. yield keys in cls.__dict__ order
+ # 2. if a subclass has the same key as a superclass, include that
+ # key as part of the ordering of the superclass, because an
+ # overridden key is usually installed by the mapper which is going
+ # on a different ordering
+ # 3. don't use getattr() as this fires off descriptors
+
+ for supercls in self.class_.__mro__[0:-1]:
+ inherits = supercls.__mro__[1]
+ for key in supercls.__dict__:
+ found.setdefault(key, supercls)
+ if key in inherits.__dict__:
+ continue
+ val = found[key].__dict__[key]
if (
isinstance(val, interfaces.InspectionAttr)
and val.is_attribute
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index 6d22c6205..e428728e3 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -2434,6 +2434,21 @@ class Mapper(
the attribute :attr:`.InspectionAttr.extension_type` will refer
to a constant that distinguishes between different extension types.
+ The sorting of the attributes is based on what is located in
+ the ``__dict__`` of the mapped class as well as its mapped
+ superclasses. The sorting will be all those attribute names
+ that appear in the ``__dict__`` of the immediate class and not
+ any of its superclasses, then the names which appear in the
+ ``__dict__`` of the superclass and not any of the further superclasses,
+ all the way down. This will produce a deterministic ordering on
+ Python 3.6 and above. It is not guaranteed to match the declared
+ ordering of attributes on the class, however, as the mapping process
+ itself populates Python descriptors into the ``__dict__`` of a mapped
+ class which are not always explicit in a declarative mapping.
+
+ .. versionchanged:: 1.4 ensured deterministic ordering for
+ :meth:`_orm.Mapper.all_orm_descriptors`.
+
When dealing with a :class:`.QueryableAttribute`, the
:attr:`.QueryableAttribute.property` attribute refers to the
:class:`.MapperProperty` property, which is what you get when
diff --git a/lib/sqlalchemy/testing/requirements.py b/lib/sqlalchemy/testing/requirements.py
index 72f2612aa..25998c07b 100644
--- a/lib/sqlalchemy/testing/requirements.py
+++ b/lib/sqlalchemy/testing/requirements.py
@@ -1105,6 +1105,10 @@ class SuiteRequirements(Requirements):
)
@property
+ def pep520(self):
+ return self.python36
+
+ @property
def python36(self):
return exclusions.skip_if(
lambda: sys.version_info < (3, 6),