import os import sys import datetime from CodeGenerator import CodeGenerator from MiscUtils import AbstractError 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(object): 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(object): 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 isinstance(out, basestring): out = open(out, 'w') close = True else: close = False self._writePy(generator, out) if close: out.close() class Klass(object): 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 with open(filename, 'w') as f: f.write(PyStubTemplate % locals()) 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())) 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(''' from datetime import date, datetime, time, timedelta from decimal import Decimal from MiscUtils.DateParser import parseDateTime, parseDate, parseTime from MiddleKit.Run.MiddleObject import MiddleObject ''') 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 os.path import dirname sys.path.insert(0, %(backPath)s) from %(pkg)s%(supername)s 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): return max([len(attr.name()) for attr in self.attrs()] or [0]) def writePyInit(self): wr = self._pyOut.write wr('\n def __init__(self):\n') wr(' %s.__init__(self)\n' % self.supername()) maxLen = self.maxAttrNameLen() for attr in self.attrs(): name = attr.name().ljust(maxLen) wr(' self._%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(' %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(object): def defaultValue(self): """Return the default value as a legal Pythonic value.""" if 'Default' in self: default = self['Default'] if isinstance(default, basestring): 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 %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 def %(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 = True 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(' assert 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(object): def stringToValue(self, string): try: string = string.upper() except Exception: pass if string in (True, 'TRUE', 'YES', '1', '1.0', 1, 1.0): value = True elif string in (False, 'FALSE', 'NO', '0', '0.0', 0, 0.0): value = False else: value = int(string) assert value in (0, 1), value value = bool(value) return value def writePySetChecks(self, out): Attr.writePySetChecks.im_func(self, out) out.write('''\ if value is not None: if not isinstance(value, (bool, int)): 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(object): 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, long): value = int(value) if isinstance(value, long): raise OverflowError(value) elif not isinstance(value, int): raise TypeError('expecting int type, but got value %r of type %r instead' % (value, type(value))) ''') class LongAttr(object): 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, int): value = long(value) elif not isinstance(value, long): raise TypeError('expecting long type, but got value %r of type %r instead' % (value, type(value))) ''') class FloatAttr(object): 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, (int, long)): value = float(value) elif not isinstance(value, float): raise TypeError('expecting float type, but got value %r of type %r instead' % (value, type(value))) ''') class DecimalAttr(object): 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, (int, long)): value = float(value) elif isinstance(value, float): 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(object): 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, basestring): raise TypeError('expecting string type, but got value %r of type %r instead' % (value, type(value))) ''') class EnumAttr(object): 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 %(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, basestring): 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, (int, long)): 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, basestring): 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(object): def writePySetChecks(self, out): Attr.writePySetChecks.im_func(self, out) typeName = self.nativeDateTimeTypeName() if not isinstance(typeName, basestring): typeName = '(%s)' % ', '.join(typeName) out.write('''\ if value is not None: if isinstance(value, basestring): value = %s(value) if not isinstance(value, %s): raise TypeError('expecting %s type (e.g., %s), but got' ' value %%r of type %%r instead' %% (value, type(value))) ''' % (self.nativeDateTimeParser(), typeName, self['Type'], typeName)) def writePyGet(self, out): out.write(''' def %s(self): return self._%s ''' % (self.pyGetName(), self.name())) class DateAttr(object): def nativeDateTimeTypeName(self): return 'date' def nativeDateTimeParser(self): return 'parseDate' def writePySetChecks(self, out): # additional check to also allow datetime instances out.write('''\ if isinstance(value, datetime): value = value.date() ''') DateAttr.mixInSuperWritePySetChecks(self, out) class TimeAttr(object): def nativeDateTimeTypeName(self): return ('time', 'timedelta') def nativeDateTimeParser(self): return 'parseTime' def writePySetChecks(self, out): # additional check to also allow datetime instances out.write('''\ if isinstance(value, datetime): value = value.time() ''') TimeAttr.mixInSuperWritePySetChecks(self, out) class DateTimeAttr(object): def nativeDateTimeTypeName(self): return 'datetime' def nativeDateTimeParser(self): return 'parseDateTime' class ObjRefAttr(object): def stringToValue(self, string): parts = string.split('.', 2) if len(parts) == 2: className, objSerialNum = parts 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, long): if not isinstance(value, MiddleObject): raise TypeError('expecting a MiddleObject, but got value %%r of type %%r instead' %% (value, type(value))) from %(package)s%(targetClassName)s 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(object): 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 ' self._%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(self.__class__) def writePySet(self, out, names=None): """Write code for setter. 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'] = '()' if self.setting( 'AccessorStyle', 'methods') == 'methods' else '' out.write(''' def addTo%(capName)s(self, value): assert value is not None from %(package)s%(targetClassName)s import %(targetClassName)s assert isinstance(value, %(targetClassName)s) assert value.%(backRefAttrName)s%(getParens)s is None self.%(pyGetName)s().append(value) value.setValueForKey('%(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'] = '()' if self.setting( 'AccessorStyle', 'methods') == 'methods' else '' out.write(''' def delFrom%(capName)s(self, value): assert value is not None from %(package)s%(targetClassName)s 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.setValueForKey('%(backRefAttrName)s', None) store = self.store() if self.isInStore() and value.isInStore(): store.deleteObject(value) ''' % names) del names['getParens']