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