sqlobject 3.13.1__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. sqlobject/__init__.py +15 -0
  2. sqlobject/__version__.py +8 -0
  3. sqlobject/boundattributes.py +124 -0
  4. sqlobject/cache.py +376 -0
  5. sqlobject/classregistry.py +138 -0
  6. sqlobject/col.py +2187 -0
  7. sqlobject/compat.py +59 -0
  8. sqlobject/conftest.py +64 -0
  9. sqlobject/constraints.py +77 -0
  10. sqlobject/converters.py +278 -0
  11. sqlobject/dbconnection.py +1137 -0
  12. sqlobject/dberrors.py +54 -0
  13. sqlobject/declarative.py +211 -0
  14. sqlobject/events.py +365 -0
  15. sqlobject/firebird/__init__.py +8 -0
  16. sqlobject/firebird/firebirdconnection.py +463 -0
  17. sqlobject/include/__init__.py +1 -0
  18. sqlobject/include/hashcol.py +109 -0
  19. sqlobject/include/tests/__init__.py +1 -0
  20. sqlobject/include/tests/test_hashcol.py +84 -0
  21. sqlobject/index.py +189 -0
  22. sqlobject/inheritance/__init__.py +551 -0
  23. sqlobject/inheritance/iteration.py +106 -0
  24. sqlobject/inheritance/tests/__init__.py +1 -0
  25. sqlobject/inheritance/tests/test_aggregates.py +23 -0
  26. sqlobject/inheritance/tests/test_asdict.py +48 -0
  27. sqlobject/inheritance/tests/test_deep_inheritance.py +114 -0
  28. sqlobject/inheritance/tests/test_destroy_cascade.py +23 -0
  29. sqlobject/inheritance/tests/test_foreignKey.py +94 -0
  30. sqlobject/inheritance/tests/test_indexes.py +42 -0
  31. sqlobject/inheritance/tests/test_inheritance.py +164 -0
  32. sqlobject/inheritance/tests/test_inheritance_tree.py +50 -0
  33. sqlobject/joins.py +656 -0
  34. sqlobject/main.py +1880 -0
  35. sqlobject/manager/__init__.py +1 -0
  36. sqlobject/manager/command.py +1309 -0
  37. sqlobject/maxdb/__init__.py +8 -0
  38. sqlobject/maxdb/maxdbconnection.py +320 -0
  39. sqlobject/maxdb/readme.txt +27 -0
  40. sqlobject/mssql/__init__.py +8 -0
  41. sqlobject/mssql/mssqlconnection.py +417 -0
  42. sqlobject/mysql/__init__.py +8 -0
  43. sqlobject/mysql/mysqlconnection.py +562 -0
  44. sqlobject/postgres/__init__.py +8 -0
  45. sqlobject/postgres/pgconnection.py +619 -0
  46. sqlobject/sqlbuilder.py +1655 -0
  47. sqlobject/sqlite/__init__.py +8 -0
  48. sqlobject/sqlite/sqliteconnection.py +418 -0
  49. sqlobject/sresults.py +377 -0
  50. sqlobject/styles.py +174 -0
  51. sqlobject/sybase/__init__.py +8 -0
  52. sqlobject/sybase/sybaseconnection.py +183 -0
  53. sqlobject/tests/__init__.py +1 -0
  54. sqlobject/tests/dbtest.py +363 -0
  55. sqlobject/tests/test_ForeignKey.py +133 -0
  56. sqlobject/tests/test_ForeignKey_cascade.py +150 -0
  57. sqlobject/tests/test_NoneValuedResultItem.py +33 -0
  58. sqlobject/tests/test_SQLMultipleJoin.py +65 -0
  59. sqlobject/tests/test_SQLRelatedJoin.py +114 -0
  60. sqlobject/tests/test_SingleJoin.py +30 -0
  61. sqlobject/tests/test_aggregates.py +75 -0
  62. sqlobject/tests/test_aliases.py +49 -0
  63. sqlobject/tests/test_asdict.py +18 -0
  64. sqlobject/tests/test_auto.py +218 -0
  65. sqlobject/tests/test_basic.py +359 -0
  66. sqlobject/tests/test_blob.py +34 -0
  67. sqlobject/tests/test_boundattributes.py +67 -0
  68. sqlobject/tests/test_cache.py +57 -0
  69. sqlobject/tests/test_class_hash.py +22 -0
  70. sqlobject/tests/test_columns_order.py +18 -0
  71. sqlobject/tests/test_combining_joins.py +47 -0
  72. sqlobject/tests/test_comparison.py +24 -0
  73. sqlobject/tests/test_compat.py +10 -0
  74. sqlobject/tests/test_complex_sorting.py +77 -0
  75. sqlobject/tests/test_constraints.py +43 -0
  76. sqlobject/tests/test_converters.py +276 -0
  77. sqlobject/tests/test_create_drop.py +29 -0
  78. sqlobject/tests/test_csvexport.py +101 -0
  79. sqlobject/tests/test_csvimport.py +21 -0
  80. sqlobject/tests/test_cyclic_reference.py +55 -0
  81. sqlobject/tests/test_datetime.py +171 -0
  82. sqlobject/tests/test_dbconnection.py +17 -0
  83. sqlobject/tests/test_decimal.py +63 -0
  84. sqlobject/tests/test_declarative.py +83 -0
  85. sqlobject/tests/test_default_style.py +65 -0
  86. sqlobject/tests/test_delete.py +74 -0
  87. sqlobject/tests/test_distinct.py +36 -0
  88. sqlobject/tests/test_empty.py +22 -0
  89. sqlobject/tests/test_enum.py +75 -0
  90. sqlobject/tests/test_events.py +142 -0
  91. sqlobject/tests/test_exceptions.py +40 -0
  92. sqlobject/tests/test_expire.py +29 -0
  93. sqlobject/tests/test_groupBy.py +45 -0
  94. sqlobject/tests/test_identity.py +29 -0
  95. sqlobject/tests/test_indexes.py +100 -0
  96. sqlobject/tests/test_inheritance.py +35 -0
  97. sqlobject/tests/test_joins.py +160 -0
  98. sqlobject/tests/test_joins_conditional.py +143 -0
  99. sqlobject/tests/test_jsonbcol.py +53 -0
  100. sqlobject/tests/test_jsoncol.py +56 -0
  101. sqlobject/tests/test_lazy.py +165 -0
  102. sqlobject/tests/test_md5.py +13 -0
  103. sqlobject/tests/test_mysql.py +50 -0
  104. sqlobject/tests/test_new_joins.py +165 -0
  105. sqlobject/tests/test_parse_uri.py +147 -0
  106. sqlobject/tests/test_paste.py +92 -0
  107. sqlobject/tests/test_perConnection.py +20 -0
  108. sqlobject/tests/test_pickle.py +42 -0
  109. sqlobject/tests/test_picklecol.py +44 -0
  110. sqlobject/tests/test_postgres.py +82 -0
  111. sqlobject/tests/test_reparent_sqlmeta.py +33 -0
  112. sqlobject/tests/test_schema.py +30 -0
  113. sqlobject/tests/test_select.py +218 -0
  114. sqlobject/tests/test_select_through.py +63 -0
  115. sqlobject/tests/test_setters.py +23 -0
  116. sqlobject/tests/test_slice.py +59 -0
  117. sqlobject/tests/test_sorting.py +80 -0
  118. sqlobject/tests/test_sqlbuilder.py +134 -0
  119. sqlobject/tests/test_sqlbuilder_dbspecific.py +66 -0
  120. sqlobject/tests/test_sqlbuilder_importproxy.py +55 -0
  121. sqlobject/tests/test_sqlbuilder_joins_instances.py +90 -0
  122. sqlobject/tests/test_sqlite.py +160 -0
  123. sqlobject/tests/test_sqlmeta_idName.py +52 -0
  124. sqlobject/tests/test_sqlobject_admin.py +38 -0
  125. sqlobject/tests/test_string_id.py +79 -0
  126. sqlobject/tests/test_style.py +36 -0
  127. sqlobject/tests/test_subqueries.py +142 -0
  128. sqlobject/tests/test_transactions.py +122 -0
  129. sqlobject/tests/test_unicode.py +84 -0
  130. sqlobject/tests/test_uuidcol.py +40 -0
  131. sqlobject/tests/test_validation.py +125 -0
  132. sqlobject/tests/test_views.py +159 -0
  133. sqlobject/util/__init__.py +1 -0
  134. sqlobject/util/csvexport.py +210 -0
  135. sqlobject/util/csvimport.py +366 -0
  136. sqlobject/util/moduleloader.py +35 -0
  137. sqlobject/util/threadinglocal.py +6 -0
  138. sqlobject/versioning/__init__.py +120 -0
  139. sqlobject/versioning/test/__init__.py +0 -0
  140. sqlobject/versioning/test/test_version.py +183 -0
  141. sqlobject/views.py +151 -0
  142. sqlobject/wsgi_middleware.py +109 -0
  143. sqlobject-3.13.1.data/scripts/sqlobject-admin +36 -0
  144. sqlobject-3.13.1.data/scripts/sqlobject-convertOldURI +19 -0
  145. sqlobject-3.13.1.dist-info/METADATA +177 -0
  146. sqlobject-3.13.1.dist-info/RECORD +149 -0
  147. sqlobject-3.13.1.dist-info/WHEEL +6 -0
  148. sqlobject-3.13.1.dist-info/entry_points.txt +4 -0
  149. sqlobject-3.13.1.dist-info/top_level.txt +1 -0
