Commits

Mike Bayer  committed 10da484

- remove remote_foreign annotation
- support annotations on Column where name isn't immediately present

  • Participants
  • Parent commits 181d302

Comments (0)

Files changed (6)

File lib/sqlalchemy/orm/__init__.py

 from .relationships import (
     foreign,
     remote,
-    remote_foreign
 )
 from .session import (
     Session,
     'relationship',
     'relation',
     'remote',
-    'remote_foreign',
     'scoped_session',
     'sessionmaker',
     'subqueryload',
     :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

File lib/sqlalchemy/orm/relationships.py

     """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
 
-    """
+    .. seealso::
 
-    return _annotate_columns(expression._clause_element_as_expr(expr), {"foreign":True})
+        :ref:`relationship_custom_foreign`
 
-def remote_foreign(expr):
-    """Annotate a portion of a primaryjoin expression
-    with a 'remote' and 'foreign' annotation.
-
-    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):

File lib/sqlalchemy/orm/session.py

     :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`
     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
         """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
         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.
 
         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

File lib/sqlalchemy/sql/util.py

             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):
             # 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)
         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__())
 
 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

File test/orm/test_rel_fn.py

 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
             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
         )
 

File test/sql/test_selectable.py

         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):