]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
A meaningful :attr:`.QueryableAttribute.info` attribute is
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 9 Mar 2013 18:24:54 +0000 (13:24 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 9 Mar 2013 18:24:54 +0000 (13:24 -0500)
added, which proxies down to the ``.info`` attribute on either
the :class:`.schema.Column` object if directly present, or
the :class:`.MapperProperty` otherwise.  The full behavior
is documented and ensured by tests to remain stable.
[ticket:2675]

doc/build/changelog/changelog_08.rst
lib/sqlalchemy/orm/attributes.py
lib/sqlalchemy/orm/interfaces.py
lib/sqlalchemy/orm/properties.py
lib/sqlalchemy/sql/util.py
test/orm/test_mapper.py

index 8c34dab09ff4c4e62df4d4b9eb48c055fa498d9a..3a93667b6c91aec158f3db3f01ca3767b9389218 100644 (file)
 
       * :ref:`correlation_context_specific`
 
+    .. change::
+        :tags: feature, orm
+        :tickets: 2675
+
+      A meaningful :attr:`.QueryableAttribute.info` attribute is
+      added, which proxies down to the ``.info`` attribute on either
+      the :class:`.schema.Column` object if directly present, or
+      the :class:`.MapperProperty` otherwise.  The full behavior
+      is documented and ensured by tests to remain stable.
+
     .. change::
         :tags: bug, sql
         :tickets: 2668
index 891aef862acbdc5587f8f499ce32cf48c8084410..3eda127fd5b29ce9b03773ab1e14ad725d8c51a0 100644 (file)
@@ -174,6 +174,49 @@ class QueryableAttribute(interfaces._MappedAttribute,
         # TODO: conditionally attach this method based on clause_element ?
         return self
 
+
+    @util.memoized_property
+    def info(self):
+        """Return the 'info' dictionary for the underlying SQL element.
+
+        The behavior here is as follows:
+
+        * If the attribute is a column-mapped property, i.e.
+          :class:`.ColumnProperty`, which is mapped directly
+          to a schema-level :class:`.Column` object, this attribute
+          will return the :attr:`.SchemaItem.info` dictionary associated
+          with the core-level :class:`.Column` object.
+
+        * If the attribute is a :class:`.ColumnProperty` but is mapped to
+          any other kind of SQL expression other than a :class:`.Column`,
+          the attribute will refer to the :attr:`.MapperProperty.info` dictionary
+          associated directly with the :class:`.ColumnProperty`, assuming the SQL
+          expression itself does not have it's own ``.info`` attribute
+          (which should be the case, unless a user-defined SQL construct
+          has defined one).
+
+        * If the attribute refers to any other kind of :class:`.MapperProperty`,
+          including :class:`.RelationshipProperty`, the attribute will refer
+          to the :attr:`.MapperProperty.info` dictionary associated with
+          that :class:`.MapperProperty`.
+
+        * To access the :attr:`.MapperProperty.info` dictionary of the :class:`.MapperProperty`
+          unconditionally, including for a :class:`.ColumnProperty` that's
+          associated directly with a :class:`.schema.Column`, the attribute
+          can be referred to using :attr:`.QueryableAttribute.property`
+          attribute, as ``MyClass.someattribute.property.info``.
+
+        .. versionadded:: 0.8.0
+
+        .. seealso::
+
+            :attr:`.SchemaItem.info`
+
+            :attr:`.MapperProperty.info`
+
+        """
+        return self.comparator.info
+
     @util.memoized_property
     def parent(self):
         """Return an inspection instance representing the parent.
index b3f7baf0f9eea351a1e2c2288c14dd4ab14e343b..62cdb2710741e438dec0cab1e56c2e1e9ed41b32 100644 (file)
@@ -209,6 +209,12 @@ class MapperProperty(_MappedAttribute, _InspectionAttr):
         .. versionadded:: 0.8  Added support for .info to all
            :class:`.MapperProperty` subclasses.
 
+        .. seealso::
+
+            :attr:`.QueryableAttribute.info`
+
+            :attr:`.SchemaItem.info`
+
         """
         return {}
 
@@ -390,6 +396,10 @@ class PropComparator(operators.ColumnOperators):
 
         return self.__class__(self.prop, self._parentmapper, adapter)
 
+    @util.memoized_property
+    def info(self):
+        return self.property.info
+
     @staticmethod
     def any_op(a, b, **kwargs):
         return a.any(b, **kwargs)
index 9d977b2218109379224f57f91f60625195a68be8..9f8721de90dcef87ba80b5c9d57252b33ec558ef 100644 (file)
@@ -193,6 +193,14 @@ class ColumnProperty(StrategizedProperty):
                     "parententity": self._parentmapper,
                     "parentmapper": self._parentmapper})
 
+        @util.memoized_property
+        def info(self):
+            ce = self.__clause_element__()
+            try:
+                return ce.info
+            except AttributeError:
+                return self.prop.info
+
         def __getattr__(self, key):
             """proxy attribute access down to the mapped column.
 
index fd138cfec6b3bfd713b2bc5154ce546abe8898b7..27ba0f95b62b68d84f4f93615e2d89b2c89c1d19 100644 (file)
@@ -507,6 +507,9 @@ class AnnotatedColumnElement(Annotated):
         """pull 'key' from parent, if not present"""
         return self._Annotated__element.key
 
+    @util.memoized_property
+    def info(self):
+        return self._Annotated__element.info
 
 # hard-generate Annotated subclasses.  this technique
 # is used instead of on-the-fly types (i.e. type.__new__())
index 66082b549449e5664563cbe9ea70a3dee6214217..6b97fb13532235b1ec6dcafac9cc0d898c22596b 100644 (file)
@@ -407,6 +407,37 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
             obj.info["q"] = "p"
             eq_(obj.info, {"q": "p"})
 
+    def test_info_via_instrumented(self):
+        m = MetaData()
+        # create specific tables here as we don't want
+        # users.c.id.info to be pre-initialized
+        users = Table('u', m, Column('id', Integer, primary_key=True),
+                            Column('name', String))
+        addresses = Table('a', m, Column('id', Integer, primary_key=True),
+                            Column('name', String),
+                            Column('user_id', Integer, ForeignKey('u.id')))
+        Address = self.classes.Address
+        User = self.classes.User
+
+        mapper(User, users, properties={
+                "name_lower": column_property(func.lower(users.c.name)),
+                "addresses": relationship(Address)
+            })
+        mapper(Address, addresses)
+
+        # attr.info goes down to the original Column object
+        # for the dictionary.  The annotated element needs to pass
+        # this on.
+        assert 'info' not in users.c.id.__dict__
+        is_(User.id.info, users.c.id.info)
+        assert 'info' in users.c.id.__dict__
+
+        # for SQL expressions, ORM-level .info
+        is_(User.name_lower.info, User.name_lower.property.info)
+
+        # same for relationships
+        is_(User.addresses.info, User.addresses.property.info)
+
 
     def test_add_property(self):
         users, addresses, Address = (self.tables.users,