]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- remove remote_foreign annotation
authorMike Bayer <mike_mp@zzzcomputing.com>
Sun, 28 Oct 2012 22:23:57 +0000 (18:23 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sun, 28 Oct 2012 22:23:57 +0000 (18:23 -0400)
- support annotations on Column where name isn't immediately present

lib/sqlalchemy/orm/__init__.py
lib/sqlalchemy/orm/relationships.py
lib/sqlalchemy/orm/session.py
lib/sqlalchemy/sql/util.py
test/orm/test_rel_fn.py
test/sql/test_selectable.py

index ea8e5a36e241bee7207f16138b90e0445edf9254..b8085ca3c686805dd795a266713b818d68f1f9a6 100644 (file)
@@ -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
index c861edf836d4a2b7290a2d93a539816b2ed140aa..373fba785ee4dbb0e45528e0d9847b97cce442cf 100644 (file)
@@ -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):
index 15a78b842c6c819e8dfb463b0010d973da9f58df..5a8b086d97fecf8090de7ee571d62dd7d704d18b 100644 (file)
@@ -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
index 28c13398f45cbd3f1637de5a5ddfa8d58db9fc87..2c07690122dbd37dcf2fe271ac86d1c65807a888 100644 (file)
@@ -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
index ac56f876b8455e304fda1210537bcb9715c3c455..bad3a0dd770977fb7befec13361b8a52ddb890c5 100644 (file)
@@ -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
         )
 
index bbf7eeab12b3cf02f90a37ba1ee0795c190b9053..35d5a0b05002ccb5e63c9536151e16b253cb1a4c 100644 (file)
@@ -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):