From: Mike Bayer Date: Sun, 10 Nov 2013 07:55:06 +0000 (-0500) Subject: - tutorial updates X-Git-Tag: rel_0_9_0~128 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=e269433efddb7e34c7ce455904650b43c401a082;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - tutorial updates - emphasis on not using a custom contructor by default, making sure it's clear that you *can* use one - other separation of narrative from special notes using sidebars --- diff --git a/doc/build/faq.rst b/doc/build/faq.rst index 7fed38c216..fea1a27d15 100644 --- a/doc/build/faq.rst +++ b/doc/build/faq.rst @@ -294,9 +294,40 @@ using the Python warnings filter (see http://docs.python.org/library/warnings.ht ORM Configuration ================== +.. _faq_mapper_primary_key: + How do I map a table that has no primary key? --------------------------------------------- +The SQLAlchemy ORM, in order to map to a particular table, needs there to be +at least one column denoted as a primary key column; multiple-column, +i.e. composite, primary keys are of course entirely feasible as well. These +columns do **not** need to be actually known to the database as primary key +columns, though it's a good idea that they are. It's only necessary that the columns +*behave* as a primary key does, e.g. as a unique and not nullable identifier +for a row. + +Most ORMs require that objects have some kind of primary key defined at the +because the object in memory must correspond to a uniquely identifiable +row in the database table; at the very least, this allows the +object can be targeted for UPDATE and DELETE statements which will affect only +that object's row and no other. However, the importance of the primary key +goes far beyond that. In SQLAlchemy, all ORM-mapped objects are at all times +linked uniquely within a :class:`.Session` +to their specific database row using a pattern called the :term:`identity map`, +a pattern that's central to the unit of work system employed by SQLAlchemy, +and is also key to the most common (and not-so-common) patterns of ORM usage. + + +.. note:: + + It's important to note that we're only talking about the SQLAlchemy ORM; an + application which builds on Core and deals only with :class:`.Table` objects, + :func:`.select` constructs and the like, **does not** need any primary key + to be present on or associated with a table in any way (though again, in SQL, all tables + should really have some kind of primary key, lest you need to actually + update or delete specific rows). + In almost all cases, a table does have a so-called :term:`candidate key`, which is a column or series of columns that uniquely identify a row. If a table truly doesn't have this, and has actual fully duplicate rows, the table is not corresponding to `first normal form `_ and cannot be mapped. Otherwise, whatever columns comprise the best candidate key can be diff --git a/doc/build/orm/tutorial.rst b/doc/build/orm/tutorial.rst index 737961baca..7289d57837 100644 --- a/doc/build/orm/tutorial.rst +++ b/doc/build/orm/tutorial.rst @@ -65,26 +65,21 @@ the SQL behind a popup window so it doesn't get in our way; just click the "SQL" links to see what's being generated. The return value of :func:`.create_engine` is an instance of :class:`.Engine`, and it represents -the core interface to the database, adapted through a **dialect** that handles the details -of the database and DBAPI in use. In this case the SQLite dialect will interpret instructions +the core interface to the database, adapted through a :term:`dialect` that handles the details +of the database and :term:`DBAPI` in use. In this case the SQLite dialect will interpret instructions to the Python built-in ``sqlite3`` module. -The :class:`.Engine` has not actually tried to connect to the database yet; that happens -only the first time it is asked to perform a task against the database. We can illustrate -this by asking it to perform a simple SELECT statement: +.. sidebar:: Lazy Connecting -.. sourcecode:: python+sql - - {sql}>>> engine.execute("select 1").scalar() - select 1 - () - {stop}1 + The :class:`.Engine`, when first returned by :func:`.create_engine`, + has not actually tried to connect to the database yet; that happens + only the first time it is asked to perform a task against the database. -As the :meth:`.Engine.execute` method is called, the :class:`.Engine` establishes a connection to the -SQLite database, which is then used to emit the SQL. The connection is then returned to an internal -connection pool where it will be reused on subsequent statement executions. While we illustrate direct usage of the -:class:`.Engine` here, this isn't typically necessary when using the ORM, where the :class:`.Engine`, -once created, is used behind the scenes by the ORM as we'll see shortly. +The first time a method like :meth:`.Engine.execute` or :meth:`.Engine.connect` +is called, the :class:`.Engine` establishes a real :term:`DBAPI` connection to the +database, which is then used to emit the SQL. When using the ORM, we typically +don't use the :class:`.Engine` directly once created; instead, it's used +behind the scenes by the ORM as we'll see shortly. Declare a Mapping ================= @@ -111,11 +106,9 @@ function, as follows:: Now that we have a "base", we can define any number of mapped classes in terms of it. We will start with just a single table called ``users``, which will store records for the end-users using our application. -A new class called ``User`` will be the class to which we map this table. The -imports we'll need to accomplish this include objects that represent the components -of our table, including the :class:`.Column` class which represents a database column, -as well as the :class:`.Integer` and :class:`.String` classes that -represent basic datatypes used in columns:: +A new class called ``User`` will be the class to which we map this table. Within +the class, we define details about the table to which we'll be mapping, primarily +the table name, and names and datatypes of columns:: >>> from sqlalchemy import Column, Integer, String >>> class User(Base): @@ -126,71 +119,84 @@ represent basic datatypes used in columns:: ... fullname = Column(String) ... password = Column(String) ... - ... def __init__(self, name, fullname, password): - ... self.name = name - ... self.fullname = fullname - ... self.password = password - ... ... def __repr__(self): - ... return "" % (self.name, self.fullname, self.password) - -The above ``User`` class establishes details about the table being mapped, including the name of the table denoted -by the ``__tablename__`` attribute, a set of columns ``id``, ``name``, ``fullname`` and ``password``, -where the ``id`` column will also be the primary key of the table. While its certainly possible -that some database tables don't have primary key columns (as is also the case with views, which can -also be mapped), the ORM in order to actually map to a particular table needs there -to be at least one column denoted as a primary key column; multiple-column, i.e. composite, primary keys -are of course entirely feasible as well. - -We define a constructor via ``__init__()`` and also a ``__repr__()`` method - both are optional. The -class of course can have any number of other methods and attributes as required by the application, -as it's basically just a plain Python class. Inheriting from ``Base`` is also only a requirement -of the declarative configurational system, which itself is optional and relatively open ended; at its -core, the SQLAlchemy ORM only requires that a class be a so-called "new style class", that is, it inherits -from ``object`` in Python 2, in order to be mapped. All classes in Python 3 are "new style" classes. - -.. topic:: The Non Opinionated Philosophy - - In our ``User`` mapping example, it was required that we identify the name of the table - in use, as well as the names and characteristics of all columns which we care about, - including which column or columns - represent the primary key, as well as some basic information about the types in use. - SQLAlchemy never makes assumptions about these decisions - the developer must - always be explicit about specific conventions in use. However, that doesn't mean the - task can't be automated. While this tutorial will keep things explicit, developers are - encouraged to make use of helper functions as well as "Declarative Mixins" to - automate their tasks in large scale applications. The section :ref:`declarative_mixins` - introduces many of these techniques. + ... return "" % ( + ... self.name, self.fullname, self.password) + +.. sidebar:: Tip + + The ``User`` class defines a ``__repr__()`` method, + but note that is **optional**; we only implement it in + this tutorial so that our examples show nicely + formatted ``User`` objects. + +A class using Declarative at a minimum +needs a ``__tablename__`` attribute, and at least one +:class:`.Column` which is part of a primary key [#]_. SQLAlchemy never makes any +assumptions by itself about the table to which +a class refers, including that it has no built-in conventions for names, +datatypes, or constraints. But this doesn't mean +boilerplate is required; instead, you're encouraged to create your +own automated conventions using helper functions and mixin classes, which +is described in detail at :ref:`declarative_mixins`. + +When our class is constructed, Declarative replaces all the :class:`.Column` +objects with special Python accessors known as :term:`descriptors`; this is a +process known as :term:`instrumentation`. The "instrumented" mapped class +will provide us with the means to refer to our table in a SQL context as well +as to persist and load the values of columns from the database. + +Outside of what the mapping process does to our class, the class remains +otherwise mostly a normal Python class, to which we can define any +number of ordinary attributes and methods needed by our application. + +.. [#] For information on why a primary key is required, see + :ref:`faq_mapper_primary_key`. + + +Create a Schema +=============== With our ``User`` class constructed via the Declarative system, we have defined information about -our table, known as **table metadata**, as well as a user-defined class which is linked to this -table, known as a **mapped class**. Declarative has provided for us a shorthand system for what in SQLAlchemy is -called a "Classical Mapping", which specifies these two units separately and is discussed -in :ref:`classical_mapping`. The table -is actually represented by a datastructure known as :class:`.Table`, and the mapping represented -by a :class:`.Mapper` object generated by a function called :func:`.mapper`. Declarative performs both of -these steps for us, making available the -:class:`.Table` it has created via the ``__table__`` attribute:: +our table, known as :term:`table metadata`. The object used by SQLAlchemy to represent +this information for a specific table is called the :class:`.Table` object, and here Declarative has made +one for us. We can see this object by inspecting the ``__table__`` attribute:: >>> User.__table__ # doctest: +NORMALIZE_WHITESPACE - Table('users', MetaData(None), + Table('users', MetaData(bind=None), Column('id', Integer(), table=, primary_key=True, nullable=False), Column('name', String(), table=), Column('fullname', String(), table=), Column('password', String(), table=), schema=None) -and while rarely needed, making available the :class:`.Mapper` object via the ``__mapper__`` attribute:: - - >>> User.__mapper__ # doctest: +ELLIPSIS - - -The Declarative base class also contains a catalog of all the :class:`.Table` objects -that have been defined called :class:`.MetaData`, available via the ``.metadata`` -attribute. In this example, we are defining -new tables that have yet to be created in our SQLite database, so one helpful feature -the :class:`.MetaData` object offers is the ability to issue CREATE TABLE statements -to the database for all tables that don't yet exist. We illustrate this -by calling the :meth:`.MetaData.create_all` method, passing in our :class:`.Engine` +.. sidebar:: Classical Mappings + + The Declarative system, though highly recommended, + is not required in order to use SQLAlchemy's ORM. + Outside of Declarative, any + plain Python class can be mapped to any :class:`.Table` + using the :func:`.mapper` function directly; this + less common usage is described at :ref:`classical_mapping`. + +When we declared our class, Declarative used a Python metaclass in order to +perform additional activities once the class declaration was complete; within +this phase, it then created a :class:`.Table` object according to our +specifications, and associated it with the class by constructing +a :class:`.Mapper` object. This object is a behind-the-scenes object we normally +don't need to deal with directly (though it can provide plenty of information +about our mapping when we need it). + +The :class:`.Table` object is a member of a larger collection +known as :class:`.MetaData`. When using Declarative, +this object is available using the ``.metadata`` +attribute of our declarative base class. + +The :class:`.MetaData` +is a :term:`registry` which includes the ability to emit a limited set +of schema generation commands to the database. As our SQLite database +does not actually have a ``users`` table present, we can use :class:`.MetaData` +to issue CREATE TABLE statements to the database for all tables that don't yet exist. +Below, we call the :meth:`.MetaData.create_all` method, passing in our :class:`.Engine` as a source of database connectivity. We will see that special commands are first emitted to check for the presence of the ``users`` table, and following that the actual ``CREATE TABLE`` statement: @@ -242,13 +248,9 @@ the actual ``CREATE TABLE`` statement: fullname = Column(String(50)) password = Column(String(12)) - def __init__(self, name, fullname, password): - self.name = name - self.fullname = fullname - self.password = password - def __repr__(self): - return "" % (self.name, self.fullname, self.password) + return "" % ( + self.name, self.fullname, self.password) We include this more verbose table definition separately to highlight the difference between a minimal construct geared primarily @@ -261,7 +263,7 @@ Create an Instance of the Mapped Class With mappings complete, let's now create and inspect a ``User`` object:: - >>> ed_user = User('ed', 'Ed Jones', 'edspassword') + >>> ed_user = User(name='ed', fullname='Ed Jones', password='edspassword') >>> ed_user.name 'ed' >>> ed_user.password @@ -269,41 +271,23 @@ With mappings complete, let's now create and inspect a ``User`` object:: >>> str(ed_user.id) 'None' -The ``id`` attribute, which while not defined by our ``__init__()`` method, -exists with a value of ``None`` on our ``User`` instance due to the ``id`` -column we declared in our mapping. By -default, the ORM creates class attributes for all columns present -in the table being mapped. These class attributes exist as -:term:`descriptors`, and -define **instrumentation** for the mapped class. The -functionality of this instrumentation includes the ability to fire on change -events, track modifications, and to automatically load new data from the database when -needed. - -Since we have not yet told SQLAlchemy to persist ``Ed Jones`` within the -database, its id is ``None``. When we persist the object later, this attribute -will be populated with a newly generated value. - -.. topic:: The default ``__init__()`` method - - Note that in our ``User`` example we supplied an ``__init__()`` method, - which receives ``name``, ``fullname`` and ``password`` as positional arguments. - The Declarative system supplies for us a default constructor if one is - not already present, which accepts keyword arguments of the same name - as that of the mapped attributes. Below we define ``User`` without - specifying a constructor:: - - class User(Base): - __tablename__ = 'users' - id = Column(Integer, primary_key=True) - name = Column(String) - fullname = Column(String) - password = Column(String) - - Our ``User`` class above will make usage of the default constructor, and provide - ``id``, ``name``, ``fullname``, and ``password`` as keyword arguments:: - - u1 = User(name='ed', fullname='Ed Jones', password='foobar') + +.. sidebar:: the ``__init__()`` method + + Our ``User`` class, as defined using the Declarative system, has + been provided with a constructor (e.g. ``__init__()`` method) which automatically + accepts keyword names that match the columns we've mapped. We are free + to define any explicit ``__init__()`` method we prefer on our class, which + will override the default method provided by Declarative. + +Even though we didn't specify it in the constructor, the ``id`` attribute +still produces a value of ``None`` when we access it (as opposed to Python's +usual behavior of raising ``AttributeError`` for an undefined attribute). +SQLAlchemy's :term:`instrumentation` normally produces this default value for +column-mapped attributes when first accessed. For those attributes where +we've actually assigned a value, the instrumentation system is tracking +those assignments for use within an eventual INSERT statement to be emitted to the +database. Creating a Session ================== @@ -330,6 +314,17 @@ connect it to the :class:`~sqlalchemy.orm.session.Session` using >>> Session.configure(bind=engine) # once engine is available +.. sidebar:: Session Lifecycle Patterns + + The question of when to make a :class:`.Session` depends a lot on what + kind of application is being built. Keep in mind, + the :class:`.Session` is just a workspace for your objects, + local to a particular database connection - if you think of + an application thread as a guest at a dinner party, the :class:`.Session` + is the guest's plate and the objects it holds are the food + (and the database...the kitchen?)! More on this topic + available at :ref:`session_faq_whentocreate`. + This custom-made :class:`~sqlalchemy.orm.session.Session` class will create new :class:`~sqlalchemy.orm.session.Session` objects which are bound to our database. Other transactional characteristics may be defined when calling @@ -345,24 +340,13 @@ used, it retrieves a connection from a pool of connections maintained by the :class:`.Engine`, and holds onto it until we commit all changes and/or close the session object. -.. topic:: Session Creational Patterns - - The business of acquiring a :class:`.Session` has a good deal of variety based - on the variety of types of applications and frameworks out there. - Keep in mind the :class:`.Session` is just a workspace for your objects, - local to a particular database connection - if you think of - an application thread as a guest at a dinner party, the :class:`.Session` - is the guest's plate and the objects it holds are the food - (and the database...the kitchen?)! Hints on - how :class:`.Session` is integrated into an application are at - :ref:`session_faq`. Adding New Objects ================== To persist our ``User`` object, we :meth:`~.Session.add` it to our :class:`~sqlalchemy.orm.session.Session`:: - >>> ed_user = User('ed', 'Ed Jones', 'edspassword') + >>> ed_user = User(name='ed', fullname='Ed Jones', password='edspassword') >>> session.add(ed_user) At this point, we say that the instance is **pending**; no SQL has yet been issued @@ -393,7 +377,7 @@ added: LIMIT ? OFFSET ? ('ed', 1, 0) {stop}>>> our_user - + In fact, the :class:`~sqlalchemy.orm.session.Session` has identified that the row returned is the **same** row as one already represented within its @@ -420,9 +404,9 @@ We can add more ``User`` objects at once using .. sourcecode:: python+sql >>> session.add_all([ - ... User('wendy', 'Wendy Williams', 'foobar'), - ... User('mary', 'Mary Contrary', 'xxg527'), - ... User('fred', 'Fred Flinstone', 'blah')]) + ... User(name='wendy', fullname='Wendy Williams', password='foobar'), + ... User(name='mary', fullname='Mary Contrary', password='xxg527'), + ... User(name='fred', fullname='Fred Flinstone', password='blah')]) Also, Ed has already decided his password isn't too secure, so lets change it: @@ -436,16 +420,16 @@ for example, that ``Ed Jones`` has been modified: .. sourcecode:: python+sql >>> session.dirty - IdentitySet([]) + IdentitySet([]) and that three new ``User`` objects are pending: .. sourcecode:: python+sql >>> session.new # doctest: +SKIP - IdentitySet([, - , - ]) + IdentitySet([, + , + ]) We tell the :class:`~sqlalchemy.orm.session.Session` that we'd like to issue all remaining changes to the database and commit the transaction, which has @@ -517,7 +501,7 @@ and we'll add another erroneous user, ``fake_user``: .. sourcecode:: python+sql - >>> fake_user = User('fakeuser', 'Invalid', '12345') + >>> fake_user = User(name='fakeuser', fullname='Invalid', password='12345') >>> session.add(fake_user) Querying the session, we can see that they're flushed into the current transaction: @@ -536,7 +520,7 @@ Querying the session, we can see that they're flushed into the current transacti FROM users WHERE users.name IN (?, ?) ('Edwardo', 'fakeuser') - {stop}[, ] + {stop}[, ] Rolling back, we can see that ``ed_user``'s name is back to ``ed``, and ``fake_user`` has been kicked out of the session: @@ -572,7 +556,7 @@ issuing a SELECT illustrates the changes made to the database: FROM users WHERE users.name IN (?, ?) ('ed', 'fakeuser') - {stop}[] + {stop}[] .. _ormtutorial_querying: @@ -638,10 +622,10 @@ class: users.password AS users_password FROM users () - {stop} ed - wendy - mary - fred + {stop} ed + wendy + mary + fred You can control the names of individual column expressions using the :meth:`~.CompareMixin.label` construct, which is available from @@ -677,10 +661,10 @@ entities are present in the call to :meth:`~.Session.query`, can be controlled u user_alias.password AS user_alias_password FROM users AS user_alias (){stop} - - - - + + + + Basic operations with :class:`~sqlalchemy.orm.query.Query` include issuing LIMIT and OFFSET, most conveniently using Python array slices and typically in @@ -697,8 +681,8 @@ conjunction with ORDER BY: FROM users ORDER BY users.id LIMIT ? OFFSET ? (2, 1){stop} - - + + and filtering results, which is accomplished either with :func:`~sqlalchemy.orm.query.Query.filter_by`, which uses keyword arguments: @@ -747,8 +731,7 @@ users named "ed" with a full name of "Ed Jones", you can call FROM users WHERE users.name = ? AND users.fullname = ? ('ed', 'Ed Jones') - {stop} - + {stop} Common Filter Operators ----------------------- @@ -826,7 +809,7 @@ non-iterator value. :meth:`~sqlalchemy.orm.query.Query.all()` returns a list: FROM users WHERE users.name LIKE ? ORDER BY users.id ('%ed',) - {stop}[, ] + {stop}[, ] :meth:`~sqlalchemy.orm.query.Query.first()` applies a limit of one and returns the first result as a scalar: @@ -842,7 +825,7 @@ the first result as a scalar: WHERE users.name LIKE ? ORDER BY users.id LIMIT ? OFFSET ? ('%ed', 1, 0) - {stop} + {stop} :meth:`~sqlalchemy.orm.query.Query.one()`, fully fetches all rows, and if not exactly one object identity or composite row is present in the result, raises @@ -924,7 +907,7 @@ method: FROM users WHERE id + {stop} To use an entirely string-based statement, using :meth:`~sqlalchemy.orm.query.Query.from_statement()`; just ensure that the @@ -938,7 +921,7 @@ mapper (below illustrated using an asterisk): ... params(name='ed').all() SELECT * FROM users where name=? ('ed',) - {stop}[] + {stop}[] You can use :meth:`~sqlalchemy.orm.query.Query.from_statement()` to go completely "raw", using string names to identify desired columns: @@ -1058,6 +1041,16 @@ counting called :meth:`~sqlalchemy.orm.query.Query.count()`: ('%ed',) {stop}2 +.. sidebar:: Counting on ``count()`` + + :meth:`.Query.count` used to be a very complicated method + when it would try to guess whether or not a subquery was needed + around the + existing query, and in some exotic cases it wouldn't do the right thing. + Now that it uses a simple subquery every time, it's only two lines long + and always returns the right answer. Use ``func.count()`` if a + particular statement absolutely cannot tolerate the subquery being present. + The :meth:`~.Query.count()` method is used to determine how many rows the SQL statement would return. Looking at the generated SQL above, SQLAlchemy always places whatever it is we are @@ -1125,11 +1118,8 @@ declarative, we define this table along with its mapped class, ``Address``: ... ... user = relationship("User", backref=backref('addresses', order_by=id)) ... - ... def __init__(self, email_address): - ... self.email_address = email_address - ... ... def __repr__(self): - ... return "" % self.email_address + ... return "" % self.email_address The above class introduces the :class:`.ForeignKey` construct, which is a directive applied to :class:`.Column` that indicates that values in this @@ -1220,7 +1210,7 @@ default, the collection is a Python list. .. sourcecode:: python+sql - >>> jack = User('jack', 'Jack Bean', 'gjffdd') + >>> jack = User(name='jack', fullname='Jack Bean', password='gjffdd') >>> jack.addresses [] @@ -1241,10 +1231,10 @@ using any SQL: .. sourcecode:: python+sql >>> jack.addresses[1] - + >>> jack.addresses[1].user - + Let's add and commit ``Jack Bean`` to the database. ``jack`` as well as the two ``Address`` members in his ``addresses`` collection are both added to the @@ -1278,7 +1268,7 @@ Querying for Jack, we get just Jack back. No SQL is yet issued for Jack's addre ('jack',) {stop}>>> jack - + Let's look at the ``addresses`` collection. Watch the SQL: @@ -1292,7 +1282,7 @@ Let's look at the ``addresses`` collection. Watch the SQL: FROM addresses WHERE ? = addresses.user_id ORDER BY addresses.id (5,) - {stop}[, ] + {stop}[, ] When we accessed the ``addresses`` collection, SQL was suddenly issued. This is an example of a **lazy loading relationship**. The ``addresses`` collection @@ -1320,7 +1310,8 @@ Below we load the ``User`` and ``Address`` entities at once using this method: ... filter(User.id==Address.user_id).\ ... filter(Address.email_address=='jack@google.com').\ ... all(): # doctest: +NORMALIZE_WHITESPACE - ... print u, a + ... print u + ... print a SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, @@ -1332,10 +1323,11 @@ Below we load the ``User`` and ``Address`` entities at once using this method: WHERE users.id = addresses.user_id AND addresses.email_address = ? ('jack@google.com',) - {stop} + {stop} + -The actual SQL JOIN syntax, on the other hand, is most easily achieved using the :meth:`.Query.join` -method: +The actual SQL JOIN syntax, on the other hand, is most easily achieved +using the :meth:`.Query.join` method: .. sourcecode:: python+sql @@ -1349,7 +1341,7 @@ method: FROM users JOIN addresses ON users.id = addresses.user_id WHERE addresses.email_address = ? ('jack@google.com',) - {stop}[] + {stop}[] :meth:`.Query.join` knows how to join between ``User`` and ``Address`` because there's only one foreign key between them. If there @@ -1459,11 +1451,11 @@ accessible through an attribute called ``c``: ON users.id = anon_1.user_id ORDER BY users.id ('*',) - {stop} None - None - None - None - 2 + {stop} None + None + None + None + 2 Selecting Entities from Subqueries ---------------------------------- @@ -1480,7 +1472,8 @@ to associate an "alias" of a mapped class to a subquery: >>> adalias = aliased(Address, stmt) >>> for user, address in session.query(User, adalias).\ ... join(adalias, User.addresses): # doctest: +NORMALIZE_WHITESPACE - ... print user, address + ... print user + ... print address SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, @@ -1496,7 +1489,8 @@ to associate an "alias" of a mapped class to a subquery: WHERE addresses.email_address != ?) AS anon_1 ON users.id = anon_1.user_id ('j25@yahoo.com',) - {stop} + {stop} + Using EXISTS ------------ @@ -1657,10 +1651,10 @@ very easy to use: ORDER BY anon_1.users_id, addresses.id ('jack',) {stop}>>> jack - + >>> jack.addresses - [, ] + [, ] Joined Load ------------- @@ -1693,10 +1687,10 @@ will emit the extra join regardless: ('jack',) {stop}>>> jack - + >>> jack.addresses - [, ] + [, ] Note that even though the OUTER JOIN resulted in two rows, we still only got one instance of ``User`` back. This is because :class:`.Query` applies a "uniquing" @@ -1754,10 +1748,10 @@ attribute: ('jack',) {stop}>>> jacks_addresses - [, ] + [, ] >>> jacks_addresses[0].user - + For more information on eager loading, including how to configure various forms of loading by default, see the section :doc:`/orm/loading`. @@ -1837,10 +1831,12 @@ including the cascade configuration (we'll leave the constructor out too):: ... fullname = Column(String) ... password = Column(String) ... - ... addresses = relationship("Address", backref='user', cascade="all, delete, delete-orphan") + ... addresses = relationship("Address", backref='user', + ... cascade="all, delete, delete-orphan") ... ... def __repr__(self): - ... return "" % (self.name, self.fullname, self.password) + ... return "" % ( + ... self.name, self.fullname, self.password) Then we recreate ``Address``, noting that in this case we've created the ``Address.user`` relationship via the ``User`` class already:: @@ -1852,7 +1848,7 @@ via the ``User`` class already:: ... user_id = Column(Integer, ForeignKey('users.id')) ... ... def __repr__(self): - ... return "" % self.email_address + ... return "" % self.email_address Now when we load Jack (below using :meth:`~.Query.get`, which loads by primary key), removing an address from his ``addresses`` collection will result in that @@ -1993,6 +1989,11 @@ via the ``post_keywords`` table:: ... def __init__(self, keyword): ... self.keyword = keyword +.. note:: + + The above class declarations illustrate explicit ``__init__()`` methods. + Remember, when using Declarative, it's optional! + Above, the many-to-many relationship is ``BlogPost.keywords``. The defining feature of a many-to-many relationship is the ``secondary`` keyword argument which references a :class:`~sqlalchemy.schema.Table` object representing the @@ -2114,7 +2115,7 @@ keyword string 'firstpost'": AND keywords.id = post_keywords.keyword_id AND keywords.keyword = ?) ('firstpost',) - {stop}[BlogPost("Wendy's Blog Post", 'This is a test', )] + {stop}[BlogPost("Wendy's Blog Post", 'This is a test', )] If we want to look up just Wendy's posts, we can tell the query to narrow down to her as a parent: @@ -2136,7 +2137,7 @@ to her as a parent: AND keywords.id = post_keywords.keyword_id AND keywords.keyword = ?)) (2, 'firstpost') - {stop}[BlogPost("Wendy's Blog Post", 'This is a test', )] + {stop}[BlogPost("Wendy's Blog Post", 'This is a test', )] Or we can use Wendy's own ``posts`` relationship, which is a "dynamic" relationship, to query straight from there: @@ -2157,7 +2158,7 @@ relationship, to query straight from there: AND keywords.id = post_keywords.keyword_id AND keywords.keyword = ?)) (2, 'firstpost') - {stop}[BlogPost("Wendy's Blog Post", 'This is a test', )] + {stop}[BlogPost("Wendy's Blog Post", 'This is a test', )] Further Reference ================== diff --git a/doc/build/static/docs.css b/doc/build/static/docs.css index 191a2041cb..bd08f3b209 100644 --- a/doc/build/static/docs.css +++ b/doc/build/static/docs.css @@ -322,10 +322,16 @@ th.field-name { text-align:right; } +div.section { + clear:right; +} div.note, div.warning, p.deprecated, div.topic, div.admonition { background-color:#EEFFEF; } +.footnote { + font-size: .95em; +} div.faq { background-color: #EFEFEF; @@ -348,7 +354,7 @@ div.sidebar { background-color: #FFFFEE; border: 1px solid #DDDDBB; float: right; - margin: 0 0 0.5em 1em; + margin: 10px 0 10px 1em; padding: 7px 7px 0; width: 40%; font-size:.9em; diff --git a/doc/build/testdocs.py b/doc/build/testdocs.py index 9d84808e51..815aa86694 100644 --- a/doc/build/testdocs.py +++ b/doc/build/testdocs.py @@ -60,8 +60,7 @@ def replace_file(s, newfile): raise ValueError("Couldn't find suitable create_engine call to replace '%s' in it" % oldfile) return s -#for filename in 'orm/tutorial','core/tutorial',: -for filename in 'core/tutorial',: +for filename in 'orm/tutorial','core/tutorial',: filename = '%s.rst' % filename s = open(filename).read() #s = replace_file(s, ':memory:')