sqlobject/main.py ADDED
@@ -0,0 +1,1880 @@
1
+ """
2
+ SQLObject
3
+ ---------
4
+
5
+ :author: Ian Bicking <ianb@colorstudy.com>
6
+
7
+ SQLObject is a object-relational mapper. See SQLObject.html or
8
+ SQLObject.rst for more.
9
+
10
+ With the help by Oleg Broytman and many other contributors.
11
+ See Authors.rst.
12
+
13
+ This program is free software; you can redistribute it and/or modify
14
+ it under the terms of the GNU Lesser General Public License as
15
+ published by the Free Software Foundation; either version 2.1 of the
16
+ License, or (at your option) any later version.
17
+
18
+ This program is distributed in the hope that it will be useful,
19
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
20
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
+ GNU General Public License for more details.
22
+
23
+ You should have received a copy of the GNU Lesser General Public
24
+ License along with this program; if not, write to the Free Software
25
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,
26
+ USA.
27
+ """
28
+ import sys
29
+ import threading
30
+ import weakref
31
+ import types
32
+ import warnings
33
+ from . import sqlbuilder
34
+ from . import dbconnection
35
+ from . import col
36
+ from . import styles
37
+ from . import joins
38
+ from . import index
39
+ from . import classregistry
40
+ from . import declarative
41
+ from . import events
42
+ from .sresults import SelectResults
43
+ from .util.threadinglocal import local
44
+ from sqlobject.compat import PY2, with_metaclass, string_type, unicode_type
45
+
46
+ if ((sys.version_info[0] == 2) and (sys.version_info[:2] < (2, 7))) or \
47
+ ((sys.version_info[0] == 3) and (sys.version_info[:2] < (3, 4))):
48
+ raise ImportError("SQLObject requires Python 2.7 or 3.4+")
49
+
50
+ if not PY2:
51
+ # alias for python 3 compatability
52
+ long = int
53
+
54
+ """
55
+ This thread-local storage is needed for RowCreatedSignals. It gathers
56
+ code-blocks to execute _after_ the whole hierachy of inherited SQLObjects
57
+ is created. See SQLObject._create
58
+ """
59
+
60
+ NoDefault = sqlbuilder.NoDefault
61
+
62
+
63
+ class SQLObjectNotFound(LookupError):
64
+ pass
65
+
66
+
67
+ class SQLObjectIntegrityError(Exception):
68
+ pass
69
+
70
+
71
+ def makeProperties(obj):
72
+ """
73
+ This function takes a dictionary of methods and finds
74
+ methods named like:
75
+ * _get_attr
76
+ * _set_attr
77
+ * _del_attr
78
+ * _doc_attr
79
+ Except for _doc_attr, these should be methods. It
80
+ then creates properties from these methods, like
81
+ property(_get_attr, _set_attr, _del_attr, _doc_attr).
82
+ Missing methods are okay.
83
+ """
84
+
85
+ if isinstance(obj, dict):
86
+ def setFunc(var, value):
87
+ obj[var] = value
88
+ d = obj
89
+ else:
90
+ def setFunc(var, value):
91
+ setattr(obj, var, value)
92
+ d = obj.__dict__
93
+
94
+ props = {}
95
+ for var, value in d.items():
96
+ if var.startswith('_set_'):
97
+ props.setdefault(var[5:], {})['set'] = value
98
+ elif var.startswith('_get_'):
99
+ props.setdefault(var[5:], {})['get'] = value
100
+ elif var.startswith('_del_'):
101
+ props.setdefault(var[5:], {})['del'] = value
102
+ elif var.startswith('_doc_'):
103
+ props.setdefault(var[5:], {})['doc'] = value
104
+ for var, setters in props.items():
105
+ if len(setters) == 1 and 'doc' in setters:
106
+ continue
107
+ if var in d:
108
+ if isinstance(d[var], (types.MethodType, types.FunctionType)):
109
+ warnings.warn(
110
+ "I tried to set the property %r, but it was "
111
+ "already set, as a method (%r). Methods have "
112
+ "significantly different semantics than properties, "
113
+ "and this may be a sign of a bug in your code."
114
+ % (var, d[var]))
115
+ continue
116
+ setFunc(var,
117
+ property(setters.get('get'), setters.get('set'),
118
+ setters.get('del'), setters.get('doc')))
119
+
120
+
121
+ def unmakeProperties(obj):
122
+ if isinstance(obj, dict):
123
+ def delFunc(obj, var):
124
+ del obj[var]
125
+ d = obj
126
+ else:
127
+ delFunc = delattr
128
+ d = obj.__dict__
129
+
130
+ for var, value in list(d.items()):
131
+ if isinstance(value, property):
132
+ for prop in [value.fget, value.fset, value.fdel]:
133
+ if prop and prop.__name__ not in d:
134
+ delFunc(obj, var)
135
+ break
136
+
137
+
138
+ def findDependencies(name, registry=None):
139
+ depends = []
140
+ for klass in classregistry.registry(registry).allClasses():
141
+ if findDependantColumns(name, klass):
142
+ depends.append(klass)
143
+ else:
144
+ for join in klass.sqlmeta.joins:
145
+ if isinstance(join, joins.SORelatedJoin) and \
146
+ join.otherClassName == name:
147
+ depends.append(klass)
148
+ break
149
+ return depends
150
+
151
+
152
+ def findDependantColumns(name, klass):
153
+ depends = []
154
+ for _col in klass.sqlmeta.columnList:
155
+ if _col.foreignKey == name and _col.cascade is not None:
156
+ depends.append(_col)
157
+ return depends
158
+
159
+
160
+ def _collectAttributes(cls, new_attrs, look_for_class):
161
+ """Finds all attributes in `new_attrs` that are instances of
162
+ `look_for_class`. The ``.name`` attribute is set for any matching objects.
163
+ Returns them as a list.
164
+
165
+ """
166
+ result = []
167
+ for attr, value in new_attrs.items():
168
+ if isinstance(value, look_for_class):
169
+ value.name = attr
170
+ delattr(cls, attr)
171
+ result.append(value)
172
+ return result
173
+
174
+
175
+ class CreateNewSQLObject:
176
+ """
177
+ Dummy singleton to use in place of an ID, to signal we want
178
+ a new object.
179
+ """
180
+ pass
181
+
182
+
183
+ class sqlmeta(with_metaclass(declarative.DeclarativeMeta, object)):
184
+ """
185
+ This object is the object we use to keep track of all sorts of
186
+ information. Subclasses are made for each SQLObject subclass
187
+ (dynamically if necessary), and instances are created to go
188
+ alongside every SQLObject instance.
189
+ """
190
+
191
+ table = None
192
+ idName = None
193
+ idSize = None # Allowed values are 'TINY/SMALL/MEDIUM/BIG/None'
194
+ idSequence = None
195
+ # This function is used to coerce IDs into the proper format,
196
+ # so you should replace it with str, or another function, if you
197
+ # aren't using integer IDs
198
+ idType = int
199
+ style = None
200
+ lazyUpdate = False
201
+ defaultOrder = None
202
+ cacheValues = True
203
+ registry = None
204
+ fromDatabase = False
205
+ # Default is false, but we set it to true for the *instance*
206
+ # when necessary: (bad clever? maybe)
207
+ expired = False
208
+
209
+ # This is a mapping from column names to SOCol (or subclass)
210
+ # instances:
211
+ columns = {}
212
+ columnList = []
213
+
214
+ # This is a mapping from column names to Col (or subclass)
215
+ # instances; these objects don't have the logic that the SOCol
216
+ # objects do, and are not attached to this class closely.
217
+ columnDefinitions = {}
218
+
219
+ # These are lists of the join and index objects:
220
+ indexes = []
221
+ indexDefinitions = []
222
+ joins = []
223
+ joinDefinitions = []
224
+
225
+ # These attributes shouldn't be shared with superclasses:
226
+ _unshared_attributes = ['table', 'columns', 'childName']
227
+
228
+ # These are internal bookkeeping attributes; the class-level
229
+ # definition is a default for the instances, instances will
230
+ # reset these values.
231
+
232
+ # When an object is being created, it has an instance
233
+ # variable _creating, which is true. This way all the
234
+ # setters can be captured until the object is complete,
235
+ # and then the row is inserted into the database. Once
236
+ # that happens, _creating is deleted from the instance,
237
+ # and only the class variable (which is always false) is
238
+ # left.
239
+ _creating = False
240
+ _obsolete = False
241
+ # Sometimes an intance is attached to a connection, not
242
+ # globally available. In that case, self.sqlmeta._perConnection
243
+ # will be true. It's false by default:
244
+ _perConnection = False
245
+
246
+ # Inheritance definitions:
247
+ parentClass = None # A reference to the parent class
248
+ childClasses = {} # References to child classes, keyed by childName
249
+ childName = None # Class name for inheritance child object creation
250
+
251
+ # Does the row require syncing?
252
+ dirty = False
253
+
254
+ # Default encoding for UnicodeCol's
255
+ dbEncoding = None
256
+
257
+ def __classinit__(cls, new_attrs):
258
+ for attr in cls._unshared_attributes:
259
+ if attr not in new_attrs:
260
+ setattr(cls, attr, None)
261
+ declarative.setup_attributes(cls, new_attrs)
262
+
263
+ def __init__(self, instance):
264
+ self.instance = weakref.proxy(instance)
265
+
266
+ @classmethod
267
+ def send(cls, signal, *args, **kw):
268
+ events.send(signal, cls.soClass, *args, **kw)
269
+
270
+ @classmethod
271
+ def setClass(cls, soClass):
272
+ if cls.idType not in (int, str):
273
+ raise TypeError('sqlmeta.idType must be int or str, not %r'
274
+ % cls.idType)
275
+ if cls.idSize not in ('TINY', 'SMALL', 'MEDIUM', 'BIG', None):
276
+ raise ValueError(
277
+ "sqlmeta.idType must be 'TINY', 'SMALL', 'MEDIUM', 'BIG' "
278
+ "or None, not %r" % cls.idSize)
279
+ cls.soClass = soClass
280
+ if not cls.style:
281
+ cls.style = styles.defaultStyle
282
+ try:
283
+ if cls.soClass._connection and cls.soClass._connection.style:
284
+ cls.style = cls.soClass._connection.style
285
+ except AttributeError:
286
+ pass
287
+ if cls.table is None:
288
+ cls.table = cls.style.pythonClassToDBTable(cls.soClass.__name__)
289
+ if cls.idName is None:
290
+ cls.idName = cls.style.idForTable(cls.table)
291
+
292
+ # plainSetters are columns that haven't been overridden by the
293
+ # user, so we can contact the database directly to set them.
294
+ # Note that these can't set these in the SQLObject class
295
+ # itself, because they specific to this subclass of SQLObject,
296
+ # and cannot be shared among classes.
297
+ cls._plainSetters = {}
298
+ cls._plainGetters = {}
299
+ cls._plainForeignSetters = {}
300
+ cls._plainForeignGetters = {}
301
+ cls._plainJoinGetters = {}
302
+ cls._plainJoinAdders = {}
303
+ cls._plainJoinRemovers = {}
304
+
305
+ # This is a dictionary of columnName: columnObject
306
+ # None of these objects can be shared with superclasses
307
+ cls.columns = {}
308
+ cls.columnList = []
309
+ # These, however, can be shared:
310
+ cls.columnDefinitions = cls.columnDefinitions.copy()
311
+ cls.indexes = []
312
+ cls.indexDefinitions = cls.indexDefinitions[:]
313
+ cls.joins = []
314
+ cls.joinDefinitions = cls.joinDefinitions[:]
315
+
316
+ ############################################################
317
+ # Adding special values, like columns and indexes
318
+ ############################################################
319
+
320
+ ########################################
321
+ # Column handling
322
+ ########################################
323
+
324
+ @classmethod
325
+ def addColumn(cls, columnDef, changeSchema=False, connection=None):
326
+ post_funcs = []
327
+ cls.send(events.AddColumnSignal, cls.soClass, connection,
328
+ columnDef.name, columnDef, changeSchema, post_funcs)
329
+ sqlmeta = cls
330
+ soClass = cls.soClass
331
+ del cls
332
+ column = columnDef.withClass(soClass)
333
+ name = column.name
334
+ assert name != 'id', (
335
+ "The 'id' column is implicit, and should not be defined as "
336
+ "a column")
337
+ assert name not in sqlmeta.columns, (
338
+ "The class %s.%s already has a column %r (%r), you cannot "
339
+ "add the column %r"
340
+ % (soClass.__module__, soClass.__name__, name,
341
+ sqlmeta.columnDefinitions[name], columnDef))
342
+ # Collect columns from the parent classes to test
343
+ # if the column is not in a parent class
344
+ parent_columns = []
345
+ for base in soClass.__bases__:
346
+ if hasattr(base, "sqlmeta"):
347
+ parent_columns.extend(base.sqlmeta.columns.keys())
348
+ if hasattr(soClass, name):
349
+ assert (name in parent_columns) or (name == "childName"), (
350
+ "The class %s.%s already has a variable or method %r, "
351
+ "you cannot add the column %r" % (
352
+ soClass.__module__, soClass.__name__, name, name))
353
+ sqlmeta.columnDefinitions[name] = columnDef
354
+ sqlmeta.columns[name] = column
355
+ # A stable-ordered version of the list...
356
+ sqlmeta.columnList.append(column)
357
+
358
+ ###################################################
359
+ # Create the getter function(s). We'll start by
360
+ # creating functions like _SO_get_columnName,
361
+ # then if there's no function named _get_columnName
362
+ # we'll alias that to _SO_get_columnName. This
363
+ # allows a sort of super call, even though there's
364
+ # no superclass that defines the database access.
365
+ if sqlmeta.cacheValues:
366
+ # We create a method here, which is just a function
367
+ # that takes "self" as the first argument.
368
+ getter = eval(
369
+ 'lambda self: self._SO_loadValue(%s)' %
370
+ repr(instanceName(name)))
371
+
372
+ else:
373
+ # If we aren't caching values, we just call the
374
+ # function _SO_getValue, which fetches from the
375
+ # database.
376
+ getter = eval('lambda self: self._SO_getValue(%s)' % repr(name))
377
+ setattr(soClass, rawGetterName(name), getter)
378
+
379
+ # Here if the _get_columnName method isn't in the
380
+ # definition, we add it with the default
381
+ # _SO_get_columnName definition.
382
+ if not hasattr(soClass, getterName(name)) or (name == 'childName'):
383
+ setattr(soClass, getterName(name), getter)
384
+ sqlmeta._plainGetters[name] = 1
385
+
386
+ #################################################
387
+ # Create the setter function(s)
388
+ # Much like creating the getters, we will create
389
+ # _SO_set_columnName methods, and then alias them
390
+ # to _set_columnName if the user hasn't defined
391
+ # those methods themself.
392
+
393
+ # @@: This is lame; immutable right now makes it unsettable,
394
+ # making the table read-only
395
+ if not column.immutable:
396
+ # We start by just using the _SO_setValue method
397
+ setter = eval(
398
+ 'lambda self, val: self._SO_setValue'
399
+ '(%s, val, self.%s, self.%s)' % (
400
+ repr(name),
401
+ '_SO_from_python_%s' % name, '_SO_to_python_%s' % name))
402
+ setattr(soClass, '_SO_from_python_%s' % name, column.from_python)
403
+ setattr(soClass, '_SO_to_python_%s' % name, column.to_python)
404
+ setattr(soClass, rawSetterName(name), setter)
405
+ # Then do the aliasing
406
+ if not hasattr(soClass, setterName(name)) or (name == 'childName'):
407
+ setattr(soClass, setterName(name), setter)
408
+ # We keep track of setters that haven't been
409
+ # overridden, because we can combine these
410
+ # set columns into one SQL UPDATE query.
411
+ sqlmeta._plainSetters[name] = 1
412
+
413
+ ##################################################
414
+ # Here we check if the column is a foreign key, in
415
+ # which case we need to make another method that
416
+ # fetches the key and constructs the sister
417
+ # SQLObject instance.
418
+ if column.foreignKey:
419
+
420
+ # We go through the standard _SO_get_columnName deal
421
+ # we're giving the object, not the ID of the
422
+ # object this time:
423
+ origName = column.origName
424
+ if sqlmeta.cacheValues:
425
+ # self._SO_class_className is a reference
426
+ # to the class in question.
427
+ getter = eval(
428
+ 'lambda self: self._SO_foreignKey'
429
+ '(self._SO_loadValue(%r), self._SO_class_%s, %s)' %
430
+ (instanceName(name), column.foreignKey,
431
+ column.refColumn and repr(column.refColumn)))
432
+ else:
433
+ # Same non-caching version as above.
434
+ getter = eval(
435
+ 'lambda self: self._SO_foreignKey'
436
+ '(self._SO_getValue(%s), self._SO_class_%s, %s)' %
437
+ (repr(name), column.foreignKey,
438
+ column.refColumn and repr(column.refColumn)))
439
+ setattr(soClass, rawGetterName(origName), getter)
440
+
441
+ # And we set the _get_columnName version
442
+ if not hasattr(soClass, getterName(origName)):
443
+ setattr(soClass, getterName(origName), getter)
444
+ sqlmeta._plainForeignGetters[origName] = 1
445
+
446
+ if not column.immutable:
447
+ # The setter just gets the ID of the object,
448
+ # and then sets the real column.
449
+ setter = eval(
450
+ 'lambda self, val: '
451
+ 'setattr(self, %s, self._SO_getID(val, %s))' %
452
+ (repr(name), column.refColumn and repr(column.refColumn)))
453
+ setattr(soClass, rawSetterName(origName), setter)
454
+ if not hasattr(soClass, setterName(origName)):
455
+ setattr(soClass, setterName(origName), setter)
456
+ sqlmeta._plainForeignSetters[origName] = 1
457
+
458
+ classregistry.registry(sqlmeta.registry).addClassCallback(
459
+ column.foreignKey,
460
+ lambda foreign, me, attr: setattr(me, attr, foreign),
461
+ soClass, '_SO_class_%s' % column.foreignKey)
462
+
463
+ if column.alternateMethodName:
464
+ func = eval(
465
+ 'lambda cls, val, connection=None: '
466
+ 'cls._SO_fetchAlternateID(%s, %s, val, connection=connection)'
467
+ % (repr(column.name), repr(column.dbName)))
468
+ setattr(soClass, column.alternateMethodName, classmethod(func))
469
+
470
+ if changeSchema:
471
+ conn = connection or soClass._connection
472
+ conn.addColumn(sqlmeta.table, column)
473
+
474
+ if soClass._SO_finishedClassCreation:
475
+ makeProperties(soClass)
476
+
477
+ for func in post_funcs:
478
+ func(soClass, column)
479
+
480
+ @classmethod
481
+ def addColumnsFromDatabase(sqlmeta, connection=None):
482
+ soClass = sqlmeta.soClass
483
+ conn = connection or soClass._connection
484
+ for columnDef in conn.columnsFromSchema(sqlmeta.table, soClass):
485
+ if columnDef.name not in sqlmeta.columnDefinitions:
486
+ if isinstance(columnDef.name, unicode_type) and PY2:
487
+ columnDef.name = columnDef.name.encode('ascii')
488
+ sqlmeta.addColumn(columnDef)
489
+
490
+ @classmethod
491
+ def delColumn(cls, column, changeSchema=False, connection=None):
492
+ sqlmeta = cls
493
+ soClass = sqlmeta.soClass
494
+ if isinstance(column, str):
495
+ if column in sqlmeta.columns:
496
+ column = sqlmeta.columns[column]
497
+ elif column + 'ID' in sqlmeta.columns:
498
+ column = sqlmeta.columns[column + 'ID']
499
+ else:
500
+ raise ValueError('Unknown column ' + column)
501
+ if isinstance(column, col.Col):
502
+ for c in sqlmeta.columns.values():
503
+ if column is c.columnDef:
504
+ column = c
505
+ break
506
+ else:
507
+ raise IndexError(
508
+ "Column with definition %r not found" % column)
509
+ post_funcs = []
510
+ cls.send(events.DeleteColumnSignal, cls.soClass, connection,
511
+ column.name, column, post_funcs)
512
+ name = column.name
513
+ del sqlmeta.columns[name]
514
+ del sqlmeta.columnDefinitions[name]
515
+ sqlmeta.columnList.remove(column)
516
+ delattr(soClass, rawGetterName(name))
517
+ if name in sqlmeta._plainGetters:
518
+ delattr(soClass, getterName(name))
519
+ delattr(soClass, rawSetterName(name))
520
+ if name in sqlmeta._plainSetters:
521
+ delattr(soClass, setterName(name))
522
+ if column.foreignKey:
523
+ delattr(soClass,
524
+ rawGetterName(soClass.sqlmeta.style.
525
+ instanceIDAttrToAttr(name)))
526
+ if name in sqlmeta._plainForeignGetters:
527
+ delattr(soClass, getterName(name))
528
+ delattr(soClass,
529
+ rawSetterName(soClass.sqlmeta.style.
530
+ instanceIDAttrToAttr(name)))
531
+ if name in sqlmeta._plainForeignSetters:
532
+ delattr(soClass, setterName(name))
533
+ if column.alternateMethodName:
534
+ delattr(soClass, column.alternateMethodName)
535
+
536
+ if changeSchema:
537
+ conn = connection or soClass._connection
538
+ conn.delColumn(sqlmeta, column)
539
+
540
+ if soClass._SO_finishedClassCreation:
541
+ unmakeProperties(soClass)
542
+ makeProperties(soClass)
543
+
544
+ for func in post_funcs:
545
+ func(soClass, column)
546
+
547
+ ########################################
548
+ # Join handling
549
+ ########################################
550
+
551
+ @classmethod
552
+ def addJoin(cls, joinDef):
553
+ sqlmeta = cls
554
+ soClass = cls.soClass
555
+ # The name of the method we'll create. If it's
556
+ # automatically generated, it's generated by the
557
+ # join class.
558
+ join = joinDef.withClass(soClass)
559
+ meth = join.joinMethodName
560
+
561
+ sqlmeta.joins.append(join)
562
+ index = len(sqlmeta.joins) - 1
563
+ if joinDef not in sqlmeta.joinDefinitions:
564
+ sqlmeta.joinDefinitions.append(joinDef)
565
+
566
+ # The function fetches the join by index, and
567
+ # then lets the join object do the rest of the
568
+ # work:
569
+ func = eval(
570
+ 'lambda self: self.sqlmeta.joins[%i].performJoin(self)' % index)
571
+
572
+ # And we do the standard _SO_get_... _get_... deal
573
+ setattr(soClass, rawGetterName(meth), func)
574
+ if not hasattr(soClass, getterName(meth)):
575
+ setattr(soClass, getterName(meth), func)
576
+ sqlmeta._plainJoinGetters[meth] = 1
577
+
578
+ # Some joins allow you to remove objects from the
579
+ # join.
580
+ if hasattr(join, 'remove'):
581
+ # Again, we let it do the remove, and we do the
582
+ # standard naming trick.
583
+ func = eval(
584
+ 'lambda self, obj: self.sqlmeta.joins[%i].remove(self, obj)' %
585
+ index)
586
+ setattr(soClass, '_SO_remove' + join.addRemoveName, func)
587
+ if not hasattr(soClass, 'remove' + join.addRemoveName):
588
+ setattr(soClass, 'remove' + join.addRemoveName, func)
589
+ sqlmeta._plainJoinRemovers[meth] = 1
590
+
591
+ # Some joins allow you to add objects.
592
+ if hasattr(join, 'add'):
593
+ # And again...
594
+ func = eval(
595
+ 'lambda self, obj: self.sqlmeta.joins[%i].add(self, obj)' %
596
+ index)
597
+ setattr(soClass, '_SO_add' + join.addRemoveName, func)
598
+ if not hasattr(soClass, 'add' + join.addRemoveName):
599
+ setattr(soClass, 'add' + join.addRemoveName, func)
600
+ sqlmeta._plainJoinAdders[meth] = 1
601
+
602
+ if soClass._SO_finishedClassCreation:
603
+ makeProperties(soClass)
604
+
605
+ @classmethod
606
+ def delJoin(sqlmeta, joinDef):
607
+ soClass = sqlmeta.soClass
608
+ for join in sqlmeta.joins:
609
+ # previously deleted joins will be None, so it must
610
+ # be skipped or it'll error out on the next line.
611
+ if join is None:
612
+ continue
613
+ if joinDef is join.joinDef:
614
+ break
615
+ else:
616
+ raise IndexError(
617
+ "Join %r not found in class %r (from %r)"
618
+ % (joinDef, soClass, sqlmeta.joins))
619
+ meth = join.joinMethodName
620
+ sqlmeta.joinDefinitions.remove(joinDef)
621
+ for i in range(len(sqlmeta.joins)):
622
+ if sqlmeta.joins[i] is join:
623
+ # Have to leave None, because we refer to joins
624
+ # by index.
625
+ sqlmeta.joins[i] = None
626
+ delattr(soClass, rawGetterName(meth))
627
+ if meth in sqlmeta._plainJoinGetters:
628
+ delattr(soClass, getterName(meth))
629
+ if hasattr(join, 'remove'):
630
+ delattr(soClass, '_SO_remove' + join.addRemovePrefix)
631
+ if meth in sqlmeta._plainJoinRemovers:
632
+ delattr(soClass, 'remove' + join.addRemovePrefix)
633
+ if hasattr(join, 'add'):
634
+ delattr(soClass, '_SO_add' + join.addRemovePrefix)
635
+ if meth in sqlmeta._plainJoinAdders:
636
+ delattr(soClass, 'add' + join.addRemovePrefix)
637
+
638
+ if soClass._SO_finishedClassCreation:
639
+ unmakeProperties(soClass)
640
+ makeProperties(soClass)
641
+
642
+ ########################################
643
+ # Indexes
644
+ ########################################
645
+
646
+ @classmethod
647
+ def addIndex(cls, indexDef):
648
+ cls.indexDefinitions.append(indexDef)
649
+ index = indexDef.withClass(cls.soClass)
650
+ cls.indexes.append(index)
651
+ setattr(cls.soClass, index.name, index)
652
+
653
+ ########################################
654
+ # Utility methods
655
+ ########################################
656
+
657
+ @classmethod
658
+ def getColumns(sqlmeta):
659
+ return sqlmeta.columns.copy()
660
+
661
+ def asDict(self):
662
+ """
663
+ Return the object as a dictionary of columns to values.
664
+ """
665
+ result = {}
666
+ for key in self.getColumns():
667
+ result[key] = getattr(self.instance, key)
668
+ result['id'] = self.instance.id
669
+ return result
670
+
671
+ @classmethod
672
+ def expireAll(sqlmeta, connection=None):
673
+ """
674
+ Expire all instances of this class.
675
+ """
676
+ soClass = sqlmeta.soClass
677
+ connection = connection or soClass._connection
678
+ cache_set = connection.cache
679
+ cache_set.weakrefAll(soClass)
680
+ for item in cache_set.getAll(soClass):
681
+ item.expire()
682
+
683
+
684
+ sqlhub = dbconnection.ConnectionHub()
685
+
686
+
687
+ # Turning it on gives earlier warning about things
688
+ # that will be deprecated (having this off we won't flood people
689
+ # with warnings right away).
690
+ warnings_level = 1
691
+ exception_level = None
692
+ # Current levels:
693
+ # 1) Actively deprecated
694
+ # 2) Deprecated after 1
695
+ # 3) Deprecated after 2
696
+
697
+
698
+ def deprecated(message, level=1, stacklevel=2):
699
+ if exception_level is not None and exception_level <= level:
700
+ raise NotImplementedError(message)
701
+ if warnings_level is not None and warnings_level <= level:
702
+ warnings.warn(message, DeprecationWarning, stacklevel=stacklevel)
703
+
704
+ # if sys.version_info[:2] < (2, 7):
705
+ # deprecated("Support for Python 2.6 has been declared obsolete "
706
+ # "and will be removed in the next release of SQLObject")
707
+
708
+
709
+ def setDeprecationLevel(warning=1, exception=None):
710
+ """
711
+ Set the deprecation level for SQLObject. Low levels are more
712
+ actively being deprecated. Any warning at a level at or below
713
+ ``warning`` will give a warning. Any warning at a level at or
714
+ below ``exception`` will give an exception. You can use a higher
715
+ ``exception`` level for tests to help upgrade your code. ``None``
716
+ for either value means never warn or raise exceptions.
717
+
718
+ The levels currently mean:
719
+
720
+ 1) Deprecated in current version. Will be removed in next version.
721
+
722
+ 2) Planned to deprecate in next version, remove later.
723
+
724
+ 3) Planned to deprecate sometime, remove sometime much later.
725
+
726
+ As the SQLObject versions progress, the deprecation level of
727
+ specific features will go down, indicating the advancing nature of
728
+ the feature's doom. We'll try to keep features at 1 for a major
729
+ revision.
730
+
731
+ As time continues there may be a level 0, which will give a useful
732
+ error message (better than ``AttributeError``) but where the
733
+ feature has been fully removed.
734
+ """
735
+ global warnings_level, exception_level
736
+ warnings_level = warning
737
+ exception_level = exception
738
+
739
+
740
+ class _sqlmeta_attr(object):
741
+
742
+ def __init__(self, name, deprecation_level):
743
+ self.name = name
744
+ self.deprecation_level = deprecation_level
745
+
746
+ def __get__(self, obj, type=None):
747
+ if self.deprecation_level is not None:
748
+ deprecated(
749
+ 'Use of this attribute should be replaced with '
750
+ '.sqlmeta.%s' % self.name, level=self.deprecation_level)
751
+ return getattr((type or obj).sqlmeta, self.name)
752
+
753
+
754
+ _postponed_local = local()
755
+
756
+
757
+ # SQLObject is the superclass for all SQLObject classes, of
758
+ # course. All the deeper magic is done in MetaSQLObject, and
759
+ # only lesser magic is done here. All the actual work is done
760
+ # here, though -- just automatic method generation (like
761
+ # methods and properties for each column) is done in
762
+ # MetaSQLObject.
763
+
764
+
765
+ class SQLObject(with_metaclass(declarative.DeclarativeMeta, object)):
766
+
767
+ _connection = sqlhub
768
+
769
+ sqlmeta = sqlmeta
770
+
771
+ # DSM: The _inheritable attribute controls wheter the class can by
772
+ # DSM: inherited 'logically' with a foreignKey and a back reference.
773
+ _inheritable = False # Is this class inheritable?
774
+ _parent = None # A reference to the parent instance
775
+ childName = None # Children name (to be able to get a subclass)
776
+
777
+ # The law of Demeter: the class should not call another classes by name
778
+ SelectResultsClass = SelectResults
779
+
780
+ def __classinit__(cls, new_attrs):
781
+
782
+ # This is true if we're initializing the SQLObject class,
783
+ # instead of a subclass:
784
+ is_base = cls.__bases__ == (object,)
785
+
786
+ cls._SO_setupSqlmeta(new_attrs, is_base)
787
+
788
+ implicitColumns = _collectAttributes(cls, new_attrs, col.Col)
789
+ implicitJoins = _collectAttributes(cls, new_attrs, joins.Join)
790
+ implicitIndexes = _collectAttributes(cls, new_attrs,
791
+ index.DatabaseIndex)
792
+
793
+ if not is_base:
794
+ cls._SO_cleanDeprecatedAttrs(new_attrs)
795
+
796
+ if '_connection' in new_attrs:
797
+ connection = new_attrs['_connection']
798
+ del cls._connection
799
+ assert 'connection' not in new_attrs
800
+ elif 'connection' in new_attrs:
801
+ connection = new_attrs['connection']
802
+ del cls.connection
803
+ else:
804
+ connection = None
805
+
806
+ cls._SO_finishedClassCreation = False
807
+
808
+ ######################################################
809
+ # Set some attributes to their defaults, if necessary.
810
+ # First we get the connection:
811
+ if not connection and not getattr(cls, '_connection', None):
812
+ mod = sys.modules[cls.__module__]
813
+ # See if there's a __connection__ global in
814
+ # the module, use it if there is.
815
+ if hasattr(mod, '__connection__'):
816
+ connection = mod.__connection__
817
+
818
+ # Do not check hasattr(cls, '_connection') here - it is possible
819
+ # SQLObject parent class has a connection attribute that came
820
+ # from sqlhub, e.g.; check __dict__ only.
821
+ if connection and ('_connection' not in cls.__dict__):
822
+ cls.setConnection(connection)
823
+
824
+ sqlmeta = cls.sqlmeta
825
+
826
+ # We have to check if there are columns in the inherited
827
+ # _columns where the attribute has been set to None in this
828
+ # class. If so, then we need to remove that column from
829
+ # _columns.
830
+ for key in sqlmeta.columnDefinitions.keys():
831
+ if (key in new_attrs and new_attrs[key] is None):
832
+ del sqlmeta.columnDefinitions[key]
833
+
834
+ for column in sqlmeta.columnDefinitions.values():
835
+ sqlmeta.addColumn(column)
836
+
837
+ for column in implicitColumns:
838
+ sqlmeta.addColumn(column)
839
+
840
+ # Now the class is in an essentially OK-state, so we can
841
+ # set up any magic attributes:
842
+ declarative.setup_attributes(cls, new_attrs)
843
+
844
+ if sqlmeta.fromDatabase:
845
+ sqlmeta.addColumnsFromDatabase()
846
+
847
+ for j in implicitJoins:
848
+ sqlmeta.addJoin(j)
849
+ for i in implicitIndexes:
850
+ sqlmeta.addIndex(i)
851
+
852
+ def order_getter(o):
853
+ return o.creationOrder
854
+ sqlmeta.columnList.sort(key=order_getter)
855
+ sqlmeta.indexes.sort(key=order_getter)
856
+ sqlmeta.indexDefinitions.sort(key=order_getter)
857
+ # Joins cannot be sorted because addJoin created accessors
858
+ # that remember indexes.
859
+ # sqlmeta.joins.sort(key=order_getter)
860
+ sqlmeta.joinDefinitions.sort(key=order_getter)
861
+
862
+ # We don't setup the properties until we're finished with the
863
+ # batch adding of all the columns...
864
+ cls._notifyFinishClassCreation()
865
+ cls._SO_finishedClassCreation = True
866
+ makeProperties(cls)
867
+
868
+ # We use the magic "q" attribute for accessing lazy
869
+ # SQL where-clause generation. See the sql module for
870
+ # more.
871
+ if not is_base:
872
+ cls.q = sqlbuilder.SQLObjectTable(cls)
873
+ cls.j = sqlbuilder.SQLObjectTableWithJoins(cls)
874
+
875
+ classregistry.registry(sqlmeta.registry).addClass(cls)
876
+
877
+ @classmethod
878
+ def _SO_setupSqlmeta(cls, new_attrs, is_base):
879
+ """
880
+ This fixes up the sqlmeta attribute. It handles both the case
881
+ where no sqlmeta was given (in which we need to create another
882
+ subclass), or the sqlmeta given doesn't have the proper
883
+ inheritance. Lastly it calls sqlmeta.setClass, which handles
884
+ much of the setup.
885
+ """
886
+ if ('sqlmeta' not in new_attrs and not is_base):
887
+ # We have to create our own subclass, usually.
888
+ # type(className, bases_tuple, attr_dict) creates a new subclass.
889
+ cls.sqlmeta = type('sqlmeta', (cls.sqlmeta,), {})
890
+ if not issubclass(cls.sqlmeta, sqlmeta):
891
+ # We allow no superclass and an object superclass, instead
892
+ # of inheriting from sqlmeta; but in that case we replace
893
+ # the class and just move over its attributes:
894
+ assert cls.sqlmeta.__bases__ in ((), (object,)), (
895
+ "If you do not inherit your sqlmeta class from "
896
+ "sqlobject.sqlmeta, it must not inherit from any other "
897
+ "class (your sqlmeta inherits from: %s)"
898
+ % cls.sqlmeta.__bases__)
899
+ for base in cls.__bases__:
900
+ superclass = getattr(base, 'sqlmeta', None)
901
+ if superclass:
902
+ break
903
+ else:
904
+ assert 0, (
905
+ "No sqlmeta class could be found in any superclass "
906
+ "(while fixing up sqlmeta %r inheritance)"
907
+ % cls.sqlmeta)
908
+ values = dict(cls.sqlmeta.__dict__)
909
+ for key in list(values.keys()):
910
+ if key.startswith('__') and key.endswith('__'):
911
+ # Magic values shouldn't be passed through:
912
+ del values[key]
913
+ cls.sqlmeta = type('sqlmeta', (superclass,), values)
914
+
915
+ if not is_base: # Do not pollute the base sqlmeta class
916
+ cls.sqlmeta.setClass(cls)
917
+
918
+ @classmethod
919
+ def _SO_cleanDeprecatedAttrs(cls, new_attrs):
920
+ """
921
+ This removes attributes on SQLObject subclasses that have
922
+ been deprecated; they are moved to the sqlmeta class, and
923
+ a deprecation warning is given.
924
+ """
925
+ for attr in ():
926
+ if attr in new_attrs:
927
+ deprecated("%r is deprecated and read-only; please do "
928
+ "not use it in your classes until it is fully "
929
+ "deprecated" % attr, level=1, stacklevel=5)
930
+
931
+ @classmethod
932
+ def get(cls, id, connection=None, selectResults=None):
933
+
934
+ assert id is not None, \
935
+ 'None is not a possible id for %s' % cls.__name__
936
+
937
+ id = cls.sqlmeta.idType(id)
938
+
939
+ if connection is None:
940
+ cache = cls._connection.cache
941
+ else:
942
+ cache = connection.cache
943
+
944
+ # This whole sequence comes from Cache.CacheFactory's
945
+ # behavior, where a None returned means a cache miss.
946
+ val = cache.get(id, cls)
947
+ if val is None:
948
+ try:
949
+ val = cls(_SO_fetch_no_create=1)
950
+ val._SO_validatorState = sqlbuilder.SQLObjectState(val)
951
+ val._init(id, connection, selectResults)
952
+ cache.put(id, cls, val)
953
+ finally:
954
+ cache.finishPut(cls)
955
+ elif selectResults and not val.sqlmeta.dirty:
956
+ val._SO_writeLock.acquire()
957
+ try:
958
+ val._SO_selectInit(selectResults)
959
+ val.sqlmeta.expired = False
960
+ finally:
961
+ val._SO_writeLock.release()
962
+ return val
963
+
964
+ @classmethod
965
+ def _notifyFinishClassCreation(cls):
966
+ pass
967
+
968
+ def _init(self, id, connection=None, selectResults=None):
969
+ assert id is not None
970
+ # This function gets called only when the object is
971
+ # created, unlike __init__ which would be called
972
+ # anytime the object was returned from cache.
973
+ self.id = id
974
+ self._SO_writeLock = threading.Lock()
975
+
976
+ # If no connection was given, we'll inherit the class
977
+ # instance variable which should have a _connection
978
+ # attribute.
979
+ if (connection is not None) and \
980
+ (getattr(self, '_connection', None) is not connection):
981
+ self._connection = connection
982
+ # Sometimes we need to know if this instance is
983
+ # global or tied to a particular connection.
984
+ # This flag tells us that:
985
+ self.sqlmeta._perConnection = True
986
+
987
+ if not selectResults:
988
+ dbNames = [col.dbName for col in self.sqlmeta.columnList]
989
+ selectResults = self._connection._SO_selectOne(self, dbNames)
990
+ if not selectResults:
991
+ raise SQLObjectNotFound(
992
+ "The object %s by the ID %s does not exist" % (
993
+ self.__class__.__name__, self.id))
994
+ self._SO_selectInit(selectResults)
995
+ self._SO_createValues = {}
996
+ self.sqlmeta.dirty = False
997
+
998
+ def _SO_loadValue(self, attrName):
999
+ try:
1000
+ return getattr(self, attrName)
1001
+ except AttributeError:
1002
+ try:
1003
+ self._SO_writeLock.acquire()
1004
+ try:
1005
+ # Maybe, just in the moment since we got the lock,
1006
+ # some other thread did a _SO_loadValue and we
1007
+ # have the attribute! Let's try and find out! We
1008
+ # can keep trying this all day and still beat the
1009
+ # performance on the database call (okay, we can
1010
+ # keep trying this for a few msecs at least)...
1011
+ result = getattr(self, attrName)
1012
+ except AttributeError:
1013
+ pass
1014
+ else:
1015
+ return result
1016
+ self.sqlmeta.expired = False
1017
+ dbNames = [col.dbName for col in self.sqlmeta.columnList]
1018
+ selectResults = self._connection._SO_selectOne(self, dbNames)
1019
+ if not selectResults:
1020
+ raise SQLObjectNotFound(
1021
+ "The object %s by the ID %s has been deleted" % (
1022
+ self.__class__.__name__, self.id))
1023
+ self._SO_selectInit(selectResults)
1024
+ result = getattr(self, attrName)
1025
+ return result
1026
+ finally:
1027
+ self._SO_writeLock.release()
1028
+
1029
+ def sync(self):
1030
+ if self.sqlmeta.lazyUpdate and self._SO_createValues:
1031
+ self.syncUpdate()
1032
+ self._SO_writeLock.acquire()
1033
+ try:
1034
+ dbNames = [col.dbName for col in self.sqlmeta.columnList]
1035
+ selectResults = self._connection._SO_selectOne(self, dbNames)
1036
+ if not selectResults:
1037
+ raise SQLObjectNotFound(
1038
+ "The object %s by the ID %s has been deleted" % (
1039
+ self.__class__.__name__, self.id))
1040
+ self._SO_selectInit(selectResults)
1041
+ self.sqlmeta.expired = False
1042
+ finally:
1043
+ self._SO_writeLock.release()
1044
+
1045
+ def syncUpdate(self):
1046
+ if not self._SO_createValues:
1047
+ return
1048
+ self._SO_writeLock.acquire()
1049
+ try:
1050
+ if self.sqlmeta.columns:
1051
+ columns = self.sqlmeta.columns
1052
+ values = [(columns[v[0]].dbName, v[1])
1053
+ for v in sorted(
1054
+ self._SO_createValues.items(),
1055
+ key=lambda c: columns[c[0]].creationOrder)]
1056
+ self._connection._SO_update(self, values)
1057
+ self.sqlmeta.dirty = False
1058
+ self._SO_createValues = {}
1059
+ finally:
1060
+ self._SO_writeLock.release()
1061
+
1062
+ post_funcs = []
1063
+ self.sqlmeta.send(events.RowUpdatedSignal, self, post_funcs)
1064
+ for func in post_funcs:
1065
+ func(self)
1066
+
1067
+ def expire(self):
1068
+ if self.sqlmeta.expired:
1069
+ return
1070
+ self._SO_writeLock.acquire()
1071
+ try:
1072
+ if self.sqlmeta.expired:
1073
+ return
1074
+ for column in self.sqlmeta.columnList:
1075
+ delattr(self, instanceName(column.name))
1076
+ self.sqlmeta.expired = True
1077
+ self._connection.cache.expire(self.id, self.__class__)
1078
+ self._SO_createValues = {}
1079
+ finally:
1080
+ self._SO_writeLock.release()
1081
+
1082
+ def _SO_setValue(self, name, value, from_python, to_python):
1083
+ # This is the place where we actually update the
1084
+ # database.
1085
+
1086
+ # If we are _creating, the object doesn't yet exist
1087
+ # in the database, and we can't insert it until all
1088
+ # the parts are set. So we just keep them in a
1089
+ # dictionary until later:
1090
+ d = {name: value}
1091
+ if not self.sqlmeta._creating and \
1092
+ not getattr(self.sqlmeta, "row_update_sig_suppress", False):
1093
+ self.sqlmeta.send(events.RowUpdateSignal, self, d)
1094
+ if len(d) != 1 or name not in d:
1095
+ # Already called RowUpdateSignal, don't call it again
1096
+ # inside .set()
1097
+ self.sqlmeta.row_update_sig_suppress = True
1098
+ self.set(**d)
1099
+ del self.sqlmeta.row_update_sig_suppress
1100
+ value = d[name]
1101
+ if from_python:
1102
+ dbValue = from_python(value, self._SO_validatorState)
1103
+ else:
1104
+ dbValue = value
1105
+ if to_python:
1106
+ value = to_python(dbValue, self._SO_validatorState)
1107
+ if self.sqlmeta._creating or self.sqlmeta.lazyUpdate:
1108
+ self.sqlmeta.dirty = True
1109
+ self._SO_createValues[name] = dbValue
1110
+ setattr(self, instanceName(name), value)
1111
+ return
1112
+
1113
+ self._connection._SO_update(
1114
+ self, [(self.sqlmeta.columns[name].dbName,
1115
+ dbValue)])
1116
+
1117
+ if self.sqlmeta.cacheValues:
1118
+ setattr(self, instanceName(name), value)
1119
+
1120
+ post_funcs = []
1121
+ self.sqlmeta.send(events.RowUpdatedSignal, self, post_funcs)
1122
+ for func in post_funcs:
1123
+ func(self)
1124
+
1125
+ def set(self, _suppress_set_sig=False, **kw):
1126
+ if not self.sqlmeta._creating and \
1127
+ not getattr(self.sqlmeta, "row_update_sig_suppress", False) \
1128
+ and not _suppress_set_sig:
1129
+ self.sqlmeta.send(events.RowUpdateSignal, self, kw)
1130
+ # set() is used to update multiple values at once,
1131
+ # potentially with one SQL statement if possible.
1132
+
1133
+ # Filter out items that don't map to column names.
1134
+ # Those will be set directly on the object using
1135
+ # setattr(obj, name, value).
1136
+ def is_column(_c):
1137
+ return _c in self.sqlmeta._plainSetters
1138
+
1139
+ def f_is_column(item):
1140
+ return is_column(item[0])
1141
+
1142
+ def f_not_column(item):
1143
+ return not is_column(item[0])
1144
+ items = kw.items()
1145
+ extra = dict(filter(f_not_column, items))
1146
+ kw = dict(filter(f_is_column, items))
1147
+
1148
+ # _creating is special, see _SO_setValue
1149
+ if self.sqlmeta._creating or self.sqlmeta.lazyUpdate:
1150
+ for name, value in kw.items():
1151
+ from_python = getattr(self, '_SO_from_python_%s' % name, None)
1152
+ if from_python:
1153
+ kw[name] = dbValue = from_python(value,
1154
+ self._SO_validatorState)
1155
+ else:
1156
+ dbValue = value
1157
+ to_python = getattr(self, '_SO_to_python_%s' % name, None)
1158
+ if to_python:
1159
+ value = to_python(dbValue, self._SO_validatorState)
1160
+ setattr(self, instanceName(name), value)
1161
+
1162
+ self._SO_createValues.update(kw)
1163
+
1164
+ for name, value in extra.items():
1165
+ try:
1166
+ getattr(self.__class__, name)
1167
+ except AttributeError:
1168
+ if name not in self.sqlmeta.columns:
1169
+ raise TypeError(
1170
+ "%s.set() got an unexpected keyword argument "
1171
+ "%s" % (self.__class__.__name__, name))
1172
+ try:
1173
+ setattr(self, name, value)
1174
+ except AttributeError as e:
1175
+ raise AttributeError('%s (with attribute %r)' % (e, name))
1176
+
1177
+ self.sqlmeta.dirty = True
1178
+ return
1179
+
1180
+ self._SO_writeLock.acquire()
1181
+
1182
+ try:
1183
+ # We have to go through and see if the setters are
1184
+ # "plain", that is, if the user has changed their
1185
+ # definition in any way (put in something that
1186
+ # normalizes the value or checks for consistency,
1187
+ # for instance). If so then we have to use plain
1188
+ # old setattr() to change the value, since we can't
1189
+ # read the user's mind. We'll combine everything
1190
+ # else into a single UPDATE, if necessary.
1191
+ toUpdate = {}
1192
+ for name, value in kw.items():
1193
+ from_python = getattr(self, '_SO_from_python_%s' % name, None)
1194
+ if from_python:
1195
+ dbValue = from_python(value, self._SO_validatorState)
1196
+ else:
1197
+ dbValue = value
1198
+ to_python = getattr(self, '_SO_to_python_%s' % name, None)
1199
+ if to_python:
1200
+ value = to_python(dbValue, self._SO_validatorState)
1201
+ if self.sqlmeta.cacheValues:
1202
+ setattr(self, instanceName(name), value)
1203
+ toUpdate[name] = dbValue
1204
+ for name, value in extra.items():
1205
+ try:
1206
+ getattr(self.__class__, name)
1207
+ except AttributeError:
1208
+ if name not in self.sqlmeta.columns:
1209
+ raise TypeError(
1210
+ "%s.set() got an unexpected keyword argument "
1211
+ "%s" % (self.__class__.__name__, name))
1212
+ try:
1213
+ setattr(self, name, value)
1214
+ except AttributeError as e:
1215
+ raise AttributeError('%s (with attribute %r)' % (e, name))
1216
+
1217
+ if toUpdate:
1218
+ toUpdate = sorted(
1219
+ toUpdate.items(),
1220
+ key=lambda c: self.sqlmeta.columns[c[0]].creationOrder)
1221
+ args = [(self.sqlmeta.columns[name].dbName, value)
1222
+ for name, value in toUpdate]
1223
+ self._connection._SO_update(self, args)
1224
+ finally:
1225
+ self._SO_writeLock.release()
1226
+
1227
+ post_funcs = []
1228
+ self.sqlmeta.send(events.RowUpdatedSignal, self, post_funcs)
1229
+ for func in post_funcs:
1230
+ func(self)
1231
+
1232
+ def _SO_selectInit(self, row):
1233
+ for _col, colValue in zip(self.sqlmeta.columnList, row):
1234
+ if _col.to_python:
1235
+ colValue = _col.to_python(colValue, self._SO_validatorState)
1236
+ setattr(self, instanceName(_col.name), colValue)
1237
+
1238
+ def _SO_getValue(self, name):
1239
+ # Retrieves a single value from the database. Simple.
1240
+ assert not self.sqlmeta._obsolete, (
1241
+ "%s with id %s has become obsolete"
1242
+ % (self.__class__.__name__, self.id))
1243
+ # @@: do we really need this lock?
1244
+ # self._SO_writeLock.acquire()
1245
+ column = self.sqlmeta.columns[name]
1246
+ results = self._connection._SO_selectOne(self, [column.dbName])
1247
+ # self._SO_writeLock.release()
1248
+ assert results is not None, "%s with id %s is not in the database" % (
1249
+ self.__class__.__name__, self.id)
1250
+ value = results[0]
1251
+ if column.to_python:
1252
+ value = column.to_python(value, self._SO_validatorState)
1253
+ return value
1254
+
1255
+ def _SO_foreignKey(self, value, joinClass, idName=None):
1256
+ if value is None:
1257
+ return None
1258
+ if self.sqlmeta._perConnection:
1259
+ connection = self._connection
1260
+ else:
1261
+ connection = None
1262
+ if idName is None: # Get by id
1263
+ return joinClass.get(value, connection=connection)
1264
+ return joinClass.select(
1265
+ getattr(joinClass.q, idName) == value,
1266
+ connection=connection).getOne()
1267
+
1268
+ def __init__(self, **kw):
1269
+ # If we are the outmost constructor of a hiearchy of
1270
+ # InheritableSQLObjects (or simlpy _the_ constructor of a "normal"
1271
+ # SQLObject), we create a threadlocal list that collects the
1272
+ # RowCreatedSignals, and executes them if this very constructor is left
1273
+ try:
1274
+ _postponed_local.postponed_calls
1275
+ postponed_created = False
1276
+ except AttributeError:
1277
+ _postponed_local.postponed_calls = []
1278
+ postponed_created = True
1279
+
1280
+ try:
1281
+ # We shadow the sqlmeta class with an instance of sqlmeta
1282
+ # that points to us (our sqlmeta buddy object; where the
1283
+ # sqlmeta class is our class's buddy class)
1284
+ self.sqlmeta = self.__class__.sqlmeta(self)
1285
+ # The get() classmethod/constructor uses a magic keyword
1286
+ # argument when it wants an empty object, fetched from the
1287
+ # database. So we have nothing more to do in that case:
1288
+ if '_SO_fetch_no_create' in kw:
1289
+ return
1290
+
1291
+ post_funcs = []
1292
+ self.sqlmeta.send(events.RowCreateSignal, self, kw, post_funcs)
1293
+
1294
+ # Pass the connection object along if we were given one.
1295
+ if 'connection' in kw:
1296
+ connection = kw.pop('connection')
1297
+ if getattr(self, '_connection', None) is not connection:
1298
+ self._connection = connection
1299
+ self.sqlmeta._perConnection = True
1300
+
1301
+ self._SO_writeLock = threading.Lock()
1302
+
1303
+ if 'id' in kw:
1304
+ id = self.sqlmeta.idType(kw['id'])
1305
+ del kw['id']
1306
+ else:
1307
+ id = None
1308
+
1309
+ self._create(id, **kw)
1310
+
1311
+ for func in post_funcs:
1312
+ func(self)
1313
+ finally:
1314
+ # if we are the creator of the tl-storage, we
1315
+ # have to exectute and under all circumstances
1316
+ # remove the tl-storage
1317
+ if postponed_created:
1318
+ try:
1319
+ for func in _postponed_local.postponed_calls:
1320
+ func()
1321
+ finally:
1322
+ del _postponed_local.postponed_calls
1323
+
1324
+ def _create(self, id, **kw):
1325
+
1326
+ self.sqlmeta._creating = True
1327
+ self._SO_createValues = {}
1328
+ self._SO_validatorState = sqlbuilder.SQLObjectState(self)
1329
+
1330
+ # First we do a little fix-up on the keywords we were
1331
+ # passed:
1332
+ for column in self.sqlmeta.columnList:
1333
+
1334
+ # Then we check if the column wasn't passed in, and
1335
+ # if not we try to get the default.
1336
+ if column.name not in kw and column.foreignName not in kw:
1337
+ default = column.default
1338
+
1339
+ # If we don't get it, it's an error:
1340
+ # If we specified an SQL DEFAULT, then we should use that
1341
+ if default is NoDefault:
1342
+ if column.defaultSQL is None:
1343
+ raise TypeError(
1344
+ "%s() did not get expected keyword argument "
1345
+ "'%s'" % (self.__class__.__name__, column.name))
1346
+ else:
1347
+ # There is defaultSQL for the column -
1348
+ # do not put the column to kw
1349
+ # so that the backend creates the value.
1350
+ continue
1351
+
1352
+ # Otherwise we put it in as though they did pass
1353
+ # that keyword:
1354
+
1355
+ kw[column.name] = default
1356
+
1357
+ self.set(**kw)
1358
+
1359
+ # Then we finalize the process:
1360
+ self._SO_finishCreate(id)
1361
+
1362
+ def _SO_finishCreate(self, id=None):
1363
+ # Here's where an INSERT is finalized.
1364
+ # These are all the column values that were supposed
1365
+ # to be set, but were delayed until now:
1366
+ setters = self._SO_createValues.items()
1367
+ setters = sorted(
1368
+ setters, key=lambda c: self.sqlmeta.columns[c[0]].creationOrder)
1369
+ # Here's their database names:
1370
+ names = [self.sqlmeta.columns[v[0]].dbName for v in setters]
1371
+ values = [v[1] for v in setters]
1372
+ # Get rid of _SO_create*, we aren't creating anymore.
1373
+ # Doesn't have to be threadsafe because we're still in
1374
+ # new(), which doesn't need to be threadsafe.
1375
+ self.sqlmeta.dirty = False
1376
+ if not self.sqlmeta.lazyUpdate:
1377
+ del self._SO_createValues
1378
+ else:
1379
+ self._SO_createValues = {}
1380
+ del self.sqlmeta._creating
1381
+
1382
+ # Do the insert -- most of the SQL in this case is left
1383
+ # up to DBConnection, since getting a new ID is
1384
+ # non-standard.
1385
+ id = self._connection.queryInsertID(self,
1386
+ id, names, values)
1387
+ cache = self._connection.cache
1388
+ cache.created(id, self.__class__, self)
1389
+ self._init(id)
1390
+ post_funcs = []
1391
+ kw = dict([('class', self.__class__), ('id', id)])
1392
+
1393
+ def _send_RowCreatedSignal():
1394
+ self.sqlmeta.send(events.RowCreatedSignal, self, kw, post_funcs)
1395
+ for func in post_funcs:
1396
+ func(self)
1397
+ _postponed_local.postponed_calls.append(_send_RowCreatedSignal)
1398
+
1399
+ def _SO_getID(self, obj, refColumn=None):
1400
+ return getID(obj, refColumn)
1401
+
1402
+ @classmethod
1403
+ def _findAlternateID(cls, name, dbName, value, connection=None):
1404
+ if isinstance(name, str):
1405
+ name = (name,)
1406
+ value = (value,)
1407
+ if len(name) != len(value):
1408
+ raise ValueError(
1409
+ "'column' and 'value' tuples must be of the same size")
1410
+ new_value = []
1411
+ for n, v in zip(name, value):
1412
+ from_python = getattr(cls, '_SO_from_python_' + n)
1413
+ if from_python:
1414
+ v = from_python(
1415
+ v, sqlbuilder.SQLObjectState(cls, connection=connection))
1416
+ new_value.append(v)
1417
+ condition = sqlbuilder.AND(
1418
+ *[getattr(cls.q, _n) == _v for _n, _v in zip(name, new_value)])
1419
+ return (connection or cls._connection)._SO_selectOneAlt(
1420
+ cls,
1421
+ [cls.sqlmeta.idName]
1422
+ + [column.dbName for column in cls.sqlmeta.columnList],
1423
+ condition), None
1424
+
1425
+ @classmethod
1426
+ def _SO_fetchAlternateID(cls, name, dbName, value, connection=None,
1427
+ idxName=None):
1428
+ result, obj = cls._findAlternateID(name, dbName, value, connection)
1429
+ if not result:
1430
+ if idxName is None:
1431
+ raise SQLObjectNotFound(
1432
+ "The %s by alternateID %s = %s does not exist" % (
1433
+ cls.__name__, name, repr(value)))
1434
+ else:
1435
+ names = []
1436
+ for i in range(len(name)):
1437
+ names.append("%s = %s" % (name[i], repr(value[i])))
1438
+ names = ', '.join(names)
1439
+ raise SQLObjectNotFound(
1440
+ "The %s by unique index %s(%s) does not exist" % (
1441
+ cls.__name__, idxName, names))
1442
+ if obj:
1443
+ return obj
1444
+ if connection:
1445
+ obj = cls.get(result[0], connection=connection,
1446
+ selectResults=result[1:])
1447
+ else:
1448
+ obj = cls.get(result[0], selectResults=result[1:])
1449
+ return obj
1450
+
1451
+ @classmethod
1452
+ def _SO_depends(cls):
1453
+ return findDependencies(cls.__name__, cls.sqlmeta.registry)
1454
+
1455
+ @classmethod
1456
+ def select(cls, clause=None, clauseTables=None,
1457
+ orderBy=NoDefault, limit=None,
1458
+ lazyColumns=False, reversed=False,
1459
+ distinct=False, connection=None,
1460
+ join=None, forUpdate=False):
1461
+ return cls.SelectResultsClass(cls, clause,
1462
+ clauseTables=clauseTables,
1463
+ orderBy=orderBy,
1464
+ limit=limit,
1465
+ lazyColumns=lazyColumns,
1466
+ reversed=reversed,
1467
+ distinct=distinct,
1468
+ connection=connection,
1469
+ join=join, forUpdate=forUpdate)
1470
+
1471
+ @classmethod
1472
+ def selectBy(cls, connection=None, **kw):
1473
+ conn = connection or cls._connection
1474
+ return cls.SelectResultsClass(cls,
1475
+ conn._SO_columnClause(cls, kw),
1476
+ connection=conn)
1477
+
1478
+ @classmethod
1479
+ def tableExists(cls, connection=None):
1480
+ conn = connection or cls._connection
1481
+ return conn.tableExists(cls.sqlmeta.table)
1482
+
1483
+ @classmethod
1484
+ def dropTable(cls, ifExists=False, dropJoinTables=True, cascade=False,
1485
+ connection=None):
1486
+ conn = connection or cls._connection
1487
+ if ifExists and not cls.tableExists(connection=conn):
1488
+ return
1489
+ extra_sql = []
1490
+ post_funcs = []
1491
+ cls.sqlmeta.send(events.DropTableSignal, cls, connection,
1492
+ extra_sql, post_funcs)
1493
+ conn.dropTable(cls.sqlmeta.table, cascade)
1494
+ if dropJoinTables:
1495
+ cls.dropJoinTables(ifExists=ifExists, connection=conn)
1496
+ for sql in extra_sql:
1497
+ connection.query(sql)
1498
+ for func in post_funcs:
1499
+ func(cls, conn)
1500
+
1501
+ @classmethod
1502
+ def createTable(cls, ifNotExists=False, createJoinTables=True,
1503
+ createIndexes=True, applyConstraints=True,
1504
+ connection=None):
1505
+ conn = connection or cls._connection
1506
+ if ifNotExists and cls.tableExists(connection=conn):
1507
+ return
1508
+ extra_sql = []
1509
+ post_funcs = []
1510
+ cls.sqlmeta.send(events.CreateTableSignal, cls, connection,
1511
+ extra_sql, post_funcs)
1512
+ constraints = conn.createTable(cls)
1513
+ if applyConstraints:
1514
+ for constraint in constraints:
1515
+ conn.query(constraint)
1516
+ else:
1517
+ extra_sql.extend(constraints)
1518
+ if createJoinTables:
1519
+ cls.createJoinTables(ifNotExists=ifNotExists,
1520
+ connection=conn)
1521
+ if createIndexes:
1522
+ cls.createIndexes(ifNotExists=ifNotExists,
1523
+ connection=conn)
1524
+ for func in post_funcs:
1525
+ func(cls, conn)
1526
+ return extra_sql
1527
+
1528
+ @classmethod
1529
+ def createTableSQL(cls, createJoinTables=True, createIndexes=True,
1530
+ connection=None):
1531
+ conn = connection or cls._connection
1532
+ sql, constraints = conn.createTableSQL(cls)
1533
+ if createJoinTables:
1534
+ join_sql = cls.createJoinTablesSQL(connection=conn)
1535
+ if join_sql:
1536
+ sql += ';\n' + join_sql
1537
+ if createIndexes:
1538
+ index_sql = cls.createIndexesSQL(connection=conn)
1539
+ if index_sql:
1540
+ sql += ';\n' + index_sql
1541
+ return sql, constraints
1542
+
1543
+ @classmethod
1544
+ def createJoinTables(cls, ifNotExists=False, connection=None):
1545
+ conn = connection or cls._connection
1546
+ for join in cls._getJoinsToCreate():
1547
+ if (ifNotExists
1548
+ and conn.tableExists(join.intermediateTable)):
1549
+ continue
1550
+ conn._SO_createJoinTable(join)
1551
+
1552
+ @classmethod
1553
+ def createJoinTablesSQL(cls, connection=None):
1554
+ conn = connection or cls._connection
1555
+ sql = []
1556
+ for join in cls._getJoinsToCreate():
1557
+ sql.append(conn._SO_createJoinTableSQL(join))
1558
+ return ';\n'.join(sql)
1559
+
1560
+ @classmethod
1561
+ def createIndexes(cls, ifNotExists=False, connection=None):
1562
+ conn = connection or cls._connection
1563
+ for _index in cls.sqlmeta.indexes:
1564
+ if not _index:
1565
+ continue
1566
+ conn._SO_createIndex(cls, _index)
1567
+
1568
+ @classmethod
1569
+ def createIndexesSQL(cls, connection=None):
1570
+ conn = connection or cls._connection
1571
+ sql = []
1572
+ for _index in cls.sqlmeta.indexes:
1573
+ if not _index:
1574
+ continue
1575
+ sql.append(conn.createIndexSQL(cls, _index))
1576
+ return ';\n'.join(sql)
1577
+
1578
+ @classmethod
1579
+ def _getJoinsToCreate(cls):
1580
+ joins = []
1581
+ for join in cls.sqlmeta.joins:
1582
+ if not join:
1583
+ continue
1584
+ if not join.hasIntermediateTable() or \
1585
+ not getattr(join, 'createRelatedTable', True):
1586
+ continue
1587
+ if join.soClass.__name__ > join.otherClass.__name__:
1588
+ continue
1589
+ joins.append(join)
1590
+ return joins
1591
+
1592
+ @classmethod
1593
+ def dropJoinTables(cls, ifExists=False, connection=None):
1594
+ conn = connection or cls._connection
1595
+ for join in cls.sqlmeta.joins:
1596
+ if not join:
1597
+ continue
1598
+ if not join.hasIntermediateTable() or \
1599
+ not getattr(join, 'createRelatedTable', True):
1600
+ continue
1601
+ if join.soClass.__name__ > join.otherClass.__name__:
1602
+ continue
1603
+ if ifExists and \
1604
+ not conn.tableExists(join.intermediateTable):
1605
+ continue
1606
+ conn._SO_dropJoinTable(join)
1607
+
1608
+ @classmethod
1609
+ def clearTable(cls, connection=None, clearJoinTables=True):
1610
+ # 3-03 @@: Maybe this should check the cache... but it's
1611
+ # kind of crude anyway, so...
1612
+ conn = connection or cls._connection
1613
+ conn.clearTable(cls.sqlmeta.table)
1614
+ if clearJoinTables:
1615
+ for join in cls._getJoinsToCreate():
1616
+ conn.clearTable(join.intermediateTable)
1617
+
1618
+ def destroySelf(self):
1619
+ post_funcs = []
1620
+ self.sqlmeta.send(events.RowDestroySignal, self, post_funcs)
1621
+ # Kills this object. Kills it dead!
1622
+
1623
+ klass = self.__class__
1624
+
1625
+ # Free related joins on the base class
1626
+ for join in klass.sqlmeta.joins:
1627
+ if isinstance(join, joins.SORelatedJoin):
1628
+ q = "DELETE FROM %s WHERE %s=%d" % (join.intermediateTable,
1629
+ join.joinColumn, self.id)
1630
+ self._connection.query(q)
1631
+
1632
+ depends = self._SO_depends()
1633
+ for k in depends:
1634
+ # Free related joins
1635
+ for join in k.sqlmeta.joins:
1636
+ if isinstance(join, joins.SORelatedJoin) and \
1637
+ join.otherClassName == klass.__name__:
1638
+ q = "DELETE FROM %s WHERE %s=%d" % (join.intermediateTable,
1639
+ join.otherColumn,
1640
+ self.id)
1641
+ self._connection.query(q)
1642
+
1643
+ cols = findDependantColumns(klass.__name__, k)
1644
+
1645
+ # Don't confuse the rest of the process
1646
+ if len(cols) == 0:
1647
+ continue
1648
+
1649
+ query = []
1650
+ restrict = False
1651
+ for _col in cols:
1652
+ query.append(getattr(k.q, _col.name) == self.id)
1653
+ if _col.cascade is False:
1654
+ # Found a restriction
1655
+ restrict = True
1656
+ query = sqlbuilder.OR(*query)
1657
+ results = k.select(query, connection=self._connection)
1658
+ if restrict and results.count():
1659
+ # Restrictions only apply if there are
1660
+ # matching records on the related table
1661
+ raise SQLObjectIntegrityError(
1662
+ "Tried to delete %s::%s but "
1663
+ "table %s has a restriction against it" %
1664
+ (klass.__name__, self.id, k.__name__))
1665
+
1666
+ setnull = {}
1667
+ for _col in cols:
1668
+ if _col.cascade == 'null':
1669
+ setnull[_col.name] = None
1670
+ if setnull:
1671
+ for row in results:
1672
+ clear = {}
1673
+ for name in setnull:
1674
+ if getattr(row, name) == self.id:
1675
+ clear[name] = None
1676
+ row.set(**clear)
1677
+
1678
+ delete = False
1679
+ for _col in cols:
1680
+ if _col.cascade is True:
1681
+ delete = True
1682
+ assert delete or setnull or restrict, (
1683
+ "Class %s depends on %s accoriding to "
1684
+ "findDependantColumns, but this seems inaccurate"
1685
+ % (k, klass))
1686
+ if delete:
1687
+ for row in results:
1688
+ row.destroySelf()
1689
+
1690
+ self.sqlmeta._obsolete = True
1691
+ self._connection._SO_delete(self)
1692
+ self._connection.cache.expire(self.id, self.__class__)
1693
+
1694
+ for func in post_funcs:
1695
+ func(self)
1696
+
1697
+ post_funcs = []
1698
+ self.sqlmeta.send(events.RowDestroyedSignal, self, post_funcs)
1699
+ for func in post_funcs:
1700
+ func(self)
1701
+
1702
+ @classmethod
1703
+ def delete(cls, id, connection=None):
1704
+ obj = cls.get(id, connection=connection)
1705
+ obj.destroySelf()
1706
+
1707
+ @classmethod
1708
+ def deleteMany(cls, where=NoDefault, connection=None):
1709
+ conn = connection or cls._connection
1710
+ conn.query(conn.sqlrepr(sqlbuilder.Delete(cls.sqlmeta.table, where)))
1711
+
1712
+ @classmethod
1713
+ def deleteBy(cls, connection=None, **kw):
1714
+ conn = connection or cls._connection
1715
+ conn.query(conn.sqlrepr(sqlbuilder.Delete(
1716
+ cls.sqlmeta.table, conn._SO_columnClause(cls, kw))))
1717
+
1718
+ def __repr__(self):
1719
+ if not hasattr(self, 'id'):
1720
+ # Object initialization not finished. No attributes can be read.
1721
+ return '<%s (not initialized)>' % self.__class__.__name__
1722
+ return '<%s %r %s>' \
1723
+ % (self.__class__.__name__,
1724
+ self.id,
1725
+ ' '.join(
1726
+ ['%s=%s' % (name, repr(value))
1727
+ for name, value in self._reprItems()]))
1728
+
1729
+ def __sqlrepr__(self, db):
1730
+ return str(self.id)
1731
+
1732
+ @classmethod
1733
+ def sqlrepr(cls, value, connection=None):
1734
+ return (connection or cls._connection).sqlrepr(value)
1735
+
1736
+ @classmethod
1737
+ def coerceID(cls, value):
1738
+ if isinstance(value, cls):
1739
+ return value.id
1740
+ else:
1741
+ return cls.sqlmeta.idType(value)
1742
+
1743
+ def _reprItems(self):
1744
+ items = []
1745
+ for _col in self.sqlmeta.columnList:
1746
+ value = getattr(self, _col.name)
1747
+ r = repr(value)
1748
+ if len(r) > 20:
1749
+ value = r[:17] + "..." + r[-1]
1750
+ items.append((_col.name, value))
1751
+ return items
1752
+
1753
+ @classmethod
1754
+ def setConnection(cls, value):
1755
+ if isinstance(value, string_type):
1756
+ value = dbconnection.connectionForURI(value)
1757
+ cls._connection = value
1758
+
1759
+ def tablesUsedImmediate(self):
1760
+ return [self.__class__.q]
1761
+
1762
+ # hash implementation
1763
+
1764
+ def __hash__(self):
1765
+ # We hash on class name and id, since that should be
1766
+ # unique
1767
+ return hash((self.__class__.__name__, self.id))
1768
+
1769
+ # Comparison
1770
+
1771
+ def __eq__(self, other):
1772
+ if self.__class__ is other.__class__:
1773
+ if self.id == other.id:
1774
+ return True
1775
+ return False
1776
+
1777
+ def __ne__(self, other):
1778
+ return not self.__eq__(other)
1779
+
1780
+ def __lt__(self, other):
1781
+ return NotImplemented
1782
+
1783
+ def __le__(self, other):
1784
+ return NotImplemented
1785
+
1786
+ def __gt__(self, other):
1787
+ return NotImplemented
1788
+
1789
+ def __ge__(self, other):
1790
+ return NotImplemented
1791
+
1792
+ # (De)serialization (pickle, etc.)
1793
+
1794
+ def __getstate__(self):
1795
+ if self.sqlmeta._perConnection:
1796
+ from pickle import PicklingError
1797
+ raise PicklingError(
1798
+ 'Cannot pickle an SQLObject instance '
1799
+ 'that has a per-instance connection')
1800
+ if self.sqlmeta.lazyUpdate and self._SO_createValues:
1801
+ self.syncUpdate()
1802
+ d = self.__dict__.copy()
1803
+ del d['sqlmeta']
1804
+ del d['_SO_validatorState']
1805
+ del d['_SO_writeLock']
1806
+ del d['_SO_createValues']
1807
+ return d
1808
+
1809
+ def __setstate__(self, d):
1810
+ self.__init__(_SO_fetch_no_create=1)
1811
+ self._SO_validatorState = sqlbuilder.SQLObjectState(self)
1812
+ self._SO_writeLock = threading.Lock()
1813
+ self._SO_createValues = {}
1814
+ self.__dict__.update(d)
1815
+ cls = self.__class__
1816
+ cache = self._connection.cache
1817
+ if cache.tryGet(self.id, cls) is not None:
1818
+ raise ValueError(
1819
+ "Cannot unpickle %s row with id=%s - "
1820
+ "a different instance with the id already exists "
1821
+ "in the cache" % (cls.__name__, self.id))
1822
+ cache.created(self.id, cls, self)
1823
+
1824
+
1825
+ def setterName(name):
1826
+ return '_set_%s' % name
1827
+
1828
+
1829
+ def rawSetterName(name):
1830
+ return '_SO_set_%s' % name
1831
+
1832
+
1833
+ def getterName(name):
1834
+ return '_get_%s' % name
1835
+
1836
+
1837
+ def rawGetterName(name):
1838
+ return '_SO_get_%s' % name
1839
+
1840
+
1841
+ def instanceName(name):
1842
+ return '_SO_val_%s' % name
1843
+
1844
+
1845
+ ########################################
1846
+ # Utility functions (for external consumption)
1847
+ ########################################
1848
+
1849
+ def getID(obj, refColumn=None):
1850
+ if isinstance(obj, SQLObject):
1851
+ return getattr(obj, refColumn or 'id')
1852
+ elif isinstance(obj, int):
1853
+ return obj
1854
+ elif isinstance(obj, long):
1855
+ return int(obj)
1856
+ elif isinstance(obj, str):
1857
+ try:
1858
+ return int(obj)
1859
+ except ValueError:
1860
+ return obj
1861
+ elif obj is None:
1862
+ return None
1863
+
1864
+
1865
+ def getObject(obj, klass):
1866
+ if isinstance(obj, int):
1867
+ return klass(obj)
1868
+ elif isinstance(obj, long):
1869
+ return klass(int(obj))
1870
+ elif isinstance(obj, str):
1871
+ return klass(int(obj))
1872
+ elif obj is None:
1873
+ return None
1874
+ else:
1875
+ return obj
1876
+
1877
+ __all__ = [
1878
+ 'NoDefault', 'SQLObject', 'SQLObjectIntegrityError', 'SQLObjectNotFound',
1879
+ 'getID', 'getObject', 'sqlhub', 'sqlmeta',
1880
+ ]