summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2012-10-28 18:23:57 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2012-10-28 18:23:57 -0400
commita13812606cc49909eb0bdceccfd899359e098ca2 (patch)
treefcc38801fa383ec6a23b7c5121fc030137a5dba8
parent2637c9eddc78e1eceadf544597ec69a9e9b13369 (diff)
downloadsqlalchemy-a13812606cc49909eb0bdceccfd899359e098ca2.tar.gz
- remove remote_foreign annotation
- support annotations on Column where name isn't immediately present
-rw-r--r--lib/sqlalchemy/orm/__init__.py8
-rw-r--r--lib/sqlalchemy/orm/relationships.py63
-rw-r--r--lib/sqlalchemy/orm/session.py21
-rw-r--r--lib/sqlalchemy/sql/util.py31
-rw-r--r--test/orm/test_rel_fn.py6
-rw-r--r--test/sql/test_selectable.py7
6 files changed, 67 insertions, 69 deletions
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py
index ea8e5a36e..b8085ca3c 100644
--- a/lib/sqlalchemy/orm/__init__.py
+++ b/lib/sqlalchemy/orm/__init__.py
@@ -49,7 +49,6 @@ from .properties import (
from .relationships import (
foreign,
remote,
- remote_foreign
)
from .session import (
Session,
@@ -118,7 +117,6 @@ __all__ = (
'relationship',
'relation',
'remote',
- 'remote_foreign',
'scoped_session',
'sessionmaker',
'subqueryload',
@@ -452,9 +450,9 @@ def relationship(argument, secondary=None, **kwargs):
:param load_on_pending=False:
Indicates loading behavior for transient or pending parent objects.
- .. note::
-
- load_on_pending is superseded by :meth:`.Session.enable_relationship_loading`.
+ .. versionchanged:: 0.8
+ load_on_pending is superseded by
+ :meth:`.Session.enable_relationship_loading`.
When set to ``True``, causes the lazy-loader to
issue a query for a parent object that is not persistent, meaning it has
diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py
index c861edf83..373fba785 100644
--- a/lib/sqlalchemy/orm/relationships.py
+++ b/lib/sqlalchemy/orm/relationships.py
@@ -26,74 +26,41 @@ def remote(expr):
"""Annotate a portion of a primaryjoin expression
with a 'remote' annotation.
- :func:`.remote`, :func:`.foreign`, and :func:`.remote_foreign`
- are intended to be used with
- :func:`.relationship` in conjunction with a
- ``primaryjoin`` expression which contains
- indirect equality conditions, meaning the comparison
- of mapped columns involves extraneous SQL functions
- such as :func:`.cast`. They can also be used in
- lieu of the ``foreign_keys`` and ``remote_side``
- parameters to :func:`.relationship`, if a
- primaryjoin expression is also being sent explicitly.
-
- Below, a mapped class ``DNSRecord`` relates to the
- ``DHCPHost`` class using a primaryjoin that casts
- the ``content`` column to a string. The :func:`.foreign`
- and :func:`.remote` annotation functions are used
- to mark with full accuracy those mapped columns that
- are significant to the :func:`.relationship`, in terms
- of how they are joined::
-
- from sqlalchemy import cast, String
- from sqlalchemy.orm import remote, foreign
- from sqlalchemy.dialects.postgresql import INET
-
- class DNSRecord(Base):
- __tablename__ = 'dns'
-
- id = Column(Integer, primary_key=True)
- content = Column(INET)
- dhcphost = relationship(DHCPHost,
- primaryjoin=cast(foreign(content), String) ==
- remote(DHCPHost.ip_address)
- )
+ See the section :ref:`relationship_custom_foreign` for a
+ description of use.
.. versionadded:: 0.8
- See also:
+ .. seealso::
- * :func:`.foreign`
+ :ref:`relationship_custom_foreign`
- * :func:`.remote_foreign`
+ :func:`.foreign`
"""
- return _annotate_columns(expression._clause_element_as_expr(expr), {"remote":True})
+ return _annotate_columns(expression._clause_element_as_expr(expr),
+ {"remote": True})
def foreign(expr):
"""Annotate a portion of a primaryjoin expression
with a 'foreign' annotation.
- See the example at :func:`.remote`.
+ See the section :ref:`relationship_custom_foreign` for a
+ description of use.
.. versionadded:: 0.8
- """
-
- return _annotate_columns(expression._clause_element_as_expr(expr), {"foreign":True})
+ .. seealso::
-def remote_foreign(expr):
- """Annotate a portion of a primaryjoin expression
- with a 'remote' and 'foreign' annotation.
+ :ref:`relationship_custom_foreign`
- See the example at :func:`.remote`.
-
- .. versionadded:: 0.8
+ :func:`.remote`
"""
- return _annotate_columns(expr, {"foreign":True,
- "remote":True})
+ return _annotate_columns(expression._clause_element_as_expr(expr),
+ {"foreign": True})
+
def _annotate_columns(element, annotations):
def clone(elem):
diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py
index 15a78b842..5a8b086d9 100644
--- a/lib/sqlalchemy/orm/session.py
+++ b/lib/sqlalchemy/orm/session.py
@@ -90,7 +90,7 @@ class SessionTransaction(object):
:meth:`.Session.begin` method is called.
Another detail of :class:`.SessionTransaction` behavior is that it is
- capable of "nesting". This means that the :meth:`.begin` method can
+ capable of "nesting". This means that the :meth:`.Session.begin` method can
be called while an existing :class:`.SessionTransaction` is already present,
producing a new :class:`.SessionTransaction` that temporarily replaces
the parent :class:`.SessionTransaction`. When a :class:`.SessionTransaction`
@@ -101,8 +101,8 @@ class SessionTransaction(object):
behavior is effectively a stack, where :attr:`.Session.transaction` refers
to the current head of the stack.
- The purpose of this stack is to allow nesting of :meth:`.rollback` or
- :meth:`.commit` calls in context with various flavors of :meth:`.begin`.
+ The purpose of this stack is to allow nesting of :meth:`.Session.rollback` or
+ :meth:`.Session.commit` calls in context with various flavors of :meth:`.Session.begin`.
This nesting behavior applies to when :meth:`.Session.begin_nested`
is used to emit a SAVEPOINT transaction, and is also used to produce
a so-called "subtransaction" which allows a block of code to use a
@@ -1628,6 +1628,11 @@ class Session(_SessionClassMethods):
"""Associate an object with this :class:`.Session` for related
object loading.
+ .. warning::
+
+ :meth:`.enable_relationship_loading` exists to serve special
+ use cases and is not recommended for general use.
+
Accesses of attributes mapped with :func:`.relationship`
will attempt to load a value from the database using this
:class:`.Session` as the source of connectivity. The values
@@ -1636,7 +1641,7 @@ class Session(_SessionClassMethods):
generally only works for many-to-one-relationships.
The object will be attached to this session, but will
- ''not'' participate in any persistence operations; its state
+ **not** participate in any persistence operations; its state
for almost all purposes will remain either "transient" or
"detached", except for the case of relationship loading.
@@ -1988,18 +1993,18 @@ class Session(_SessionClassMethods):
The "partial rollback" state refers to when an "inner" transaction,
typically used during a flush, encounters an error and emits
a rollback of the DBAPI connection. At this point, the :class:`.Session`
- is in "partial rollback" and awaits for the user to call :meth:`.rollback`,
+ is in "partial rollback" and awaits for the user to call :meth:`.Session.rollback`,
in order to close out the transaction stack. It is in this "partial
rollback" period that the :attr:`.is_active` flag returns False. After
- the call to :meth:`.rollback`, the :class:`.SessionTransaction` is replaced
+ the call to :meth:`.Session.rollback`, the :class:`.SessionTransaction` is replaced
with a new one and :attr:`.is_active` returns ``True`` again.
When a :class:`.Session` is used in ``autocommit=True`` mode, the
:class:`.SessionTransaction` is only instantiated within the scope
of a flush call, or when :meth:`.Session.begin` is called. So
:attr:`.is_active` will always be ``False`` outside of a flush or
- :meth:`.begin` block in this mode, and will be ``True`` within the
- :meth:`.begin` block as long as it doesn't enter "partial rollback"
+ :meth:`.Session.begin` block in this mode, and will be ``True`` within the
+ :meth:`.Session.begin` block as long as it doesn't enter "partial rollback"
state.
From all the above, it follows that the only purpose to this flag is
diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py
index 28c13398f..2c0769012 100644
--- a/lib/sqlalchemy/sql/util.py
+++ b/lib/sqlalchemy/sql/util.py
@@ -411,7 +411,7 @@ class Annotated(object):
except KeyError:
cls = annotated_classes[element.__class__] = type.__new__(type,
"Annotated%s" % element.__class__.__name__,
- (Annotated, element.__class__), {})
+ (cls, element.__class__), {})
return object.__new__(cls)
def __init__(self, element, values):
@@ -462,7 +462,7 @@ class Annotated(object):
# update the clone with any changes that have occurred
# to this object's __dict__.
clone.__dict__.update(self.__dict__)
- return Annotated(clone, self._annotations)
+ return self.__class__(clone, self._annotations)
def __hash__(self):
return hash(self.__element)
@@ -473,6 +473,23 @@ class Annotated(object):
else:
return hash(other) == hash(self)
+class AnnotatedColumnElement(Annotated):
+ def __init__(self, element, values):
+ Annotated.__init__(self, element, values)
+ for attr in ('name', 'key'):
+ if self.__dict__.get(attr, False) is None:
+ self.__dict__.pop(attr)
+
+ @util.memoized_property
+ def name(self):
+ """pull 'name' from parent, if not present"""
+ return self._Annotated__element.name
+
+ @util.memoized_property
+ def key(self):
+ """pull 'key' from parent, if not present"""
+ return self._Annotated__element.key
+
# hard-generate Annotated subclasses. this technique
# is used instead of on-the-fly types (i.e. type.__new__())
@@ -481,9 +498,13 @@ annotated_classes = {}
for cls in expression.__dict__.values() + [schema.Column, schema.Table]:
if isinstance(cls, type) and issubclass(cls, expression.ClauseElement):
- exec "class Annotated%s(Annotated, cls):\n" \
- " pass" % (cls.__name__, ) in locals()
- exec "annotated_classes[cls] = Annotated%s" % (cls.__name__)
+ if issubclass(cls, expression.ColumnElement):
+ annotation_cls = "AnnotatedColumnElement"
+ else:
+ annotation_cls = "Annotated"
+ exec "class Annotated%s(%s, cls):\n" \
+ " pass" % (cls.__name__, annotation_cls) in locals()
+ exec "annotated_classes[cls] = Annotated%s" % (cls.__name__,)
def _deep_annotate(element, annotations, exclude=None):
"""Deep copy the given ClauseElement, annotating each element
diff --git a/test/orm/test_rel_fn.py b/test/orm/test_rel_fn.py
index ac56f876b..bad3a0dd7 100644
--- a/test/orm/test_rel_fn.py
+++ b/test/orm/test_rel_fn.py
@@ -1,7 +1,7 @@
from sqlalchemy.testing import assert_raises, assert_raises_message, eq_, \
AssertsCompiledSQL, is_
from sqlalchemy.testing import fixtures
-from sqlalchemy.orm import relationships, foreign, remote, remote_foreign
+from sqlalchemy.orm import relationships, foreign, remote
from sqlalchemy import MetaData, Table, Column, ForeignKey, Integer, \
select, ForeignKeyConstraint, exc, func, and_
from sqlalchemy.orm.interfaces import ONETOMANY, MANYTOONE, MANYTOMANY
@@ -245,9 +245,9 @@ class _JoinFixtures(object):
self.left,
self.right,
primaryjoin=(self.left.c.x + self.left.c.y) == \
- relationships.remote_foreign(
+ relationships.remote(relationships.foreign(
self.right.c.x * self.right.c.y
- ),
+ )),
**kw
)
diff --git a/test/sql/test_selectable.py b/test/sql/test_selectable.py
index bbf7eeab1..35d5a0b05 100644
--- a/test/sql/test_selectable.py
+++ b/test/sql/test_selectable.py
@@ -1277,6 +1277,13 @@ class AnnotationsTest(fixtures.TestBase):
assert x_p.compare(x_p_a)
assert not x_p_a.compare(x_a)
+ def test_late_name_add(self):
+ from sqlalchemy.schema import Column
+ c1 = Column(Integer)
+ c1_a = c1._annotate({"foo": "bar"})
+ c1.name = 'somename'
+ eq_(c1_a.name, 'somename')
+
def test_custom_constructions(self):
from sqlalchemy.schema import Column
class MyColumn(Column):