1=============================
2What's New in SQLAlchemy 0.8?
3=============================
4
5.. admonition:: About this Document
6
7    This document describes changes between SQLAlchemy version 0.7,
8    undergoing maintenance releases as of October, 2012,
9    and SQLAlchemy version 0.8, which is expected for release
10    in early 2013.
11
12    Document date: October 25, 2012
13    Updated: March 9, 2013
14
15Introduction
16============
17
18This guide introduces what's new in SQLAlchemy version 0.8,
19and also documents changes which affect users migrating
20their applications from the 0.7 series of SQLAlchemy to 0.8.
21
22SQLAlchemy releases are closing in on 1.0, and each new
23version since 0.5 features fewer major usage changes.   Most
24applications that are settled into modern 0.7 patterns
25should be movable to 0.8 with no changes. Applications that
26use 0.6 and even 0.5 patterns should be directly migratable
27to 0.8 as well, though larger applications may want to test
28with each interim version.
29
30Platform Support
31================
32
33Targeting Python 2.5 and Up Now
34-------------------------------
35
36SQLAlchemy 0.8 will target Python 2.5 and forward;
37compatibility for Python 2.4 is being dropped.
38
39The internals will be able to make usage of Python ternaries
40(that is, ``x if y else z``) which will improve things
41versus the usage of ``y and x or z``, which naturally has
42been the source of some bugs, as well as context managers
43(that is, ``with:``) and perhaps in some cases
44``try:/except:/else:`` blocks which will help with code
45readability.
46
47SQLAlchemy will eventually drop 2.5 support as well - when
482.6 is reached as the baseline, SQLAlchemy will move to use
492.6/3.3 in-place compatibility, removing the usage of the
50``2to3`` tool and maintaining a source base that works with
51Python 2 and 3 at the same time.
52
53New ORM Features
54================
55
56.. _feature_relationship_08:
57
58Rewritten :func:`.relationship` mechanics
59-----------------------------------------
60
610.8 features a much improved and capable system regarding
62how :func:`.relationship` determines how to join between two
63entities.  The new system includes these features:
64
65* The ``primaryjoin`` argument is **no longer needed** when
66  constructing a :func:`.relationship`   against a class that
67  has multiple foreign key paths to the target.  Only the
68  ``foreign_keys``   argument is needed to specify those
69  columns which should be included:
70
71  ::
72
73
74        class Parent(Base):
75            __tablename__ = 'parent'
76            id = Column(Integer, primary_key=True)
77            child_id_one = Column(Integer, ForeignKey('child.id'))
78            child_id_two = Column(Integer, ForeignKey('child.id'))
79
80            child_one = relationship("Child", foreign_keys=child_id_one)
81            child_two = relationship("Child", foreign_keys=child_id_two)
82
83        class Child(Base):
84            __tablename__ = 'child'
85            id = Column(Integer, primary_key=True)
86
87* relationships against self-referential, composite foreign
88  keys where **a column points to itself**   are now
89  supported.   The canonical case is as follows:
90
91  ::
92
93        class Folder(Base):
94            __tablename__ = 'folder'
95            __table_args__ = (
96              ForeignKeyConstraint(
97                  ['account_id', 'parent_id'],
98                  ['folder.account_id', 'folder.folder_id']),
99            )
100
101            account_id = Column(Integer, primary_key=True)
102            folder_id = Column(Integer, primary_key=True)
103            parent_id = Column(Integer)
104            name = Column(String)
105
106            parent_folder = relationship("Folder",
107                                backref="child_folders",
108                                remote_side=[account_id, folder_id]
109                          )
110
111  Above, the ``Folder`` refers to its parent ``Folder``
112  joining from ``account_id`` to itself, and ``parent_id``
113  to ``folder_id``.  When SQLAlchemy constructs an auto-
114  join, no longer can it assume all columns on the "remote"
115  side are aliased, and all columns on the "local" side are
116  not - the ``account_id`` column is **on both sides**.   So
117  the internal relationship mechanics were totally rewritten
118  to support an entirely different system whereby two copies
119  of ``account_id`` are generated, each containing different
120  *annotations* to determine their role within the
121  statement.  Note the join condition within a basic eager
122  load:
123
124  ::
125
126        SELECT
127            folder.account_id AS folder_account_id,
128            folder.folder_id AS folder_folder_id,
129            folder.parent_id AS folder_parent_id,
130            folder.name AS folder_name,
131            folder_1.account_id AS folder_1_account_id,
132            folder_1.folder_id AS folder_1_folder_id,
133            folder_1.parent_id AS folder_1_parent_id,
134            folder_1.name AS folder_1_name
135        FROM folder
136            LEFT OUTER JOIN folder AS folder_1
137            ON
138                folder_1.account_id = folder.account_id
139                AND folder.folder_id = folder_1.parent_id
140
141        WHERE folder.folder_id = ? AND folder.account_id = ?
142
143* Previously difficult custom join conditions, like those involving
144  functions and/or CASTing of types, will now function as
145  expected in most cases::
146
147    class HostEntry(Base):
148        __tablename__ = 'host_entry'
149
150        id = Column(Integer, primary_key=True)
151        ip_address = Column(INET)
152        content = Column(String(50))
153
154        # relationship() using explicit foreign_keys, remote_side
155        parent_host = relationship("HostEntry",
156                            primaryjoin=ip_address == cast(content, INET),
157                            foreign_keys=content,
158                            remote_side=ip_address
159                        )
160
161  The new :func:`.relationship` mechanics make use of a
162  SQLAlchemy concept known as :term:`annotations`.  These annotations
163  are also available to application code explicitly via
164  the :func:`.foreign` and :func:`.remote` functions, either
165  as a means to improve readability for advanced configurations
166  or to directly inject an exact configuration, bypassing
167  the usual join-inspection heuristics::
168
169    from sqlalchemy.orm import foreign, remote
170
171    class HostEntry(Base):
172        __tablename__ = 'host_entry'
173
174        id = Column(Integer, primary_key=True)
175        ip_address = Column(INET)
176        content = Column(String(50))
177
178        # relationship() using explicit foreign() and remote() annotations
179        # in lieu of separate arguments
180        parent_host = relationship("HostEntry",
181                            primaryjoin=remote(ip_address) == \
182                                    cast(foreign(content), INET),
183                        )
184
185
186.. seealso::
187
188    :ref:`relationship_configure_joins` - a newly revised section on :func:`.relationship`
189    detailing the latest techniques for customizing related attributes and collection
190    access.
191
192:ticket:`1401` :ticket:`610`
193
194.. _feature_orminspection_08:
195
196New Class/Object Inspection System
197----------------------------------
198
199Lots of SQLAlchemy users are writing systems that require
200the ability to inspect the attributes of a mapped class,
201including being able to get at the primary key columns,
202object relationships, plain attributes, and so forth,
203typically for the purpose of building data-marshalling
204systems, like JSON/XML conversion schemes and of course form
205libraries galore.
206
207Originally, the :class:`.Table` and :class:`.Column` model were the
208original inspection points, which have a well-documented
209system.  While SQLAlchemy ORM models are also fully
210introspectable, this has never been a fully stable and
211supported feature, and users tended to not have a clear idea
212how to get at this information.
213
2140.8 now provides a consistent, stable and fully
215documented API for this purpose, including an inspection
216system which works on mapped classes, instances, attributes,
217and other Core and ORM constructs.  The entrypoint to this
218system is the core-level :func:`.inspect` function.
219In most cases, the object being inspected
220is one already part of SQLAlchemy's system,
221such as :class:`.Mapper`, :class:`.InstanceState`,
222:class:`.Inspector`.  In some cases, new objects have been
223added with the job of providing the inspection API in
224certain contexts, such as :class:`.AliasedInsp` and
225:class:`.AttributeState`.
226
227A walkthrough of some key capabilities follows::
228
229    >>> class User(Base):
230    ...     __tablename__ = 'user'
231    ...     id = Column(Integer, primary_key=True)
232    ...     name = Column(String)
233    ...     name_syn = synonym(name)
234    ...     addresses = relationship("Address")
235    ...
236
237    >>> # universal entry point is inspect()
238    >>> b = inspect(User)
239
240    >>> # b in this case is the Mapper
241    >>> b
242    <Mapper at 0x101521950; User>
243
244    >>> # Column namespace
245    >>> b.columns.id
246    Column('id', Integer(), table=<user>, primary_key=True, nullable=False)
247
248    >>> # mapper's perspective of the primary key
249    >>> b.primary_key
250    (Column('id', Integer(), table=<user>, primary_key=True, nullable=False),)
251
252    >>> # MapperProperties available from .attrs
253    >>> b.attrs.keys()
254    ['name_syn', 'addresses', 'id', 'name']
255
256    >>> # .column_attrs, .relationships, etc. filter this collection
257    >>> b.column_attrs.keys()
258    ['id', 'name']
259
260    >>> list(b.relationships)
261    [<sqlalchemy.orm.properties.RelationshipProperty object at 0x1015212d0>]
262
263    >>> # they are also namespaces
264    >>> b.column_attrs.id
265    <sqlalchemy.orm.properties.ColumnProperty object at 0x101525090>
266
267    >>> b.relationships.addresses
268    <sqlalchemy.orm.properties.RelationshipProperty object at 0x1015212d0>
269
270    >>> # point inspect() at a mapped, class level attribute,
271    >>> # returns the attribute itself
272    >>> b = inspect(User.addresses)
273    >>> b
274    <sqlalchemy.orm.attributes.InstrumentedAttribute object at 0x101521fd0>
275
276    >>> # From here we can get the mapper:
277    >>> b.mapper
278    <Mapper at 0x101525810; Address>
279
280    >>> # the parent inspector, in this case a mapper
281    >>> b.parent
282    <Mapper at 0x101521950; User>
283
284    >>> # an expression
285    >>> print(b.expression)
286    "user".id = address.user_id
287
288    >>> # inspect works on instances
289    >>> u1 = User(id=3, name='x')
290    >>> b = inspect(u1)
291
292    >>> # it returns the InstanceState
293    >>> b
294    <sqlalchemy.orm.state.InstanceState object at 0x10152bed0>
295
296    >>> # similar attrs accessor refers to the
297    >>> b.attrs.keys()
298    ['id', 'name_syn', 'addresses', 'name']
299
300    >>> # attribute interface - from attrs, you get a state object
301    >>> b.attrs.id
302    <sqlalchemy.orm.state.AttributeState object at 0x10152bf90>
303
304    >>> # this object can give you, current value...
305    >>> b.attrs.id.value
306    3
307
308    >>> # ... current history
309    >>> b.attrs.id.history
310    History(added=[3], unchanged=(), deleted=())
311
312    >>> # InstanceState can also provide session state information
313    >>> # lets assume the object is persistent
314    >>> s = Session()
315    >>> s.add(u1)
316    >>> s.commit()
317
318    >>> # now we can get primary key identity, always
319    >>> # works in query.get()
320    >>> b.identity
321    (3,)
322
323    >>> # the mapper level key
324    >>> b.identity_key
325    (<class '__main__.User'>, (3,))
326
327    >>> # state within the session
328    >>> b.persistent, b.transient, b.deleted, b.detached
329    (True, False, False, False)
330
331    >>> # owning session
332    >>> b.session
333    <sqlalchemy.orm.session.Session object at 0x101701150>
334
335.. seealso::
336
337    :ref:`core_inspection_toplevel`
338
339:ticket:`2208`
340
341New with_polymorphic() feature, can be used anywhere
342----------------------------------------------------
343
344The :meth:`.Query.with_polymorphic` method allows the user to
345specify which tables should be present when querying against
346a joined-table entity.   Unfortunately the method is awkward
347and only applies to the first entity in the list, and
348otherwise has awkward behaviors both in usage as well as
349within the internals.  A new enhancement to the
350:func:`.aliased` construct has been added called
351:func:`.with_polymorphic` which allows any entity to be
352"aliased" into a "polymorphic" version of itself, freely
353usable anywhere:
354
355::
356
357    from sqlalchemy.orm import with_polymorphic
358    palias = with_polymorphic(Person, [Engineer, Manager])
359    session.query(Company).\
360                join(palias, Company.employees).\
361                filter(or_(Engineer.language=='java', Manager.hair=='pointy'))
362
363.. seealso::
364
365    :ref:`with_polymorphic` - newly updated documentation for polymorphic
366    loading control.
367
368:ticket:`2333`
369
370of_type() works with alias(), with_polymorphic(), any(), has(), joinedload(), subqueryload(), contains_eager()
371--------------------------------------------------------------------------------------------------------------
372
373The :meth:`.PropComparator.of_type` method is used to specify
374a specific subtype to use when constructing SQL expressions along
375a :func:`.relationship` that has a :term:`polymorphic` mapping as its target.
376This method can now be used to target *any number* of target subtypes,
377by combining it with the new :func:`.with_polymorphic` function::
378
379    # use eager loading in conjunction with with_polymorphic targets
380    Job_P = with_polymorphic(Job, [SubJob, ExtraJob], aliased=True)
381    q = s.query(DataContainer).\
382                join(DataContainer.jobs.of_type(Job_P)).\
383                    options(contains_eager(DataContainer.jobs.of_type(Job_P)))
384
385The method now works equally well in most places a regular relationship
386attribute is accepted, including with loader functions like
387:func:`.joinedload`, :func:`.subqueryload`, :func:`.contains_eager`,
388and comparison methods like :meth:`.PropComparator.any`
389and :meth:`.PropComparator.has`::
390
391    # use eager loading in conjunction with with_polymorphic targets
392    Job_P = with_polymorphic(Job, [SubJob, ExtraJob], aliased=True)
393    q = s.query(DataContainer).\
394                join(DataContainer.jobs.of_type(Job_P)).\
395                    options(contains_eager(DataContainer.jobs.of_type(Job_P)))
396
397    # pass subclasses to eager loads (implicitly applies with_polymorphic)
398    q = s.query(ParentThing).\
399                    options(
400                        joinedload_all(
401                            ParentThing.container,
402                            DataContainer.jobs.of_type(SubJob)
403                    ))
404
405    # control self-referential aliasing with any()/has()
406    Job_A = aliased(Job)
407    q = s.query(Job).join(DataContainer.jobs).\
408                    filter(
409                        DataContainer.jobs.of_type(Job_A).\
410                            any(and_(Job_A.id < Job.id, Job_A.type=='fred')
411                        )
412                    )
413
414.. seealso::
415
416    :ref:`of_type`
417
418:ticket:`2438` :ticket:`1106`
419
420Events Can Be Applied to Unmapped Superclasses
421----------------------------------------------
422
423Mapper and instance events can now be associated with an unmapped
424superclass, where those events will be propagated to subclasses
425as those subclasses are mapped.   The ``propagate=True`` flag
426should be used.  This feature allows events to be associated
427with a declarative base class::
428
429    from sqlalchemy.ext.declarative import declarative_base
430
431    Base = declarative_base()
432
433    @event.listens_for("load", Base, propagate=True)
434    def on_load(target, context):
435        print("New instance loaded:", target)
436
437    # on_load() will be applied to SomeClass
438    class SomeClass(Base):
439        __tablename__ = 'sometable'
440
441        # ...
442
443:ticket:`2585`
444
445Declarative Distinguishes Between Modules/Packages
446--------------------------------------------------
447
448A key feature of Declarative is the ability to refer
449to other mapped classes using their string name.   The
450registry of class names is now sensitive to the owning
451module and package of a given class.   The classes
452can be referred to via dotted name in expressions::
453
454    class Snack(Base):
455        # ...
456
457        peanuts = relationship("nuts.Peanut",
458                primaryjoin="nuts.Peanut.snack_id == Snack.id")
459
460The resolution allows that any full or partial
461disambiguating package name can be used.   If the
462path to a particular class is still ambiguous,
463an error is raised.
464
465:ticket:`2338`
466
467
468New DeferredReflection Feature in Declarative
469---------------------------------------------
470
471The "deferred reflection" example has been moved to a
472supported feature within Declarative.  This feature allows
473the construction of declarative mapped classes with only
474placeholder ``Table`` metadata, until a ``prepare()`` step
475is called, given an ``Engine`` with which to reflect fully
476all tables and establish actual mappings.   The system
477supports overriding of columns, single and joined
478inheritance, as well as distinct bases-per-engine. A full
479declarative configuration can now be created against an
480existing table that is assembled upon engine creation time
481in one step:
482
483::
484
485    class ReflectedOne(DeferredReflection, Base):
486        __abstract__ = True
487
488    class ReflectedTwo(DeferredReflection, Base):
489        __abstract__ = True
490
491    class MyClass(ReflectedOne):
492        __tablename__ = 'mytable'
493
494    class MyOtherClass(ReflectedOne):
495        __tablename__ = 'myothertable'
496
497    class YetAnotherClass(ReflectedTwo):
498        __tablename__ = 'yetanothertable'
499
500    ReflectedOne.prepare(engine_one)
501    ReflectedTwo.prepare(engine_two)
502
503.. seealso::
504
505    :class:`.DeferredReflection`
506
507:ticket:`2485`
508
509ORM Classes Now Accepted by Core Constructs
510-------------------------------------------
511
512While the SQL expressions used with :meth:`.Query.filter`,
513such as ``User.id == 5``, have always been compatible for
514use with core constructs such as :func:`.select`, the mapped
515class itself would not be recognized when passed to :func:`.select`,
516:meth:`.Select.select_from`, or :meth:`.Select.correlate`.
517A new SQL registration system allows a mapped class to be
518accepted as a FROM clause within the core::
519
520    from sqlalchemy import select
521
522    stmt = select([User]).where(User.id == 5)
523
524Above, the mapped ``User`` class will expand into
525the :class:`.Table` to which ``User`` is mapped.
526
527:ticket:`2245`
528
529Query.update() supports UPDATE..FROM
530------------------------------------
531
532The new UPDATE..FROM mechanics work in query.update().
533Below, we emit an UPDATE against ``SomeEntity``, adding
534a FROM clause (or equivalent, depending on backend)
535against ``SomeOtherEntity``::
536
537    query(SomeEntity).\
538        filter(SomeEntity.id==SomeOtherEntity.id).\
539        filter(SomeOtherEntity.foo=='bar').\
540        update({"data":"x"})
541
542In particular, updates to joined-inheritance
543entities are supported, provided the target of the UPDATE is local to the
544table being filtered on, or if the parent and child tables
545are mixed, they are joined explicitly in the query.  Below,
546given ``Engineer`` as a joined subclass of ``Person``:
547
548::
549
550    query(Engineer).\
551            filter(Person.id==Engineer.id).\
552            filter(Person.name=='dilbert').\
553            update({"engineer_data":"java"})
554
555would produce:
556
557::
558
559    UPDATE engineer SET engineer_data='java' FROM person
560    WHERE person.id=engineer.id AND person.name='dilbert'
561
562:ticket:`2365`
563
564rollback() will only roll back "dirty" objects from a begin_nested()
565--------------------------------------------------------------------
566
567A behavioral change that should improve efficiency for those
568users using SAVEPOINT via ``Session.begin_nested()`` - upon
569``rollback()``, only those objects that were made dirty
570since the last flush will be expired, the rest of the
571``Session`` remains intact.  This because a ROLLBACK to a
572SAVEPOINT does not terminate the containing transaction's
573isolation, so no expiry is needed except for those changes
574that were not flushed in the current transaction.
575
576:ticket:`2452`
577
578Caching Example now uses dogpile.cache
579--------------------------------------
580
581The caching example now uses `dogpile.cache <https://dogpilecache.readthedocs.io/>`_.
582Dogpile.cache is a rewrite of the caching portion
583of Beaker, featuring vastly simpler and faster operation,
584as well as support for distributed locking.
585
586Note that the SQLAlchemy APIs used by the Dogpile example as well
587as the previous Beaker example have changed slightly, in particular
588this change is needed as illustrated in the Beaker example::
589
590    --- examples/beaker_caching/caching_query.py
591    +++ examples/beaker_caching/caching_query.py
592    @@ -222,7 +222,8 @@
593
594             """
595             if query._current_path:
596    -            mapper, key = query._current_path[-2:]
597    +            mapper, prop = query._current_path[-2:]
598    +            key = prop.key
599
600                 for cls in mapper.class_.__mro__:
601                     if (cls, key) in self._relationship_options:
602
603.. seealso::
604
605    :mod:`dogpile_caching`
606
607:ticket:`2589`
608
609New Core Features
610=================
611
612Fully extensible, type-level operator support in Core
613-----------------------------------------------------
614
615The Core has to date never had any system of adding support
616for new SQL operators to Column and other expression
617constructs, other than the :meth:`.ColumnOperators.op` method
618which is "just enough" to make things work. There has also
619never been any system in place for Core which allows the
620behavior of existing operators to be overridden.   Up until
621now, the only way operators could be flexibly redefined was
622in the ORM layer, using :func:`.column_property` given a
623``comparator_factory`` argument.   Third party libraries
624like GeoAlchemy therefore were forced to be ORM-centric and
625rely upon an array of hacks to apply new opertions as well
626as to get them to propagate correctly.
627
628The new operator system in Core adds the one hook that's
629been missing all along, which is to associate new and
630overridden operators with *types*.   Since after all, it's
631not really a column, CAST operator, or SQL function that
632really drives what kinds of operations are present, it's the
633*type* of the expression.   The implementation details are
634minimal - only a few extra methods are added to the core
635:class:`.ColumnElement` type so that it consults its
636:class:`.TypeEngine` object for an optional set of operators.
637New or revised operations can be associated with any type,
638either via subclassing of an existing type, by using
639:class:`.TypeDecorator`, or "globally across-the-board" by
640attaching a new :class:`.TypeEngine.Comparator` object to an existing type
641class.
642
643For example, to add logarithm support to :class:`.Numeric` types:
644
645::
646
647
648    from sqlalchemy.types import Numeric
649    from sqlalchemy.sql import func
650
651    class CustomNumeric(Numeric):
652        class comparator_factory(Numeric.Comparator):
653            def log(self, other):
654                return func.log(self.expr, other)
655
656The new type is usable like any other type:
657
658::
659
660
661    data = Table('data', metadata,
662              Column('id', Integer, primary_key=True),
663              Column('x', CustomNumeric(10, 5)),
664              Column('y', CustomNumeric(10, 5))
665         )
666
667    stmt = select([data.c.x.log(data.c.y)]).where(data.c.x.log(2) < value)
668    print(conn.execute(stmt).fetchall())
669
670
671New features which have come from this immediately include
672support for PostgreSQL's HSTORE type, as well as new
673operations associated with PostgreSQL's ARRAY
674type.    It also paves the way for existing types to acquire
675lots more operators that are specific to those types, such
676as more string, integer and date operators.
677
678.. seealso::
679
680    :ref:`types_operators`
681
682    :class:`.HSTORE`
683
684:ticket:`2547`
685
686.. _feature_2623:
687
688Multiple-VALUES support for Insert
689----------------------------------
690
691The :meth:`.Insert.values` method now supports a list of dictionaries,
692which will render a multi-VALUES statement such as
693``VALUES (<row1>), (<row2>), ...``.  This is only relevant to backends which
694support this syntax, including PostgreSQL, SQLite, and MySQL.  It is
695not the same thing as the usual ``executemany()`` style of INSERT which
696remains unchanged::
697
698    users.insert().values([
699                        {"name": "some name"},
700                        {"name": "some other name"},
701                        {"name": "yet another name"},
702                    ])
703
704.. seealso::
705
706    :meth:`.Insert.values`
707
708:ticket:`2623`
709
710Type Expressions
711----------------
712
713SQL expressions can now be associated with types.  Historically,
714:class:`.TypeEngine` has always allowed Python-side functions which
715receive both bound parameters as well as result row values, passing
716them through a Python side conversion function on the way to/back from
717the database.   The new feature allows similar
718functionality, except on the database side::
719
720    from sqlalchemy.types import String
721    from sqlalchemy import func, Table, Column, MetaData
722
723    class LowerString(String):
724        def bind_expression(self, bindvalue):
725            return func.lower(bindvalue)
726
727        def column_expression(self, col):
728            return func.lower(col)
729
730    metadata = MetaData()
731    test_table = Table(
732            'test_table',
733            metadata,
734            Column('data', LowerString)
735    )
736
737Above, the ``LowerString`` type defines a SQL expression that will be emitted
738whenever the ``test_table.c.data`` column is rendered in the columns
739clause of a SELECT statement::
740
741    >>> print(select([test_table]).where(test_table.c.data == 'HI'))
742    SELECT lower(test_table.data) AS data
743    FROM test_table
744    WHERE test_table.data = lower(:data_1)
745
746This feature is also used heavily by the new release of GeoAlchemy,
747to embed PostGIS expressions inline in SQL based on type rules.
748
749.. seealso::
750
751    :ref:`types_sql_value_processing`
752
753:ticket:`1534`
754
755Core Inspection System
756----------------------
757
758The :func:`.inspect` function introduced in :ref:`feature_orminspection_08`
759also applies to the core.  Applied to an :class:`.Engine` it produces
760an :class:`.Inspector` object::
761
762    from sqlalchemy import inspect
763    from sqlalchemy import create_engine
764
765    engine = create_engine("postgresql://scott:tiger@localhost/test")
766    insp = inspect(engine)
767    print(insp.get_table_names())
768
769It can also be applied to any :class:`.ClauseElement`, which returns
770the :class:`.ClauseElement` itself, such as :class:`.Table`, :class:`.Column`,
771:class:`.Select`, etc.   This allows it to work fluently between Core
772and ORM constructs.
773
774
775New Method :meth:`.Select.correlate_except`
776-------------------------------------------
777
778:func:`.select` now has a method :meth:`.Select.correlate_except`
779which specifies "correlate on all FROM clauses except those
780specified".  It can be used for mapping scenarios where
781a related subquery should correlate normally, except
782against a particular target selectable::
783
784    class SnortEvent(Base):
785        __tablename__ = "event"
786
787        id = Column(Integer, primary_key=True)
788        signature = Column(Integer, ForeignKey("signature.id"))
789
790        signatures = relationship("Signature", lazy=False)
791
792    class Signature(Base):
793        __tablename__ = "signature"
794
795        id = Column(Integer, primary_key=True)
796
797        sig_count = column_property(
798                        select([func.count('*')]).\
799                            where(SnortEvent.signature == id).
800                            correlate_except(SnortEvent)
801                    )
802
803.. seealso::
804
805    :meth:`.Select.correlate_except`
806
807PostgreSQL HSTORE type
808----------------------
809
810Support for PostgreSQL's ``HSTORE`` type is now available as
811:class:`.postgresql.HSTORE`.   This type makes great usage
812of the new operator system to provide a full range of operators
813for HSTORE types, including index access, concatenation,
814and containment methods such as
815:meth:`~.HSTORE.comparator_factory.has_key`,
816:meth:`~.HSTORE.comparator_factory.has_any`, and
817:meth:`~.HSTORE.comparator_factory.matrix`::
818
819    from sqlalchemy.dialects.postgresql import HSTORE
820
821    data = Table('data_table', metadata,
822            Column('id', Integer, primary_key=True),
823            Column('hstore_data', HSTORE)
824        )
825
826    engine.execute(
827        select([data.c.hstore_data['some_key']])
828    ).scalar()
829
830    engine.execute(
831        select([data.c.hstore_data.matrix()])
832    ).scalar()
833
834
835.. seealso::
836
837    :class:`.postgresql.HSTORE`
838
839    :class:`.postgresql.hstore`
840
841:ticket:`2606`
842
843Enhanced PostgreSQL ARRAY type
844------------------------------
845
846The :class:`.postgresql.ARRAY` type will accept an optional
847"dimension" argument, pinning it to a fixed number of
848dimensions and greatly improving efficiency when retrieving
849results:
850
851::
852
853    # old way, still works since PG supports N-dimensions per row:
854    Column("my_array", postgresql.ARRAY(Integer))
855
856    # new way, will render ARRAY with correct number of [] in DDL,
857    # will process binds and results more efficiently as we don't need
858    # to guess how many levels deep to go
859    Column("my_array", postgresql.ARRAY(Integer, dimensions=2))
860
861The type also introduces new operators, using the new type-specific
862operator framework.  New operations include indexed access::
863
864    result = conn.execute(
865        select([mytable.c.arraycol[2]])
866    )
867
868slice access in SELECT::
869
870    result = conn.execute(
871        select([mytable.c.arraycol[2:4]])
872    )
873
874slice updates in UPDATE::
875
876    conn.execute(
877        mytable.update().values({mytable.c.arraycol[2:3]: [7, 8]})
878    )
879
880freestanding array literals::
881
882    >>> from sqlalchemy.dialects import postgresql
883    >>> conn.scalar(
884    ...    select([
885    ...        postgresql.array([1, 2]) + postgresql.array([3, 4, 5])
886    ...    ])
887    ...  )
888    [1, 2, 3, 4, 5]
889
890array concatenation, where below, the right side ``[4, 5, 6]`` is coerced into an array literal::
891
892    select([mytable.c.arraycol + [4, 5, 6]])
893
894.. seealso::
895
896    :class:`.postgresql.ARRAY`
897
898    :class:`.postgresql.array`
899
900:ticket:`2441`
901
902New, configurable DATE, TIME types for SQLite
903---------------------------------------------
904
905SQLite has no built-in DATE, TIME, or DATETIME types, and
906instead provides some support for storage of date and time
907values either as strings or integers.   The date and time
908types for SQLite are enhanced in 0.8 to be much more
909configurable as to the specific format, including that the
910"microseconds" portion is optional, as well as pretty much
911everything else.
912
913::
914
915    Column('sometimestamp', sqlite.DATETIME(truncate_microseconds=True))
916    Column('sometimestamp', sqlite.DATETIME(
917                        storage_format=(
918                                    "%(year)04d%(month)02d%(day)02d"
919                                    "%(hour)02d%(minute)02d%(second)02d%(microsecond)06d"
920                        ),
921                        regexp="(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{6})"
922                        )
923                )
924    Column('somedate', sqlite.DATE(
925                        storage_format="%(month)02d/%(day)02d/%(year)04d",
926                        regexp="(?P<month>\d+)/(?P<day>\d+)/(?P<year>\d+)",
927                    )
928                )
929
930Huge thanks to Nate Dub for the sprinting on this at Pycon 2012.
931
932.. seealso::
933
934    :class:`.sqlite.DATETIME`
935
936    :class:`.sqlite.DATE`
937
938    :class:`.sqlite.TIME`
939
940:ticket:`2363`
941
942"COLLATE" supported across all dialects; in particular MySQL, PostgreSQL, SQLite
943--------------------------------------------------------------------------------
944
945The "collate" keyword, long accepted by the MySQL dialect, is now established
946on all :class:`.String` types and will render on any backend, including
947when features such as :meth:`.MetaData.create_all` and :func:`.cast` is used::
948
949    >>> stmt = select([cast(sometable.c.somechar, String(20, collation='utf8'))])
950    >>> print(stmt)
951    SELECT CAST(sometable.somechar AS VARCHAR(20) COLLATE "utf8") AS anon_1
952    FROM sometable
953
954.. seealso::
955
956    :class:`.String`
957
958:ticket:`2276`
959
960"Prefixes" now supported for :func:`.update`, :func:`.delete`
961-------------------------------------------------------------
962
963Geared towards MySQL, a "prefix" can be rendered within any of
964these constructs.   E.g.::
965
966    stmt = table.delete().prefix_with("LOW_PRIORITY", dialect="mysql")
967
968
969    stmt = table.update().prefix_with("LOW_PRIORITY", dialect="mysql")
970
971The method is new in addition to those which already existed
972on :func:`.insert`, :func:`.select` and :class:`.Query`.
973
974.. seealso::
975
976    :meth:`.Update.prefix_with`
977
978    :meth:`.Delete.prefix_with`
979
980    :meth:`.Insert.prefix_with`
981
982    :meth:`.Select.prefix_with`
983
984    :meth:`.Query.prefix_with`
985
986:ticket:`2431`
987
988
989Behavioral Changes
990==================
991
992.. _legacy_is_orphan_addition:
993
994The consideration of a "pending" object as an "orphan" has been made more aggressive
995------------------------------------------------------------------------------------
996
997This is a late add to the 0.8 series, however it is hoped that the new behavior
998is generally more consistent and intuitive in a wider variety of
999situations.   The ORM has since at least version 0.4 included behavior
1000such that an object that's "pending", meaning that it's
1001associated with a :class:`.Session` but hasn't been inserted into the database
1002yet, is automatically expunged from the :class:`.Session` when it becomes an "orphan",
1003which means it has been de-associated with a parent object that refers to it
1004with ``delete-orphan`` cascade on the configured :func:`.relationship`.   This
1005behavior is intended to approximately mirror the behavior of a persistent
1006(that is, already inserted) object, where the ORM will emit a DELETE for such
1007objects that become orphans based on the interception of detachment events.
1008
1009The behavioral change comes into play for objects that
1010are referred to by multiple kinds of parents that each specify ``delete-orphan``; the
1011typical example is an :ref:`association object <association_pattern>` that bridges two other kinds of objects
1012in a many-to-many pattern.   Previously, the behavior was such that the
1013pending object would be expunged only when de-associated with *all* of its parents.
1014With the behavioral change, the pending object
1015is expunged as soon as it is de-associated from *any* of the parents that it was
1016previously associated with.  This behavior is intended to more closely
1017match that of persistent objects, which are deleted as soon
1018as they are de-associated from any parent.
1019
1020The rationale for the older behavior dates back
1021at least to version 0.4, and was basically a defensive decision to try to alleviate
1022confusion when an object was still being constructed for INSERT.   But the reality
1023is that the object is re-associated with the :class:`.Session` as soon as it is
1024attached to any new parent in any case.
1025
1026It's still possible to flush an object
1027that is not associated with all of its required parents, if the object was either
1028not associated with those parents in the first place, or if it was expunged, but then
1029re-associated with a :class:`.Session` via a subsequent attachment event but still
1030not fully associated.   In this situation, it is expected that the database
1031would emit an integrity error, as there are likely NOT NULL foreign key columns
1032that are unpopulated.   The ORM makes the decision to let these INSERT attempts
1033occur, based on the judgment that an object that is only partially associated with
1034its required parents but has been actively associated with some of them,
1035is more often than not a user error, rather than an intentional
1036omission which should be silently skipped - silently skipping the INSERT here would
1037make user errors of this nature very hard to debug.
1038
1039The old behavior, for applications that might have been relying upon it, can be re-enabled for
1040any :class:`.Mapper` by specifying the flag ``legacy_is_orphan`` as a mapper
1041option.
1042
1043The new behavior allows the following test case to work::
1044
1045    from sqlalchemy import Column, Integer, String, ForeignKey
1046    from sqlalchemy.orm import relationship, backref
1047    from sqlalchemy.ext.declarative import declarative_base
1048
1049    Base = declarative_base()
1050
1051    class User(Base):
1052        __tablename__ = 'user'
1053        id = Column(Integer, primary_key=True)
1054        name = Column(String(64))
1055
1056    class UserKeyword(Base):
1057        __tablename__ = 'user_keyword'
1058        user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
1059        keyword_id = Column(Integer, ForeignKey('keyword.id'), primary_key=True)
1060
1061        user = relationship(User,
1062                    backref=backref("user_keywords",
1063                                    cascade="all, delete-orphan")
1064                )
1065
1066        keyword = relationship("Keyword",
1067                    backref=backref("user_keywords",
1068                                    cascade="all, delete-orphan")
1069                )
1070
1071        # uncomment this to enable the old behavior
1072        # __mapper_args__ = {"legacy_is_orphan": True}
1073
1074    class Keyword(Base):
1075        __tablename__ = 'keyword'
1076        id = Column(Integer, primary_key=True)
1077        keyword = Column('keyword', String(64))
1078
1079    from sqlalchemy import create_engine
1080    from sqlalchemy.orm import Session
1081
1082    # note we're using PostgreSQL to ensure that referential integrity
1083    # is enforced, for demonstration purposes.
1084    e = create_engine("postgresql://scott:tiger@localhost/test", echo=True)
1085
1086    Base.metadata.drop_all(e)
1087    Base.metadata.create_all(e)
1088
1089    session = Session(e)
1090
1091    u1 = User(name="u1")
1092    k1 = Keyword(keyword="k1")
1093
1094    session.add_all([u1, k1])
1095
1096    uk1 = UserKeyword(keyword=k1, user=u1)
1097
1098    # previously, if session.flush() were called here,
1099    # this operation would succeed, but if session.flush()
1100    # were not called here, the operation fails with an
1101    # integrity error.
1102    # session.flush()
1103    del u1.user_keywords[0]
1104
1105    session.commit()
1106
1107
1108:ticket:`2655`
1109
1110The after_attach event fires after the item is associated with the Session instead of before; before_attach added
1111-----------------------------------------------------------------------------------------------------------------
1112
1113Event handlers which use after_attach can now assume the
1114given instance is associated with the given session:
1115
1116::
1117
1118    @event.listens_for(Session, "after_attach")
1119    def after_attach(session, instance):
1120        assert instance in session
1121
1122Some use cases require that it work this way.  However,
1123other use cases require that the item is *not* yet part of
1124the session, such as when a query, intended to load some
1125state required for an instance, emits autoflush first and
1126would otherwise prematurely flush the target object.  Those
1127use cases should use the new "before_attach" event:
1128
1129::
1130
1131    @event.listens_for(Session, "before_attach")
1132    def before_attach(session, instance):
1133        instance.some_necessary_attribute = session.query(Widget).\
1134                                                filter_by(instance.widget_name).\
1135                                                first()
1136
1137:ticket:`2464`
1138
1139
1140
1141Query now auto-correlates like a select() does
1142----------------------------------------------
1143
1144Previously it was necessary to call :meth:`.Query.correlate` in
1145order to have a column- or WHERE-subquery correlate to the
1146parent:
1147
1148::
1149
1150    subq = session.query(Entity.value).\
1151                    filter(Entity.id==Parent.entity_id).\
1152                    correlate(Parent).\
1153                    as_scalar()
1154    session.query(Parent).filter(subq=="some value")
1155
1156This was the opposite behavior of a plain ``select()``
1157construct which would assume auto-correlation by default.
1158The above statement in 0.8 will correlate automatically:
1159
1160::
1161
1162    subq = session.query(Entity.value).\
1163                    filter(Entity.id==Parent.entity_id).\
1164                    as_scalar()
1165    session.query(Parent).filter(subq=="some value")
1166
1167like in ``select()``, correlation can be disabled by calling
1168``query.correlate(None)`` or manually set by passing an
1169entity, ``query.correlate(someentity)``.
1170
1171:ticket:`2179`
1172
1173.. _correlation_context_specific:
1174
1175Correlation is now always context-specific
1176------------------------------------------
1177
1178To allow a wider variety of correlation scenarios, the behavior of
1179:meth:`.Select.correlate` and :meth:`.Query.correlate` has changed slightly
1180such that the SELECT statement will omit the "correlated" target from the
1181FROM clause only if the statement is actually used in that context.  Additionally,
1182it's no longer possible for a SELECT statement that's placed as a FROM
1183in an enclosing SELECT statement to "correlate" (i.e. omit) a FROM clause.
1184
1185This change only makes things better as far as rendering SQL, in that it's no
1186longer possible to render illegal SQL where there are insufficient FROM
1187objects relative to what's being selected::
1188
1189    from sqlalchemy.sql import table, column, select
1190
1191    t1 = table('t1', column('x'))
1192    t2 = table('t2', column('y'))
1193    s = select([t1, t2]).correlate(t1)
1194
1195    print(s)
1196
1197Prior to this change, the above would return::
1198
1199    SELECT t1.x, t2.y FROM t2
1200
1201which is invalid SQL as "t1" is not referred to in any FROM clause.
1202
1203Now, in the absence of an enclosing SELECT, it returns::
1204
1205    SELECT t1.x, t2.y FROM t1, t2
1206
1207Within a SELECT, the correlation takes effect as expected::
1208
1209    s2 = select([t1, t2]).where(t1.c.x == t2.c.y).where(t1.c.x == s)
1210
1211    print(s2)
1212
1213    SELECT t1.x, t2.y FROM t1, t2
1214    WHERE t1.x = t2.y AND t1.x =
1215        (SELECT t1.x, t2.y FROM t2)
1216
1217This change is not expected to impact any existing applications, as
1218the correlation behavior remains identical for properly constructed
1219expressions.  Only an application that relies, most likely within a
1220testing scenario, on the invalid string output of a correlated
1221SELECT used in a non-correlating context would see any change.
1222
1223:ticket:`2668`
1224
1225
1226.. _metadata_create_drop_tables:
1227
1228create_all() and drop_all() will now honor an empty list as such
1229----------------------------------------------------------------
1230
1231The methods :meth:`.MetaData.create_all` and :meth:`.MetaData.drop_all`
1232will now accept a list of :class:`.Table` objects that is empty,
1233and will not emit any CREATE or DROP statements.  Previously,
1234an empty list was interepreted the same as passing ``None``
1235for a collection, and CREATE/DROP would be emitted for all
1236items unconditionally.
1237
1238This is a bug fix but some applications may have been relying upon
1239the previous behavior.
1240
1241:ticket:`2664`
1242
1243Repaired the Event Targeting of :class:`.InstrumentationEvents`
1244---------------------------------------------------------------
1245
1246The :class:`.InstrumentationEvents` series of event targets have
1247documented that the events will only be fired off according to
1248the actual class passed as a target.  Through 0.7, this wasn't the
1249case, and any event listener applied to :class:`.InstrumentationEvents`
1250would be invoked for all classes mapped.  In 0.8, additional
1251logic has been added so that the events will only invoke for those
1252classes sent in.  The ``propagate`` flag here is set to ``True``
1253by default as class instrumentation events are typically used to
1254intercept classes that aren't yet created.
1255
1256:ticket:`2590`
1257
1258No more magic coercion of "=" to IN when comparing to subquery in MS-SQL
1259------------------------------------------------------------------------
1260
1261We found a very old behavior in the MSSQL dialect which
1262would attempt to rescue users from themselves when
1263doing something like this:
1264
1265::
1266
1267    scalar_subq = select([someothertable.c.id]).where(someothertable.c.data=='foo')
1268    select([sometable]).where(sometable.c.id==scalar_subq)
1269
1270SQL Server doesn't allow an equality comparison to a scalar
1271SELECT, that is, "x = (SELECT something)". The MSSQL dialect
1272would convert this to an IN.   The same thing would happen
1273however upon a comparison like "(SELECT something) = x", and
1274overall this level of guessing is outside of SQLAlchemy's
1275usual scope so the behavior is removed.
1276
1277:ticket:`2277`
1278
1279Fixed the behavior of :meth:`.Session.is_modified`
1280--------------------------------------------------
1281
1282The :meth:`.Session.is_modified` method accepts an argument
1283``passive`` which basically should not be necessary, the
1284argument in all cases should be the value ``True`` - when
1285left at its default of ``False`` it would have the effect of
1286hitting the database, and often triggering autoflush which
1287would itself change the results.   In 0.8 the ``passive``
1288argument will have no effect, and unloaded attributes will
1289never be checked for history since by definition there can
1290be no pending state change on an unloaded attribute.
1291
1292.. seealso::
1293
1294    :meth:`.Session.is_modified`
1295
1296:ticket:`2320`
1297
1298:attr:`.Column.key` is honored in the :attr:`.Select.c` attribute of :func:`.select` with :meth:`.Select.apply_labels`
1299-----------------------------------------------------------------------------------------------------------------------
1300
1301Users of the expression system know that :meth:`.Select.apply_labels`
1302prepends the table name to each column name, affecting the
1303names that are available from :attr:`.Select.c`:
1304
1305::
1306
1307    s = select([table1]).apply_labels()
1308    s.c.table1_col1
1309    s.c.table1_col2
1310
1311Before 0.8, if the :class:`.Column` had a different :attr:`.Column.key`, this
1312key would be ignored, inconsistently versus when
1313:meth:`.Select.apply_labels` were not used:
1314
1315::
1316
1317    # before 0.8
1318    table1 = Table('t1', metadata,
1319        Column('col1', Integer, key='column_one')
1320    )
1321    s = select([table1])
1322    s.c.column_one # would be accessible like this
1323    s.c.col1 # would raise AttributeError
1324
1325    s = select([table1]).apply_labels()
1326    s.c.table1_column_one # would raise AttributeError
1327    s.c.table1_col1 # would be accessible like this
1328
1329In 0.8, :attr:`.Column.key` is honored in both cases:
1330
1331::
1332
1333    # with 0.8
1334    table1 = Table('t1', metadata,
1335        Column('col1', Integer, key='column_one')
1336    )
1337    s = select([table1])
1338    s.c.column_one # works
1339    s.c.col1 # AttributeError
1340
1341    s = select([table1]).apply_labels()
1342    s.c.table1_column_one # works
1343    s.c.table1_col1 # AttributeError
1344
1345All other behavior regarding "name" and "key" are the same,
1346including that the rendered SQL will still use the form
1347``<tablename>_<colname>`` - the emphasis here was on
1348preventing the :attr:`.Column.key` contents from being rendered into the
1349``SELECT`` statement so that there are no issues with
1350special/ non-ascii characters used in the :attr:`.Column.key`.
1351
1352:ticket:`2397`
1353
1354single_parent warning is now an error
1355-------------------------------------
1356
1357A :func:`.relationship` that is many-to-one or many-to-many and
1358specifies "cascade='all, delete-orphan'", which is an
1359awkward but nonetheless supported use case (with
1360restrictions) will now raise an error if the relationship
1361does not specify the ``single_parent=True`` option.
1362Previously it would only emit a warning, but a failure would
1363follow almost immediately within the attribute system in any
1364case.
1365
1366:ticket:`2405`
1367
1368Adding the ``inspector`` argument to the ``column_reflect`` event
1369-----------------------------------------------------------------
1370
13710.7 added a new event called ``column_reflect``, provided so
1372that the reflection of columns could be augmented as each
1373one were reflected.   We got this event slightly wrong in
1374that the event gave no way to get at the current
1375``Inspector`` and ``Connection`` being used for the
1376reflection, in the case that additional information from the
1377database is needed.   As this is a new event not widely used
1378yet, we'll be adding the ``inspector`` argument into it
1379directly:
1380
1381::
1382
1383    @event.listens_for(Table, "column_reflect")
1384    def listen_for_col(inspector, table, column_info):
1385        # ...
1386
1387:ticket:`2418`
1388
1389Disabling auto-detect of collations, casing for MySQL
1390-----------------------------------------------------
1391
1392The MySQL dialect does two calls, one very expensive, to
1393load all possible collations from the database as well as
1394information on casing, the first time an ``Engine``
1395connects.   Neither of these collections are used for any
1396SQLAlchemy functions, so these calls will be changed to no
1397longer be emitted automatically. Applications that might
1398have relied on these collections being present on
1399``engine.dialect`` will need to call upon
1400``_detect_collations()`` and ``_detect_casing()`` directly.
1401
1402:ticket:`2404`
1403
1404"Unconsumed column names" warning becomes an exception
1405------------------------------------------------------
1406
1407Referring to a non-existent column in an ``insert()`` or
1408``update()`` construct will raise an error instead of a
1409warning:
1410
1411::
1412
1413    t1 = table('t1', column('x'))
1414    t1.insert().values(x=5, z=5) # raises "Unconsumed column names: z"
1415
1416:ticket:`2415`
1417
1418Inspector.get_primary_keys() is deprecated, use Inspector.get_pk_constraint
1419---------------------------------------------------------------------------
1420
1421These two methods on ``Inspector`` were redundant, where
1422``get_primary_keys()`` would return the same information as
1423``get_pk_constraint()`` minus the name of the constraint:
1424
1425::
1426
1427    >>> insp.get_primary_keys()
1428    ["a", "b"]
1429
1430    >>> insp.get_pk_constraint()
1431    {"name":"pk_constraint", "constrained_columns":["a", "b"]}
1432
1433:ticket:`2422`
1434
1435Case-insensitive result row names will be disabled in most cases
1436----------------------------------------------------------------
1437
1438A very old behavior, the column names in ``RowProxy`` were
1439always compared case-insensitively:
1440
1441::
1442
1443    >>> row = result.fetchone()
1444    >>> row['foo'] == row['FOO'] == row['Foo']
1445    True
1446
1447This was for the benefit of a few dialects which in the
1448early days needed this, like Oracle and Firebird, but in
1449modern usage we have more accurate ways of dealing with the
1450case-insensitive behavior of these two platforms.
1451
1452Going forward, this behavior will be available only
1453optionally, by passing the flag ```case_sensitive=False```
1454to ```create_engine()```, but otherwise column names
1455requested from the row must match as far as casing.
1456
1457:ticket:`2423`
1458
1459``InstrumentationManager`` and alternate class instrumentation is now an extension
1460----------------------------------------------------------------------------------
1461
1462The ``sqlalchemy.orm.interfaces.InstrumentationManager``
1463class is moved to
1464``sqlalchemy.ext.instrumentation.InstrumentationManager``.
1465The "alternate instrumentation" system was built for the
1466benefit of a very small number of installations that needed
1467to work with existing or unusual class instrumentation
1468systems, and generally is very seldom used.   The complexity
1469of this system has been exported to an ``ext.`` module.  It
1470remains unused until once imported, typically when a third
1471party library imports ``InstrumentationManager``, at which
1472point it is injected back into ``sqlalchemy.orm`` by
1473replacing the default ``InstrumentationFactory`` with
1474``ExtendedInstrumentationRegistry``.
1475
1476Removed
1477=======
1478
1479SQLSoup
1480-------
1481
1482SQLSoup is a handy package that presents an alternative
1483interface on top of the SQLAlchemy ORM.   SQLSoup is now
1484moved into its own project and documented/released
1485separately; see https://bitbucket.org/zzzeek/sqlsoup.
1486
1487SQLSoup is a very simple tool that could also benefit from
1488contributors who are interested in its style of usage.
1489
1490:ticket:`2262`
1491
1492MutableType
1493-----------
1494
1495The older "mutable" system within the SQLAlchemy ORM has
1496been removed.   This refers to the ``MutableType`` interface
1497which was applied to types such as ``PickleType`` and
1498conditionally to ``TypeDecorator``, and since very early
1499SQLAlchemy versions has provided a way for the ORM to detect
1500changes in so-called "mutable" data structures such as JSON
1501structures and pickled objects.   However, the
1502implementation was never reasonable and forced a very
1503inefficient mode of usage on the unit-of-work which caused
1504an expensive scan of all objects to take place during flush.
1505In 0.7, the `sqlalchemy.ext.mutable <http://docs.sqlalchemy.
1506org/en/latest/orm/extensions/mutable.html>`_ extension was
1507introduced so that user-defined datatypes can appropriately
1508send events to the unit of work as changes occur.
1509
1510Today, usage of ``MutableType`` is expected to be low, as
1511warnings have been in place for some years now regarding its
1512inefficiency.
1513
1514:ticket:`2442`
1515
1516sqlalchemy.exceptions (has been sqlalchemy.exc for years)
1517---------------------------------------------------------
1518
1519We had left in an alias ``sqlalchemy.exceptions`` to attempt
1520to make it slightly easier for some very old libraries that
1521hadn't yet been upgraded to use ``sqlalchemy.exc``.  Some
1522users are still being confused by it however so in 0.8 we're
1523taking it out entirely to eliminate any of that confusion.
1524
1525:ticket:`2433`
1526
1527