[Zope3-checkins] CVS: Zope3/src/zope/app/schemagen - __init__.py:1.1.2.1 modulegen.py:1.1.2.1 schemaspec.py:1.1.2.1 typereg.py:1.1.2.1

Jim Fulton jim@zope.com
Mon, 23 Dec 2002 14:32:15 -0500


Update of /cvs-repository/Zope3/src/zope/app/schemagen
In directory cvs.zope.org:/tmp/cvs-serv19908/zope/app/schemagen

Added Files:
      Tag: NameGeddon-branch
	__init__.py modulegen.py schemaspec.py typereg.py 
Log Message:
Initial renaming before debugging

=== Added File Zope3/src/zope/app/schemagen/__init__.py ===
#
# This file is necessary to make this directory a package.


=== Added File Zope3/src/zope/app/schemagen/modulegen.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
# 
##############################################################################
"""
$Id: modulegen.py,v 1.1.2.1 2002/12/23 19:32:13 jim Exp $
"""

from zope.app.schemagen.typereg import fieldRegistry

def generateModuleSource(schema_name, fields, class_name,
                         schema_version=0, extra_imports='', extra_methods=''):
    """Generate module source from schema information.

    extra_methods if supplied needs to be a string with a four space indent.
    """
    if extra_methods:
        extra_methods = '\n%s' % extra_methods
    if extra_imports:
        extra_imports = '%s\n' % extra_imports
        
    import_list = []
    field_text_list = []
    property_text_list = []
    for field_name, field in fields:
        r = fieldRegistry.represent(field)
        field_text_list.append('    %s = %s' % (field_name, r.text))
        property_text_list.append(
            "    %s = "
            "FieldProperty(%s['%s'])" % (
            field_name, schema_name, field_name))
        for import_entry in r.importList:
            import_list.append("from %s import %s" % import_entry)
    
    import_text = '\n'.join(import_list)
    field_text = '\n'.join(field_text_list)
    property_text = '\n'.join(property_text_list)
         
    text = '''\
from zope.interface import Interface
from persistence import Persistent
from zope.schema.fieldproperty import FieldProperty

# field imports
%(import_text)s
%(extra_imports)s
class %(schema_name)s(Interface):
    """Autogenerated schema."""
%(field_text)s
    
class %(class_name)s(Persistent):
    """Autogenerated class for %(schema_name)s."""
    __implements__ = %(schema_name)s

    def __init__(self):
        self.__schema_version__ = %(schema_version)s
        
%(property_text)s
%(extra_methods)s
''' % vars()
    
    return text



