Skip to content

Commit 5de0f1c

Browse files
committed
Convert remaining ORM APIs to support 2.0 style
This is kind of a mixed bag of all kinds to help get us to 1.4 betas. The documentation stuff is a work in progress. Lots of other relatively small changes to APIs and things. More commits will follow to continue improving the documentation and transitioning to the 1.4/2.0 hybrid documentation. In particular some refinements to Session usage models so that it can match Engine's scoping / transactional patterns, and a decision to start moving away from "subtransactions" completely. * add select().from_statement() to produce FromStatement in an ORM context * begin referring to select() that has "plugins" for the few edge cases where select() will have ORM-only behaviors * convert dynamic.AppenderQuery to its own object that can use select(), though at the moment it uses Query to support legacy join calling forms. * custom query classes for AppenderQuery are replaced by do_orm_execute() hooks for custom actions, a separate gerrit will document this * add Session.get() to replace query.get() * Deprecate session.begin->subtransaction. propose within the test suite a hypothetical recipe for apps that rely on this pattern * introduce Session construction level context manager, sessionmaker context manager, rewrite the whole top of the session_transaction.rst documentation. Establish context manager patterns for Session that are identical to engine * ensure same begin_nested() / commit() behavior as engine * devise all new "join into an external transaction" recipe, add test support for it, add rules into Session so it just works, write new docs. need to ensure this doesn't break anything * vastly reduce the verbosity of lots of session docs as I dont think people read this stuff and it's difficult to keep current in any case * constructs like case(), with_only_columns() really need to move to *columns, add a coercion rule to just change these. * docs need changes everywhere I look. in_() is not in the Core tutorial? how do people even know about it? Remove tons of cruft from Select docs, etc. * build a system for common ORM options like populate_existing and autoflush to populate from execution options. * others? Change-Id: Ia4bea0f804250e54d90b3884cf8aab8b66b82ecf
1 parent e2d4b2e commit 5de0f1c

45 files changed

Lines changed: 4230 additions & 2075 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

