Traits question

classic Classic list List threaded Threaded
7 messages Options
Reply | Threaded
Open this post in threaded view
|

Traits question

Des.P
If I have:

class Person(HasTraits):
        owns = Instance('House')

class House(HasTraits):
        owned_by = Instance(Person)

h = House()
p = Person(owns = h)  
# or
p.owns = h

Is there any way (e.g. by overriding something related to "owns=...") to add some code so that when
        p.owns
is set (either in the constructor, or in the separate assignment), the corresponding
        h.owned_by
also gets set?

Thanks!

_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev
Reply | Threaded
Open this post in threaded view
|

Re: Traits question

Robert Kern
On Tue, Mar 19, 2013 at 6:57 PM, Des.P <[hidden email]> wrote:

> If I have:
>
> class Person(HasTraits):
>         owns = Instance('House')
>
> class House(HasTraits):
>         owned_by = Instance(Person)
>
> h = House()
> p = Person(owns = h)
> # or
> p.owns = h
>
> Is there any way (e.g. by overriding something related to "owns=...") to add some code so that when
>         p.owns
> is set (either in the constructor, or in the separate assignment), the corresponding
>         h.owned_by
> also gets set?

from traits.api import HasTraits, Instance, WeakRef, Undefined

class Person(HasTraits):
        owns = Instance('House')

        def _owns_changed(self, old, new):
            if old is not None and old is not Undefined:
                old.owned_by = None
            if new is not None and new is not Undefined:
                new.owned_by = self

class House(HasTraits):
        owned_by = WeakRef(Person, allow_none=True)

h = House()
p = Person(owns=h)
print h.owned_by
p.owns = None
print h.owned_by
p.owns = h
print h.owned_by

--
Robert Kern
Enthought
_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev
Reply | Threaded
Open this post in threaded view
|

Re: Traits question

Des.P
Great, thanks Robert!  

I'd like to encapsulate this 'inverse' functionality in a single place. Is there any extensible hook in Traits that I can use to implement something broadly like this generic 'inverse' meta-data processing:

class Person(HasTraits):
        owns = Instance('House', inverse='owned_by')

        @traits_metadata_hook('inverse')  
        # would work as a class method
        # invoked by Traits when it encounters meta-data named 'inverse'
        def process_inverse(klass, trait_name, inverse_name):
                # assuming there is a way to get the names 'owns' & 'owned_by' as params
                # create the equivalent of def _owns_changed(...)
                # and install it on klass
                # I could do variations for 1-1, 1-N, N-M, etc.

It this is feasible, I would probably put it on a subclass of HasTraits and inherit that for my domain classes.

If not, perhaps I could just explicitly process all traits marked with my "inverse" meta-data like this:

process_inverse(Person, House, ...)

Thanks for any further advice!

On Mar 19, 2013, at 2:02 PM, Robert Kern wrote:

> On Tue, Mar 19, 2013 at 6:57 PM, Des.P <[hidden email]> wrote:
>> If I have:
>>
>> class Person(HasTraits):
>>        owns = Instance('House')
>>
>> class House(HasTraits):
>>        owned_by = Instance(Person)
>>
>> h = House()
>> p = Person(owns = h)
>> # or
>> p.owns = h
>>
>> Is there any way (e.g. by overriding something related to "owns=...") to add some code so that when
>>        p.owns
>> is set (either in the constructor, or in the separate assignment), the corresponding
>>        h.owned_by
>> also gets set?
>
> from traits.api import HasTraits, Instance, WeakRef, Undefined
>
> class Person(HasTraits):
>        owns = Instance('House')
>
>        def _owns_changed(self, old, new):
>            if old is not None and old is not Undefined:
>                old.owned_by = None
>            if new is not None and new is not Undefined:
>                new.owned_by = self
>
> class House(HasTraits):
>        owned_by = WeakRef(Person, allow_none=True)
>
> h = House()
> p = Person(owns=h)
> print h.owned_by
> p.owns = None
> print h.owned_by
> p.owns = h
> print h.owned_by
>
> --
> Robert Kern
> Enthought
> _______________________________________________
> Enthought-Dev mailing list
> [hidden email]
> https://mail.enthought.com/mailman/listinfo/enthought-dev

