0001"""
0002SQLObject 0.10
0003--------------
0004
0005:author: Ian Bicking <ianb@colorstudy.com>
0006
0007SQLObject is a object-relational mapper. See SQLObject.html or
0008SQLObject.txt for more.
0009
0010With the help by Oleg Broytmann <phd@phd.pp.ru> and many other contributors.
0011See Authors.txt.
0012
0013This program is free software; you can redistribute it and/or modify
0014it under the terms of the GNU Lesser General Public License as
0015published by the Free Software Foundation; either version 2.1 of the
0016License, or (at your option) any later version.
0017
0018This program is distributed in the hope that it will be useful,
0019but WITHOUT ANY WARRANTY; without even the implied warranty of
0020MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0021GNU General Public License for more details.
0022
0023You should have received a copy of the GNU Lesser General Public
0024License along with this program; if not, write to the Free Software
0025Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
0026USA.
0027"""
0028
0029import threading
0030import weakref
0031import sqlbuilder
0032import dbconnection
0033import col
0034import styles
0035import types
0036import warnings
0037import joins
0038import index
0039import classregistry
0040import declarative
0041import events
0042from sresults import SelectResults
0043from formencode import schema, compound
0044from util.threadinglocal import local
0045
0046import sys
0047if sys.version_info[:3] < (2, 2, 0):
0048 raise ImportError, "SQLObject requires Python 2.2.0 or later"
0049
0050"""
0051This thread-local storage is needed for RowCreatedSignals. It gathers
0052code-blocks to execute _after_ the whole hierachy of inherited SQLObjects
0053is created. See SQLObject._create
0054"""
0055_postponed_local = local()
0056
0057NoDefault = sqlbuilder.NoDefault
0058
0059class SQLObjectNotFound(LookupError): pass
0060class SQLObjectIntegrityError(Exception): pass
0061
0062def makeProperties(obj):
0063 """
0064 This function takes a dictionary of methods and finds
0065 methods named like:
0066 * _get_attr
0067 * _set_attr
0068 * _del_attr
0069 * _doc_attr
0070 Except for _doc_attr, these should be methods. It
0071 then creates properties from these methods, like
0072 property(_get_attr, _set_attr, _del_attr, _doc_attr).
0073 Missing methods are okay.
0074 """
0075
0076 if isinstance(obj, dict):
0077 def setFunc(var, value):
0078 obj[var] = value
0079 d = obj
0080 else:
0081 def setFunc(var, value):
0082 setattr(obj, var, value)
0083 d = obj.__dict__
0084
0085 props = {}
0086 for var, value in d.items():
0087 if var.startswith('_set_'):
0088 props.setdefault(var[5:], {})['set'] = value
0089 elif var.startswith('_get_'):
0090 props.setdefault(var[5:], {})['get'] = value
0091 elif var.startswith('_del_'):
0092 props.setdefault(var[5:], {})['del'] = value
0093 elif var.startswith('_doc_'):
0094 props.setdefault(var[5:], {})['doc'] = value
0095 for var, setters in props.items():
0096 if len(setters) == 1 and setters.has_key('doc'):
0097 continue
0098 if d.has_key(var):
0099 if isinstance(d[var], (types.MethodType, types.FunctionType)):
0100 warnings.warn(
0101 "I tried to set the property %r, but it was "
0102 "already set, as a method (%r). Methods have "
0103 "significantly different semantics than properties, "
0104 "and this may be a sign of a bug in your code."
0105 % (var, d[var]))
0106 continue
0107 setFunc(var,
0108 property(setters.get('get'), setters.get('set'),
0109 setters.get('del'), setters.get('doc')))
0110
0111def unmakeProperties(obj):
0112 if isinstance(obj, dict):
0113 def delFunc(obj, var):
0114 del obj[var]
0115 d = obj
0116 else:
0117 delFunc = delattr
0118 d = obj.__dict__
0119
0120 for var, value in d.items():
0121 if isinstance(value, property):
0122 for prop in [value.fget, value.fset, value.fdel]:
0123 if prop and not d.has_key(prop.__name__):
0124 delFunc(obj, var)
0125 break
0126
0127def findDependencies(name, registry=None):
0128 depends = []
0129 for klass in classregistry.registry(registry).allClasses():
0130 if findDependantColumns(name, klass):
0131 depends.append(klass)
0132 else:
0133 for join in klass.sqlmeta.joins:
0134 if isinstance(join, joins.SORelatedJoin) and join.otherClassName == name:
0135 depends.append(klass)
0136 break
0137 return depends
0138
0139def findDependantColumns(name, klass):
0140 depends = []
0141 for col in klass.sqlmeta.columnList:
0142 if col.foreignKey == name and col.cascade is not None:
0143 depends.append(col)
0144 return depends
0145
0146def _collectAttributes(cls, new_attrs, look_for_class, delete=True,
0147 set_name=False, sort=False):
0148 """
0149 Finds all attributes in `new_attrs` that are instances of
0150 `look_for_class`. Returns them as a list. If `delete` is true
0151 they are also removed from the `cls`. If `set_name` is true, then
0152 the ``.name`` attribute is set for any matching objects. If
0153 `sort` is true, then they will be sorted by ``obj.creationOrder``.
0154 """
0155 result = []
0156 for attr, value in new_attrs.items():
0157 if isinstance(value, look_for_class):
0158 result.append(value)
0159 if set_name:
0160 value.name = attr
0161 if delete:
0162 delattr(cls, attr)
0163 if sort:
0164 result.sort(
0165 lambda a, b: cmp(a.creationOrder, b.creationOrder))
0166 return result
0167
0168class CreateNewSQLObject:
0169 """
0170 Dummy singleton to use in place of an ID, to signal we want
0171 a new object.
0172 """
0173 pass
0174
0175class sqlmeta(object):
0176
0177 """
0178 This object is the object we use to keep track of all sorts of
0179 information. Subclasses are made for each SQLObject subclass
0180 (dynamically if necessary), and instances are created to go
0181 alongside every SQLObject instance.
0182 """
0183
0184 table = None
0185 idName = None
0186 idSequence = None
0187
0188
0189
0190 idType = int
0191 style = None
0192 lazyUpdate = False
0193 defaultOrder = None
0194 cacheValues = True
0195 registry = None
0196 fromDatabase = False
0197
0198
0199 expired = False
0200
0201
0202
0203 columns = {}
0204 columnList = []
0205
0206
0207
0208
0209 columnDefinitions = {}
0210
0211
0212 joins = []
0213 indexes = []
0214 indexDefinitions = []
0215 joinDefinitions = []
0216
0217 __metaclass__ = declarative.DeclarativeMeta
0218
0219
0220 _unshared_attributes = ['table', 'columns', 'childName']
0221
0222
0223
0224
0225
0226
0227
0228
0229
0230
0231
0232
0233 _creating = False
0234 _obsolete = False
0235
0236
0237
0238 _perConnection = False
0239
0240
0241 parentClass = None
0242 childClasses = {}
0243 childName = None
0244
0245 def __classinit__(cls, new_attrs):
0246 for attr in cls._unshared_attributes:
0247 if not new_attrs.has_key(attr):
0248 setattr(cls, attr, None)
0249 declarative.setup_attributes(cls, new_attrs)
0250
0251 def __init__(self, instance):
0252 self.instance = weakref.proxy(instance)
0253
0254 def send(cls, signal, *args, **kw):
0255 events.send(signal, cls.soClass, *args, **kw)
0256
0257 send = classmethod(send)
0258
0259 def setClass(cls, soClass):
0260 cls.soClass = soClass
0261 if not cls.style:
0262 cls.style = styles.defaultStyle
0263 try:
0264 if cls.soClass._connection and cls.soClass._connection.style:
0265 cls.style = cls.soClass._connection.style
0266 except AttributeError:
0267 pass
0268 if cls.table is None:
0269 cls.table = cls.style.pythonClassToDBTable(cls.soClass.__name__)
0270 if cls.idName is None:
0271 cls.idName = cls.style.idForTable(cls.table)
0272
0273
0274
0275
0276
0277
0278 cls._plainSetters = {}
0279 cls._plainGetters = {}
0280 cls._plainForeignSetters = {}
0281 cls._plainForeignGetters = {}
0282 cls._plainJoinGetters = {}
0283 cls._plainJoinAdders = {}
0284 cls._plainJoinRemovers = {}
0285
0286
0287
0288 cls.columns = {}
0289 cls.columnList = []
0290
0291 cls.columnDefinitions = cls.columnDefinitions.copy()
0292 cls.indexes = []
0293 cls.indexDefinitions = cls.indexDefinitions[:]
0294 cls.joins = []
0295 cls.joinDefinitions = cls.joinDefinitions[:]
0296
0297 setClass = classmethod(setClass)
0298
0299
0300
0301
0302
0303
0304
0305
0306
0307 def addColumn(cls, columnDef, changeSchema=False, connection=None):
0308 post_funcs = []
0309 cls.send(events.AddColumnSignal, cls.soClass, connection,
0310 columnDef.name, columnDef, changeSchema, post_funcs)
0311 sqlmeta = cls
0312 soClass = cls.soClass
0313 del cls
0314 column = columnDef.withClass(soClass)
0315 name = column.name
0316 assert name != 'id', (
0317 "The 'id' column is implicit, and should not be defined as "
0318 "a column")
0319 assert name not in sqlmeta.columns, (
0320 "The class %s.%s already has a column %r (%r), you cannot "
0321 "add the column %r"
0322 % (soClass.__module__, soClass.__name__, name,
0323 sqlmeta.columnDefinitions[name], columnDef))
0324
0325
0326 parent_columns = []
0327 for base in soClass.__bases__:
0328 if hasattr(base, "sqlmeta"):
0329 parent_columns.extend(base.sqlmeta.columns.keys())
0330 if hasattr(soClass, name):
0331 assert (name in parent_columns) or (name == "childName"), (
0332 "The class %s.%s already has a variable or method %r, you cannot "
0333 "add the column %r"
0334 % (soClass.__module__, soClass.__name__, name, name))
0335 sqlmeta.columnDefinitions[name] = columnDef
0336 sqlmeta.columns[name] = column
0337
0338 sqlmeta.columnList.append(column)
0339
0340
0341
0342
0343
0344
0345
0346
0347 if sqlmeta.cacheValues:
0348
0349
0350 getter = eval('lambda self: self._SO_loadValue(%s)' % repr(instanceName(name)))
0351
0352 else:
0353
0354
0355
0356 getter = eval('lambda self: self._SO_getValue(%s)' % repr(name))
0357 setattr(soClass, rawGetterName(name), getter)
0358
0359
0360
0361
0362 if not hasattr(soClass, getterName(name)) or (name == 'childName'):
0363 setattr(soClass, getterName(name), getter)
0364 sqlmeta._plainGetters[name] = 1
0365
0366
0367
0368
0369
0370
0371
0372
0373
0374
0375 if not column.immutable:
0376
0377 setter = eval('lambda self, val: self._SO_setValue(%s, val, self.%s, self.%s)' % (repr(name), '_SO_from_python_%s' % name, '_SO_to_python_%s' % name))
0378 setattr(soClass, '_SO_from_python_%s' % name, column.from_python)
0379 setattr(soClass, '_SO_to_python_%s' % name, column.to_python)
0380 setattr(soClass, rawSetterName(name), setter)
0381
0382 if not hasattr(soClass, setterName(name)) or (name == 'childName'):
0383 setattr(soClass, setterName(name), setter)
0384
0385
0386
0387 sqlmeta._plainSetters[name] = 1
0388
0389
0390
0391
0392
0393
0394 if column.foreignKey:
0395
0396
0397
0398
0399 origName = column.origName
0400 if sqlmeta.cacheValues:
0401
0402
0403 getter = eval('lambda self: self._SO_foreignKey(self._SO_loadValue(%r), self._SO_class_%s)' % (instanceName(name), column.foreignKey))
0404 else:
0405
0406 getter = eval('lambda self: self._SO_foreignKey(self._SO_getValue(%s), self._SO_class_%s)' % (repr(name), column.foreignKey))
0407 setattr(soClass, rawGetterName(origName), getter)
0408
0409
0410 if not hasattr(soClass, getterName(origName)):
0411 setattr(soClass, getterName(origName), getter)
0412 sqlmeta._plainForeignGetters[origName] = 1
0413
0414 if not column.immutable:
0415
0416
0417 setter = eval('lambda self, val: setattr(self, %s, self._SO_getID(val))' % (repr(name)))
0418 setattr(soClass, rawSetterName(origName), setter)
0419 if not hasattr(soClass, setterName(origName)):
0420 setattr(soClass, setterName(origName), setter)
0421 sqlmeta._plainForeignSetters[origName] = 1
0422
0423 classregistry.registry(sqlmeta.registry).addClassCallback(
0424 column.foreignKey,
0425 lambda foreign, me, attr: setattr(me, attr, foreign),
0426 soClass, '_SO_class_%s' % column.foreignKey)
0427
0428 if column.alternateMethodName:
0429 func = eval('lambda cls, val, connection=None: cls._SO_fetchAlternateID(%s, %s, val, connection=connection)' % (repr(column.name), repr(column.dbName)))
0430 setattr(soClass, column.alternateMethodName, classmethod(func))
0431
0432 if changeSchema:
0433 conn = connection or soClass._connection
0434 conn.addColumn(sqlmeta.table, column)
0435
0436 if soClass._SO_finishedClassCreation:
0437 makeProperties(soClass)
0438
0439 for func in post_funcs:
0440 func(soClass, column)
0441
0442 addColumn = classmethod(addColumn)
0443
0444 def addColumnsFromDatabase(sqlmeta, connection=None):
0445 soClass = sqlmeta.soClass
0446 conn = connection or soClass._connection
0447 for columnDef in conn.columnsFromSchema(sqlmeta.table, soClass):
0448 if columnDef.name not in sqlmeta.columnDefinitions:
0449 if isinstance(columnDef.name, unicode):
0450 columnDef.name = columnDef.name.encode('ascii')
0451 sqlmeta.addColumn(columnDef)
0452
0453 addColumnsFromDatabase = classmethod(addColumnsFromDatabase)
0454
0455 def delColumn(cls, column, changeSchema=False, connection=None):
0456 sqlmeta = cls
0457 soClass = sqlmeta.soClass
0458 if isinstance(column, str):
0459 column = sqlmeta.columns[column]
0460 if isinstance(column, col.Col):
0461 for c in sqlmeta.columns.values():
0462 if column is c.columnDef:
0463 column = c
0464 break
0465 else:
0466 raise IndexError(
0467 "Column with definition %r not found" % column)
0468 post_funcs = []
0469 cls.send(events.DeleteColumnSignal, connection, column.name, column,
0470 post_funcs)
0471 name = column.name
0472 del sqlmeta.columns[name]
0473 del sqlmeta.columnDefinitions[name]
0474 sqlmeta.columnList.remove(column)
0475 delattr(soClass, rawGetterName(name))
0476