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

Jim Fulton jim@zope.com
Wed, 25 Dec 2002 09:13:46 -0500


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

Added Files:
	__init__.py modulegen.py schemaspec.py typereg.py 
Log Message:
Grand renaming:

- Renamed most files (especially python modules) to lower case.

- Moved views and interfaces into separate hierarchies within each
  project, where each top-level directory under the zope package
  is a separate project.

- Moved everything to src from lib/python.

  lib/python will eventually go away. I need access to the cvs
  repository to make this happen, however.

There are probably some bits that are broken. All tests pass
and zope runs, but I haven't tried everything. There are a number
of cleanups I'll work on tomorrow.



=== Zope3/src/zope/app/schemagen/__init__.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:45 2002
+++ Zope3/src/zope/app/schemagen/__init__.py	Wed Dec 25 09:13:14 2002
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.


=== Zope3/src/zope/app/schemagen/modulegen.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:45 2002
+++ Zope3/src/zope/app/schemagen/modulegen.py	Wed Dec 25 09:13:14 2002
@@ -0,0 +1,76 @@
+##############################################################################
+#
+# 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$
+"""
+
+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()
+
+    # Normalize to one trailing newline.  This helps exact-match tests pass.
+    # Since the loop is unlikely to go around more than once, don't bother
+    # with a fancy algorithm.
+    while text.endswith('\n\n'):
+        text = text[:-1]
+    return text


=== Zope3/src/zope/app/schemagen/schemaspec.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:45 2002
+++ Zope3/src/zope/app/schemagen/schemaspec.py	Wed Dec 25 09:13:14 2002
@@ -0,0 +1,238 @@
+##############################################################################
+#
+# 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$
+"""
+__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


=== Zope3/src/zope/app/schemagen/typereg.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:45 2002
+++ Zope3/src/zope/app/schemagen/typereg.py	Wed Dec 25 09:13:14 2002
@@ -0,0 +1,150 @@
+##############################################################################
+#
+# 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$
+"""
+
+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)