~vcs-imports/sqlobject/trunk

« back to all changes in this revision

Viewing changes to sqlobject/main.py

  • Committer: ianb
  • Date: 2004-02-05 03:29:19 UTC
  • Revision ID: svn-v4:95a46c32-92d2-0310-94a5-8d71aeb3d4b3:trunk/SQLObject:1
Initial import of SQLObject and DataTest

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""
 
2
SQLObject.py
 
3
    Ian Bicking <ianb@colorstudy.com> 17 Oct 2002
 
4
SQLObject is a object-relational mapper.  See SQLObject.html or
 
5
SQLObject.txt for more.
 
6
 
 
7
This program is free software; you can redistribute it and/or modify
 
8
it under the terms of the GNU Lesser General Public License as
 
9
published by the Free Software Foundation; either version 2.1 of the
 
10
License, or (at your option) any later version.
 
11
 
 
12
This program is distributed in the hope that it will be useful,
 
13
but WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
15
GNU General Public License for more details.
 
16
 
 
17
You should have received a copy of the GNU Lesser General Public
 
18
License along with this program; if not, write to the Free Software
 
19
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
 
20
USA.
 
21
"""
 
22
 
 
23
import threading
 
24
import SQLBuilder
 
25
import DBConnection
 
26
import Col
 
27
import Style
 
28
import types
 
29
import warnings
 
30
import Join
 
31
 
 
32
import sys
 
33
if sys.version_info[:3] < (2, 2, 0):
 
34
    raise ImportError, "SQLObject requires Python 2.2.0 or later"
 
35
 
 
36
NoDefault = SQLBuilder.NoDefault
 
37
 
 
38
class SQLObjectNotFound(LookupError): pass
 
39
class SQLObjectIntegrityError(Exception): pass
 
40
 
 
41
True, False = 1==1, 0==1
 
42
 
 
43
# We'll be dealing with classes that reference each other, so
 
44
# class C1 may reference C2 (in a join), while C2 references
 
45
# C1 right back.  Since classes are created in an order, there
 
46
# will be a point when C1 exists but C2 doesn't.  So we deal
 
47
# with classes by name, and after each class is created we
 
48
# try to fix up any references by replacing the names with
 
49
# actual classes.
 
50
 
 
51
# Here we keep a dictionaries of class names to classes -- note
 
52
# that the classes might be spread among different modules, so
 
53
# since we pile them together names need to be globally unique,
 
54
# not just module unique.
 
55
# Like needSet below, the container dictionary is keyed by the
 
56
# "class registry".
 
57
classRegistry = {}
 
58
 
 
59
# This contains the list of (cls, needClass) pairs, where cls has
 
60
# a reference to the class named needClass.  It is keyed by "class
 
61
# registries", which are disjunct sets of classes.
 
62
needSet = {}
 
63
 
 
64
# Here's what we call after each class is created, to fix up
 
65
# what we can.  We never really know what the last class to be
 
66
# created is, so we have to call this over and over.
 
67
def setNeedSet():
 
68
    global needSet
 
69
    for registryName, needClassDict in needSet.items():
 
70
        newNeedClassDict = {}
 
71
        for needClass, q in needClassDict.items():
 
72
            try:
 
73
                cls = findClass(needClass, registry=registryName)
 
74
                for obj, attr in q:
 
75
                    curr = getattr(obj, attr, None)
 
76
                    if curr is cls:
 
77
                        pass
 
78
                    elif callable(curr):
 
79
                        curr(cls)
 
80
                    else:
 
81
                        setattr(obj, attr, cls)
 
82
            except KeyError:
 
83
                newNeedClassDict[needClass] = q
 
84
        needSet[registryName] = newNeedClassDict
 
85
 
 
86
def addNeedSet(obj, setCls, registry, attr):
 
87
    try:
 
88
        cls = findClass(setCls, registry=registry)
 
89
        if callable(getattr(obj, attr, None)):
 
90
            if not isinstance(getattr(obj, attr), type):
 
91
                # Otherwise we got a class, which means we probably
 
92
                # already set this column.
 
93
                getattr(obj, attr)(cls)
 
94
        else:
 
95
            setattr(obj, attr, cls)
 
96
        return
 
97
    except KeyError:
 
98
        pass
 
99
    q = needSet.setdefault(registry, {}).setdefault(setCls, [])
 
100
    q.append((obj, attr))
 
101
 
 
102
 
 
103
# This is the metaclass.  It essentially takes a dictionary
 
104
# of all the attributes (and thus methods) defined in the
 
105
# class definition.  It futzes with them, and spits out the
 
106
# new class definition.
 
107
class MetaSQLObject(type):
 
108
 
 
109
    def __new__(cls, className, bases, d):
 
110
 
 
111
        global classRegistry, needSet
 
112
 
 
113
        # We fix up the columns here -- replacing any strings with
 
114
        # simply-contructed Col objects, and searching the class
 
115
        # variables for instances of Col objects (which get put into
 
116
        # the _columns instance variable and deleted).
 
117
        columns = []
 
118
        for column in d.get('_columns', []):
 
119
            if isinstance(column, str):
 
120
                column = Col.Col(column)
 
121
            columns.append(column)
 
122
        if columns:
 
123
            d['_columns'] = columns
 
124
 
 
125
        implicitColumns = []
 
126
        implicitJoins = []
 
127
        for attr, value in d.items():
 
128
            if isinstance(value, Col.Col):
 
129
                value.setName(attr)
 
130
                implicitColumns.append(value)
 
131
                del d[attr]
 
132
                continue
 
133
            if isinstance(value, Join.Join):
 
134
                value.setName(attr)
 
135
                implicitJoins.append(value)
 
136
                del d[attr]
 
137
                continue
 
138
 
 
139
        # We *don't* want to inherit _table, so we make sure it
 
140
        # is defined in this class (not a superclass)
 
141
        if not d.has_key('_table'):
 
142
            d['_table'] = None
 
143
 
 
144
        # We actually create the class.
 
145
        newClass = type.__new__(cls, className, bases, d)
 
146
        newClass._SO_finishedClassCreation = False
 
147
 
 
148
        # needSet stuff (see top of module) would get messed
 
149
        # up if more than one SQLObject class has the same
 
150
        # name.
 
151
        registry = newClass._registry
 
152
        assert not classRegistry.get(registry, {}).has_key(className), "A database object by the name %s has already been created" % repr(className)
 
153
 
 
154
        # Register it, for use with needSet
 
155
        if not classRegistry.has_key(registry):
 
156
            classRegistry[registry] = {}
 
157
        classRegistry[registry][className] = newClass
 
158
 
 
159
        # We append to _columns, but we don't want to change the
 
160
        # superclass's _columns list, so we make a copy if necessary
 
161
        if not d.has_key('_columns'):
 
162
            newClass._columns = newClass._columns[:]
 
163
        newClass._columns.extend(implicitColumns)
 
164
        if not d.has_key('_joins'):
 
165
            newClass._joins = newClass._joins[:]
 
166
        newClass._joins.extend(implicitJoins)
 
167
 
 
168
        ######################################################
 
169
        # Set some attributes to their defaults, if necessary.
 
170
        # First we get the connection:
 
171
        if not newClass._connection:
 
172
 
 
173
            mod = sys.modules[newClass.__module__]
 
174
            # See if there's a __connection__ global in
 
175
            # the module, use it if there is.
 
176
            if hasattr(mod, '__connection__'):
 
177
                newClass._connection = mod.__connection__
 
178
 
 
179
        # If the connection is named, we turn the name into
 
180
        # a real connection.
 
181
        if isinstance(newClass._connection, str):
 
182
            newClass._connection = DBConnection.connectionForName(
 
183
                newClass._connection)
 
184
 
 
185
        # The style object tells how to map between Python
 
186
        # identifiers and Database identifiers:
 
187
        if not newClass._style:
 
188
            if newClass._connection and newClass._connection.style:
 
189
                newClass._style = newClass._connection.style
 
190
            else:
 
191
                newClass._style = Style.defaultStyle
 
192
 
 
193
        # plainSetters are columns that haven't been overridden by the
 
194
        # user, so we can contact the database directly to set them.
 
195
        # Note that these can't set these in the SQLObject class
 
196
        # itself, because they specific to this subclass of SQLObject,
 
197
        # and cannot be shared among classes.
 
198
        newClass._SO_plainSetters = {}
 
199
        newClass._SO_plainGetters = {}
 
200
        newClass._SO_plainForeignSetters = {}
 
201
        newClass._SO_plainForeignGetters = {}
 
202
        newClass._SO_plainJoinGetters = {}
 
203
        newClass._SO_plainJoinAdders = {}
 
204
        newClass._SO_plainJoinRemovers = {}
 
205
 
 
206
        # This is a dictionary of columnName: columnObject
 
207
        newClass._SO_columnDict = {}
 
208
        newClass._SO_columns = []
 
209
 
 
210
        # If _table isn't given, use style default
 
211
        if not newClass._table:
 
212
            newClass._table = newClass._style.pythonClassToDBTable(className)
 
213
 
 
214
        # If _idName isn't given, use style default
 
215
        if not hasattr(newClass, '_idName'):
 
216
            newClass._idName = newClass._style.idForTable(newClass._table)
 
217
 
 
218
        # We use the magic "q" attribute for accessing lazy
 
219
        # SQL where-clause generation.  See the sql module for
 
220
        # more.
 
221
        newClass.q = SQLBuilder.SQLObjectTable(newClass)
 
222
 
 
223
        for column in newClass._columns[:]:
 
224
            newClass.addColumn(column)
 
225
        if newClass._fromDatabase:
 
226
            newClass.addColumnsFromDatabase()
 
227
 
 
228
        ########################################
 
229
        # Now we do the joins:
 
230
 
 
231
        # We keep track of the different joins by index,
 
232
        # putting them in this list.
 
233
        newClass._SO_joinList = []
 
234
        newClass._SO_joinDict = {}
 
235
 
 
236
        for join in newClass._joins:
 
237
            newClass.addJoin(join)
 
238
 
 
239
        # We don't setup the properties until we're finished with the
 
240
        # batch adding of all the columns...
 
241
        newClass._SO_finishedClassCreation = True
 
242
        makeProperties(newClass)
 
243
 
 
244
        # Call needSet
 
245
        setNeedSet()
 
246
 
 
247
        # And return the class
 
248
        return newClass
 
249
 
 
250
def makeProperties(obj):
 
251
    """
 