=== Added File Zope3/src/zope/app/schemagen/schemaspec.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
$Id: schemaspec.py,v 1.1.2.1 2002/12/23 19:32:13 jim Exp $
"""
__metaclass__ = type

from zope.app.interfaces.schemagen import ISchemaSpec
from persistence import Persistent
from zope.app.schemagen.modulegen import generateModuleSource

_helper_import = 'from Zope.App.schemagen import schemaspec'
_helper_module = 'schemaspec'

class SchemaSpec(Persistent):
    __implements__ = ISchemaSpec

    def __init__(self, schema_name, class_name=None):
        if class_name is None:
            class_name = schema_name + 'Class'
            if class_name.startswith('I'):
                class_name = class_name[1:]
        self._schema_name = schema_name
        self._class_name = class_name
        self._fields = {}
        self._field_names = []
        self._current_version = 0
        self._history = []

    def _appendHistory(self, action):
        self._history.append(action)
        self._current_version += 1
    
    def addField(self, name, field):
        if name in self._fields:
            raise KeyError, "Field %s already exists." % name
        # XXX should check name is a sensible pythonic name
        self._field_names.append(name)
        self._fields[name] = field
        action = AddField(name)
        self._appendHistory(action)
        self._p_changed = 1
        return action
    
    def removeField(self, name):
        if name not in self._fields:
            raise KeyError, "Field %s does not exist." % name
        del self._fields[name]
        self._field_names.remove(name)
        action = RemoveField(name)
        self._appendHistory(action)
        self._p_changed = 1
        return action

    def renameField(self, orig_name, target_name):
        if orig_name not in self._fields:
            raise KeyError, "Field %s does not exist." % orig_name
        if target_name in self._fields:
            raise KeyError, "Field %s already exists." % target_name
        # XXX should check target_name is pythonic
        position = self._field_names.index(orig_name)
        self._field_names[position] = target_name
        self._fields[target_name] = self._fields[orig_name]
        del self._fields[orig_name]
        action = RenameField(orig_name, target_name)
        self._appendHistory(action)
        self._p_changed = 1
        return action
    
    def insertField(self, name, field, position):
        if name in self._fields:
            raise KeyError, "Field %s already exists." % name
        if not 0 <= position <= len(self._field_names):
            raise IndexError, "Position %s out of range." % name
        # XXX should check name is pythonic
        self._fields[name] = field
        self._field_names.insert(position, name)
        action = InsertField(name, position)
        self._appendHistory(action)
        self._p_changed = 1
        return action
    
    def moveField(self, name, position):
        if name not in self._fields:
            raise KeyError, "Field %s does not exist." % name
        if not 0 <= position <= len(self._field_names):
            raise IndexError, "Position %s out of range." % name
        self._field_names.remove(name)
        self._field_names.insert(position, name)
        action = MoveField(name, position)
        self._appendHistory(action)
        self._p_changed = 1
        return action
    
    def getFieldsInOrder(self):
        """Get all fields in order as (name, field) tuples.
        """
        return [(field_name, self._fields[field_name])
                for field_name in self._field_names]

    def getCurrentVersion(self):
        return self._current_version
    
    def getHistory(self):
        return self._history

    def generateSetstateSource(self):
        lines = [
        'transformations = %s.prepareSetstate(self, state, %r)' % (
            _helper_module, self._current_version),
        'if transformations is None:',
        '    return',
        'dict = self.__dict__'
        ]
        count = self._current_version - len(self._history)
        for item in self._history:
            args = ', '.join(map(repr, item.code()))
            if args:
                args = ', '+args
            lines.append('if %s in transformations:' % count)
            lines.append('    %s.%s.update(dict, state%s)' % (
                _helper_module, type(item).__name__, args))
            count += 1
            
        method_text_list = ['    def __setstate__(self, state):']
        # indent by 8:       '12345678%s'
        method_text_list += ['        %s' % line for line in lines]
        return '\n'.join(method_text_list) + '\n'
    
    def generateModuleSource(self):
        if not self._history:
            # don't generate any __setstate__ when there is no history
            # to update from
            return generateModuleSource(
                self._schema_name, self.getFieldsInOrder(),
                self._class_name, schema_version=self._current_version)
        else:
            return generateModuleSource(
                self._schema_name, self.getFieldsInOrder(),
                self._class_name,
                schema_version=self._current_version,
                extra_imports=_helper_import,
                extra_methods=self.generateSetstateSource())

# future plans, perhaps:
# make each of these classes have views, and a method that
# returns a clear explanation of the consequences of this change
# for instances of the schema class.
# Make the history viewable by schema authors.
# Make individual items in the history turn-off-and-on-able
# so that you can choose to treat an add.... remove-add as a no-op,
# and preserve the state of instances.
class AddField:
    def __init__(self, name):
        self.name = name

    def code(self):
        return (self.name,)

    def update(dict, state, name):
        # clear the way for any default value if schema changes are
        # undone
        try:
            del dict[name]
        except KeyError:
            pass
    update = staticmethod(update)
    
class RemoveField:
    def __init__(self, name):
        self.name = name

    def code(self):
        return (self.name,)

    def update(dict, state, name):
        del dict[name]
    update = staticmethod(update)
    
class RenameField:
    def __init__(self, original, target):
        self.original = original
        self.target = target

    def code(self):
        return self.original, self.target

    def update(dict, state, original, target):
        del dict[original]
        dict[target] = state[original]
    update = staticmethod(update)
    
class InsertField:
    def __init__(self, name, position):
        self.name = name
        self.position = position

    def code(self):
        return self.name, self.position

    def update(dict, state, name, position):
        pass
    update = staticmethod(update)

class MoveField:
    def __init__(self, name, position):
        self.name = name
        self.position = position

    def code(self):
        return self.name, self.position

    def update(dict, state, name, position):
        pass
    update = staticmethod(update)

def prepareSetstate(obj, state, schema_version):
    obj.__dict__.update(state)
    state_version = state['__schema_version__']
    if schema_version == state_version:
        return None
    assert state_version < schema_version
    obj.__schema_version__ = schema_version
    d = {}
    for i in range(state_version, schema_version):
        d[i] = 1
    return d


=== Added File Zope3/src/zope/app/schemagen/typereg.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""XXX short summary goes here.

XXX longer description goes here.

$Id: typereg.py,v 1.1.2.1 2002/12/23 19:32:13 jim Exp $
"""

