]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- [feature] Can now provide class-bound attributes
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 4 Aug 2012 22:11:18 +0000 (18:11 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 4 Aug 2012 22:11:18 +0000 (18:11 -0400)
    that override columns which are of any
    non-ORM type, not just descriptors.
    [ticket:2535]

CHANGES
lib/sqlalchemy/orm/mapper.py
test/orm/test_mapper.py

diff --git a/CHANGES b/CHANGES
index c654fef43f684c2accb1be01435d9090640ecf42..b49c76d793f26800306f4f56e2501830c45ccbac 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -185,6 +185,11 @@ underneath "0.7.xx".
     when dereferenced by a unit test.
     [ticket:2526]
 
+  - [feature] Can now provide class-bound attributes
+    that override columns which are of any
+    non-ORM type, not just descriptors.
+    [ticket:2535]
+
   - [feature] Added with_labels and
     reduce_columns keyword arguments to
     Query.subquery(), to provide two alternate
index 0254933c29d31c0b937daa83d1c0a86b7b845a06..a1d3d6954834f9e411b97bfe76d9b0167f792e11 100644 (file)
@@ -1630,18 +1630,12 @@ class Mapper(_InspectionAttr):
 
     def _is_userland_descriptor(self, obj):
         if isinstance(obj, (MapperProperty,
-                            attributes.QueryableAttribute)):
-            return False
-        elif not hasattr(obj, '__get__'):
+                            attributes.QueryableAttribute,
+                            instrumentation.ClassManager,
+                            expression.ColumnElement)):
             return False
         else:
-            obj = util.unbound_method_to_callable(obj)
-            if isinstance(
-                        obj.__get__(None, obj),
-                        attributes.QueryableAttribute
-                    ):
-                return False
-        return True
+            return True
 
     def _should_exclude(self, name, assigned_name, local, column):
         """determine whether a particular property should be implicitly
@@ -1652,8 +1646,8 @@ class Mapper(_InspectionAttr):
 
         """
 
-        # check for descriptors, either local or from
-        # an inherited class
+        # check for class-bound attributes and/or descriptors,
+        # either local or from an inherited class
         if local:
             if self.class_.__dict__.get(assigned_name, None) is not None \
                 and self._is_userland_descriptor(
index 89f20362d6eb87aa4cf5012a4c3aaf3e131b3a10..9e6e7fbbfef81b986e710c2079e80a899f014585 100644 (file)
@@ -12,7 +12,7 @@ from sqlalchemy.orm import mapper, relationship, backref, \
     column_property, composite, dynamic_loader, \
     comparable_property, Session
 from sqlalchemy.orm.persistence import _sort_states
-from test.lib.testing import eq_, AssertsCompiledSQL
+from test.lib.testing import eq_, AssertsCompiledSQL, is_
 from test.lib import fixtures
 from test.orm import _fixtures
 from test.lib.assertsql import CompiledSQL
@@ -3098,6 +3098,61 @@ class RequirementsTest(fixtures.MappedTest):
         h2.value = "Asdf"
         h2.value = "asdf asdf" # ding
 
+class IsUserlandTest(fixtures.MappedTest):
+    @classmethod
+    def define_tables(cls, metadata):
+        Table('foo', metadata,
+            Column('id', Integer, primary_key=True),
+            Column('someprop', Integer)
+        )
+
+    def _test(self, value, instancelevel=None):
+        class Foo(object):
+            someprop = value
+
+        m = mapper(Foo, self.tables.foo)
+        eq_(Foo.someprop, value)
+        f1 = Foo()
+        if instancelevel is not None:
+            eq_(f1.someprop, instancelevel)
+        else:
+            eq_(f1.someprop, value)
+        assert self.tables.foo.c.someprop not in m._columntoproperty
+
+    def _test_not(self, value):
+        class Foo(object):
+            someprop = value
+
+        m = mapper(Foo, self.tables.foo)
+        is_(Foo.someprop.property.columns[0], self.tables.foo.c.someprop)
+        assert self.tables.foo.c.someprop in m._columntoproperty
+
+    def test_string(self):
+        self._test("someprop")
+
+    def test_unicode(self):
+        self._test(u"someprop")
+
+    def test_int(self):
+        self._test(5)
+
+    def test_dict(self):
+        self._test({"bar": "bat"})
+
+    def test_set(self):
+        self._test(set([6]))
+
+    def test_column(self):
+        self._test_not(self.tables.foo.c.someprop)
+
+    def test_relationship(self):
+        self._test_not(relationship("bar"))
+
+    def test_descriptor(self):
+        def somefunc(self):
+            return "hi"
+        self._test(property(somefunc), "hi")
+
 class MagicNamesTest(fixtures.MappedTest):
 
     @classmethod
@@ -3157,7 +3212,6 @@ class MagicNamesTest(fixtures.MappedTest):
                       Column(reserved, Integer))
             class T(object):
                 pass
-
             assert_raises_message(
                 KeyError,
                 ('%r: requested attribute name conflicts with '