252
    This function takes a dictionary of methods and finds
 
253
    methods named like:
 
254
    * _get_attr
 
255
    * _set_attr
 
256
    * _del_attr
 
257
    * _doc_attr
 
258
    Except for _doc_attr, these should be methods.  It
 
259
    then creates properties from these methods, like
 
260
    property(_get_attr, _set_attr, _del_attr, _doc_attr).
 
261
    Missing methods are okay.
 
262
    """
 
263
 
 
264
    if isinstance(obj, dict):
 
265
        def setFunc(var, value):
 
266
            obj[var] = value
 
267
        d = obj
 
268
    else:
 
269
        def setFunc(var, value):
 
270
            setattr(obj, var, value)
 
271
        d = obj.__dict__
 
272
 
 
273
    props = {}
 
274
    for var, value in d.items():
 
275
        if var.startswith('_set_'):
 
276
            props.setdefault(var[5:], {})['set'] = value
 
277
        elif var.startswith('_get_'):
 
278
            props.setdefault(var[5:], {})['get'] = value
 
279
        elif var.startswith('_del_'):
 
280
            props.setdefault(var[5:], {})['del'] = value
 
281
        elif var.startswith('_doc_'):
 
282
            props.setdefault(var[5:], {})['doc'] = value
 
283
    for var, setters in props.items():
 
284
        if len(setters) == 1 and setters.has_key('doc'):
 
285
            continue
 
286
        if d.has_key(var):
 
287
            if isinstance(d[var], types.MethodType) \
 
288
                   or isinstance(d[var], types.FunctionType):
 
289
                warnings.warn("""I tried to set the property "%s", but it was already set, as a method.  Methods have significantly different semantics than properties, and this may be a sign of a bug in your code.""" % var)
 
290
            continue
 
291
        setFunc(var,
 
292
                property(setters.get('get'), setters.get('set'),
 
293
                         setters.get('del'), setters.get('doc')))
 
294
 
 
295
def unmakeProperties(obj):
 
296
    if isinstance(obj, dict):
 
297
        def delFunc(obj, var):
 
298
            del obj[var]
 
299
        d = obj
 
300
    else:
 
301
        delFunc = delattr
 
302
        d = obj.__dict__
 
303
 
 
304
    for var, value in d.items():
 
305
        if isinstance(value, property):
 
306
            for prop in [value.fget, value.fset, value.fdel]:
 
307
                if prop and not d.has_key(prop.__name__):
 
308
                    delFunc(obj, var)
 
309
                    break
 
310
 
 
311
def findClass(name, registry=None):
 
312
    #assert classRegistry.get(registry, {}).has_key(name), "No class by the name %s found (I have %s)" % (repr(name), ', '.join(map(str, classRegistry.keys())))
 
313
    return classRegistry[registry][name]
 
314
 
 
315
def findDependencies(name, registry=None):
 
316
    depends = []
 
317
    for n, klass in classRegistry[registry].items():
 
318
        if findDependantColumns(name, klass):
 
319
            depends.append(klass)
 
320
    return depends
 
321
 
 
322
def findDependantColumns(name, klass):
 
323
    depends = []
 
324
    for col in klass._SO_columns:
 
325
        if col.foreignKey == name and col.cascade is not None:
 
326
            depends.append(col)
 
327
    return depends
 
328
 
 
329
class CreateNewSQLObject:
 
330
    """
 