from zope.interface.implements import visitImplements
from zope.schema import getFields

from zope.app.interfaces.schemagen import ITypeRepresentation

class TypeRepresentationRegistry:
    def __init__(self, default):
        self._registry = {}
        self._default = default
        
    #def registerType(self, type, factory):
    #    self._registry[type] = factory

    def represent(self, object):
        # returns an ITypeRepresentation object
        factory = self._registry.get(type(object))
        if factory is not None:
            return factory(object)
        return self._default(object)

    def register(self, representation):
        for type in representation.getTypes():
            self._registry[type] = representation
        
class DefaultTypeRepresentation:
    __implements__ = ITypeRepresentation

    def __init__(self, object):
        self.text = repr(object)
        import_name = type(object).__name__
        import_module = type(object).__module__
        if import_module != '__builtin__':
            self.importList = [(import_module, import_name)]
        else:
            self.importList = []
            
    def getTypes():
        return ()
    getTypes = staticmethod(getTypes)

class DatetimeRepresentation:
    __implements__ = ITypeRepresentation

    def __init__(self, dt):
        r = repr(dt)
        assert r.startswith('datetime.')
        self.text = r[9:]
        self.importList = [('datetime', type(dt).__name__)]

    def getTypes():
        import datetime
        # XXX not supporting tz or timedelta yet
        return [
            datetime.date,
            datetime.datetime,
          # datetime.datetimetz,
            datetime.time,
          #  datetime.timedelta,
          # datetime.timetz
        ]
    getTypes = staticmethod(getTypes)

class DefaultFieldRepresentation:
    __implements__ = ITypeRepresentation

    def __init__(self, field):
        # This field is described by a schema, or schemas, as found in its
        # __implements__ attribute. The fields of this field's schema are
        # its properties -- that is, the things we give as arguments when
        # constructing this field.
        # We're going to get these properties, get source-code
        # representations of them, and sort out appropriate imports.
        names = {} # used as set of property names, ignoring values
        visitImplements(field.__implements__, field,
                        lambda interface: names.update(getFields(interface)))
        # getFields only returns data for Fields in the interface.
        # Otherwise, it returns an empty dict.
    
        # At this point, name_property is a dict with keys of the
        # property names, and values of property objects.
        # XXX I don't know if we're following proper MRO though.

        global typeRegistry
        self.importList =  self._getImportList(field)
        arguments = []
        # don't represent order of this field within its schema,
        # as that will be implicit
        if 'order' in names:
            del names['order']
        # we want to order the field constructor arguments according to the
        # order of the fields on the schema describing this field
        propertysorter = lambda x, y: cmp(x[1].order, y[1].order)
        names_items = names.items()
        names_items.sort(propertysorter)
        # make a representation of property value and collect necessary imports
        # we are not interested in the property field itself, just
        # property value
        for name, property in names_items:
            value = getattr(field, name)
            if property.default == value:
                continue
            representation = typeRegistry.represent(value)
            arguments.append((name, representation.text))
            for import_spec in representation.importList:
                self.importList.append(import_spec) 

        arguments_text = ', '.join(["%s=%s" % item for item in arguments])

        self.text = "%s(%s)" % (type(field).__name__, arguments_text)
        
    def getTypes():
        return ()
    getTypes = staticmethod(getTypes)

    def _getImportList(field):
        import Zope.Schema

        field_class = type(field)
        if getattr(Zope.Schema, field_class.__name__, None) is field_class:
            module_name = 'Zope.Schema'
        else:
            module_name = field_class.__module__
        return [(module_name, field_class.__name__)]
    
    _getImportList = staticmethod(_getImportList)
    
typeRegistry = TypeRepresentationRegistry(DefaultTypeRepresentation)
typeRegistry.register(DatetimeRepresentation)

fieldRegistry = TypeRepresentationRegistry(DefaultFieldRepresentation)