From: Mike Bayer Date: Sun, 4 Dec 2005 00:19:07 +0000 (+0000) Subject: dev X-Git-Tag: rel_0_1_0~265 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=67203d8d43c6248ba1b04cbcd8ba52e314721ef6;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git dev --- diff --git a/doc/build/content/adv_datamapping.myt b/doc/build/content/adv_datamapping.myt index 54f4dbaaf2..31da6f7716 100644 --- a/doc/build/content/adv_datamapping.myt +++ b/doc/build/content/adv_datamapping.myt @@ -6,7 +6,7 @@

To start, heres the tables we will work with again:

<&|formatting.myt:code&> from sqlalchemy import * - db = create_engine('sqlite', {'filename':'mydb'}, echo=True) + db = create_engine('sqlite://filename=mydb', echo=True) # a table to store users users = Table('users', db, @@ -39,22 +39,123 @@ -<&|doclib.myt:item, name="creating", description="Creating Mappers" &> +<&|doclib.myt:item, name="creatingrelations", description="Creating Mapper Relations" &> <&|doclib.myt:item, name="customjoin", description="Custom Join Conditions" &> +

When creating relations on a mapper, most examples so far have illustrated the mapper and relationship joining up based on the foreign keys of the tables they represent. in fact, this "automatic" inspection can be completely circumvented using the primaryjoin and secondaryjoin arguments to relation, as in this example which creates a User object which has a relationship to all of its Addresses which are in Boston: <&|formatting.myt:code&> - + class User(object): + pass + class Address(object): + pass + Address.mapper = mapper(Address, addresses) + User.mapper = mapper(User, users, properties={ + 'boston_addreses' : relation(Address.mapper, primaryjoin= + and_(users.c.user_id==Address.c.user_id, + Addresses.c.city=='Boston')) + }) + +

Many to many relationships can be customized by one or both of primaryjoin and secondaryjoin, shown below with just the default many-to-many relationship explicitly set:

+ <&|formatting.myt:code&> + class User(object): + pass + class Keyword(object): + pass + Keyword.mapper = mapper(Keyword, keywords) + User.mapper = mapper(User, users, properties={ + 'keywords':relation(Keyword.mapper, + primaryjoin=users.c.user_id==userkeywords.c.user_id, + secondaryjoin=userkeywords.c.keyword_id==keywords.c.keyword_id + ) + }) + + <&|doclib.myt:item, name="multiplejoin", description="Lazy/Eager Joins Multiple Times to One Table" &> - <&|doclib.myt:item, name="loadingoptions", description="Loading Options" &> +

The previous example leads in to the idea of joining against the same table multiple times. Below is a User object that has lists of its Boston and New York addresses, both lazily loaded when they are first accessed:

<&|formatting.myt:code&> - + User.mapper = mapper(User, users, properties={ + 'boston_addreses' : relation(Address.mapper, primaryjoin= + and_(users.c.user_id==Address.c.user_id, + Addresses.c.city=='Boston')), + 'newyork_addresses' : relation(Address.mapper, primaryjoin= + and_(users.c.user_id==Address.c.user_id, + Addresses.c.city=='New York')), + }) + +

A complication arises with the above pattern if you want the relations to be eager loaded. Since there will be two separate joins to the addresses table during an eager load, an alias needs to be used to separate them. You can create an alias of the addresses table to separate them, but then you are in effect creating a brand new mapper for each property, unrelated to the main Address mapper, which can create problems with commit operations. So an additional argument selectalias can be used with an eager relationship to specify the alias to be used just within the eager query:

+ <&|formatting.myt:code&> + User.mapper = mapper(User, users, properties={ + 'boston_addreses' : relation(Address.mapper, primaryjoin= + and_(User.c.user_id==Address.c.user_id, + Addresses.c.city=='Boston'), lazy=False, selectalias='boston_ad'), + 'newyork_addresses' : relation(Address.mapper, primaryjoin= + and_(User.c.user_id==Address.c.user_id, + Addresses.c.city=='New York'), lazy=False, selectalias='newyork_ad'), + }) + + <&formatting.myt:poplink&>u = User.mapper.select() + + <&|formatting.myt:codepopper, link="sql" &> + SELECT users.user_id AS users_user_id, users.user_name AS users_user_name, + users.password AS users_password, + boston_ad.address_id AS boston_ad_address_id, boston_ad.user_id AS boston_ad_user_id, + boston_ad.street AS boston_ad_street, boston_ad.city AS boston_ad_city, + boston_ad.state AS boston_ad_state, boston_ad.zip AS boston_ad_zip, + newyork_ad.address_id AS newyork_ad_address_id, newyork_ad.user_id AS newyork_ad_user_id, + newyork_ad.street AS newyork_ad_street, newyork_ad.city AS newyork_ad_city, + newyork_ad.state AS newyork_ad_state, newyork_ad.zip AS newyork_ad_zip + FROM users + LEFT OUTER JOIN addresses AS boston_ad ON users.user_id = boston_ad.user_id + AND boston_ad.city = :addresses_city + LEFT OUTER JOIN addresses AS newyork_ad ON users.user_id = newyork_ad.user_id + AND newyork_ad.city = :addresses_city_1 + ORDER BY users.oid, boston_ad.oid, newyork_ad.oid + {'addresses_city_1': 'New York', 'addresses_city': 'Boston'} + + + + + <&|doclib.myt:item, name="relationoptions", description="Relation Options" &> + Keyword options to the relation function include: + <&|doclib.myt:item, name="options", description="Mapper Options" &> +

The options method of mapper produces a copy of the mapper, with modified properties and/or options. This makes it easy to take a mapper and just change a few things on it. The method takes a variable number of MapperOption objects which know how to change specific things about the mapper. The four available options are eagerload, lazyload, noload and extension.

+

An example of a mapper with a lazy load relationship, upgraded to an eager load relationship: + <&|formatting.myt:code&> + class User(object): + pass + class Address(object): + pass + + # a 'lazy' relationship + User.mapper = mapper(User, users, properties = { + 'addreses':relation(Address, addresses, lazy=True) + }) + + # copy the mapper and convert 'addresses' to be eager + eagermapper = User.mapper.options(eagerload('addresses')) + + +

The load options also can take keyword arguments that apply to the new relationship. To take the "double" address lazy relationship from the previous section and upgrade it to eager, adding the "selectalias" keywords as well:

<&|formatting.myt:code&> - + m = User.mapper.options( + eagerload('boston_addresses', selectalias='boston_ad'), + eagerload('newyork_addresses', selectalias='newyork_ad') + ) + <&|doclib.myt:item, name="custom", description="Custom Queries" &> @@ -197,6 +298,25 @@

This kind of mapper goes through a lot of extra effort when saving and deleting items, to determine the correct dependency graph of nodes within the tree.

+<&|doclib.myt:item, name="circular", description="Circular Mapping" &> +

Oftentimes it is necessary for two mappers to be related to each other. With a datamodel that consists of Users that store Addresses, you might have an Address object and want to access the "user" attribute on it, or have a User object and want to get the list of Address objects. To achieve this involves creating the first mapper not referencing the second, then creating the second mapper referencing the first, then adding references to the first mapper to reference the second:

+<&|formatting.myt:code&> + class User(object): + pass + class Address(object): + pass + User.mapper = mapper(User, users) + Address.mapper = mapper(Address, addresses, properties={ + 'user':relation(User.mapper) + }) + User.mapper.add_property('addresses', relation(Address.mapper)) + +

Note that with a circular relationship as above, you cannot declare both relationships as "eager" relationships, since that produces a circular query situation which will generate a recursion exception. So what if you want to then load an Address and its User eagerly? Just make a second mapper using options: +<&|formatting.myt:code&> + eagermapper = Address.mapper.options(eagerload('user')) + s = eagermapper.select(Address.c.address_id==12) + + <&|doclib.myt:item, name="resultset", description="Result-Set Mapping" &>

