On 12/13/06, Christian Theune <[EMAIL PROTECTED]> wrote:
Hi,

I've been trying to DRY some code and found that the following pattern
keeps popping up:

class SomeObject(object):

        def __init__(self, many_keyword_arguments):
                self.y = y
                self.x = x
                self.z = z

From the outside this then is called from a view (where data comes from
a formlib AddForm:

        def create(self, **data)
                return SomeObject(**data)

I'd like to remove the explicit assignments from the constructor and use
a function to do this in a generic way. I didn't find anything that does
this yet, except from a formlib function that requires FormFields
instead of a schema, so I wrote my own:

-----
def set_schema_data(context, schema, data):
    """Update the context's attributes with the given data that belongs
       to the specified schema.
    """
    for name in schema:
        field = schema[name]
        if not zope.schema.interfaces.IField.providedBy(field):
            continue
        if name in data:
            field.set(context, data[name])
-----

I've used a similar pattern for a while - especially in a couple of
non-zope experiments that used interfaces and schema. I've noticed
that I seldom write initializers for my persistent objects anymore, as
I often have formlib or some other function applying data. That's
probably a bad habit.

One thing that I like about formlib is the ability to combine fields
from multiple Interfaces together, and have adaptation apply them.
Outside of form usage, however, this might not be as necessary. With
forms, it's excellent, as DublinCore fields (like 'title') can easily
be included in the field set.

There are more complex things you could do in here, such as validation
or setting of defaults. Even without that, here's what I would do::

   import zope.schema

   def applyschema(target, data, schema, validate=False, applydefaults=False):
       for name, field in zope.schema.getFieldsInOrder(schema):
           field = field.bind(target)

           if name not in data:
               if applydefaults:
                   field.set(target, field.default)
               continue

           value = data.get(name)
           if validate:
               field.validate(value)
           field.set(target, value)

There is a function around already which comes from zope.formlib and
requires the schema to be specified as a FormFields setup. I'd like to
not have the formlib dependency in this code.

I propose to include the given function in zope.schema or
zope.interface. Eventually this could also be spelled as a method on a
schema (symmetrical to field.set): schema.set(object, data)

Would that then be a function of Interfaces? zope.schema is basically
nothing but a bunch of extensions of `zope.interface.Attribute` and
some helper functions to filter out Field objects from the Interface's
members. If applied to zope.interface, you'd have to either move the
basic definition of a 'schema field' to zope.interface (which I
believe is beyond its scope), or have 'schema.set' work for Attributes
(but not interface.Method, which is also extensions of Attribute).

I'd propose putting this in `zope.schema._schema` or some other module
in zope.schema for utility functions. It could also include my
favorite trick - a schema-to-dict-by-way-of-tuple-pairs iterator!

   def __iter__(self):
       """ Yields pairs of (name, value) reflecting IStreetAddress """
       for name, field in zope.schema.getFieldsInOrder(IStreetAddress):
           yield (name, field.query(self, field.default))

Since ``dict(iterable)`` works if the iterable produces (key,value)
pairs, this is a handy to get consistent mappings out of objects. I
use the above heavily to copy / move address data around between ZODB,
RDBMS (via SQLAlchemy), and CSV files. Maybe made as a general
function like this? ::

   _marker = object()
   def pairs(source, schema, applydefaults=True):
       """
       Yields pairs of (name, value) from `source` based on the schema fields
       in 'schema'. If 'applydefaults' is True (default), missing values will
       be replaced with the default specified on the field.
       """
       for name, field in zope.schema.getFieldsInOrder(schema):
           value = field.query(source, _marker)
           if (value is _marker) and applydefaults:
               value = field.default
           yield (name, value)

   address = dict(pairs(contact.address, IStreetAddress))
   # or
   pprint.pprint(list(pairs(contact.address, IStreetAddress)))

that latter one is handy when debugging.

--
Jeff Shell
_______________________________________________
Zope3-dev mailing list
Zope3-dev@zope.org
Unsub: http://mail.zope.org/mailman/options/zope3-dev/archive%40mail-archive.com

Reply via email to