doc/build/changelog/migration_20.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,7 @@ In 2.0, an application that still wishes to use a separate :class:`_schema.Table
738738
does not want to use Declarative with ``__table__``, can instead use the above
739739
pattern which basically does the same thing.
740740

741+
.. _migration_20_unify_select:
741742

742743
ORM Query Unified with Core Select
743744
==================================

doc/build/core/future.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,29 @@
33
SQLAlchemy 2.0 Future (Core)
44
============================
55

6+
This package includes a relatively small number of transitional elements
7+
to allow "2.0 mode" to take place within SQLAlchemy 1.4. The primary
8+
objects provided here are :class:`_future.Engine` and :class:`_future.Connection`,
9+
which are both subclasses of the existing :class:`_engine.Engine` and
10+
:class:`_engine.Connection` objects with essentially a smaller set of
11+
methods and the removal of "autocommit".
12+
13+
Within the 1.4 series, the "2.0" style of engines and connections is enabled
14+
by passing the :paramref:`_sa.create_engine.future` flag to
15+
:func:`_sa.create_engine`::
16+
17+
from sqlalchemy import create_engine
18+
engine = create_engine("postgresql://user:pass@host/dbname", future=True)
19+
20+
Similarly, with the ORM, to enable "future" behavior in the ORM :class:`.Session`,
21+
pass the :paramref:`_orm.Session.future` parameter either to the
22+
:class:`.Session` constructor directly, or via the :class:`_orm.sessionmaker`
23+
class::
24+
25+
from sqlalchemy.orm import sessionmaker
26+
27+
Session = sessionmaker(engine, future=True)
28+
629
.. seealso::
730

831
:ref:`migration_20_toplevel` - Introduction to the 2.0 series of SQLAlchemy

doc/build/core/selectable.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,22 @@ elements are themselves :class:`_expression.ColumnElement` subclasses).
8080
.. autoclass:: Lateral
8181
:members:
8282

83+
.. autoclass:: ReturnsRows
84+
:members:
85+
:inherited-members: ClauseElement
86+
8387
.. autoclass:: ScalarSelect
8488
:members:
8589

8690
.. autoclass:: Select
8791
:members:
8892
:inherited-members: ClauseElement
89-
:exclude-members: memoized_attribute, memoized_instancemethod
93+
:exclude-members: memoized_attribute, memoized_instancemethod, append_correlation, append_column, append_prefix, append_whereclause, append_having, append_from, append_order_by, append_group_by
94+
9095

9196
.. autoclass:: Selectable
9297
:members:
98+
:inherited-members: ClauseElement
9399

94100
.. autoclass:: SelectBase
95101
:members:

doc/build/core/tutorial.rst

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,112 @@ will ensure that the return type of the expression is handled as boolean::
716716

717717
somecolumn.bool_op('-->')('some value')
718718

719-
.. versionadded:: 1.2.0b3 Added the :meth:`.Operators.bool_op` method.
719+
720+
Commonly Used Operators
721+
-------------------------
722+
723+
724+
Here's a rundown of some of the most common operators used in both the
725+
Core expression language as well as in the ORM. Here we see expressions
726+
that are most commonly present when using the :meth:`_sql.Select.where` method,
727+
but can be used in other scenarios as well.
728+
729+
A listing of all the column-level operations common to all column-like
730+
objects is at :class:`.ColumnOperators`.
731+
732+
733+
* :meth:`equals <.ColumnOperators.__eq__>`::
734+
735+
statement.where(users.c.name == 'ed')
736+
737+
* :meth:`not equals <.ColumnOperators.__ne__>`::
738+
739+
statement.where(users.c.name != 'ed')
740+
741+
* :meth:`LIKE <.ColumnOperators.like>`::
742+
743+
statement.where(users.c.name.like('%ed%'))
744+
745+
.. note:: :meth:`.ColumnOperators.like` renders the LIKE operator, which
746+
is case insensitive on some backends, and case sensitive
747+
on others. For guaranteed case-insensitive comparisons, use
748+
:meth:`.ColumnOperators.ilike`.
749+
750+
* :meth:`ILIKE <.ColumnOperators.ilike>` (case-insensitive LIKE)::
751+
752+
statement.where(users.c.name.ilike('%ed%'))
753+
754+
.. note:: most backends don't support ILIKE directly. For those,
755+
the :meth:`.ColumnOperators.ilike` operator renders an expression
756+
combining LIKE with the LOWER SQL function applied to each operand.
757+
758+
* :meth:`IN <.ColumnOperators.in_>`::
759+
760+
statement.where(users.c..name.in_(['ed', 'wendy', 'jack']))
761+
762+
# works with Select objects too:
763+
statement.where.filter(users.c.name.in_(
764+
select(users.c.name).where(users.c.name.like('%ed%'))
765+
))
766+
767+
# use tuple_() for composite (multi-column) queries
768+
from sqlalchemy import tuple_
769+
statement.where(
770+
tuple_(users.c.name, users.c.nickname).\
771+
in_([('ed', 'edsnickname'), ('wendy', 'windy')])
772+
)
773+
774+
* :meth:`NOT IN <.ColumnOperators.notin_>`::
775+
776+
statement.where(~users.c.name.in_(['ed', 'wendy', 'jack']))
777+
778+
* :meth:`IS NULL <.ColumnOperators.is_>`::
779+
780+
statement.where(users.c. == None)
781+
782+
# alternatively, if pep8/linters are a concern
783+
statement.where(users.c.name.is_(None))
784+
785+
* :meth:`IS NOT NULL <.ColumnOperators.isnot>`::
786+
787+
statement.where(users.c.name != None)
788+
789+
# alternatively, if pep8/linters are a concern
790+
statement.where(users.c.name.isnot(None))
791+
792+
* :func:`AND <.sql.expression.and_>`::
793+
794+
# use and_()
795+
from sqlalchemy import and_
796+
statement.where(and_(users.c.name == 'ed', users.c.fullname == 'Ed Jones'))
797+
798+
# or send multiple expressions to .where()
799+
statement.where(users.c.name == 'ed', users.c.fullname == 'Ed Jones')
800+
801+
# or chain multiple where() calls
802+
statement.where(users.c.name == 'ed').where(users.c.fullname == 'Ed Jones')
803+
804+
.. note:: Make sure you use :func:`.and_` and **not** the
805+
Python ``and`` operator!
806+
807+
* :func:`OR <.sql.expression.or_>`::
808+
809+
from sqlalchemy import or_
810+
statement.where(or_(users.c.name == 'ed', users.c.name == 'wendy'))
811+
812+
.. note:: Make sure you use :func:`.or_` and **not** the
813+
Python ``or`` operator!
814+
815+
* :meth:`MATCH <.ColumnOperators.match>`::
816+
817+
statement.where(users.c.name.match('wendy'))
818+
819+
.. note::
820+
821+
:meth:`~.ColumnOperators.match` uses a database-specific ``MATCH``
822+
or ``CONTAINS`` function; its behavior will vary by backend and is not
823+
available on some backends such as SQLite.
824+
720825

721826
Operator Customization
722827
----------------------

doc/build/glossary.rst

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,22 @@ Glossary
99
.. glossary::
1010
:sorted:
1111

12+
1.x style
13+
2.0 style
14+
1.x-style
15+
2.0-style
16+
These terms are new in SQLAlchemy 1.4 and refer to the SQLAlchemy 1.4->
17+
2.0 transition plan, described at :ref:`migration_20_toplevel`. The
18+
term "1.x style" refers to an API used in the way it's been documented
19+
throughout the 1.x series of SQLAlhcemy and earlier (e.g. 1.3, 1.2, etc)
20+
and the term "2.0 style" refers to the way an API will look in version
21+
2.0. Version 1.4 implements nearly all of 2.0's API in so-called
22+
"transition mode".
23+
24+
.. seealso::
25+
26+
:ref:`migration_20_toplevel`
27+
1228
relational
1329
relational algebra
1430

@@ -49,6 +65,25 @@ Glossary
4965
in terms of one particular table alias or another, based on its position
5066
within the join expression.
5167

68+
plugin
69+
plugin-specific
70+
"plugin-specific" generally indicates a function or method in
71+
SQLAlchemy Core which will behave differently when used in an ORM
72+
context.
73+
74+
SQLAlchemy allows Core consrtucts such as :class:`_sql.Select` objects
75+
to participate in a "plugin" system, which can inject additional
76+
behaviors and features into the object that are not present by default.
77+
78+
Specifically, the primary "plugin" is the "orm" plugin, which is
79+
at the base of the system that the SQLAlchemy ORM makes use of
80+
Core constructs in order to compose and execute SQL queries that
81+
return ORM results.
82+
83+
.. seealso::
84+
85+
:ref:`migration_20_unify_select`
86+
5287
crud
5388
CRUD
5489
An acronym meaning "Create, Update, Delete". The term in SQL refers to the

doc/build/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ are documented here. In contrast to the ORM's domain-centric mode of usage, the
9191
:doc:`Core Event Interfaces <core/events>` |
9292
:doc:`Creating Custom SQL Constructs <core/compiler>` |
9393

94-
* **SQLAlchemy 2.0 Compatibility:** :doc:`SQLAlchemy 2.0 Future (Core) <core/future>`
94+
* **SQLAlchemy 2.0 Compatibility:** :ref:`migration_20_toplevel`
9595

9696
Dialect Documentation
9797
======================

doc/build/orm/collections.rst

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,12 @@ loading of child items both at load time as well as deletion time.
3232
Dynamic Relationship Loaders
3333
----------------------------
3434

35-
A key feature to enable management of a large collection is the so-called "dynamic"
36-
relationship. This is an optional form of :func:`~sqlalchemy.orm.relationship` which
37-
returns a :class:`~sqlalchemy.orm.query.Query` object in place of a collection
38-
when accessed. :func:`~sqlalchemy.orm.query.Query.filter` criterion may be
39-
applied as well as limits and offsets, either explicitly or via array slices::
35+
A key feature to enable management of a large collection is the so-called
36+
"dynamic" relationship. This is an optional form of
37+
:func:`_orm.relationship` which returns a
38+
:class:`_orm.AppenderQuery` object in place of a collection
39+
when accessed. Filtering criterion may be applied as well as limits and
40+
offsets, either explicitly or via array slices::
4041

4142
class User(Base):
4243
__tablename__ = 'user'
@@ -52,7 +53,7 @@ applied as well as limits and offsets, either explicitly or via array slices::
5253
posts = jack.posts[5:20]
5354

5455
The dynamic relationship supports limited write operations, via the
55-
``append()`` and ``remove()`` methods::
56+
:meth:`_orm.AppenderQuery.append` and :meth:`_orm.AppenderQuery.remove` methods::
5657

5758
oldpost = jack.posts.filter(Post.headline=='old post').one()
5859
jack.posts.remove(oldpost)
@@ -78,6 +79,9 @@ function in conjunction with ``lazy='dynamic'``::
7879

7980
Note that eager/lazy loading options cannot be used in conjunction dynamic relationships at this time.
8081

82+
.. autoclass:: sqlalchemy.orm.AppenderQuery
83+
:members:
84+
8185
.. note::
8286

8387
The :func:`_orm.dynamic_loader` function is essentially the same

doc/build/orm/persistence_techniques.rst

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,107 @@
22
Additional Persistence Techniques
33
=================================
44

5+
.. _session_deleting_from_collections:
6+
7+
Notes on Delete - Deleting Objects Referenced from Collections and Scalar Relationships
8+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9+
10+
The ORM in general never modifies the contents of a collection or scalar
11+
relationship during the flush process. This means, if your class has a
12+
:func:`_orm.relationship` that refers to a collection of objects, or a reference
13+
to a single object such as many-to-one, the contents of this attribute will
14+
not be modified when the flush process occurs. Instead, it is expected
15+
that the :class:`.Session` would eventually be expired, either through the expire-on-commit behavior of
16+
:meth:`.Session.commit` or through explicit use of :meth:`.Session.expire`.
17+
At that point, any referenced object or collection associated with that
18+
:class:`.Session` will be cleared and will re-load itself upon next access.
19+
20+
A common confusion that arises regarding this behavior involves the use of the
21+
:meth:`~.Session.delete` method. When :meth:`.Session.delete` is invoked upon
22+
an object and the :class:`.Session` is flushed, the row is deleted from the
23+
database. Rows that refer to the target row via foreign key, assuming they
24+
are tracked using a :func:`_orm.relationship` between the two mapped object types,
25+
will also see their foreign key attributes UPDATED to null, or if delete
26+
cascade is set up, the related rows will be deleted as well. However, even
27+
though rows related to the deleted object might be themselves modified as well,
28+
**no changes occur to relationship-bound collections or object references on
29+
the objects** involved in the operation within the scope of the flush
30+
itself. This means if the object was a
31+
member of a related collection, it will still be present on the Python side
32+
until that collection is expired. Similarly, if the object were
33+
referenced via many-to-one or one-to-one from another object, that reference
34+
will remain present on that object until the object is expired as well.
35+
36+
Below, we illustrate that after an ``Address`` object is marked
37+
for deletion, it's still present in the collection associated with the
38+
parent ``User``, even after a flush::
39+
40+
>>> address = user.addresses[1]
41+
>>> session.delete(address)
42+
>>> session.flush()
43+
>>> address in user.addresses
44+
True
45+
46+
When the above session is committed, all attributes are expired. The next
47+
access of ``user.addresses`` will re-load the collection, revealing the
48+
desired state::
49+
50+
>>> session.commit()
51+
>>> address in user.addresses
52+
False
53+
54+
There is a recipe for intercepting :meth:`.Session.delete` and invoking this
55+
expiration automatically; see `ExpireRelationshipOnFKChange <http://www.sqlalchemy.org/trac/wiki/UsageRecipes/ExpireRelationshipOnFKChange>`_ for this. However, the usual practice of
56+
deleting items within collections is to forego the usage of
57+
:meth:`~.Session.delete` directly, and instead use cascade behavior to
58+
automatically invoke the deletion as a result of removing the object from the
59+
parent collection. The ``delete-orphan`` cascade accomplishes this, as
60+
illustrated in the example below::
61+
62+
class User(Base):
63+
__tablename__ = 'user'
64+
65+
# ...
66+
67+
addresses = relationship(
68+
"Address", cascade="all, delete-orphan")
69+
70+
# ...
71+
72+
del user.addresses[1]
73+
session.flush()
74+
75+
Where above, upon removing the ``Address`` object from the ``User.addresses``
76+
collection, the ``delete-orphan`` cascade has the effect of marking the ``Address``
77+
object for deletion in the same way as passing it to :meth:`~.Session.delete`.
78+
79+
The ``delete-orphan`` cascade can also be applied to a many-to-one
80+
or one-to-one relationship, so that when an object is de-associated from its
81+
parent, it is also automatically marked for deletion. Using ``delete-orphan``
82+
cascade on a many-to-one or one-to-one requires an additional flag
83+
:paramref:`_orm.relationship.single_parent` which invokes an assertion
84+
that this related object is not to shared with any other parent simultaneously::
85+
86+
class User(Base):
87+
# ...
88+
89+
preference = relationship(
90+
"Preference", cascade="all, delete-orphan",
91+
single_parent=True)
92+
93+
94+
Above, if a hypothetical ``Preference`` object is removed from a ``User``,
95+
it will be deleted on flush::
96+
97+
some_user.preference = None
98+
session.flush() # will delete the Preference object
99+
100+
.. seealso::
101+
102+
:ref:`unitofwork_cascades` for detail on cascades.
103+
104+
105+
5106
.. _flush_embedded_sql_expressions:
6107

7108
Embedding SQL Insert/Update Expressions into a Flush

doc/build/orm/session.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ Using the Session
66

77
.. module:: sqlalchemy.orm.session
88

9-
The :func:`_orm.mapper` function and :mod:`~sqlalchemy.ext.declarative` extensions
10-
are the primary configurational interface for the ORM. Once mappings are
11-
configured, the primary usage interface for persistence operations is the
9+
The declarative base and ORM mapping functions described at
10+
:ref:`mapper_config_toplevel` are the primary configurational interface for the
11+
ORM. Once mappings are configured, the primary usage interface for
12+
persistence operations is the
1213
:class:`.Session`.
1314

1415
.. toctree::

0 commit comments

Comments
 (0)