331
    Dummy singleton to use in place of an ID, to signal we want
 
332
    a new object.
 
333
    """
 
334
    pass
 
335
 
 
336
# SQLObject is the superclass for all SQLObject classes, of
 
337
# course.  All the deeper magic is done in MetaSQLObject, and
 
338
# only lesser magic is done here.  All the actual work is done
 
339
# here, though -- just automatic method generation (like
 
340
# methods and properties for each column) is done in
 
341
# MetaSQLObject.
 
342
class SQLObject(object):
 
343
 
 
344
    __metaclass__ = MetaSQLObject
 
345
 
 
346
    # When an object is being created, it has an instance
 
347
    # variable _SO_creating, which is true.  This way all the
 
348
    # setters can be captured until the object is complete,
 
349
    # and then the row is inserted into the database.  Once
 
350
    # that happens, _SO_creating is deleted from the instance,
 
351
    # and only the class variable (which is always false) is
 
352
    # left.
 
353
    _SO_creating = False
 
354
    _SO_obsolete = False
 
355
 
 
356
    # Sometimes an intance is attached to a connection, not
 
357
    # globally available.  In that case, self._SO_perConnection
 
358
    # will be true.  It's false by default:
 
359
    _SO_perConnection = False
 
360
 
 
361
    # The _cacheValues attribute controls if you cache
 
362
    # values fetched from the database.  We make sure
 
363
    # it's set (default 1).
 
364
    _cacheValues = True
 
365
 
 
366
    # The _defaultOrder is used by SelectResults
 
367
    _defaultOrder = None
 
368
 
 
369
    _connection = None
 
370
 
 
371
    _columns = []
 
372
 
 
373
    _joins = []
 
374
 
 
375
    _fromDatabase = False
 
376
 
 
377
    _style = None
 
378
 
 
379
    _registry = None
 
380
 
 
381
    # Default is false, but we set it to true for the *instance*
 
382
    # when necessary: (bad clever? maybe)
 
383
    _expired = False
 
384
 
 
385
    def __new__(cls, id, connection=None, selectResults=None):
 
386
 
 
387
        assert id is not None, 'None is not a possible id for %s' % cls.__name
 
388
 
 
389
        # When id is CreateNewSQLObject, that means we are trying to
 
390
        # create a new object.  This is a contract of sorts with the
 
391
        # `new()` method.
 
392
        if id is CreateNewSQLObject:
 
393
            # Create an actual new object:
 
394
            inst = object.__new__(cls)
 
395
            inst._SO_creating = True
 
396
            inst._SO_validatorState = SQLObjectState(inst)
 
397
            # This is a dictionary of column-names to
 
398
            # column-values for the new row:
 
399
            inst._SO_createValues = {}
 
400
            if connection is not None:
 
401
                inst._connection = connection
 
402
            assert selectResults is None
 
403
            return inst
 
404
 
 
405
        # Some databases annoyingly return longs for INT
 
406
        if isinstance(id, long):
 
407
            id = int(id)
 
408
 
 
409
        if connection is None:
 
410
            cache = cls._connection.cache
 
411
        else:
 
412
            cache = connection.cache
 
413
 
 
414
        # This whole sequence comes from Cache.CacheFactory's
 
415
        # behavior, where a None returned means a cache miss.
 
416
        val = cache.get(id, cls)
 
417
        if val is None:
 
418
            try:
 
419
                val = object.__new__(cls)
 
420
                val._SO_validatorState = SQLObjectState(val)
 
421
                val._init(id, connection, selectResults)
 
422
                cache.put(id, cls, val)
 
423
            finally:
 
424
                cache.finishPut(cls)
 
425
        return val
 
426
 
 
427
    def addColumn(cls, columnDef, changeSchema=False):
 
428
        column = columnDef.withClass(cls)
 
429
        name = column.name
 
430
        assert name != 'id', "The 'id' column is implicit, and should not be defined as a column"
 
431
        cls._SO_columnDict[name] = column
 
432
        cls._SO_columns.append(column)
 
433
 
 
434
        if columnDef not in cls._columns:
 
435
            cls._columns.append(columnDef)
 
436
 
 
437
        ###################################################
 
438
        # Create the getter function(s).  We'll start by
 
439
        # creating functions like _SO_get_columnName,
 
440
        # then if there's no function named _get_columnName
 
441
        # we'll alias that to _SO_get_columnName.  This
 
442
        # allows a sort of super call, even though there's
 
443
        # no superclass that defines the database access.
 
444
        if cls._cacheValues:
 
445
            # We create a method here, which is just a function
 
446
            # that takes "self" as the first argument.
 
447
            getter = eval('lambda self: self._SO_loadValue(%s)' % repr(instanceName(name)))
 
448
 
 
449
        else:
 
450
            # If we aren't caching values, we just call the
 
451
            # function _SO_getValue, which fetches from the
 
452
            # database.
 
453
            getter = eval('lambda self: self._SO_getValue(%s)' % repr(name))
 
454
        setattr(cls, rawGetterName(name), getter)
 
455
 
 
456
        # Here if the _get_columnName method isn't in the
 
457
        # definition, we add it with the default
 
458
        # _SO_get_columnName definition.
 
459
        if not hasattr(cls, getterName(name)):
 
460
            setattr(cls, getterName(name), getter)
 
461
            cls._SO_plainGetters[name] = 1
 
462
 
 
463
        #################################################
 
464
        # Create the setter function(s)
 
465
        # Much like creating the getters, we will create
 
466
        # _SO_set_columnName methods, and then alias them
 
467
        # to _set_columnName if the user hasn't defined
 
468
        # those methods themself.
 
469
 
 
470
        if not column.immutable:
 
471
            # We start by just using the _SO_setValue method
 
472
            setter = eval('lambda self, val: self._SO_setValue(%s, val, self.%s)' % (repr(name), '_SO_fromPython_%s' % name))
 
473
            setattr(cls, '_SO_fromPython_%s' % name, column.fromPython)
 
474
            setattr(cls, rawSetterName(name), setter)
 
475
            # Then do the aliasing
 
476
            if not hasattr(cls, setterName(name)):
 
477
                setattr(cls, setterName(name), setter)
 
478
                # We keep track of setters that haven't been
 
479
                # overridden, because we can combine these
 
480
                # set columns into one SQL UPDATE query.
 
481
                cls._SO_plainSetters[name] = 1
 
482
 
 
483
        ##################################################
 
484
        # Here we check if the column is a foreign key, in
 
485
        # which case we need to make another method that
 
486
        # fetches the key and constructs the sister
 
487
        # SQLObject instance.
 
488
        if column.foreignKey:
 
489
 
 
490
            # We go through the standard _SO_get_columnName
 
491
            # deal, except chopping off the "ID" ending since
 
492
            # we're giving the object, not the ID of the
 
493
            # object this time:
 
494
            if cls._cacheValues:
 
495
                # self._SO_class_className is a reference
 
496
                # to the class in question.
 
497
                getter = eval('lambda self: self._SO_foreignKey(self.%s, self._SO_class_%s)' % (instanceName(name), column.foreignKey))
 
498
            else:
 
499
                # Same non-caching version as above.
 
500
                getter = eval('lambda self: self._SO_foreignKey(self._SO_getValue(%s), self._SO_class_%s)' % (repr(name), column.foreignKey))
 
501
            setattr(cls, rawGetterName(name)[:-2], getter)
 
502
 
 
503
            # And we set the _get_columnName version
 
504
            # (sans ID ending)
 
505
            if not hasattr(cls, getterName(name)[:-2]):
 
506
                setattr(cls, getterName(name)[:-2], getter)
 
507
                cls._SO_plainForeignGetters[name[:-2]] = 1
 
508
 
 
509
            if not column.immutable:
 
510
                # The setter just gets the ID of the object,
 
511
                # and then sets the real column.
 
512
                setter = eval('lambda self, val: setattr(self, %s, self._SO_getID(val))' % (repr(name)))
 
513
                setattr(cls, rawSetterName(name)[:-2], setter)
 
514
                if not hasattr(cls, setterName(name)[:-2]):
 
515
                    setattr(cls, setterName(name)[:-2], setter)
 
516
                    cls._SO_plainForeignSetters[name[:-2]] = 1
 
517
 
 
518
            # We'll need to put in a real reference at
 
519
            # some point.  See needSet at the top of the
 
520
            # file for more on this.
 
521
            addNeedSet(cls, column.foreignKey, cls._registry,
 
522
                       '_SO_class_%s' % column.foreignKey)
 
523
 
 
524
        if column.alternateMethodName:
 
525
            func = eval('lambda cls, val, connection=None: cls._SO_fetchAlternateID(%s, val, connection=connection)' % repr(column.dbName))
 
526
            setattr(cls, column.alternateMethodName, classmethod(func))
 
527
 
 
528
        if changeSchema:
 
529
            cls._connection.addColumn(cls._table, column)
 
530
 
 
531
        if cls._SO_finishedClassCreation:
 
532
            makeProperties(cls)
 
533
 
 
534
    addColumn = classmethod(addColumn)
 
535
 
 
536
    def addColumnsFromDatabase(cls):
 
537
        for columnDef in cls._connection.columnsFromSchema(cls._table, cls):
 
538
            alreadyExists = False
 
539
            for c in cls._columns:
 
540
                if c.kw['name'] == columnDef.kw['name']:
 
541
                    alreadyExists = True
 
542
                    break
 
543
            if not alreadyExists:
 
544
                cls.addColumn(columnDef)
 
545
 
 
546
    addColumnsFromDatabase = classmethod(addColumnsFromDatabase)
 
547
 
 
548
    def delColumn(cls, column, changeSchema=False):
 
549
        if isinstance(column, str):
 
550
            column = cls._SO_columnDict[column]
 
551
        if isinstance(column, Col.Col):
 
552
            for c in cls._SO_columns:
 
553
                if column is c.columnDef:
 
554
                    column = c
 
555
                    break
 
556
        cls._SO_columns.remove(column)
 
557
        cls._columns.remove(column.columnDef)
 
558
        name = column.name
 
559
        del cls._SO_columnDict[name]
 
560
        delattr(cls, rawGetterName(name))
 
561
        if cls._SO_plainGetters.has_key(name):
 
562
            delattr(cls, getterName(name))
 
563
        delattr(cls, rawSetterName(name))
 
564
        if cls._SO_plainSetters.has_key(name):
 
565
            delattr(cls, setterName(name))
 
566
        if column.foreignKey:
 
567
            delattr(cls, rawGetterName(name)[:-2])
 
568
            if cls._SO_plainForeignGetters.has_key(name[:-2]):
 
569
                delattr(cls, getterName(name)[:-2])
 
570
            delattr(cls, rawSetterName(name)[:-2])
 
571
            if cls._SO_plainForeignSetters.has_key(name[:-2]):
 
572
                delattr(cls, setterName(name)[:-2])
 
573
        if column.alternateMethodName:
 
574
            delattr(cls, column.alternateMethodName)
 
575
 
 
576
        if changeSchema:
 
577
            cls._connection.delColumn(cls._table, column)
 
578
 
 
579
        if cls._SO_finishedClassCreation:
 
580
            unmakeProperties(cls)
 
581
 
 
582
    delColumn = classmethod(delColumn)
 
583
 
 
584
    def addJoin(cls, joinDef):
 
585
        # The name of the method we'll create.  If it's
 
586
        # automatically generated, it's generated by the
 
587
        # join class.
 
588
        join = joinDef.withClass(cls)
 
589
        meth = join.joinMethodName
 
590
        cls._SO_joinDict[joinDef] = join
 
591
 
 
592
        cls._SO_joinList.append(join)
 
593
        index = len(cls._SO_joinList)-1
 
594
        if joinDef not in cls._joins:
 
595
            cls._joins.append(joinDef)
 
596
 
 
597
        # The function fetches the join by index, and
 
598
        # then lets the join object do the rest of the
 
599
        # work:
 
600
        func = eval('lambda self: self._SO_joinList[%i].performJoin(self)' % index)
 
601
 
 
602
        # And we do the standard _SO_get_... _get_... deal
 
603
        setattr(cls, rawGetterName(meth), func)
 
604
        if not hasattr(cls, getterName(meth)):
 
605
            setattr(cls, getterName(meth), func)
 
606
            cls._SO_plainJoinGetters[meth] = 1
 
607
 
 
608
        # Some joins allow you to remove objects from the
 
609
        # join.
 
610
        if hasattr(join, 'remove'):
 
611
 
 
612
            # Again, we let it do the remove, and we do the
 
613
            # standard naming trick.
 
614
            func = eval('lambda self, obj: self._SO_joinList[%i].remove(self, obj)' % index)
 
615
            setattr(cls, '_SO_remove' + join.addRemoveName, func)
 
616
            if not hasattr(cls, 'remove' + join.addRemoveName):
 
617
                setattr(cls, 'remove' + join.addRemoveName, func)
 
618
                cls._SO_plainJoinRemovers[meth] = 1
 
619
 
 
620
        # Some joins allow you to add objects.
 
621
        if hasattr(join, 'add'):
 
622
 
 
623
            # And again...
 
624
            func = eval('lambda self, obj: self._SO_joinList[%i].add(self, obj)' % (len(cls._SO_joinList)-1))
 
625
            setattr(cls, '_SO_add' + join.addRemoveName, func)
 
626
            if not hasattr(cls, 'add' + join.addRemoveName):
 
627
                setattr(cls, 'add' + join.addRemoveName, func)
 
628
                cls._SO_plainJoinAdders[meth] = 1
 
629
 
 
630
        if cls._SO_finishedClassCreation:
 
631
            makeProperties(cls)
 
632
 
 
633
    addJoin = classmethod(addJoin)
 
634
 
 
635
    def delJoin(cls, joinDef):
 
636
        join = cls._SO_joinDict[joinDef]
 
637
        meth = join.joinMethodName
 
638
        cls._joins.remove(joinDef)
 
639
        del cls._SO_joinDict[joinDef]
 
640
        for i in range(len(cls._SO_joinList)):
 
641
            if cls._SO_joinList[i] is joinDef:
 
642
                # Have to leave None, because we refer to joins
 
643
                # by index.
 
644
                cls._SO_joinList[i] = None
 
645
        delattr(cls, rawGetterName(meth))
 
646
        if cls._SO_plainJoinGetters.has_key(meth):
 
647
            delattr(cls, getterName(meth))
 
648
        if hasattr(join, 'remove'):
 
649
            delattr(cls, '_SO_remove' + join.addRemovePrefix)
 
650
            if cls._SO_plainJoinRemovers.has_key(meth):
 
651
                delattr(cls, 'remove' + join.addRemovePrefix)
 
652
        if hasattr(join, 'add'):
 
653
            delattr(cls, '_SO_add' + join.addRemovePrefix)
 
654
            if cls._SO_plainJoinAdders.has_key(meth):
 
655
                delattr(cls, 'add' + join.addRemovePrefix)
 
656
 
 
657
        if cls._SO_finishedClassCreation:
 
658
            unmakeProperties(cls)
 
659
 
 
660
    delJoin = classmethod(delJoin)
 
661
 
 
662
    def _init(self, id, connection=None, selectResults=None):
 
663
        assert id is not None
 
664
        # This function gets called only when the object is
 
665
        # created, unlike __init__ which would be called
 
666
        # anytime the object was returned from cache.
 
667
        self.id = id
 
668
        self._SO_writeLock = threading.Lock()
 
669
        # If no connection was given, we'll inherit the class
 
670
        # instance variable which should have a _connection
 
671
        # attribute.
 
672
        if connection is not None:
 
673
            self._connection = connection
 
674
            # Sometimes we need to know if this instance is
 
675
            # global or tied to a particular connection.
 
676
            # This flag tells us that:
 
677
            self._SO_perConnection = True
 
678
 
 
679
        if not selectResults:
 
680
            dbNames = [col.dbName for col in self._SO_columns]
 
681
            selectResults = self._connection._SO_selectOne(self, dbNames)
 
682
            if not selectResults:
 
683
                raise SQLObjectNotFound, "The object %s by the ID %s does not exist" % (self.__class__.__name__, self.id)
 
684
        self._SO_selectInit(selectResults)
 
685
 
 
686
    def _SO_loadValue(self, attrName):
 
687
        try:
 
688
            return getattr(self, attrName)
 
689
        except AttributeError:
 
690
            try:
 
691
                self._SO_writeLock.acquire()
 
692
                try:
 
693
                    # Maybe, just in the moment since we got the lock,
 
694
                    # some other thread did a _SO_loadValue and we
 
695
                    # have the attribute!  Let's try and find out!  We
 
696
                    # can keep trying this all day and still beat the
 
697
                    # performance on the database call (okay, we can
 
698
                    # keep trying this for a few msecs at least)...
 
699
                    result = getattr(self, attrName)
 
700
                except AttributeError:
 
701
                    pass
 
702
                else:
 
703
                    return result
 
704
                self._expired = False
 
705
                dbNames = [col.dbName for col in self._SO_columns]
 
706
                selectResults = self._connection._SO_selectOne(self, dbNames)
 
707
                if not selectResults:
 
708
                    raise SQLObjectNotFound, "The object %s by the ID %s has been deleted" % (self.__class__.__name__, self.id)
 
709
                self._SO_selectInit(selectResults)
 
710
                result = getattr(self, attrName)
 
711
                return result
 
712
            finally:
 
713
                self._SO_writeLock.release()
 
714
 
 
715
    def sync(self):
 
716
        self._SO_writeLock.acquire()
 
717
        try:
 
718
            dbNames = [col.dbName for col in self._SO_columns]
 
719
            selectResults = self._connection._SO_selectOne(self, dbNames)
 
720
            if not selectResults:
 
721
                raise SQLObjectNotFound, "The object %s by the ID %s has been deleted" % (self.__class__.__name__, self.id)
 
722
            self._SO_selectInit(selectResults)
 
723
            self._expired = False
 
724
        finally:
 
725
            self._SO_writeLock.release()
 
726
 
 
727
    def expire(self):
 
728
        if self._expired:
 
729
            return
 
730
        self._SO_writeLock.acquire()
 
731
        try:
 
732
            if self._expired:
 
733
                return
 
734
            for column in self._SO_columns:
 
735
                delattr(self, instanceName(column.name))
 
736
            self._expired = True
 
737
            self._connection.cache.expire(self.id, self.__class__)
 
738
        finally:
 
739
            self._SO_writeLock.release()
 
740
 
 
741
    def _SO_setValue(self, name, value, fromPython):
 
742
        # This is the place where we actually update the
 
743
        # database.
 
744
 
 
745
        # If we are _SO_creating, the object doesn't yet exist
 
746
        # in the database, and we can't insert it until all
 
747
        # the parts are set.  So we just keep them in a
 
748
        # dictionary until later:
 
749
        if fromPython:
 
750
            value = fromPython(value, self._SO_validatorState)
 
751
        if self._SO_creating:
 
752
            self._SO_createValues[name] = value
 
753
            return
 
754
 
 
755
        self._connection._SO_update(self,
 
756
                                    [(self._SO_columnDict[name].dbName,
 
757
                                      value)])
 
758
 
 
759
        if self._cacheValues:
 
760
            setattr(self, instanceName(name), value)
 
761
 
 
762
    def set(self, **kw):
 
763
        # set() is used to update multiple values at once,
 
764
        # potentially with one SQL statement if possible.
 
765
 
 
766
        # _SO_creating is special, see _SO_setValue
 
767
        if self._SO_creating:
 
768
            for name, value in kw.items():
 
769
                fromPython = getattr(self, '_SO_fromPython_%s' % name)
 
770
                if fromPython:
 
771
                    kw[name] = fromPython(value, self._SO_validatorState)
 
772
            self._SO_createValues.update(kw)
 
773
            return
 
774
 
 
775
        self._SO_writeLock.acquire()
 
776
 
 
777
        try:
 
778
            # We have to go through and see if the setters are
 
779
            # "plain", that is, if the user has changed their
 
780
            # definition in any way (put in something that
 
781
            # normalizes the value or checks for consistency,
 
782
            # for instance).  If so then we have to use plain
 
783
            # old setattr() to change the value, since we can't
 
784
            # read the user's mind.  We'll combine everything
 
785
            # else into a single UPDATE, if necessary.
 
786
            toUpdate = {}
 
787
            for name, value in kw.items():
 
788
                if self._SO_plainSetters.has_key(name):
 
789
                    fromPython = getattr(self, '_SO_fromPython_%s' % name)
 
790
                    if fromPython:
 
791
                        value = fromPython(value, self._SO_validatorState)
 
792
                    toUpdate[name] = value
 
793
                    if self._cacheValues:
 
794
                        setattr(self, instanceName(name), value)
 
795
                else:
 
796
                    setattr(self, name, value)
 
797
 
 
798
            if toUpdate:
 
799
                self._connection._SO_update(self, [(self._SO_columnDict[name].dbName, value) for name, value in toUpdate.items()])
 
800
        finally:
 
801
            self._SO_writeLock.release()
 
802
 
 
803
    def _SO_selectInit(self, row):
 
804
        for col, colValue in zip(self._SO_columns, row):
 
805
            if col.toPython:
 
806
                colValue = col.toPython(colValue, self._SO_validatorState)
 
807
            setattr(self, instanceName(col.name), colValue)
 
808
 
 
809
    def _SO_getValue(self, name):
 
810
        # Retrieves a single value from the database.  Simple.
 
811
        assert not self._SO_obsolete, "%s with id %s has become obsolete" \
 
812
               % (self.__class__.__name__, self.id)
 
813
        # @@: do we really need this lock?
 
814
        #self._SO_writeLock.acquire()
 
815
        column = self._SO_columnDict[name]
 
816
        results = self._connection._SO_selectOne(self, [column.dbName])
 
817
        #self._SO_writeLock.release()
 
818
        assert results != None, "%s with id %s is not in the database" \
 
819
               % (self.__class__.__name__, self.id)
 
820
        value = results[0]
 
821
        if column.toPython:
 
822
            value = column.toPython(value, self._SO_validatorState)
 
823
        return value
 
824
 
 
825
    def _SO_foreignKey(self, id, joinClass):
 
826
        if id is None:
 
827
            return None
 
828
        elif self._SO_perConnection:
 
829
            return joinClass(id, connection=self._connection)
 
830
        else:
 
831
            return joinClass(id)
 
832
 
 
833
    def new(cls, **kw):
 
834
        # This is what creates a new row, plus the new Python
 
835
        # object to go with it.
 
836
 
 
837
        # Pass the connection object along if we were given one.
 
838
        # Passing None for the ID tells __new__ we want to create
 
839
        # a new object.
 
840
        if kw.has_key('connection'):
 
841
            inst = cls(CreateNewSQLObject, connection=kw['connection'])
 
842
            del kw['connection']
 
843
        else:
 
844
            inst = cls(CreateNewSQLObject)
 
845
 
 
846
        if kw.has_key('id'):
 
847
            id = kw['id']
 
848
            del kw['id']
 
849
        else:
 
850
            id = None
 
851
 
 
852
        # First we do a little fix-up on the keywords we were
 
853
        # passed:
 
854
        for column in inst._SO_columns:
 
855
 
 
856
            # If a foreign key is given, we get the ID of the object
 
857
            # and put that in instead
 
858
            if kw.has_key(column.foreignName):
 
859
                kw[column.name] = getID(kw[column.foreignName])
 
860
                del kw[column.foreignName]
 
861
 
 
862
            # Then we check if the column wasn't passed in, and
 
863
            # if not we try to get the default.
 
864
            if not kw.has_key(column.name):
 
865
                default = column.default
 
866
 
 
867
                # If we don't get it, it's an error:
 
868
                if default is NoDefault:
 
869
                    raise TypeError, "%s did not get expected keyword argument %s" % (cls.__name__, repr(column.name))
 
870
                # Otherwise we put it in as though they did pass
 
871
                # that keyword:
 
872
                kw[column.name] = default
 
873
 
 
874
        # We sort out what columns go straight into the database,
 
875
        # and which ones need setattr() directly here:
 
876
        forDB = {}
 
877
        others = {}
 
878
        for name, value in kw.items():
 
879
            if name in inst._SO_plainSetters:
 
880
                forDB[name] = value
 
881
            else:
 
882
                others[name] = value
 
883
 
 
884
        # We take all the straight-to-DB values and use set() to
 
885
        # set them:
 
886
        inst.set(**forDB)
 
887
 
 
888
        # The rest go through setattr():
 
889
        for name, value in others.items():
 
890
            try:
 
891
                getattr(cls, name)
 
892
            except AttributeError:
 
893
                raise TypeError, "%s.new() got an unexpected keyword argument %s" % (cls.__name__, name)
 
894
            setattr(inst, name, value)
 
895
 
 
896
        # Then we finalize the process:
 
897
        inst._SO_finishCreate(id)
 
898
        return inst
 
899
    new = classmethod(new)
 
900
 
 
901
    def _SO_finishCreate(self, id=None):
 
902
        # Here's where an INSERT is finalized.
 
903
        # These are all the column values that were supposed
 
904
        # to be set, but were delayed until now:
 
905
        setters = self._SO_createValues.items()
 
906
        # Here's their database names:
 
907
        names = [self._SO_columnDict[v[0]].dbName for v in setters]
 
908
        values = [v[1] for v in setters]
 
909
        # Get rid of _SO_create*, we aren't creating anymore.
 
910
        # Doesn't have to be threadsafe because we're still in
 
911
        # new(), which doesn't need to be threadsafe.
 
912
        del self._SO_createValues
 
913
        del self._SO_creating
 
914
 
 
915
        # Do the insert -- most of the SQL in this case is left
 
916
        # up to DBConnection, since getting a new ID is
 
917
        # non-standard.
 
918
        id = self._connection.queryInsertID(self._table, self._idName,
 
919
                                            id, names, values)
 
920
        cache = self._connection.cache
 
921
        cache.created(id, self.__class__, self)
 
922
        self._init(id)
 
923
 
 
924
    def _SO_getID(self, obj):
 
925
        return getID(obj)
 
926
 
 
927
    def _SO_fetchAlternateID(cls, dbIDName, value, connection=None):
 
928
        result = (connection or cls._connection)._SO_selectOneAlt(
 
929
            cls,
 
930
            [cls._idName] +
 
931
            [col.dbName for col in cls._SO_columns],
 
932
            dbIDName,
 
933
            value)
 
934
        if not result:
 
935
            raise SQLObjectNotFound, "The %s by alternateID %s=%s does not exist" % (cls.__name__, dbIDName, repr(value))
 
936
        if connection:
 
937
            obj = cls(result[0], connection=connection)
 
938
        else:
 
939
            obj = cls(result[0])
 
940
        if not obj._cacheValues:
 
941
            obj._SO_writeLock.acquire()
 
942
            try:
 
943
                obj._SO_selectInit(result[1:])
 
944
            finally:
 
945
                obj._SO_writeLock.release()
 
946
        return obj
 
947
    _SO_fetchAlternateID = classmethod(_SO_fetchAlternateID)
 
948
 
 
949
    def _SO_depends(cls):
 
950
        return findDependencies(cls.__name__, cls._registry)
 
951
    _SO_depends = classmethod(_SO_depends)
 
952
 
 
953
    def select(cls, clause=None, clauseTables=None,
 
954
               orderBy=NoDefault, limit=None,
 
955
               lazyColumns=False, reversed=False,
 
956
               connection=None):
 
957
        return SelectResults(cls, clause, clauseTables=clauseTables,
 
958
                             orderBy=orderBy,
 
959
                             limit=limit, lazyColumns=lazyColumns,
 
960
                             reversed=reversed,
 
961
                             connection=connection)
 
962
    select = classmethod(select)
 
963
 
 
964
    def selectBy(cls, connection=None, **kw):
 
965
        return SelectResults(cls,
 
966
                             cls._connection._SO_columnClause(cls, kw),
 
967
                             connection=connection)
 
968
 
 
969
    selectBy = classmethod(selectBy)
 
970
 
 
971
    # 3-03 @@: Should these have a connection argument?
 
972
    def dropTable(cls, ifExists=False, dropJoinTables=True, cascade=False):
 
973
        if ifExists and not cls._connection.tableExists(cls._table):
 
974
            return
 
975
        cls._connection.dropTable(cls._table, cascade)
 
976
        if dropJoinTables:
 
977
            cls.dropJoinTables(ifExists=ifExists)
 
978
    dropTable = classmethod(dropTable)
 
979
 
 
980
    def createTable(cls, ifNotExists=False, createJoinTables=True):
 
981
        if ifNotExists and cls._connection.tableExists(cls._table):
 
982
            return
 
983
        cls._connection.createTable(cls)
 
984
        if createJoinTables:
 
985
            cls.createJoinTables(ifNotExists=ifNotExists)
 
986
    createTable = classmethod(createTable)
 
987
 
 
988
    def createJoinTables(cls, ifNotExists=False):
 
989
        for join in cls._SO_joinList:
 
990
            if not join:
 
991
                continue
 
992
            if not join.hasIntermediateTable():
 
993
                continue
 
994
            # This join will show up twice, in each of the
 
995
            # classes, but we only create the table once.  We
 
996
            # arbitrarily create it while we're creating the
 
997
            # alphabetically earlier class.
 
998
            if join.soClass.__name__ > join.otherClass.__name__:
 
999
                continue
 
1000
            if ifNotExists and \
 
1001
               cls._connection.tableExists(join.intermediateTable):
 
1002
                continue
 
1003
            cls._connection._SO_createJoinTable(join)
 
1004
 
 
1005
    createJoinTables = classmethod(createJoinTables)
 
1006
 
 
1007
    def dropJoinTables(cls, ifExists=False):
 
1008
        for join in cls._SO_joinList:
 
1009
            if not join:
 
1010
                continue
 
1011
            if not join.hasIntermediateTable():
 
1012
                continue
 
1013
            if join.soClass.__name__ > join.otherClass.__name__:
 
1014
                continue
 
1015
            if ifExists and \
 
1016
               not cls._connection.tableExists(join.intermediateTable):
 
1017
                continue
 
1018
            cls._connection._SO_dropJoinTable(join)
 
1019
 
 
1020
    dropJoinTables = classmethod(dropJoinTables)
 
1021
 
 
1022
    def clearTable(cls):
 
1023
        # 3-03 @@: Maybe this should check the cache... but it's
 
1024
        # kind of crude anyway, so...
 
1025
        cls._connection.clearTable(cls._table)
 
1026
    clearTable = classmethod(clearTable)
 
1027
 
 
1028
    def destroySelf(self):
 
1029
        # Kills this object.  Kills it dead!
 
1030
        depends = []
 
1031
        klass = self.__class__
 
1032
        depends = self._SO_depends()
 
1033
        for k in depends:
 
1034
            cols = findDependantColumns(klass.__name__, k)
 
1035
            query = []
 
1036
            restrict = False
 
1037
            for col in cols:
 
1038
                if col.cascade == False:
 
1039
                    # Found a restriction
 
1040
                    restrict = True
 
1041
                query.append("%s = %s" % (col.dbName, self.id))
 
1042
            query = ' OR '.join(query)
 
1043
            results = k.select(query)
 
1044
            if restrict and results.count():
 
1045
                # Restrictions only apply if there are
 
1046
                # matching records on the related table
 
1047
                raise SQLObjectIntegrityError, (
 
1048
                    "Tried to delete %s::%s but "
 
1049
                    "table %s has a restriction against it" %
 
1050
                    (klass.__name__, self.id, k.__name__))
 
1051
            for row in results:
 
1052
                row.destroySelf()
 
1053
        self._SO_obsolete = True
 
1054
        self._connection._SO_delete(self)
 
1055
        self._connection.cache.expire(self.id, self.__class__)
 
1056
 
 
1057
    def delete(cls, id):
 
1058
        obj = cls(id)
 
1059
        obj.destroySelf()
 
1060
 
 
1061
    delete = classmethod(delete)
 
1062
 
 
1063
    def __repr__(self):
 
1064
        return '<%s %i %s>' \
 
1065
               % (self.__class__.__name__,
 
1066
                  self.id,
 
1067
                  ' '.join(['%s=%s' % (name, repr(value)) for name, value in self._reprItems()]))
 
1068
 
 
1069
    def sqlrepr(cls, value):
 
1070
        return cls._connection.sqlrepr(value)
 
1071
 
 
1072
    sqlrepr = classmethod(sqlrepr)
 
1073
 
 
1074
    def _reprItems(self):
 
1075
        items = []
 
1076
        for col in self._SO_columns:
 
1077
            value = getattr(self, col.name)
 
1078
            r = repr(value)
 
1079
            if len(r) > 20:
 
1080
                value = r[:17] + "..." + r[-1]
 
1081
            items.append((col.name, getattr(self, col.name)))
 
1082
        return items
 
1083
 
 
1084
 
 
1085
def capitalize(name):
 
1086
    return name[0].capitalize() + name[1:]
 
1087
 
 
1088
def setterName(name):
 
1089
    return '_set_%s' % name
 
1090
def rawSetterName(name):
 
1091
    return '_SO_set_%s' % name
 
1092
def getterName(name):
 
1093
    return '_get_%s' % name
 
1094
def rawGetterName(name):
 
1095
    return '_SO_get_%s' % name
 
1096
def instanceName(name):
 
1097
    return '_SO_val_%s' % name
 
1098
 
 
1099
 
 
1100
class SelectResults(object):
 
1101
 
 
1102
    def __init__(self, sourceClass, clause, clauseTables=None,
 
1103
                 **ops):
 
1104
        self.sourceClass = sourceClass
 
1105
        if clause is None or isinstance(clause, str) and clause == 'all':
 
1106
            clause = SQLBuilder.SQLTrueClause
 
1107
        self.clause = clause
 
1108
        tablesDict = SQLBuilder.tablesUsedDict(self.clause)
 
1109
        tablesDict[sourceClass._table] = 1
 
1110
        if clauseTables:
 
1111
            for table in clauseTables:
 
1112
                tablesDict[table] = 1
 
1113
        self.clauseTables = clauseTables
 
1114
        self.tables = tablesDict.keys()
 
1115
        self.ops = ops
 
1116
        if self.ops.get('orderBy', NoDefault) is NoDefault:
 
1117
            self.ops['orderBy'] = sourceClass._defaultOrder
 
1118
        orderBy = self.ops['orderBy']
 
1119
        if isinstance(orderBy, list) or isinstance(orderBy, tuple):
 
1120
            orderBy = map(self._mungeOrderBy, orderBy)
 
1121
        else:
 
1122
            orderBy = self._mungeOrderBy(orderBy)
 
1123
        self.ops['dbOrderBy'] = orderBy
 
1124
        if ops.has_key('connection') and ops['connection'] is None:
 
1125
            del ops['connection']
 
1126
 
 
1127
    def _mungeOrderBy(self, orderBy):
 
1128
        if isinstance(orderBy, str) and orderBy.startswith('-'):
 
1129
            orderBy = orderBy[1:]
 
1130
            desc = True
 
1131
        else:
 
1132
            desc = False
 
1133
        if self.sourceClass._SO_columnDict.has_key(orderBy):
 
1134
            val = self.sourceClass._SO_columnDict[orderBy].dbName
 
1135
            if desc:
 
1136
                return '-' + val
 
1137
            else:
 
1138
                return val
 
1139
        else:
 
1140
            if desc:
 
1141
                return SQLBuilder.DESC(orderBy)
 
1142
            else:
 
1143
                return orderBy
 
1144
 
 
1145
    def clone(self, **newOps):
 
1146
        ops = self.ops.copy()
 
1147
        ops.update(newOps)
 
1148
        return self.__class__(self.sourceClass, self.clause,
 
1149
                              self.clauseTables, **ops)
 
1150
 
 
1151
    def orderBy(self, orderBy):
 
1152
        return self.clone(orderBy=orderBy)
 
1153
 
 
1154
    def connection(self, conn):
 
1155
        return self.clone(connection=conn)
 
1156
 
 
1157
    def limit(self, limit):
 
1158
        return self[:limit]
 
1159
 
 
1160
    def lazyColumns(self, value):
 
1161
        return self.clone(lazyColumns=value)
 
1162
 
 
1163
    def reversed(self):
 
1164
        return self.clone(reversed=not self.ops.get('reversed', False))
 
1165
 
 
1166
    def __getitem__(self, value):
 
1167
        if type(value) is type(slice(1)):
 
1168
            assert not value.step, "Slices do not support steps"
 
1169
            if not value.start and not value.stop:
 
1170
                # No need to copy, I'm immutable
 
1171
                return self
 
1172
 
 
1173
            # Negative indexes aren't handled (and everything we
 
1174
            # don't handle ourselves we just create a list to
 
1175
            # handle)
 
1176
            if (value.start and value.start < 0) \
 
1177
               or (value.stop and value.stop < 0):
 
1178
                if value.start:
 
1179
                    if value.stop:
 
1180
                        return list(self)[value.start:value.stop]
 
1181
                    return list(self)[value.start:]
 
1182
                return list(self)[:value.stop]
 
1183
 
 
1184
 
 
1185
            if value.start:
 
1186
                assert value.start >= 0
 
1187
                start = self.ops.get('start', 0) + value.start
 
1188
                if value.stop is not None:
 
1189
                    assert value.stop >= 0
 
1190
                    if value.stop < value.start:
 
1191
                        # an empty result:
 
1192
                        end = start
 
1193
                    else:
 
1194
                        end = value.stop + self.ops.get('start', 0)
 
1195
                        if self.ops.get('end', None) is not None \
 
1196
                           and value['end'] < end:
 
1197
                            # truncated by previous slice:
 
1198
                            end = self.ops['end']
 
1199
                else:
 
1200
                    end = self.ops.get('end', None)
 
1201
            else:
 
1202
                start = self.ops.get('start', 0)
 
1203
                end = value.stop + start
 
1204
                if self.ops.get('end', None) is not None \
 
1205
                   and self.ops['end'] < end:
 
1206
                    end = self.ops['end']
 
1207
            return self.clone(start=start, end=end)
 
1208
        else:
 
1209
            if value < 0:
 
1210
                return list(iter(self))[value]
 
1211
            else:
 
1212
                start = self.ops.get('start', 0) + value
 
1213
                return list(self.clone(start=start, end=start+1))[0]
 
1214
 
 
1215
    def __iter__(self):
 
1216
        conn = self.ops.get('connection', self.sourceClass._connection)
 
1217
        return conn.iterSelect(self)
 
1218
 
 
1219
    def count(self):
 
1220
        conn = self.ops.get('connection', self.sourceClass._connection)
 
1221
        count = conn.countSelect(self)
 
1222
        if self.ops.get('start'):
 
1223
            count -= self.ops['start']
 
1224
        if self.ops.get('end'):
 
1225
            count = min(self.ops['end'] - self.ops.get('start', 0), count)
 
1226
        return count
 
1227
 
 
1228
class SQLObjectState(object):
 
1229
 
 
1230
    def __init__(self, soObject):
 
1231
        self.soObject = soObject
 
1232
        self.protocol = 'sql'
 
1233
 
 
1234
 
 
1235
########################################
 
1236
## Utility functions (for external consumption)
 
1237
########################################
 
1238
 
 
1239
def getID(obj):
 
1240
    if isinstance(obj, SQLObject):
 
1241
        return obj.id
 
1242
    elif type(obj) is type(1):
 
1243
        return obj
 
1244
    elif type(obj) is type(1L):
 
1245
        return int(obj)
 
1246
    elif type(obj) is type(""):
 
1247
        return int(obj)
 
1248
    elif obj is None:
 
1249
        return None
 
1250
 
 
1251
def getObject(obj, klass):
 
1252
    if type(obj) is type(1):
 
1253
        return klass(obj)
 
1254
    elif type(obj) is type(1L):
 
1255
        return klass(int(obj))
 
1256
    elif type(obj) is type(""):
 
1257
        return klass(int(obj))
 
1258
    elif obj is None:
 
1259
        return None
 
1260
    else:
 
1261
        return obj
 
1262
 
 
1263
__all__ = ['NoDefault', 'SQLObject',
 
1264
           'getID', 'getObject',
 
1265
           'SQLObjectNotFound']