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.
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.
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.
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,
33
if sys.version_info[:3] < (2, 2, 0):
34
raise ImportError, "SQLObject requires Python 2.2.0 or later"
36
NoDefault = SQLBuilder.NoDefault
38
class SQLObjectNotFound(LookupError): pass
39
class SQLObjectIntegrityError(Exception): pass
41
True, False = 1==1, 0==1
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
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
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.
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.
69
for registryName, needClassDict in needSet.items():
71
for needClass, q in needClassDict.items():
73
cls = findClass(needClass, registry=registryName)
75
curr = getattr(obj, attr, None)
81
setattr(obj, attr, cls)
83
newNeedClassDict[needClass] = q
84
needSet[registryName] = newNeedClassDict
86
def addNeedSet(obj, setCls, registry, attr):
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)
95
setattr(obj, attr, cls)
99
q = needSet.setdefault(registry, {}).setdefault(setCls, [])
100
q.append((obj, attr))
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):
109
def __new__(cls, className, bases, d):
111
global classRegistry, needSet
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).
118
for column in d.get('_columns', []):
119
if isinstance(column, str):
120
column = Col.Col(column)
121
columns.append(column)
123
d['_columns'] = columns
127
for attr, value in d.items():
128
if isinstance(value, Col.Col):
130
implicitColumns.append(value)
133
if isinstance(value, Join.Join):
135
implicitJoins.append(value)
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'):
144
# We actually create the class.
145
newClass = type.__new__(cls, className, bases, d)
146
newClass._SO_finishedClassCreation = False
148
# needSet stuff (see top of module) would get messed
149
# up if more than one SQLObject class has the same
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)
154
# Register it, for use with needSet
155
if not classRegistry.has_key(registry):
156
classRegistry[registry] = {}
157
classRegistry[registry][className] = newClass
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)
168
######################################################
169
# Set some attributes to their defaults, if necessary.
170
# First we get the connection:
171
if not newClass._connection:
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__
179
# If the connection is named, we turn the name into
181
if isinstance(newClass._connection, str):
182
newClass._connection = DBConnection.connectionForName(
183
newClass._connection)
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
191
newClass._style = Style.defaultStyle
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 = {}
206
# This is a dictionary of columnName: columnObject
207
newClass._SO_columnDict = {}
208
newClass._SO_columns = []
210
# If _table isn't given, use style default
211
if not newClass._table:
212
newClass._table = newClass._style.pythonClassToDBTable(className)
214
# If _idName isn't given, use style default
215
if not hasattr(newClass, '_idName'):
216
newClass._idName = newClass._style.idForTable(newClass._table)
218
# We use the magic "q" attribute for accessing lazy
219
# SQL where-clause generation. See the sql module for
221
newClass.q = SQLBuilder.SQLObjectTable(newClass)
223
for column in newClass._columns[:]:
224
newClass.addColumn(column)
225
if newClass._fromDatabase:
226
newClass.addColumnsFromDatabase()
228
########################################
229
# Now we do the joins:
231
# We keep track of the different joins by index,
232
# putting them in this list.
233
newClass._SO_joinList = []
234
newClass._SO_joinDict = {}
236
for join in newClass._joins:
237
newClass.addJoin(join)
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)
247
# And return the class
250
def makeProperties(obj):
252
This function takes a dictionary of methods and finds
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.
264
if isinstance(obj, dict):
265
def setFunc(var, value):
269
def setFunc(var, value):
270
setattr(obj, var, value)
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'):
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)
292
property(setters.get('get'), setters.get('set'),
293
setters.get('del'), setters.get('doc')))
295
def unmakeProperties(obj):
296
if isinstance(obj, dict):
297
def delFunc(obj, var):
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__):
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]
315
def findDependencies(name, registry=None):
317
for n, klass in classRegistry[registry].items():
318
if findDependantColumns(name, klass):
319
depends.append(klass)
322
def findDependantColumns(name, klass):
324
for col in klass._SO_columns:
325
if col.foreignKey == name and col.cascade is not None:
329
class CreateNewSQLObject:
331
Dummy singleton to use in place of an ID, to signal we want
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
342
class SQLObject(object):
344
__metaclass__ = MetaSQLObject
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
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
361
# The _cacheValues attribute controls if you cache
362
# values fetched from the database. We make sure
363
# it's set (default 1).
366
# The _defaultOrder is used by SelectResults
375
_fromDatabase = False
381
# Default is false, but we set it to true for the *instance*
382
# when necessary: (bad clever? maybe)
385
def __new__(cls, id, connection=None, selectResults=None):
387
assert id is not None, 'None is not a possible id for %s' % cls.__name
389
# When id is CreateNewSQLObject, that means we are trying to
390
# create a new object. This is a contract of sorts with the
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
405
# Some databases annoyingly return longs for INT
406
if isinstance(id, long):
409
if connection is None:
410
cache = cls._connection.cache
412
cache = connection.cache
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)
419
val = object.__new__(cls)
420
val._SO_validatorState = SQLObjectState(val)
421
val._init(id, connection, selectResults)
422
cache.put(id, cls, val)
427
def addColumn(cls, columnDef, changeSchema=False):
428
column = columnDef.withClass(cls)
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)
434
if columnDef not in cls._columns:
435
cls._columns.append(columnDef)
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.
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)))
450
# If we aren't caching values, we just call the
451
# function _SO_getValue, which fetches from the
453
getter = eval('lambda self: self._SO_getValue(%s)' % repr(name))
454
setattr(cls, rawGetterName(name), getter)
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
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.
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
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:
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
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))
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)
503
# And we set the _get_columnName version
505
if not hasattr(cls, getterName(name)[:-2]):
506
setattr(cls, getterName(name)[:-2], getter)
507
cls._SO_plainForeignGetters[name[:-2]] = 1
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
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)
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))
529
cls._connection.addColumn(cls._table, column)
531
if cls._SO_finishedClassCreation:
534
addColumn = classmethod(addColumn)
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']:
543
if not alreadyExists:
544
cls.addColumn(columnDef)
546
addColumnsFromDatabase = classmethod(addColumnsFromDatabase)
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:
556
cls._SO_columns.remove(column)
557
cls._columns.remove(column.columnDef)
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)
577
cls._connection.delColumn(cls._table, column)
579
if cls._SO_finishedClassCreation:
580
unmakeProperties(cls)
582
delColumn = classmethod(delColumn)
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
588
join = joinDef.withClass(cls)
589
meth = join.joinMethodName
590
cls._SO_joinDict[joinDef] = join
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)
597
# The function fetches the join by index, and
598
# then lets the join object do the rest of the
600
func = eval('lambda self: self._SO_joinList[%i].performJoin(self)' % index)
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
608
# Some joins allow you to remove objects from the
610
if hasattr(join, 'remove'):
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
620
# Some joins allow you to add objects.
621
if hasattr(join, 'add'):
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
630
if cls._SO_finishedClassCreation:
633
addJoin = classmethod(addJoin)
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
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)
657
if cls._SO_finishedClassCreation:
658
unmakeProperties(cls)
660
delJoin = classmethod(delJoin)
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.
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
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
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)
686
def _SO_loadValue(self, attrName):
688
return getattr(self, attrName)
689
except AttributeError:
691
self._SO_writeLock.acquire()
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:
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)
713
self._SO_writeLock.release()
716
self._SO_writeLock.acquire()
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
725
self._SO_writeLock.release()
730
self._SO_writeLock.acquire()
734
for column in self._SO_columns:
735
delattr(self, instanceName(column.name))
737
self._connection.cache.expire(self.id, self.__class__)
739
self._SO_writeLock.release()
741
def _SO_setValue(self, name, value, fromPython):
742
# This is the place where we actually update the
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:
750
value = fromPython(value, self._SO_validatorState)
751
if self._SO_creating:
752
self._SO_createValues[name] = value
755
self._connection._SO_update(self,
756
[(self._SO_columnDict[name].dbName,
759
if self._cacheValues:
760
setattr(self, instanceName(name), value)
763
# set() is used to update multiple values at once,
764
# potentially with one SQL statement if possible.
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)
771
kw[name] = fromPython(value, self._SO_validatorState)
772
self._SO_createValues.update(kw)
775
self._SO_writeLock.acquire()
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.
787
for name, value in kw.items():
788
if self._SO_plainSetters.has_key(name):
789
fromPython = getattr(self, '_SO_fromPython_%s' % name)
791
value = fromPython(value, self._SO_validatorState)
792
toUpdate[name] = value
793
if self._cacheValues:
794
setattr(self, instanceName(name), value)
796
setattr(self, name, value)
799
self._connection._SO_update(self, [(self._SO_columnDict[name].dbName, value) for name, value in toUpdate.items()])
801
self._SO_writeLock.release()
803
def _SO_selectInit(self, row):
804
for col, colValue in zip(self._SO_columns, row):
806
colValue = col.toPython(colValue, self._SO_validatorState)
807
setattr(self, instanceName(col.name), colValue)
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)
822
value = column.toPython(value, self._SO_validatorState)
825
def _SO_foreignKey(self, id, joinClass):
828
elif self._SO_perConnection:
829
return joinClass(id, connection=self._connection)
834
# This is what creates a new row, plus the new Python
835
# object to go with it.
837
# Pass the connection object along if we were given one.
838
# Passing None for the ID tells __new__ we want to create
840
if kw.has_key('connection'):
841
inst = cls(CreateNewSQLObject, connection=kw['connection'])
844
inst = cls(CreateNewSQLObject)
852
# First we do a little fix-up on the keywords we were
854
for column in inst._SO_columns:
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]
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
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
872
kw[column.name] = default
874
# We sort out what columns go straight into the database,
875
# and which ones need setattr() directly here:
878
for name, value in kw.items():
879
if name in inst._SO_plainSetters:
884
# We take all the straight-to-DB values and use set() to
888
# The rest go through setattr():
889
for name, value in others.items():
892
except AttributeError:
893
raise TypeError, "%s.new() got an unexpected keyword argument %s" % (cls.__name__, name)
894
setattr(inst, name, value)
896
# Then we finalize the process:
897
inst._SO_finishCreate(id)
899
new = classmethod(new)
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
915
# Do the insert -- most of the SQL in this case is left
916
# up to DBConnection, since getting a new ID is
918
id = self._connection.queryInsertID(self._table, self._idName,
920
cache = self._connection.cache
921
cache.created(id, self.__class__, self)
924
def _SO_getID(self, obj):
927
def _SO_fetchAlternateID(cls, dbIDName, value, connection=None):
928
result = (connection or cls._connection)._SO_selectOneAlt(
931
[col.dbName for col in cls._SO_columns],
935
raise SQLObjectNotFound, "The %s by alternateID %s=%s does not exist" % (cls.__name__, dbIDName, repr(value))
937
obj = cls(result[0], connection=connection)
940
if not obj._cacheValues:
941
obj._SO_writeLock.acquire()
943
obj._SO_selectInit(result[1:])
945
obj._SO_writeLock.release()
947
_SO_fetchAlternateID = classmethod(_SO_fetchAlternateID)
949
def _SO_depends(cls):
950
return findDependencies(cls.__name__, cls._registry)
951
_SO_depends = classmethod(_SO_depends)
953
def select(cls, clause=None, clauseTables=None,
954
orderBy=NoDefault, limit=None,
955
lazyColumns=False, reversed=False,
957
return SelectResults(cls, clause, clauseTables=clauseTables,
959
limit=limit, lazyColumns=lazyColumns,
961
connection=connection)
962
select = classmethod(select)
964
def selectBy(cls, connection=None, **kw):
965
return SelectResults(cls,
966
cls._connection._SO_columnClause(cls, kw),
967
connection=connection)
969
selectBy = classmethod(selectBy)
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):
975
cls._connection.dropTable(cls._table, cascade)
977
cls.dropJoinTables(ifExists=ifExists)
978
dropTable = classmethod(dropTable)
980
def createTable(cls, ifNotExists=False, createJoinTables=True):
981
if ifNotExists and cls._connection.tableExists(cls._table):
983
cls._connection.createTable(cls)
985
cls.createJoinTables(ifNotExists=ifNotExists)
986
createTable = classmethod(createTable)
988
def createJoinTables(cls, ifNotExists=False):
989
for join in cls._SO_joinList:
992
if not join.hasIntermediateTable():
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__:
1000
if ifNotExists and \
1001
cls._connection.tableExists(join.intermediateTable):
1003
cls._connection._SO_createJoinTable(join)
1005
createJoinTables = classmethod(createJoinTables)
1007
def dropJoinTables(cls, ifExists=False):
1008
for join in cls._SO_joinList:
1011
if not join.hasIntermediateTable():
1013
if join.soClass.__name__ > join.otherClass.__name__:
1016
not cls._connection.tableExists(join.intermediateTable):
1018
cls._connection._SO_dropJoinTable(join)
1020
dropJoinTables = classmethod(dropJoinTables)
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)
1028
def destroySelf(self):
1029
# Kills this object. Kills it dead!
1031
klass = self.__class__
1032
depends = self._SO_depends()
1034
cols = findDependantColumns(klass.__name__, k)
1038
if col.cascade == False:
1039
# Found a restriction
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__))
1053
self._SO_obsolete = True
1054
self._connection._SO_delete(self)
1055
self._connection.cache.expire(self.id, self.__class__)
1057
def delete(cls, id):
1061
delete = classmethod(delete)
1064
return '<%s %i %s>' \
1065
% (self.__class__.__name__,
1067
' '.join(['%s=%s' % (name, repr(value)) for name, value in self._reprItems()]))
1069
def sqlrepr(cls, value):
1070
return cls._connection.sqlrepr(value)
1072
sqlrepr = classmethod(sqlrepr)
1074
def _reprItems(self):
1076
for col in self._SO_columns:
1077
value = getattr(self, col.name)
1080
value = r[:17] + "..." + r[-1]
1081
items.append((col.name, getattr(self, col.name)))
1085
def capitalize(name):
1086
return name[0].capitalize() + name[1:]
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
1100
class SelectResults(object):
1102
def __init__(self, sourceClass, clause, clauseTables=None,
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
1111
for table in clauseTables:
1112
tablesDict[table] = 1
1113
self.clauseTables = clauseTables
1114
self.tables = tablesDict.keys()
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)
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']
1127
def _mungeOrderBy(self, orderBy):
1128
if isinstance(orderBy, str) and orderBy.startswith('-'):
1129
orderBy = orderBy[1:]
1133
if self.sourceClass._SO_columnDict.has_key(orderBy):
1134
val = self.sourceClass._SO_columnDict[orderBy].dbName
1141
return SQLBuilder.DESC(orderBy)
1145
def clone(self, **newOps):
1146
ops = self.ops.copy()
1148
return self.__class__(self.sourceClass, self.clause,
1149
self.clauseTables, **ops)
1151
def orderBy(self, orderBy):
1152
return self.clone(orderBy=orderBy)
1154
def connection(self, conn):
1155
return self.clone(connection=conn)
1157
def limit(self, limit):
1160
def lazyColumns(self, value):
1161
return self.clone(lazyColumns=value)
1164
return self.clone(reversed=not self.ops.get('reversed', False))
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
1173
# Negative indexes aren't handled (and everything we
1174
# don't handle ourselves we just create a list to
1176
if (value.start and value.start < 0) \
1177
or (value.stop and value.stop < 0):
1180
return list(self)[value.start:value.stop]
1181
return list(self)[value.start:]
1182
return list(self)[:value.stop]
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:
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']
1200
end = self.ops.get('end', None)
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)
1210
return list(iter(self))[value]
1212
start = self.ops.get('start', 0) + value
1213
return list(self.clone(start=start, end=start+1))[0]
1216
conn = self.ops.get('connection', self.sourceClass._connection)
1217
return conn.iterSelect(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)
1228
class SQLObjectState(object):
1230
def __init__(self, soObject):
1231
self.soObject = soObject
1232
self.protocol = 'sql'
1235
########################################
1236
## Utility functions (for external consumption)
1237
########################################
1240
if isinstance(obj, SQLObject):
1242
elif type(obj) is type(1):
1244
elif type(obj) is type(1L):
1246
elif type(obj) is type(""):
1251
def getObject(obj, klass):
1252
if type(obj) is type(1):
1254
elif type(obj) is type(1L):
1255
return klass(int(obj))
1256
elif type(obj) is type(""):
1257
return klass(int(obj))
1263
__all__ = ['NoDefault', 'SQLObject',
1264
'getID', 'getObject',
1265
'SQLObjectNotFound']