From 79f26dbff98a3e5625c94fbea5bfb35a2d7016cf Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sat, 4 Aug 2012 18:11:18 -0400 Subject: [PATCH] - [feature] Can now provide class-bound attributes that override columns which are of any non-ORM type, not just descriptors. [ticket:2535] --- CHANGES | 5 ++++ lib/sqlalchemy/orm/mapper.py | 18 ++++------- test/orm/test_mapper.py | 58 ++++++++++++++++++++++++++++++++++-- 3 files changed, 67 insertions(+), 14 deletions(-) diff --git a/CHANGES b/CHANGES index c654fef43f..b49c76d793 100644 --- 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 diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 0254933c29..a1d3d69548 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -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( diff --git a/test/orm/test_mapper.py b/test/orm/test_mapper.py index 89f20362d6..9e6e7fbbfe 100644 --- a/test/orm/test_mapper.py +++ b/test/orm/test_mapper.py @@ -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 ' -- 2.47.3