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.
[ticket:2675]
This commit is contained in:
Mike Bayer
2013-03-09 13:24:54 -05:00
parent d2a256a3ad
commit 852e9954aa
6 changed files with 105 additions and 0 deletions
+10
View File
@@ -18,6 +18,16 @@
* :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
+43
View 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.
+10
View 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)
+8
View 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.
+3
View 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__())
+31
View 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,