[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)