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. -- http://mail.python.org/mailman/listinfo/python-list