[Zope3-checkins] CVS: Zope3/lib/python/Zope/App/schemagen - schemaspec.py:1.1 interfaces.py:1.3
Martijn Faassen
m.faassen@vet.uu.nl
Thu, 12 Dec 2002 12:43:16 -0500
Update of /cvs-repository/Zope3/lib/python/Zope/App/schemagen
In directory cvs.zope.org:/tmp/cvs-serv11111
Modified Files:
interfaces.py
Added Files:
schemaspec.py
Log Message:
Added a SchemaSpec class. This is designed to allow programmatic
manipulation of a (persistent) schema specification. The schema
specification keeps a history of all operations that happened to it.
This history can then be used to generate a __setstate__ method for
classes that are generated from the schema that is able to automatically
update instances of that class to the most recent version of the schema.
The latter part is untested, though we do test whether the code
generation works.
=== Added File Zope3/lib/python/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 2002/12/12 17:43:15 faassen Exp $
"""
__metaclass__ = type
from interfaces 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'
]
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):
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 current_version == state_version:
return None
assert state_version < current_version
self.__schema_version__ = schema_version
for i in range(state_version, current_version):
d[i] = 1
return d
=== Zope3/lib/python/Zope/App/schemagen/interfaces.py 1.2 => 1.3 ===
--- Zope3/lib/python/Zope/App/schemagen/interfaces.py:1.2 Wed Dec 11 14:07:22 2002
+++ Zope3/lib/python/Zope/App/schemagen/interfaces.py Thu Dec 12 12:43:15 2002
@@ -20,20 +20,6 @@
from Interface import Interface
from Interface.Attribute import Attribute
-
-class IModuleGenerator:
- def generateModuleSource(schema_name, fields, class_name,
- schema_history=None):
- """Generate module source containing an interface and class.
-
- schema_name the name of the schema to generate, fields is the
- fields in the schema (in order), class_name the name of the
- class that implements the schema. schema_history gives the
- history of the schema up till now. This will be used
- eventually in order to create class transformation code so its
- contents can be updated to a newer version of the schema.
- """
-
class ITypeRepresentation(Interface):
"""Provide a textual representation of object
@@ -50,3 +36,33 @@
def getTypes():
"""Return the sequence of types this representation can represent.
"""
+
+class ISchemaSpec(Interface):
+
+ def addField(name, field):
+ """Add a field to schema.
+
+ This should be a null operation for instances of the class
+ implementing the schema; FieldProperty will provide a default
+ for the added field.
+ """
+
+ def removeField(name):
+ """Remove field from schema.
+ """
+
+ def renameField(orig_name, target_name):
+ """Rename field.
+ """
+
+ def insertField(name, field, position):
+ """Insert a field at position.
+ """
+
+ def moveField(name, position):
+ """Move field to position.
+ """
+
+ def generateModuleSource(self):
+ pass
+