0001"""
0002SQLObject
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 Broytman 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,
0026USA.
0027"""
0028import sys
0029import threading
0030import weakref
0031import types
0032import warnings
0033from . import sqlbuilder
0034from . import dbconnection
0035from . import col
0036from . import styles
0037from . import joins
0038from . import index
0039from . import classregistry
0040from . import declarative
0041from . import events
0042from .sresults import SelectResults
0043from .util.threadinglocal import local
0044from sqlobject.compat import PY2, with_metaclass, string_type, unicode_type
0045
0046if ((sys.version_info[0] == 2) and (sys.version_info[:2] < (2, 6))) or ((sys.version_info[0] == 3) and (sys.version_info[:2] < (3, 4))):
0048 raise ImportError("SQLObject requires Python 2.6, 2.7 or 3.4+")
0049
0050if not PY2:
0051
0052 long = int
0053
0054"""
0055This thread-local storage is needed for RowCreatedSignals. It gathers
0056code-blocks to execute _after_ the whole hierachy of inherited SQLObjects
0057is created. See SQLObject._create
0058"""
0059
0060NoDefault = sqlbuilder.NoDefault
0061
0062
0063class SQLObjectNotFound(LookupError):
0064 pass
0065
0066
0067class SQLObjectIntegrityError(Exception):
0068 pass
0069
0070
0071def makeProperties(obj):
0072 """
0073 This function takes a dictionary of methods and finds
0074 methods named like:
0075 * _get_attr
0076 * _set_attr
0077 * _del_attr
0078 * _doc_attr
0079 Except for _doc_attr, these should be methods. It
0080 then creates properties from these methods, like
0081 property(_get_attr, _set_attr, _del_attr, _doc_attr).
0082 Missing methods are okay.
0083 """
0084
0085 if isinstance(obj, dict):
0086 def setFunc(var, value):
0087 obj[var] = value
0088 d = obj
0089 else:
0090 def setFunc(var, value):
0091 setattr(obj, var, value)
0092 d = obj.__dict__
0093
0094 props = {}
0095 for var, value in d.items():
0096 if var.startswith('_set_'):
0097 props.setdefault(var[5:], {})['set'] = value
0098 elif var.startswith('_get_'):
0099 props.setdefault(var[5:], {})['get'] = value
0100 elif var.startswith('_del_'):
0101 props.setdefault(var[5:], {})['del'] = value
0102 elif var.startswith('_doc_'):
0103 props.setdefault(var[5:], {})['doc'] = value
0104 for var, setters in props.items():
0105 if len(setters) == 1 and 'doc' in setters:
0106 continue
0107 if var in d:
0108 if isinstance(d[var], (types.MethodType, types.FunctionType)):
0109 warnings.warn(
0110 "I tried to set the property %r, but it was "
0111 "already set, as a method (%r). Methods have "
0112 "significantly different semantics than properties, "
0113 "and this may be a sign of a bug in your code."
0114 % (var, d[var]))
0115 continue
0116 setFunc(var,
0117 property(setters.get('get'), setters.get('set'),
0118 setters.get('del'), setters.get('doc')))
0119
0120
0121def unmakeProperties(obj):
0122 if isinstance(obj, dict):
0123 def delFunc(obj, var):
0124 del obj[var]
0125 d = obj
0126 else:
0127 delFunc = delattr
0128 d = obj.__dict__
0129
0130 for var, value in list(d.items()):
0131 if isinstance(value, property):
0132 for prop in [value.fget, value.fset, value.fdel]:
0133 if prop and prop.__name__ not in d:
0134 delFunc(obj, var)
0135 break
0136
0137
0138def findDependencies(name, registry=None):
0139 depends = []
0140 for klass in classregistry.registry(registry).allClasses():
0141 if findDependantColumns(name, klass):
0142 depends.append(klass)
0143 else:
0144 for join in klass.sqlmeta.joins:
0145 if isinstance(join, joins.SORelatedJoin) and join.otherClassName == name:
0147 depends.append(klass)
0148 break
0149 return depends
0150
0151
0152def findDependantColumns(name, klass):
0153 depends = []
0154 for _col in klass.sqlmeta.columnList:
0155 if _col.foreignKey == name and _col.cascade is not None:
0156 depends.append(_col)
0157 return depends
0158
0159
0160def _collectAttributes(cls, new_attrs, look_for_class):
0161 """Finds all attributes in `new_attrs` that are instances of
0162 `look_for_class`. The ``.name`` attribute is set for any matching objects.
0163 Returns them as a list.
0164
0165 """
0166 result = []
0167 for attr, value in new_attrs.items():
0168 if isinstance(value, look_for_class):
0169 value.name = attr
0170 delattr(cls, attr)
0171 result.append(value)
0172 return result
0173
0174
0175class CreateNewSQLObject:
0176 """
0177 Dummy singleton to use in place of an ID, to signal we want
0178 a new object.
0179 """
0180 pass
0181
0182
0183class sqlmeta(with_metaclass(declarative.DeclarativeMeta, object)):
0184 """
0185 This object is the object we use to keep track of all sorts of
0186 information. Subclasses are made for each SQLObject subclass
0187 (dynamically if necessary), and instances are created to go
0188 alongside every SQLObject instance.
0189 """
0190
0191 table = None
0192 idName = None
0193 idSequence = None
0194
0195
0196
0197 idType = int
0198 style = None
0199 lazyUpdate = False
0200 defaultOrder = None
0201 cacheValues = True
0202 registry = None
0203 fromDatabase = False
0204
0205
0206 expired = False
0207
0208
0209
0210 columns = {}
0211 columnList = []
0212
0213
0214
0215
0216 columnDefinitions = {}
0217
0218
0219 indexes = []
0220 indexDefinitions = []
0221 joins = []
0222 joinDefinitions = []
0223
0224
0225 _unshared_attributes = ['table', 'columns', 'childName']
0226
0227
0228
0229
0230
0231
0232
0233
0234
0235
0236
0237
0238 _creating = False
0239 _obsolete = False
0240
0241
0242
0243 _perConnection = False
0244
0245
0246 parentClass = None
0247 childClasses = {}
0248 childName = None
0249
0250
0251 dirty = False
0252
0253
0254 dbEncoding = None
0255
0256 def __classinit__(cls, new_attrs):
0257 for attr in cls._unshared_attributes:
0258 if attr not in new_attrs:
0259 setattr(cls, attr, None)
0260 declarative.setup_attributes(cls, new_attrs)
0261
0262 def __init__(self, instance):
0263 self.instance = weakref.proxy(instance)
0264
0265 @classmethod
0266 def send(cls, signal, *args, **kw):
0267 events.send(signal, cls.soClass, *args, **kw)
0268
0269 @classmethod
0270 def setClass(cls, soClass):
0271 cls.soClass = soClass
0272 if not cls.style:
0273 cls.style = styles.defaultStyle
0274 try:
0275 if cls.soClass._connection and cls.soClass._connection.style:
0276 cls.style = cls.soClass._connection.style
0277 except AttributeError:
0278 pass
0279 if cls.table is None:
0280 cls.table = cls.style.pythonClassToDBTable(cls.soClass.__name__)
0281 if cls.idName is None:
0282 cls.idName = cls.style.idForTable(cls.table)
0283
0284
0285
0286
0287
0288
0289 cls._plainSetters = {}
0290 cls._plainGetters = {}
0291 cls._plainForeignSetters = {}
0292 cls._plainForeignGetters = {}
0293 cls._plainJoinGetters = {}
0294 cls._plainJoinAdders = {}
0295 cls._plainJoinRemovers = {}
0296
0297
0298
0299 cls.columns = {}
0300 cls.columnList = []
0301
0302 cls.columnDefinitions = cls.columnDefinitions.copy()
0303 cls.indexes = []
0304 cls.indexDefinitions = cls.indexDefinitions[:]
0305 cls.joins = []
0306 cls.joinDefinitions = cls.joinDefinitions[:]
0307
0308
0309
0310
0311
0312
0313
0314
0315
0316 @classmethod
0317 def addColumn(cls, columnDef, changeSchema=False, connection=None):
0318 post_funcs = []
0319 cls.send(events.AddColumnSignal, cls.soClass, connection,
0320 columnDef.name, columnDef, changeSchema, post_funcs)
0321 sqlmeta = cls
0322 soClass = cls.soClass
0323 del cls
0324 column = columnDef.withClass(soClass)
0325 name = column.name
0326 assert name != 'id', (
0327 "The 'id' column is implicit, and should not be defined as "
0328 "a column")
0329 assert name not in sqlmeta.columns, (
0330 "The class %s.%s already has a column %r (%r), you cannot "
0331 "add the column %r"
0332 % (soClass.__module__, soClass.__name__, name,
0333 sqlmeta.columnDefinitions[name], columnDef))
0334
0335
0336 parent_columns = []
0337 for base in soClass.__bases__:
0338 if hasattr(base, "sqlmeta"):
0339 parent_columns.extend(base.sqlmeta.columns.keys())
0340 if hasattr(soClass, name):
0341 assert (name in parent_columns) or (name == "childName"), (
0342 "The class %s.%s already has a variable or method %r, "
0343 "you cannot add the column %r" % (
0344 soClass.__module__, soClass.__name__, name, name))
0345 sqlmeta.columnDefinitions[name] = columnDef
0346 sqlmeta.columns[name] = column
0347
0348 sqlmeta.columnList.append(column)
0349
0350
0351
0352
0353
0354
0355
0356
0357 if sqlmeta.cacheValues:
0358
0359
0360 getter = eval(
0361 'lambda self: self._SO_loadValue(%s)' %
0362 repr(instanceName(name)))
0363
0364 else:
0365
0366
0367
0368 getter = eval('lambda self: self._SO_getValue(%s)' % repr(name))
0369 setattr(soClass, rawGetterName(name), getter)
0370
0371
0372
0373
0374 if not hasattr(soClass, getterName(name)) or (name == 'childName'):
0375 setattr(soClass, getterName(name), getter)
0376 sqlmeta._plainGetters[name] = 1
0377
0378
0379
0380
0381
0382
0383
0384
0385
0386
0387 if not column.immutable:
0388
0389 setter = eval(
0390 'lambda self, val: self._SO_setValue'
0391 '(%s, val, self.%s, self.%s)' % (
0392 repr(name),
0393 '_SO_from_python_%s' % name, '_SO_to_python_%s' % name))
0394 setattr(soClass, '_SO_from_python_%s' % name, column.from_python)
0395 setattr(soClass, '_SO_to_python_%s' % name, column.to_python)
0396 setattr(soClass, rawSetterName(name), setter)
0397
0398 if not hasattr(soClass, setterName(name)) or (name == 'childName'):
0399 setattr(soClass, setterName(name), setter)
0400
0401
0402
0403 sqlmeta._plainSetters[name] = 1
0404
0405
0406
0407
0408
0409
0410 if column.foreignKey:
0411
0412
0413
0414
0415 origName = column.origName
0416 if sqlmeta.cacheValues:
0417
0418
0419 getter = eval(
0420 'lambda self: self._SO_foreignKey'
0421 '(self._SO_loadValue(%r), self._SO_class_%s, %s)' %
0422 (instanceName(name), column.foreignKey,
0423 column.refColumn and repr(column.refColumn)))
0424 else:
0425
0426 getter = eval(
0427 'lambda self: self._SO_foreignKey'
0428 '(self._SO_getValue(%s), self._SO_class_%s, %s)' %
0429 (repr(name), column.foreignKey,
0430 column.refColumn and repr(column.refColumn)))
0431 setattr(soClass, rawGetterName(origName), getter)
0432
0433
0434 if not hasattr(soClass, getterName(origName)):
0435 setattr(soClass, getterName(origName), getter)
0436 sqlmeta._plainForeignGetters[origName] = 1
0437
0438 if not column.immutable:
0439
0440
0441 setter = eval(
0442 'lambda self, val: '
0443 'setattr(self, %s, self._SO_getID(val, %s))' %
0444 (repr(name), column.refColumn and repr(column.refColumn)))
0445 setattr(soClass, rawSetterName(origName), setter)
0446 if not hasattr(soClass, setterName(origName)):
0447 setattr(soClass, setterName(origName), setter)
0448 sqlmeta._plainForeignSetters[origName] = 1
0449
0450 classregistry.registry(sqlmeta.registry).addClassCallback(
0451 column.foreignKey,
0452 lambda foreign, me, attr: setattr(me, attr, foreign),
0453 soClass, '_SO_class_%s' % column.foreignKey)
0454
0455 if column.alternateMethodName:
0456 func = eval(
0457 'lambda cls, val, connection=None: '
0458 'cls._SO_fetchAlternateID(%s, %s, val, connection=connection)'
0459 % (repr(column.name), repr(column.dbName)))
0460 setattr(soClass, column.alternateMethodName, classmethod(func))
0461
0462 if changeSchema:
0463 conn = connection or soClass._connection
0464 conn.addColumn(sqlmeta.table, column)
0465
0466 if soClass._SO_finishedClassCreation:
0467 makeProperties(soClass)
0468
0469 for func in post_funcs:
0470 func(soClass, column)
0471
0472 @classmethod
0473 def addColumnsFromDatabase(sqlmeta, connection=None):
0474 soClass = sqlmeta.soClass
0475 conn = connection or soClass._connection
0476 for columnDef in conn.columnsFromSchema(sqlmeta.table, soClass):
0477 if columnDef.name not in sqlmeta.columnDefinitions:
0478 if isinstance(columnDef.name, unicode_type) and PY2:
0479 columnDef.name = columnDef.name.encode('ascii')
0480 sqlmeta.addColumn(columnDef)
0481
0482 @classmethod
0483 def delColumn(cls, column, changeSchema=False, connection=None):
0484 sqlmeta = cls
0485 soClass = sqlmeta.soClass
0486 if isinstance(column, str):
0487 if column in sqlmeta.columns:
0488 column = sqlmeta.columns[column]
0489 elif column + 'ID' in sqlmeta.columns:
0490 column = sqlmeta.columns[column + 'ID']
0491 else:
0492 raise ValueError('Unknown column ' + column)
0493 if isinstance(column, col.Col):
0494 for c in sqlmeta.columns.values():
0495 if column is c.columnDef:
0496 column = c
0497 break
0498 else:
0499 raise IndexError(
0500 "Column with definition %r not found" % column)
0501 post_funcs = []
0502 cls.send(events.DeleteColumnSignal, cls.soClass, connection,
0503 column.name, column, post_funcs)
0504 name = column.name
0505 del sqlmeta.columns[name]
0506 del sqlmeta.columnDefinitions[name]
0507 sqlmeta.columnList.remove(column)
0508 delattr(soClass, rawGetterName(name))
0509 if name in sqlmeta._plainGetters:
0510 delattr(soClass, getterName(name))
0511 delattr(soClass, rawSetterName(name))
0512 if name in sqlmeta._plainSetters:
0513 delattr(soClass, setterName(name))
0514 if column.foreignKey:
0515 delattr(soClass,
0516 rawGetterName(soClass.sqlmeta.style.
0517 instanceIDAttrToAttr(name)))
0518 if name in sqlmeta._plainForeignGetters:
0519 delattr(soClass, getterName(name))
0520 delattr(soClass,
0521 rawSetterName(soClass.sqlmeta.style.
0522 instanceIDAttrToAttr(name)))
0523 if name in sqlmeta._plainForeignSetters:
0524 delattr(soClass, setterName(name))
0525 if column.alternateMethodName:
0526 delattr(soClass, column.alternateMethodName)
0527
0528 if changeSchema:
0529 conn = connection or soClass._connection
0530 conn.delColumn(sqlmeta, column)
0531
0532 if soClass._SO_finishedClassCreation:
0533 unmakeProperties(soClass)
0534 makeProperties(soClass)
0535
0536 for func in post_funcs:
0537 func(soClass, column)
0538
0539
0540
0541
0542
0543 @classmethod
0544 def addJoin(cls, joinDef):
0545 sqlmeta = cls
0546 soClass = cls.soClass
0547
0548
0549
0550 join = joinDef.withClass(soClass)
0551 meth = join.joinMethodName
0552
0553 sqlmeta.joins.append(join)
0554 index = len(sqlmeta.joins) - 1
0555 if joinDef not in sqlmeta.joinDefinitions:
0556 sqlmeta.joinDefinitions.append(joinDef)
0557
0558
0559
0560
0561 func = eval(
0562 'lambda self: self.sqlmeta.joins[%i].performJoin(self)' % index)
0563
0564
0565 setattr(soClass, rawGetterName(meth), func)
0566 if not hasattr(soClass, getterName(meth)):
0567 setattr(soClass, getterName(meth), func)
0568 sqlmeta._plainJoinGetters[meth] = 1
0569
0570
0571
0572 if hasattr(join, 'remove'):
0573
0574
0575 func = eval(
0576 'lambda self, obj: self.sqlmeta.joins[%i].remove(self, obj)' %
0577 index)
0578 setattr(soClass, '_SO_remove' + join.addRemoveName, func)
0579 if not hasattr(soClass, 'remove' + join.addRemoveName):
0580 setattr(soClass, 'remove' + join.addRemoveName, func)
0581 sqlmeta._plainJoinRemovers[meth] = 1
0582
0583
0584 if hasattr(join, 'add'):
0585
0586 func = eval(
0587 'lambda self, obj: self.sqlmeta.joins[%i].add(self, obj)' %
0588 index)
0589 setattr(soClass, '_SO_add' + join.addRemoveName, func)
0590 if not hasattr(soClass, 'add' + join.addRemoveName):
0591 setattr(soClass, 'add' + join.addRemoveName, func)
0592 sqlmeta._plainJoinAdders[meth] = 1
0593
0594 if soClass._SO_finishedClassCreation:
0595 makeProperties(soClass)
0596
0597 @classmethod
0598 def delJoin(sqlmeta, joinDef):
0599 soClass = sqlmeta.soClass
0600 for join in sqlmeta.joins:
0601
0602
0603 if join is None:
0604 continue
0605 if joinDef is join.joinDef:
0606 break
0607 else:
0608 raise IndexError(
0609 "Join %r not found in class %r (from %r)"
0610 % (joinDef, soClass, sqlmeta.joins))
0611 meth = join.joinMethodName
0612 sqlmeta.joinDefinitions.remove(joinDef)
0613 for i in range(len(sqlmeta.joins)):
0614 if sqlmeta.joins[i] is join:
0615
0616
0617 sqlmeta.joins[i] = None
0618 delattr(soClass, rawGetterName(meth))
0619 if meth in sqlmeta._plainJoinGetters:
0620 delattr(soClass, getterName(meth))
0621 if hasattr(join, 'remove'):
0622 delattr(soClass, '_SO_remove' + join.addRemovePrefix)
0623 if meth in sqlmeta._plainJoinRemovers:
0624 delattr(soClass, 'remove' + join.addRemovePrefix)
0625 if hasattr(join, 'add'):
0626 delattr(soClass, '_SO_add' + join.addRemovePrefix)
0627 if meth in sqlmeta._plainJoinAdders:
0628 delattr(soClass, 'add' + join.addRemovePrefix)
0629
0630 if soClass._SO_finishedClassCreation:
0631 unmakeProperties(soClass)
0632 makeProperties(soClass)
0633
0634
0635
0636
0637
0638 @classmethod
0639 def addIndex(cls, indexDef):
0640 cls.indexDefinitions.append(indexDef)
0641 index = indexDef.withClass(cls.soClass)
0642 cls.indexes.append(index)
0643 setattr(cls.soClass, index.name, index)
0644
0645
0646
0647
0648
0649 @classmethod
0650 def getColumns(sqlmeta):
0651 return sqlmeta.columns.copy()
0652
0653 def asDict(self):
0654 """
0655 Return the object as a dictionary of columns to values.
0656 """
0657 result = {}
0658 for key in self.getColumns():
0659 result[key] = getattr(self.instance, key)
0660 result['id'] = self.instance.id
0661 return result
0662
0663 @classmethod
0664 def expireAll(sqlmeta, connection=None):
0665 """
0666 Expire all instances of this class.
0667 """
0668 soClass = sqlmeta.soClass
0669 connection = connection or soClass._connection
0670 cache_set = connection.cache
0671 cache_set.weakrefAll(soClass)
0672 for item in cache_set.getAll(soClass):
0673 item.expire()
0674
0675
0676sqlhub = dbconnection.ConnectionHub()
0677
0678
0679
0680
0681
0682warnings_level = 1
0683exception_level = None
0684
0685
0686
0687
0688
0689
0690def deprecated(message, level=1, stacklevel=2):
0691 if exception_level is not None and exception_level <= level:
0692 raise NotImplementedError(message)
0693 if warnings_level is not None and warnings_level <= level:
0694 warnings.warn(message, DeprecationWarning, stacklevel=stacklevel)
0695
0696
0697
0698
0699
0700
0701def setDeprecationLevel(warning=1, exception=None):
0702 """
0703 Set the deprecation level for SQLObject. Low levels are more
0704 actively being deprecated. Any warning at a level at or below
0705 ``warning`` will give a warning. Any warning at a level at or
0706 below ``exception`` will give an exception. You can use a higher
0707 ``exception`` level for tests to help upgrade your code. ``None``
0708 for either value means never warn or raise exceptions.
0709
0710 The levels currently mean:
0711
0712 1) Deprecated in current version. Will be removed in next version.
0713
0714 2) Planned to deprecate in next version, remove later.
0715
0716 3) Planned to deprecate sometime, remove sometime much later.
0717
0718 As the SQLObject versions progress, the deprecation level of
0719 specific features will go down, indicating the advancing nature of
0720 the feature's doom. We'll try to keep features at 1 for a major
0721 revision.
0722
0723 As time continues there may be a level 0, which will give a useful
0724 error message (better than ``AttributeError``) but where the
0725 feature has been fully removed.
0726 """
0727 global warnings_level, exception_level
0728 warnings_level = warning
0729 exception_level = exception
0730
0731
0732class _sqlmeta_attr(object):
0733
0734 def __init__(self, name, deprecation_level):
0735 self.name = name
0736 self.deprecation_level = deprecation_level
0737
0738 def __get__(self, obj, type=None):
0739 if self.deprecation_level is not None:
0740 deprecated(
0741 'Use of this attribute should be replaced with '
0742 '.sqlmeta.%s' % self.name, level=self.deprecation_level)
0743 return getattr((type or obj).sqlmeta, self.name)
0744
0745
0746_postponed_local = local()
0747
0748
0749
0750
0751
0752
0753
0754
0755
0756
0757class SQLObject(with_metaclass(declarative.DeclarativeMeta, object)):
0758
0759 _connection = sqlhub
0760
0761 sqlmeta = sqlmeta
0762
0763
0764
0765 _inheritable = False
0766 _parent = None
0767 childName = None
0768
0769
0770 SelectResultsClass = SelectResults
0771
0772 def __classinit__(cls, new_attrs):
0773
0774
0775
0776 is_base = cls.__bases__ == (object,)
0777
0778 cls._SO_setupSqlmeta(new_attrs, is_base)
0779
0780 implicitColumns = _collectAttributes(cls, new_attrs, col.Col)
0781 implicitJoins = _collectAttributes(cls, new_attrs, joins.Join)
0782 implicitIndexes = _collectAttributes(cls, new_attrs,
0783 index.DatabaseIndex)
0784
0785 if not is_base:
0786 cls._SO_cleanDeprecatedAttrs(new_attrs)
0787
0788 if '_connection' in new_attrs:
0789 connection = new_attrs['_connection']
0790 del cls._connection
0791 assert 'connection' not in new_attrs
0792 elif 'connection' in new_attrs:
0793 connection = new_attrs['connection']
0794 del cls.connection
0795 else:
0796 connection = None
0797
0798 cls._SO_finishedClassCreation = False
0799
0800
0801
0802
0803 if not connection and not getattr(cls, '_connection', None):
0804 mod = sys.modules[cls.__module__]
0805
0806
0807 if hasattr(mod, '__connection__'):
0808 connection = mod.__connection__
0809
0810
0811
0812
0813 if connection and ('_connection' not in cls.__dict__):
0814 cls.setConnection(connection)
0815
0816 sqlmeta = cls.sqlmeta
0817
0818
0819
0820
0821
0822 for key in sqlmeta.columnDefinitions.keys():
0823 if (key in new_attrs and new_attrs[key] is None):
0824 del sqlmeta.columnDefinitions[key]
0825
0826 for column in sqlmeta.columnDefinitions.values():
0827 sqlmeta.addColumn(column)
0828
0829 for column in implicitColumns:
0830 sqlmeta.addColumn(column)
0831
0832
0833
0834 declarative.setup_attributes(cls, new_attrs)
0835
0836 if sqlmeta.fromDatabase:
0837 sqlmeta.addColumnsFromDatabase()
0838
0839 for j in implicitJoins:
0840 sqlmeta.addJoin(j)
0841 for i in implicitIndexes:
0842 sqlmeta.addIndex(i)
0843
0844 def order_getter(o):
0845 return o.creationOrder
0846 sqlmeta.columnList.sort(key=order_getter)
0847 sqlmeta.indexes.sort(key=order_getter)
0848 sqlmeta.indexDefinitions.sort(key=order_getter)
0849
0850
0851
0852 sqlmeta.joinDefinitions.sort(key=order_getter)
0853
0854
0855
0856 cls._notifyFinishClassCreation()
0857 cls._SO_finishedClassCreation = True
0858 makeProperties(cls)
0859
0860
0861
0862
0863 if not is_base:
0864 cls.q = sqlbuilder.SQLObjectTable(cls)
0865 cls.j = sqlbuilder.SQLObjectTableWithJoins(cls)
0866
0867 classregistry.registry(sqlmeta.registry).addClass(cls)
0868
0869 @classmethod
0870 def _SO_setupSqlmeta(cls, new_attrs, is_base):
0871 """
0872 This fixes up the sqlmeta attribute. It handles both the case
0873 where no sqlmeta was given (in which we need to create another
0874 subclass), or the sqlmeta given doesn't have the proper
0875 inheritance. Lastly it calls sqlmeta.setClass, which handles
0876 much of the setup.
0877 """
0878 if ('sqlmeta' not in new_attrs and not is_base):
0879
0880
0881 cls.sqlmeta = type('sqlmeta', (cls.sqlmeta,), {})
0882 if not issubclass(cls.sqlmeta, sqlmeta):
0883
0884
0885
0886 assert cls.sqlmeta.__bases__ in ((), (object,)), (
0887 "If you do not inherit your sqlmeta class from "
0888 "sqlobject.sqlmeta, it must not inherit from any other "
0889 "class (your sqlmeta inherits from: %s)"
0890 % cls.sqlmeta.__bases__)
0891 for base in cls.__bases__:
0892 superclass = getattr(base, 'sqlmeta', None)
0893 if superclass:
0894 break
0895 else:
0896 assert 0, (
0897 "No sqlmeta class could be found in any superclass "
0898 "(while fixing up sqlmeta %r inheritance)"
0899 % cls.sqlmeta)
0900 values = dict(cls.sqlmeta.__dict__)
0901 for key in list(values.keys()):
0902 if key.startswith('__') and key.endswith('__'):
0903
0904 del values[key]
0905 cls.sqlmeta = type('sqlmeta', (superclass,), values)
0906
0907 if not is_base:
0908 cls.sqlmeta.setClass(cls)
0909
0910 @classmethod
0911 def _SO_cleanDeprecatedAttrs(cls, new_attrs):
0912 """
0913 This removes attributes on SQLObject subclasses that have
0914 been deprecated; they are moved to the sqlmeta class, and
0915 a deprecation warning is given.
0916 """
0917 for attr in ():
0918 if attr in new_attrs:
0919 deprecated("%r is deprecated and read-only; please do "
0920 "not use it in your classes until it is fully "
0921 "deprecated" % attr, level=1, stacklevel=5)
0922
0923 @classmethod
0924 def get(cls, id, connection=None, selectResults=None):
0925
0926 assert id is not None, 'None is not a possible id for %s' % cls.__name__
0928
0929 id = cls.sqlmeta.idType(id)
0930
0931 if connection is None:
0932 cache = cls._connection.cache
0933 else:
0934 cache = connection.cache
0935
0936
0937
0938 val = cache.get(id, cls)
0939 if val is None:
0940 try:
0941 val = cls(_SO_fetch_no_create=1)
0942 val._SO_validatorState = sqlbuilder.SQLObjectState(val)
0943 val._init(id, connection, selectResults)
0944 cache.put(id, cls, val)
0945 finally:
0946 cache.finishPut(cls)
0947 elif selectResults and not val.sqlmeta.dirty:
0948 val._SO_writeLock.acquire()
0949 try:
0950 val._SO_selectInit(selectResults)
0951 val.sqlmeta.expired = False
0952 finally:
0953 val._SO_writeLock.release()
0954 return val
0955
0956 @classmethod
0957 def _notifyFinishClassCreation(cls):
0958 pass
0959
0960 def _init(self, id, connection=None, selectResults=None):
0961 assert id is not None
0962
0963
0964
0965 self.id = id
0966 self._SO_writeLock = threading.Lock()
0967
0968
0969
0970
0971 if (connection is not None) and (getattr(self, '_connection', None) is not connection):
0973 self._connection = connection
0974
0975
0976
0977 self.sqlmeta._perConnection = True
0978
0979 if not selectResults:
0980 dbNames = [col.dbName for col in self.sqlmeta.columnList]
0981 selectResults = self._connection._SO_selectOne(self, dbNames)
0982 if not selectResults:
0983 raise SQLObjectNotFound(
0984 "The object %s by the ID %s does not exist" % (
0985 self.__class__.__name__, self.id))
0986 self._SO_selectInit(selectResults)
0987 self._SO_createValues = {}
0988 self.sqlmeta.dirty = False
0989
0990 def _SO_loadValue(self, attrName):
0991 try:
0992 return getattr(self, attrName)
0993 except AttributeError:
0994 try:
0995 self._SO_writeLock.acquire()
0996 try:
0997
0998
0999
1000
1001
1002
1003 result = getattr(self, attrName)
1004 except AttributeError:
1005 pass
1006 else:
1007 return result
1008 self.sqlmeta.expired = False
1009 dbNames = [col.dbName for col in self.sqlmeta.columnList]
1010 selectResults = self._connection._SO_selectOne(self, dbNames)
1011 if not selectResults:
1012 raise SQLObjectNotFound(
1013 "The object %s by the ID %s has been deleted" % (
1014 self.__class__.__name__, self.id))
1015 self._SO_selectInit(selectResults)
1016 result = getattr(self, attrName)
1017 return result
1018 finally:
1019 self._SO_writeLock.release()
1020
1021 def sync(self):
1022 if self.sqlmeta.lazyUpdate and self._SO_createValues:
1023 self.syncUpdate()
1024 self._SO_writeLock.acquire()
1025 try:
1026 dbNames = [col.dbName for col in self.sqlmeta.columnList]
1027 selectResults = self._connection._SO_selectOne(self, dbNames)
1028 if not selectResults:
1029 raise SQLObjectNotFound(
1030 "The object %s by the ID %s has been deleted" % (
1031 self.__class__.__name__, self.id))
1032 self._SO_selectInit(selectResults)
1033 self.sqlmeta.expired = False
1034 finally:
1035 self._SO_writeLock.release()
1036
1037 def syncUpdate(self):
1038 if not self._SO_createValues:
1039 return
1040 self._SO_writeLock.acquire()
1041 try:
1042 if self.sqlmeta.columns:
1043 columns = self.sqlmeta.columns
1044 values = [(columns[v[0]].dbName, v[1])
1045 for v in sorted(
1046 self._SO_createValues.items(),
1047 key=lambda c: columns[c[0]].creationOrder)]
1048 self._connection._SO_update(self, values)
1049 self.sqlmeta.dirty = False
1050 self._SO_createValues = {}
1051 finally:
1052 self._SO_writeLock.release()
1053
1054 post_funcs = []
1055 self.sqlmeta.send(events.RowUpdatedSignal, self, post_funcs)
1056 for func in post_funcs:
1057 func(self)
1058
1059 def expire(self):
1060 if self.sqlmeta.expired:
1061 return
1062 self._SO_writeLock.acquire()
1063 try:
1064 if self.sqlmeta.expired:
1065 return
1066 for column in self.sqlmeta.columnList:
1067 delattr(self, instanceName(column.name))
1068 self.sqlmeta.expired = True
1069 self._connection.cache.expire(self.id, self.__class__)
1070 self._SO_createValues = {}
1071 finally:
1072 self._SO_writeLock.release()
1073
1074 def _SO_setValue(self, name, value, from_python, to_python):
1075
1076
1077
1078
1079
1080
1081
1082 d = {name: value}
1083 if not self.sqlmeta._creating and not getattr(self.sqlmeta, "row_update_sig_suppress", False):
1085 self.sqlmeta.send(events.RowUpdateSignal, self, d)
1086 if len(d) != 1 or name not in d:
1087
1088
1089 self.sqlmeta.row_update_sig_suppress = True
1090 self.set(**d)
1091 del self.sqlmeta.row_update_sig_suppress
1092 value = d[name]
1093 if from_python:
1094 dbValue = from_python(value, self._SO_validatorState)
1095 else:
1096 dbValue = value
1097 if to_python:
1098 value = to_python(dbValue, self._SO_validatorState)
1099 if self.sqlmeta._creating or self.sqlmeta.lazyUpdate:
1100 self.sqlmeta.dirty = True
1101 self._SO_createValues[name] = dbValue
1102 setattr(self, instanceName(name), value)
1103 return
1104
1105 self._connection._SO_update(
1106 self, [(self.sqlmeta.columns[name].dbName,
1107 dbValue)])
1108
1109 if self.sqlmeta.cacheValues:
1110 setattr(self, instanceName(name), value)
1111
1112 post_funcs = []
1113 self.sqlmeta.send(events.RowUpdatedSignal, self, post_funcs)
1114 for func in post_funcs:
1115 func(self)
1116
1117 def set(self, _suppress_set_sig=False, **kw):
1118 if not self.sqlmeta._creating and not getattr(self.sqlmeta, "row_update_sig_suppress", False) and not _suppress_set_sig:
1121 self.sqlmeta.send(events.RowUpdateSignal, self, kw)
1122
1123
1124
1125
1126
1127
1128 def is_column(_c):
1129 return _c in self.sqlmeta._plainSetters
1130
1131 def f_is_column(item):
1132 return is_column(item[0])
1133
1134 def f_not_column(item):
1135 return not is_column(item[0])
1136 items = kw.items()
1137 extra = dict(filter(f_not_column, items))
1138 kw = dict(filter(f_is_column, items))
1139
1140
1141 if self.sqlmeta._creating or self.sqlmeta.lazyUpdate:
1142 for name, value in kw.items():
1143 from_python = getattr(self, '_SO_from_python_%s' % name, None)
1144 if from_python:
1145 kw[name] = dbValue = from_python(value,
1146 self._SO_validatorState)
1147 else:
1148 dbValue = value
1149 to_python = getattr(self, '_SO_to_python_%s' % name, None)
1150 if to_python:
1151 value = to_python(dbValue, self._SO_validatorState)
1152 setattr(self, instanceName(name), value)
1153
1154 self._SO_createValues.update(kw)
1155
1156 for name, value in extra.items():
1157 try:
1158 getattr(self.__class__, name)
1159 except AttributeError:
1160 if name not in self.sqlmeta.columns:
1161 raise TypeError(
1162 "%s.set() got an unexpected keyword argument "
1163 "%s" % (self.__class__.__name__, name))
1164 try:
1165 setattr(self, name, value)
1166 except AttributeError as e:
1167 raise AttributeError('%s (with attribute %r)' % (e, name))
1168
1169 self.sqlmeta.dirty = True
1170 return
1171
1172 self._SO_writeLock.acquire()
1173
1174 try:
1175
1176
1177
1178
1179
1180
1181
1182
1183 toUpdate = {}
1184 for name, value in kw.items():
1185 from_python = getattr(self, '_SO_from_python_%s' % name, None)
1186 if from_python:
1187 dbValue = from_python(value, self._SO_validatorState)
1188 else:
1189 dbValue = value
1190 to_python = getattr(self, '_SO_to_python_%s' % name, None)
1191 if to_python:
1192 value = to_python(dbValue, self._SO_validatorState)
1193 if self.sqlmeta.cacheValues:
1194 setattr(self, instanceName(name), value)
1195 toUpdate[name] = dbValue
1196 for name, value in extra.items():
1197 try:
1198 getattr(self.__class__, name)
1199 except AttributeError:
1200 if name not in self.sqlmeta.columns:
1201 raise TypeError(
1202 "%s.set() got an unexpected keyword argument "
1203 "%s" % (self.__class__.__name__, name))
1204 try:
1205 setattr(self, name, value)
1206 except AttributeError as e:
1207 raise AttributeError('%s (with attribute %r)' % (e, name))
1208
1209 if toUpdate:
1210 toUpdate = sorted(
1211 toUpdate.items(),
1212 key=lambda c: self.sqlmeta.columns[c[0]].creationOrder)
1213 args = [(self.sqlmeta.columns[name].dbName, value)
1214 for name, value in toUpdate]
1215 self._connection._SO_update(self, args)
1216 finally:
1217 self._SO_writeLock.release()
1218
1219 post_funcs = []
1220 self.sqlmeta.send(events.RowUpdatedSignal, self, post_funcs)
1221 for func in post_funcs:
1222 func(self)
1223
1224 def _SO_selectInit(self, row):
1225 for _col, colValue in zip(self.sqlmeta.columnList, row):
1226 if _col.to_python:
1227 colValue = _col.to_python(colValue, self._SO_validatorState)
1228 setattr(self, instanceName(_col.name), colValue)
1229
1230 def _SO_getValue(self, name):
1231
1232 assert not self.sqlmeta._obsolete, (
1233 "%s with id %s has become obsolete"
1234 % (self.__class__.__name__, self.id))
1235
1236
1237 column = self.sqlmeta.columns[name]
1238 results = self._connection._SO_selectOne(self, [column.dbName])
1239
1240 assert results is not None, "%s with id %s is not in the database" % (
1241 self.__class__.__name__, self.id)
1242 value = results[0]
1243 if column.to_python:
1244 value = column.to_python(value, self._SO_validatorState)
1245 return value
1246
1247 def _SO_foreignKey(self, value, joinClass, idName=None):
1248 if value is None:
1249 return None
1250 if self.sqlmeta._perConnection:
1251 connection = self._connection
1252 else:
1253 connection = None
1254 if idName is None:
1255 return joinClass.get(value, connection=connection)
1256 return joinClass.select(
1257 getattr(joinClass.q, idName) == value,
1258 connection=connection).getOne()
1259
1260 def __init__(self, **kw):
1261
1262
1263
1264
1265 try:
1266 _postponed_local.postponed_calls
1267 postponed_created = False
1268 except AttributeError:
1269 _postponed_local.postponed_calls = []
1270 postponed_created = True
1271
1272 try:
1273
1274
1275
1276 self.sqlmeta = self.__class__.sqlmeta(self)
1277
1278
1279
1280 if '_SO_fetch_no_create' in kw:
1281 return
1282
1283 post_funcs = []
1284 self.sqlmeta.send(events.RowCreateSignal, self, kw, post_funcs)
1285
1286
1287 if 'connection' in kw:
1288 connection = kw.pop('connection')
1289 if getattr(self, '_connection', None) is not connection:
1290 self._connection = connection
1291 self.sqlmeta._perConnection = True
1292
1293 self._SO_writeLock = threading.Lock()
1294
1295 if 'id' in kw:
1296 id = self.sqlmeta.idType(kw['id'])
1297 del kw['id']
1298 else:
1299 id = None
1300
1301 self._create(id, **kw)
1302
1303 for func in post_funcs:
1304 func(self)
1305 finally:
1306
1307
1308
1309 if postponed_created:
1310 try:
1311 for func in _postponed_local.postponed_calls:
1312 func()
1313 finally:
1314 del _postponed_local.postponed_calls
1315
1316 def _create(self, id, **kw):
1317
1318 self.sqlmeta._creating = True
1319 self._SO_createValues = {}
1320 self._SO_validatorState = sqlbuilder.SQLObjectState(self)
1321
1322
1323
1324 for column in self.sqlmeta.columnList:
1325
1326
1327
1328 if column.name not in kw and column.foreignName not in kw:
1329 default = column.default
1330
1331
1332
1333 if default is NoDefault:
1334 if column.defaultSQL is None:
1335 raise TypeError(
1336 "%s() did not get expected keyword argument "
1337 "'%s'" % (self.__class__.__name__, column.name))
1338 else:
1339
1340
1341
1342 continue
1343
1344
1345
1346
1347 kw[column.name] = default
1348
1349 self.set(**kw)
1350
1351
1352 self._SO_finishCreate(id)
1353
1354 def _SO_finishCreate(self, id=None):
1355
1356
1357
1358 setters = self._SO_createValues.items()
1359 setters = sorted(
1360 setters, key=lambda c: self.sqlmeta.columns[c[0]].creationOrder)
1361
1362 names = [self.sqlmeta.columns[v[0]].dbName for v in setters]
1363 values = [v[1] for v in setters]
1364
1365
1366
1367 self.sqlmeta.dirty = False
1368 if not self.sqlmeta.lazyUpdate:
1369 del self._SO_createValues
1370 else:
1371 self._SO_createValues = {}
1372 del self.sqlmeta._creating
1373
1374
1375
1376
1377 id = self._connection.queryInsertID(self,
1378 id, names, values)
1379 cache = self._connection.cache
1380 cache.created(id, self.__class__, self)
1381 self._init(id)
1382 post_funcs = []
1383 kw = dict([('class', self.__class__), ('id', id)])
1384
1385 def _send_RowCreatedSignal():
1386 self.sqlmeta.send(events.RowCreatedSignal, self, kw, post_funcs)
1387 for func in post_funcs:
1388 func(self)
1389 _postponed_local.postponed_calls.append(_send_RowCreatedSignal)
1390
1391 def _SO_getID(self, obj, refColumn=None):
1392 return getID(obj, refColumn)
1393
1394 @classmethod
1395 def _findAlternateID(cls, name, dbName, value, connection=None):
1396 if isinstance(name, str):
1397 name = (name,)
1398 value = (value,)
1399 if len(name) != len(value):
1400 raise ValueError(
1401 "'column' and 'value' tuples must be of the same size")
1402 new_value = []
1403 for n, v in zip(name, value):
1404 from_python = getattr(cls, '_SO_from_python_' + n)
1405 if from_python:
1406 v = from_python(
1407 v, sqlbuilder.SQLObjectState(cls, connection=connection))
1408 new_value.append(v)
1409 condition = sqlbuilder.AND(
1410 *[getattr(cls.q, _n) == _v for _n, _v in zip(name, new_value)])
1411 return (connection or cls._connection)._SO_selectOneAlt(
1412 cls,
1413 [cls.sqlmeta.idName] +
1414 [column.dbName for column in cls.sqlmeta.columnList],
1415 condition), None
1416
1417 @classmethod
1418 def _SO_fetchAlternateID(cls, name, dbName, value, connection=None,
1419 idxName=None):
1420 result, obj = cls._findAlternateID(name, dbName, value, connection)
1421 if not result:
1422 if idxName is None:
1423 raise SQLObjectNotFound(
1424 "The %s by alternateID %s = %s does not exist" % (
1425 cls.__name__, name, repr(value)))
1426 else:
1427 names = []
1428 for i in range(len(name)):
1429 names.append("%s = %s" % (name[i], repr(value[i])))
1430 names = ', '.join(names)
1431 raise SQLObjectNotFound(
1432 "The %s by unique index %s(%s) does not exist" % (
1433 cls.__name__, idxName, names))
1434 if obj:
1435 return obj
1436 if connection:
1437 obj = cls.get(result[0], connection=connection,
1438 selectResults=result[1:])
1439 else:
1440 obj = cls.get(result[0], selectResults=result[1:])
1441 return obj
1442
1443 @classmethod
1444 def _SO_depends(cls):
1445 return findDependencies(cls.__name__, cls.sqlmeta.registry)
1446
1447 @classmethod
1448 def select(cls, clause=None, clauseTables=None,
1449 orderBy=NoDefault, limit=None,
1450 lazyColumns=False, reversed=False,
1451 distinct=False, connection=None,
1452 join=None, forUpdate=False):
1453 return cls.SelectResultsClass(cls, clause,
1454 clauseTables=clauseTables,
1455 orderBy=orderBy,
1456 limit=limit,
1457 lazyColumns=lazyColumns,
1458 reversed=reversed,
1459 distinct=distinct,
1460 connection=connection,
1461 join=join, forUpdate=forUpdate)
1462
1463 @classmethod
1464 def selectBy(cls, connection=None, **kw):
1465 conn = connection or cls._connection
1466 return cls.SelectResultsClass(cls,
1467 conn._SO_columnClause(cls, kw),
1468 connection=conn)
1469
1470 @classmethod
1471 def tableExists(cls, connection=None):
1472 conn = connection or cls._connection
1473 return conn.tableExists(cls.sqlmeta.table)
1474
1475 @classmethod
1476 def dropTable(cls, ifExists=False, dropJoinTables=True, cascade=False,
1477 connection=None):
1478 conn = connection or cls._connection
1479 if ifExists and not cls.tableExists(connection=conn):
1480 return
1481 extra_sql = []
1482 post_funcs = []
1483 cls.sqlmeta.send(events.DropTableSignal, cls, connection,
1484 extra_sql, post_funcs)
1485 conn.dropTable(cls.sqlmeta.table, cascade)
1486 if dropJoinTables:
1487 cls.dropJoinTables(ifExists=ifExists, connection=conn)
1488 for sql in extra_sql:
1489 connection.query(sql)
1490 for func in post_funcs:
1491 func(cls, conn)
1492
1493 @classmethod
1494 def createTable(cls, ifNotExists=False, createJoinTables=True,
1495 createIndexes=True, applyConstraints=True,
1496 connection=None):
1497 conn = connection or cls._connection
1498 if ifNotExists and cls.tableExists(connection=conn):
1499 return
1500 extra_sql = []
1501 post_funcs = []
1502 cls.sqlmeta.send(events.CreateTableSignal, cls, connection,
1503 extra_sql, post_funcs)
1504 constraints = conn.createTable(cls)
1505 if applyConstraints:
1506 for constraint in constraints:
1507 conn.query(constraint)
1508 else:
1509 extra_sql.extend(constraints)
1510 if createJoinTables:
1511 cls.createJoinTables(ifNotExists=ifNotExists,
1512 connection=conn)
1513 if createIndexes:
1514 cls.createIndexes(ifNotExists=ifNotExists,
1515 connection=conn)
1516 for func in post_funcs:
1517 func(cls, conn)
1518 return extra_sql
1519
1520 @classmethod
1521 def createTableSQL(cls, createJoinTables=True, createIndexes=True,
1522 connection=None):
1523 conn = connection or cls._connection
1524 sql, constraints = conn.createTableSQL(cls)
1525 if createJoinTables:
1526 join_sql = cls.createJoinTablesSQL(connection=conn)
1527 if join_sql:
1528 sql += ';\n' + join_sql
1529 if createIndexes:
1530 index_sql = cls.createIndexesSQL(connection=conn)
1531 if index_sql:
1532 sql += ';\n' + index_sql
1533 return sql, constraints
1534
1535 @classmethod
1536 def createJoinTables(cls, ifNotExists=False, connection=None):
1537 conn = connection or cls._connection
1538 for join in cls._getJoinsToCreate():
1539 if (ifNotExists and
1540 conn.tableExists(join.intermediateTable)):
1541 continue
1542 conn._SO_createJoinTable(join)
1543
1544 @classmethod
1545 def createJoinTablesSQL(cls, connection=None):
1546 conn = connection or cls._connection
1547 sql = []
1548 for join in cls._getJoinsToCreate():
1549 sql.append(conn._SO_createJoinTableSQL(join))
1550 return ';\n'.join(sql)
1551
1552 @classmethod
1553 def createIndexes(cls, ifNotExists=False, connection=None):
1554 conn = connection or cls._connection
1555 for _index in cls.sqlmeta.indexes:
1556 if not _index:
1557 continue
1558 conn._SO_createIndex(cls, _index)
1559
1560 @classmethod
1561 def createIndexesSQL(cls, connection=None):
1562 conn = connection or cls._connection
1563 sql = []
1564 for _index in cls.sqlmeta.indexes:
1565 if not _index:
1566 continue
1567 sql.append(conn.createIndexSQL(cls, _index))
1568 return ';\n'.join(sql)
1569
1570 @classmethod
1571 def _getJoinsToCreate(cls):
1572 joins = []
1573 for join in cls.sqlmeta.joins:
1574 if not join:
1575 continue
1576 if not join.hasIntermediateTable() or not getattr(join, 'createRelatedTable', True):
1578 continue
1579 if join.soClass.__name__ > join.otherClass.__name__:
1580 continue
1581 joins.append(join)
1582 return joins
1583
1584 @classmethod
1585 def dropJoinTables(cls, ifExists=False, connection=None):
1586 conn = connection or cls._connection
1587 for join in cls.sqlmeta.joins:
1588 if not join:
1589 continue
1590 if not join.hasIntermediateTable() or not getattr(join, 'createRelatedTable', True):
1592 continue
1593 if join.soClass.__name__ > join.otherClass.__name__:
1594 continue
1595 if ifExists and not conn.tableExists(join.intermediateTable):
1597 continue
1598 conn._SO_dropJoinTable(join)
1599
1600 @classmethod
1601 def clearTable(cls, connection=None, clearJoinTables=True):
1602
1603
1604 conn = connection or cls._connection
1605 conn.clearTable(cls.sqlmeta.table)
1606 if clearJoinTables:
1607 for join in cls._getJoinsToCreate():
1608 conn.clearTable(join.intermediateTable)
1609
1610 def destroySelf(self):
1611 post_funcs = []
1612 self.sqlmeta.send(events.RowDestroySignal, self, post_funcs)
1613
1614
1615 klass = self.__class__
1616
1617
1618 for join in klass.sqlmeta.joins:
1619 if isinstance(join, joins.SORelatedJoin):
1620 q = "DELETE FROM %s WHERE %s=%d" % (join.intermediateTable,
1621 join.joinColumn, self.id)
1622 self._connection.query(q)
1623
1624 depends = []
1625 depends = self._SO_depends()
1626 for k in depends:
1627
1628 for join in k.sqlmeta.joins:
1629 if isinstance(join, joins.SORelatedJoin) and join.otherClassName == klass.__name__:
1631 q = "DELETE FROM %s WHERE %s=%d" % (join.intermediateTable,
1632 join.otherColumn,
1633 self.id)
1634 self._connection.query(q)
1635
1636 cols = findDependantColumns(klass.__name__, k)
1637
1638
1639 if len(cols) == 0:
1640 continue
1641
1642 query = []
1643 delete = setnull = restrict = False
1644 for _col in cols:
1645 if _col.cascade is False:
1646
1647 restrict = True
1648 query.append(getattr(k.q, _col.name) == self.id)
1649 if _col.cascade == 'null':
1650 setnull = _col.name
1651 elif _col.cascade:
1652 delete = True
1653 assert delete or setnull or restrict, (
1654 "Class %s depends on %s accoriding to "
1655 "findDependantColumns, but this seems inaccurate"
1656 % (k, klass))
1657 query = sqlbuilder.OR(*query)
1658 results = k.select(query, connection=self._connection)
1659 if restrict:
1660 if results.count():
1661
1662
1663 raise SQLObjectIntegrityError(
1664 "Tried to delete %s::%s but "
1665 "table %s has a restriction against it" %
1666 (klass.__name__, self.id, k.__name__))
1667 else:
1668 for row in results:
1669 if delete:
1670 row.destroySelf()
1671 else:
1672 row.set(**{setnull: None})
1673
1674 self.sqlmeta._obsolete = True
1675 self._connection._SO_delete(self)
1676 self._connection.cache.expire(self.id, self.__class__)
1677
1678 for func in post_funcs:
1679 func(self)
1680
1681 post_funcs = []
1682 self.sqlmeta.send(events.RowDestroyedSignal, self, post_funcs)
1683 for func in post_funcs:
1684 func(self)
1685
1686 @classmethod
1687 def delete(cls, id, connection=None):
1688 obj = cls.get(id, connection=connection)
1689 obj.destroySelf()
1690
1691 @classmethod
1692 def deleteMany(cls, where=NoDefault, connection=None):
1693 conn = connection or cls._connection
1694 conn.query(conn.sqlrepr(sqlbuilder.Delete(cls.sqlmeta.table, where)))
1695
1696 @classmethod
1697 def deleteBy(cls, connection=None, **kw):
1698 conn = connection or cls._connection
1699 conn.query(conn.sqlrepr(sqlbuilder.Delete(
1700 cls.sqlmeta.table, conn._SO_columnClause(cls, kw))))
1701
1702 def __repr__(self):
1703 if not hasattr(self, 'id'):
1704
1705 return '<%s (not initialized)>' % self.__class__.__name__
1706 return '<%s %r %s>' % (self.__class__.__name__,
1708 self.id,
1709 ' '.join(
1710 ['%s=%s' % (name, repr(value))
1711 for name, value in self._reprItems()]))
1712
1713 def __sqlrepr__(self, db):
1714 return str(self.id)
1715
1716 @classmethod
1717 def sqlrepr(cls, value, connection=None):
1718 return (connection or cls._connection).sqlrepr(value)
1719
1720 @classmethod
1721 def coerceID(cls, value):
1722 if isinstance(value, cls):
1723 return value.id
1724 else:
1725 return cls.sqlmeta.idType(value)
1726
1727 def _reprItems(self):
1728 items = []
1729 for _col in self.sqlmeta.columnList:
1730 value = getattr(self, _col.name)
1731 r = repr(value)
1732 if len(r) > 20:
1733 value = r[:17] + "..." + r[-1]
1734 items.append((_col.name, value))
1735 return items
1736
1737 @classmethod
1738 def setConnection(cls, value):
1739 if isinstance(value, string_type):
1740 value = dbconnection.connectionForURI(value)
1741 cls._connection = value
1742
1743 def tablesUsedImmediate(self):
1744 return [self.__class__.q]
1745
1746
1747
1748 def __hash__(self):
1749
1750
1751 return hash((self.__class__.__name__, self.id))
1752
1753
1754
1755 def __eq__(self, other):
1756 if self.__class__ is other.__class__:
1757 if self.id == other.id:
1758 return True
1759 return False
1760
1761 def __ne__(self, other):
1762 return not self.__eq__(other)
1763
1764 def __lt__(self, other):
1765 return NotImplemented
1766
1767 def __le__(self, other):
1768 return NotImplemented
1769
1770 def __gt__(self, other):
1771 return NotImplemented
1772
1773 def __ge__(self, other):
1774 return NotImplemented
1775
1776
1777
1778 def __getstate__(self):
1779 if self.sqlmeta._perConnection:
1780 from pickle import PicklingError
1781 raise PicklingError(
1782 'Cannot pickle an SQLObject instance '
1783 'that has a per-instance connection')
1784 if self.sqlmeta.lazyUpdate and self._SO_createValues:
1785 self.syncUpdate()
1786 d = self.__dict__.copy()
1787 del d['sqlmeta']
1788 del d['_SO_validatorState']
1789 del d['_SO_writeLock']
1790 del d['_SO_createValues']
1791 return d
1792
1793 def __setstate__(self, d):
1794 self.__init__(_SO_fetch_no_create=1)
1795 self._SO_validatorState = sqlbuilder.SQLObjectState(self)
1796 self._SO_writeLock = threading.Lock()
1797 self._SO_createValues = {}
1798 self.__dict__.update(d)
1799 cls = self.__class__
1800 cache = self._connection.cache
1801 if cache.tryGet(self.id, cls) is not None:
1802 raise ValueError(
1803 "Cannot unpickle %s row with id=%s - "
1804 "a different instance with the id already exists "
1805 "in the cache" % (cls.__name__, self.id))
1806 cache.created(self.id, cls, self)
1807
1808
1809def setterName(name):
1810 return '_set_%s' % name
1811
1812
1813def rawSetterName(name):
1814 return '_SO_set_%s' % name
1815
1816
1817def getterName(name):
1818 return '_get_%s' % name
1819
1820
1821def rawGetterName(name):
1822 return '_SO_get_%s' % name
1823
1824
1825def instanceName(name):
1826 return '_SO_val_%s' % name
1827
1828
1829
1830
1831
1832
1833def getID(obj, refColumn=None):
1834 if isinstance(obj, SQLObject):
1835 return getattr(obj, refColumn or 'id')
1836 elif isinstance(obj, int):
1837 return obj
1838 elif isinstance(obj, long):
1839 return int(obj)
1840 elif isinstance(obj, str):
1841 try:
1842 return int(obj)
1843 except ValueError:
1844 return obj
1845 elif obj is None:
1846 return None
1847
1848
1849def getObject(obj, klass):
1850 if isinstance(obj, int):
1851 return klass(obj)
1852 elif isinstance(obj, long):
1853 return klass(int(obj))
1854 elif isinstance(obj, str):
1855 return klass(int(obj))
1856 elif obj is None:
1857 return None
1858 else:
1859 return obj
1860
1861__all__ = [
1862 'NoDefault', 'SQLObject', 'SQLObjectIntegrityError', 'SQLObjectNotFound',
1863 'getID', 'getObject', 'sqlhub', 'sqlmeta',
1864]