import os, sys
from CodeGenerator import CodeGenerator
from MiscUtils import AbstractError,StringTypes,mxDateTime,nativeDateTime
from MiddleKit.Core.ObjRefAttr import objRefJoin
class PythonGenerator(CodeGenerator):
def generate(self, dirname):
self.requireDir(dirname)
# @@ 2000-10-17 ce: ACK! Get rid of all these hard coded 'GeneratedPy' strings
# @@ 2000-10-16 ce: should delete GeneratedPy/
self.requireDir(os.path.join(dirname, 'GeneratedPy'))
self.writeInfoFile(os.path.join(dirname, 'GeneratedPy', 'Info.text'))
self._model.writePy(self, dirname)
class Model:
def writePy(self, generator, dirname):
self._klasses.assignClassIds(generator)
if self.hasSetting('Package'):
filename = os.path.join(dirname, '__init__.py')
if not os.path.exists(filename):
open(filename, 'w').write('#')
for klass in self._allKlassesInOrder:
filename = os.path.join(dirname, klass.name() + '.py')
klass.writePyStubIfNeeded(generator, filename)
filename = os.path.join(dirname, 'GeneratedPy', 'Gen' + klass.name() + '.py')
klass.writePy(generator, filename)
filename = os.path.join(dirname, 'GeneratedPy', '__init__.py')
open(filename, 'w').write('# __init__.py\n')
class ModelObject:
def writePy(self, generator, out=sys.stdout):
"""Write the Python code to define a table for the class.
The target can be a file or a filename.
"""
if type(out) in StringTypes:
out = open(out, 'w')
close = 1
else:
close = 0
self._writePy(generator, out)
if close:
out.close()
class Klass:
def writePyStubIfNeeded(self, generator, filename):
if not os.path.exists(filename):
# Grab values for use in writing file
basename = os.path.basename(filename)
name = self.name()
superclassModule = 'GeneratedPy.Gen' + name
superclassName = 'Gen' + name
# Write file
file = open(filename, 'w')
file.write(PyStubTemplate % locals())
file.close()
def _writePy(self, generator, out):
self._pyGenerator = generator
self._pyOut = out
self.writePyFileDocString()
self.writePyAttrCaches()
self.writePyImports()
self.writePyClassDef()
def writePyFileDocString(self):
wr = self._pyOut.write
out = self._pyOut
wr("""'''\n""")
wr('Gen%s.py\n' % self.name())
#wr('%s\n' % asctime(localtime(time())))
wr('Generated by MiddleKit.\n') # @@ 2000-10-01 ce: Put the version number here
wr("""'''\n""")
def writePyAttrCaches(self):
wr = self._pyOut.write
wr('''
# MK attribute caches for setFoo() methods
''')
for attr in self.allAttrs():
wr('_%sAttr = None\n' % attr.name())
wr('\n')
def writePyImports(self):
wr = self._pyOut.write
wr('''
import types
try:
from types import StringTypes
except ImportError: # fallback for Python < 2.2
from types import StringType,UnicodeType
StringTypes = (StringType, UnicodeType)
try:
from decimal import Decimal
except ImportError: # fallback for Python < 2.4
Decimal = float
''')
if nativeDateTime:
if nativeDateTime.__name__ == 'datetime':
wr('import datetime\n')
else:
wr('import %s as datetime\n' % nativeDateTime.__name__)
if mxDateTime:
wr('import %s as mxDateTime\n' % mxDateTime.__name__)
supername = self.supername()
if supername == 'MiddleObject':
wr('\n\nfrom MiddleKit.Run.MiddleObject import MiddleObject\n')
else:
pkg = self._klasses._model.setting('Package', '')
if pkg:
pkg += '.'
# backPath = repr('../' * (pkg.count('.') + 1))
backPath = 'dirname(__file__)'
for i in xrange(pkg.count('.') + 1):
backPath = 'dirname(%s)' % backPath
wr('''\
import sys
from MiddleKit.Run.MiddleObject import MiddleObject
from os.path import dirname
sys.path.insert(0, %(backPath)s)
from pkgssupernames import %(supername)s
del sys.path[0]
''' % locals())
def writePyClassDef(self):
wr = self._pyOut.write
wr('\n\nclass Gen%s(%s):\n' % (self.name(), self.supername()))
self.writePyInit()
self.writePyReadStoreData()
self.writePyAccessors()
wr('\n')
def maxAttrNameLen(self):
maxLen = 0
for attr in self.attrs():
if maxLen < len(attr.name()):
maxLen = len(attr.name())
return maxLen
def writePyInit(self):
wr = self._pyOut.write
wr('\n\tdef __init__(self):\n')
wr('\t\t%s.__init__(self)\n' % self.supername())
maxLen = self.maxAttrNameLen()
for attr in self.attrs():
name = attr.name().ljust(maxLen)
wr('\t\tself._%s = %r\n' % (name, attr.defaultValue()))
wr('\n')
def writePyReadStoreData(self):
wr = self._pyOut.write
statements = [attr.pyReadStoreDataStatement() for attr in self.attrs()]
statements = [s for s in statements if s]
if statements:
wr('''
def readStoreData(self, store, row):
if not self._mk_inStore:
''')
for s in statements:
wr(s)
wr('\t\t%s.readStoreData(self, store, row)\n\n' % self.supername())
def writePyAccessors(self):
"""Write Python accessors for attributes simply by asking each one to do so."""
out = self._pyOut
for attr in self.attrs():
attr.writePyAccessors(out)
class Attr:
def defaultValue(self):
"""Return the default value as a legal Pythonic value."""
if self.has_key('Default'):
default = self['Default']
if type(default) in StringTypes:
default = default.strip()
if not default:
return None
else:
return self.stringToValue(default)
else:
return None
def stringToValue(self, string):
# @@ 2000-11-25 ce: consider moving this to Core
# @@ 2000-11-25 ce: also, this might be usable in the store
"""Return a bona fide Python value given a string.
Invokers should never pass None or blank strings.
Used by at least defaultValue(). Subclass responsibility.
"""
raise AbstractError, self.__class__
def pyReadStoreDataStatement(self):
return None
def writePyAccessors(self, out):
self.writePyGet(out)
self.writePySet(out)
if self.setting('AccessorStyle', 'methods') == 'properties':
out.write('\n\n\t%s = property(%s, %s)\n\n' % (self.name(), self.pyGetName(), self.pySetName()))
def writePyGet(self, out):
out.write('''
def %s(self):
return self._%s
''' % (self.pyGetName(), self.name()))
def writePySet(self, out):
name = self.name()
pySetName = self.pySetName()
out.write('\n\tdef %(pySetName)s(self, value):\n' % locals())
self.writePySetChecks(out)
self.writePySetAssignment(out.write, name)
def writePySetAssignment(self, write, name):
write('''
# set the attribute
origValue = self._%(name)s
self._%(name)s = value
# MiddleKit machinery
if not self._mk_initing and self._mk_serialNum>0 and value is not origValue:
global _%(name)sAttr
if _%(name)sAttr is None:
_%(name)sAttr = self.klass().lookupAttr('%(name)s')
if not _%(name)sAttr.shouldRegisterChanges():
_%(name)sAttr = 0
if _%(name)sAttr:
# Record that it has been changed
self._mk_changed = 1
if self._mk_changedAttrs is None:
self._mk_changedAttrs = {} # maps name to attribute
self._mk_changedAttrs['%(name)s'] = _%(name)sAttr # changedAttrs is a set
# Tell ObjectStore it happened
self._mk_store.objectChanged(self)
''' % {'name': name})
def writePySetChecks(self, out):
if self.isRequired():
out.write('\t\tassert value is not None\n')
PyStubTemplate = """\
'''
%(basename)s
'''
from %(superclassModule)s import %(superclassName)s
class %(name)s(%(superclassName)s):
def __init__(self):
%(superclassName)s.__init__(self)
"""
class BoolAttr:
def stringToValue(self, string):
try:
string = string.upper()
except Exception:
pass
if string in (True, 'TRUE', 'YES', '1', '1.0', 1, 1.0):
value = 1
elif string in (False, 'FALSE', 'NO', '0', '0.0', 0, 0.0):
value = 0
else:
value = int(string)
assert value == 0 or value == 1, value
return value
def writePySetChecks(self, out):
Attr.writePySetChecks.im_func(self, out)
out.write('''\
if value is not None:
if not isinstance(value, types.IntType):
raise TypeError, 'expecting bool or int for bool, but got value %r of type %r instead' % (value, type(value))
if value not in (True, False, 1, 0):
raise ValueError, 'expecting True, False, 1 or 0 for bool, but got %s instead' % value
''')
class IntAttr:
def stringToValue(self, string):
return int(string)
def writePySetChecks(self, out):
Attr.writePySetChecks.im_func(self, out)
out.write('''\
if value is not None:
if isinstance(value, types.LongType):
value = int(value)
if isinstance(value, types.LongType): # happens in Python 2.3
raise OverflowError, value
elif not isinstance(value, types.IntType):
raise TypeError, 'expecting int type, but got value %r of type %r instead' % (value, type(value))
''')
class LongAttr:
def stringToValue(self, string):
return long(string)
def writePySetChecks(self, out):
Attr.writePySetChecks.im_func(self, out)
out.write('''\
if value is not None:
if isinstance(value, types.IntType):
value = long(value)
elif not isinstance(value, types.LongType):
raise TypeError, 'expecting long type, but got value %r of type %r instead' % (value, type(value))
''')
class FloatAttr:
def stringToValue(self, string):
return float(string)
def writePySetChecks(self, out):
Attr.writePySetChecks.im_func(self, out)
out.write('''\
if value is not None:
if isinstance(value, (types.IntType, types.LongType)):
value = float(value)
elif not isinstance(value, types.FloatType):
raise TypeError, 'expecting float type, but got value %r of type %r instead' % (value, type(value))
''')
class DecimalAttr:
def stringToValue(self, string):
return float(string)
def writePySetChecks(self, out):
Attr.writePySetChecks.im_func(self, out)
out.write('''\
if value is not None:
if isinstance(value, (types.IntType, types.LongType)):
value = float(value)
elif isinstance(value, types.FloatType):
value = Decimal(str(value))
elif not isinstance(value, Decimal):
raise TypeError, 'expecting decimal type, but got value %r of type %r instead' % (value, type(value))
''')
class StringAttr:
def stringToValue(self, string):
return string
def writePySetChecks(self, out):
Attr.writePySetChecks.im_func(self, out)
out.write('''\
if value is not None:
if not isinstance(value, types.StringType):
raise TypeError, 'expecting string type, but got value %r of type %r instead' % (value, type(value))
''')
class EnumAttr:
def stringToValue(self, string):
if self.usesExternalSQLEnums():
return self.intValueForString(string)
else:
return string
def writePyAccessors(self, out):
Attr.writePyAccessors.im_func(self, out)
if self.setting('ExternalEnumsSQLNames')['Enable']:
name = self.name()
getName = self.pyGetName()
out.write('''
def %(getName)sString(self):
global _%(name)sAttr
if _%(name)sAttr is None:
_%(name)sAttr = self.klass().lookupAttr('%(name)s')
return _%(name)sAttr.enums()[self._%(name)s]
''' % locals())
if self.setting('AccessorStyle', 'methods') == 'properties':
out.write('\n\n\t%(name)sString = property(%(getName)sString,'
' "Returns the string form of %(name)s (instead of the integer value).")\n\n' % locals())
def writePySetChecks(self, out):
Attr.writePySetChecks.im_func(self, out)
out.write('''\
global _%(name)sAttr
if _%(name)sAttr is None:
_%(name)sAttr = self.klass().lookupAttr('%(name)s')
''' % {'name': self.name()})
if self.usesExternalSQLEnums():
out.write('''
if value is not None:
if isinstance(value, types.StringType):
try:
value = _%(name)sAttr.intValueForString(value)
except KeyError:
raise ValueError, 'expecting one of %%r, but got %%r instead' %% (_%(name)sAttr.enums(), value)
elif not isinstance(value, (types.IntType, types.LongType)):
raise TypeError, 'expecting int type for enum, but got value %%r of type %%r instead' %% (value, type(value))
if not _%(name)sAttr.hasEnum(value):
raise ValueError, 'expecting one of %%r, but got %%r instead' %% (_%(name)sAttr.enums(), value)
''' % {'name': self.name()})
else:
out.write('''
if value is not None:
if not isinstance(value, types.StringType):
raise TypeError, 'expecting string type for enum, but got value %%r of type %%r instead' %% (value, type(value))
attr = self.klass().lookupAttr('%s')
if not attr.hasEnum(value):
raise ValueError, 'expecting one of %%r, but got %%r instead' %% (attr.enums(), value)
''' % self.name())
# @@ 2001-07-11 ce: could optimize above code
def writePySetAssignment(self, write, name):
write('''
# set the attribute
origValue = self._%(name)s
self._%(name)s = value
# MiddleKit machinery
if not self._mk_initing and self._mk_serialNum>0 and value is not origValue:
# Record that it has been changed
self._mk_changed = 1
if self._mk_changedAttrs is None:
self._mk_changedAttrs = {} # maps name to attribute
self._mk_changedAttrs['%(name)s'] = _%(name)sAttr # changedAttrs is a set
# Tell ObjectStore it happened
self._mk_store.objectChanged(self)
''' % {'name': name})
## Settings ##
def usesExternalSQLEnums(self):
# @@ 2004-02-25 ce: seems like this method and its use
# should be pushed down to SQLPythonGenerator.py
flag = getattr(self, '_usesExternalSQLEnums', None)
if flag is None:
flag = self.model().usesExternalSQLEnums()
self._usesExternalSQLEnums = flag
return flag
class AnyDateTimeAttr:
def nativeDateTimeTypeName(self):
raise AbstractError, self.__class__
def mxDateTimeTypeName(self):
raise AbstractError, self.__class__
def writePySetChecks(self, out):
Attr.writePySetChecks.im_func(self, out)
out.write('''\
if value is not None:
''')
if mxDateTime:
# I don't see anything for parsing in Python's datetime module, so only
# mx.DateTime users get this convenience (which has been in MiddleKit
# for years):
out.write('''\
if type(value) in StringTypes:
value = mxDateTime.%s(value)
''' % self.mxDateTimeTypeName().replace('Type', 'From'))
dateTimeTypes = []
if nativeDateTime:
# Python's datetime types
typeNames = self.nativeDateTimeTypeName()
if not isinstance(typeNames, tuple):
typeNames = (typeNames,)
for typeName in typeNames:
dateTimeTypes.append('datetime.' + typeName)
if mxDateTime:
# egenix's mx.DateTime types
typeNames = self.mxDateTimeTypeName()
if not isinstance(typeNames, tuple):
typeNames = (typeNames,)
for typeName in typeNames:
dateTimeTypes.append('mxDateTime.' + typeName)
assert dateTimeTypes
if len(dateTimeTypes) > 1:
dateTimeTypes = '(' + ', '.join(dateTimeTypes) + ')'
else:
dateTimeTypes = dateTimeTypes[0]
out.write('''
if not isinstance(value, %s):
raise TypeError, 'expecting %s type (e.g., %s), but got value %%r of type %%r instead' %% (value, type(value))
''' % (dateTimeTypes, self['Type'], dateTimeTypes))
def writePyGetAsMXDateTime(self, out):
if nativeDateTime and mxDateTime and self.setting('ReturnMXDateTimes', False):
dateTimeTypes = []
typeNames = self.nativeDateTimeTypeName()
if not isinstance(typeNames, tuple):
typeNames = (typeNames,)
for typeName in typeNames:
dateTimeTypes.append('datetime.' + typeName)
dateTimeTypes = '(' + ', '.join(dateTimeTypes) + ')'
out.write('''
def %s(self):
if isinstance(self._%s, %s):
self._%s = mxDateTime.mktime(self._%s.timetuple())
return self._%s
''' % (self.pyGetName(), self.name(), dateTimeTypes, self.name(), self.name(), self.name()))
else:
out.write('''
def %s(self):
return self._%s
''' % (self.pyGetName(), self.name()))
class DateAttr:
def nativeDateTimeTypeName(self):
return 'date'
def mxDateTimeTypeName(self):
return 'DateTimeType'
def writePyGet(self, out):
self.writePyGetAsMXDateTime(out)
class TimeAttr:
def nativeDateTimeTypeName(self):
return ('time', 'timedelta')
def mxDateTimeTypeName(self):
return 'DateTimeDeltaType'
def writePySetChecks(self, out):
# additional check to also allow DateTime instances.
# This is what comes back from the database for 'time' columns
# using PostgresSQL and the psycopg adapter.
if mxDateTime:
out.write('''\
if isinstance(value, mxDateTime.DateTimeType):
value = mxDateTime.DateTimeDeltaFrom(value.time)
''')
TimeAttr.mixInSuperWritePySetChecks(self, out)
class DateTimeAttr:
def nativeDateTimeTypeName(self):
return 'datetime'
def mxDateTimeTypeName(self):
return 'DateTimeType'
def writePyGet(self, out):
self.writePyGetAsMXDateTime(out)
class ObjRefAttr:
def stringToValue(self, string):
parts = string.split('.')
if len(parts) == 2:
className = parts[0]
objSerialNum = parts[1]
else:
className = self.targetClassName()
objSerialNum = string
klass = self.klass().klasses()._model.klass(className)
klassId = klass.id()
objRef = objRefJoin(klassId, int(objSerialNum))
return objRef
def writePySet(self, out):
name = self.name()
pySetName = self.pySetName()
targetClassName = self.targetClassName()
package = self.setting('Package', '')
if package:
package += '.'
if self.isRequired():
reqAssert = 'assert value is not None'
else:
reqAssert = ''
out.write('''
def %(pySetName)s(self, value):
%(reqAssert)s
if value is not None and not isinstance(value, types.LongType):
if not isinstance(value, MiddleObject):
raise TypeError, 'expecting a MiddleObject, but got value %%r of type %%r instead' %% (value, type(value))
from packagestargetClassNames import %(targetClassName)s
if not isinstance(value, %(targetClassName)s):
raise TypeError, 'expecting %(targetClassName)s, but got value %%r of type %%r instead' %% (value, type(value))
''' % locals())
self.writePySetAssignment(out.write, name)
class ListAttr:
def defaultValue(self):
"""Return the default value as a legal Pythonic value."""
assert not self.get('Default', 0), 'Cannot have default values for lists.'
return []
def pyReadStoreDataStatement(self):
# Set the lists to None on the very first read from the store
# so the list get methods will fetch the lists from the store.
return '\t\t\tself._%s = None\n' % self.name()
def writePyAccessors(self, out):
# Create various name values that are needed in code generation
name = self.name()
pyGetName = self.pyGetName()
pySetName = self.pySetName()
capName = name[0].upper() + name[1:]
sourceClassName = self.klass().name()
targetClassName = self.className()
backRefAttrName = self.backRefAttrName()
upperBackRefAttrName = backRefAttrName[0].upper() + backRefAttrName[1:]
package = self.setting('Package', '')
if package:
package += '.'
names = locals()
# Invoke various code gen methods with the names
self.writePyGet(out, names)
self.writePyAddTo(out, names)
self.writePyDelFrom(out, names)
def writePyGet(self, out, names):
"""Subclass responsibility."""
raise AbstractError
def writePySet(self, out, names=None):
"""Raise an exception in order to ensure that our inherited "PySet" code generation is used."""
raise AssertionError, 'Lists do not have a set method.'
def writePyAddTo(self, out, names):
names['getParens'] = self.setting('AccessorStyle', 'methods') == 'methods' and '()' or ''
out.write('''
def addTo%(capName)s(self, value):
assert value is not None
from packagestargetClassNames import %(targetClassName)s
assert isinstance(value, %(targetClassName)s)
assert value.%(backRefAttrName)s%(getParens)s is None
self.%(pyGetName)s().append(value)
value._set('%(backRefAttrName)s', self)
store = self.store()
if value.serialNum() == 0 and self.isInStore():
store.addObject(value)
''' % names)
del names['getParens']
def writePyDelFrom(self, out, names):
names['getParens'] = self.setting('AccessorStyle', 'methods') == 'methods' and '()' or ''
out.write('''
def delFrom%(capName)s(self, value):
assert value is not None
from packagestargetClassNames import %(targetClassName)s
assert isinstance(value, %(targetClassName)s)
assert value.%(backRefAttrName)s%(getParens)s is self
assert value in self.%(pyGetName)s()
self.%(pyGetName)s().remove(value)
value._set('%(backRefAttrName)s', None)
store = self.store()
if self.isInStore() and value.isInStore():
store.deleteObject(value)
''' % names)
del names['getParens']
|