summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormike bayer <mike_mp@zzzcomputing.com>2018-11-30 18:12:02 +0000
committerGerrit Code Review <gerrit@bbpush.zzzcomputing.com>2018-11-30 18:12:02 +0000
commit7940e7dc9c40b80b103400bad6e9aac2ce8824bd (patch)
tree3cd5f617d4720323464f1c2bb43b5bf682b422bc
parentf916fa3b316c79df928277bdaf5070a77e056d30 (diff)
parent835444be72bb595b1ed3ee5458a86202813412a6 (diff)
downloadsqlalchemy-7940e7dc9c40b80b103400bad6e9aac2ce8824bd.tar.gz
Merge "Add __clause_element__ to ColumnProperty"
-rw-r--r--doc/build/changelog/unreleased_13/4372.rst10
-rw-r--r--doc/build/errors.rst44
-rw-r--r--lib/sqlalchemy/orm/properties.py7
-rw-r--r--test/ext/declarative/test_basic.py51
4 files changed, 110 insertions, 2 deletions
diff --git a/doc/build/changelog/unreleased_13/4372.rst b/doc/build/changelog/unreleased_13/4372.rst
new file mode 100644
index 000000000..60ecf114d
--- /dev/null
+++ b/doc/build/changelog/unreleased_13/4372.rst
@@ -0,0 +1,10 @@
+.. change::
+ :tags: bug, orm declarative
+ :tickets: 4372
+
+ Added a ``__clause_element__()`` method to :class:`.ColumnProperty` which
+ can allow the usage of a not-fully-declared column or deferred attribute in
+ a declarative mapped class slightly more friendly when it's used in a
+ constraint or other column-oriented scenario within the class declaration,
+ though this still can't work in open-ended expressions; prefer to call the
+ :attr:`.ColumnProperty.expression` attribute if receiving ``TypeError``.
diff --git a/doc/build/errors.rst b/doc/build/errors.rst
index a81f509a8..af2fe80e3 100644
--- a/doc/build/errors.rst
+++ b/doc/build/errors.rst
@@ -318,6 +318,50 @@ the database driver (DBAPI), not SQLAlchemy itself.
SQL Expression Language
=======================
+TypeError: <operator> not supported between instances of 'ColumnProperty' and <something>
+-----------------------------------------------------------------------------------------
+
+This often occurs when attempting to use a :func:`.column_property` or
+:func:`.deferred` object in the context of a SQL expression, usually within
+declarative such as::
+
+ class Bar(Base):
+ __tablename__ = 'bar'
+
+ id = Column(Integer, primary_key=True)
+ cprop = deferred(Column(Integer))
+
+ __table_args__ = (
+ CheckConstraint(cprop > 5),
+ )
+
+Above, the ``cprop`` attribute is used inline before it has been mapped,
+however this ``cprop`` attribute is not a :class:`.Column`,
+it's a :class:`.ColumnProperty`, which is an interim object and therefore
+does not have the full functionality of either the :class:`.Column` object
+or the :class:`.InstrmentedAttribute` object that will be mapped onto the
+``Bar`` class once the declarative process is complete.
+
+While the :class:`.ColumnProperty` does have a ``__clause_element__()`` method,
+which allows it to work in some column-oriented contexts, it can't work in an
+open-ended comparison context as illustrated above, since it has no Python
+``__eq__()`` method that would allow it to interpret the comparison to the
+number "5" as a SQL expression and not a regular Python comparison.
+
+The solution is to access the :class:`.Column` directly using the
+:attr:`.ColumnProperty.expression` attribute::
+
+ class Bar(Base):
+ __tablename__ = 'bar'
+
+ id = Column(Integer, primary_key=True)
+ cprop = deferred(Column(Integer))
+
+ __table_args__ = (
+ CheckConstraint(cprop.expression > 5),
+ )
+
+
.. _error_2afi:
This Compiled object is not bound to any Engine or Connection
diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py
index 360edc6e9..01ce7043a 100644
--- a/lib/sqlalchemy/orm/properties.py
+++ b/lib/sqlalchemy/orm/properties.py
@@ -163,6 +163,13 @@ class ColumnProperty(StrategizedProperty):
self.parent.class_manager,
strategies.LoadDeferredColumns(self.key), self.key)
+ def __clause_element__(self):
+ """Allow the ColumnProperty to work in expression before it is turned
+ into an instrumented attribute.
+ """
+
+ return self.expression
+
@property
def expression(self):
"""Return the primary column or expression for this ColumnProperty.
diff --git a/test/ext/declarative/test_basic.py b/test/ext/declarative/test_basic.py
index e8a9a140c..611598413 100644
--- a/test/ext/declarative/test_basic.py
+++ b/test/ext/declarative/test_basic.py
@@ -8,11 +8,11 @@ from sqlalchemy import exc
import sqlalchemy as sa
from sqlalchemy import testing, util
from sqlalchemy import MetaData, Integer, String, ForeignKey, \
- ForeignKeyConstraint, Index
+ ForeignKeyConstraint, Index, UniqueConstraint, CheckConstraint
from sqlalchemy.testing.schema import Table, Column
from sqlalchemy.orm import relationship, create_session, class_mapper, \
joinedload, configure_mappers, backref, clear_mappers, \
- column_property, composite, Session, properties
+ column_property, composite, Session, properties, deferred
from sqlalchemy.util import with_metaclass
from sqlalchemy.ext.declarative import declared_attr, synonym_for
from sqlalchemy.testing import fixtures, mock
@@ -264,6 +264,53 @@ class DeclarativeTest(DeclarativeTestBase):
go
)
+ def test_using_explicit_prop_in_schema_objects(self):
+ class Foo(Base):
+ __tablename__ = 'foo'
+
+ id = Column(Integer, primary_key=True)
+ cprop = column_property(Column(Integer))
+
+ __table_args__ = (
+ UniqueConstraint(cprop),
+ )
+ uq = [
+ c for c in Foo.__table__.constraints
+ if isinstance(c, UniqueConstraint)][0]
+ is_(uq.columns.cprop, Foo.__table__.c.cprop)
+
+ class Bar(Base):
+ __tablename__ = 'bar'
+
+ id = Column(Integer, primary_key=True)
+ cprop = deferred(Column(Integer))
+
+ __table_args__ = (
+ CheckConstraint(cprop > sa.func.foo()),
+ )
+ ck = [
+ c for c in Bar.__table__.constraints
+ if isinstance(c, CheckConstraint)][0]
+ is_(ck.columns.cprop, Bar.__table__.c.cprop)
+
+ if testing.requires.python3.enabled:
+ # test the existing failure case in case something changes
+ def go():
+ class Bat(Base):
+ __tablename__ = 'bat'
+
+ id = Column(Integer, primary_key=True)
+ cprop = deferred(Column(Integer))
+
+ # we still can't do an expression like
+ # "cprop > 5" because the column property isn't
+ # a full blown column
+
+ __table_args__ = (
+ CheckConstraint(cprop > 5),
+ )
+ assert_raises(TypeError, go)
+
def test_relationship_level_msg_for_invalid_callable(self):
class A(Base):
__tablename__ = 'a'