_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev
Reply | Threaded
Open this post in threaded view
|

Re: Traits question

Des.P
I did a test of this:
class Person(HasTraits):
    owns = Instance('House')
   
    def _owns_changed(self, old, new):
        if old is not None and old is not Undefined:
            old.owned_by = None
        if new is not None and new is not Undefined:
            new.owned_by = self

And it seems that the _owns_changed has to be part of the class definition itself for Traits to propagate changes i.e. I cannot later dynamically add it to the class Person with something like:

        Person._owns_changed = types.MethodType(_owns_changed, None, Person)

Can you confirm that Traits requires _x_changed methods to be in the original class definition itself?

Thanks!


On Mar 19, 2013, at 3:01 PM, Des.P wrote:

> Great, thanks Robert!  
>
> I'd like to encapsulate this 'inverse' functionality in a single place. Is there any extensible hook in Traits that I can use to implement something broadly like this generic 'inverse' meta-data processing:
>
> class Person(HasTraits):
> owns = Instance('House', inverse='owned_by')
>
> @traits_metadata_hook('inverse')  
> # would work as a class method
> # invoked by Traits when it encounters meta-data named 'inverse'
> def process_inverse(klass, trait_name, inverse_name):
> # assuming there is a way to get the names 'owns' & 'owned_by' as params
> # create the equivalent of def _owns_changed(...)
> # and install it on klass
> # I could do variations for 1-1, 1-N, N-M, etc.
>
> It this is feasible, I would probably put it on a subclass of HasTraits and inherit that for my domain classes.
>
> If not, perhaps I could just explicitly process all traits marked with my "inverse" meta-data like this:
>
> process_inverse(Person, House, ...)
>
> Thanks for any further advice!
>
> On Mar 19, 2013, at 2:02 PM, Robert Kern wrote:
>
>> On Tue, Mar 19, 2013 at 6:57 PM, Des.P <[hidden email]> wrote:
>>> If I have:
>>>
>>> class Person(HasTraits):
>>>       owns = Instance('House')
>>>
>>> class House(HasTraits):
>>>       owned_by = Instance(Person)
>>>
>>> h = House()
>>> p = Person(owns = h)
>>> # or
>>> p.owns = h
>>>
>>> Is there any way (e.g. by overriding something related to "owns=...") to add some code so that when
>>>       p.owns
>>> is set (either in the constructor, or in the separate assignment), the corresponding
>>>       h.owned_by
>>> also gets set?
>>
>> from traits.api import HasTraits, Instance, WeakRef, Undefined
>>
>> class Person(HasTraits):
>>       owns = Instance('House')
>>
>>       def _owns_changed(self, old, new):
>>           if old is not None and old is not Undefined:
>>               old.owned_by = None
>>           if new is not None and new is not Undefined:
>>               new.owned_by = self
>>
>> class House(HasTraits):
>>       owned_by = WeakRef(Person, allow_none=True)
>>
>> h = House()
>> p = Person(owns=h)
>> print h.owned_by
>> p.owns = None
>> print h.owned_by
>> p.owns = h
>> print h.owned_by
>>
>> --
>> Robert Kern
>> Enthought
>> _______________________________________________
>> Enthought-Dev mailing list
>> [hidden email]
>> https://mail.enthought.com/mailman/listinfo/enthought-dev
>

_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev
Reply | Threaded
Open this post in threaded view
|

Re: Traits question

Robert Kern
On Sun, Mar 24, 2013 at 3:51 PM, Des.P <[hidden email]> wrote:

