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/sresults.py ADDED
@@ -0,0 +1,377 @@
1
+ from . import dbconnection
2
+ from . import sqlbuilder
3
+ from .compat import string_type
4
+
5
+
6
+ __all__ = ['SelectResults']
7
+
8
+
9
+ class SelectResults(object):
10
+ IterationClass = dbconnection.Iteration
11
+
12
+ def __init__(self, sourceClass, clause, clauseTables=None,
13
+ **ops):
14
+ self.sourceClass = sourceClass
15
+ if clause is None or isinstance(clause, str) and clause == 'all':
16
+ clause = sqlbuilder.SQLTrueClause
17
+ if not isinstance(clause, sqlbuilder.SQLExpression):
18
+ clause = sqlbuilder.SQLConstant(clause)
19
+ self.clause = clause
20
+ self.ops = ops
21
+ if ops.get('orderBy', sqlbuilder.NoDefault) is sqlbuilder.NoDefault:
22
+ ops['orderBy'] = sourceClass.sqlmeta.defaultOrder
23
+ orderBy = ops['orderBy']
24
+ if isinstance(orderBy, (tuple, list)):
25
+ orderBy = list(map(self._mungeOrderBy, orderBy))
26
+ else:
27
+ orderBy = self._mungeOrderBy(orderBy)
28
+ ops['dbOrderBy'] = orderBy
29
+ if 'connection' in ops and ops['connection'] is None:
30
+ del ops['connection']
31
+ if ops.get('limit', None):
32
+ assert not ops.get('start', None) and not ops.get('end', None), \
33
+ "'limit' cannot be used with 'start' or 'end'"
34
+ ops["start"] = 0
35
+ ops["end"] = ops.pop("limit")
36
+
37
+ tablesSet = sqlbuilder.tablesUsedSet(self.clause,
38
+ self._getConnection().dbName)
39
+ if clauseTables:
40
+ for table in clauseTables:
41
+ tablesSet.add(table)
42
+ self.clauseTables = clauseTables
43
+ # Explicitly post-adding-in sqlmeta.table,
44
+ # sqlbuilder.Select will handle sqlrepr'ing and dupes.
45
+ self.tables = list(tablesSet) + [sourceClass.sqlmeta.table]
46
+
47
+ def queryForSelect(self):
48
+ columns = [self.sourceClass.q.id] + \
49
+ [getattr(self.sourceClass.q, x.name)
50
+ for x in self.sourceClass.sqlmeta.columnList]
51
+ query = sqlbuilder.Select(columns,
52
+ where=self.clause,
53
+ join=self.ops.get(
54
+ 'join', sqlbuilder.NoDefault),
55
+ distinct=self.ops.get('distinct', False),
56
+ lazyColumns=self.ops.get(
57
+ 'lazyColumns', False),
58
+ start=self.ops.get('start', 0),
59
+ end=self.ops.get('end', None),
60
+ orderBy=self.ops.get(
61
+ 'dbOrderBy', sqlbuilder.NoDefault),
62
+ reversed=self.ops.get('reversed', False),
63
+ staticTables=self.tables,
64
+ forUpdate=self.ops.get('forUpdate', False))
65
+ return query
66
+
67
+ def __repr__(self):
68
+ return "<%s at %x>" % (self.__class__.__name__, id(self))
69
+
70
+ def _getConnection(self):
71
+ return self.ops.get('connection') or self.sourceClass._connection
72
+
73
+ def __str__(self):
74
+ conn = self._getConnection()
75
+ return conn.queryForSelect(self)
76
+
77
+ def _mungeOrderBy(self, orderBy):
78
+ if isinstance(orderBy, string_type) and orderBy.startswith('-'):
79
+ orderBy = orderBy[1:]
80
+ desc = True
81
+ else:
82
+ desc = False
83
+ if isinstance(orderBy, string_type):
84
+ if orderBy in self.sourceClass.sqlmeta.columns:
85
+ val = getattr(self.sourceClass.q,
86
+ self.sourceClass.sqlmeta.columns[orderBy].name)
87
+ if desc:
88
+ return sqlbuilder.DESC(val)
89
+ else:
90
+ return val
91
+ else:
92
+ orderBy = sqlbuilder.SQLConstant(orderBy)
93
+ if desc:
94
+ return sqlbuilder.DESC(orderBy)
95
+ else:
96
+ return orderBy
97
+ else:
98
+ return orderBy
99
+
100
+ def clone(self, **newOps):
101
+ ops = self.ops.copy()
102
+ ops.update(newOps)
103
+ return self.__class__(self.sourceClass, self.clause,
104
+ self.clauseTables, **ops)
105
+
106
+ def orderBy(self, orderBy):
107
+ return self.clone(orderBy=orderBy)
108
+
109
+ def connection(self, conn):
110
+ return self.clone(connection=conn)
111
+
112
+ def limit(self, limit):
113
+ return self[:limit]
114
+
115
+ def lazyColumns(self, value):
116
+ return self.clone(lazyColumns=value)
117
+
118
+ def reversed(self):
119
+ return self.clone(reversed=not self.ops.get('reversed', False))
120
+
121
+ def distinct(self):
122
+ return self.clone(distinct=True)
123
+
124
+ def newClause(self, new_clause):
125
+ return self.__class__(self.sourceClass, new_clause,
126
+ self.clauseTables, **self.ops)
127
+
128
+ def filter(self, filter_clause):
129
+ if filter_clause is None:
130
+ # None doesn't filter anything, it's just a no-op:
131
+ return self
132
+ clause = self.clause
133
+ if isinstance(clause, string_type):
134
+ clause = sqlbuilder.SQLConstant('(%s)' % clause)
135
+ return self.newClause(sqlbuilder.AND(clause, filter_clause))
136
+
137
+ def __getitem__(self, value):
138
+ if isinstance(value, slice):
139
+ assert not value.step, "Slices do not support steps"
140
+ if not value.start and not value.stop:
141
+ # No need to copy, I'm immutable
142
+ return self
143
+
144
+ # Negative indexes aren't handled (and everything we
145
+ # don't handle ourselves we just create a list to
146
+ # handle)
147
+ if (value.start and value.start < 0) \
148
+ or (value.stop and value.stop < 0):
149
+ if value.start:
150
+ if value.stop:
151
+ return list(self)[value.start:value.stop]
152
+ return list(self)[value.start:]
153
+ return list(self)[:value.stop]
154
+
155
+ if value.start:
156
+ assert value.start >= 0
157
+ start = self.ops.get('start', 0) + value.start
158
+ if value.stop is not None:
159
+ assert value.stop >= 0
160
+ if value.stop < value.start:
161
+ # an empty result:
162
+ end = start
163
+ else:
164
+ end = value.stop + self.ops.get('start', 0)
165
+ if self.ops.get('end', None) is not None and \
166
+ self.ops['end'] < end:
167
+ # truncated by previous slice:
168
+ end = self.ops['end']
169
+ else:
170
+ end = self.ops.get('end', None)
171
+ else:
172
+ start = self.ops.get('start', 0)
173
+ end = value.stop + start
174
+ if self.ops.get('end', None) is not None \
175
+ and self.ops['end'] < end:
176
+ end = self.ops['end']
177
+ return self.clone(start=start, end=end)
178
+ else:
179
+ if value < 0:
180
+ return list(iter(self))[value]
181
+ else:
182
+ start = self.ops.get('start', 0) + value
183
+ return list(self.clone(start=start, end=start + 1))[0]
184
+
185
+ def __iter__(self):
186
+ # @@: This could be optimized, using a simpler algorithm
187
+ # since we don't have to worry about garbage collection,
188
+ # etc., like we do with .lazyIter()
189
+ return iter(list(self.lazyIter()))
190
+
191
+ def lazyIter(self):
192
+ """
193
+ Returns an iterator that will lazily pull rows out of the
194
+ database and return SQLObject instances
195
+ """
196
+ conn = self._getConnection()
197
+ return conn.iterSelect(self)
198
+
199
+ def accumulate(self, *expressions):
200
+ """ Use accumulate expression(s) to select result
201
+ using another SQL select through current
202
+ connection.
203
+ Return the accumulate result
204
+ """
205
+ conn = self._getConnection()
206
+ exprs = []
207
+ for expr in expressions:
208
+ if not isinstance(expr, sqlbuilder.SQLExpression):
209
+ expr = sqlbuilder.SQLConstant(expr)
210
+ exprs.append(expr)
211
+ return conn.accumulateSelect(self, *exprs)
212
+
213
+ def count(self):
214
+ """ Counting elements of current select results """
215
+ assert not self.ops.get('start') and not self.ops.get('end'), \
216
+ "start/end/limit have no meaning with 'count'"
217
+ assert not (self.ops.get('distinct')
218
+ and (self.ops.get('start') or self.ops.get('end'))), \
219
+ "distinct-counting of sliced objects is not supported"
220
+ if self.ops.get('distinct'):
221
+ # Column must be specified, so we are using unique ID column.
222
+ # COUNT(DISTINCT column) is supported by MySQL and PostgreSQL,
223
+ # but not by SQLite. Perhaps more portable would be subquery:
224
+ # SELECT COUNT(*) FROM (SELECT DISTINCT id FROM table)
225
+ count = self.accumulate(
226
+ 'COUNT(DISTINCT %s)' % self._getConnection().sqlrepr(
227
+ self.sourceClass.q.id))
228
+ else:
229
+ count = self.accumulate('COUNT(*)')
230
+ if self.ops.get('start'):
231
+ count -= self.ops['start']
232
+ if self.ops.get('end'):
233
+ count = min(self.ops['end'] - self.ops.get('start', 0), count)
234
+ return count
235
+
236
+ def accumulateMany(self, *attributes):
237
+ """ Making the expressions for count/sum/min/max/avg
238
+ of a given select result attributes.
239
+ `attributes` must be a list/tuple of pairs (func_name, attribute);
240
+ `attribute` can be a column name (like 'a_column')
241
+ or a dot-q attribute (like Table.q.aColumn)
242
+ """
243
+ expressions = []
244
+ conn = self._getConnection()
245
+ if self.ops.get('distinct'):
246
+ distinct = 'DISTINCT '
247
+ else:
248
+ distinct = ''
249
+ for func_name, attribute in attributes:
250
+ if not isinstance(attribute, str):
251
+ attribute = conn.sqlrepr(attribute)
252
+ expression = '%s(%s%s)' % (func_name, distinct, attribute)
253
+ expressions.append(expression)
254
+ return self.accumulate(*expressions)
255
+
256
+ def accumulateOne(self, func_name, attribute):
257
+ """ Making the sum/min/max/avg of a given select result attribute.
258
+ `attribute` can be a column name (like 'a_column')
259
+ or a dot-q attribute (like Table.q.aColumn)
260
+ """
261
+ return self.accumulateMany((func_name, attribute))
262
+
263
+ def sum(self, attribute):
264
+ return self.accumulateOne("SUM", attribute)
265
+
266
+ def min(self, attribute):
267
+ return self.accumulateOne("MIN", attribute)
268
+
269
+ def avg(self, attribute):
270
+ return self.accumulateOne("AVG", attribute)
271
+
272
+ def max(self, attribute):
273
+ return self.accumulateOne("MAX", attribute)
274
+
275
+ def getOne(self, default=sqlbuilder.NoDefault):
276
+ """
277
+ If a query is expected to only return a single value,
278
+ using ``.getOne()`` will return just that value.
279
+
280
+ If not results are found, ``SQLObjectNotFound`` will be
281
+ raised, unless you pass in a default value (like
282
+ ``.getOne(None)``).
283
+
284
+ If more than one result is returned,
285
+ ``SQLObjectIntegrityError`` will be raised.
286
+ """
287
+ from . import main
288
+ results = list(self)
289
+ if not results:
290
+ if default is sqlbuilder.NoDefault:
291
+ raise main.SQLObjectNotFound(
292
+ "No results matched the query for %s"
293
+ % self.sourceClass.__name__)
294
+ return default
295
+ if len(results) > 1:
296
+ raise main.SQLObjectIntegrityError(
297
+ "More than one result returned from query: %s"
298
+ % results)
299
+ return results[0]
300
+
301
+ def throughTo(self):
302
+ class _throughTo_getter(object):
303
+ def __init__(self, inst):
304
+ self.sresult = inst
305
+
306
+ def __getattr__(self, attr):
307
+ return self.sresult._throughTo(attr)
308
+ return _throughTo_getter(self)
309
+ throughTo = property(throughTo)
310
+
311
+ def _throughTo(self, attr):
312
+ otherClass = None
313
+ orderBy = sqlbuilder.NoDefault
314
+
315
+ ref = self.sourceClass.sqlmeta.columns.get(
316
+ attr.endswith('ID') and attr or attr + 'ID', None)
317
+ if ref and ref.foreignKey:
318
+ otherClass, clause = self._throughToFK(ref)
319
+ else:
320
+ join = [x for x in self.sourceClass.sqlmeta.joins
321
+ if x.joinMethodName == attr]
322
+ if join:
323
+ join = join[0]
324
+ orderBy = join.orderBy
325
+ if hasattr(join, 'otherColumn'):
326
+ otherClass, clause = self._throughToRelatedJoin(join)
327
+ else:
328
+ otherClass, clause = self._throughToMultipleJoin(join)
329
+
330
+ if not otherClass:
331
+ raise AttributeError(
332
+ "throughTo argument (got %s) should be "
333
+ "name of foreignKey or SQL*Join in %s" % (attr,
334
+ self.sourceClass))
335
+
336
+ return otherClass.select(clause,
337
+ orderBy=orderBy,
338
+ connection=self._getConnection())
339
+
340
+ def _throughToFK(self, col):
341
+ otherClass = getattr(self.sourceClass, "_SO_class_" + col.foreignKey)
342
+ colName = col.name
343
+ query = self.queryForSelect().newItems([
344
+ sqlbuilder.ColumnAS(getattr(self.sourceClass.q, colName), colName)
345
+ ]).orderBy(None).distinct()
346
+ query = sqlbuilder.Alias(query,
347
+ "%s_%s" % (self.sourceClass.__name__,
348
+ col.name))
349
+ return otherClass, otherClass.q.id == getattr(query.q, colName)
350
+
351
+ def _throughToMultipleJoin(self, join):
352
+ otherClass = join.otherClass
353
+ colName = join.soClass.sqlmeta.style.\
354
+ dbColumnToPythonAttr(join.joinColumn)
355
+ query = self.queryForSelect().newItems(
356
+ [sqlbuilder.ColumnAS(self.sourceClass.q.id, 'id')]).\
357
+ orderBy(None).distinct()
358
+ query = sqlbuilder.Alias(query,
359
+ "%s_%s" % (self.sourceClass.__name__,
360
+ join.joinMethodName))
361
+ joinColumn = getattr(otherClass.q, colName)
362
+ return otherClass, joinColumn == query.q.id
363
+
364
+ def _throughToRelatedJoin(self, join):
365
+ otherClass = join.otherClass
366
+ intTable = sqlbuilder.Table(join.intermediateTable)
367
+ colName = join.joinColumn
368
+ query = self.queryForSelect().newItems(
369
+ [sqlbuilder.ColumnAS(self.sourceClass.q.id, 'id')]).\
370
+ orderBy(None).distinct()
371
+ query = sqlbuilder.Alias(query,
372
+ "%s_%s" % (self.sourceClass.__name__,
373
+ join.joinMethodName))
374
+ clause = sqlbuilder.AND(
375
+ otherClass.q.id == getattr(intTable, join.otherColumn),
376
+ getattr(intTable, colName) == query.q.id)
377
+ return otherClass, clause
sqlobject/styles.py ADDED
@@ -0,0 +1,174 @@
1
+ import re
2
+
3
+
4
+ __all__ = ["Style", "MixedCaseUnderscoreStyle", "DefaultStyle",
5
+ "MixedCaseStyle"]
6
+
7
+
8
+ class Style(object):
9
+
10
+ """
11
+ The base Style class, and also the simplest implementation. No
12
+ translation occurs -- column names and attribute names match,
13
+ as do class names and table names (when using auto class or
14
+ schema generation).
15
+ """
16
+
17
+ def __init__(self, pythonAttrToDBColumn=None,
18
+ dbColumnToPythonAttr=None,
19
+ pythonClassToDBTable=None,
20
+ dbTableToPythonClass=None,
21
+ idForTable=None,
22
+ longID=False):
23
+ if pythonAttrToDBColumn:
24
+ self.pythonAttrToDBColumn = \
25
+ lambda a, s=self: pythonAttrToDBColumn(s, a)
26
+ if dbColumnToPythonAttr:
27
+ self.dbColumnToPythonAttr = \
28
+ lambda a, s=self: dbColumnToPythonAttr(s, a)
29
+ if pythonClassToDBTable:
30
+ self.pythonClassToDBTable = \
31
+ lambda a, s=self: pythonClassToDBTable(s, a)
32
+ if dbTableToPythonClass:
33
+ self.dbTableToPythonClass = \
34
+ lambda a, s=self: dbTableToPythonClass(s, a)
35
+ if idForTable:
36
+ self.idForTable = lambda a, s=self: idForTable(s, a)
37
+ self.longID = longID
38
+
39
+ def pythonAttrToDBColumn(self, attr):
40
+ return attr
41
+
42
+ def dbColumnToPythonAttr(self, col):
43
+ return col
44
+
45
+ def pythonClassToDBTable(self, className):
46
+ return className
47
+
48
+ def dbTableToPythonClass(self, table):
49
+ return table
50
+
51
+ def idForTable(self, table):
52
+ if self.longID:
53
+ return self.tableReference(table)
54
+ else:
55
+ return 'id'
56
+
57
+ def pythonClassToAttr(self, className):
58
+ return lowerword(className)
59
+
60
+ def instanceAttrToIDAttr(self, attr):
61
+ return attr + "ID"
62
+
63
+ def instanceIDAttrToAttr(self, attr):
64
+ return attr[:-2]
65
+
66
+ def tableReference(self, table):
67
+ return table + "_id"
68
+
69
+
70
+ class MixedCaseUnderscoreStyle(Style):
71
+
72
+ """
73
+ This is the default style. Python attributes use mixedCase,
74
+ while database columns use underscore_separated.
75
+ """
76
+
77
+ def pythonAttrToDBColumn(self, attr):
78
+ return mixedToUnder(attr)
79
+
80
+ def dbColumnToPythonAttr(self, col):
81
+ return underToMixed(col)
82
+
83
+ def pythonClassToDBTable(self, className):
84
+ return className[0].lower() \
85
+ + mixedToUnder(className[1:])
86
+
87
+ def dbTableToPythonClass(self, table):
88
+ return table[0].upper() \
89
+ + underToMixed(table[1:])
90
+
91
+ def pythonClassToDBTableReference(self, className):
92
+ return self.tableReference(self.pythonClassToDBTable(className))
93
+
94
+ def tableReference(self, table):
95
+ return table + "_id"
96
+
97
+ DefaultStyle = MixedCaseUnderscoreStyle
98
+
99
+
100
+ class MixedCaseStyle(Style):
101
+
102
+ """
103
+ This style leaves columns as mixed-case, and uses long
104
+ ID names (like ProductID instead of simply id).
105
+ """
106
+
107
+ def pythonAttrToDBColumn(self, attr):
108
+ return capword(attr)
109
+
110
+ def dbColumnToPythonAttr(self, col):
111
+ return lowerword(col)
112
+
113
+ def dbTableToPythonClass(self, table):
114
+ return capword(table)
115
+
116
+ def tableReference(self, table):
117
+ return table + "ID"
118
+
119
+ defaultStyle = DefaultStyle()
120
+
121
+
122
+ def getStyle(soClass, dbConnection=None):
123
+ if dbConnection is None:
124
+ if hasattr(soClass, '_connection'):
125
+ dbConnection = soClass._connection
126
+ if hasattr(soClass.sqlmeta, 'style') and soClass.sqlmeta.style:
127
+ return soClass.sqlmeta.style
128
+ elif dbConnection and dbConnection.style:
129
+ return dbConnection.style
130
+ else:
131
+ return defaultStyle
132
+
133
+
134
+ ############################################################
135
+ # Text utilities
136
+ ############################################################
137
+
138
+
139
+ _mixedToUnderRE = re.compile(r'[A-Z]+')
140
+
141
+
142
+ def mixedToUnder(s):
143
+ if s.endswith('ID'):
144
+ return mixedToUnder(s[:-2] + "_id")
145
+ trans = _mixedToUnderRE.sub(mixedToUnderSub, s)
146
+ if trans.startswith('_'):
147
+ trans = trans[1:]
148
+ return trans
149
+
150
+
151
+ def mixedToUnderSub(match):
152
+ m = match.group(0).lower()
153
+ if len(m) > 1:
154
+ return '_%s_%s' % (m[:-1], m[-1])
155
+ else:
156
+ return '_%s' % m
157
+
158
+
159
+ def capword(s):
160
+ return s[0].upper() + s[1:]
161
+
162
+
163
+ def lowerword(s):
164
+ return s[0].lower() + s[1:]
165
+
166
+
167
+ _underToMixedRE = re.compile('_.')
168
+
169
+
170
+ def underToMixed(name):
171
+ if name.endswith('_id'):
172
+ return underToMixed(name[:-3] + "ID")
173
+ return _underToMixedRE.sub(lambda m: m.group(0)[1].upper(),
174
+ name)
@@ -0,0 +1,8 @@
1
+ from sqlobject.dbconnection import registerConnection
2
+
3
+
4
+ def builder():
5
+ from . import sybaseconnection
6
+ return sybaseconnection.SybaseConnection
7
+
8
+ registerConnection(['sybase'], builder)