Take any result set and feed it into a mapper to produce objects. Multiple mappers can be combined to retrieve unrelated objects from the same row in one step.

<&|formatting.myt:code&> diff --git a/doc/build/content/datamapping.myt b/doc/build/content/datamapping.myt index 5c4c3e4e06..71747b4200 100644 --- a/doc/build/content/datamapping.myt +++ b/doc/build/content/datamapping.myt @@ -596,7 +596,7 @@ INSERT INTO article_keywords (article_id, keyword_id) VALUES (:article_id, :keyw # lazy loading for that. m = mapper(Article, articles, properties=dict( keywords = relation(KeywordAssociation, itemkeywords, lazy=False, association=Keyword, - primary_keys = [itemkeywords.c.article_id, itemkeywords.c.keyword_id], + primary_key = [itemkeywords.c.article_id, itemkeywords.c.keyword_id], properties={ 'keyword' : relation(Keyword, lazy = False), 'user' : relation(User, lazy = True) diff --git a/doc/build/content/dbengine.myt b/doc/build/content/dbengine.myt index 754b4f71f9..2af9fb25b4 100644 --- a/doc/build/content/dbengine.myt +++ b/doc/build/content/dbengine.myt @@ -31,6 +31,15 @@ 'user':'scott', 'password':'tiger'}, **opts) + # mysql + mysql_engine = create_engine('mysql', + { + 'db':'mydb', + 'user':'scott', + 'passwd':'tiger', + 'host':'127.0.0.1' + } + **opts) # oracle oracle_engine = create_engine('oracle', {'dsn':'mydsn', @@ -48,7 +57,11 @@ )

The second argument is a dictionary whose key/value pairs will be passed to the underlying DBAPI connect() method as keyword arguments. Any keyword argument supported by the DBAPI module can be in this dictionary.

-

An additional URL-string based calling style will also be added soon, as this is a highly requested feature. +

Engines can also be loaded by URL. The above format is converted into <% '://key=val&key=val' |h %>: + <&|formatting.myt:code&> + sqlite_engine = create_engine('sqlite://filename=querytest.db') + postgres_engine = create_engine('postgres://database=test&user=scott&password=tiger') +

<&|doclib.myt:item, name="options", description="Database Engine Options" &> diff --git a/doc/build/content/docstrings.myt b/doc/build/content/docstrings.myt index b45c90f757..24f6e52c4e 100644 --- a/doc/build/content/docstrings.myt +++ b/doc/build/content/docstrings.myt @@ -6,8 +6,7 @@ import sqlalchemy.engine as engine import sqlalchemy.sql as sql import sqlalchemy.pool as pool - import sqlalchemy.mapper as mapper - import sqlalchemy.objectstore as objectstore + import sqlalchemy.mapping as mapping @@ -15,6 +14,6 @@ <& pydoc.myt:obj_doc, obj=engine, classes=[engine.SQLEngine, engine.ResultProxy, engine.RowProxy] &> <& pydoc.myt:obj_doc, obj=sql &> <& pydoc.myt:obj_doc, obj=pool, classes=[pool.DBProxy, pool.Pool, pool.QueuePool, pool.SingletonThreadPool] &> -<& pydoc.myt:obj_doc, obj=mapper &> -<& pydoc.myt:obj_doc, obj=objectstore, classes=[objectstore.UnitOfWork] &> +<& pydoc.myt:obj_doc, obj=mapping &> +<& pydoc.myt:obj_doc, obj=mapping.objectstore, classes=[mapping.objectstore.UnitOfWork] &> \ No newline at end of file diff --git a/doc/build/content/metadata.myt b/doc/build/content/metadata.myt index b0d66a4986..dcc0e5a11d 100644 --- a/doc/build/content/metadata.myt +++ b/doc/build/content/metadata.myt @@ -43,7 +43,7 @@ # ... # get the table's primary key columns - for primary_key in employees.primary_keys: + for primary_key in employees.primary_key: # ... # get the table's foreign key objects: