Using an _AssociationList subclass as proxy_factory is awkward

Issue #1259 resolved
Former user created an issue

The standard association proxies used by the association proxy only blank out the foreign key column when you remove an item from the list. For many use cases this is not ideal since it leaves a lot of orphans in the database, something which the schema might not even allow.

As a workaround I wanted to customize the default _AssocationList to remove the target object instead of just removing it from the underlying collection. It turns out this is impossible: AssociationProxy instantiaties the built-in proxies with different parameters than a a proxy_factory you pass in by hand. This makes it impossible to make a derived class from _AssociationList and use that as proxy factory.

Comments (8)

  1. Former user Account Deleted

    For reference, this is the workaround I ended up using:

    class RemovingAssociationList(_AssociationList):
        """Assocation list which removes items from the database when they are
        removed from the collecting.
        """
    
        def __init__(self, lazy_collection, creator, value_attr):
            # AssociationProxy invokes a customer proxy factory differently than it
            # does the built-in ones. We have to play some tricks to reconstruct the
            # right parameters for us.
            proxy_locals=sys._getframe(1).f_locals
            proxy=proxy_locals["self"]("self")
    
            if proxy.getset_factory:
                getter, setter = proxy.getset_factory(proxy.collection_class, proxy)
            else:
                getter, setter = proxy._default_getset(proxy.collection_class)
    
            _AssociationList.__init__(self, proxy_locals["lazy_collection"]("lazy_collection"), proxy_locals["creator"]("creator"),
                    getter, setter)
    
  2. Former user Account Deleted

    For completeness sake: this is the rest of that class. Perhaps someone else finds this helpful as well.

        def _delete(self, index):
            obj=self.col[index](index)
            state=orm.attributes.instance_state(obj)
            # Be careful: if an object has not been persisted we have to expunge,
            # not delete.
            if state.key is None:
                meta.Session.expunge(obj)
            else:
                meta.Session.delete(obj)
    
            del self.col[index](index)
    
    
        def __delitem__(self, index):
            self._delete(index)
    
    
        def remove(self, value):
            for i, val in enumerate(self):
                if val == value:
                    self._delete(i)
                    return
            raise ValueError("value not in list")
    
  3. Former user Account Deleted

    .. of course this specific use case is invalid since one can just use the ''delete-orphan'' cascade option on the relation. The basic problem of it being impossible to use a customised version of the standard proxy factories remains though.

  4. jek

    Extending one of the _private types is hardly impossible. The getter and setter simply need to be manufactured to suit. Either use an adapting factory:

    def mylist_factory(lazy_collection, creator, value_attr):
        return MyList(lazy_collection, creator, operator.attrgetter(value_attr),
                      lambda o, v: setattr(o, attr, value_attr))
    

    or adapt in the __init__ of your subclass:

        def __init__(self, lazy_collection, creator, value_attr):
           super(MyList, self).__init__(lazy_collection, creator,
                                        operator.attrgetter(value_attr),
                                        lambda o, v: setattr(o, attr, value_attr))
    

    Will consider some sort of short-circuit if proxy_factory is a subclass of one of the built in proxies.

  5. Mike Bayer repo owner

    Replying to guest:

    The standard association proxies used by the association proxy only blank out the foreign key column when you remove an item from the list. For many use cases this is not ideal since it leaves a lot of orphans in the database, something which the schema might not even allow.

    Since jek already replied I guess I'm missing something, but aren't you just looking for delete, delete-orphan cascade here ? the "blank out the foreign keys" behavior is only one of two behaviors you can choose from.

  6. Log in to comment