Update of /cvsroot/sqlobject/SQLObject/SQLObject
In directory sc8-pr-cvs1:/tmp/cvs-serv25249/SQLObject
Modified Files:
DBConnection.py SQLObject.py
Log Message:
More dynamicism -- add columns and joins at runtime, including
changing the database schema to match (MySQL only).
Also have default ordering.
Index: DBConnection.py
===================================================================
RCS file: /cvsroot/sqlobject/SQLObject/SQLObject/DBConnection.py,v
retrieving revision 1.7
retrieving revision 1.8
diff -C2 -d -r1.7 -r1.8
*** DBConnection.py 14 Mar 2003 08:59:24 -0000 1.7
--- DBConnection.py 31 Mar 2003 02:00:58 -0000 1.8
***************
*** 300,303 ****
--- 300,313 ----
return False
+ def addColumn(self, tableName, column):
+ self.query('ALTER TABLE %s ADD COLUMN %s' %
+ (tableName,
+ column.mysqlCreateSQL()))
+
+ def dropColumn(self, tableName, column):
+ self.query('ALTER TABLE %s DROP COLUMN %s' %
+ (tableName,
+ column.dbName))
+
class PostgresConnection(DBAPI):
Index: SQLObject.py
===================================================================
RCS file: /cvsroot/sqlobject/SQLObject/SQLObject/SQLObject.py,v
retrieving revision 1.10
retrieving revision 1.11
diff -C2 -d -r1.10 -r1.11
*** SQLObject.py 30 Mar 2003 01:47:35 -0000 1.10
--- SQLObject.py 31 Mar 2003 02:00:58 -0000 1.11
***************
*** 1,3 ****
! Scol"""
SQLObject.py
Ian Bicking <ia...@co...> 17 Oct 2002
--- 1,3 ----
! """
SQLObject.py
Ian Bicking <ia...@co...> 17 Oct 2002
***************
*** 81,84 ****
--- 81,90 ----
# database directly to set them.
dict['_SO_plainSetters'] = {}
+ dict['_SO_plainGetters'] = {}
+ dict['_SO_plainForeignSetters'] = {}
+ dict['_SO_plainForeignGetters'] = {}
+ dict['_SO_plainJoinGetters'] = {}
+ dict['_SO_plainJoinAdders'] = {}
+ dict['_SO_plainJoinRemovers'] = {}
# This is a dictionary of columnName: columnObject
***************
*** 91,94 ****
--- 97,104 ----
dict['_cacheValues'] = cacheValues
+ # The _defaultOrder is used by SelectResults
+ if not dict.has_key('_defaultOrder'):
+ dict['_defaultOrder'] = None
+
# We fix up the columns here -- replacing any
# strings with simply-contructed Col objects,
***************
*** 97,101 ****
columns = []
for column in dict.get('_columns', []):
! if type(column) is type(""):
column = Col.Col(column)
columns.append(column)
--- 107,111 ----
columns = []
for column in dict.get('_columns', []):
! if isinstance(column, str):
column = Col.Col(column)
columns.append(column)
***************
*** 103,202 ****
dict['_columns'] = columns
-
- for column in columns:
- name = column.name
- dict['_SO_columnDict'][name] = column
- # if the column is someColumn, then capName
- # is SomeColumn. We use this when we want to
- # prefix it, like setSomeColumn.
- capName = capitalize(name)
-
- ###################################################
- # Create the getter function(s). We'll start by
- # creating functions like _SO_get_columnName,
- # then if there's no function named _get_columnName
- # we'll alias that to _SO_get_columnName. This
- # allows a sort of super call, even though there's
- # no superclass that defines the database access.
- if cacheValues:
-
- # We create a method here, which is just a function
- # that takes "self" as the first argument. The
- # basic logic of the lambda is:
- # When we have cached values, self._SO_autoInitDone
- # is true, so we skip out of the parenthesis.
- # If we don't, we run self._SO_autoInit() which
- # gets the cached values (for all columns).
- # The cached values are stored in something
- # like _SO_val_columnName, so we return that
- # last.
- dict[rawGetterName(name)] = eval('lambda self: (self._SO_autoInitDone or self._SO_autoInit()) and self.%s' % (instanceName(name)))
- else:
- # If we aren't caching values, we just call the
- # function _SO_getValue, which fetches from the
- # database.
- dict[rawGetterName(name)] = eval('lambda self: self._SO_getValue(%s)' % repr(name))
-
- # Here if the _get_columnName method isn't in the
- # definition, we add it with the default
- # _SO_get_columnName definition.
- if not dict.has_key(getterName(name)):
- dict[getterName(name)] = dict[rawGetterName(name)]
-
- #################################################
- # Create the setter function(s)
- # Much like creating the getters, we will create
- # _SO_set_columnName methods, and then alias them
- # to _set_columnName if the user hasn't defined
- # those methods themself.
-
- # We start by just using the _SO_setValue method
- dict[rawSetterName(name)] = eval('lambda self, val: self._SO_setValue(%s, val)' % repr(name))
- # Then do the aliasing
- if not dict.has_key(setterName(name)):
- dict[setterName(name)] = dict[rawSetterName(name)]
- # We keep track of setters that haven't been
- # overridden, because we can combine these
- # set columns into one SQL UPDATE query.
- dict['_SO_plainSetters'][name] = 1
-
-
- ##################################################
- # Here we check if the column is a foreign key, in
- # which case we need to make another method that
- # fetches the key and constructs the sister
- # SQLObject instance.
- if column.foreignKey:
-
- # We go through the standard _SO_get_columnName
- # deal, except chopping off the "ID" ending since
- # we're giving the object, not the ID of the
- # object this time:
- if cacheValues:
- # self._SO_class_className is a reference
- # to the class in question.
- dict[rawGetterName(name)[:-2]] = eval('lambda self: (self._SO_autoInitDone or self._SO_autoInit()) and self._SO_class_%s(self.%s)' % (column.foreignKey, instanceName(name)))
- else:
- # Same non-caching version as above.
- dict[rawGetterName(name)[:-2]] = eval('lambda self: self._SO_class_%s(self._SO_getValue(%s))' % (column.foreignKey, repr(name)))
- # And we set the _get_columnName version
- # (sans ID ending)
- if not dict.has_key(getterName(name)[:-2]):
- dict[getterName(name)[:-2]] = dict[rawGetterName(name)[:-2]]
-
- # The setter just gets the ID of the object,
- # and then sets the real column.
- dict[rawSetterName(name)[:-2]] = eval('lambda self, val: setattr(self, %s, self._SO_getID(val))' % (repr(name)))
- if not dict.has_key(setterName(name)[:-2]):
- dict[setterName(name)[:-2]] = dict[rawSetterName(name)[:-2]]
- # We'll need to put in a real reference at
- # some point. See needSet at the top of the
- # file for more on this.
- needSet.append((className, column.foreignKey))
-
- if column.alternateMethodName:
- func = eval('lambda cls, val: cls._SO_fetchAlternateID(%s, val)' % repr(column.dbName))
- dict[column.alternateMethodName] = classmethod(func)
-
######################################################
# Set some attributes to their defaults, if necessary.
--- 113,116 ----
***************
*** 235,285 ****
if not dict.has_key('_joins'):
dict['_joins'] = []
- for join in dict['_joins']:
-
- # The name of the method we'll create. If it's
- # automatically generated, it's generated by the
- # join class.
- meth = join.joinMethodName
-
- # The method name for adding a joined object:
- appendMeth = meth[0].upper() + meth[1:]
-
- # The join sometimes needs to know who we are,
- # mostly to generate some internal names:
- join.initCallingClass(className, dict['_table'])
-
- # Now that the join is set up, we add it to our
- # list of joins:
- dict['_SO_joinList'].append(join)
-
- # The function fetches the join by index, and
- # then lets the join object do the rest of the
- # work:
- func = eval('lambda self: self._SO_joinList[%i].performJoin(self)' % (len(dict['_SO_joinList'])-1))
-
- # And we do the standard _SO_get_... _get_... deal
- dict[rawGetterName(meth)] = func
- if not dict.has_key(getterName(meth)):
- dict[getterName(meth)] = func
-
- # Some joins allow you to remove objects from the
- # join.
- if hasattr(join, 'remove'):
-
- # Again, we let it do the remove, and we do the
- # standard naming trick.
- func = eval('lambda self, obj: self._SO_joinList[%i].remove(self, obj)' % (len(dict['_SO_joinList'])-1))
- dict['_SO_remove' + join.addRemovePrefix] = func
- if not dict.has_key('remove' + appendMeth):
- dict['remove' + join.addRemovePrefix] = func
-
- # Some joins allow you to add objects.
- if hasattr(join, 'add'):
-
- # And again...
- func = eval('lambda self, obj: self._SO_joinList[%i].add(self, obj)' % (len(dict['_SO_joinList'])-1))
- dict['_SO_add' + join.addRemovePrefix] = func
- if not dict.has_key('add' + appendMeth):
- dict['add' + join.addRemovePrefix] = func
# We use the magic "q" attribute for accessing lazy
--- 149,152 ----
***************
*** 295,299 ****
# a real connection.
if dict.has_key('_connection') and \
! type(dict['_connection']) is type(""):
dict['_connection'] = DBConnection.connectionForName(dict['_connection'])
--- 162,166 ----
# a real connection.
if dict.has_key('_connection') and \
! isinstance(dict['_connection'], str):
dict['_connection'] = DBConnection.connectionForName(dict['_connection'])
***************
*** 306,309 ****
--- 173,182 ----
newClass = type.__new__(cls, className, bases, dict)
+ for column in newClass._columns:
+ newClass.addColumn(column)
+
+ for join in newClass._joins:
+ newClass.addJoin(join)
+
# Register it, for use with needSet
classRegistry[className] = newClass
***************
*** 312,319 ****
setNeedSet()
# And return the class
return newClass
! def makeProperties(dict):
"""
This function takes a dictionary of methods and finds
--- 185,194 ----
setNeedSet()
+ newClass._SO_finishedClassCreation = True
+
# And return the class
return newClass
! def makeProperties(obj):
"""
This function takes a dictionary of methods and finds
***************
*** 328,334 ****
Missing methods are okay.
"""
!
props = {}
! for var, value in dict.items():
if var.startswith('_set_'):
props.setdefault(var[5:], {})['set'] = value
--- 203,218 ----
Missing methods are okay.
"""
!
! if isinstance(obj, dict):
! def setFunc(var, value):
! obj[var] = value
! d = obj
! else:
! def setFunc(var, value):
! setattr(obj, var, value)
! d = obj.__dict__
!
props = {}
! for var, value in d.items():
if var.startswith('_set_'):
props.setdefault(var[5:], {})['set'] = value
***************
*** 342,349 ****
if len(setters) == 1 and setters.has_key('doc'):
continue
! if dict.has_key(var): continue
! dict[var] = property(setters.get('get'), setters.get('set'),
! setters.get('del'), setters.get('doc'))
! return dict
def generateTableName(s):
--- 226,249 ----
if len(setters) == 1 and setters.has_key('doc'):
continue
! if d.has_key(var): continue
! setFunc(var,
! property(setters.get('get'), setters.get('set'),
! setters.get('del'), setters.get('doc')))
!
! def unmakeProperties(obj):
! if isinstance(obj, dict):
! def delFunc(var):
! del obj[var]
! d = obj
! else:
! def delFunc(var):
! delattr(obj, var)
! d = obj.__dict__
!
! for var, value in d.items():
! if isinstance(value, property):
! for prop in [value.fget, value.fset, value.fdel]:
! if prop and not dict.has_key(prop.__name__):
! delFunc(var)
def generateTableName(s):
***************
*** 398,402 ****
# Some databases annoyingly return longs for INT
! if type(id) is type(1L):
id = int(id)
--- 298,302 ----
# Some databases annoyingly return longs for INT
! if isinstance(id, long):
id = int(id)
***************
*** 413,416 ****
--- 313,526 ----
return val
+ def addColumn(cls, column, changeSchema=False):
+ name = column.name
+ cls._SO_columnDict[name] = column
+
+ ###################################################
+ # Create the getter function(s). We'll start by
+ # creating functions like _SO_get_columnName,
+ # then if there's no function named _get_columnName
+ # we'll alias that to _SO_get_columnName. This
+ # allows a sort of super call, even though there's
+ # no superclass that defines the database access.
+ if cls._cacheValues:
+
+ # We create a method here, which is just a function
+ # that takes "self" as the first argument. The
+ # basic logic of the lambda is:
+ # When we have cached values, self._SO_autoInitDone
+ # is true, so we skip out of the parenthesis.
+ # If we don't, we run self._SO_autoInit() which
+ # gets the cached values (for all columns).
+ # The cached values are stored in something
+ # like _SO_val_columnName, so we return that
+ # last.
+ setattr(cls, rawGetterName(name), eval('lambda self: (self._SO_autoInitDone or self._SO_autoInit()) and self.%s' % (instanceName(name))))
+ else:
+ # If we aren't caching values, we just call the
+ # function _SO_getValue, which fetches from the
+ # database.
+ setattr(cls, rawGetterName(name), eval('lambda self: self._SO_getValue(%s)' % repr(name)))
+
+ # Here if the _get_columnName method isn't in the
+ # definition, we add it with the default
+ # _SO_get_columnName definition.
+ if not hasattr(cls, getterName(name)):
+ setattr(cls, getterName(name), getattr(cls, rawGetterName(name)))
+ cls._SO_plainGetters[name] = 1
+
+ #################################################
+ # Create the setter function(s)
+ # Much like creating the getters, we will create
+ # _SO_set_columnName methods, and then alias them
+ # to _set_columnName if the user hasn't defined
+ # those methods themself.
+
+ # We start by just using the _SO_setValue method
+ setattr(cls, rawSetterName(name), eval('lambda self, val: self._SO_setValue(%s, val)' % repr(name)))
+ # Then do the aliasing
+ if not hasattr(cls, setterName(name)):
+ setattr(cls, setterName(name), getattr(cls, rawSetterName(name)))
+ # We keep track of setters that haven't been
+ # overridden, because we can combine these
+ # set columns into one SQL UPDATE query.
+ cls._SO_plainSetters[name] = 1
+
+
+ ##################################################
+ # Here we check if the column is a foreign key, in
+ # which case we need to make another method that
+ # fetches the key and constructs the sister
+ # SQLObject instance.
+ if column.foreignKey:
+
+ # We go through the standard _SO_get_columnName
+ # deal, except chopping off the "ID" ending since
+ # we're giving the object, not the ID of the
+ # object this time:
+ if cls._cacheValues:
+ # self._SO_class_className is a reference
+ # to the class in question.
+ setattr(cls, rawGetterName(name)[:-2], eval('lambda self: (self._SO_autoInitDone or self._SO_autoInit()) and self._SO_class_%s(self.%s)' % (column.foreignKey, instanceName(name))))
+ else:
+ # Same non-caching version as above.
+ setattr(cls, rawGetterName(name)[:-2], eval('lambda self: self._SO_class_%s(self._SO_getValue(%s))' % (column.foreignKey, repr(name))))
+ # And we set the _get_columnName version
+ # (sans ID ending)
+ if not hasattr(cls, getterName(name)[:-2]):
+ setattr(cls, getterName(name)[:-2], getattr(cls, rawGetterName(name)[:-2]))
+ cls._SO_plainForeignGetters[name[:-2]] = 1
+
+ # The setter just gets the ID of the object,
+ # and then sets the real column.
+ setattr(cls, rawSetterName(name)[:-2], eval('lambda self, val: setattr(self, %s, self._SO_getID(val))' % (repr(name))))
+ if not hasattr(cls, setterName(name)[:-2]):
+ setattr(cls, setterName(name)[:-2], getattr(cls, rawSetterName(name)[:-2]))
+ cls._SO_plainForeignSetters[name[:-2]] = 1
+ # We'll need to put in a real reference at
+ # some point. See needSet at the top of the
+ # file for more on this.
+ needSet.append((cls.__name__, column.foreignKey))
+
+ if column.alternateMethodName:
+ func = eval('lambda cls, val: cls._SO_fetchAlternateID(%s, val)' % repr(column.dbName))
+ setattr(cls, column.alternateMethodName, classmethod(func))
+
+ if changeSchema:
+ cls._connection.addColumn(cls._table, column)
+
+ if cls._SO_finishedClassCreation:
+ makeProperties(cls)
+
+ addColumn = classmethod(addColumn)
+
+ def delColumn(cls, column, changeSchema=False):
+ if isinstance(column, str):
+ column = self._SO_columnDict[column]
+ name = column.name
+ del cls._SO_columnDict[name]
+ delattr(cls, rawGetterName(name))
+ if cls._SO_plainGetters.has_key(name):
+ delattr(cls, getterName(name))
+ delattr(cls, rawSetterName(name))
+ if cls._SO_plainSetters.has_key(name):
+ delattr(cls, setterName(name))
+ if column.foreignKey:
+ delattr(cls, rawGetterName(name)[:-2])
+ if cls._SO_plainForeignGetter.has_key(name[:-2]):
+ delattr(cls, getterName(name)[:-2])
+ delattr(cls, rawSetterName(name)[:-2])
+ if cls._SO_plainForeignSetter.has_key(name[:-2]):
+ delattr(cls, setterName(name)[:-2])
+ if column.alternateMethodName:
+ delattr(cls, column.alternateMethodName)
+
+ if changeSchema:
+ cls._connection.delColumn(self._table, column)
+
+ if cls._SO_finishedClassCreation:
+ unmakeProperties(cls)
+
+ delColumn = classmethod(delColumn)
+
+ def addJoin(cls, join):
+ # The name of the method we'll create. If it's
+ # automatically generated, it's generated by the
+ # join class.
+ meth = join.joinMethodName
+
+ # The method name for adding a joined object:
+ appendMeth = meth[0].upper() + meth[1:]
+
+ # The join sometimes needs to know who we are,
+ # mostly to generate some internal names:
+ join.initCallingClass(cls.__name__, cls._table)
+
+ # Now that the join is set up, we add it to our
+ # list of joins:
+ cls._SO_joinList.append(join)
+ index = len(cls._SO_joinList)-1
+
+ # The function fetches the join by index, and
+ # then lets the join object do the rest of the
+ # work:
+ func = eval('lambda self: self._SO_joinList[%i].performJoin(self)' % index)
+
+ # And we do the standard _SO_get_... _get_... deal
+ setattr(cls, rawGetterName(meth), func)
+ if not hasattr(cls, getterName(meth)):
+ setattr(cls, getterName(meth), func)
+ cls._SO_plainJoinGetters[meth] = 1
+
+ # Some joins allow you to remove objects from the
+ # join.
+ if hasattr(join, 'remove'):
+
+ # Again, we let it do the remove, and we do the
+ # standard naming trick.
+ func = eval('lambda self, obj: self._SO_joinList[%i].remove(self, obj)' % index)
+ setattr(cls, '_SO_remove' + join.addRemovePrefix, func)
+ if not hasattr(cls, 'remove' + appendMeth):
+ setattr(cls, 'remove' + join.addRemovePrefix, func)
+ cls._SO_plainJoinRemovers[meth] = 1
+
+ # Some joins allow you to add objects.
+ if hasattr(join, 'add'):
+
+ # And again...
+ func = eval('lambda self, obj: self._SO_joinList[%i].add(self, obj)' % (len(dict['_SO_joinList'])-1))
+ setattr(cls, '_SO_add' + join.addRemovePrefix, func)
+ if not hasattr(cls, 'add' + appendMeth):
+ setattr(cls, 'add' + join.addRemovePrefix, func)
+ cls._SO_plainJoinAdders[meth] = 1
+
+ if cls._SO_finishedClassCreation:
+ makeProperties(cls)
+
+ addJoin = classmethod(addJoin)
+
+ def delJoin(cls, join):
+ meth = join.joinMethodName
+ appendMeth = meth[0].upper() + meth[1:]
+ for i in range(len(cls._SO_joinList)):
+ if cls._SO_joinList[i] is join:
+ cls._SO_joinList[i] = None
+ delattr(cls, rawGetterName(meth))
+ if cls._SO_plainJoinGetters.has_key(meth):
+ delattr(cls, getterName(meth))
+ if hasattr(join, 'remove'):
+ delattr(cls, '_SO_remove' + join.addRemovePrefix)
+ if cls._SO_plainJoinRemovers.has_key(meth):
+ delattr(cls, 'remove' + join.addRemovePrefix)
+ if hasattr(join, 'add'):
+ delattr(cls, '_SO_add' + join.addRemovePrefix)
+ if cls._SO_plainJoinAdders.has_key(meth):
+ delattr(cls, 'add' + join.addRemovePrefix)
+
+ if cls._SO_finishedClassCreation:
+ unmakeProperties(cls)
+
+ delJoin = classmethod(delJoin)
+
def _init(self, id, connection=None):
assert id is not None
***************
*** 633,637 ****
# 3-03 @@: Should this have a connection argument?
def select(cls, clause, clauseTables=None,
! orderBy=None, groupBy=None, limit=None,
lazyColumns=False):
return SelectResults(cls, clause, clauseTables=clauseTables,
--- 743,747 ----
# 3-03 @@: Should this have a connection argument?
def select(cls, clause, clauseTables=None,
! orderBy=NoDefault, groupBy=None, limit=None,
lazyColumns=False):
return SelectResults(cls, clause, clauseTables=clauseTables,
***************
*** 640,643 ****
--- 750,763 ----
select = classmethod(select)
+ def selectBy(cls, **kw):
+ return SelectResults(cls,
+ ' '.join(['%s = %s' %
+ (cls._SO_columnDict[key].dbName,
+ SQLBuilder.sqlRepr(value))
+ for key, value
+ in kw.items()]))
+
+ selectBy = classmethod(selectBy)
+
# 3-03 @@: Should these have a connection argument?
def dropTable(cls, ifExists=False):
***************
*** 858,862 ****
**ops):
self.sourceClass = sourceClass
! if type(clause) is type("") and clause == 'all':
clause = SQLBuilder.SQLConstant('1 = 1')
self.clause = clause
--- 978,982 ----
**ops):
self.sourceClass = sourceClass
! if isinstance(clause, str) and clause == 'all':
clause = SQLBuilder.SQLConstant('1 = 1')
self.clause = clause
***************
*** 869,872 ****
--- 989,994 ----
self.tables = tablesDict.keys()
self.ops = ops
+ if self.ops.get('orderBy', NoDefault) is NoDefault:
+ self.ops['orderBy'] = sourceClass._defaultOrder
def clone(self, **newOps):
|