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 with_metaclass, string_type, unicode_type
0045
0046if ((sys.version_info[0] == 2) and (sys.version_info[:3] < (2, 6, 0))) or      ((sys.version_info[0] == 3) and (sys.version_info[:3] < (3, 4, 0))):
0048    raise ImportError("SQLObject requires Python 2.6, 2.7 or 3.4+")
0049
0050if sys.version_info[0] > 2:
0051    # alias for python 3 compatability
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    # This function is used to coerce IDs into the proper format,
0195    # so you should replace it with str, or another function, if you
0196    # aren't using integer IDs
0197    idType = int
0198    style = None
0199    lazyUpdate = False
0200    defaultOrder = None
0201    cacheValues = True
0202    registry = None
0203    fromDatabase = False
0204    # Default is false, but we set it to true for the *instance*
0205    # when necessary: (bad clever? maybe)
0206    expired = False
0207
0208    # This is a mapping from column names to SOCol (or subclass)
0209    # instances:
0210    columns = {}
0211    columnList = []
0212
0213    # This is a mapping from column names to Col (or subclass)
0214    # instances; these objects don't have the logic that the SOCol
0215    # objects do, and are not attached to this class closely.
0216    columnDefinitions = {}
0217
0218    # These are lists of the join and index objects:
0219    indexes = []
0220    indexDefinitions = []
0221    joins = []
0222    joinDefinitions = []
0223
0224    # These attributes shouldn't be shared with superclasses:
0225    _unshared_attributes = ['table', 'columns', 'childName']
0226
0227    # These are internal bookkeeping attributes; the class-level
0228    # definition is a default for the instances, instances will
0229    # reset these values.
0230
0231    # When an object is being created, it has an instance
0232    # variable _creating, which is true.  This way all the
0233    # setters can be captured until the object is complete,
0234    # and then the row is inserted into the database.  Once
0235    # that happens, _creating is deleted from the instance,
0236    # and only the class variable (which is always false) is
0237    # left.
0238    _creating = False
0239    _obsolete = False
0240    # Sometimes an intance is attached to a connection, not
0241    # globally available.  In that case, self.sqlmeta._perConnection
0242    # will be true.  It's false by default:
0243    _perConnection = False
0244
0245    # Inheritance definitions:
0246    parentClass = None  # A reference to the parent class
0247    childClasses = {}  # References to child classes, keyed by childName
0248    childName = None  # Class name for inheritance child object creation
0249
0250    # Does the row require syncing?
0251    dirty = False
0252
0253    # Default encoding for UnicodeCol's
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        # plainSetters are columns that haven't been overridden by the
0285        # user, so we can contact the database directly to set them.
0286        # Note that these can't set these in the SQLObject class
0287        # itself, because they specific to this subclass of SQLObject,
0288        # and cannot be shared among classes.
0289        cls._plainSetters = {}
0290        cls._plainGetters = {}
0291        cls._plainForeignSetters = {}
0292        cls._plainForeignGetters = {}
0293        cls._plainJoinGetters = {}
0294        cls._plainJoinAdders = {}
0295        cls._plainJoinRemovers = {}
0296
0297        # This is a dictionary of columnName: columnObject
0298        # None of these objects can be shared with superclasses
0299        cls.columns = {}
0300        cls.columnList = []
0301        # These, however, can be shared:
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    # Adding special values, like columns and indexes
0310    ############################################################
0311
0312    ########################################
0313    # Column handling
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        # Collect columns from the parent classes to test
0335        # if the column is not in a parent class
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        # A stable-ordered version of the list...
0348        sqlmeta.columnList.append(column)
0349
0350        ###################################################
0351        # Create the getter function(s).  We'll start by
0352        # creating functions like _SO_get_columnName,
0353        # then if there's no function named _get_columnName
0354        # we'll alias that to _SO_get_columnName.  This
0355        # allows a sort of super call, even though there's
0356        # no superclass that defines the database access.
0357        if sqlmeta.cacheValues:
0358            # We create a method here, which is just a function
0359            # that takes "self" as the first argument.
0360            getter = eval(
0361                'lambda self: self._SO_loadValue(%s)' %
0362                repr(instanceName(name)))
0363
0364        else:
0365            # If we aren't caching values, we just call the
0366            # function _SO_getValue, which fetches from the
0367            # database.
0368            getter = eval('lambda self: self._SO_getValue(%s)' % repr(name))
0369        setattr(soClass, rawGetterName(name), getter)
0370
0371        # Here if the _get_columnName method isn't in the
0372        # definition, we add it with the default
0373        # _SO_get_columnName definition.
0374        if not hasattr(soClass, getterName(name)) or (name == 'childName'):
0375            setattr(soClass, getterName(name), getter)
0376            sqlmeta._plainGetters[name] = 1
0377
0378        #################################################
0379        # Create the setter function(s)
0380        # Much like creating the getters, we will create
0381        # _SO_set_columnName methods, and then alias them
0382        # to _set_columnName if the user hasn't defined
0383        # those methods themself.
0384
0385        # @@: This is lame; immutable right now makes it unsettable,
0386        # making the table read-only
0387        if not column.immutable:
0388            # We start by just using the _SO_setValue method
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            # Then do the aliasing
0398            if not hasattr(soClass, setterName(name)) or (name == 'childName'):
0399                setattr(soClass, setterName(name), setter)
0400                # We keep track of setters that haven't been
0401                # overridden, because we can combine these
0402                # set columns into one SQL UPDATE query.
0403                sqlmeta._plainSetters[name] = 1
0404
0405        ##################################################
0406        # Here we check if the column is a foreign key, in
0407        # which case we need to make another method that
0408        # fetches the key and constructs the sister
0409        # SQLObject instance.
0410        if column.foreignKey:
0411
0412            # We go through the standard _SO_get_columnName deal
0413            # we're giving the object, not the ID of the
0414            # object this time:
0415            origName = column.origName
0416            if sqlmeta.cacheValues:
0417                # self._SO_class_className is a reference
0418                # to the class in question.
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                # Same non-caching version as above.
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            # And we set the _get_columnName version
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                # The setter just gets the ID of the object,
0440                # and then sets the real column.
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                           sys.version_info[0] == 2:
0480                    columnDef.name = columnDef.name.encode('ascii')
0481                sqlmeta.addColumn(columnDef)
0482
0483    @classmethod
0484    def delColumn(cls, column, changeSchema=False, connection=None):
0485        sqlmeta = cls
0486        soClass = sqlmeta.soClass
0487        if isinstance(column, str):
0488            if column in sqlmeta.columns:
0489                column = sqlmeta.columns[column]
0490            elif column + 'ID' in sqlmeta.columns:
0491                column = sqlmeta.columns[column + 'ID']
0492            else:
0493                raise ValueError('Unknown column ' + column)
0494        if isinstance(column, col.Col):
0495            for c in sqlmeta.columns.values():
0496                if column is c.columnDef:
0497                    column = c
0498                    break
0499            else:
0500                raise IndexError(
0501                    "Column with definition %r not found" % column)
0502        post_funcs = []
0503        cls.send(events.DeleteColumnSignal, cls.soClass, connection,
0504                 column.name, column, post_funcs)
0505        name = column.name
0506        del sqlmeta.columns[name]
0507        del sqlmeta.columnDefinitions[name]
0508        sqlmeta.columnList.remove(column)
0509        delattr(soClass, rawGetterName(name))
0510        if name in sqlmeta._plainGetters:
0511            delattr(soClass, getterName(name))
0512        delattr(soClass, rawSetterName(name))
0513        if name in sqlmeta._plainSetters:
0514            delattr(soClass, setterName(name))
0515        if column.foreignKey:
0516            delattr(soClass,
0517                    rawGetterName(soClass.sqlmeta.style.
0518                                  instanceIDAttrToAttr(name)))
0519            if name in sqlmeta._plainForeignGetters:
0520                delattr(soClass, getterName(name))
0521            delattr(soClass,
0522                    rawSetterName(soClass.sqlmeta.style.
0523                                  instanceIDAttrToAttr(name)))
0524            if name in sqlmeta._plainForeignSetters:
0525                delattr(soClass, setterName(name))
0526        if column.alternateMethodName:
0527            delattr(soClass, column.alternateMethodName)
0528
0529        if changeSchema:
0530            conn = connection or soClass._connection
0531            conn.delColumn(sqlmeta, column)
0532
0533        if soClass._SO_finishedClassCreation:
0534            unmakeProperties(soClass)
0535            makeProperties(soClass)
0536
0537        for func in post_funcs:
0538            func(soClass, column)
0539
0540    ########################################
0541    # Join handling
0542    ########################################
0543
0544    @classmethod
0545    def addJoin(cls, joinDef):
0546        sqlmeta = cls
0547        soClass = cls.soClass
0548        # The name of the method we'll create.  If it's
0549        # automatically generated, it's generated by the
0550        # join class.
0551        join = joinDef.withClass(soClass)
0552        meth = join.joinMethodName
0553
0554        sqlmeta.joins.append(join)
0555        index = len(sqlmeta.joins) - 1
0556        if joinDef not in sqlmeta.joinDefinitions:
0557            sqlmeta.joinDefinitions.append(joinDef)
0558
0559        # The function fetches the join by index, and
0560        # then lets the join object do the rest of the
0561        # work:
0562        func = eval(
0563            'lambda self: self.sqlmeta.joins[%i].performJoin(self)' % index)
0564
0565        # And we do the standard _SO_get_... _get_... deal
0566        setattr(soClass, rawGetterName(meth), func)
0567        if not hasattr(soClass, getterName(meth)):
0568            setattr(soClass, getterName(meth), func)
0569            sqlmeta._plainJoinGetters[meth] = 1
0570
0571        # Some joins allow you to remove objects from the
0572        # join.
0573        if hasattr(join, 'remove'):
0574            # Again, we let it do the remove, and we do the
0575            # standard naming trick.
0576            func = eval(
0577                'lambda self, obj: self.sqlmeta.joins[%i].remove(self, obj)' %
0578                index)
0579            setattr(soClass, '_SO_remove' + join.addRemoveName, func)
0580            if not hasattr(soClass, 'remove' + join.addRemoveName):
0581                setattr(soClass, 'remove' + join.addRemoveName, func)
0582                sqlmeta._plainJoinRemovers[meth] = 1
0583
0584        # Some joins allow you to add objects.
0585        if hasattr(join, 'add'):
0586            # And again...
0587            func = eval(
0588                'lambda self, obj: self.sqlmeta.joins[%i].add(self, obj)' %
0589                index)
0590            setattr(soClass, '_SO_add' + join.addRemoveName, func)
0591            if not hasattr(soClass, 'add' + join.addRemoveName):
0592                setattr(soClass, 'add' + join.addRemoveName, func)
0593                sqlmeta._plainJoinAdders[meth] = 1
0594
0595        if soClass._SO_finishedClassCreation:
0596            makeProperties(soClass)
0597
0598    @classmethod
0599    def delJoin(sqlmeta, joinDef):
0600        soClass = sqlmeta.soClass
0601        for join in sqlmeta.joins:
0602            # previously deleted joins will be None, so it must
0603            # be skipped or it'll error out on the next line.
0604            if join is None:
0605                continue
0606            if joinDef is join.joinDef:
0607                break
0608        else:
0609            raise IndexError(
0610                "Join %r not found in class %r (from %r)"
0611                % (joinDef, soClass, sqlmeta.joins))
0612        meth = join.joinMethodName
0613        sqlmeta.joinDefinitions.remove(joinDef)
0614        for i in range(len(sqlmeta.joins)):
0615            if sqlmeta.joins[i] is join:
0616                # Have to leave None, because we refer to joins
0617                # by index.
0618                sqlmeta.joins[i] = None
0619        delattr(soClass, rawGetterName(meth))
0620        if meth in sqlmeta._plainJoinGetters:
0621            delattr(soClass, getterName(meth))
0622        if hasattr(join, 'remove'):
0623            delattr(soClass, '_SO_remove' + join.addRemovePrefix)
0624            if meth in sqlmeta._plainJoinRemovers:
0625                delattr(soClass, 'remove' + join.addRemovePrefix)
0626        if hasattr(join, 'add'):
0627            delattr(soClass, '_SO_add' + join.addRemovePrefix)
0628            if meth in sqlmeta._plainJoinAdders:
0629                delattr(soClass, 'add' + join.addRemovePrefix)
0630
0631        if soClass._SO_finishedClassCreation:
0632            unmakeProperties(soClass)
0633            makeProperties(soClass)
0634
0635    ########################################
0636    # Indexes
0637    ########################################
0638
0639    @classmethod
0640    def addIndex(cls, indexDef):
0641        cls.indexDefinitions.append(indexDef)
0642        index = indexDef.withClass(cls.soClass)
0643        cls.indexes.append(index)
0644        setattr(cls.soClass, index.name, index)
0645
0646    ########################################
0647    # Utility methods
0648    ########################################
0649
0650    @classmethod
0651    def getColumns(sqlmeta):
0652        return sqlmeta.columns.copy()
0653
0654    def asDict(self):
0655        """
0656        Return the object as a dictionary of columns to values.
0657        """
0658        result = {}
0659        for key in self.getColumns():
0660            result[key] = getattr(self.instance, key)
0661        result['id'] = self.instance.id
0662        return result
0663
0664    @classmethod
0665    def expireAll(sqlmeta, connection=None):
0666        """
0667        Expire all instances of this class.
0668        """
0669        soClass = sqlmeta.soClass
0670        connection = connection or soClass._connection
0671        cache_set = connection.cache
0672        cache_set.weakrefAll(soClass)
0673        for item in cache_set.getAll(soClass):
0674            item.expire()
0675
0676
0677sqlhub = dbconnection.ConnectionHub()
0678
0679
0680# Turning it on gives earlier warning about things
0681# that will be deprecated (having this off we won't flood people
0682# with warnings right away).
0683warnings_level = 1
0684exception_level = None
0685# Current levels:
0686#  1) Actively deprecated
0687#  2) Deprecated after 1
0688#  3) Deprecated after 2
0689
0690
0691def deprecated(message, level=1, stacklevel=2):
0692    if exception_level is not None and exception_level <= level:
0693        raise NotImplementedError(message)
0694    if warnings_level is not None and warnings_level <= level:
0695        warnings.warn(message, DeprecationWarning, stacklevel=stacklevel)
0696
0697# if sys.version_info[:3] < (2, 5, 0):
0698#     deprecated("Support for Python 2.4 has been declared obsolete "
0699#     "and will be removed in the next release of SQLObject")
0700
0701
0702def setDeprecationLevel(warning=1, exception=None):
0703    """
0704    Set the deprecation level for SQLObject.  Low levels are more
0705    actively being deprecated.  Any warning at a level at or below
0706    ``warning`` will give a warning.  Any warning at a level at or
0707    below ``exception`` will give an exception.  You can use a higher
0708    ``exception`` level for tests to help upgrade your code.  ``None``
0709    for either value means never warn or raise exceptions.
0710
0711    The levels currently mean:
0712
0713      1) Deprecated in current version.  Will be removed in next version.
0714
0715      2) Planned to deprecate in next version, remove later.
0716
0717      3) Planned to deprecate sometime, remove sometime much later.
0718
0719    As the SQLObject versions progress, the deprecation level of
0720    specific features will go down, indicating the advancing nature of
0721    the feature's doom.  We'll try to keep features at 1 for a major
0722    revision.
0723
0724    As time continues there may be a level 0, which will give a useful
0725    error message (better than ``AttributeError``) but where the
0726    feature has been fully removed.
0727    """
0728    global warnings_level, exception_level
0729    warnings_level = warning
0730    exception_level = exception
0731
0732
0733class _sqlmeta_attr(object):
0734
0735    def __init__(self, name, deprecation_level):
0736        self.name = name
0737        self.deprecation_level = deprecation_level
0738
0739    def __get__(self, obj, type=None):
0740        if self.deprecation_level is not None:
0741            deprecated(
0742                'Use of this attribute should be replaced with '
0743                '.sqlmeta.%s' % self.name, level=self.deprecation_level)
0744        return getattr((type or obj).sqlmeta, self.name)
0745
0746
0747_postponed_local = local()
0748
0749
0750# SQLObject is the superclass for all SQLObject classes, of
0751# course.  All the deeper magic is done in MetaSQLObject, and
0752# only lesser magic is done here.  All the actual work is done
0753# here, though -- just automatic method generation (like
0754# methods and properties for each column) is done in
0755# MetaSQLObject.
0756
0757
0758class SQLObject(with_metaclass(declarative.DeclarativeMeta, object)):
0759
0760    _connection = sqlhub
0761
0762    sqlmeta = sqlmeta
0763
0764    # DSM: The _inheritable attribute controls wheter the class can by
0765    # DSM: inherited 'logically' with a foreignKey and a back reference.
0766    _inheritable = False  # Is this class inheritable?
0767    _parent = None  # A reference to the parent instance
0768    childName = None  # Children name (to be able to get a subclass)
0769
0770    # The law of Demeter: the class should not call another classes by name
0771    SelectResultsClass = SelectResults
0772
0773    def __classinit__(cls, new_attrs):
0774
0775        # This is true if we're initializing the SQLObject class,
0776        # instead of a subclass:
0777        is_base = cls.__bases__ == (object,)
0778
0779        cls._SO_setupSqlmeta(new_attrs, is_base)
0780
0781        implicitColumns = _collectAttributes(cls, new_attrs, col.Col)
0782        implicitJoins = _collectAttributes(cls, new_attrs, joins.Join)
0783        implicitIndexes = _collectAttributes(cls, new_attrs,
0784                                             index.DatabaseIndex)
0785
0786        if not is_base:
0787            cls._SO_cleanDeprecatedAttrs(new_attrs)
0788
0789        if '_connection' in new_attrs:
0790            connection = new_attrs['_connection']
0791            del cls._connection
0792            assert 'connection' not in new_attrs
0793        elif 'connection' in new_attrs:
0794            connection = new_attrs['connection']
0795            del cls.connection
0796        else:
0797            connection = None
0798
0799        cls._SO_finishedClassCreation = False
0800
0801        ######################################################
0802        # Set some attributes to their defaults, if necessary.
0803        # First we get the connection:
0804        if not connection and not getattr(cls, '_connection', None):
0805            mod = sys.modules[cls.__module__]
0806            # See if there's a __connection__ global in
0807            # the module, use it if there is.
0808            if hasattr(mod, '__connection__'):
0809                connection = mod.__connection__
0810
0811        # Do not check hasattr(cls, '_connection') here - it is possible
0812        # SQLObject parent class has a connection attribute that came
0813        # from sqlhub, e.g.; check __dict__ only.
0814        if connection and ('_connection' not in cls.__dict__):
0815            cls.setConnection(connection)
0816
0817        sqlmeta = cls.sqlmeta
0818
0819        # We have to check if there are columns in the inherited
0820        # _columns where the attribute has been set to None in this
0821        # class.  If so, then we need to remove that column from
0822        # _columns.
0823        for key in sqlmeta.columnDefinitions.keys():
0824            if (key in new_attrs and new_attrs[key] is None):
0825                del sqlmeta.columnDefinitions[key]
0826
0827        for column in sqlmeta.columnDefinitions.values():
0828            sqlmeta.addColumn(column)
0829
0830        for column in implicitColumns:
0831            sqlmeta.addColumn(column)
0832
0833        # Now the class is in an essentially OK-state, so we can
0834        # set up any magic attributes:
0835        declarative.setup_attributes(cls, new_attrs)
0836
0837        if sqlmeta.fromDatabase:
0838            sqlmeta.addColumnsFromDatabase()
0839
0840        for j in implicitJoins:
0841            sqlmeta.addJoin(j)
0842        for i in implicitIndexes:
0843            sqlmeta.addIndex(i)
0844
0845        def order_getter(o):
0846            return o.creationOrder
0847        sqlmeta.columnList.sort(key=order_getter)
0848        sqlmeta.indexes.sort(key=order_getter)
0849        sqlmeta.indexDefinitions.sort(key=order_getter)
0850        # Joins cannot be sorted because addJoin created accessors
0851        # that remember indexes.
0852        # sqlmeta.joins.sort(key=order_getter)
0853        sqlmeta.joinDefinitions.sort(key=order_getter)
0854
0855        # We don't setup the properties until we're finished with the
0856        # batch adding of all the columns...
0857        cls._notifyFinishClassCreation()
0858        cls._SO_finishedClassCreation = True
0859        makeProperties(cls)
0860
0861        # We use the magic "q" attribute for accessing lazy
0862        # SQL where-clause generation.  See the sql module for
0863        # more.
0864        if not is_base:
0865            cls.q = sqlbuilder.SQLObjectTable(cls)
0866            cls.j = sqlbuilder.SQLObjectTableWithJoins(cls)
0867
0868        classregistry.registry(sqlmeta.registry).addClass(cls)
0869
0870    @classmethod
0871    def _SO_setupSqlmeta(cls, new_attrs, is_base):
0872        """
0873        This fixes up the sqlmeta attribute.  It handles both the case
0874        where no sqlmeta was given (in which we need to create another
0875        subclass), or the sqlmeta given doesn't have the proper
0876        inheritance.  Lastly it calls sqlmeta.setClass, which handles
0877        much of the setup.
0878        """
0879        if ('sqlmeta' not in new_attrs and not is_base):
0880            # We have to create our own subclass, usually.
0881            # type(className, bases_tuple, attr_dict) creates a new subclass.
0882            cls.sqlmeta = type('sqlmeta', (cls.sqlmeta,), {})
0883        if not issubclass(cls.sqlmeta, sqlmeta):
0884            # We allow no superclass and an object superclass, instead
0885            # of inheriting from sqlmeta; but in that case we replace
0886            # the class and just move over its attributes:
0887            assert cls.sqlmeta.__bases__ in ((), (object,)), (
0888                "If you do not inherit your sqlmeta class from "
0889                "sqlobject.sqlmeta, it must not inherit from any other "
0890                "class (your sqlmeta inherits from: %s)"
0891                % cls.sqlmeta.__bases__)
0892            for base in cls.__bases__:
0893                superclass = getattr(base, 'sqlmeta', None)
0894                if superclass:
0895                    break
0896            else:
0897                assert 0, (
0898                    "No sqlmeta class could be found in any superclass "
0899                    "(while fixing up sqlmeta %r inheritance)"
0900                    % cls.sqlmeta)
0901            values = dict(cls.sqlmeta.__dict__)
0902            for key in list(values.keys()):
0903                if key.startswith('__') and key.endswith('__'):
0904                    # Magic values shouldn't be passed through:
0905                    del values[key]
0906            cls.sqlmeta = type('sqlmeta', (superclass,), values)
0907
0908        if not is_base:  # Do not pollute the base sqlmeta class
0909            cls.sqlmeta.setClass(cls)
0910
0911    @classmethod
0912    def _SO_cleanDeprecatedAttrs(cls, new_attrs):
0913        """
0914        This removes attributes on SQLObject subclasses that have
0915        been deprecated; they are moved to the sqlmeta class, and
0916        a deprecation warning is given.
0917        """
0918        for attr in ():
0919            if attr in new_attrs:
0920                deprecated("%r is deprecated and read-only; please do "
0921                           "not use it in your classes until it is fully "
0922                           "deprecated" % attr, level=1, stacklevel=5)
0923
0924    @classmethod
0925    def get(cls, id, connection=None, selectResults=None):
0926
0927        assert id is not None,               'None is not a possible id for %s' % cls.__name__
0929
0930        id = cls.sqlmeta.idType(id)
0931
0932        if connection is None:
0933            cache = cls._connection.cache
0934        else:
0935            cache = connection.cache
0936
0937        # This whole sequence comes from Cache.CacheFactory's
0938        # behavior, where a None returned means a cache miss.
0939        val = cache.get(id, cls)
0940        if val is None:
0941            try:
0942                val = cls(_SO_fetch_no_create=1)
0943                val._SO_validatorState = sqlbuilder.SQLObjectState(val)
0944                val._init(id, connection, selectResults)
0945                cache.put(id, cls, val)
0946            finally:
0947                cache.finishPut(cls)
0948        elif selectResults and not val.sqlmeta.dirty:
0949            val._SO_writeLock.acquire()
0950            try:
0951                val._SO_selectInit(selectResults)
0952                val.sqlmeta.expired = False
0953            finally:
0954                val._SO_writeLock.release()
0955        return val
0956
0957    @classmethod
0958    def _notifyFinishClassCreation(cls):
0959        pass
0960
0961    def _init(self, id, connection=None, selectResults=None):
0962        assert id is not None
0963        # This function gets called only when the object is
0964        # created, unlike __init__ which would be called
0965        # anytime the object was returned from cache.
0966        self.id = id
0967        self._SO_writeLock = threading.Lock()
0968
0969        # If no connection was given, we'll inherit the class
0970        # instance variable which should have a _connection
0971        # attribute.
0972        if (connection is not None) and                   (getattr(self, '_connection', None) is not connection):
0974            self._connection = connection
0975            # Sometimes we need to know if this instance is
0976            # global or tied to a particular connection.
0977            # This flag tells us that:
0978            self.sqlmeta._perConnection = True
0979
0980        if not selectResults:
0981            dbNames = [col.dbName for col in self.sqlmeta.columnList]
0982            selectResults = self._connection._SO_selectOne(self, dbNames)
0983            if not selectResults:
0984                raise SQLObjectNotFound(
0985                    "The object %s by the ID %s does not exist" % (
0986                        self.__class__.__name__, self.id))
0987        self._SO_selectInit(selectResults)
0988        self._SO_createValues = {}
0989        self.sqlmeta.dirty = False
0990
0991    def _SO_loadValue(self, attrName):
0992        try:
0993            return getattr(self, attrName)
0994        except AttributeError:
0995            try:
0996                self._SO_writeLock.acquire()
0997                try:
0998                    # Maybe, just in the moment since we got the lock,
0999                    # some other thread did a _SO_loadValue and we
1000                    # have the attribute!  Let's try and find out!  We
1001                    # can keep trying this all day and still beat the
1002                    # performance on the database call (okay, we can
1003                    # keep trying this for a few msecs at least)...
1004                    result = getattr(self, attrName)
1005                except AttributeError:
1006                    pass
1007                else:
1008                    return result
1009                self.sqlmeta.expired = False
1010                dbNames = [col.dbName for col in self.sqlmeta.columnList]
1011                selectResults = self._connection._SO_selectOne(self, dbNames)
1012                if not selectResults:
1013                    raise SQLObjectNotFound(
1014                        "The object %s by the ID %s has been deleted" % (
1015                            self.__class__.__name__, self.id))
1016                self._SO_selectInit(selectResults)
1017                result = getattr(self, attrName)
1018                return result
1019            finally:
1020                self._SO_writeLock.release()
1021
1022    def sync(self):
1023        if self.sqlmeta.lazyUpdate and self._SO_createValues:
1024            self.syncUpdate()
1025        self._SO_writeLock.acquire()
1026        try:
1027            dbNames = [col.dbName for col in self.sqlmeta.columnList]
1028            selectResults = self._connection._SO_selectOne(self, dbNames)
1029            if not selectResults:
1030                raise SQLObjectNotFound(
1031                    "The object %s by the ID %s has been deleted" % (
1032                        self.__class__.__name__, self.id))
1033            self._SO_selectInit(selectResults)
1034            self.sqlmeta.expired = False
1035        finally:
1036            self._SO_writeLock.release()
1037
1038    def syncUpdate(self):
1039        if not self._SO_createValues:
1040            return
1041        self._SO_writeLock.acquire()
1042        try:
1043            if self.sqlmeta.columns:
1044                columns = self.sqlmeta.columns
1045                values = [(columns[v[0]].dbName, v[1])
1046                          for v in sorted(
1047                              self._SO_createValues.items(),
1048                              key=lambda c: columns[c[0]].creationOrder)]
1049                self._connection._SO_update(self, values)
1050            self.sqlmeta.dirty = False
1051            self._SO_createValues = {}
1052        finally:
1053            self._SO_writeLock.release()
1054
1055        post_funcs = []
1056        self.sqlmeta.send(events.RowUpdatedSignal, self, post_funcs)
1057        for func in post_funcs:
1058            func(self)
1059
1060    def expire(self):
1061        if self.sqlmeta.expired:
1062            return
1063        self._SO_writeLock.acquire()
1064        try:
1065            if self.sqlmeta.expired:
1066                return
1067            for column in self.sqlmeta.columnList:
1068                delattr(self, instanceName(column.name))
1069            self.sqlmeta.expired = True
1070            self._connection.cache.expire(self.id, self.__class__)
1071            self._SO_createValues = {}
1072        finally:
1073            self._SO_writeLock.release()
1074
1075    def _SO_setValue(self, name, value, from_python, to_python):
1076        # This is the place where we actually update the
1077        # database.
1078
1079        # If we are _creating, the object doesn't yet exist
1080        # in the database, and we can't insert it until all
1081        # the parts are set.  So we just keep them in a
1082        # dictionary until later:
1083        d = {name: value}
1084        if not self.sqlmeta._creating and                   not getattr(self.sqlmeta, "row_update_sig_suppress", False):
1086            self.sqlmeta.send(events.RowUpdateSignal, self, d)
1087        if len(d) != 1 or name not in d:
1088            # Already called RowUpdateSignal, don't call it again
1089            # inside .set()
1090            self.sqlmeta.row_update_sig_suppress = True
1091            self.set(**d)
1092            del self.sqlmeta.row_update_sig_suppress
1093        value = d[name]
1094        if from_python:
1095            dbValue = from_python(value, self._SO_validatorState)
1096        else:
1097            dbValue = value
1098        if to_python:
1099            value = to_python(dbValue, self._SO_validatorState)
1100        if self.sqlmeta._creating or self.sqlmeta.lazyUpdate:
1101            self.sqlmeta.dirty = True
1102            self._SO_createValues[name] = dbValue
1103            setattr(self, instanceName(name), value)
1104            return
1105
1106        self._connection._SO_update(
1107            self, [(self.sqlmeta.columns[name].dbName,
1108                    dbValue)])
1109
1110        if self.sqlmeta.cacheValues:
1111            setattr(self, instanceName(name), value)
1112
1113        post_funcs = []
1114        self.sqlmeta.send(events.RowUpdatedSignal, self, post_funcs)
1115        for func in post_funcs:
1116            func(self)
1117
1118    def set(self, _suppress_set_sig=False, **kw):
1119        if not self.sqlmeta._creating and                   not getattr(self.sqlmeta, "row_update_sig_suppress", False)                   and not _suppress_set_sig:
1122            self.sqlmeta.send(events.RowUpdateSignal, self, kw)
1123        # set() is used to update multiple values at once,
1124        # potentially with one SQL statement if possible.
1125
1126        # Filter out items that don't map to column names.
1127        # Those will be set directly on the object using
1128        # setattr(obj, name, value).
1129        def is_column(_c):
1130            return _c in self.sqlmeta._plainSetters
1131
1132        def f_is_column(item):
1133            return is_column(item[0])
1134
1135        def f_not_column(item):
1136            return not is_column(item[0])
1137        items = kw.items()
1138        extra = dict(filter(f_not_column, items))
1139        kw = dict(filter(f_is_column, items))
1140
1141        # _creating is special, see _SO_setValue
1142        if self.sqlmeta._creating or self.sqlmeta.lazyUpdate:
1143            for name, value in kw.items():
1144                from_python = getattr(self, '_SO_from_python_%s' % name, None)
1145                if from_python:
1146                    kw[name] = dbValue = from_python(value,
1147                                                     self._SO_validatorState)
1148                else:
1149                    dbValue = value
1150                to_python = getattr(self, '_SO_to_python_%s' % name, None)
1151                if to_python:
1152                    value = to_python(dbValue, self._SO_validatorState)
1153                setattr(self, instanceName(name), value)
1154
1155            self._SO_createValues.update(kw)
1156
1157            for name, value in extra.items():
1158                try:
1159                    getattr(self.__class__, name)
1160                except AttributeError:
1161                    if name not in self.sqlmeta.columns:
1162                        raise TypeError(
1163                            "%s.set() got an unexpected keyword argument "
1164                            "%s" % (self.__class__.__name__, name))
1165                try:
1166                    setattr(self, name, value)
1167                except AttributeError as e:
1168                    raise AttributeError('%s (with attribute %r)' % (e, name))
1169
1170            self.sqlmeta.dirty = True
1171            return
1172
1173        self._SO_writeLock.acquire()
1174
1175        try:
1176            # We have to go through and see if the setters are
1177            # "plain", that is, if the user has changed their
1178            # definition in any way (put in something that
1179            # normalizes the value or checks for consistency,
1180            # for instance).  If so then we have to use plain
1181            # old setattr() to change the value, since we can't
1182            # read the user's mind.  We'll combine everything
1183            # else into a single UPDATE, if necessary.
1184            toUpdate = {}
1185            for name, value in kw.items():
1186                from_python = getattr(self, '_SO_from_python_%s' % name, None)
1187                if from_python:
1188                    dbValue = from_python(value, self._SO_validatorState)
1189                else:
1190                    dbValue = value
1191                to_python = getattr(self, '_SO_to_python_%s' % name, None)
1192                if to_python:
1193                    value = to_python(dbValue, self._SO_validatorState)
1194                if self.sqlmeta.cacheValues:
1195                    setattr(self, instanceName(name), value)
1196                toUpdate[name] = dbValue
1197            for name, value in extra.items():
1198                try:
1199                    getattr(self.__class__, name)
1200                except AttributeError:
1201                    if name not in self.sqlmeta.columns:
1202                        raise TypeError(
1203                            "%s.set() got an unexpected keyword argument "
1204                            "%s" % (self.__class__.__name__, name))
1205                try:
1206                    setattr(self, name, value)
1207                except AttributeError as e:
1208                    raise AttributeError('%s (with attribute %r)' % (e, name))
1209
1210            if toUpdate:
1211                toUpdate = sorted(
1212                    toUpdate.items(),
1213                    key=lambda c: self.sqlmeta.columns[c[0]].creationOrder)
1214                args = [(self.sqlmeta.columns[name].dbName, value)
1215                        for name, value in toUpdate]
1216                self._connection._SO_update(self, args)
1217        finally:
1218            self._SO_writeLock.release()
1219
1220        post_funcs = []
1221        self.sqlmeta.send(events.RowUpdatedSignal, self, post_funcs)
1222        for func in post_funcs:
1223            func(self)
1224
1225    def _SO_selectInit(self, row):
1226        for _col, colValue in zip(self.sqlmeta.columnList, row):
1227            if _col.to_python:
1228                colValue = _col.to_python(colValue, self._SO_validatorState)
1229            setattr(self, instanceName(_col.name), colValue)
1230
1231    def _SO_getValue(self, name):
1232        # Retrieves a single value from the database.  Simple.
1233        assert not self.sqlmeta._obsolete, (
1234            "%s with id %s has become obsolete"
1235            % (self.__class__.__name__, self.id))
1236        # @@: do we really need this lock?
1237        # self._SO_writeLock.acquire()
1238        column = self.sqlmeta.columns[name]
1239        results = self._connection._SO_selectOne(self, [column.dbName])
1240        # self._SO_writeLock.release()
1241        assert results is not None, "%s with id %s is not in the database" % (
1242            self.__class__.__name__, self.id)
1243        value = results[0]
1244        if column.to_python:
1245            value = column.to_python(value, self._SO_validatorState)
1246        return value
1247
1248    def _SO_foreignKey(self, value, joinClass, idName=None):
1249        if value is None:
1250            return None
1251        if self.sqlmeta._perConnection:
1252            connection = self._connection
1253        else:
1254            connection = None
1255        if idName is None:  # Get by id
1256            return joinClass.get(value, connection=connection)
1257        return joinClass.select(
1258            getattr(joinClass.q, idName) == value,
1259            connection=connection).getOne()
1260
1261    def __init__(self, **kw):
1262        # If we are the outmost constructor of a hiearchy of
1263        # InheritableSQLObjects (or simlpy _the_ constructor of a "normal"
1264        # SQLObject), we create a threadlocal list that collects the
1265        # RowCreatedSignals, and executes them if this very constructor is left
1266        try:
1267            _postponed_local.postponed_calls
1268            postponed_created = False
1269        except AttributeError:
1270            _postponed_local.postponed_calls = []
1271            postponed_created = True
1272
1273        try:
1274            # We shadow the sqlmeta class with an instance of sqlmeta
1275            # that points to us (our sqlmeta buddy object; where the
1276            # sqlmeta class is our class's buddy class)
1277            self.sqlmeta = self.__class__.sqlmeta(self)
1278            # The get() classmethod/constructor uses a magic keyword
1279            # argument when it wants an empty object, fetched from the
1280            # database.  So we have nothing more to do in that case:
1281            if '_SO_fetch_no_create' in kw:
1282                return
1283
1284            post_funcs = []
1285            self.sqlmeta.send(events.RowCreateSignal, self, kw, post_funcs)
1286
1287            # Pass the connection object along if we were given one.
1288            if 'connection' in kw:
1289                connection = kw.pop('connection')
1290                if getattr(self, '_connection', None) is not connection:
1291                    self._connection = connection
1292                    self.sqlmeta._perConnection = True
1293
1294            self._SO_writeLock = threading.Lock()
1295
1296            if 'id' in kw:
1297                id = self.sqlmeta.idType(kw['id'])
1298                del kw['id']
1299            else:
1300                id = None
1301
1302            self._create(id, **kw)
1303
1304            for func in post_funcs:
1305                func(self)
1306        finally:
1307            # if we are the creator of the tl-storage, we
1308            # have to exectute and under all circumstances
1309            # remove the tl-storage
1310            if postponed_created:
1311                try:
1312                    for func in _postponed_local.postponed_calls:
1313                        func()
1314                finally:
1315                    del _postponed_local.postponed_calls
1316
1317    def _create(self, id, **kw):
1318
1319        self.sqlmeta._creating = True
1320        self._SO_createValues = {}
1321        self._SO_validatorState = sqlbuilder.SQLObjectState(self)
1322
1323        # First we do a little fix-up on the keywords we were
1324        # passed:
1325        for column in self.sqlmeta.columnList:
1326
1327            # Then we check if the column wasn't passed in, and
1328            # if not we try to get the default.
1329            if column.name not in kw and column.foreignName not in kw:
1330                default = column.default
1331
1332                # If we don't get it, it's an error:
1333                # If we specified an SQL DEFAULT, then we should use that
1334                if default is NoDefault:
1335                    if column.defaultSQL is None:
1336                        raise TypeError(
1337                            "%s() did not get expected keyword argument "
1338                            "'%s'" % (self.__class__.__name__, column.name))
1339                    else:
1340                        # There is defaultSQL for the column -
1341                        # do not put the column to kw
1342                        # so that the backend creates the value.
1343                        continue
1344
1345                # Otherwise we put it in as though they did pass
1346                # that keyword:
1347
1348                kw[column.name] = default
1349
1350        self.set(**kw)
1351
1352        # Then we finalize the process:
1353        self._SO_finishCreate(id)
1354
1355    def _SO_finishCreate(self, id=None):
1356        # Here's where an INSERT is finalized.
1357        # These are all the column values that were supposed
1358        # to be set, but were delayed until now:
1359        setters = self._SO_createValues.items()
1360        setters = sorted(
1361            setters, key=lambda c: self.sqlmeta.columns[c[0]].creationOrder)
1362        # Here's their database names:
1363        names = [self.sqlmeta.columns[v[0]].dbName for v in setters]
1364        values = [v[1] for v in setters]
1365        # Get rid of _SO_create*, we aren't creating anymore.
1366        # Doesn't have to be threadsafe because we're still in
1367        # new(), which doesn't need to be threadsafe.
1368        self.sqlmeta.dirty = False
1369        if not self.sqlmeta.lazyUpdate:
1370            del self._SO_createValues
1371        else:
1372            self._SO_createValues = {}
1373        del self.sqlmeta._creating
1374
1375        # Do the insert -- most of the SQL in this case is left
1376        # up to DBConnection, since getting a new ID is
1377        # non-standard.
1378        id = self._connection.queryInsertID(self,
1379                                            id, names, values)
1380        cache = self._connection.cache
1381        cache.created(id, self.__class__, self)
1382        self._init(id)
1383        post_funcs = []
1384        kw = dict([('class', self.__class__), ('id', id)])
1385
1386        def _send_RowCreatedSignal():
1387            self.sqlmeta.send(events.RowCreatedSignal, self, kw, post_funcs)
1388            for func in post_funcs:
1389                func(self)
1390        _postponed_local.postponed_calls.append(_send_RowCreatedSignal)
1391
1392    def _SO_getID(self, obj, refColumn=None):
1393        return getID(obj, refColumn)
1394
1395    @classmethod
1396    def _findAlternateID(cls, name, dbName, value, connection=None):
1397        if isinstance(name, str):
1398            name = (name,)
1399            value = (value,)
1400        if len(name) != len(value):
1401            raise ValueError(
1402                "'column' and 'value' tuples must be of the same size")
1403        new_value = []
1404        for n, v in zip(name, value):
1405            from_python = getattr(cls, '_SO_from_python_' + n)
1406            if from_python:
1407                v = from_python(
1408                    v, sqlbuilder.SQLObjectState(cls, connection=connection))
1409            new_value.append(v)
1410        condition = sqlbuilder.AND(
1411            *[getattr(cls.q, _n) == _v for _n, _v in zip(name, new_value)])
1412        return (connection or cls._connection)._SO_selectOneAlt(
1413            cls,
1414            [cls.sqlmeta.idName] +
1415            [column.dbName for column in cls.sqlmeta.columnList],
1416            condition), None
1417
1418    @classmethod
1419    def _SO_fetchAlternateID(cls, name, dbName, value, connection=None,
1420                             idxName=None):
1421        result, obj = cls._findAlternateID(name, dbName, value, connection)
1422        if not result:
1423            if idxName is None:
1424                raise SQLObjectNotFound(
1425                    "The %s by alternateID %s = %s does not exist" % (
1426                        cls.__name__, name, repr(value)))
1427            else:
1428                names = []
1429                for i in range(len(name)):
1430                    names.append("%s = %s" % (name[i], repr(value[i])))
1431                names = ', '.join(names)
1432                raise SQLObjectNotFound(
1433                    "The %s by unique index %s(%s) does not exist" % (
1434                        cls.__name__, idxName, names))
1435        if obj:
1436            return obj
1437        if connection:
1438            obj = cls.get(result[0], connection=connection,
1439                          selectResults=result[1:])
1440        else:
1441            obj = cls.get(result[0], selectResults=result[1:])
1442        return obj
1443
1444    @classmethod
1445    def _SO_depends(cls):
1446        return findDependencies(cls.__name__, cls.sqlmeta.registry)
1447
1448    @classmethod
1449    def select(cls, clause=None, clauseTables=None,
1450               orderBy=NoDefault, limit=None,
1451               lazyColumns=False, reversed=False,
1452               distinct=False, connection=None,
1453               join=None, forUpdate=False):
1454        return cls.SelectResultsClass(cls, clause,
1455                                      clauseTables=clauseTables,
1456                                      orderBy=orderBy,
1457                                      limit=limit,
1458                                      lazyColumns=lazyColumns,
1459                                      reversed=reversed,
1460                                      distinct=distinct,
1461                                      connection=connection,
1462                                      join=join, forUpdate=forUpdate)
1463
1464    @classmethod
1465    def selectBy(cls, connection=None, **kw):
1466        conn = connection or cls._connection
1467        return cls.SelectResultsClass(cls,
1468                                      conn._SO_columnClause(cls, kw),
1469                                      connection=conn)
1470
1471    @classmethod
1472    def tableExists(cls, connection=None):
1473        conn = connection or cls._connection
1474        return conn.tableExists(cls.sqlmeta.table)
1475
1476    @classmethod
1477    def dropTable(cls, ifExists=False, dropJoinTables=True, cascade=False,
1478                  connection=None):
1479        conn = connection or cls._connection
1480        if ifExists and not cls.tableExists(connection=conn):
1481            return
1482        extra_sql = []
1483        post_funcs = []
1484        cls.sqlmeta.send(events.DropTableSignal, cls, connection,
1485                         extra_sql, post_funcs)
1486        conn.dropTable(cls.sqlmeta.table, cascade)
1487        if dropJoinTables:
1488            cls.dropJoinTables(ifExists=ifExists, connection=conn)
1489        for sql in extra_sql:
1490            connection.query(sql)
1491        for func in post_funcs:
1492            func(cls, conn)
1493
1494    @classmethod
1495    def createTable(cls, ifNotExists=False, createJoinTables=True,
1496                    createIndexes=True, applyConstraints=True,
1497                    connection=None):
1498        conn = connection or cls._connection
1499        if ifNotExists and cls.tableExists(connection=conn):
1500            return
1501        extra_sql = []
1502        post_funcs = []
1503        cls.sqlmeta.send(events.CreateTableSignal, cls, connection,
1504                         extra_sql, post_funcs)
1505        constraints = conn.createTable(cls)
1506        if applyConstraints:
1507            for constraint in constraints:
1508                conn.query(constraint)
1509        else:
1510            extra_sql.extend(constraints)
1511        if createJoinTables:
1512            cls.createJoinTables(ifNotExists=ifNotExists,
1513                                 connection=conn)
1514        if createIndexes:
1515            cls.createIndexes(ifNotExists=ifNotExists,
1516                              connection=conn)
1517        for func in post_funcs:
1518            func(cls, conn)
1519        return extra_sql
1520
1521    @classmethod
1522    def createTableSQL(cls, createJoinTables=True, createIndexes=True,
1523                       connection=None):
1524        conn = connection or cls._connection
1525        sql, constraints = conn.createTableSQL(cls)
1526        if createJoinTables:
1527            join_sql = cls.createJoinTablesSQL(connection=conn)
1528            if join_sql:
1529                sql += ';\n' + join_sql
1530        if createIndexes:
1531            index_sql = cls.createIndexesSQL(connection=conn)
1532            if index_sql:
1533                sql += ';\n' + index_sql
1534        return sql, constraints
1535
1536    @classmethod
1537    def createJoinTables(cls, ifNotExists=False, connection=None):
1538        conn = connection or cls._connection
1539        for join in cls._getJoinsToCreate():
1540            if (ifNotExists and
1541                    conn.tableExists(join.intermediateTable)):
1542                continue
1543            conn._SO_createJoinTable(join)
1544
1545    @classmethod
1546    def createJoinTablesSQL(cls, connection=None):
1547        conn = connection or cls._connection
1548        sql = []
1549        for join in cls._getJoinsToCreate():
1550            sql.append(conn._SO_createJoinTableSQL(join))
1551        return ';\n'.join(sql)
1552
1553    @classmethod
1554    def createIndexes(cls, ifNotExists=False, connection=None):
1555        conn = connection or cls._connection
1556        for _index in cls.sqlmeta.indexes:
1557            if not _index:
1558                continue
1559            conn._SO_createIndex(cls, _index)
1560
1561    @classmethod
1562    def createIndexesSQL(cls, connection=None):
1563        conn = connection or cls._connection
1564        sql = []
1565        for _index in cls.sqlmeta.indexes:
1566            if not _index:
1567                continue
1568            sql.append(conn.createIndexSQL(cls, _index))
1569        return ';\n'.join(sql)
1570
1571    @classmethod
1572    def _getJoinsToCreate(cls):
1573        joins = []
1574        for join in cls.sqlmeta.joins:
1575            if not join:
1576                continue
1577            if not join.hasIntermediateTable() or                       not getattr(join, 'createRelatedTable', True):
1579                continue
1580            if join.soClass.__name__ > join.otherClass.__name__:
1581                continue
1582            joins.append(join)
1583        return joins
1584
1585    @classmethod
1586    def dropJoinTables(cls, ifExists=False, connection=None):
1587        conn = connection or cls._connection
1588        for join in cls.sqlmeta.joins:
1589            if not join:
1590                continue
1591            if not join.hasIntermediateTable() or                       not getattr(join, 'createRelatedTable', True):
1593                continue
1594            if join.soClass.__name__ > join.otherClass.__name__:
1595                continue
1596            if ifExists and                  not conn.tableExists(join.intermediateTable):
1598                continue
1599            conn._SO_dropJoinTable(join)
1600
1601    @classmethod
1602    def clearTable(cls, connection=None, clearJoinTables=True):
1603        # 3-03 @@: Maybe this should check the cache... but it's
1604        # kind of crude anyway, so...
1605        conn = connection or cls._connection
1606        conn.clearTable(cls.sqlmeta.table)
1607        if clearJoinTables:
1608            for join in cls._getJoinsToCreate():
1609                conn.clearTable(join.intermediateTable)
1610
1611    def destroySelf(self):
1612        post_funcs = []
1613        self.sqlmeta.send(events.RowDestroySignal, self, post_funcs)
1614        # Kills this object.  Kills it dead!
1615
1616        klass = self.__class__
1617
1618        # Free related joins on the base class
1619        for join in klass.sqlmeta.joins:
1620            if isinstance(join, joins.SORelatedJoin):
1621                q = "DELETE FROM %s WHERE %s=%d" % (join.intermediateTable,
1622                                                    join.joinColumn, self.id)
1623                self._connection.query(q)
1624
1625        depends = []
1626        depends = self._SO_depends()
1627        for k in depends:
1628            # Free related joins
1629            for join in k.sqlmeta.joins:
1630                if isinstance(join, joins.SORelatedJoin) and                           join.otherClassName == klass.__name__:
1632                    q = "DELETE FROM %s WHERE %s=%d" % (join.intermediateTable,
1633                                                        join.otherColumn,
1634                                                        self.id)
1635                    self._connection.query(q)
1636
1637            cols = findDependantColumns(klass.__name__, k)
1638
1639            # Don't confuse the rest of the process
1640            if len(cols) == 0:
1641                continue
1642
1643            query = []
1644            delete = setnull = restrict = False
1645            for _col in cols:
1646                if _col.cascade is False:
1647                    # Found a restriction
1648                    restrict = True
1649                query.append(getattr(k.q, _col.name) == self.id)
1650                if _col.cascade == 'null':
1651                    setnull = _col.name
1652                elif _col.cascade:
1653                    delete = True
1654            assert delete or setnull or restrict, (
1655                "Class %s depends on %s accoriding to "
1656                "findDependantColumns, but this seems inaccurate"
1657                % (k, klass))
1658            query = sqlbuilder.OR(*query)
1659            results = k.select(query, connection=self._connection)
1660            if restrict:
1661                if results.count():
1662                    # Restrictions only apply if there are
1663                    # matching records on the related table
1664                    raise SQLObjectIntegrityError(
1665                        "Tried to delete %s::%s but "
1666                        "table %s has a restriction against it" %
1667                        (klass.__name__, self.id, k.__name__))
1668            else:
1669                for row in results:
1670                    if delete:
1671                        row.destroySelf()
1672                    else:
1673                        row.set(**{setnull: None})
1674
1675        self.sqlmeta._obsolete = True
1676        self._connection._SO_delete(self)
1677        self._connection.cache.expire(self.id, self.__class__)
1678
1679        for func in post_funcs:
1680            func(self)
1681
1682        post_funcs = []
1683        self.sqlmeta.send(events.RowDestroyedSignal, self, post_funcs)
1684        for func in post_funcs:
1685            func(self)
1686
1687    @classmethod
1688    def delete(cls, id, connection=None):
1689        obj = cls.get(id, connection=connection)
1690        obj.destroySelf()
1691
1692    @classmethod
1693    def deleteMany(cls, where=NoDefault, connection=None):
1694        conn = connection or cls._connection
1695        conn.query(conn.sqlrepr(sqlbuilder.Delete(cls.sqlmeta.table, where)))
1696
1697    @classmethod
1698    def deleteBy(cls, connection=None, **kw):
1699        conn = connection or cls._connection
1700        conn.query(conn.sqlrepr(sqlbuilder.Delete(
1701            cls.sqlmeta.table, conn._SO_columnClause(cls, kw))))
1702
1703    def __repr__(self):
1704        if not hasattr(self, 'id'):
1705            # Object initialization not finished.  No attributes can be read.
1706            return '<%s (not initialized)>' % self.__class__.__name__
1707        return '<%s %r %s>'                  % (self.__class__.__name__,
1709                  self.id,
1710                  ' '.join(
1711                      ['%s=%s' % (name, repr(value))
1712                       for name, value in self._reprItems()]))
1713
1714    def __sqlrepr__(self, db):
1715        return str(self.id)
1716
1717    @classmethod
1718    def sqlrepr(cls, value, connection=None):
1719        return (connection or cls._connection).sqlrepr(value)
1720
1721    @classmethod
1722    def coerceID(cls, value):
1723        if isinstance(value, cls):
1724            return value.id
1725        else:
1726            return cls.sqlmeta.idType(value)
1727
1728    def _reprItems(self):
1729        items = []
1730        for _col in self.sqlmeta.columnList:
1731            value = getattr(self, _col.name)
1732            r = repr(value)
1733            if len(r) > 20:
1734                value = r[:17] + "..." + r[-1]
1735            items.append((_col.name, value))
1736        return items
1737
1738    @classmethod
1739    def setConnection(cls, value):
1740        if isinstance(value, string_type):
1741            value = dbconnection.connectionForURI(value)
1742        cls._connection = value
1743
1744    def tablesUsedImmediate(self):
1745        return [self.__class__.q]
1746
1747    # Comparison
1748
1749    def __eq__(self, other):
1750        if self.__class__ is other.__class__:
1751            if self.id == other.id:
1752                return True
1753        return False
1754
1755    def __ne__(self, other):
1756        return not self.__eq__(other)
1757
1758    def __lt__(self, other):
1759        return NotImplemented
1760
1761    def __le__(self, other):
1762        return NotImplemented
1763
1764    def __gt__(self, other):
1765        return NotImplemented
1766
1767    def __ge__(self, other):
1768        return NotImplemented
1769
1770    # (De)serialization (pickle, etc.)
1771
1772    def __getstate__(self):
1773        if self.sqlmeta._perConnection:
1774            from pickle import PicklingError
1775            raise PicklingError(
1776                'Cannot pickle an SQLObject instance '
1777                'that has a per-instance connection')
1778        if self.sqlmeta.lazyUpdate and self._SO_createValues:
1779            self.syncUpdate()
1780        d = self.__dict__.copy()
1781        del d['sqlmeta']
1782        del d['_SO_validatorState']
1783        del d['_SO_writeLock']
1784        del d['_SO_createValues']
1785        return d
1786
1787    def __setstate__(self, d):
1788        self.__init__(_SO_fetch_no_create=1)
1789        self._SO_validatorState = sqlbuilder.SQLObjectState(self)
1790        self._SO_writeLock = threading.Lock()
1791        self._SO_createValues = {}
1792        self.__dict__.update(d)
1793        cls = self.__class__
1794        cache = self._connection.cache
1795        if cache.tryGet(self.id, cls) is not None:
1796            raise ValueError(
1797                "Cannot unpickle %s row with id=%s - "
1798                "a different instance with the id already exists "
1799                "in the cache" % (cls.__name__, self.id))
1800        cache.created(self.id, cls, self)
1801
1802
1803def setterName(name):
1804    return '_set_%s' % name
1805
1806
1807def rawSetterName(name):
1808    return '_SO_set_%s' % name
1809
1810
1811def getterName(name):
1812    return '_get_%s' % name
1813
1814
1815def rawGetterName(name):
1816    return '_SO_get_%s' % name
1817
1818
1819def instanceName(name):
1820    return '_SO_val_%s' % name
1821
1822
1823########################################
1824# Utility functions (for external consumption)
1825########################################
1826
1827def getID(obj, refColumn=None):
1828    if isinstance(obj, SQLObject):
1829        return getattr(obj, refColumn or 'id')
1830    elif isinstance(obj, int):
1831        return obj
1832    elif isinstance(obj, long):
1833        return int(obj)
1834    elif isinstance(obj, str):
1835        try:
1836            return int(obj)
1837        except ValueError:
1838            return obj
1839    elif obj is None:
1840        return None
1841
1842
1843def getObject(obj, klass):
1844    if isinstance(obj, int):
1845        return klass(obj)
1846    elif isinstance(obj, long):
1847        return klass(int(obj))
1848    elif isinstance(obj, str):
1849        return klass(int(obj))
1850    elif obj is None:
1851        return None
1852    else:
1853        return obj
1854
1855__all__ = [
1856    'NoDefault', 'SQLObject', 'SQLObjectIntegrityError', 'SQLObjectNotFound',
1857    'getID', 'getObject', 'sqlhub', 'sqlmeta',
1858]