Concrete Observable Properties

Concrete OPs have values stored inside the model. They are different from Logical OP whose values are calculated, or come from outside (e.g. from a database).

Values of OP are declared as class attributes in models, and OP names are declared in a special class member __observables__.

from gtkmvc import Model
# ----------------------------
class MyModel (Model):
   name = "Roberto"
   secondname = 'Mario'
   surname = "Cavada"
   energy = 0.2 # needs holidays!
   status = "sleepy"

   __observables__ = "name surname energy".split()
   pass # end of class

In the example, name, surname and energy are all observable, whereas status is not observable.

Special member __observables__ is a tuple (or a list) of names of class attributes that has to be observable. Names can contain wildcards like * to match any sequence of characters, ? to match one single character, etc. See module fnmatch in Python library for other information about possible use of wildcards in names. Important to say that if wildcards are used, attribute names starting with a double underscore __ will be not matched.

Note

It is also possible (but deprecated!) for the user to add a class variable called __properties__. This variable must be a map, whose elements’ keys are names of properties, and the associated values are the initial values. Using __properties__ is complementary to the use of __observables__, but it is provided for backward compatibility and should be not used in new code. This is an example of usage of deprecated __properties__, but you will not find another in this manual:

from gtkmvc import Model
class MyModelDeprecated (Model):
      __properties__ = {
      'name' : 'Rob',
      }
      pass # end of class

This is another example showing the usage of wildcards in names:

from gtkmvc import Model
# ----------------------------
class MyModel (Model):
   name = "Roberto"
   secondname = 'Mario'
   surname = "Cavada"
   energy = 0.2 # needs holidays!
   entropy = 1.0
   enology = "good science"
   status = "sleepy"

   __observables__ = ("*name", "en????y")
   pass # end of class

In the example, all attributes but energy and status are declared to be observable.

Concrete OP and inheritance

Things so far are easy enough, but they get a bit complicated when you derive custom models from other custom models. For example, what happens to OP if you derive a new model class from the class MyModel?

In this case the behavior of the OP trusty follows the typical Object Oriented rules:

  • Any concrete OP in base class are inherited by derived classes.
  • Derived class can override any concrete OP in base classes.
  • If multiple base classes defines the same OP, only the first OP will be accessible from the derived class.

For example:

from gtkmvc import Model

class Base (Model):
   prop1 = 1
   __observables__ = ("prop1", )

   def __init__(self):
       Model.__init__(self)

        # this class is an observer of its own properties:
       self.register_observer(self)
       return

   @Model.observe("prop1", assign=True)
   def prop1_changed(self, model, name, info):
       print model, "prop1 changed from '%s' to '%s'" % (info.old, info.new)
       return
   pass # end of class
# --------------------------------------------------------

class Der (Base):
   prop2 = 2
   __observables__ = ("prop2",)

   @Model.observe("prop2", assign=True)
   def prop2_changed(self, model, name, info):
       print self, "prop2 changed from '%s' to '%s'" % (info.old, info.new)
       return
   pass # end of class
# --------------------------------------------------------

# test code:
b = Base()
d = Der()

d.prop2 *= 10
d.prop1 *= 10
b.prop1 *= 10

When executed, this script generates this output:

<__main__.Der object  ...> prop2 changed from '2' to '20'
<__main__.Der object  ...> prop1 changed from '1' to '10'
<__main__.Base object ...> prop1 changed from '1' to '10'

Let’s analyse the example.

First, in the Base.__init__ constructor you can see that the instance registers itself as an observer... of itself! As we will see in section Observers, class Model derives from Observer, so all models are also observer.

In the example this is exploited only to write a compact example (it is not needed to define an additional class for the observer). However, in complex designs it is quite common to see models observing them self, or sub-models contained inside them.

Second, method Base.prop1_changed is explicitly marked to observe property prop1.

Third, in class Der only the OP prop2 is declared, as prop1 is inherited from class Base. This is clearly visible in the output:

<__main__.Der object  ...> prop1 changed from '1' to '10'

It is possible to change type and default values of OPs in derived class, by re-declaring the OSs. For example:

class Der (Base):
   prop1 = 3
   prop2 = 2
   __observables__ = ("prop?",)

   @Observer.observe("prop2", assign=True)
   def prop2_changed(self, model, name, info):
       print self, "prop2 changed from '%s' to '%s'" % (info.old, info.new)
       return
   pass # end of class
# --------------------------------------------------------

This would produce the output:

<__main__.Der object  ...> prop2 changed from '2' to '20'
<__main__.Der object  ...> prop1 changed from '3' to '30'
<__main__.Base object ...> prop1 changed from '1' to '10'

As you can see, d.prop1 overrides the OP prop1 defined in Base (they have different initial values now).

Table Of Contents

This Page