Edward said:
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.