C
Carlos Ribeiro
Hello all,
I'm posting this to the list with the intention to form a group of
people interested in this type of solution. I'm not going to spam the
list with it, unless for occasional and relevant announcements. If
you're interested, drop me a note. But if for some reason you think
that this discussion is fine here at the c.l.py, please let me know.
** LONG POST AHEAD **
I'm doing good progress on my form declarative language library. The
idea (for those who haven't read about it) is to be able to specify a
form declaration in the following format:
class ComplexForm(Form):
class Header(Form):
nickname = TextBox(length=15, default="")
password = TextBox(length=10, default="", password=True)
name = TextBox(length=40, default="")
class Comment(Form):
comments = TextBox(length=200, default="", multiline=True)
This is an experimental approach, with the intention to allow one to
write the form description in pure Python code, with the least
syntactic clutter possible. The main alternative is to use a data
driven approach -- feeding dicts or XML based representation to a
constructor -- but as I said, that's not the goal of this experiment.
I'm already able to properly build the example mentioned above. The
result is a conventional class filled with all attributes, and an
extra <_fields> member which is a ordered list of field object
instances. The metaclass constructor checks for some issues:
1) For the purposes of GUI description, field attributes are
order-dependant. But the dict passed to the metaclass constructor
isn't ordered. My first solution used stack information (using the
inspect module) to solve this problem. Thomas Heller and Andrew Dalke
proposed a simpler solution using a global counter that I've included
in the current incarnation (I'm wondering why did I try that hack
before, this solution is *much* better).
2) The inner classes (Header and Comment, in the example above) are
converted to *instances* of the same name during the construction of
the outer class (ComplexForm, in the example). The goal was to achieve
consistency; if some field members are classes and other instances,
then you have to check types while iterating the _fields structure to
render the GUI -- that's not what I had in mind.
Building GUIs
I'm just starting to work on this part. A renderer object will take
the form as a parameter, and will build the GUI accordingly with the
structure contained there.
To build the GUI, the renderer has to iterate over its fields. It's a
recursive process, because a form can contain other forms, either
declared directly as classes as in the example above, or through
normal attributes. I''m working on a generic iterator interface for
the form, which would allow to proceed without recursion on the
renderer side, but I'm not sure if this is the best approach. I'm
still thinkering with this part of the code. Suggestions are welcome.
#=======================
Enough talk, that's the code. Use it as you want, but please, give it
proper attribution if you're going to use it elsewhere.
#=======================
from inspect import isclass
# auxiliar functions
def getmyposition():
"""
Returns the position of the caller, relative to the sequence of
statements that were executed during class creation.
Three possible methods were evaluated: using a global counter; using
a timestamp; or using the line number retrieved from the call stack.
For all purposes here, a simple counter is enough. It doesn't need
to be thread aware, because all that matters is the relative
ordering of statements that, by definition, will be executed within
the same thread during the class initialization.
"""
counter = 0
while True:
counter += 1
yield counter
# decorates getmyposition to hide the internal static var
getmyposition = getmyposition().next
def getfields(dct):
"""
takes a dictionary of class attributes and returns a decorated list
containing all valid field instances and their relative position.
"""
for fname, fobj in dct.items():
if isinstance(fobj,Component):
print "found box: ", fname, fobj._mypos
yield (fobj._mypos, (fname, fobj))
elif isclass(fobj) and issubclass(fobj,Component):
print "found container: ", fname, fobj._mypos
# substitutes the contained class for its instance
yield (fobj._mypos, (fname, fobj()))
else:
print "unknown: ", fname, fobj, type(fobj)
# returns the original object with tag order = 0
yield (0, (fname, fobj))
def makefieldsdict(dct):
# build the field list and sort it
fields = list(getfields(dct))
fields.sort()
# undecorate the list and return it converted to dict; the _field
# attribute is added automatically to refer to the sorted list
sorted_field_list = [field[1] for field in fields]
field_dict = dict(sorted_field_list)
field_dict['_fields'] = [field for field in sorted_field_list
if isinstance(field[1],Component)]
return field_dict
# metaclasses
class Container(type):
def __new__(cls, name, bases, dct):
# creates the class using only the processed field list
newclass = type.__new__(cls, name, bases, makefieldsdict(dct))
newclass._mypos = getmyposition()
return newclass
# Component is the ancestor of all elements in a
# form, including containers and boxes
class Component(object):
pass
# BoxElement is the ancestor of all elements that are
# instantiated as objects inside the container classes
class Box(Component):
def __init__(self):
self._mypos = getmyposition()
# a form is a container of other components
class Form(Component):
__metaclass__ = Container
# I'm trying several versions of the iterator interface, I really don't
# know which one is going to be useful in real cases -- I'll have to write
# more code an try some alternatives first
def iterfields1(self):
# this iterator returns field objects without recursion
for fname, fobj in self._fields:
yield (fname, fobj)
def iterfields2(self, _level=0):
# this iterator recursively returns tuples of
# (parent, level, iscontainer, name, component)
for fname, fobj in self._fields:
if hasattr(fobj, '_fields'):
yield (self, _level, True, fname, fobj)
for parent, level, iscont, name, field in
fobj.iterfields2(_level=_level+1):
yield (parent, level, iscont, name, field)
else:
yield (self, _level, False, fname, fobj)
def iterfields3(self):
# this iterator returns a nested list representation that is
# built recursively
for fname, fobj in self._fields:
if hasattr(fobj,'_fields'):
yield (self, fname, list(fobj.iterfields3()))
else:
yield (self, fname, fobj)
# data entry controls
class TextBox(Box):
def __init__(self, length=40, default="", password=False, multiline=False):
Box.__init__(self)
self._length = length
self._default = default
self._password = password
self._multiline = multiline
# forms
class UserForm(Form):
nickname = TextBox(length=15, default="")
password = TextBox(length=10, default="", password=True)
name = TextBox(length=40, default="")
class ComplexForm(Form):
class Header(Form):
nickname = TextBox(length=15, default="")
password = TextBox(length=10, default="", password=True)
name = TextBox(length=40, default="")
class Comment(Form):
comments = TextBox(length=200, default="", multiline=True)
summary = TextBox(length=200, default="", multiline=True)
# simple test for the iterators
x = ComplexForm()
print
print "ComplexForm.iterfields1"
print "\n".join([str(item) for item in x.iterfields1()])
print
print "ComplexForm.iterfields2"
print "\n".join([str(item) for item in x.iterfields2()])
print
print "ComplexForm.iterfields3"
print "\n".join([str(item) for item in x.iterfields3()])
print
--
Carlos Ribeiro
Consultoria em Projetos
blog: http://rascunhosrotos.blogspot.com
blog: http://pythonnotes.blogspot.com
mail: (e-mail address removed)
mail: (e-mail address removed)
I'm posting this to the list with the intention to form a group of
people interested in this type of solution. I'm not going to spam the
list with it, unless for occasional and relevant announcements. If
you're interested, drop me a note. But if for some reason you think
that this discussion is fine here at the c.l.py, please let me know.
** LONG POST AHEAD **
I'm doing good progress on my form declarative language library. The
idea (for those who haven't read about it) is to be able to specify a
form declaration in the following format:
class ComplexForm(Form):
class Header(Form):
nickname = TextBox(length=15, default="")
password = TextBox(length=10, default="", password=True)
name = TextBox(length=40, default="")
class Comment(Form):
comments = TextBox(length=200, default="", multiline=True)
This is an experimental approach, with the intention to allow one to
write the form description in pure Python code, with the least
syntactic clutter possible. The main alternative is to use a data
driven approach -- feeding dicts or XML based representation to a
constructor -- but as I said, that's not the goal of this experiment.
I'm already able to properly build the example mentioned above. The
result is a conventional class filled with all attributes, and an
extra <_fields> member which is a ordered list of field object
instances. The metaclass constructor checks for some issues:
1) For the purposes of GUI description, field attributes are
order-dependant. But the dict passed to the metaclass constructor
isn't ordered. My first solution used stack information (using the
inspect module) to solve this problem. Thomas Heller and Andrew Dalke
proposed a simpler solution using a global counter that I've included
in the current incarnation (I'm wondering why did I try that hack
before, this solution is *much* better).
2) The inner classes (Header and Comment, in the example above) are
converted to *instances* of the same name during the construction of
the outer class (ComplexForm, in the example). The goal was to achieve
consistency; if some field members are classes and other instances,
then you have to check types while iterating the _fields structure to
render the GUI -- that's not what I had in mind.
Building GUIs
I'm just starting to work on this part. A renderer object will take
the form as a parameter, and will build the GUI accordingly with the
structure contained there.
To build the GUI, the renderer has to iterate over its fields. It's a
recursive process, because a form can contain other forms, either
declared directly as classes as in the example above, or through
normal attributes. I''m working on a generic iterator interface for
the form, which would allow to proceed without recursion on the
renderer side, but I'm not sure if this is the best approach. I'm
still thinkering with this part of the code. Suggestions are welcome.
#=======================
Enough talk, that's the code. Use it as you want, but please, give it
proper attribution if you're going to use it elsewhere.
#=======================
from inspect import isclass
# auxiliar functions
def getmyposition():
"""
Returns the position of the caller, relative to the sequence of
statements that were executed during class creation.
Three possible methods were evaluated: using a global counter; using
a timestamp; or using the line number retrieved from the call stack.
For all purposes here, a simple counter is enough. It doesn't need
to be thread aware, because all that matters is the relative
ordering of statements that, by definition, will be executed within
the same thread during the class initialization.
"""
counter = 0
while True:
counter += 1
yield counter
# decorates getmyposition to hide the internal static var
getmyposition = getmyposition().next
def getfields(dct):
"""
takes a dictionary of class attributes and returns a decorated list
containing all valid field instances and their relative position.
"""
for fname, fobj in dct.items():
if isinstance(fobj,Component):
print "found box: ", fname, fobj._mypos
yield (fobj._mypos, (fname, fobj))
elif isclass(fobj) and issubclass(fobj,Component):
print "found container: ", fname, fobj._mypos
# substitutes the contained class for its instance
yield (fobj._mypos, (fname, fobj()))
else:
print "unknown: ", fname, fobj, type(fobj)
# returns the original object with tag order = 0
yield (0, (fname, fobj))
def makefieldsdict(dct):
# build the field list and sort it
fields = list(getfields(dct))
fields.sort()
# undecorate the list and return it converted to dict; the _field
# attribute is added automatically to refer to the sorted list
sorted_field_list = [field[1] for field in fields]
field_dict = dict(sorted_field_list)
field_dict['_fields'] = [field for field in sorted_field_list
if isinstance(field[1],Component)]
return field_dict
# metaclasses
class Container(type):
def __new__(cls, name, bases, dct):
# creates the class using only the processed field list
newclass = type.__new__(cls, name, bases, makefieldsdict(dct))
newclass._mypos = getmyposition()
return newclass
# Component is the ancestor of all elements in a
# form, including containers and boxes
class Component(object):
pass
# BoxElement is the ancestor of all elements that are
# instantiated as objects inside the container classes
class Box(Component):
def __init__(self):
self._mypos = getmyposition()
# a form is a container of other components
class Form(Component):
__metaclass__ = Container
# I'm trying several versions of the iterator interface, I really don't
# know which one is going to be useful in real cases -- I'll have to write
# more code an try some alternatives first
def iterfields1(self):
# this iterator returns field objects without recursion
for fname, fobj in self._fields:
yield (fname, fobj)
def iterfields2(self, _level=0):
# this iterator recursively returns tuples of
# (parent, level, iscontainer, name, component)
for fname, fobj in self._fields:
if hasattr(fobj, '_fields'):
yield (self, _level, True, fname, fobj)
for parent, level, iscont, name, field in
fobj.iterfields2(_level=_level+1):
yield (parent, level, iscont, name, field)
else:
yield (self, _level, False, fname, fobj)
def iterfields3(self):
# this iterator returns a nested list representation that is
# built recursively
for fname, fobj in self._fields:
if hasattr(fobj,'_fields'):
yield (self, fname, list(fobj.iterfields3()))
else:
yield (self, fname, fobj)
# data entry controls
class TextBox(Box):
def __init__(self, length=40, default="", password=False, multiline=False):
Box.__init__(self)
self._length = length
self._default = default
self._password = password
self._multiline = multiline
# forms
class UserForm(Form):
nickname = TextBox(length=15, default="")
password = TextBox(length=10, default="", password=True)
name = TextBox(length=40, default="")
class ComplexForm(Form):
class Header(Form):
nickname = TextBox(length=15, default="")
password = TextBox(length=10, default="", password=True)
name = TextBox(length=40, default="")
class Comment(Form):
comments = TextBox(length=200, default="", multiline=True)
summary = TextBox(length=200, default="", multiline=True)
# simple test for the iterators
x = ComplexForm()
print "ComplexForm.iterfields1"
print "\n".join([str(item) for item in x.iterfields1()])
print "ComplexForm.iterfields2"
print "\n".join([str(item) for item in x.iterfields2()])
print "ComplexForm.iterfields3"
print "\n".join([str(item) for item in x.iterfields3()])
--
Carlos Ribeiro
Consultoria em Projetos
blog: http://rascunhosrotos.blogspot.com
blog: http://pythonnotes.blogspot.com
mail: (e-mail address removed)
mail: (e-mail address removed)