Commits

Mike Bayer  committed 88b2d8a Merge

Fixed the (most likely never used) "@collection.link" collection
method, which fires off each time the collection is associated
or de-associated with a mapped object - the decorator
was not tested or functional. The decorator method
is now named :meth:`.collection.linker` though the name "link"
remains for backwards compatibility. Courtesy Luca Wehrstedt.
[ticket:2653]

  • Participants
  • Parent commits f7dc7d4, c6023d7

Comments (0)

Files changed (3)

File doc/build/changelog/changelog_08.rst

 
     .. change::
         :tags: orm, bug
+        :tickets: 2653
+
+      Fixed the (most likely never used) "@collection.link" collection
+      method, which fires off each time the collection is associated
+      or de-associated with a mapped object - the decorator
+      was not tested or functional.  The decorator method
+      is now named :meth:`.collection.linker` though the name "link"
+      remains for backwards compatibility.  Courtesy Luca Wehrstedt.
+
+    .. change::
+        :tags: orm, bug
         :tickets: 2654
 
       Made some fixes to the system of producing custom instrumented

File lib/sqlalchemy/orm/collections.py

 
     The decorators fall into two groups: annotations and interception recipes.
 
-    The annotating decorators (appender, remover, iterator,
-    internally_instrumented, link) indicate the method's purpose and take no
+    The annotating decorators (appender, remover, iterator, linker, converter,
+    internally_instrumented) indicate the method's purpose and take no
     arguments.  They are not written with parens::
 
         @collection.appender
         return fn
 
     @staticmethod
-    def link(fn):
-        """Tag the method as a the "linked to attribute" event handler.
+    def linker(fn):
+        """Tag the method as a "linked to attribute" event handler.
 
         This optional event handler will be called when the collection class
         is linked to or unlinked from the InstrumentedAttribute.  It is
         that has been linked, or None if unlinking.
 
         """
-        setattr(fn, '_sa_instrument_role', 'link')
+        setattr(fn, '_sa_instrument_role', 'linker')
         return fn
 
+    link = linker
+    """deprecated; synonym for :meth:`.collection.linker`."""
+
     @staticmethod
     def converter(fn):
         """Tag the method as the collection converter.
     def link_to_self(self, data):
         """Link a collection to this adapter, and fire a link event."""
         setattr(data, '_sa_adapter', self)
-        if hasattr(data, '_sa_on_link'):
-            getattr(data, '_sa_on_link')(self)
+        if hasattr(data, '_sa_linker'):
+            getattr(data, '_sa_linker')(self)
 
     def unlink(self, data):
         """Unlink a collection from any adapter, and fire a link event."""
         setattr(data, '_sa_adapter', None)
-        if hasattr(data, '_sa_on_link'):
-            getattr(data, '_sa_on_link')(None)
+        if hasattr(data, '_sa_linker'):
+            getattr(data, '_sa_linker')(None)
 
     def adapt_like_to_iterable(self, obj):
         """Converts collection-compatible objects to an iterable of values.
             if hasattr(method, '_sa_instrument_role'):
                 role = method._sa_instrument_role
                 assert role in ('appender', 'remover', 'iterator',
-                                'link', 'converter')
+                                'linker', 'converter')
                 roles.setdefault(role, name)
 
             # transfer instrumentation requests from decorated function

File test/orm/test_collection.py

         eq_(Sub._sa_iterator(Sub(), 5), "base_iterate")
         eq_(Sub._sa_converter(Sub(), 5), "sub_convert")
 
+    def test_link_event(self):
+        canary = []
+        class Collection(list):
+            @collection.linker
+            def _on_link(self, obj):
+                canary.append(obj)
 
+        class Foo(object):
+            pass
 
+        instrumentation.register_class(Foo)
+        attributes.register_attribute(Foo, 'attr', uselist=True,
+                                   typecallable=Collection, useobject=True)
+
+        f1 = Foo()
+        f1.attr.append(3)
+
+        eq_(canary, [f1.attr._sa_adapter])
+        adapter_1 = f1.attr._sa_adapter
+
+        l2 = Collection()
+        f1.attr = l2
+        eq_(canary, [adapter_1, f1.attr._sa_adapter, None])
+
+
+
+
+
+