Python component model

Edward Diener No Spam eldiener_no_spam_here at earthlink.net
Tue Oct 10 17:29:58 EDT 2006


fumanchu wrote:
> Edward Diener No Spam wrote:
>> OK, here is my idea of what such a component model envisages as a list
>> of items. After this, unless I get some intelligent comments from people
>> who might be interested in what I envision, or something very similar, I
>> will be off to investigate it myself rather than do battle with the
>> horde of people who will just tell me that Python, being a great
>> language, does not need what I have suggested.
> 
> [This quote hacked up by me:]
>> 1) Component property: This is a glorified attribute with a type that
>> a) can be specified in a "static" manner, or discovered dynamically,
>> b) has converters between a string and the actual value
>> c) has a getter function to retrieve the value if it is readable and a
>> setter function to set the value if it is writable.
>> d) be either readable or writable or both.
>> e) not any Python class attribute since a component has the right
>> to specify only certain values as manipulatable in a design-time
>> RAD environment.
> 
> Whenever you say "glorified attribute", your first thought should be
> "Python descriptor" (but not your last--it's not a cure-all). They are
> able to do all of (a, b, c, d, e) which I marked in your text above.
> For example, here's a descriptor for
> attributes-you-want-to-persist-in-a-database from my ORM, Dejavu (see
> http://projects.amor.org/dejavu/browser/trunk/units.py#l290):
> 
> 
> class UnitProperty(object):
>     """Data descriptor for Unit data which will persist in storage."""
> 
>     def __init__(self, type=unicode, index=False, hints=None, key=None,
> default=None):
>         self.type = type
>         self.index = index
>         if hints is None: hints = {}
>         self.hints = hints
>         self.key = key
>         self.default = default
> 
>     def __get__(self, unit, unitclass=None):
>         if unit is None:
>             # When calling on the class instead of an instance...
>             return self
>         else:
>             return unit._properties[self.key]
> 
>     def __set__(self, unit, value):
>         if self.coerce:
>             value = self.coerce(unit, value)
>         oldvalue = unit._properties[self.key]
>         if oldvalue != value:
>             unit._properties[self.key] = value
> 
>     def coerce(self, unit, value):
>         if value is not None and not isinstance(value, self.type):
>             # Try to cast the value to self.type.
>             try:
>                 value = self.type(value)
>             except Exception, x:
>                 x.args += (value, type(value))
>                 raise
>         return value
> 
>     def __delete__(self, unit):
>         raise AttributeError("Unit Properties may not be deleted.")
> 
>> a) can be specified in a "static" manner, or discovered dynamically,
> 
> The "component model" can either scan a class for instances of
> UnitProperty or keep a registry of them in the class or elsewhere (via
> a metaclass + add_property functions).
> 
>> b) has converters between a string and the actual value
> 
> Note the "coerce" function above. Something similar could be done for
> serialization (which I can prove in my case because I use UnitProperty
> to help produce SQL ;) but you could just as easily pickle
> unit._properties and be done with it.
> 
>> c) has a getter function to retrieve the value if it is readable and a
>> setter function to set the value if it is writable.
>> d) be either readable or writable or both.
> 
> Descriptors that only have __get__ are read-only; if they have __set__
> they are read-write.
> 
>> e) not any Python class attribute since a component has the right
>> to specify only certain values as manipulatable in a design-time
>> RAD environment.
> 
> Right. Descriptors allow the creator of a class to use "normal"
> attributes (including functions) which don't participate in the
> component model.
> 
>> 2) Component event: This is an type which encapsulates an array, or a
>> list of callable objects with the same function signature, along with
>> the functionality to add and remove elements from the array, as well as
>> cycle through the array calling the callable objects as a particular
>> event is triggered. A component event is an event source for a
>> particular event. Component events have to be dicoverable by the Visual
>> RAD system so that an object's appropriate event handler, an event sink,
>> can be hooked to the component event itself, an event source, through a
>> design time interface which propagates the connection at run-time.
> 
> This can be accomplished by creating a ComponentEvent descriptor whose
> __get__ returns an object with a __call__ method. Here's a base class
> for something similar (again, from Dejavu):
> 
> class UnitAssociation(object):
>     """Non-data descriptor method to retrieve related Units via
> attributes."""
> 
>     to_many = None
> 
>     def __init__(self, nearKey, farClass, farKey):
>         # Since the keys will be used as kwarg keys, they must be
> strings.
>         self.nearKey = str(nearKey)
>         self.farKey = str(farKey)
> 
>         self.nearClass = None
>         self.farClass = farClass
> 
>     def __get__(self, unit, unitclass=None):
>         if unit is None:
>             # When calling on the class instead of an instance...
>             return self
>         else:
>             m = types.MethodType(self.related, unit, unitclass)
>             return m
> 
>     def __delete__(self, unit):
>         raise AttributeError("Unit Associations may not be deleted.")
> 
>     def related(self, unit, expr=None, **kwargs):
>         raise NotImplementedError
> 
> Subclasses override the "related" method, but a ComponentEvent class
> could just as easily do:
> 
>     def run(self, *args, **kwargs):
>         for sink in self.sinks:
>             sink(*args, **kwargs)
> 
>> 3: Component serialization: A component which has its properties and
>> events set by a visual design-time RAD environment needs to be
>> serialized at design time and deserialized at run-time. This can be a
>> default serialization of all component properties and events, or the
>> component itself can participate in the serilization effort either
>> wholly or partly.
> 
> In Dejavu, the UnitProperty class is your "component property" and the
> Unit class is the component. The Unit class has a copy method:
> 
>     def __copy__(self):
>         newUnit = self.__class__()
>         for key in self.properties:
>             if key in self.identifiers:
>                 prop = getattr(self.__class__, key)
>                 newUnit._properties[key] = prop.default
>             else:
>                 newUnit._properties[key] = self._properties[key]
>         newUnit.sandbox = None
>         return newUnit
> 
> It wouldn't be hard to replace "newUnit._properties[key] =
> self._properties[key]" with "dump(self._properties[key])".
> 
>> 4) Custom property and component editors: A component editor can present
>> a property editor or an editor for an entire component which the visual
>> design-time RAD environment can use to allow the programmer end-user of
>> the component to set or get component property values. Normally a design
>> time environment will present default property editors for each
>> component property type, but a component can override this.
> 
> This is the hard part. I believe Dabo has done some work in this space,
> but this is where the tight coupling comes in between code and tool, a
> coupling which Python has traditionally resisted.

It's more of a coupling between a type and a tool. Only user-defined 
types as component properties need their own property editor, whereas 
all other Python types can be coupled with default property editors. Of 
course the class designer must have a right to create their own property 
editor for a particular type or a particular property of that type in 
order to override the default property editors for a particular type.

> 
>> 5) Custom type converters: A component should be able to specify a
>> custom converter for any property to convert, in both directions or
>> either direction, between the property's string value as seen by a
>> property editor and the actual value of the component property's type.
> 
> A ComponentProperty descriptor could include a custom pair of methods
> to get/set as string. This is often done in web frameworks which need
> to coerce incoming string values to the correct type.

Thanks for your information abouit using descriptors. I will look into 
it further but I really appreciate your example.

> 
> All of which is to say: nobody's done this yet because parts 1, 2, 3
> and 5 are trivial to do with descriptors, but actually building a
> visual RAD environment is too much work. ;)

The idea of course is that with the correct component underpinnings and 
perhaps high-level modules which make it easy for a visual RAD 
environment to introspect the necessary component properties and 
component events, as well as serialize them at design time and create 
code to deserialize them at run-time, the work of creating a visual RAD 
environment would not be so daunting.



More information about the Python-list mailing list