> I did a test of this:
> class Person(HasTraits):
>     owns = Instance('House')
>
>     def _owns_changed(self, old, new):
>         if old is not None and old is not Undefined:
>             old.owned_by = None
>         if new is not None and new is not Undefined:
>             new.owned_by = self
>
> And it seems that the _owns_changed has to be part of the class definition itself for Traits to propagate changes i.e. I cannot later dynamically add it to the class Person with something like:
>
>         Person._owns_changed = types.MethodType(_owns_changed, None, Person)
>
> Can you confirm that Traits requires _x_changed methods to be in the original class definition itself?

Yes, they are analyzed and hooked up by the metaclass underneath
HasTraits on class construction time. Why do you want to add it to the
class after the class has been defined?

--
Robert Kern
Enthought
_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev
Reply | Threaded
Open this post in threaded view
|

Re: Traits question

Des.P
In reply to this post by Des.P
Thanks, Robert. More below...

On Sun, Mar 24, 2013 at 3:51 PM, Des.P <[hidden email]> wrote:
I did a test of this:
class Person(HasTraits):
  owns = Instance('House')

  def _owns_changed(self, old, new):
      if old is not None and old is not Undefined:
          old.owned_by = None
      if new is not None and new is not Undefined:
          new.owned_by = self

And it seems that the _owns_changed has to be part of the class definition itself for Traits to propagate changes i.e. I cannot later dynamically add it to the class Person with something like:

      Person._owns_changed = types.MethodType(_owns_changed, None, Person)

Can you confirm that Traits requires _x_changed methods to be in the original class definition itself?

Yes, they are analyzed and hooked up by the metaclass underneath
HasTraits on class construction time. Why do you want to add it to the
class after the class has been defined?

Good question. My actual goal is to be able to use something roughly like this:
class Person(HasTraits):
 owns = Instance('House', inverse="owned_by")

and then auto-generate supporting methods based on 'owns' & inverse='owned_by':
 def _owns_changed(self, old, new): ....

and add such methods to both Person & House as appropriate, to correctly maintain inverse relations in my models. I use such inverse relations a lot, and the code for 
_<xyz>_changed
is identical everywhere (with a variant for 1-1, 1-N, and N-M relations).

Any hooks in Traits I could utilize? Or any alternative approaches I should consider? 

Thanks!

_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev
Reply | Threaded
Open this post in threaded view
|

Re: Traits question

Robert Kern
On Tue, Mar 26, 2013 at 2:19 PM, Des.P <[hidden email]> wrote:
> Thanks, Robert. More below...

> Good question. My actual goal is to be able to use something roughly like
> this:
> class Person(HasTraits):
>  owns = Instance('House', inverse="owned_by")
>
> and then auto-generate supporting methods based on 'owns' &
> inverse='owned_by':
>  def _owns_changed(self, old, new): ....
>
> and add such methods to both Person & House as appropriate, to correctly
> maintain inverse relations in my models. I use such inverse relations a lot,
> and the code for
> _<xyz>_changed
> is identical everywhere (with a variant for 1-1, 1-N, and N-M relations).
>
> Any hooks in Traits I could utilize? Or any alternative approaches I should
> consider?

I would recommend going with the base class that has a
`_process_inverse()` method similar to what you sketched earlier. Here
is the Traits machinery to do it:


from traits.api import HasTraits, Instance, WeakRef, on_trait_change


class HasInverse(HasTraits):

    @on_trait_change('+inverse')
    def _process_inverse(self, object, name, old, new):
        """ Trait change handler for all traits with the `inverse` metadata.
        """
        # Get the name of the inverse trait on the owned objects.
        inverse_name = self.trait(name).inverse
        # Unset ourself on the old object.
        if old is not None:
            setattr(old, inverse_name, None)
        # Set ourself on the new object.
        if new is not None:
            setattr(new, inverse_name, self)


class Person(HasInverse):
    owns = Instance('House', inverse='owned_by')


class House(HasTraits):
    owned_by = WeakRef('Person', allow_none=True)


p = Person()
h = House()
print h.owned_by
p.owns = h
print h.owned_by
p.owns = None
print h.owned_by


--
Robert Kern
Enthought
_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev