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