summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2010-03-17 17:48:29 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2010-03-17 17:48:29 -0400
commit065fcbd9d2b463920d439c20d99a5a1cd7f216ed (patch)
tree2230349df4cc7bc884f128e2c463c2e334152b7e /lib/sqlalchemy
parent95c0214356a55b6bc051d2b779e54d6de7b0b22e (diff)
downloadsqlalchemy-065fcbd9d2b463920d439c20d99a5a1cd7f216ed.tar.gz
- The official name for the relation() function is now
relationship(), to eliminate confusion over the relational algebra term. relation() however will remain available in equal capacity for the foreseeable future. [ticket:1740]
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/dialects/oracle/base.py2
-rw-r--r--lib/sqlalchemy/ext/associationproxy.py20
-rw-r--r--lib/sqlalchemy/ext/declarative.py42
-rw-r--r--lib/sqlalchemy/ext/orderinglist.py16
-rw-r--r--lib/sqlalchemy/ext/sqlsoup.py12
-rw-r--r--lib/sqlalchemy/orm/__init__.py62
-rw-r--r--lib/sqlalchemy/orm/collections.py2
-rw-r--r--lib/sqlalchemy/orm/dependency.py12
-rw-r--r--lib/sqlalchemy/orm/dynamic.py2
-rw-r--r--lib/sqlalchemy/orm/mapper.py6
-rw-r--r--lib/sqlalchemy/orm/properties.py78
-rw-r--r--lib/sqlalchemy/orm/query.py16
-rw-r--r--lib/sqlalchemy/orm/strategies.py14
-rw-r--r--lib/sqlalchemy/orm/unitofwork.py4
-rw-r--r--lib/sqlalchemy/orm/util.py10
15 files changed, 152 insertions, 146 deletions
diff --git a/lib/sqlalchemy/dialects/oracle/base.py b/lib/sqlalchemy/dialects/oracle/base.py
index 879a296a9..f76edabf2 100644
--- a/lib/sqlalchemy/dialects/oracle/base.py
+++ b/lib/sqlalchemy/dialects/oracle/base.py
@@ -90,7 +90,7 @@ is available at http://asktom.oracle.com/tkyte/update_cascade/index.html .
When using the SQLAlchemy ORM, the ORM has limited ability to manually issue
cascading updates - specify ForeignKey objects using the
"deferrable=True, initially='deferred'" keyword arguments,
-and specify "passive_updates=False" on each relation().
+and specify "passive_updates=False" on each relationship().
Oracle 8 Compatibility
----------------------
diff --git a/lib/sqlalchemy/ext/associationproxy.py b/lib/sqlalchemy/ext/associationproxy.py
index b63bd9b00..c7437d722 100644
--- a/lib/sqlalchemy/ext/associationproxy.py
+++ b/lib/sqlalchemy/ext/associationproxy.py
@@ -30,13 +30,13 @@ def association_proxy(target_collection, attr, **kw):
always in sync with *target_collection*, and mutations made to either
collection will be reflected in both.
- Implements a Python property representing a relation as a collection of
+ Implements a Python property representing a relationship as a collection of
simpler values. The proxied property will mimic the collection type of
- the target (list, dict or set), or, in the case of a one to one relation,
+ the target (list, dict or set), or, in the case of a one to one relationship,
a simple scalar value.
- :param target_collection: Name of the relation attribute we'll proxy to,
- usually created with :func:`~sqlalchemy.orm.relation`.
+ :param target_collection: Name of the relationship attribute we'll proxy to,
+ usually created with :func:`~sqlalchemy.orm.relationship`.
:param attr: Attribute on the associated instances we'll proxy for.
@@ -44,7 +44,7 @@ def association_proxy(target_collection, attr, **kw):
by this proxy property would look like [getattr(obj1, *attr*),
getattr(obj2, *attr*)]
- If the relation is one-to-one or otherwise uselist=False, then simply:
+ If the relationship is one-to-one or otherwise uselist=False, then simply:
getattr(obj, *attr*)
:param creator: optional.
@@ -58,14 +58,14 @@ def association_proxy(target_collection, attr, **kw):
If you want to construct instances differently, supply a *creator*
function that takes arguments as above and returns instances.
- For scalar relations, creator() will be called if the target is None.
+ For scalar relationships, creator() will be called if the target is None.
If the target is present, set operations are proxied to setattr() on the
associated object.
If you have an associated object with multiple attributes, you may set
up multiple association proxies mapping to different attributes. See
the unit tests for examples, and for examples of how creator() functions
- can be used to construct the scalar relation on-demand in this
+ can be used to construct the scalar relationship on-demand in this
situation.
:param \*\*kw: Passes along any other keyword arguments to
@@ -84,7 +84,7 @@ class AssociationProxy(object):
target_collection
Name of the collection we'll proxy to, usually created with
- 'relation()' in a mapper setup.
+ 'relationship()' in a mapper setup.
attr
Attribute on the collected instances we'll proxy for. For example,
@@ -117,7 +117,7 @@ class AssociationProxy(object):
sniffing the target collection. If your collection type can't be
determined by duck typing or you'd like to use a different
collection implementation, you may supply a factory function to
- produce those collections. Only applicable to non-scalar relations.
+ produce those collections. Only applicable to non-scalar relationships.
proxy_bulk_set
Optional, use with proxy_factory. See the _set() method for
@@ -316,7 +316,7 @@ class _AssociationCollection(object):
lazy_collection
A callable returning a list-based collection of entities (usually an
- object attribute managed by a SQLAlchemy relation())
+ object attribute managed by a SQLAlchemy relationship())
creator
A function that creates new target entities. Given one parameter:
diff --git a/lib/sqlalchemy/ext/declarative.py b/lib/sqlalchemy/ext/declarative.py
index fe45e6c17..c6423e7a0 100644
--- a/lib/sqlalchemy/ext/declarative.py
+++ b/lib/sqlalchemy/ext/declarative.py
@@ -49,7 +49,7 @@ added to the underlying :class:`~sqlalchemy.schema.Table` and
:func:`~sqlalchemy.orm.mapper()` definitions as appropriate::
SomeClass.data = Column('data', Unicode)
- SomeClass.related = relation(RelatedInfo)
+ SomeClass.related = relationship(RelatedInfo)
Classes which are mapped explicitly using
:func:`~sqlalchemy.orm.mapper()` can interact freely with declarative
@@ -96,11 +96,11 @@ objects::
mymetadata = MetaData()
Base = declarative_base(metadata=mymetadata)
-Configuring Relations
-=====================
+Configuring Relationships
+=========================
-Relations to other classes are done in the usual way, with the added
-feature that the class specified to :func:`~sqlalchemy.orm.relation()`
+Relationships to other classes are done in the usual way, with the added
+feature that the class specified to :func:`~sqlalchemy.orm.relationship()`
may be a string name. The "class registry" associated with ``Base``
is used at mapper compilation time to resolve the name into the actual
class object, which is expected to have been defined once the mapper
@@ -111,7 +111,7 @@ configuration is used::
id = Column(Integer, primary_key=True)
name = Column(String(50))
- addresses = relation("Address", backref="user")
+ addresses = relationship("Address", backref="user")
class Address(Base):
__tablename__ = 'addresses'
@@ -130,9 +130,9 @@ class using them::
id = Column(Integer, primary_key=True)
email = Column(String(50))
user_id = Column(Integer, ForeignKey('users.id'))
- user = relation(User, primaryjoin=user_id == User.id)
+ user = relationship(User, primaryjoin=user_id == User.id)
-In addition to the main argument for :func:`~sqlalchemy.orm.relation`,
+In addition to the main argument for :func:`~sqlalchemy.orm.relationship`,
other arguments which depend upon the columns present on an as-yet
undefined class may also be specified as strings. These strings are
evaluated as Python expressions. The full namespace available within
@@ -143,7 +143,7 @@ expression functions like :func:`~sqlalchemy.sql.expression.desc` and
class User(Base):
# ....
- addresses = relation("Address",
+ addresses = relationship("Address",
order_by="desc(Address.email)",
primaryjoin="Address.user_id==User.id")
@@ -151,14 +151,14 @@ As an alternative to string-based attributes, attributes may also be
defined after all classes have been created. Just add them to the target
class after the fact::
- User.addresses = relation(Address,
+ User.addresses = relationship(Address,
primaryjoin=Address.user_id==User.id)
-Configuring Many-to-Many Relations
-==================================
+Configuring Many-to-Many Relationships
+======================================
There's nothing special about many-to-many with declarative. The
-``secondary`` argument to :func:`~sqlalchemy.orm.relation` still
+``secondary`` argument to :func:`~sqlalchemy.orm.relationship` still
requires a :class:`~sqlalchemy.schema.Table` object, not a declarative
class. The :class:`~sqlalchemy.schema.Table` should share the same
:class:`~sqlalchemy.schema.MetaData` object used by the declarative
@@ -173,10 +173,10 @@ base::
class Author(Base):
__tablename__ = 'authors'
id = Column(Integer, primary_key=True)
- keywords = relation("Keyword", secondary=keywords)
+ keywords = relationship("Keyword", secondary=keywords)
You should generally **not** map a class and also specify its table in
-a many-to-many relation, since the ORM may issue duplicate INSERT and
+a many-to-many relationship, since the ORM may issue duplicate INSERT and
DELETE statements.
@@ -576,7 +576,7 @@ def _as_declarative(cls, classname, dict_):
continue
if not isinstance(value, (Column, MapperProperty)):
continue
- prop = _deferred_relation(cls, value)
+ prop = _deferred_relationship(cls, value)
our_stuff[k] = prop
# set up attributes in the order they were created
@@ -717,7 +717,7 @@ class DeclarativeMeta(type):
cls.__table__.append_column(col)
cls.__mapper__.add_property(key, value)
elif isinstance(value, MapperProperty):
- cls.__mapper__.add_property(key, _deferred_relation(cls, value))
+ cls.__mapper__.add_property(key, _deferred_relationship(cls, value))
else:
type.__setattr__(cls, key, value)
else:
@@ -740,7 +740,7 @@ class _GetColumns(object):
return getattr(self.cls, key)
-def _deferred_relation(cls, prop):
+def _deferred_relationship(cls, prop):
def resolve_arg(arg):
import sqlalchemy
@@ -764,7 +764,7 @@ def _deferred_relation(cls, prop):
except NameError, n:
raise exceptions.InvalidRequestError(
"When compiling mapper %s, expression %r failed to locate a name (%r). "
- "If this is a class name, consider adding this relation() to the %r "
+ "If this is a class name, consider adding this relationship() to the %r "
"class after both dependent classes have been defined." % (
prop.parent, arg, n.args[0], cls))
return return_cls
@@ -838,7 +838,7 @@ def _declarative_constructor(self, **kwargs):
Only keys that are present as
attributes of the instance's class are allowed. These could be,
- for example, any mapped columns or relations.
+ for example, any mapped columns or relationships.
"""
for k in kwargs:
if not hasattr(type(self), k):
@@ -890,7 +890,7 @@ def declarative_base(bind=None, metadata=None, mapper=None, cls=object,
Defaults to
:func:`~sqlalchemy.ext.declarative._declarative_constructor`, an
__init__ implementation that assigns \**kwargs for declared
- fields and relations to an instance. If ``None`` is supplied,
+ fields and relationships to an instance. If ``None`` is supplied,
no __init__ will be provided and construction will fall back to
cls.__init__ by way of the normal Python semantics.
diff --git a/lib/sqlalchemy/ext/orderinglist.py b/lib/sqlalchemy/ext/orderinglist.py
index 8e63ed1c2..db0bd2a4e 100644
--- a/lib/sqlalchemy/ext/orderinglist.py
+++ b/lib/sqlalchemy/ext/orderinglist.py
@@ -1,17 +1,17 @@
"""A custom list that manages index/position information for its children.
``orderinglist`` is a custom list collection implementation for mapped
-relations that keeps an arbitrary "position" attribute on contained objects in
+relationships that keeps an arbitrary "position" attribute on contained objects in
sync with each object's position in the Python list.
The collection acts just like a normal Python ``list``, with the added
behavior that as you manipulate the list (via ``insert``, ``pop``, assignment,
deletion, what have you), each of the objects it contains is updated as needed
-to reflect its position. This is very useful for managing ordered relations
+to reflect its position. This is very useful for managing ordered relationships
which have a user-defined, serialized order::
>>> from sqlalchemy import MetaData, Table, Column, Integer, String, ForeignKey
- >>> from sqlalchemy.orm import mapper, relation
+ >>> from sqlalchemy.orm import mapper, relationship
>>> from sqlalchemy.ext.orderinglist import ordering_list
A simple model of users their "top 10" things::
@@ -32,7 +32,7 @@ A simple model of users their "top 10" things::
... self.blurb = blurb
...
>>> mapper(User, users, properties={
- ... 'topten': relation(Blurb, collection_class=ordering_list('position'),
+ ... 'topten': relationship(Blurb, collection_class=ordering_list('position'),
... order_by=[blurbs.c.position])})
<Mapper ...>
>>> mapper(Blurb, blurbs)
@@ -73,7 +73,7 @@ __all__ = [ 'ordering_list' ]
def ordering_list(attr, count_from=None, **kw):
"""Prepares an OrderingList factory for use in mapper definitions.
- Returns an object suitable for use as an argument to a Mapper relation's
+ Returns an object suitable for use as an argument to a Mapper relationship's
``collection_class`` option. Arguments are:
attr
@@ -136,7 +136,7 @@ class OrderingList(list):
See the module and __init__ documentation for more details. The
``ordering_list`` factory function is used to configure ``OrderingList``
- collections in ``mapper`` relation definitions.
+ collections in ``mapper`` relationship definitions.
"""
@@ -149,11 +149,11 @@ class OrderingList(list):
mapped objects.
This implementation relies on the list starting in the proper order,
- so be **sure** to put an ``order_by`` on your relation.
+ so be **sure** to put an ``order_by`` on your relationship.
ordering_attr
Name of the attribute that stores the object's order in the
- relation.
+ relationship.
ordering_func
Optional. A function that maps the position in the Python list to a
diff --git a/lib/sqlalchemy/ext/sqlsoup.py b/lib/sqlalchemy/ext/sqlsoup.py
index 8b5d6bbc3..4d5f4b76f 100644
--- a/lib/sqlalchemy/ext/sqlsoup.py
+++ b/lib/sqlalchemy/ext/sqlsoup.py
@@ -170,10 +170,10 @@ You can also join directly to a labeled object::
[u'name', u'email', u'password', u'classname', u'admin', u'loans_book_id', u'loans_user_name', u'loans_loan_date']
-Relations
-=========
+Relationships
+=============
-You can define relations on SqlSoup classes:
+You can define relationships on SqlSoup classes:
>>> db.users.relate('loans', db.loans)
@@ -186,7 +186,7 @@ These can then be used like a normal SA property:
[MappedUsers(name=u'Bhargan Basepair',email='basepair+nospam@example.edu',password=u'basepair',classname=None,admin=1)]
-relate can take any options that the relation function accepts in normal mapper definition:
+relate can take any options that the relationship function accepts in normal mapper definition:
>>> del db._cache['users']
>>> db.users.relate('loans', db.loans, order_by=db.loans.loan_date, cascade='all, delete-orphan')
@@ -308,7 +308,7 @@ from sqlalchemy import Table, MetaData, join
from sqlalchemy import schema, sql
from sqlalchemy.engine.base import Engine
from sqlalchemy.orm import scoped_session, sessionmaker, mapper, \
- class_mapper, relation, session,\
+ class_mapper, relationship, session,\
object_session
from sqlalchemy.orm.interfaces import MapperExtension, EXT_CONTINUE
from sqlalchemy.exceptions import SQLAlchemyError, InvalidRequestError, ArgumentError
@@ -373,7 +373,7 @@ class TableClassType(SelectableClassType):
return o
def relate(cls, propname, *args, **kwargs):
- class_mapper(cls)._configure_property(propname, relation(*args, **kwargs))
+ class_mapper(cls)._configure_property(propname, relationship(*args, **kwargs))
def _is_outer_join(selectable):
if not isinstance(selectable, sql.Join):
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py
index 6c19a12e7..26cb071b7 100644
--- a/lib/sqlalchemy/orm/__init__.py
+++ b/lib/sqlalchemy/orm/__init__.py
@@ -41,7 +41,7 @@ from sqlalchemy.orm.properties import (
ColumnProperty,
ComparableProperty,
CompositeProperty,
- RelationProperty,
+ RelationshipProperty,
PropertyLoader,
SynonymProperty,
)
@@ -92,6 +92,7 @@ __all__ = (
'outerjoin',
'polymorphic_union',
'reconstructor',
+ 'relationship',
'relation',
'scoped_session',
'sessionmaker',
@@ -172,21 +173,21 @@ def create_session(bind=None, **kwargs):
kwargs.setdefault('expire_on_commit', False)
return _Session(bind=bind, **kwargs)
-def relation(argument, secondary=None, **kwargs):
+def relationship(argument, secondary=None, **kwargs):
"""Provide a relationship of a primary Mapper to a secondary Mapper.
This corresponds to a parent-child or associative table relationship. The
- constructed class is an instance of :class:`RelationProperty`.
+ constructed class is an instance of :class:`RelationshipProperty`.
- A typical :func:`relation`::
+ A typical :func:`relationship`::
mapper(Parent, properties={
- 'children': relation(Children)
+ 'children': relationship(Children)
})
:param argument:
a class or :class:`Mapper` instance, representing the target of
- the relation.
+ the relationship.
:param secondary:
for a many-to-many relationship, specifies the intermediary
@@ -202,14 +203,14 @@ def relation(argument, secondary=None, **kwargs):
direction. The other property will be created automatically
when the mappers are configured. Can also be passed as a
:func:`backref` object to control the configuration of the
- new relation.
+ new relationship.
:param back_populates:
Takes a string name and has the same meaning as ``backref``,
except the complementing property is **not** created automatically,
and instead must be configured explicitly on the other mapper. The
complementing property should also indicate ``back_populates``
- to this relation to ensure proper functioning.
+ to this relationship to ensure proper functioning.
:param cascade:
a comma-separated list of cascade rules which determines how
@@ -244,7 +245,7 @@ def relation(argument, secondary=None, **kwargs):
be used in place of a plain list for storing elements.
:param comparator_factory:
- a class which extends :class:`RelationProperty.Comparator` which
+ a class which extends :class:`RelationshipProperty.Comparator` which
provides custom SQL clause generation for comparison operations.
:param extension:
@@ -261,7 +262,7 @@ def relation(argument, secondary=None, **kwargs):
this parameter should be used in conjunction with explicit
``primaryjoin`` and ``secondaryjoin`` (if needed) arguments, and
the columns within the ``foreign_keys`` list should be present
- within those join conditions. Normally, ``relation()`` will
+ within those join conditions. Normally, ``relationship()`` will
inspect the columns within the join conditions to determine
which columns are the "foreign key" columns, based on
information in the ``Table`` metadata. Use this argument when no
@@ -273,7 +274,7 @@ def relation(argument, secondary=None, **kwargs):
against related tables instead of an outer join. The purpose
of this option is strictly one of performance, as inner joins
generally perform better than outer joins. This flag can
- be set to ``True`` when the relation references an object
+ be set to ``True`` when the relationship references an object
via many-to-one using local foreign keys that are not nullable,
or when the reference is one-to-one or a collection that is
guaranteed to have one or at least one entry.
@@ -344,12 +345,12 @@ def relation(argument, secondary=None, **kwargs):
dependent rows. Note that with databases which enforce
referential integrity (i.e. PostgreSQL, MySQL with InnoDB tables),
ON UPDATE CASCADE is required for this operation. The
- relation() will update the value of the attribute on related
+ relationship() will update the value of the attribute on related
items which are locally present in the session during a flush.
When False, it is assumed that the database does not enforce
referential integrity and will not be issuing its own CASCADE
- operation for an update. The relation() will issue the
+ operation for an update. The relationship() will issue the
appropriate UPDATE statements to the database in response to the
change of a referenced key, and items locally present in the
session during a flush will also be refreshed.
@@ -405,19 +406,19 @@ def relation(argument, secondary=None, **kwargs):
This is used for many-to-one or many-to-many relationships that
should be treated either as one-to-one or one-to-many. Its
usage is optional unless delete-orphan cascade is also
- set on this relation(), in which case its required (new in 0.5.2).
+ set on this relationship(), in which case its required (new in 0.5.2).
:param uselist=(True|False):
a boolean that indicates if this property should be loaded as a
list or a scalar. In most cases, this value is determined
- automatically by ``relation()``, based on the type and direction
+ automatically by ``relationship()``, based on the type and direction
of the relationship - one to many forms a list, many to one
forms a scalar, many to many is a list. If a scalar is desired
where normally a list would be present, such as a bi-directional
one-to-one relationship, set uselist to False.
:param viewonly=False:
- when set to True, the relation is used only for loading objects
+ when set to True, the relationship is used only for loading objects
within the relationship, and has no effect on the unit-of-work
flush process. Relationships with viewonly can specify any kind of
join conditions to provide additional views of related objects
@@ -427,8 +428,13 @@ def relation(argument, secondary=None, **kwargs):
case, use an alternative method.
"""
- return RelationProperty(argument, secondary=secondary, **kwargs)
+ return RelationshipProperty(argument, secondary=secondary, **kwargs)
+def relation(*arg, **kw):
+ """A synonym for :func:`relationship`."""
+
+ return relationship(*arg, **kw)
+
def dynamic_loader(argument, secondary=None, primaryjoin=None,
secondaryjoin=None, foreign_keys=None, backref=None,
post_update=False, cascade=False, remote_side=None,
@@ -436,7 +442,7 @@ def dynamic_loader(argument, secondary=None, primaryjoin=None,
order_by=None, comparator_factory=None, query_class=None):
"""Construct a dynamically-loading mapper property.
- This property is similar to :func:`relation`, except read
+ This property is similar to :func:`relationship`, except read
operations return an active :class:`Query` object which reads from
the database when accessed. Items may be appended to the
attribute via ``append()``, or removed via ``remove()``; changes
@@ -444,12 +450,12 @@ def dynamic_loader(argument, secondary=None, primaryjoin=None,
However, no other Python list or collection mutation operations
are available.
- A subset of arguments available to :func:`relation` are available
+ A subset of arguments available to :func:`relationship` are available
here.
:param argument:
a class or :class:`Mapper` instance, representing the target of
- the relation.
+ the relationship.
:param secondary:
for a many-to-many relationship, specifies the intermediary
@@ -466,7 +472,7 @@ def dynamic_loader(argument, secondary=None, primaryjoin=None,
"""
from sqlalchemy.orm.dynamic import DynaLoader
- return RelationProperty(
+ return RelationshipProperty(
argument, secondary=secondary, primaryjoin=primaryjoin,
secondaryjoin=secondaryjoin, foreign_keys=foreign_keys, backref=backref,
post_update=post_update, cascade=cascade, remote_side=remote_side,
@@ -589,9 +595,9 @@ def composite(class_, *cols, **kwargs):
def backref(name, **kwargs):
"""Create a back reference with explicit arguments, which are the same
- arguments one can send to ``relation()``.
+ arguments one can send to ``relationship()``.
- Used with the `backref` keyword argument to ``relation()`` in
+ Used with the `backref` keyword argument to ``relationship()`` in
place of a string argument.
"""
@@ -695,12 +701,12 @@ def mapper(class_, local_table=None, *args, **params):
dependent rows. Note that with databases which enforce
referential integrity (i.e. PostgreSQL, MySQL with InnoDB tables),
ON UPDATE CASCADE is required for this operation. The
- relation() will update the value of the attribute on related
+ relationship() will update the value of the attribute on related
items which are locally present in the session during a flush.
When False, it is assumed that the database does not enforce
referential integrity and will not be issuing its own CASCADE
- operation for an update. The relation() will issue the
+ operation for an update. The relationship() will issue the
appropriate UPDATE statements to the database in response to the
change of a referenced key, and items locally present in the
session during a flush will also be refreshed.
@@ -709,7 +715,7 @@ def mapper(class_, local_table=None, *args, **params):
are expected and the database in use doesn't support CASCADE
(i.e. SQLite, MySQL MyISAM tables).
- Also see the passive_updates flag on :func:`relation()`.
+ Also see the passive_updates flag on :func:`relationship()`.
A future SQLAlchemy release will provide a "detect" feature for
this flag.
@@ -791,7 +797,7 @@ def synonym(name, map_column=False, descriptor=None, comparator_factory=None):
`name` refers to the name of the existing mapped property, which can be
any other ``MapperProperty`` including column-based properties and
- relations.
+ relationships.
If `map_column` is ``True``, an additional ``ColumnProperty`` is created
on the mapper automatically, using the synonym's name as the keyname of
@@ -950,7 +956,7 @@ def eagerload_all(*keys, **kw):
query.options(eagerload_all(User.orders, Order.items, Item.keywords))
The keyword arguments accept a flag `innerjoin=True|False` which will
- override the value of the `innerjoin` flag specified on the relation().
+ override the value of the `innerjoin` flag specified on the relationship().
"""
innerjoin = kw.pop('innerjoin', None)
diff --git a/lib/sqlalchemy/orm/collections.py b/lib/sqlalchemy/orm/collections.py
index 6a7701846..616f2510a 100644
--- a/lib/sqlalchemy/orm/collections.py
+++ b/lib/sqlalchemy/orm/collections.py
@@ -52,7 +52,7 @@ decoration::
class InstrumentedList(list):
pass
-Collection classes can be specified in ``relation(collection_class=)`` as
+Collection classes can be specified in ``relationship(collection_class=)`` as
types or a function that returns an instance. Collection classes are
inspected and instrumented during the mapper compilation phase. The
collection_class callable will be executed once to produce a specimen
diff --git a/lib/sqlalchemy/orm/dependency.py b/lib/sqlalchemy/orm/dependency.py
index b0ad3ae94..cbbfb0883 100644
--- a/lib/sqlalchemy/orm/dependency.py
+++ b/lib/sqlalchemy/orm/dependency.py
@@ -6,8 +6,8 @@
"""Relationship dependencies.
-Bridges the ``PropertyLoader`` (i.e. a ``relation()``) and the
-``UOWTransaction`` together to allow processing of relation()-based
+Bridges the ``PropertyLoader`` (i.e. a ``relationship()``) and the
+``UOWTransaction`` together to allow processing of relationship()-based
dependencies at flush time.
"""
@@ -43,7 +43,7 @@ class DependencyProcessor(object):
self.key = prop.key
self.dependency_marker = MapperStub(self.parent, self.mapper, self.key)
if not self.prop.synchronize_pairs:
- raise sa_exc.ArgumentError("Can't build a DependencyProcessor for relation %s. "
+ raise sa_exc.ArgumentError("Can't build a DependencyProcessor for relationship %s. "
"No target attributes to populate between parent and child are present" % self.prop)
def _get_instrumented_attribute(self):
@@ -141,7 +141,7 @@ class DependencyProcessor(object):
def _check_reverse_action(self, uowcommit, parent, child, action):
"""Determine if an action has been performed by the 'reverse' property of this property.
- this is used to ensure that only one side of a bidirectional relation
+ this is used to ensure that only one side of a bidirectional relationship
issues a certain operation for a parent/child pair.
"""
@@ -162,7 +162,7 @@ class DependencyProcessor(object):
def _conditional_post_update(self, state, uowcommit, related):
"""Execute a post_update call.
- For relations that contain the post_update flag, an additional
+ For relationships that contain the post_update flag, an additional
``UPDATE`` statement may be associated after an ``INSERT`` or
before a ``DELETE`` in order to resolve circular row
dependencies.
@@ -298,7 +298,7 @@ class OneToManyDP(DependencyProcessor):
return sync.source_modified(uowcommit, state, self.parent, self.prop.synchronize_pairs)
class DetectKeySwitch(DependencyProcessor):
- """a special DP that works for many-to-one relations, fires off for
+ """a special DP that works for many-to-one relationships, fires off for
child items who have changed their referenced key."""
has_dependencies = False
diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py
index 2157bafc8..d7960406b 100644
--- a/lib/sqlalchemy/orm/dynamic.py
+++ b/lib/sqlalchemy/orm/dynamic.py
@@ -22,7 +22,7 @@ from sqlalchemy.orm.query import Query
from sqlalchemy.orm.util import _state_has_identity, has_identity
from sqlalchemy.orm import attributes, collections
-class DynaLoader(strategies.AbstractRelationLoader):
+class DynaLoader(strategies.AbstractRelationshipLoader):
def init_class_attribute(self, mapper):
self.is_class_level = True
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index 30b6dd070..8f0f2128b 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -58,7 +58,7 @@ _COMPILE_MUTEX = util.threading.RLock()
ColumnProperty = None
SynonymProperty = None
ComparableProperty = None
-RelationProperty = None
+RelationshipProperty = None
ConcreteInheritedProperty = None
_expire_state = None
_state_session = None
@@ -1202,7 +1202,7 @@ class Mapper(object):
def cascade_iterator(self, type_, state, halt_on=None):
"""Iterate each element and its mapper in an object graph,
- for all relations that meet the given cascade rule.
+ for all relationships that meet the given cascade rule.
``type\_``:
The name of the cascade rule (i.e. save-update, delete,
@@ -1210,7 +1210,7 @@ class Mapper(object):
``state``:
The lead InstanceState. child items will be processed per
- the relations defined for this object's mapper.
+ the relationships defined for this object's mapper.
the return value are object instances; this provides a strong
reference so that they don't fall out of scope immediately.
diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py
index e900b0cab..879695e13 100644
--- a/lib/sqlalchemy/orm/properties.py
+++ b/lib/sqlalchemy/orm/properties.py
@@ -26,7 +26,7 @@ from sqlalchemy.orm.interfaces import (
NoneType = type(None)
__all__ = ('ColumnProperty', 'CompositeProperty', 'SynonymProperty',
- 'ComparableProperty', 'RelationProperty', 'BackRef')
+ 'ComparableProperty', 'RelationshipProperty', 'BackRef')
class ColumnProperty(StrategizedProperty):
@@ -343,7 +343,7 @@ class ComparableProperty(MapperProperty):
pass
-class RelationProperty(StrategizedProperty):
+class RelationshipProperty(StrategizedProperty):
"""Describes an object property that holds a single item or list
of items that correspond to a related database table.
"""
@@ -388,7 +388,7 @@ class RelationProperty(StrategizedProperty):
self.join_depth = join_depth
self.local_remote_pairs = _local_remote_pairs
self.extension = extension
- self.comparator_factory = comparator_factory or RelationProperty.Comparator
+ self.comparator_factory = comparator_factory or RelationshipProperty.Comparator
self.comparator = self.comparator_factory(self, None)
util.set_creation_order(self)
@@ -467,10 +467,10 @@ class RelationProperty(StrategizedProperty):
return op(self, *other, **kwargs)
def of_type(self, cls):
- return RelationProperty.Comparator(self.property, self.mapper, cls, adapter=self.adapter)
+ return RelationshipProperty.Comparator(self.property, self.mapper, cls, adapter=self.adapter)
def in_(self, other):
- raise NotImplementedError("in_() not yet supported for relations. For a "
+ raise NotImplementedError("in_() not yet supported for relationships. For a "
"simple many-to-one, use in_() against the set of foreign key values.")
__hash__ = None
@@ -730,8 +730,8 @@ class RelationProperty(StrategizedProperty):
other._reverse_property.add(self)
if not other._get_target().common_parent(self.parent):
- raise sa_exc.ArgumentError("reverse_property %r on relation %s references "
- "relation %s, which does not reference mapper %s" % (key, self, other, self.parent))
+ raise sa_exc.ArgumentError("reverse_property %r on relationship %s references "
+ "relationship %s, which does not reference mapper %s" % (key, self, other, self.parent))
if self.direction in (ONETOMANY, MANYTOONE) and self.direction == other.direction:
raise sa_exc.ArgumentError("%s and back-reference %s are both of the same direction %r."
@@ -747,7 +747,7 @@ class RelationProperty(StrategizedProperty):
self._determine_local_remote_pairs()
self._post_init()
self._generate_backref()
- super(RelationProperty, self).do_init()
+ super(RelationshipProperty, self).do_init()
def _get_target(self):
if not hasattr(self, 'mapper'):
@@ -759,7 +759,7 @@ class RelationProperty(StrategizedProperty):
# accept a callable to suit various deferred-configurational schemes
self.mapper = mapper.class_mapper(self.argument(), compile=False)
else:
- raise sa_exc.ArgumentError("relation '%s' expects a class or a mapper argument (received: %s)" % (self.key, type(self.argument)))
+ raise sa_exc.ArgumentError("relationship '%s' expects a class or a mapper argument (received: %s)" % (self.key, type(self.argument)))
assert isinstance(self.mapper, mapper.Mapper), self.mapper
return self.mapper
@@ -789,8 +789,8 @@ class RelationProperty(StrategizedProperty):
for inheriting in self.parent.iterate_to_root():
if inheriting is not self.parent and inheriting._get_property(self.key, raiseerr=False):
util.warn(
- ("Warning: relation '%s' on mapper '%s' supercedes "
- "the same relation on inherited mapper '%s'; this "
+ ("Warning: relationship '%s' on mapper '%s' supercedes "
+ "the same relationship on inherited mapper '%s'; this "
"can cause dependency issues during flush") %
(self.key, self.parent, inheriting))
@@ -830,9 +830,9 @@ class RelationProperty(StrategizedProperty):
self.primaryjoin = _search_for_join(self.parent, self.target)
except sa_exc.ArgumentError, e:
raise sa_exc.ArgumentError("Could not determine join condition between "
- "parent/child tables on relation %s. "
+ "parent/child tables on relationship %s. "
"Specify a 'primaryjoin' expression. If this is a "
- "many-to-many relation, 'secondaryjoin' is needed as well." % (self))
+ "many-to-many relationship, 'secondaryjoin' is needed as well." % (self))
def _col_is_part_of_mappings(self, column):
if self.secondary is None:
@@ -872,21 +872,21 @@ class RelationProperty(StrategizedProperty):
if not eq_pairs:
if not self.viewonly and criterion_as_pairs(self.primaryjoin, consider_as_foreign_keys=self._foreign_keys, any_operator=True):
raise sa_exc.ArgumentError("Could not locate any equated, locally "
- "mapped column pairs for primaryjoin condition '%s' on relation %s. "
- "For more relaxed rules on join conditions, the relation may be "
+ "mapped column pairs for primaryjoin condition '%s' on relationship %s. "
+ "For more relaxed rules on join conditions, the relationship may be "
"marked as viewonly=True." % (self.primaryjoin, self)
)
else:
if self._foreign_keys:
- raise sa_exc.ArgumentError("Could not determine relation direction for "
- "primaryjoin condition '%s', on relation %s. "
+ raise sa_exc.ArgumentError("Could not determine relationship direction for "
+ "primaryjoin condition '%s', on relationship %s. "
"Do the columns in 'foreign_keys' represent only the 'foreign' columns "
"in this join condition ?" % (self.primaryjoin, self))
else:
- raise sa_exc.ArgumentError("Could not determine relation direction for "
- "primaryjoin condition '%s', on relation %s. "
+ raise sa_exc.ArgumentError("Could not determine relationship direction for "
+ "primaryjoin condition '%s', on relationship %s. "
"Specify the 'foreign_keys' argument to indicate which columns "
- "on the relation are foreign." % (self.primaryjoin, self))
+ "on the relationship are foreign." % (self.primaryjoin, self))
self.synchronize_pairs = eq_pairs
@@ -897,15 +897,15 @@ class RelationProperty(StrategizedProperty):
if not sq_pairs:
if not self.viewonly and criterion_as_pairs(self.secondaryjoin, consider_as_foreign_keys=self._foreign_keys, any_operator=True):
raise sa_exc.ArgumentError("Could not locate any equated, locally mapped "
- "column pairs for secondaryjoin condition '%s' on relation %s. "
+ "column pairs for secondaryjoin condition '%s' on relationship %s. "
"For more relaxed rules on join conditions, the "
- "relation may be marked as viewonly=True." % (self.secondaryjoin, self)
+ "relationship may be marked as viewonly=True." % (self.secondaryjoin, self)
)
else:
- raise sa_exc.ArgumentError("Could not determine relation direction "
- "for secondaryjoin condition '%s', on relation %s. "
+ raise sa_exc.ArgumentError("Could not determine relationship direction "
+ "for secondaryjoin condition '%s', on relationship %s. "
"Specify the foreign_keys argument to indicate which "
- "columns on the relation are foreign." % (self.secondaryjoin, self))
+ "columns on the relationship are foreign." % (self.secondaryjoin, self))
self.secondary_synchronize_pairs = sq_pairs
else:
@@ -951,7 +951,7 @@ class RelationProperty(StrategizedProperty):
if not onetomany_fk and not manytoone_fk:
raise sa_exc.ArgumentError(
- "Can't determine relation direction for relationship '%s' "
+ "Can't determine relationship direction for relationship '%s' "
"- foreign key columns are present in neither the "
"parent nor the child's mapped tables" % self )
@@ -973,7 +973,7 @@ class RelationProperty(StrategizedProperty):
if not self.direction:
raise sa_exc.ArgumentError(
- "Can't determine relation direction for relationship '%s' "
+ "Can't determine relationship direction for relationship '%s' "
"- foreign key columns are present in both the parent and "
"the child's mapped tables. Specify 'foreign_keys' "
"argument." % self)
@@ -982,7 +982,7 @@ class RelationProperty(StrategizedProperty):
(self.direction is MANYTOMANY or self.direction is MANYTOONE):
util.warn("On %s, delete-orphan cascade is not supported on a "
"many-to-many or many-to-one relationship when single_parent is not set. "
- " Set single_parent=True on the relation()." % self)
+ " Set single_parent=True on the relationship()." % self)
def _determine_local_remote_pairs(self):
if not self.local_remote_pairs:
@@ -996,7 +996,7 @@ class RelationProperty(StrategizedProperty):
self.local_remote_pairs = criterion_as_pairs(self.primaryjoin, consider_as_foreign_keys=self.remote_side, any_operator=True)
if not self.local_remote_pairs:
- raise sa_exc.ArgumentError("Relation %s could not determine any local/remote column pairs from remote side argument %r" % (self, self.remote_side))
+ raise sa_exc.ArgumentError("Relationship %s could not determine any local/remote column pairs from remote side argument %r" % (self, self.remote_side))
else:
if self.viewonly:
@@ -1034,8 +1034,8 @@ class RelationProperty(StrategizedProperty):
if not self.is_primary() and \
not mapper.class_mapper(self.parent.class_, compile=False)._get_property(self.key, raiseerr=False):
- raise sa_exc.ArgumentError("Attempting to assign a new relation '%s' to "
- "a non-primary mapper on class '%s'. New relations can only be "
+ raise sa_exc.ArgumentError("Attempting to assign a new relationship '%s' to "
+ "a non-primary mapper on class '%s'. New relationships can only be "
"added to the primary mapper, i.e. the very first "
"mapper created for class '%s' " % (self.key, self.parent.class_.__name__, self.parent.class_.__name__))
@@ -1051,7 +1051,7 @@ class RelationProperty(StrategizedProperty):
mapper = self.mapper.primary_mapper()
if mapper._get_property(backref_key, raiseerr=False) is not None:
- raise sa_exc.ArgumentError("Error creating backref '%s' on relation '%s': "
+ raise sa_exc.ArgumentError("Error creating backref '%s' on relationship '%s': "
"property of that name exists on mapper '%s'" % (backref_key, self, mapper))
if self.secondary is not None:
@@ -1063,7 +1063,7 @@ class RelationProperty(StrategizedProperty):
if sj:
raise sa_exc.InvalidRequestError(
"Can't assign 'secondaryjoin' on a backref against "
- "a non-secondary relation.")
+ "a non-secondary relationship.")
foreign_keys = kwargs.pop('foreign_keys', self._foreign_keys)
@@ -1072,7 +1072,7 @@ class RelationProperty(StrategizedProperty):
kwargs.setdefault('post_update', self.post_update)
self.back_populates = backref_key
- relation = RelationProperty(
+ relationship = RelationshipProperty(
parent,
self.secondary,
pj,
@@ -1081,7 +1081,7 @@ class RelationProperty(StrategizedProperty):
back_populates=self.key,
**kwargs)
- mapper._configure_property(backref_key, relation)
+ mapper._configure_property(backref_key, relationship)
if self.back_populates:
@@ -1096,7 +1096,7 @@ class RelationProperty(StrategizedProperty):
self.logger.info("%s synchronize pairs [%s]", self, ",".join("(%s => %s)" % (l, r) for l, r in self.synchronize_pairs))
self.logger.info("%s secondary synchronize pairs [%s]", self, ",".join(("(%s => %s)" % (l, r) for l, r in self.secondary_synchronize_pairs or [])))
self.logger.info("%s local/remote pairs [%s]", self, ",".join("(%s / %s)" % (l, r) for l, r in self.local_remote_pairs))
- self.logger.info("%s relation direction %s", self, self.direction)
+ self.logger.info("%s relationship direction %s", self, self.direction)
if self.uselist is None:
self.uselist = self.direction is not MANYTOONE
@@ -1199,11 +1199,11 @@ class RelationProperty(StrategizedProperty):
if not self.viewonly:
self._dependency_processor.register_processors(uowcommit)
-PropertyLoader = RelationProperty
-log.class_logger(RelationProperty)
+PropertyLoader = RelationshipProperty
+log.class_logger(RelationshipProperty)
mapper.ColumnProperty = ColumnProperty
mapper.SynonymProperty = SynonymProperty
mapper.ComparableProperty = ComparableProperty
-mapper.RelationProperty = RelationProperty
+mapper.RelationshipProperty = RelationshipProperty
mapper.ConcreteInheritedProperty = ConcreteInheritedProperty
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index fde93ff1b..682aa2bbf 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -904,11 +904,11 @@ class Query(object):
Each element in \*props may be:
* a string property name, i.e. "rooms". This will join along the
- relation of the same name from this Query's "primary" mapper, if
+ relationship of the same name from this Query's "primary" mapper, if
one is present.
* a class-mapped attribute, i.e. Houses.rooms. This will create a
- join from "Houses" table to that of the "rooms" relation.
+ join from "Houses" table to that of the "rooms" relationship.
* a 2-tuple containing a target class or selectable, and an "ON"
clause. The ON clause can be the property name/ attribute like
@@ -921,13 +921,13 @@ class Query(object):
session.query(Company).join('employees', 'tasks')
# join the Person entity to an alias of itself,
- # along the "friends" relation
+ # along the "friends" relationship
PAlias = aliased(Person)
session.query(Person).join((Palias, Person.friends))
# join from Houses to the "rooms" attribute on the
# "Colonials" subclass of Houses, then join to the
- # "closets" relation on Room
+ # "closets" relationship on Room
session.query(Houses).join(Colonials.rooms, Room.closets)
# join from Company entities to the "employees" collection,
@@ -936,7 +936,7 @@ class Query(object):
session.query(Company).join((people.join(engineers), 'employees'), Engineer.computers)
# join from Articles to Keywords, using the "keywords" attribute.
- # assume this is a many-to-many relation.
+ # assume this is a many-to-many relationship.
session.query(Article).join(Article.keywords)
# same thing, but spelled out entirely explicitly
@@ -1735,7 +1735,7 @@ class Query(object):
Returns the number of rows deleted, excluding any cascades.
- The method does *not* offer in-Python cascading of relations - it is
+ The method does *not* offer in-Python cascading of relationships - it is
assumed that ON DELETE CASCADE is configured for any foreign key
references which require it. The Session needs to be expired (occurs
automatically after commit(), or call expire_all()) in order for the
@@ -1843,7 +1843,7 @@ class Query(object):
Returns the number of rows matched by the update.
- The method does *not* offer in-Python cascading of relations - it is assumed that
+ The method does *not* offer in-Python cascading of relationships - it is assumed that
ON UPDATE CASCADE is configured for any foreign key references which require it.
The Session needs to be expired (occurs automatically after commit(), or call expire_all())
@@ -1857,7 +1857,7 @@ class Query(object):
"""
#TODO: value keys need to be mapped to corresponding sql cols and instr.attr.s to string keys
- #TODO: updates of manytoone relations need to be converted to fk assignments
+ #TODO: updates of manytoone relationships need to be converted to fk assignments
#TODO: cascades need handling.
if synchronize_session == 'expire':
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index d76afe28c..ce19667c6 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -308,7 +308,7 @@ class UndeferGroupOption(MapperOption):
def process_query(self, query):
query._attributes[('undefer', self.group)] = True
-class AbstractRelationLoader(LoaderStrategy):
+class AbstractRelationshipLoader(LoaderStrategy):
"""LoaderStratgies which deal with related objects as opposed to scalars."""
def init(self):
@@ -317,8 +317,8 @@ class AbstractRelationLoader(LoaderStrategy):
self.table = self.parent_property.table
self.uselist = self.parent_property.uselist
-class NoLoader(AbstractRelationLoader):
- """Strategize a relation() that doesn't load data automatically."""
+class NoLoader(AbstractRelationshipLoader):
+ """Strategize a relationship() that doesn't load data automatically."""
def init_class_attribute(self, mapper):
self.is_class_level = True
@@ -336,8 +336,8 @@ class NoLoader(AbstractRelationLoader):
log.class_logger(NoLoader)
-class LazyLoader(AbstractRelationLoader):
- """Strategize a relation() that loads when first accessed."""
+class LazyLoader(AbstractRelationshipLoader):
+ """Strategize a relationship() that loads when first accessed."""
def init(self):
super(LazyLoader, self).init()
@@ -575,8 +575,8 @@ class LoadLazyAttribute(object):
else:
return None
-class EagerLoader(AbstractRelationLoader):
- """Strategize a relation() that loads within the process of the parent object being selected."""
+class EagerLoader(AbstractRelationshipLoader):
+ """Strategize a relationship() that loads within the process of the parent object being selected."""
def init(self):
super(EagerLoader, self).init()
diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py
index 4a694bc33..c0a088b01 100644
--- a/lib/sqlalchemy/orm/unitofwork.py
+++ b/lib/sqlalchemy/orm/unitofwork.py
@@ -30,7 +30,7 @@ object_session = None
_state_session = None
class UOWEventHandler(interfaces.AttributeExtension):
- """An event handler added to all relation attributes which handles
+ """An event handler added to all relationship attributes which handles
session cascade operations.
"""
@@ -388,7 +388,7 @@ class UOWTask(object):
def append_postupdate(self, state, post_update_cols):
"""issue a 'post update' UPDATE statement via this object's mapper immediately.
- this operation is used only with relations that specify the `post_update=True`
+ this operation is used only with relationships that specify the `post_update=True`
flag.
"""
diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py
index f00400bc2..63b9d565f 100644
--- a/lib/sqlalchemy/orm/util.py
+++ b/lib/sqlalchemy/orm/util.py
@@ -20,7 +20,7 @@ all_cascades = frozenset(("delete", "delete-orphan", "all", "merge",
_INSTRUMENTOR = ('mapper', 'instrumentor')
class CascadeOptions(object):
- """Keeps track of the options sent to relation().cascade"""
+ """Keeps track of the options sent to relationship().cascade"""
def __init__(self, arg=""):
if not arg:
@@ -434,8 +434,8 @@ def join(left, right, onclause=None, isouter=False, join_to_left=True):
In addition to the interface provided by
:func:`~sqlalchemy.sql.expression.join()`, left and right may be mapped
classes or AliasedClass instances. The onclause may be a
- string name of a relation(), or a class-bound descriptor
- representing a relation.
+ string name of a relationship(), or a class-bound descriptor
+ representing a relationship.
join_to_left indicates to attempt aliasing the ON clause,
in whatever form it is passed, to the selectable
@@ -451,8 +451,8 @@ def outerjoin(left, right, onclause=None, join_to_left=True):
In addition to the interface provided by
:func:`~sqlalchemy.sql.expression.outerjoin()`, left and right may be mapped
classes or AliasedClass instances. The onclause may be a
- string name of a relation(), or a class-bound descriptor
- representing a relation.
+ string name of a relationship(), or a class-bound descriptor
+ representing a relationship.
"""
return _ORMJoin(left, right, onclause, True, join_to_left)