SQLAlchemy 1.2 now moves the minimum Python version to 2.7, no longer
supporting 2.6. New language features are expected to be merged
into the 1.2 series that were not supported in Python 2.6. For Python 3 support,
-SQLAlchemy is currnetly tested on versions 3.5 and 3.6.
+SQLAlchemy is currently tested on versions 3.5 and 3.6.
New Features and Improvements - ORM
:ref:`hybrid_bulk_update`
-.. _change_3896_validates:
-
-A @validates method receives all values on bulk-collection set before comparison
---------------------------------------------------------------------------------
-
-A method that uses ``@validates`` will now receive all members of a collection
-during a "bulk set" operation, before comparison is applied against the
-existing collection.
-
-Given a mapping as::
-
- class A(Base):
- __tablename__ = 'a'
- id = Column(Integer, primary_key=True)
- bs = relationship("B")
-
- @validates('bs')
- def convert_dict_to_b(self, key, value):
- return B(data=value['data'])
-
- class B(Base):
- __tablename__ = 'b'
- id = Column(Integer, primary_key=True)
- a_id = Column(ForeignKey('a.id'))
- data = Column(String)
-
-Above, we could use the validator as follows, to convert from an incoming
-dictionary to an instance of ``B`` upon collection append::
-
- a1 = A()
- a1.bs.append({"data": "b1"})
-
-However, a collection assignment would fail, since the ORM would assume
-incoming objects are already instances of ``B`` as it attempts to compare them
-to the existing members of the collection, before doing collection appends
-which actually invoke the validator. This would make it impossible for bulk
-set operations to accomodate non-ORM objects like dictionaries that needed
-up-front modification::
-
- a1 = A()
- a1.bs = [{"data": "b1"}]
-
-The new logic uses the new :meth:`.AttributeEvents.bulk_replace` event to ensure
-that all values are sent to the ``@validates`` function up front.
-
-As part of this change, this means that validators will now receive
-**all** members of a collection upon bulk set, not just the members that
-are new. Supposing a simple validator such as::
-
- class A(Base):
- # ...
-
- @validates('bs')
- def validate_b(self, key, value):
- assert value.data is not None
- return value
-
-Above, if we began with a collection as::
-
- a1 = A()
-
- b1, b2 = B(data="one"), B(data="two")
- a1.bs = [b1, b2]
-
-And then, replaced the collection with one that overlaps the first::
-
- b3 = B(data="three")
- a1.bs = [b2, b3]
-
-Previously, the second assignment would trigger the ``A.validate_b``
-method only once, for the ``b3`` object. The ``b2`` object would be seen
-as being already present in the collection and not validated. With the new
-behavior, both ``b2`` and ``b3`` are passed to ``A.validate_b`` before passing
-onto the collection. It is thus important that valiation methods employ
-idempotent behavior to suit such a case.
-
-.. seealso::
-
- :ref:`change_3896_event`
-
-:ticket:`3896`
-
-.. _change_3896_event:
-
-New bulk_replace event
-----------------------
-
-To suit the validation use case described in :ref:`change_3896_validates`,
-a new :meth:`.AttributeEvents.bulk_replace` method is added, which is
-called in conjunction with the :meth:`.AttributeEvents.append` and
-:meth:`.AttributeEvents.remove` events. "bulk_replace" is called before
-"append" and "remove" so that the collection can be modified ahead of comparison
-to the existing collection. After that, individual items
-are appended to a new target collection, firing off the "append"
-event for items new to the collection, as was the previous behavior.
-Below illustrates both "bulk_replace" and
-"append" at the same time, including that "append" will receive an object
-already handled by "bulk_replace" if collection assignment is used.
-A new symbol :attr:`~.attributes.OP_BULK_REPLACE` may be used to determine
-if this "append" event is the second part of a bulk replace::
-
- from sqlalchemy.orm.attributes import OP_BULK_REPLACE
-
- @event.listens_for(SomeObject.collection, "bulk_replace")
- def process_collection(target, values, initiator):
- values[:] = [_make_value(value) for value in values]
-
- @event.listens_for(SomeObject.collection, "append", retval=True)
- def process_collection(target, value, initiator):
- # make sure bulk_replace didn't already do it
- if initiator is None or initiator.op is not OP_BULK_REPLACE:
- return _make_value(value)
- else:
- return value
-
-
-:ticket:`3896`
-
-
.. _change_3911_3912:
Hybrid attributes support reuse among subclasses, redefinition of @getter
:ticket:`3912`
-New Features and Improvements - Core
-====================================
+.. _change_3896_event:
-.. _change_1546:
+New bulk_replace event
+----------------------
-Support for table, column comments, including DDL and reflection
-----------------------------------------------------------------
+To suit the validation use case described in :ref:`change_3896_validates`,
+a new :meth:`.AttributeEvents.bulk_replace` method is added, which is
+called in conjunction with the :meth:`.AttributeEvents.append` and
+:meth:`.AttributeEvents.remove` events. "bulk_replace" is called before
+"append" and "remove" so that the collection can be modified ahead of comparison
+to the existing collection. After that, individual items
+are appended to a new target collection, firing off the "append"
+event for items new to the collection, as was the previous behavior.
+Below illustrates both "bulk_replace" and
+"append" at the same time, including that "append" will receive an object
+already handled by "bulk_replace" if collection assignment is used.
+A new symbol :attr:`~.attributes.OP_BULK_REPLACE` may be used to determine
+if this "append" event is the second part of a bulk replace::
-The Core receives support for string comments associated with tables
-and columns. These are specified via the :paramref:`.Table.comment` and
-:paramref:`.Column.comment` arguments::
+ from sqlalchemy.orm.attributes import OP_BULK_REPLACE
- Table(
- 'my_table', metadata,
- Column('q', Integer, comment="the Q value"),
- comment="my Q table"
- )
+ @event.listens_for(SomeObject.collection, "bulk_replace")
+ def process_collection(target, values, initiator):
+ values[:] = [_make_value(value) for value in values]
-Above, DDL will be rendered appropriately upon table create to associate
-the above comments with the table/ column within the schema. When
-the above table is autoloaded or inspected with :meth:`.Inspector.get_columns`,
-the comments are included. The table comment is also available independently
-using the :meth:`.Inspector.get_table_comment` method.
+ @event.listens_for(SomeObject.collection, "append", retval=True)
+ def process_collection(target, value, initiator):
+ # make sure bulk_replace didn't already do it
+ if initiator is None or initiator.op is not OP_BULK_REPLACE:
+ return _make_value(value)
+ else:
+ return value
-Current backend support includes MySQL, Postgresql, and Oracle.
-:ticket:`1546`
+:ticket:`3896`
+
+
+
+New Features and Improvements - Core
+====================================
.. _change_3919:
:ticket:`3919`
+.. _change_3907:
+
+The IN / NOT IN operator's empty collection behavior is now configurable; default expression simplified
+-------------------------------------------------------------------------------------------------------
+
+An expression such as ``column.in_([])``, which is assumed to be false,
+now produces the expression ``1 != 1``
+by default, instead of ``column != column``. This will **change the result**
+of a query that is comparing a SQL expression or column that evaluates to
+NULL when compared to an empty set, producing a boolean value false or true
+(for NOT IN) rather than NULL. The warning that would emit under
+this condition is also removed. The old behavior is available using the
+:paramref:`.create_engine.empty_in_strategy` parameter to
+:func:`.create_engine`.
+
+In SQL, the IN and NOT IN operators do not support comparison to a
+collection of values that is explicitly empty; meaning, this syntax is
+illegal::
+
+ mycolumn IN ()
+
+To work around this, SQLAlchemy and other database libraries detect this
+condition and render an alternative expression that evaluates to false, or
+in the case of NOT IN, to true, based on the theory that "col IN ()" is always
+false since nothing is in "the empty set". Typically, in order to
+produce a false/true constant that is portable across databases and works
+in the context of the WHERE clause, a simple tautology such as ``1 != 1`` is
+used to evaluate to false and ``1 = 1`` to evaluate to true (a simple constant
+"0" or "1" often does not work as the target of a WHERE clause).
+
+SQLAlchemy in its early days began with this approach as well, but soon it
+was theorized that the SQL expression ``column IN ()`` would not evaluate to
+false if the "column" were NULL; instead, the expression would produce NULL,
+since "NULL" means "unknown", and comparisons to NULL in SQL usually produce
+NULL.
+
+To simulate this result, SQLAlchemy changed from using ``1 != 1`` to
+instead use th expression ``expr != expr`` for empty "IN" and ``expr = expr``
+for empty "NOT IN"; that is, instead of using a fixed value we use the
+actual left-hand side of the expression. If the left-hand side of
+the expression passed evaluates to NULL, then the comparison overall
+also gets the NULL result instead of false or true.
+
+Unfortunately, users eventually complained that this expression had a very
+severe performance impact on some query planners. At that point, a warning
+was added when an empty IN expression was encountered, favoring that SQLAlchemy
+continues to be "correct" and urging users to avoid code that generates empty
+IN predicates in general, since typically they can be safely omitted. However,
+this is of course burdensome in the case of queries that are built up dynamically
+from input variables, where an incoming set of values might be empty.
+
+In recent months, the original assumptions of this decision have been
+questioned. The notion that the expression "NULL IN ()" should return NULL was
+only theoretical, and could not be tested since databases don't support that
+syntax. However, as it turns out, you can in fact ask a relational database
+what value it would return for "NULL IN ()" by simulating the empty set as
+follows::
+
+ SELECT NULL IN (SELECT 1 WHERE 1 != 1)
+
+With the above test, we see that the databases themselves can't agree on
+the answer. Postgresql, considered by most to be the most "correct" database,
+returns False; because even though "NULL" represents "unknown", the "empty set"
+means nothing is present, including all unknown values. On the
+other hand, MySQL and MariaDB return NULL for the above expression, defaulting
+to the more common behavior of "all comparisons to NULL return NULL".
+
+SQLAlchemy's SQL architecture is more sophisticated than it was when this
+design decision was first made, so we can now allow either behavior to
+be invoked at SQL string compilation time. Previously, the conversion to a
+comparison expression were done at construction time, that is, the moment
+the :meth:`.ColumnOperators.in_` or :meth:`.ColumnOperators.notin_` operators were invoked.
+With the compilation-time behavior, the dialect itself can be instructed
+to invoke either approach, that is, the "static" ``1 != 1`` comparison or the
+"dynamic" ``expr != expr`` comparison. The default has been **changed**
+to be the "static" comparison, since this agrees with the behavior that
+Postgresql would have in any case and this is also what the vast majority
+of users prefer. This will **change the result** of a query that is comparing
+a null expression to the empty set, particularly one that is querying
+for the negation ``where(~null_expr.in_([]))``, since this now evaluates to true
+and not NULL.
+
+The behavior can now be controlled using the flag
+:paramref:`.create_engine.empty_in_strategy`, which defaults to the
+``"static"`` setting, but may also be set to ``"dynamic"`` or
+``"dynamic_warn"``, where the ``"dynamic_warn"`` setting is equivalent to the
+previous behavior of emitting ``expr != expr`` as well as a performance
+warning. However, it is anticipated that most users will appreciate the
+"static" default.
+
+:ticket:`3907`
+
+.. _change_1546:
+
+Support for SQL Comments on Table, Column, includes DDL, reflection
+-------------------------------------------------------------------
+
+The Core receives support for string comments associated with tables
+and columns. These are specified via the :paramref:`.Table.comment` and
+:paramref:`.Column.comment` arguments::
+
+ Table(
+ 'my_table', metadata,
+ Column('q', Integer, comment="the Q value"),
+ comment="my Q table"
+ )
+
+Above, DDL will be rendered appropriately upon table create to associate
+the above comments with the table/ column within the schema. When
+the above table is autoloaded or inspected with :meth:`.Inspector.get_columns`,
+the comments are included. The table comment is also available independently
+using the :meth:`.Inspector.get_table_comment` method.
+
+Current backend support includes MySQL, Postgresql, and Oracle.
+
+:ticket:`1546`
+
+
.. _change_2694:
New "autoescape" option for startswith(), endswith()
:ticket:`3913`
+.. _change_3896_validates:
+
+A @validates method receives all values on bulk-collection set before comparison
+--------------------------------------------------------------------------------
+
+A method that uses ``@validates`` will now receive all members of a collection
+during a "bulk set" operation, before comparison is applied against the
+existing collection.
+
+Given a mapping as::
+
+ class A(Base):
+ __tablename__ = 'a'
+ id = Column(Integer, primary_key=True)
+ bs = relationship("B")
+
+ @validates('bs')
+ def convert_dict_to_b(self, key, value):
+ return B(data=value['data'])
+
+ class B(Base):
+ __tablename__ = 'b'
+ id = Column(Integer, primary_key=True)
+ a_id = Column(ForeignKey('a.id'))
+ data = Column(String)
+
+Above, we could use the validator as follows, to convert from an incoming
+dictionary to an instance of ``B`` upon collection append::
+
+ a1 = A()
+ a1.bs.append({"data": "b1"})
+
+However, a collection assignment would fail, since the ORM would assume
+incoming objects are already instances of ``B`` as it attempts to compare them
+to the existing members of the collection, before doing collection appends
+which actually invoke the validator. This would make it impossible for bulk
+set operations to accomodate non-ORM objects like dictionaries that needed
+up-front modification::
+
+ a1 = A()
+ a1.bs = [{"data": "b1"}]
+
+The new logic uses the new :meth:`.AttributeEvents.bulk_replace` event to ensure
+that all values are sent to the ``@validates`` function up front.
+
+As part of this change, this means that validators will now receive
+**all** members of a collection upon bulk set, not just the members that
+are new. Supposing a simple validator such as::
+
+ class A(Base):
+ # ...
+
+ @validates('bs')
+ def validate_b(self, key, value):
+ assert value.data is not None
+ return value
+
+Above, if we began with a collection as::
+
+ a1 = A()
+
+ b1, b2 = B(data="one"), B(data="two")
+ a1.bs = [b1, b2]
+
+And then, replaced the collection with one that overlaps the first::
+
+ b3 = B(data="three")
+ a1.bs = [b2, b3]
+
+Previously, the second assignment would trigger the ``A.validate_b``
+method only once, for the ``b3`` object. The ``b2`` object would be seen
+as being already present in the collection and not validated. With the new
+behavior, both ``b2`` and ``b3`` are passed to ``A.validate_b`` before passing
+onto the collection. It is thus important that valiation methods employ
+idempotent behavior to suit such a case.
+
+.. seealso::
+
+ :ref:`change_3896_event`
+
+:ticket:`3896`
+
.. _change_3753:
Use flag_dirty() to mark an object as "dirty" without any attribute changing
Key Behavioral Changes - Core
=============================
-.. _change_3907:
-
-The IN / NOT IN operators render a simplified boolean expression with an empty collection
------------------------------------------------------------------------------------------
-
-An expression such as ``column.in_([])``, which is assumed to be false,
-now produces the expression ``1 != 1``
-by default, instead of ``column != column``. This will **change the result**
-of a query that is comparing a SQL expression or column that evaluates to
-NULL when compared to an empty set, producing a boolean value false or true
-(for NOT IN) rather than NULL. The warning that would emit under
-this condition is also removed. The old behavior is available using the
-:paramref:`.create_engine.empty_in_strategy` parameter to
-:func:`.create_engine`.
-
-In SQL, the IN and NOT IN operators do not support comparison to a
-collection of values that is explicitly empty; meaning, this syntax is
-illegal::
-
- mycolumn IN ()
-
-To work around this, SQLAlchemy and other database libraries detect this
-condition and render an alternative expression that evaluates to false, or
-in the case of NOT IN, to true, based on the theory that "col IN ()" is always
-false since nothing is in "the empty set". Typically, in order to
-produce a false/true constant that is portable across databases and works
-in the context of the WHERE clause, a simple tautology such as ``1 != 1`` is
-used to evaluate to false and ``1 = 1`` to evaluate to true (a simple constant
-"0" or "1" often does not work as the target of a WHERE clause).
-
-SQLAlchemy in its early days began with this approach as well, but soon it
-was theorized that the SQL expression ``column IN ()`` would not evaluate to
-false if the "column" were NULL; instead, the expression would produce NULL,
-since "NULL" means "unknown", and comparisons to NULL in SQL usually produce
-NULL.
-
-To simulate this result, SQLAlchemy changed from using ``1 != 1`` to
-instead use th expression ``expr != expr`` for empty "IN" and ``expr = expr``
-for empty "NOT IN"; that is, instead of using a fixed value we use the
-actual left-hand side of the expression. If the left-hand side of
-the expression passed evaluates to NULL, then the comparison overall
-also gets the NULL result instead of false or true.
-
-Unfortunately, users eventually complained that this expression had a very
-severe performance impact on some query planners. At that point, a warning
-was added when an empty IN expression was encountered, favoring that SQLAlchemy
-continues to be "correct" and urging users to avoid code that generates empty
-IN predicates in general, since typically they can be safely omitted. However,
-this is of course burdensome in the case of queries that are built up dynamically
-from input variables, where an incoming set of values might be empty.
-
-In recent months, the original assumptions of this decision have been
-questioned. The notion that the expression "NULL IN ()" should return NULL was
-only theoretical, and could not be tested since databases don't support that
-syntax. However, as it turns out, you can in fact ask a relational database
-what value it would return for "NULL IN ()" by simulating the empty set as
-follows::
-
- SELECT NULL IN (SELECT 1 WHERE 1 != 1)
-
-With the above test, we see that the databases themselves can't agree on
-the answer. Postgresql, considered by most to be the most "correct" database,
-returns False; because even though "NULL" represents "unknown", the "empty set"
-means nothing is present, including all unknown values. On the
-other hand, MySQL and MariaDB return NULL for the above expression, defaulting
-to the more common behavior of "all comparisons to NULL return NULL".
-
-SQLAlchemy's SQL architecture is more sophisticated than it was when this
-design decision was first made, so we can now allow either behavior to
-be invoked at SQL string compilation time. Previously, the conversion to a
-comparison expression were done at construction time, that is, the moment
-the :meth:`.ColumnOperators.in_` or :meth:`.ColumnOperators.notin_` operators were invoked.
-With the compilation-time behavior, the dialect itself can be instructed
-to invoke either approach, that is, the "static" ``1 != 1`` comparison or the
-"dynamic" ``expr != expr`` comparison. The default has been **changed**
-to be the "static" comparison, since this agrees with the behavior that
-Postgresql would have in any case and this is also what the vast majority
-of users prefer. This will **change the result** of a query that is comparing
-a null expression to the empty set, particularly one that is querying
-for the negation ``where(~null_expr.in_([]))``, since this now evaluates to true
-and not NULL.
-
-The behavior can now be controlled using the flag
-:paramref:`.create_engine.empty_in_strategy`, which defaults to the
-``"static"`` setting, but may also be set to ``"dynamic"`` or
-``"dynamic_warn"``, where the ``"dynamic_warn"`` setting is equivalent to the
-previous behavior of emitting ``expr != expr`` as well as a performance
-warning. However, it is anticipated that most users will appreciate the
-"static" default.
-
-:ticket:`3907`
.. _change_3785: