-
Notifications
You must be signed in to change notification settings - Fork 28
Expand file tree
/
Copy pathmain.py
More file actions
1880 lines (1621 loc) · 68.9 KB
/
main.py
File metadata and controls
1880 lines (1621 loc) · 68.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
SQLObject
---------
:author: Ian Bicking <[email protected]>
SQLObject is a object-relational mapper. See SQLObject.html or
SQLObject.rst for more.
With the help by Oleg Broytman and many other contributors.
See Authors.rst.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,
USA.
"""
import sys
import threading
import weakref
import types
import warnings
from . import sqlbuilder
from . import dbconnection
from . import col
from . import styles
from . import joins
from . import index
from . import classregistry
from . import declarative
from . import events
from .sresults import SelectResults
from .util.threadinglocal import local
from sqlobject.compat import PY2, with_metaclass, string_type, unicode_type
if ((sys.version_info[0] == 2) and (sys.version_info[:2] < (2, 7))) or \
((sys.version_info[0] == 3) and (sys.version_info[:2] < (3, 4))):
raise ImportError("SQLObject requires Python 2.7 or 3.4+")
if not PY2:
# alias for python 3 compatability
long = int
"""
This thread-local storage is needed for RowCreatedSignals. It gathers
code-blocks to execute _after_ the whole hierachy of inherited SQLObjects
is created. See SQLObject._create
"""
NoDefault = sqlbuilder.NoDefault
class SQLObjectNotFound(LookupError):
pass
class SQLObjectIntegrityError(Exception):
pass
def makeProperties(obj):
"""
This function takes a dictionary of methods and finds
methods named like:
* _get_attr
* _set_attr
* _del_attr
* _doc_attr
Except for _doc_attr, these should be methods. It
then creates properties from these methods, like
property(_get_attr, _set_attr, _del_attr, _doc_attr).
Missing methods are okay.
"""
if isinstance(obj, dict):
def setFunc(var, value):
obj[var] = value
d = obj
else:
def setFunc(var, value):
setattr(obj, var, value)
d = obj.__dict__
props = {}
for var, value in d.items():
if var.startswith('_set_'):
props.setdefault(var[5:], {})['set'] = value
elif var.startswith('_get_'):
props.setdefault(var[5:], {})['get'] = value
elif var.startswith('_del_'):
props.setdefault(var[5:], {})['del'] = value
elif var.startswith('_doc_'):
props.setdefault(var[5:], {})['doc'] = value
for var, setters in props.items():
if len(setters) == 1 and 'doc' in setters:
continue
if var in d:
if isinstance(d[var], (types.MethodType, types.FunctionType)):
warnings.warn(
"I tried to set the property %r, but it was "
"already set, as a method (%r). Methods have "
"significantly different semantics than properties, "
"and this may be a sign of a bug in your code."
% (var, d[var]))
continue
setFunc(var,
property(setters.get('get'), setters.get('set'),
setters.get('del'), setters.get('doc')))
def unmakeProperties(obj):
if isinstance(obj, dict):
def delFunc(obj, var):
del obj[var]
d = obj
else:
delFunc = delattr
d = obj.__dict__
for var, value in list(d.items()):
if isinstance(value, property):
for prop in [value.fget, value.fset, value.fdel]:
if prop and prop.__name__ not in d:
delFunc(obj, var)
break
def findDependencies(name, registry=None):
depends = []
for klass in classregistry.registry(registry).allClasses():
if findDependantColumns(name, klass):
depends.append(klass)
else:
for join in klass.sqlmeta.joins:
if isinstance(join, joins.SORelatedJoin) and \
join.otherClassName == name:
depends.append(klass)
break
return depends
def findDependantColumns(name, klass):
depends = []
for _col in klass.sqlmeta.columnList:
if _col.foreignKey == name and _col.cascade is not None:
depends.append(_col)
return depends
def _collectAttributes(cls, new_attrs, look_for_class):
"""Finds all attributes in `new_attrs` that are instances of
`look_for_class`. The ``.name`` attribute is set for any matching objects.
Returns them as a list.
"""
result = []
for attr, value in new_attrs.items():
if isinstance(value, look_for_class):
value.name = attr
delattr(cls, attr)
result.append(value)
return result
class CreateNewSQLObject:
"""
Dummy singleton to use in place of an ID, to signal we want
a new object.
"""
pass
class sqlmeta(with_metaclass(declarative.DeclarativeMeta, object)):
"""
This object is the object we use to keep track of all sorts of
information. Subclasses are made for each SQLObject subclass
(dynamically if necessary), and instances are created to go
alongside every SQLObject instance.
"""
table = None
idName = None
idSize = None # Allowed values are 'TINY/SMALL/MEDIUM/BIG/None'
idSequence = None
# This function is used to coerce IDs into the proper format,
# so you should replace it with str, or another function, if you
# aren't using integer IDs
idType = int
style = None
lazyUpdate = False
defaultOrder = None
cacheValues = True
registry = None
fromDatabase = False
# Default is false, but we set it to true for the *instance*
# when necessary: (bad clever? maybe)
expired = False
# This is a mapping from column names to SOCol (or subclass)
# instances:
columns = {}
columnList = []
# This is a mapping from column names to Col (or subclass)
# instances; these objects don't have the logic that the SOCol
# objects do, and are not attached to this class closely.
columnDefinitions = {}
# These are lists of the join and index objects:
indexes = []
indexDefinitions = []
joins = []
joinDefinitions = []
# These attributes shouldn't be shared with superclasses:
_unshared_attributes = ['table', 'columns', 'childName']
# These are internal bookkeeping attributes; the class-level
# definition is a default for the instances, instances will
# reset these values.
# When an object is being created, it has an instance
# variable _creating, which is true. This way all the
# setters can be captured until the object is complete,
# and then the row is inserted into the database. Once
# that happens, _creating is deleted from the instance,
# and only the class variable (which is always false) is
# left.
_creating = False
_obsolete = False
# Sometimes an intance is attached to a connection, not
# globally available. In that case, self.sqlmeta._perConnection
# will be true. It's false by default:
_perConnection = False
# Inheritance definitions:
parentClass = None # A reference to the parent class
childClasses = {} # References to child classes, keyed by childName
childName = None # Class name for inheritance child object creation
# Does the row require syncing?
dirty = False
# Default encoding for UnicodeCol's
dbEncoding = None
def __classinit__(cls, new_attrs):
for attr in cls._unshared_attributes:
if attr not in new_attrs:
setattr(cls, attr, None)
declarative.setup_attributes(cls, new_attrs)
def __init__(self, instance):
self.instance = weakref.proxy(instance)
@classmethod
def send(cls, signal, *args, **kw):
events.send(signal, cls.soClass, *args, **kw)
@classmethod
def setClass(cls, soClass):
if cls.idType not in (int, str):
raise TypeError('sqlmeta.idType must be int or str, not %r'
% cls.idType)
if cls.idSize not in ('TINY', 'SMALL', 'MEDIUM', 'BIG', None):
raise ValueError(
"sqlmeta.idType must be 'TINY', 'SMALL', 'MEDIUM', 'BIG' "
"or None, not %r" % cls.idSize)
cls.soClass = soClass
if not cls.style:
cls.style = styles.defaultStyle
try:
if cls.soClass._connection and cls.soClass._connection.style:
cls.style = cls.soClass._connection.style
except AttributeError:
pass
if cls.table is None:
cls.table = cls.style.pythonClassToDBTable(cls.soClass.__name__)
if cls.idName is None:
cls.idName = cls.style.idForTable(cls.table)
# plainSetters are columns that haven't been overridden by the
# user, so we can contact the database directly to set them.
# Note that these can't set these in the SQLObject class
# itself, because they specific to this subclass of SQLObject,
# and cannot be shared among classes.
cls._plainSetters = {}
cls._plainGetters = {}
cls._plainForeignSetters = {}
cls._plainForeignGetters = {}
cls._plainJoinGetters = {}
cls._plainJoinAdders = {}
cls._plainJoinRemovers = {}
# This is a dictionary of columnName: columnObject
# None of these objects can be shared with superclasses
cls.columns = {}
cls.columnList = []
# These, however, can be shared:
cls.columnDefinitions = cls.columnDefinitions.copy()
cls.indexes = []
cls.indexDefinitions = cls.indexDefinitions[:]
cls.joins = []
cls.joinDefinitions = cls.joinDefinitions[:]
############################################################
# Adding special values, like columns and indexes
############################################################
########################################
# Column handling
########################################
@classmethod
def addColumn(cls, columnDef, changeSchema=False, connection=None):
post_funcs = []
cls.send(events.AddColumnSignal, cls.soClass, connection,
columnDef.name, columnDef, changeSchema, post_funcs)
sqlmeta = cls
soClass = cls.soClass
del cls
column = columnDef.withClass(soClass)
name = column.name
assert name != 'id', (
"The 'id' column is implicit, and should not be defined as "
"a column")
assert name not in sqlmeta.columns, (
"The class %s.%s already has a column %r (%r), you cannot "
"add the column %r"
% (soClass.__module__, soClass.__name__, name,
sqlmeta.columnDefinitions[name], columnDef))
# Collect columns from the parent classes to test
# if the column is not in a parent class
parent_columns = []
for base in soClass.__bases__:
if hasattr(base, "sqlmeta"):
parent_columns.extend(base.sqlmeta.columns.keys())
if hasattr(soClass, name):
assert (name in parent_columns) or (name == "childName"), (
"The class %s.%s already has a variable or method %r, "
"you cannot add the column %r" % (
soClass.__module__, soClass.__name__, name, name))
sqlmeta.columnDefinitions[name] = columnDef
sqlmeta.columns[name] = column
# A stable-ordered version of the list...
sqlmeta.columnList.append(column)
###################################################
# Create the getter function(s). We'll start by
# creating functions like _SO_get_columnName,
# then if there's no function named _get_columnName
# we'll alias that to _SO_get_columnName. This
# allows a sort of super call, even though there's
# no superclass that defines the database access.
if sqlmeta.cacheValues:
# We create a method here, which is just a function
# that takes "self" as the first argument.
getter = eval(
'lambda self: self._SO_loadValue(%s)' %
repr(instanceName(name)))
else:
# If we aren't caching values, we just call the
# function _SO_getValue, which fetches from the
# database.
getter = eval('lambda self: self._SO_getValue(%s)' % repr(name))
setattr(soClass, rawGetterName(name), getter)
# Here if the _get_columnName method isn't in the
# definition, we add it with the default
# _SO_get_columnName definition.
if not hasattr(soClass, getterName(name)) or (name == 'childName'):
setattr(soClass, getterName(name), getter)
sqlmeta._plainGetters[name] = 1
#################################################
# Create the setter function(s)
# Much like creating the getters, we will create
# _SO_set_columnName methods, and then alias them
# to _set_columnName if the user hasn't defined
# those methods themself.
# @@: This is lame; immutable right now makes it unsettable,
# making the table read-only
if not column.immutable:
# We start by just using the _SO_setValue method
setter = eval(
'lambda self, val: self._SO_setValue'
'(%s, val, self.%s, self.%s)' % (
repr(name),
'_SO_from_python_%s' % name, '_SO_to_python_%s' % name))
setattr(soClass, '_SO_from_python_%s' % name, column.from_python)
setattr(soClass, '_SO_to_python_%s' % name, column.to_python)
setattr(soClass, rawSetterName(name), setter)
# Then do the aliasing
if not hasattr(soClass, setterName(name)) or (name == 'childName'):
setattr(soClass, setterName(name), setter)
# We keep track of setters that haven't been
# overridden, because we can combine these
# set columns into one SQL UPDATE query.
sqlmeta._plainSetters[name] = 1
##################################################
# Here we check if the column is a foreign key, in
# which case we need to make another method that
# fetches the key and constructs the sister
# SQLObject instance.
if column.foreignKey:
# We go through the standard _SO_get_columnName deal
# we're giving the object, not the ID of the
# object this time:
origName = column.origName
if sqlmeta.cacheValues:
# self._SO_class_className is a reference
# to the class in question.
getter = eval(
'lambda self: self._SO_foreignKey'
'(self._SO_loadValue(%r), self._SO_class_%s, %s)' %
(instanceName(name), column.foreignKey,
column.refColumn and repr(column.refColumn)))
else:
# Same non-caching version as above.
getter = eval(
'lambda self: self._SO_foreignKey'
'(self._SO_getValue(%s), self._SO_class_%s, %s)' %
(repr(name), column.foreignKey,
column.refColumn and repr(column.refColumn)))
setattr(soClass, rawGetterName(origName), getter)
# And we set the _get_columnName version
if not hasattr(soClass, getterName(origName)):
setattr(soClass, getterName(origName), getter)
sqlmeta._plainForeignGetters[origName] = 1
if not column.immutable:
# The setter just gets the ID of the object,
# and then sets the real column.
setter = eval(
'lambda self, val: '
'setattr(self, %s, self._SO_getID(val, %s))' %
(repr(name), column.refColumn and repr(column.refColumn)))
setattr(soClass, rawSetterName(origName), setter)
if not hasattr(soClass, setterName(origName)):
setattr(soClass, setterName(origName), setter)
sqlmeta._plainForeignSetters[origName] = 1
classregistry.registry(sqlmeta.registry).addClassCallback(
column.foreignKey,
lambda foreign, me, attr: setattr(me, attr, foreign),
soClass, '_SO_class_%s' % column.foreignKey)
if column.alternateMethodName:
func = eval(
'lambda cls, val, connection=None: '
'cls._SO_fetchAlternateID(%s, %s, val, connection=connection)'
% (repr(column.name), repr(column.dbName)))
setattr(soClass, column.alternateMethodName, classmethod(func))
if changeSchema:
conn = connection or soClass._connection
conn.addColumn(sqlmeta.table, column)
if soClass._SO_finishedClassCreation:
makeProperties(soClass)
for func in post_funcs:
func(soClass, column)
@classmethod
def addColumnsFromDatabase(sqlmeta, connection=None):
soClass = sqlmeta.soClass
conn = connection or soClass._connection
for columnDef in conn.columnsFromSchema(sqlmeta.table, soClass):
if columnDef.name not in sqlmeta.columnDefinitions:
if isinstance(columnDef.name, unicode_type) and PY2:
columnDef.name = columnDef.name.encode('ascii')
sqlmeta.addColumn(columnDef)
@classmethod
def delColumn(cls, column, changeSchema=False, connection=None):
sqlmeta = cls
soClass = sqlmeta.soClass
if isinstance(column, str):
if column in sqlmeta.columns:
column = sqlmeta.columns[column]
elif column + 'ID' in sqlmeta.columns:
column = sqlmeta.columns[column + 'ID']
else:
raise ValueError('Unknown column ' + column)
if isinstance(column, col.Col):
for c in sqlmeta.columns.values():
if column is c.columnDef:
column = c
break
else:
raise IndexError(
"Column with definition %r not found" % column)
post_funcs = []
cls.send(events.DeleteColumnSignal, cls.soClass, connection,
column.name, column, post_funcs)
name = column.name
del sqlmeta.columns[name]
del sqlmeta.columnDefinitions[name]
sqlmeta.columnList.remove(column)
delattr(soClass, rawGetterName(name))
if name in sqlmeta._plainGetters:
delattr(soClass, getterName(name))
delattr(soClass, rawSetterName(name))
if name in sqlmeta._plainSetters:
delattr(soClass, setterName(name))
if column.foreignKey:
delattr(soClass,
rawGetterName(soClass.sqlmeta.style.
instanceIDAttrToAttr(name)))
if name in sqlmeta._plainForeignGetters:
delattr(soClass, getterName(name))
delattr(soClass,
rawSetterName(soClass.sqlmeta.style.
instanceIDAttrToAttr(name)))
if name in sqlmeta._plainForeignSetters:
delattr(soClass, setterName(name))
if column.alternateMethodName:
delattr(soClass, column.alternateMethodName)
if changeSchema:
conn = connection or soClass._connection
conn.delColumn(sqlmeta, column)
if soClass._SO_finishedClassCreation:
unmakeProperties(soClass)
makeProperties(soClass)
for func in post_funcs:
func(soClass, column)
########################################
# Join handling
########################################
@classmethod
def addJoin(cls, joinDef):
sqlmeta = cls
soClass = cls.soClass
# The name of the method we'll create. If it's
# automatically generated, it's generated by the
# join class.
join = joinDef.withClass(soClass)
meth = join.joinMethodName
sqlmeta.joins.append(join)
index = len(sqlmeta.joins) - 1
if joinDef not in sqlmeta.joinDefinitions:
sqlmeta.joinDefinitions.append(joinDef)
# The function fetches the join by index, and
# then lets the join object do the rest of the
# work:
func = eval(
'lambda self: self.sqlmeta.joins[%i].performJoin(self)' % index)
# And we do the standard _SO_get_... _get_... deal
setattr(soClass, rawGetterName(meth), func)
if not hasattr(soClass, getterName(meth)):
setattr(soClass, getterName(meth), func)
sqlmeta._plainJoinGetters[meth] = 1
# Some joins allow you to remove objects from the
# join.
if hasattr(join, 'remove'):
# Again, we let it do the remove, and we do the
# standard naming trick.
func = eval(
'lambda self, obj: self.sqlmeta.joins[%i].remove(self, obj)' %
index)
setattr(soClass, '_SO_remove' + join.addRemoveName, func)
if not hasattr(soClass, 'remove' + join.addRemoveName):
setattr(soClass, 'remove' + join.addRemoveName, func)
sqlmeta._plainJoinRemovers[meth] = 1
# Some joins allow you to add objects.
if hasattr(join, 'add'):
# And again...
func = eval(
'lambda self, obj: self.sqlmeta.joins[%i].add(self, obj)' %
index)
setattr(soClass, '_SO_add' + join.addRemoveName, func)
if not hasattr(soClass, 'add' + join.addRemoveName):
setattr(soClass, 'add' + join.addRemoveName, func)
sqlmeta._plainJoinAdders[meth] = 1
if soClass._SO_finishedClassCreation:
makeProperties(soClass)
@classmethod
def delJoin(sqlmeta, joinDef):
soClass = sqlmeta.soClass
for join in sqlmeta.joins:
# previously deleted joins will be None, so it must
# be skipped or it'll error out on the next line.
if join is None:
continue
if joinDef is join.joinDef:
break
else:
raise IndexError(
"Join %r not found in class %r (from %r)"
% (joinDef, soClass, sqlmeta.joins))
meth = join.joinMethodName
sqlmeta.joinDefinitions.remove(joinDef)
for i in range(len(sqlmeta.joins)):
if sqlmeta.joins[i] is join:
# Have to leave None, because we refer to joins
# by index.
sqlmeta.joins[i] = None
delattr(soClass, rawGetterName(meth))
if meth in sqlmeta._plainJoinGetters:
delattr(soClass, getterName(meth))
if hasattr(join, 'remove'):
delattr(soClass, '_SO_remove' + join.addRemovePrefix)
if meth in sqlmeta._plainJoinRemovers:
delattr(soClass, 'remove' + join.addRemovePrefix)
if hasattr(join, 'add'):
delattr(soClass, '_SO_add' + join.addRemovePrefix)
if meth in sqlmeta._plainJoinAdders:
delattr(soClass, 'add' + join.addRemovePrefix)
if soClass._SO_finishedClassCreation:
unmakeProperties(soClass)
makeProperties(soClass)
########################################
# Indexes
########################################
@classmethod
def addIndex(cls, indexDef):
cls.indexDefinitions.append(indexDef)
index = indexDef.withClass(cls.soClass)
cls.indexes.append(index)
setattr(cls.soClass, index.name, index)
########################################
# Utility methods
########################################
@classmethod
def getColumns(sqlmeta):
return sqlmeta.columns.copy()
def asDict(self):
"""
Return the object as a dictionary of columns to values.
"""
result = {}
for key in self.getColumns():
result[key] = getattr(self.instance, key)
result['id'] = self.instance.id
return result
@classmethod
def expireAll(sqlmeta, connection=None):
"""
Expire all instances of this class.
"""
soClass = sqlmeta.soClass
connection = connection or soClass._connection
cache_set = connection.cache
cache_set.weakrefAll(soClass)
for item in cache_set.getAll(soClass):
item.expire()
sqlhub = dbconnection.ConnectionHub()
# Turning it on gives earlier warning about things
# that will be deprecated (having this off we won't flood people
# with warnings right away).
warnings_level = 1
exception_level = None
# Current levels:
# 1) Actively deprecated
# 2) Deprecated after 1
# 3) Deprecated after 2
def deprecated(message, level=1, stacklevel=2):
if exception_level is not None and exception_level <= level:
raise NotImplementedError(message)
if warnings_level is not None and warnings_level <= level:
warnings.warn(message, DeprecationWarning, stacklevel=stacklevel)
# if sys.version_info[:2] < (2, 7):
# deprecated("Support for Python 2.6 has been declared obsolete "
# "and will be removed in the next release of SQLObject")
def setDeprecationLevel(warning=1, exception=None):
"""
Set the deprecation level for SQLObject. Low levels are more
actively being deprecated. Any warning at a level at or below
``warning`` will give a warning. Any warning at a level at or
below ``exception`` will give an exception. You can use a higher
``exception`` level for tests to help upgrade your code. ``None``
for either value means never warn or raise exceptions.
The levels currently mean:
1) Deprecated in current version. Will be removed in next version.
2) Planned to deprecate in next version, remove later.
3) Planned to deprecate sometime, remove sometime much later.
As the SQLObject versions progress, the deprecation level of
specific features will go down, indicating the advancing nature of
the feature's doom. We'll try to keep features at 1 for a major
revision.
As time continues there may be a level 0, which will give a useful
error message (better than ``AttributeError``) but where the
feature has been fully removed.
"""
global warnings_level, exception_level
warnings_level = warning
exception_level = exception
class _sqlmeta_attr(object):
def __init__(self, name, deprecation_level):
self.name = name
self.deprecation_level = deprecation_level
def __get__(self, obj, type=None):
if self.deprecation_level is not None:
deprecated(
'Use of this attribute should be replaced with '
'.sqlmeta.%s' % self.name, level=self.deprecation_level)
return getattr((type or obj).sqlmeta, self.name)
_postponed_local = local()
# SQLObject is the superclass for all SQLObject classes, of
# course. All the deeper magic is done in MetaSQLObject, and
# only lesser magic is done here. All the actual work is done
# here, though -- just automatic method generation (like
# methods and properties for each column) is done in
# MetaSQLObject.
class SQLObject(with_metaclass(declarative.DeclarativeMeta, object)):
_connection = sqlhub
sqlmeta = sqlmeta
# DSM: The _inheritable attribute controls wheter the class can by
# DSM: inherited 'logically' with a foreignKey and a back reference.
_inheritable = False # Is this class inheritable?
_parent = None # A reference to the parent instance
childName = None # Children name (to be able to get a subclass)
# The law of Demeter: the class should not call another classes by name
SelectResultsClass = SelectResults
def __classinit__(cls, new_attrs):
# This is true if we're initializing the SQLObject class,
# instead of a subclass:
is_base = cls.__bases__ == (object,)
cls._SO_setupSqlmeta(new_attrs, is_base)
implicitColumns = _collectAttributes(cls, new_attrs, col.Col)
implicitJoins = _collectAttributes(cls, new_attrs, joins.Join)
implicitIndexes = _collectAttributes(cls, new_attrs,
index.DatabaseIndex)
if not is_base:
cls._SO_cleanDeprecatedAttrs(new_attrs)
if '_connection' in new_attrs:
connection = new_attrs['_connection']
del cls._connection
assert 'connection' not in new_attrs
elif 'connection' in new_attrs:
connection = new_attrs['connection']
del cls.connection
else:
connection = None
cls._SO_finishedClassCreation = False
######################################################
# Set some attributes to their defaults, if necessary.
# First we get the connection:
if not connection and not getattr(cls, '_connection', None):
mod = sys.modules[cls.__module__]
# See if there's a __connection__ global in
# the module, use it if there is.
if hasattr(mod, '__connection__'):
connection = mod.__connection__
# Do not check hasattr(cls, '_connection') here - it is possible
# SQLObject parent class has a connection attribute that came
# from sqlhub, e.g.; check __dict__ only.
if connection and ('_connection' not in cls.__dict__):
cls.setConnection(connection)
sqlmeta = cls.sqlmeta
# We have to check if there are columns in the inherited
# _columns where the attribute has been set to None in this
# class. If so, then we need to remove that column from
# _columns.
for key in sqlmeta.columnDefinitions.keys():
if (key in new_attrs and new_attrs[key] is None):
del sqlmeta.columnDefinitions[key]
for column in sqlmeta.columnDefinitions.values():
sqlmeta.addColumn(column)
for column in implicitColumns:
sqlmeta.addColumn(column)
# Now the class is in an essentially OK-state, so we can
# set up any magic attributes:
declarative.setup_attributes(cls, new_attrs)
if sqlmeta.fromDatabase:
sqlmeta.addColumnsFromDatabase()
for j in implicitJoins:
sqlmeta.addJoin(j)
for i in implicitIndexes:
sqlmeta.addIndex(i)
def order_getter(o):
return o.creationOrder
sqlmeta.columnList.sort(key=order_getter)
sqlmeta.indexes.sort(key=order_getter)
sqlmeta.indexDefinitions.sort(key=order_getter)
# Joins cannot be sorted because addJoin created accessors
# that remember indexes.
# sqlmeta.joins.sort(key=order_getter)
sqlmeta.joinDefinitions.sort(key=order_getter)
# We don't setup the properties until we're finished with the
# batch adding of all the columns...
cls._notifyFinishClassCreation()
cls._SO_finishedClassCreation = True
makeProperties(cls)
# We use the magic "q" attribute for accessing lazy
# SQL where-clause generation. See the sql module for
# more.
if not is_base:
cls.q = sqlbuilder.SQLObjectTable(cls)
cls.j = sqlbuilder.SQLObjectTableWithJoins(cls)
classregistry.registry(sqlmeta.registry).addClass(cls)
@classmethod
def _SO_setupSqlmeta(cls, new_attrs, is_base):
"""
This fixes up the sqlmeta attribute. It handles both the case
where no sqlmeta was given (in which we need to create another
subclass), or the sqlmeta given doesn't have the proper
inheritance. Lastly it calls sqlmeta.setClass, which handles
much of the setup.
"""
if ('sqlmeta' not in new_attrs and not is_base):
# We have to create our own subclass, usually.
# type(className, bases_tuple, attr_dict) creates a new subclass.
cls.sqlmeta = type('sqlmeta', (cls.sqlmeta,), {})
if not issubclass(cls.sqlmeta, sqlmeta):
# We allow no superclass and an object superclass, instead
# of inheriting from sqlmeta; but in that case we replace
# the class and just move over its attributes:
assert cls.sqlmeta.__bases__ in ((), (object,)), (
"If you do not inherit your sqlmeta class from "
"sqlobject.sqlmeta, it must not inherit from any other "
"class (your sqlmeta inherits from: %s)"
% cls.sqlmeta.__bases__)
for base in cls.__bases__:
superclass = getattr(base, 'sqlmeta', None)
if superclass:
break
else:
assert 0, (
"No sqlmeta class could be found in any superclass "
"(while fixing up sqlmeta %r inheritance)"
% cls.sqlmeta)
values = dict(cls.sqlmeta.__dict__)
for key in list(values.keys()):
if key.startswith('__') and key.endswith('__'):
# Magic values shouldn't be passed through:
del values[key]
cls.sqlmeta = type('sqlmeta', (superclass,), values)
if not is_base: # Do not pollute the base sqlmeta class
cls.sqlmeta.setClass(cls)
@classmethod
def _SO_cleanDeprecatedAttrs(cls, new_attrs):
"""
This removes attributes on SQLObject subclasses that have
been deprecated; they are moved to the sqlmeta class, and
a deprecation warning is given.
"""
for attr in ():
if attr in new_attrs:
deprecated("%r is deprecated and read-only; please do "
"not use it in your classes until it is fully "
"deprecated" % attr, level=1, stacklevel=5)
@classmethod
def get(cls, id, connection=None, selectResults=None):
assert id is not None, \
'None is not a possible id for %s' % cls.__name__
id = cls.sqlmeta.idType(id)
if connection is None:
cache = cls._connection.cache
else:
cache = connection.cache
# This whole sequence comes from Cache.CacheFactory's
# behavior, where a None returned means a cache miss.
val = cache.get(id, cls)
if val is None:
try:
val = cls(_SO_fetch_no_create=1)
val._SO_validatorState = sqlbuilder.SQLObjectState(val)
val._init(id, connection, selectResults)
cache.put(id, cls, val)
finally:
cache.finishPut(cls)
elif selectResults and not val.sqlmeta.dirty:
val._SO_writeLock.acquire()
try:
val._SO_selectInit(selectResults)
val.sqlmeta.expired = False
finally:
val._SO_writeLock.release()
return val
@classmethod
def _notifyFinishClassCreation(cls):
pass
def _init(self, id, connection=None, selectResults=None):
assert id is not None
# This function gets called only when the object is
# created, unlike __init__ which would be called
# anytime the object was returned from cache.
self.id = id
self._SO_writeLock = threading.Lock()
# If no connection was given, we'll inherit the class
# instance variable which should have a _connection
# attribute.
if (connection is not None) and \
(getattr(self, '_connection', None) is not connection):
self._connection = connection
# Sometimes we need to know if this instance is
# global or tied to a particular connection.
# This flag tells us that:
self.sqlmeta._perConnection = True
if not selectResults:
dbNames = [col.dbName for col in self.sqlmeta.columnList]
selectResults = self._connection._SO_selectOne(self, dbNames)
if not selectResults:
raise SQLObjectNotFound(
"The object %s by the ID %s does not exist" % (
self.__class__.__name__, self.id))
self._SO_selectInit(selectResults)
self._SO_createValues = {}
self.sqlmeta.dirty = False
def _SO_loadValue(self, attrName):
try:
return getattr(self, attrName)