the tests. [ticket:970]
- orm
+ - Session.mapper is now *deprecated*.
+ Call session.add() if you'd like a free-standing object to be
+ part of your session. Otherwise, a DIY version of
+ Session.mapper is now documented at
+ http://www.sqlalchemy.org/trac/wiki/UsageRecipes/SessionAwareMapper
+ The method will remain deprecated throughout 0.6.
+
- Fixed bug introduced in 0.5.4 whereby Composite types
fail when default-holding columns are flushed.
columns, has been enhanced such that the fk->itself aspect of the
relation won't be used to determine relation direction.
+ - repaired non-working attributes.set_committed_value function.
+
- Trimmed the pickle format for InstanceState which should further
reduce the memory footprint of pickled instances. The format
should be backwards compatible with that of 0.5.4 and previous.
Specifying Alternate Join Conditions to relation()
---------------------------------------------------
-
-The ``relation()`` function uses the foreign key relationship between the parent and child tables to formulate the **primary join condition** between parent and child; in the case of a many-to-many relationship it also formulates the **secondary join condition**. If you are working with a ``Table`` which has no ``ForeignKey`` objects on it (which can be the case when using reflected tables with MySQL), or if the join condition cannot be expressed by a simple foreign key relationship, use the ``primaryjoin`` and possibly ``secondaryjoin`` conditions to create the appropriate relationship.
+The ``relation()`` function uses the foreign key relationship between the parent and child tables to formulate the **primary join condition** between parent and child; in the case of a many-to-many relationship it also formulates the **secondary join condition**::
+
+ one to many/many to one:
+ ------------------------
+
+ parent_table --> parent_table.c.id == child_table.c.parent_id --> child_table
+ primaryjoin
+
+ many to many:
+ -------------
+
+ parent_table --> parent_table.c.id == secondary_table.c.parent_id -->
+ primaryjoin
+
+ secondary_table.c.child_id == child_table.c.id --> child_table
+ secondaryjoin
+
+If you are working with a ``Table`` which has no ``ForeignKey`` objects on it (which can be the case when using reflected tables with MySQL), or if the join condition cannot be expressed by a simple foreign key relationship, use the ``primaryjoin`` and possibly ``secondaryjoin`` conditions to create the appropriate relationship.
In this example we create a relation ``boston_addresses`` which will only load the user addresses with a city of "Boston":
.. _alternate_collection_implementations:
+Rows that point to themselves / Mutually Dependent Rows
+-------------------------------------------------------
+
+This is a very specific case where relation() must perform an INSERT and a second UPDATE in order to properly populate a row (and vice versa an UPDATE and DELETE in order to delete without violating foreign key constraints). The two use cases are:
+
+ * A table contains a foreign key to itself, and a single row will have a foreign key value pointing to its own primary key.
+ * Two tables each contain a foreign key referencing the other table, with a row in each table referencing the other.
+
+For example::
+
+ user
+ ---------------------------------
+ user_id name related_user_id
+ 1 'ed' 1
+
+Or::
+
+ widget entry
+ ------------------------------------------- ---------------------------------
+ widget_id name favorite_entry_id entry_id name widget_id
+ 1 'somewidget' 5 5 'someentry' 1
+
+In the first case, a row points to itself. Technically, a database that uses sequences such as Postgres or Oracle can INSERT the row at once using a previously generated value, but databases which rely upon autoincrement-style primary key identifiers cannot. The ``relation()`` always assumes a "parent/child" model of row population during flush, so unless you are populating the primary key/foreign key columns directly, ``relation()`` needs to use two statements.
+
+In the second case, the "widget" row must be inserted before any referring "entry" rows, but then the "favorite_entry_id" column of that "widget" row cannot be set until the "entry" rows have been generated. In this case, it's typically impossible to insert the "widget" and "entry" rows using just two INSERT statements; an UPDATE must be performed in order to keep foreign key constraints fulfilled. The exception is if the foreign keys are configured as "deferred until commit" (a feature some databases support) and if the identifiers were populated manually (again essentially bypassing ``relation()``).
+
+To enable the UPDATE after INSERT / UPDATE before DELETE behavior on ``relation()``, use the ``post_update`` flag on *one* of the relations, preferably the many-to-one side::
+
+ mapper(Widget, widget, properties={
+ 'entries':relation(Entry, primaryjoin=widget.c.widget_id==entry.c.widget_id),
+ 'favorite_entry':relation(Entry, primaryjoin=widget.c.favorite_entry_id==entry.c.entry_id, post_update=True)
+ })
+
+When a structure using the above mapping is flushed, the "widget" row will be INSERTed minus the "favorite_entry_id" value, then all the "entry" rows will be INSERTed referencing the parent "widget" row, and then an UPDATE statement will populate the "favorite_entry_id" column of the "widget" table (it's one row at a time for the time being).
+
+
Alternate Collection Implementations
-------------------------------------
-
Mapping a one-to-many or many-to-many relationship results in a collection of values accessible through an attribute on the parent instance. By default, this collection is a ``list``:
.. sourcecode:: python+sql
'DATE': lambda r: FBDate(),
'TIME': lambda r: FBTime(),
'TEXT': lambda r: FBString(r['flen']),
- 'INT64': lambda r: FBNumeric(precision=r['fprec'], length=r['fscale'] * -1), # This generically handles NUMERIC()
+ 'INT64': lambda r: FBNumeric(precision=r['fprec'], scale=r['fscale'] * -1), # This generically handles NUMERIC()
'DOUBLE': lambda r: FBFloat(),
'TIMESTAMP': lambda r: FBDateTime(),
'VARYING': lambda r: FBString(r['flen']),
"""
state, dict_ = instance_state(instance), instance_dict(instance)
- state.get_impl(key).set_committed_value(state, dict_, key, value)
+ state.get_impl(key).set_committed_value(state, dict_, value)
def set_attribute(instance, key, value):
"""Set the value of an attribute, firing history events.
# the MIT License: http://www.opensource.org/licenses/mit-license.php
import sqlalchemy.exceptions as sa_exc
-from sqlalchemy.util import ScopedRegistry, to_list, get_cls_kwargs
+from sqlalchemy.util import ScopedRegistry, to_list, get_cls_kwargs, deprecated
from sqlalchemy.orm import (
EXT_CONTINUE, MapperExtension, class_mapper, object_session
)
Session = scoped_session(sessionmaker(autoflush=True))
- To map classes so that new instances are saved in the current
- Session automatically, as well as to provide session-aware
- class attributes such as "query":
-
- mapper = Session.mapper
- mapper(Class, table, ...)
+ ... use session normally.
"""
self.registry().close()
self.registry.clear()
+ @deprecated("Session.mapper is deprecated. "
+ "Please see http://www.sqlalchemy.org/trac/wiki/UsageRecipes/SessionAwareMapper "
+ "for information on how to replicate its behavior.")
def mapper(self, *args, **kwargs):
- """return a mapper() function which associates this ScopedSession with the Mapper."""
+ """return a mapper() function which associates this ScopedSession with the Mapper.
+
+ DEPRECATED.
+
+ """
from sqlalchemy.orm import mapper
Address(email='two'),
])])
+ @testing.uses_deprecated()
def test_custom_mapper(self):
class MyExt(sa.orm.MapperExtension):
def create_instance(self):
except sa_exc.ArgumentError, e:
assert False
+class UtilTest(_base.ORMTest):
+ def test_helpers(self):
+ class Foo(object):
+ pass
+
+ class Bar(object):
+ pass
+
+ attributes.register_class(Foo)
+ attributes.register_class(Bar)
+ attributes.register_attribute(Foo, "coll", uselist=True, useobject=True)
+
+ f1 = Foo()
+ b1 = Bar()
+ b2 = Bar()
+ coll = attributes.init_collection(f1, "coll")
+ assert coll.data is f1.coll
+ assert attributes.get_attribute(f1, "coll") is f1.coll
+ attributes.set_attribute(f1, "coll", [b1])
+ assert f1.coll == [b1]
+ eq_(attributes.get_history(f1, "coll"), ([b1], [], []))
+ attributes.set_committed_value(f1, "coll", [b2])
+ eq_(attributes.get_history(f1, "coll"), ((), [b2], ()))
+
+ attributes.del_attribute(f1, "coll")
+ assert "coll" not in f1.__dict__
class BackrefTest(_base.ORMTest):
pass
@classmethod
+ @testing.uses_deprecated()
@testing.resolve_artifact_names
def setup_mappers(cls):
Session = scoped_session(sa.orm.create_session)
sso = SomeOtherObject.query().first()
assert SomeObject.query.filter_by(id=1).one().options[0].id == sso.id
+ @testing.uses_deprecated()
@testing.resolve_artifact_names
def test_query_compiles(self):
class Foo(object):
Session.mapper(Baz, table2, extension=ext)
assert hasattr(Baz, 'query')
+ @testing.uses_deprecated()
@testing.resolve_artifact_names
def test_default_constructor_state_not_shared(self):
scope = scoped_session(sa.orm.sessionmaker())
assert_raises(TypeError, C, foo='bar')
D(foo='bar')
+ @testing.uses_deprecated()
@testing.resolve_artifact_names
def test_validating_constructor(self):
s2 = SomeObject(someid=12)
assert_raises(sa.exc.ArgumentError, ValidatedOtherObject,
someid=12, bogus=345)
+ @testing.uses_deprecated()
@testing.resolve_artifact_names
def test_dont_clobber_methods(self):
class MyClass(object):
pass
@classmethod
+ @testing.uses_deprecated()
@testing.resolve_artifact_names
def setup_mappers(cls):
Session = scoped_session(sa.orm.sessionmaker())