diff options
-rw-r--r-- | doc/build/content/datamapping.txt | 263 | ||||
-rw-r--r-- | doc/build/templates/formatting.html | 6 |
2 files changed, 176 insertions, 93 deletions
diff --git a/doc/build/content/datamapping.txt b/doc/build/content/datamapping.txt index 02f3be4dc..c342460cb 100644 --- a/doc/build/content/datamapping.txt +++ b/doc/build/content/datamapping.txt @@ -31,7 +31,7 @@ For this tutorial we will use an in-memory-only SQLite database. This is an ea The `echo` flag is a shortcut to setting up SQLAlchemy logging, which is accomplished via Python's standard `logging` module. With it enabled, we'll see all the generated SQL produced. If you are working through this tutorial and want less output generated, set it to `False`. This tutorial will format the SQL behind a popup window so it doesn't get in our way; just click the "SQL" links to see whats being generated. -## Define and Create a Table +## Define and Create a Table {@name=tables} Next we want to tell SQLAlchemy about our tables. We will start with just a single table called `users`, which will store records for the end-users using our application (lets assume its a website). We define our tables all within a catalog called `MetaData`, using the `Table` construct, which resembles regular SQL CREATE TABLE syntax: @@ -64,7 +64,7 @@ Next, to tell the `MetaData` we'd actually like to create our `users_table` for So now our database is created, our initial schema is present, and our SQLAlchemy application knows all about the tables and columns in the database; this information is to be re-used by the Object Relational Mapper, as we'll see now. -## Define a Python Class to be Mapped +## Define a Python Class to be Mapped {@name=mapping} So lets create a rudimental `User` object to be mapped in the database. This object will for starters have three attributes, `name`, `fullname` and `password`. It only need subclass Python's built-in `object` class (i.e. its a new style class). We will give it a constructor so that it may conveniently be instantiated with its attributes at once, as well as a `__repr__` method so that we can get a nice string representation of it: @@ -78,7 +78,7 @@ So lets create a rudimental `User` object to be mapped in the database. This ob ... def __repr__(self): ... return "<User(%r,%r, %r)>" % (self.name, self.fullname, self.password) -## Setting up Mapping +## Setting up the Mapping With our `users_table` as well as our `User` class, we want to map the two together. That's where the SQLAlchemy ORM package comes in. We'll use the `mapper` function to create a **mapping** between `users_table` and `User`: @@ -192,7 +192,19 @@ Once we have a query, we can start loading objects. The Query object, when firs mary fred -...and the SQL will be issued at the point where the query is evaluated as a list. To narrow results, the two main methods are `filter()` and `filter_by()`. `filter_by()` uses keyword arguments, which translate into equality clauses joined together via 'AND': +...and the SQL will be issued at the point where the query is evaluated as a list. If you apply array slices before iterating, LIMIT and OFFSET are applied to the query: + + {python} + {sql}>>> for u in session.query(User)[1:3]: #doctest: +NORMALIZE_WHITESPACE + ... print u + SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password + FROM users ORDER BY users.oid + LIMIT 2 OFFSET 1 + [] + {stop}<User('wendy','Wendy Williams', 'foobar')> + <User('mary','Mary Contrary', 'xxg527')> + +Narrowing the results down is accomplished either with `filter_by()`, which uses keyword arguments: {python} {sql}>>> for user in session.query(User).filter_by(name='ed', fullname='Ed Jones'): @@ -202,8 +214,8 @@ Once we have a query, we can start loading objects. The Query object, when firs WHERE users.fullname = ? AND users.name = ? ORDER BY users.oid ['Ed Jones', 'ed'] {stop}<User('ed','Ed Jones', 'f8s7ccs')> - -`filter()`, on the other hand, works with constructed SQL expressions, like those described in [sql](rel:sql). These expressions are normally constructed from the class-level attributes of the mapped class. The basic idea is to reference the attribute, for example `User.name`, and then combine it with a regular Python operator and other arguments to issue a "generated SQL expression" to the `Query` object: + +...or `filter()`, which uses SQL expression language constructs. These allow you to use regular Python operators with the properties on your mapped class: {python} {sql}>>> for user in session.query(User).filter(User.name=='ed'): @@ -214,7 +226,7 @@ Once we have a query, we can start loading objects. The Query object, when firs ['ed'] {stop}<User('ed','Ed Jones', 'f8s7ccs')> -Instead of the `User` class you can also just as easily use the `Column` constructs attached to the `users_table` object directly: +You can also use the `Column` constructs attached to the `users_table` object to construct SQL expressions: {python} {sql}>>> for user in session.query(User).filter(users_table.c.name=='ed'): @@ -225,19 +237,7 @@ Instead of the `User` class you can also just as easily use the `Column` constru ['ed'] {stop}<User('ed','Ed Jones', 'f8s7ccs')> -Evaluating the query using an array slice returns a new Query which will apply LIMIT and OFFSET clauses when iterated: - - {python} - {sql}>>> for u in session.query(User)[1:3]: #doctest: +NORMALIZE_WHITESPACE - ... print u - SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password - FROM users ORDER BY users.oid - LIMIT 2 OFFSET 1 - [] - {stop}<User('wendy','Wendy Williams', 'foobar')> - <User('mary','Mary Contrary', 'xxg527')> - -A single array index adds LIMIT/OFFSET and returns a result immediately. Below, we'll substitute our `==` operator with a special SQL operator, `like()`: +Most common SQL operators are available, such as `LIKE`: {python} {sql}>>> session.query(User).filter(User.name.like('%ed'))[1] # doctest: +NORMALIZE_WHITESPACE @@ -248,114 +248,197 @@ A single array index adds LIMIT/OFFSET and returns a result immediately. Below, ['%ed'] {stop}<User('fred','Fred Flinstone', 'blah')> -There are also methods which will immediately issue the SQL represented by a `Query` without using an iterative context or array index; these methods are `all()`, `one()`, and `first()`. `all()` returns a list of all instances, `one()` returns exactly one instance as a scalar, and `first()` returns the first instance also as a scalar: +Note above our array index of `1` placed the appropriate LIMIT/OFFSET and returned a scalar result immediately. + +The `all()`, `one()`, and `first()` methods immediately issue SQL without using an iterative context or array index. `all()` returns a list: {python} - query = session.query(User).filter(users_table.c.name=='john') + >>> query = session.query(User).filter(User.name.like('%ed')) - # get all results into a list - allusers = query.all() + {sql}>>> query.all() + SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password + FROM users + WHERE users.name LIKE ? ORDER BY users.oid + ['%ed'] + {stop}[<User('ed','Ed Jones', 'f8s7ccs')>, <User('fred','Fred Flinstone', 'blah')>] - # get the first user - user = query.first() +`first()` applies a limit of one and returns the first result as a scalar: - # get exactly one user; raises an exception if not exactly one result is returned - user = query.one() + {python} + {sql}>>> query.first() + SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password + FROM users + WHERE users.name LIKE ? ORDER BY users.oid + LIMIT 1 OFFSET 0 + ['%ed'] + {stop}<User('ed','Ed Jones', 'f8s7ccs')> -Note that most methods on `Query` are *generative*, in that they return a new `Query` instance that is a modified version of the previous one. It's only when you evaluate the query in an iterative context, use an array index, or call `all()`, `first()`, or `one()` (as well as some other methods we'll cover later), that SQL is issued. For example, you can issue `filter()` or `filter_by()` as many times as needed; the various criteria are joined together using `AND`: +and `one()`, applies a limit of *two*, and if not exactly one row returned (no more, no less), raises an error: {python} - result = session.query(User).filter(users_table.c.user_id>224).filter_by(name='john'). - {sql} filter(users.c.fullname=='John Smith').all() - SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, - users.fullname AS users_fullname, users.password AS users_password + {sql}>>> try: + ... user = query.one() + ... except Exception, e: + ... print e + SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password FROM users - WHERE users.user_id>:users_user_id AND users.user_name = :users_user_name - AND users.fullname = :users_fullname - ORDER BY users.oid - {'users_user_name': 'john', 'users_fullname': 'John Smith', 'users_user_id': 224} + WHERE users.name LIKE ? ORDER BY users.oid + LIMIT 2 OFFSET 0 + ['%ed'] + {stop}Multiple rows returned for one() -If you need to use other conjunctions besides `AND`, all SQL conjunctions are available explicitly within expressions, such as `and_()` and `or_()`, when using `filter()`: +All `Query` methods that don't return a result instead return a new `Query` object, with modifications applied. Therefore you can call many query methods successively to build up the criterion you want: {python} - result = session.query(User).filter( - and_(users_table.c.user_id>224, or_(users_table.c.name=='john', users_table.c.name=='ed')) - ).all() + >>> query = session.query(User).filter(User.id<2).filter_by(name='ed') + {sql}>>> query.filter(User.fullname=='Ed Jones').all() + SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password + FROM users + WHERE users.id < ? AND users.name = ? AND users.fullname = ? ORDER BY users.oid + [2, 'ed', 'Ed Jones'] + {stop}[<User('ed','Ed Jones', 'f8s7ccs')>] -Sometimes, constructing criterion via expressions can be cumbersome. For a quick, string-based expression, the `filter()` method can also accomodate straight text: +If you need to use other conjunctions besides `AND`, all SQL conjunctions are available explicitly within expressions, such as `and_()` and `or_()`, when using `filter()`: {python} - {sql}result = session.query(User).filter("user_id>224").all() - SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, - users.fullname AS users_fullname, users.password AS users_password + >>> from sqlalchemy import and_, or_ + + {sql}>>> session.query(User).filter( + ... and_(User.id<224, or_(User.name=='ed', User.name=='wendy')) + ... ).all() + SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password FROM users - WHERE users.user_id>224 - ORDER BY users.oid - {} + WHERE users.id < ? AND (users.name = ? OR users.name = ?) ORDER BY users.oid + [224, 'ed', 'wendy'] + {stop}[<User('ed','Ed Jones', 'f8s7ccs')>, <User('wendy','Wendy Williams', 'foobar')>] + +You also have full ability to use literal strings to construct SQL. For a single criterion, use a string with `filter()`: -When using text, bind parameters can be specified the same way as in a `text()` clause, using a colon. To specify the bind parameter values, use the `params()` method: + {python} + {sql}>>> for user in session.query(User).filter("id<224").all(): + ... print user.name + SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password + FROM users + WHERE id<224 ORDER BY users.oid + [] + {stop}ed + wendy + mary + fred + +Bind parameters can be specified with string-based SQL, using a colon. To specify the values, use the `params()` method: {python} - {sql}result = session.query(User).filter("user_id>:value and user_name=:name").params(value=224, name='jack').all() - SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, - users.fullname AS users_fullname, users.password AS users_password + {sql}>>> session.query(User).filter("id<:value and name=:name").params(value=224, name='fred').one() # doctest: +NORMALIZE_WHITESPACE + SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password FROM users - WHERE users.user_id>:value and user_name=:name - ORDER BY users.oid - {'value': 224, 'name': 'jack'} + WHERE id<? and name=? ORDER BY users.oid + LIMIT 2 OFFSET 0 + [224, 'fred'] + {stop}<User('fred','Fred Flinstone', 'blah')> -It's also straightforward to use an entirely string-based statement, using `from_statement()`; just ensure that the columns clause of the statement contains the column names normally used by the mapper (below illustrated using an asterisk): +Note that when we use constructed SQL expressions, bind paramters are generated for us automatically; we don't need to worry about them. + +To use an entirely string-based statement, using `from_statement()`; just ensure that the columns clause of the statement contains the column names normally used by the mapper (below illustrated using an asterisk): {python} - {sql}result = session.query(User).from_statement("SELECT * FROM users").all() + {sql}>>> result = session.query(User).from_statement("SELECT * FROM users").all() SELECT * FROM users - {} + [] -`from_statement()` can also accomodate full `select()` constructs: +`from_statement()` can also accomodate full `select()` constructs. These are described in the [sql](rel:sql): {python} - result = session.query(User).from_statement( - select([Users], users.c.name<'e', having=users.c.name==func.max(users.c.name), group_by=[c for c in users.c]) - {sql} ).all() - SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, - users.fullname AS users_fullname, users.password AS users_password + >>> from sqlalchemy import select, func + + {sql}>>> session.query(User).from_statement( + ... select([users_table], select([func.max(users_table.c.name)]).label('maxuser')==users_table.c.name) + ... ).all() + SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password FROM users - WHERE users.user_name>:users_user_name HAVING users.user_name == max(users.user_name) - GROUP BY users.user_id, users.user_name, users.fullname, users.password - ORDER BY users.oid - {'users_user_name': 'e'} + WHERE (SELECT max(users.name) + FROM users) = users.name + [] + {stop}[<User('wendy','Wendy Williams', 'foobar')>] -The current criterion represented by a `Query` can be distilled into a count of rows using `count()`. This is another function which executes SQL immediately, returning an integer result: +There's also a way to combine scalar results with objects, using `add_column()`. This is often used for functions and aggregates. When `add_column()` (or its cousin `add_entity()`, described later) is used, tuples are returned: + + {python} + {sql}>>> for r in session.query(User).add_column(select([func.max(users_table.c.name)]).label('maxuser')): + ... print r + SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password, (SELECT max(users.name) + FROM users) AS maxuser + FROM users ORDER BY users.oid + [] + {stop}(<User('ed','Ed Jones', 'f8s7ccs')>, u'wendy') + (<User('wendy','Wendy Williams', 'foobar')>, u'wendy') + (<User('mary','Mary Contrary', 'xxg527')>, u'wendy') + (<User('fred','Fred Flinstone', 'blah')>, u'wendy') + +## Building a One-to-Many Relation + +We've spent a lot of time dealing with just one class, and one table. Let's now look at how SQLAlchemy deals with two tables, which have a relationship to each other. Let's say that the users in our system also can store any number of email addresses associated with their username. This implies a basic one to many association from the `users_table` to a new table which stores email addresess, which we will call `addresses`. We will also create a relationship between this new table to the users table, using a `ForeignKey`: {python} - {sql}num = session.query(Users).filter(users_table.c.user_id>224).count() - SELECT count(users.id) FROM users WHERE users.user_id>:users_user_id - {'users_user_id': 224} + >>> from sqlalchemy import ForeignKey -To add limit and offset values explicitly at any time, you can use `limit()` and `offset()`: + >>> addresses_table = Table('addresses', metadata, + ... Column('id', Integer, primary_key=True), + ... Column('email_address', String(100), nullable=False), + ... Column('user_id', Integer, ForeignKey('users.id'))) + +Another call to `create_all()` will skip over our `users` table and build just the new `addresses` table: {python} - {sql}result = session.query(User).limit(20).offset(5).all() - SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, - users.fullname AS users_fullname, users.password AS users_password - FROM users ORDER BY users.oid - LIMIT 20 OFFSET 5 + {sql}>>> metadata.create_all(engine) # doctest: +NORMALIZE_WHITESPACE + PRAGMA table_info("users") {} + PRAGMA table_info("addresses") + {} + CREATE TABLE addresses ( + id INTEGER NOT NULL, + email_address VARCHAR(100) NOT NULL, + user_id INTEGER, + PRIMARY KEY (id), + FOREIGN KEY(user_id) REFERENCES users (id) + ) + None + COMMIT -Ordering is applied, using `Column` objects and related SQL constructs, with `order_by()`: +For our ORM setup, we're going to start all over again. We will first close out our `Session` and clear all `Mapper` objects: {python} - query = session.query(User).order_by(desc(users_table.c.user_name)) - {sql}for user in query: - print user - SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, - users.fullname AS users_fullname, users.password AS users_password - FROM users ORDER BY users.user_name DESC - {} + >>> from sqlalchemy.orm import clear_mappers + >>> session.close() + >>> clear_mappers() -There's also a way to combine scalar results with objects, using `add_column()`. This is often used for functions and aggregates. When `add_column()` (or its cousin `add_entity()`, described later) is used, tuples are returned: +Our `User` class, still around, reverts to being just a plain old class. Lets create an `Address` class to represent a user's email address: {python} - for r in session.query(User).add_column(func.max(users_table.c.name)).group_by([c for c in users_table.c]): - print "user:", r[0] - print "max name:", r[1] + >>> class Address(object): + ... def __init__(self, email_address): + ... self.email_address = email_address + ... + ... def __repr__(self): + ... return "<Address(%r)>" % self.email_address + +Now comes the fun part. We define a mapper for each class, and associate them using a function called `relation()`. We can define each mapper in any order we want: + + {python} + >>> from sqlalchemy.orm import relation + + >>> mapper(User, users_table, properties={ # doctest: +ELLIPSIS + ... 'addresses':relation(Address, backref='user') + ... }) + <sqlalchemy.orm.mapper.Mapper object at 0x...> + + >>> mapper(Address, addresses_table) # doctest: +ELLIPSIS + <sqlalchemy.orm.mapper.Mapper object at 0x...> + +Above, the new thing we see is that `User` has defined a relation named `addresses`, which will reference a list of `Address` objects. How does it know it's a list ? SQLAlchemy figures it out for you, based on the foreign key relationship between `users_table` and `addresses_table`. Now when we create a `User`, it automatically has this collection present: + + {python} + >>> jack = User('jack', 'Jack Bean', 'gjffdd') + >>> jack.addresses + [] +
\ No newline at end of file diff --git a/doc/build/templates/formatting.html b/doc/build/templates/formatting.html index 629ec87cc..1c4013a9f 100644 --- a/doc/build/templates/formatting.html +++ b/doc/build/templates/formatting.html @@ -18,7 +18,7 @@ <% content = capture(caller.body) re2 = re.compile(r"'''PYESC(.+?)PYESC'''", re.S) - content = re2.sub(lambda m: m.group(1), content) + content = re2.sub(lambda m: filters.url_unescape(m.group(1)), content) item = toc.get_by_path(path) subsection = item.depth > 1 @@ -55,7 +55,7 @@ <span class="codeline">${ caller.body() }</span> </%def> -<%def name="code(toc, paged, extension, title=None, syntaxtype='mako', html_escape=False, use_sliders=False)"> +<%def name="code(toc, paged, extension, title=None, syntaxtype='mako', html_escape=True, use_sliders=False)"> <% def fix_indent(f): f =string.expandtabs(f, 4) @@ -123,7 +123,7 @@ javascript:togglePopbox('${name}', '${show}', '${hide}') <% href = capture(popboxlink) %> - '''PYESC${nav.link(href=href, text=link, class_="codepoplink")}PYESC''' + '''PYESC${capture(nav.link, href=href, text=link, class_="codepoplink") | u}PYESC''' </%def> <%def name="codepopper()" filter="trim"> |