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
@@ -0,0 +1,551 @@
1
+ from functools import reduce
2
+
3
+ from sqlobject import dbconnection
4
+ from sqlobject import classregistry
5
+ from sqlobject import events
6
+ from sqlobject import sqlbuilder
7
+ from sqlobject.col import StringCol, ForeignKey
8
+ from sqlobject.main import sqlmeta, SQLObject, SelectResults, \
9
+ makeProperties, unmakeProperties, getterName, setterName
10
+ from sqlobject.compat import string_type
11
+ from . import iteration
12
+
13
+
14
+ def tablesUsedSet(obj, db):
15
+ if hasattr(obj, "tablesUsedSet"):
16
+ return obj.tablesUsedSet(db)
17
+ elif isinstance(obj, (tuple, list, set, frozenset)):
18
+ s = set()
19
+ for component in obj:
20
+ s.update(tablesUsedSet(component, db))
21
+ return s
22
+ else:
23
+ return set()
24
+
25
+
26
+ class InheritableSelectResults(SelectResults):
27
+ IterationClass = iteration.InheritableIteration
28
+
29
+ def __init__(self, sourceClass, clause, clauseTables=None,
30
+ inheritedTables=None, **ops):
31
+ if clause is None or isinstance(clause, str) and clause == 'all':
32
+ clause = sqlbuilder.SQLTrueClause
33
+
34
+ dbName = (ops.get('connection', None)
35
+ or sourceClass._connection).dbName
36
+
37
+ tablesSet = tablesUsedSet(clause, dbName)
38
+ tablesSet.add(str(sourceClass.sqlmeta.table))
39
+ orderBy = ops.get('orderBy')
40
+ if inheritedTables:
41
+ for tableName in inheritedTables:
42
+ tablesSet.add(str(tableName))
43
+ if orderBy and not isinstance(orderBy, string_type):
44
+ tablesSet.update(tablesUsedSet(orderBy, dbName))
45
+ # DSM: if this class has a parent, we need to link it
46
+ # DSM: and be sure the parent is in the table list.
47
+ # DSM: The following code is before clauseTables
48
+ # DSM: because if the user uses clauseTables
49
+ # DSM: (and normal string SELECT), he must know what he wants
50
+ # DSM: and will do himself the relationship between classes.
51
+ if not isinstance(clause, str):
52
+ tableRegistry = {}
53
+ allClasses = classregistry.registry(
54
+ sourceClass.sqlmeta.registry).allClasses()
55
+ for registryClass in allClasses:
56
+ if str(registryClass.sqlmeta.table) in tablesSet:
57
+ # DSM: By default, no parents are needed for the clauses
58
+ tableRegistry[registryClass] = registryClass
59
+ tableRegistryCopy = tableRegistry.copy()
60
+ for childClass in tableRegistryCopy:
61
+ if childClass not in tableRegistry:
62
+ continue
63
+ currentClass = childClass
64
+ while currentClass:
65
+ if currentClass in tableRegistryCopy:
66
+ if currentClass in tableRegistry:
67
+ # DSM: Remove this class as it is a parent one
68
+ # DSM: of a needed children
69
+ del tableRegistry[currentClass]
70
+ # DSM: Must keep the last parent needed
71
+ # DSM: (to limit the number of join needed)
72
+ tableRegistry[childClass] = currentClass
73
+ currentClass = currentClass.sqlmeta.parentClass
74
+ # DSM: Table registry contains only the last children
75
+ # DSM: or standalone classes
76
+ parentClause = []
77
+ for (currentClass, minParentClass) in tableRegistry.items():
78
+ while (currentClass != minParentClass) \
79
+ and currentClass.sqlmeta.parentClass:
80
+ parentClass = currentClass.sqlmeta.parentClass
81
+ parentClause.append(currentClass.q.id == parentClass.q.id)
82
+ currentClass = parentClass
83
+ tablesSet.add(str(currentClass.sqlmeta.table))
84
+ clause = reduce(sqlbuilder.AND, parentClause, clause)
85
+
86
+ super(InheritableSelectResults, self).__init__(
87
+ sourceClass, clause, clauseTables, **ops)
88
+
89
+ def accumulateMany(self, *attributes, **kw):
90
+ if kw.get("skipInherited"):
91
+ return super(InheritableSelectResults, self).\
92
+ accumulateMany(*attributes)
93
+ tables = []
94
+ for func_name, attribute in attributes:
95
+ if not isinstance(attribute, string_type):
96
+ tables.append(attribute.tableName)
97
+ clone = self.__class__(self.sourceClass, self.clause,
98
+ self.clauseTables, inheritedTables=tables,
99
+ **self.ops)
100
+ return clone.accumulateMany(skipInherited=True, *attributes)
101
+
102
+
103
+ class InheritableSQLMeta(sqlmeta):
104
+ @classmethod
105
+ def addColumn(sqlmeta, columnDef, changeSchema=False, connection=None,
106
+ childUpdate=False):
107
+ soClass = sqlmeta.soClass
108
+ # DSM: Try to add parent properties to the current class
109
+ # DSM: Only do this once if possible at object creation and once for
110
+ # DSM: each new dynamic column to refresh the current class
111
+ if sqlmeta.parentClass:
112
+ for col in sqlmeta.parentClass.sqlmeta.columnList:
113
+ cname = col.name
114
+ if cname == 'childName':
115
+ continue
116
+ if cname.endswith("ID"):
117
+ cname = cname[:-2]
118
+ setattr(soClass, getterName(cname), eval(
119
+ 'lambda self: self._parent.%s' % cname))
120
+ if not col.immutable:
121
+ def make_setfunc(cname):
122
+ def setfunc(self, val):
123
+ if not self.sqlmeta._creating and \
124
+ not getattr(self.sqlmeta,
125
+ "row_update_sig_suppress", False):
126
+ self.sqlmeta.send(events.RowUpdateSignal, self,
127
+ {cname: val})
128
+
129
+ setattr(self._parent, cname, val)
130
+ return setfunc
131
+
132
+ setfunc = make_setfunc(cname)
133
+ setattr(soClass, setterName(cname), setfunc)
134
+ if childUpdate:
135
+ makeProperties(soClass)
136
+ return
137
+
138
+ if columnDef:
139
+ super(InheritableSQLMeta, sqlmeta).addColumn(columnDef,
140
+ changeSchema,
141
+ connection)
142
+
143
+ # DSM: Update each child class if needed and existing (only for new
144
+ # DSM: dynamic column as no child classes exists at object creation)
145
+ if columnDef and hasattr(soClass, "q"):
146
+ q = getattr(soClass.q, columnDef.name, None)
147
+ else:
148
+ q = None
149
+ for c in sqlmeta.childClasses.values():
150
+ c.sqlmeta.addColumn(columnDef, connection=connection,
151
+ childUpdate=True)
152
+ if q:
153
+ setattr(c.q, columnDef.name, q)
154
+
155
+ @classmethod
156
+ def delColumn(sqlmeta, column, changeSchema=False, connection=None,
157
+ childUpdate=False):
158
+ if childUpdate:
159
+ soClass = sqlmeta.soClass
160
+ unmakeProperties(soClass)
161
+ makeProperties(soClass)
162
+
163
+ if isinstance(column, str):
164
+ name = column
165
+ else:
166
+ name = column.name
167
+ delattr(soClass, name)
168
+ delattr(soClass.q, name)
169
+ return
170
+
171
+ super(InheritableSQLMeta, sqlmeta).delColumn(column, changeSchema,
172
+ connection)
173
+
174
+ # DSM: Update each child class if needed
175
+ # DSM: and delete properties for this column
176
+ for c in sqlmeta.childClasses.values():
177
+ c.sqlmeta.delColumn(column, changeSchema=changeSchema,
178
+ connection=connection, childUpdate=True)
179
+
180
+ @classmethod
181
+ def addJoin(sqlmeta, joinDef, childUpdate=False):
182
+ soClass = sqlmeta.soClass
183
+ # DSM: Try to add parent properties to the current class
184
+ # DSM: Only do this once if possible at object creation and once for
185
+ # DSM: each new dynamic join to refresh the current class
186
+ if sqlmeta.parentClass:
187
+ for join in sqlmeta.parentClass.sqlmeta.joins:
188
+ jname = join.joinMethodName
189
+ jarn = join.addRemoveName
190
+ setattr(
191
+ soClass, getterName(jname),
192
+ eval('lambda self: self._parent.%s' % jname))
193
+ if hasattr(join, 'remove'):
194
+ setattr(
195
+ soClass, 'remove' + jarn,
196
+ eval('lambda self,o: self._parent.remove%s(o)' % jarn))
197
+ if hasattr(join, 'add'):
198
+ setattr(
199
+ soClass, 'add' + jarn,
200
+ eval('lambda self,o: self._parent.add%s(o)' % jarn))
201
+ if childUpdate:
202
+ makeProperties(soClass)
203
+ return
204
+
205
+ if joinDef:
206
+ super(InheritableSQLMeta, sqlmeta).addJoin(joinDef)
207
+
208
+ # DSM: Update each child class if needed and existing (only for new
209
+ # DSM: dynamic join as no child classes exists at object creation)
210
+ for c in sqlmeta.childClasses.values():
211
+ c.sqlmeta.addJoin(joinDef, childUpdate=True)
212
+
213
+ @classmethod
214
+ def delJoin(sqlmeta, joinDef, childUpdate=False):
215
+ if childUpdate:
216
+ soClass = sqlmeta.soClass
217
+ unmakeProperties(soClass)
218
+ makeProperties(soClass)
219
+ return
220
+
221
+ super(InheritableSQLMeta, sqlmeta).delJoin(joinDef)
222
+
223
+ # DSM: Update each child class if needed
224
+ # DSM: and delete properties for this join
225
+ for c in sqlmeta.childClasses.values():
226
+ c.sqlmeta.delJoin(joinDef, childUpdate=True)
227
+
228
+ @classmethod
229
+ def getAllColumns(sqlmeta):
230
+ columns = sqlmeta.columns.copy()
231
+ sm = sqlmeta
232
+ while sm.parentClass:
233
+ columns.update(sm.parentClass.sqlmeta.columns)
234
+ sm = sm.parentClass.sqlmeta
235
+ return columns
236
+
237
+ @classmethod
238
+ def getColumns(sqlmeta):
239
+ columns = sqlmeta.getAllColumns()
240
+ if 'childName' in columns:
241
+ del columns['childName']
242
+ return columns
243
+
244
+
245
+ class InheritableSQLObject(SQLObject):
246
+
247
+ sqlmeta = InheritableSQLMeta
248
+ _inheritable = True
249
+ SelectResultsClass = InheritableSelectResults
250
+
251
+ def set(self, **kw):
252
+ if self._parent:
253
+ SQLObject.set(self, _suppress_set_sig=True, **kw)
254
+ else:
255
+ SQLObject.set(self, **kw)
256
+
257
+ def __classinit__(cls, new_attrs):
258
+ SQLObject.__classinit__(cls, new_attrs)
259
+ # if we are a child class, add sqlbuilder fields from parents
260
+ currentClass = cls.sqlmeta.parentClass
261
+ while currentClass:
262
+ for column in currentClass.sqlmeta.columnDefinitions.values():
263
+ if column.name == 'childName':
264
+ continue
265
+ if isinstance(column, ForeignKey):
266
+ continue
267
+ setattr(cls.q, column.name,
268
+ getattr(currentClass.q, column.name))
269
+ currentClass = currentClass.sqlmeta.parentClass
270
+
271
+ @classmethod
272
+ def _SO_setupSqlmeta(cls, new_attrs, is_base):
273
+ # Note: cannot use super(InheritableSQLObject, cls)._SO_setupSqlmeta -
274
+ # InheritableSQLObject is not defined when it's __classinit__
275
+ # is run. Cannot use SQLObject._SO_setupSqlmeta, either:
276
+ # the method would be bound to wrong class.
277
+ if cls.__name__ == "InheritableSQLObject":
278
+ call_super = super(cls, cls)
279
+ else:
280
+ # InheritableSQLObject must be in globals yet
281
+ call_super = super(InheritableSQLObject, cls)
282
+ call_super._SO_setupSqlmeta(new_attrs, is_base)
283
+ sqlmeta = cls.sqlmeta
284
+ sqlmeta.childClasses = {}
285
+ # locate parent class and register this class in it's children
286
+ sqlmeta.parentClass = None
287
+ for superclass in cls.__bases__:
288
+ if getattr(superclass, '_inheritable', False) \
289
+ and (superclass.__name__ != 'InheritableSQLObject'):
290
+ if sqlmeta.parentClass:
291
+ # already have a parent class;
292
+ # cannot inherit from more than one
293
+ raise NotImplementedError(
294
+ "Multiple inheritance is not implemented")
295
+ sqlmeta.parentClass = superclass
296
+ superclass.sqlmeta.childClasses[cls.__name__] = cls
297
+ if sqlmeta.parentClass:
298
+ # remove inherited column definitions
299
+ cls.sqlmeta.columns = {}
300
+ cls.sqlmeta.columnList = []
301
+ cls.sqlmeta.columnDefinitions = {}
302
+ # default inheritance child name
303
+ if not sqlmeta.childName:
304
+ sqlmeta.childName = cls.__name__
305
+
306
+ @classmethod
307
+ def get(cls, id, connection=None, selectResults=None,
308
+ childResults=None, childUpdate=False):
309
+
310
+ val = super(InheritableSQLObject, cls).get(id, connection,
311
+ selectResults)
312
+
313
+ # DSM: If we are updating a child, we should never return a child...
314
+ if childUpdate:
315
+ return val
316
+ # DSM: If this class has a child, return the child
317
+ if 'childName' in cls.sqlmeta.columns:
318
+ childName = val.childName
319
+ if childName is not None:
320
+ childClass = cls.sqlmeta.childClasses[childName]
321
+ # If the class has no columns (which sometimes makes sense
322
+ # and may be true for non-inheritable (leaf) classes only),
323
+ # shunt the query to avoid almost meaningless SQL
324
+ # like "SELECT NULL FROM child WHERE id=1".
325
+ # This is based on assumption that child object exists
326
+ # if parent object exists. (If it doesn't your database
327
+ # is broken and that is a job for database maintenance.)
328
+ if not (childResults or childClass.sqlmeta.columns):
329
+ childResults = (None,)
330
+ return childClass.get(id, connection=connection,
331
+ selectResults=childResults)
332
+ # DSM: Now, we know we are alone or the last child in a family...
333
+ # DSM: It's time to find our parents
334
+ inst = val
335
+ while inst.sqlmeta.parentClass and not inst._parent:
336
+ inst._parent = inst.sqlmeta.parentClass.get(
337
+ id, connection=connection, childUpdate=True)
338
+ inst = inst._parent
339
+ # DSM: We can now return ourself
340
+ return val
341
+
342
+ @classmethod
343
+ def _notifyFinishClassCreation(cls):
344
+ sqlmeta = cls.sqlmeta
345
+ # verify names of added columns
346
+ if sqlmeta.parentClass:
347
+ # FIXME: this does not check for grandparent column overrides
348
+ parentCols = sqlmeta.parentClass.sqlmeta.columns.keys()
349
+ for column in sqlmeta.columnList:
350
+ if column.name == 'childName':
351
+ raise AttributeError(
352
+ "The column name 'childName' is reserved")
353
+ if column.name in parentCols:
354
+ raise AttributeError(
355
+ "The column '%s' is already defined "
356
+ "in an inheritable parent" % column.name)
357
+ # if this class is inheritable, add column for children distinction
358
+ if cls._inheritable and (cls.__name__ != 'InheritableSQLObject'):
359
+ sqlmeta.addColumn(
360
+ StringCol(name='childName',
361
+ # limit string length to get VARCHAR and not CLOB
362
+ length=255, default=None))
363
+ if not sqlmeta.columnList:
364
+ # There are no columns - call addColumn to propagate columns
365
+ # from parent classes to children
366
+ sqlmeta.addColumn(None)
367
+ if not sqlmeta.joins:
368
+ # There are no joins - call addJoin to propagate joins
369
+ # from parent classes to children
370
+ sqlmeta.addJoin(None)
371
+
372
+ def _create(self, id, **kw):
373
+
374
+ # DSM: If we were called by a children class,
375
+ # DSM: we must retreive the properties dictionary.
376
+ # DSM: Note: we can't use the ** call paremeter directly
377
+ # DSM: as we must be able to delete items from the dictionary
378
+ # DSM: (and our children must know that the items were removed!)
379
+ if 'kw' in kw:
380
+ kw = kw['kw']
381
+ # DSM: If we are the children of an inheritable class,
382
+ # DSM: we must first create our parent
383
+ if self.sqlmeta.parentClass:
384
+ parentClass = self.sqlmeta.parentClass
385
+ new_kw = {}
386
+ parent_kw = {}
387
+ for (name, value) in kw.items():
388
+ if (name != 'childName') and hasattr(parentClass, name):
389
+ parent_kw[name] = value
390
+ else:
391
+ new_kw[name] = value
392
+ kw = new_kw
393
+
394
+ # Need to check that we have enough data to sucesfully
395
+ # create the current subclass otherwise we will leave
396
+ # the database in an inconsistent state.
397
+ for col in self.sqlmeta.columnList:
398
+ if (col._default == sqlbuilder.NoDefault) and \
399
+ (col.name not in kw) and (col.foreignName not in kw):
400
+ raise TypeError(
401
+ "%s() did not get expected keyword argument "
402
+ "%s" % (self.__class__.__name__, col.name))
403
+
404
+ parent_kw['childName'] = self.sqlmeta.childName
405
+ self._parent = parentClass(kw=parent_kw,
406
+ connection=self._connection)
407
+
408
+ id = self._parent.id
409
+
410
+ # TC: Create this record and catch all exceptions in order to destroy
411
+ # TC: the parent if the child can not be created.
412
+ try:
413
+ super(InheritableSQLObject, self)._create(id, **kw)
414
+ except Exception:
415
+ # If we are outside a transaction and this is a child,
416
+ # destroy the parent
417
+ connection = self._connection
418
+ if (not isinstance(connection, dbconnection.Transaction)
419
+ and connection.autoCommit) and self.sqlmeta.parentClass:
420
+ self._parent.destroySelf()
421
+ # TC: Do we need to do this??
422
+ self._parent = None
423
+ # TC: Reraise the original exception
424
+ raise
425
+
426
+ @classmethod
427
+ def _findAlternateID(cls, name, dbName, value, connection=None):
428
+ result = list(cls.selectBy(connection, **{name: value}))
429
+ if not result:
430
+ return result, None
431
+ obj = result[0]
432
+ return [obj.id], obj
433
+
434
+ @classmethod
435
+ def select(cls, clause=None, *args, **kwargs):
436
+ parentClass = cls.sqlmeta.parentClass
437
+ childUpdate = kwargs.pop('childUpdate', None)
438
+ # childUpdate may have one of three values:
439
+ # True:
440
+ # select was issued by parent class to create child objects.
441
+ # Execute select without modifications.
442
+ # None (default):
443
+ # select is run by application. If this class is inheritance
444
+ # child, delegate query to the parent class to utilize
445
+ # InheritableIteration optimizations. Selected records
446
+ # are restricted to this (child) class by adding childName
447
+ # filter to the where clause.
448
+ # False:
449
+ # select is delegated from inheritance child which is parent
450
+ # of another class. Delegate the query to parent if possible,
451
+ # but don't add childName restriction: selected records
452
+ # will be filtered by join to the table filtered by childName.
453
+ if (not childUpdate) and parentClass:
454
+ if childUpdate is None:
455
+ # this is the first parent in deep hierarchy
456
+ addClause = parentClass.q.childName == cls.sqlmeta.childName
457
+ # if the clause was one of TRUE varians, replace it
458
+ if (clause is None) or (clause is sqlbuilder.SQLTrueClause) \
459
+ or (
460
+ isinstance(clause, string_type)
461
+ and (clause == 'all')):
462
+ clause = addClause
463
+ else:
464
+ # patch WHERE condition:
465
+ # change ID field of this class to ID of parent class
466
+ # XXX the clause is patched in place; it would be better
467
+ # to build a new one if we have to replace field
468
+ clsID = cls.q.id
469
+ parentID = parentClass.q.id
470
+
471
+ def _get_patched(clause):
472
+ if isinstance(clause, sqlbuilder.SQLOp):
473
+ _patch_id_clause(clause)
474
+ return None
475
+ elif not isinstance(clause, sqlbuilder.Field):
476
+ return None
477
+ elif (clause.tableName == clsID.tableName) \
478
+ and (clause.fieldName == clsID.fieldName):
479
+ return parentID
480
+ else:
481
+ return None
482
+
483
+ def _patch_id_clause(clause):
484
+ if not isinstance(clause, sqlbuilder.SQLOp):
485
+ return
486
+ expr = _get_patched(clause.expr1)
487
+ if expr:
488
+ clause.expr1 = expr
489
+ expr = _get_patched(clause.expr2)
490
+ if expr:
491
+ clause.expr2 = expr
492
+ _patch_id_clause(clause)
493
+ # add childName filter
494
+ clause = sqlbuilder.AND(clause, addClause)
495
+ return parentClass.select(clause, childUpdate=False,
496
+ *args, **kwargs)
497
+ else:
498
+ return super(InheritableSQLObject, cls).select(
499
+ clause, *args, **kwargs)
500
+
501
+ @classmethod
502
+ def selectBy(cls, connection=None, **kw):
503
+ clause = []
504
+ foreignColumns = {}
505
+ currentClass = cls
506
+ while currentClass:
507
+ foreignColumns.update(dict(
508
+ [(column.foreignName, name)
509
+ for (name, column) in currentClass.sqlmeta.columns.items()
510
+ if column.foreignKey
511
+ ]))
512
+ currentClass = currentClass.sqlmeta.parentClass
513
+ for name, value in kw.items():
514
+ if name in foreignColumns:
515
+ name = foreignColumns[name] # translate "key" to "keyID"
516
+ if isinstance(value, SQLObject):
517
+ value = value.id
518
+ currentClass = cls
519
+ while currentClass:
520
+ try:
521
+ clause.append(getattr(currentClass.q, name) == value)
522
+ break
523
+ except AttributeError:
524
+ pass
525
+ currentClass = currentClass.sqlmeta.parentClass
526
+ else:
527
+ raise AttributeError(
528
+ "'%s' instance has no attribute '%s'" % (
529
+ cls.__name__, name))
530
+ if clause:
531
+ clause = reduce(sqlbuilder.AND, clause)
532
+ else:
533
+ clause = None # select all
534
+ conn = connection or cls._connection
535
+ return cls.SelectResultsClass(cls, clause, connection=conn)
536
+
537
+ def destroySelf(self):
538
+ # DSM: If this object has parents, recursivly kill them
539
+ if hasattr(self, '_parent') and self._parent:
540
+ self._parent.destroySelf()
541
+ super(InheritableSQLObject, self).destroySelf()
542
+
543
+ def _reprItems(self):
544
+ items = super(InheritableSQLObject, self)._reprItems()
545
+ # add parent attributes (if any)
546
+ if self.sqlmeta.parentClass:
547
+ items.extend(self._parent._reprItems())
548
+ # filter out our special column
549
+ return [item for item in items if item[0] != 'childName']
550
+
551
+ __all__ = ['InheritableSQLObject']
@@ -0,0 +1,106 @@
1
+ from sqlobject import sqlbuilder
2
+ from sqlobject.classregistry import findClass
3
+ from sqlobject.dbconnection import Iteration
4
+
5
+
6
+ class InheritableIteration(Iteration):
7
+ # Default array size for cursor.fetchmany()
8
+ defaultArraySize = 10000
9
+
10
+ def __init__(self, dbconn, rawconn, select, keepConnection=False):
11
+ super(InheritableIteration, self).__init__(dbconn, rawconn, select,
12
+ keepConnection)
13
+ self.lazyColumns = select.ops.get('lazyColumns', False)
14
+ try:
15
+ self.cursor.arraysize = self.defaultArraySize
16
+ self.use_arraysize = True
17
+ except AttributeError: # pymssql doesn't have arraysize
18
+ self.use_arraysize = False
19
+ self._results = []
20
+ # Find the index of the childName column
21
+ childNameIdx = None
22
+ columns = select.sourceClass.sqlmeta.columnList
23
+ for i, column in enumerate(columns):
24
+ if column.name == "childName":
25
+ childNameIdx = i
26
+ break
27
+ self._childNameIdx = childNameIdx
28
+
29
+ def next(self):
30
+ if not self._results:
31
+ if self.use_arraysize:
32
+ _results = self.cursor.fetchmany()
33
+ else:
34
+ _results = self.cursor.fetchmany(size=self.defaultArraySize)
35
+ self._results = list(_results)
36
+ if not self.lazyColumns:
37
+ self.fetchChildren()
38
+ if not self._results:
39
+ self._cleanup()
40
+ raise StopIteration
41
+ result = self._results[0]
42
+ del self._results[0]
43
+ if self.lazyColumns:
44
+ obj = self.select.sourceClass.get(result[0],
45
+ connection=self.dbconn)
46
+ return obj
47
+ else:
48
+ id = result[0]
49
+ if id in self._childrenResults:
50
+ childResults = self._childrenResults[id]
51
+ del self._childrenResults[id]
52
+ else:
53
+ childResults = None
54
+ obj = self.select.sourceClass.get(
55
+ id, selectResults=result[1:],
56
+ childResults=childResults, connection=self.dbconn)
57
+ return obj
58
+
59
+ def fetchChildren(self):
60
+ """Prefetch childrens' data
61
+
62
+ Fetch childrens' data for every subclass in one big .select()
63
+ to avoid .get() fetching it one by one.
64
+ """
65
+ self._childrenResults = {}
66
+ if self._childNameIdx is None:
67
+ return
68
+ childIdsNames = {}
69
+ childNameIdx = self._childNameIdx
70
+ for result in self._results:
71
+ childName = result[childNameIdx + 1]
72
+ if childName:
73
+ ids = childIdsNames.get(childName)
74
+ if ids is None:
75
+ ids = childIdsNames[childName] = []
76
+ ids.append(result[0])
77
+ dbconn = self.dbconn
78
+ rawconn = self.rawconn
79
+ cursor = rawconn.cursor()
80
+ registry = self.select.sourceClass.sqlmeta.registry
81
+ for childName, ids in childIdsNames.items():
82
+ klass = findClass(childName, registry)
83
+ if len(ids) == 1:
84
+ select = klass.select(klass.q.id == ids[0],
85
+ childUpdate=True, connection=dbconn)
86
+ else:
87
+ select = klass.select(sqlbuilder.IN(klass.q.id, ids),
88
+ childUpdate=True, connection=dbconn)
89
+ query = dbconn.queryForSelect(select)
90
+ if dbconn.debug:
91
+ dbconn.printDebug(rawconn, query,
92
+ 'Select children of the class %s' %
93
+ childName)
94
+ self.dbconn._executeRetry(rawconn, cursor, query)
95
+ for result in cursor.fetchall():
96
+ # Inheritance child classes may have no own columns
97
+ # (that makes sense when child class has a join
98
+ # that does not apply to parent class objects).
99
+ # In such cases result[1:] gives an empty tuple
100
+ # which is interpreted as "no results fetched" in .get().
101
+ # So .get() issues another query which is absolutely
102
+ # meaningless (like "SELECT NULL FROM child WHERE id=1").
103
+ # In order to avoid this, we replace empty results
104
+ # with non-empty tuple. Extra values in selectResults
105
+ # are Ok - they will be ignored by ._SO_selectInit().
106
+ self._childrenResults[result[0]] = result[1:] or (None,)
@@ -0,0 +1 @@
1
+ #
@@ -0,0 +1,23 @@
1
+ from sqlobject import IntCol
2
+ from sqlobject.inheritance import InheritableSQLObject
3
+ from sqlobject.tests.dbtest import raises, setupClass
4
+
5
+
6
+ class SOTestAggregate1(InheritableSQLObject):
7
+ value1 = IntCol()
8
+
9
+
10
+ class SOTestAggregate2(SOTestAggregate1):
11
+ value2 = IntCol()
12
+
13
+
14
+ def test_aggregates():
15
+ setupClass([SOTestAggregate1, SOTestAggregate2])
16
+
17
+ SOTestAggregate1(value1=1)
18
+ SOTestAggregate2(value1=2, value2=12)
19
+
20
+ assert SOTestAggregate1.select().max("value1") == 2
21
+ assert SOTestAggregate2.select().max("value1") == 2
22
+ raises(Exception, SOTestAggregate2.select().max, "value2")
23
+ assert SOTestAggregate2.select().max(SOTestAggregate2.q.value2) == 12