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,463 @@
1
+ import os
2
+ import re
3
+ import warnings
4
+ from sqlobject import col
5
+ from sqlobject.dbconnection import DBAPI
6
+
7
+
8
+ class FirebirdConnection(DBAPI):
9
+
10
+ supportTransactions = False
11
+ dbName = 'firebird'
12
+ schemes = [dbName]
13
+
14
+ limit_re = re.compile(r'^\s*(select )(.*)', re.IGNORECASE)
15
+
16
+ def __init__(self, host, db, port='3050', user='sysdba',
17
+ password='masterkey', autoCommit=1,
18
+ dialect=None, role=None, charset=None, **kw):
19
+ drivers = kw.pop('driver', None) or 'fdb,kinterbasdb'
20
+ for driver in drivers.split(','):
21
+ driver = driver.strip()
22
+ if not driver:
23
+ continue
24
+ try:
25
+ if driver == 'fdb':
26
+ import fdb
27
+ self.module = fdb
28
+ elif driver == 'kinterbasdb':
29
+ import kinterbasdb
30
+ # See
31
+ # http://kinterbasdb.sourceforge.net/dist_docs/usage.html
32
+ # for an explanation; in short: use datetime, decimal and
33
+ # unicode.
34
+ kinterbasdb.init(type_conv=200)
35
+ self.module = kinterbasdb
36
+ elif driver in ('firebirdsql', 'pyfirebirdsql'):
37
+ import firebirdsql
38
+ self.module = firebirdsql
39
+ else:
40
+ raise ValueError(
41
+ 'Unknown FireBird driver "%s", '
42
+ 'expected fdb, kinterbasdb or firebirdsql' % driver)
43
+ except ImportError:
44
+ pass
45
+ else:
46
+ break
47
+ else:
48
+ raise ImportError(
49
+ 'Cannot find an FireBird driver, tried %s' % drivers)
50
+ self.host = host
51
+ self.port = port
52
+ self.db = db
53
+ self.user = user
54
+ self.password = password
55
+ if dialect:
56
+ self.dialect = int(dialect)
57
+ else:
58
+ self.dialect = None
59
+ self.role = role
60
+ if charset:
61
+ # Encoding defined by user in the connection string.
62
+ self.dbEncoding = charset.replace('-', '')
63
+ else:
64
+ self.dbEncoding = charset
65
+ # Encoding defined during database creation and stored in the database.
66
+ self.defaultDbEncoding = ''
67
+ DBAPI.__init__(self, **kw)
68
+
69
+ @classmethod
70
+ def _connectionFromParams(cls, auth, password, host, port, path, args):
71
+ if not password:
72
+ password = 'masterkey'
73
+ if not auth:
74
+ auth = 'sysdba'
75
+ # check for alias using
76
+ if (path[0] == '/') and path[-3:].lower() not in ('fdb', 'gdb'):
77
+ path = path[1:]
78
+ path = path.replace('/', os.sep)
79
+ return cls(host, port=port, db=path, user=auth, password=password,
80
+ **args)
81
+
82
+ def _runWithConnection(self, meth, *args):
83
+ if not self.autoCommit:
84
+ return DBAPI._runWithConnection(self, meth, args)
85
+ conn = self.getConnection()
86
+ # @@: Horrible auto-commit implementation. Just horrible!
87
+ try:
88
+ conn.begin()
89
+ except self.module.ProgrammingError:
90
+ pass
91
+ try:
92
+ val = meth(conn, *args)
93
+ try:
94
+ conn.commit()
95
+ except self.module.ProgrammingError:
96
+ pass
97
+ finally:
98
+ self.releaseConnection(conn)
99
+ return val
100
+
101
+ def _setAutoCommit(self, conn, auto):
102
+ # Only _runWithConnection does "autocommit", so we don't
103
+ # need to worry about that.
104
+ pass
105
+
106
+ def makeConnection(self):
107
+ extra = {}
108
+ if self.dialect:
109
+ extra['dialect'] = self.dialect
110
+ return self.module.connect(
111
+ host=self.host,
112
+ port=self.port,
113
+ database=self.db,
114
+ user=self.user,
115
+ password=self.password,
116
+ role=self.role,
117
+ charset=self.dbEncoding,
118
+ **extra
119
+ )
120
+
121
+ def _queryInsertID(self, conn, soInstance, id, names, values):
122
+ """Firebird uses 'generators' to create new ids for a table.
123
+ The users needs to create a generator named GEN_<tablename>
124
+ for each table this method to work."""
125
+ table = soInstance.sqlmeta.table
126
+ idName = soInstance.sqlmeta.idName
127
+ sequenceName = soInstance.sqlmeta.idSequence or 'GEN_%s' % table
128
+ c = conn.cursor()
129
+ if id is None:
130
+ c.execute('SELECT gen_id(%s,1) FROM rdb$database' % sequenceName)
131
+ id = c.fetchone()[0]
132
+ names = [idName] + names
133
+ values = [id] + values
134
+ q = self._insertSQL(table, names, values)
135
+ if self.debug:
136
+ self.printDebug(conn, q, 'QueryIns')
137
+ c.execute(q)
138
+ c.close()
139
+ if self.debugOutput:
140
+ self.printDebug(conn, id, 'QueryIns', 'result')
141
+ return id
142
+
143
+ @classmethod
144
+ def _queryAddLimitOffset(cls, query, start, end):
145
+ """Firebird slaps the limit and offset (actually 'first' and
146
+ 'skip', respectively) statement right after the select."""
147
+ if not start:
148
+ limit_str = "SELECT FIRST %i" % end
149
+ if not end:
150
+ limit_str = "SELECT SKIP %i" % start
151
+ else:
152
+ limit_str = "SELECT FIRST %i SKIP %i" % (end - start, start)
153
+
154
+ match = cls.limit_re.match(query)
155
+ if match and len(match.groups()) == 2:
156
+ return ' '.join([limit_str, match.group(2)])
157
+ else:
158
+ return query
159
+
160
+ def createTable(self, soClass):
161
+ self.query('CREATE TABLE %s (\n%s\n)' %
162
+ (soClass.sqlmeta.table, self.createColumns(soClass)))
163
+ self.query("CREATE GENERATOR GEN_%s" % soClass.sqlmeta.table)
164
+ return []
165
+
166
+ def createReferenceConstraint(self, soClass, col):
167
+ return None
168
+
169
+ def createColumn(self, soClass, col):
170
+ return col.firebirdCreateSQL()
171
+
172
+ def createIDColumn(self, soClass):
173
+ key_type = {int: "INT", str: "VARCHAR(255)"}[soClass.sqlmeta.idType]
174
+ return '%s %s NOT NULL PRIMARY KEY' % (soClass.sqlmeta.idName,
175
+ key_type)
176
+
177
+ def createIndexSQL(self, soClass, index):
178
+ return index.firebirdCreateIndexSQL(soClass)
179
+
180
+ def joinSQLType(self, join):
181
+ return 'INT NOT NULL'
182
+
183
+ def tableExists(self, tableName):
184
+ # there's something in the database by this name...let's
185
+ # assume it's a table. By default, fb 1.0 stores EVERYTHING
186
+ # it cares about in uppercase.
187
+ result = self.queryOne(
188
+ "SELECT COUNT(rdb$relation_name) FROM rdb$relations "
189
+ "WHERE rdb$relation_name = '%s'" % tableName.upper())
190
+ return result[0]
191
+
192
+ def addColumn(self, tableName, column):
193
+ self.query('ALTER TABLE %s ADD %s' %
194
+ (tableName,
195
+ column.firebirdCreateSQL()))
196
+
197
+ def dropTable(self, tableName, cascade=False):
198
+ self.query("DROP TABLE %s" % tableName)
199
+ self.query("DROP GENERATOR GEN_%s" % tableName)
200
+
201
+ def delColumn(self, sqlmeta, column):
202
+ self.query('ALTER TABLE %s DROP %s' % (sqlmeta.table, column.dbName))
203
+
204
+ def readDefaultEncodingFromDB(self):
205
+ # Get out if encoding is known allready (can by None as well).
206
+ if self.defaultDbEncoding == "":
207
+ self.defaultDbEncoding = str(self.queryOne(
208
+ "SELECT rdb$character_set_name FROM rdb$database")[0].
209
+ strip().lower()) # encoding defined during db creation
210
+ if self.defaultDbEncoding == "none":
211
+ self.defaultDbEncoding = None
212
+ if self.dbEncoding != self.defaultDbEncoding:
213
+ warningText = """\n
214
+ Database charset: %s is different """ \
215
+ """from connection charset: %s.\n""" % (
216
+ self.defaultDbEncoding, self.dbEncoding)
217
+ warnings.warn(warningText)
218
+ # TODO: ??? print out the uri string,
219
+ # so user can see what is going on
220
+ warningText = """\n
221
+ Every CHAR or VARCHAR field can (or, better: must) """ \
222
+ """have a character set defined in Firebird.
223
+ In the case, field charset is not defined, """ \
224
+ """SQLObject try to use a db default encoding instead.
225
+ Firebird is unable to transliterate between character sets.
226
+ So you must set the correct values on the server """ \
227
+ "and on the client if everything is to work smoothely.\n"
228
+ warnings.warn(warningText)
229
+
230
+ if not self.dbEncoding: # defined by user in the connection string
231
+ self.dbEncoding = self.defaultDbEncoding
232
+ warningText = """\n
233
+ encoding: %s will be used as default """ \
234
+ """for this connection\n""" % self.dbEncoding
235
+ warnings.warn(warningText)
236
+
237
+ def columnsFromSchema(self, tableName, soClass):
238
+ """
239
+ Look at the given table and create Col instances (or
240
+ subclasses of Col) for the fields it finds in that table.
241
+ """
242
+
243
+ self.readDefaultEncodingFromDB()
244
+
245
+ fieldQuery = """\
246
+ SELECT r.RDB$FIELD_NAME AS field_name,
247
+ CASE f.RDB$FIELD_TYPE
248
+ when 7 then 'smallint'
249
+ when 8 then 'integer'
250
+ when 16 then 'int64'
251
+ when 9 then 'quad'
252
+ when 10 then 'float'
253
+ when 11 then 'd_float'
254
+ when 17 then 'boolean'
255
+ when 27 then 'double'
256
+ when 12 then 'date'
257
+ when 13 then 'time'
258
+ when 35 then 'timestamp'
259
+ when 261 then 'blob'
260
+ when 37 then 'varchar'
261
+ when 14 then 'char'
262
+ when 40 then 'cstring'
263
+ when 45 then 'blob_id'
264
+ ELSE 'UNKNOWN'
265
+ END AS field_type,
266
+ case f.rdb$field_type
267
+ when 7 then
268
+ case f.rdb$field_sub_type
269
+ when 1 then 'numeric'
270
+ when 2 then 'decimal'
271
+ end
272
+ when 8 then
273
+ case f.rdb$field_sub_type
274
+ when 1 then 'numeric'
275
+ when 2 then 'decimal'
276
+ end
277
+ when 16 then
278
+ case f.rdb$field_sub_type
279
+ when 1 then 'numeric'
280
+ when 2 then 'decimal'
281
+ else 'bigint'
282
+ end
283
+ when 14 then
284
+ case f.rdb$field_sub_type
285
+ when 0 then 'unspecified'
286
+ when 1 then 'binary'
287
+ when 3 then 'acl'
288
+ else
289
+ case
290
+ when f.rdb$field_sub_type is null then 'unspecified'
291
+ end
292
+ end
293
+ when 37 then
294
+ case f.rdb$field_sub_type
295
+ when 0 then 'unspecified'
296
+ when 1 then 'text'
297
+ when 3 then 'acl'
298
+ else
299
+ case
300
+ when f.rdb$field_sub_type is null then 'unspecified'
301
+ end
302
+ end
303
+ when 261 then
304
+ case f.rdb$field_sub_type
305
+ when 0 then 'unspecified'
306
+ when 1 then 'text'
307
+ when 2 then 'blr'
308
+ when 3 then 'acl'
309
+ when 4 then 'reserved'
310
+ when 5 then 'encoded-meta-data'
311
+ when 6 then 'irregular-finished-multi-db-tx'
312
+ when 7 then 'transactional_description'
313
+ when 8 then 'external_file_description'
314
+ end
315
+ end as "ActualSubType",
316
+ f.RDB$FIELD_LENGTH AS field_length,
317
+ f.RDB$FIELD_PRECISION AS field_precision,
318
+ f.RDB$FIELD_SCALE AS field_scale,
319
+ cset.RDB$CHARACTER_SET_NAME AS field_charset,
320
+ coll.RDB$COLLATION_NAME AS field_collation,
321
+ r.rdb$default_source,
322
+ r.RDB$NULL_FLAG AS field_not_null_constraint,
323
+ r.RDB$DESCRIPTION AS field_description
324
+ FROM RDB$RELATION_FIELDS r
325
+ LEFT JOIN RDB$FIELDS f ON r.RDB$FIELD_SOURCE = f.RDB$FIELD_NAME
326
+ LEFT JOIN RDB$COLLATIONS coll
327
+ ON f.RDB$COLLATION_ID = coll.RDB$COLLATION_ID
328
+ LEFT JOIN RDB$CHARACTER_SETS cset
329
+ ON f.RDB$CHARACTER_SET_ID = cset.RDB$CHARACTER_SET_ID
330
+ WHERE r.RDB$RELATION_NAME='%s' -- table name
331
+ ORDER BY r.RDB$FIELD_POSITION
332
+ """
333
+
334
+ colData = self.queryAll(fieldQuery % tableName.upper())
335
+ results = []
336
+ for (field, fieldType, fieldSubtype, fieldLength, fieldPrecision,
337
+ fieldScale, fieldCharset, collationName, defaultSource,
338
+ fieldNotNullConstraint, fieldDescription) in colData:
339
+ field = field.strip().lower()
340
+ fieldType = fieldType.strip()
341
+ if fieldCharset:
342
+ fieldCharset = str(fieldCharset.strip())
343
+ # 'UNICODE_FSS' is less strict
344
+ # Firebird/Interbase UTF8 definition
345
+ if fieldCharset.startswith('UNICODE_FSS'):
346
+ fieldCharset = "UTF8"
347
+ if fieldSubtype:
348
+ fieldSubtype = fieldSubtype.strip()
349
+ if fieldType == "int64":
350
+ fieldType = fieldSubtype
351
+
352
+ # can look like: "DEFAULT 0", "DEFAULT 'default text'", None
353
+ if defaultSource:
354
+ defaultSource = defaultSource.split(' ')[1]
355
+ if defaultSource.startswith("'") and \
356
+ defaultSource.endswith("'"):
357
+ defaultSource = str(defaultSource[1:-1])
358
+ elif fieldType in ("integer", "smallint", "bigint"):
359
+ defaultSource = int(defaultSource)
360
+ elif fieldType in ("float", "double"):
361
+ defaultSource = float(defaultSource)
362
+ # TODO: other types for defaultSource
363
+ # elif fieldType == "datetime":
364
+
365
+ idName = str(soClass.sqlmeta.idName or 'id').upper()
366
+ if field.upper() == idName:
367
+ continue
368
+ if fieldScale:
369
+ # PRECISION refers to the total number of digits,
370
+ # and SCALE refers to the number of digits
371
+ # to the right of the decimal point.
372
+ # Both numbers can be from 1 to 18 (SQL dialect 1: 1-15),
373
+ # but SCALE mustbe less than or equal to PRECISION.
374
+ if fieldScale > fieldLength:
375
+ fieldScale = fieldLength
376
+ colClass, kw = self.guessClass(fieldType, fieldLength,
377
+ fieldCharset, fieldScale)
378
+ kw['name'] = str(
379
+ soClass.sqlmeta.style.dbColumnToPythonAttr(field).strip())
380
+ kw['dbName'] = str(field)
381
+ kw['notNone'] = not fieldNotNullConstraint
382
+ kw['default'] = defaultSource
383
+ results.append(colClass(**kw))
384
+ return results
385
+
386
+ def guessClass(self, t, flength, fCharset, fscale=None):
387
+ """
388
+ An internal method that tries to figure out what Col subclass
389
+ is appropriate given whatever introspective information is
390
+ available -- both very database-specific.
391
+ """
392
+
393
+ # TODO: check if negative values are allowed for fscale
394
+
395
+ if t == 'smallint': # -32,768 to +32,767, 16 bits
396
+ return col.IntCol, {}
397
+ elif t == 'integer': # -2,147,483,648 to +2,147,483,647, 32 bits
398
+ return col.IntCol, {}
399
+ elif t == 'bigint':
400
+ # -2^63 to 2^63-1 or
401
+ # -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807, 64 bits
402
+ return col.IntCol, {}
403
+ elif t == 'float':
404
+ # 32 bits, 3.4x10^-38 to 3.4x10^38, 7 digit precision
405
+ # (7 significant decimals)
406
+ return col.FloatCol, {}
407
+ elif t == 'double':
408
+ # 64 bits, 1.7x10^-308 to 1.7x10^308, 15 digit precision
409
+ # (15 significant decimals)
410
+ return col.FloatCol, {}
411
+ elif t == 'numeric':
412
+ # Numeric and Decimal are internally stored as smallint,
413
+ # integer or bigint depending on the size.
414
+ # They can handle up to 18 digits.
415
+ if (not flength or not fscale):
416
+ # If neither PRECISION nor SCALE are specified,
417
+ # Firebird/InterBase defines the column as INTEGER
418
+ # instead of NUMERIC and stores only the integer portion
419
+ # of the value
420
+ return col.IntCol, {}
421
+ # check if negative values are allowed for fscale
422
+ return col.DecimalCol, {'size': flength, 'precision': fscale}
423
+
424
+ elif t == 'decimal':
425
+ # Check if negative values are allowed for fscale
426
+ return col.DecimalCol, {'size': flength, 'precision': fscale}
427
+ elif t == 'date': # 32 bits, 1 Jan 100. to 29 Feb 32768.
428
+ return col.DateCol, {}
429
+ elif t == 'time': # 32 bits, 00:00 to 23:59.9999
430
+ return col.TimeCol, {}
431
+ elif t == 'timestamp': # 64 bits, 1 Jan 100 to 28 Feb 32768.
432
+ return col.DateTimeCol, {}
433
+ elif t == 'char': # 32767 bytes
434
+ if fCharset and (fCharset != "NONE"):
435
+ return col.UnicodeCol, {'length': flength, 'varchar': False,
436
+ 'dbEncoding': fCharset}
437
+ elif self.dbEncoding:
438
+ return col.UnicodeCol, {'length': flength, 'varchar': False,
439
+ 'dbEncoding': self.dbEncoding}
440
+ else:
441
+ return col.StringCol, {'length': flength, 'varchar': False}
442
+ elif t == 'varchar': # 32767 bytes
443
+ if fCharset and (fCharset != "NONE"):
444
+ return col.UnicodeCol, {'length': flength, 'varchar': True,
445
+ 'dbEncoding': fCharset}
446
+ elif self.dbEncoding:
447
+ return col.UnicodeCol, {'length': flength, 'varchar': True,
448
+ 'dbEncoding': self.dbEncoding}
449
+ else:
450
+ return col.StringCol, {'length': flength, 'varchar': True}
451
+
452
+ elif t == 'blob': # 32GB
453
+ return col.BLOBCol, {}
454
+ else:
455
+ return col.Col, {}
456
+
457
+ def createEmptyDatabase(self):
458
+ self.module.create_database(
459
+ "CREATE DATABASE '%s' user '%s' password '%s'" %
460
+ (self.db, self.user, self.password))
461
+
462
+ def dropDatabase(self):
463
+ self.module.drop_database()
@@ -0,0 +1 @@
1
+ #
@@ -0,0 +1,109 @@
1
+ import sqlobject.col
2
+ from sqlobject.compat import PY2, string_type
3
+
4
+
5
+ __all__ = ['HashCol']
6
+
7
+
8
+ class DbHash:
9
+ """
10
+ Presents a comparison object for hashes, allowing plain text to be
11
+ automagically compared with the base content
12
+ """
13
+
14
+ def __init__(self, hash, hashMethod):
15
+ self.hash = hash
16
+ self.hashMethod = hashMethod
17
+
18
+ def _get_key(self, other):
19
+ """Create the hash of the other class"""
20
+ if not isinstance(other, string_type):
21
+ raise TypeError(
22
+ "A hash may only be compared with a string, or None.")
23
+ return self.hashMethod(other)
24
+
25
+ def __eq__(self, other):
26
+ if other is None:
27
+ if self.hash is None:
28
+ return True
29
+ return False
30
+ other_key = self._get_key(other)
31
+ return other_key == self.hash
32
+
33
+ def __lt__(self, other):
34
+ if other is None:
35
+ return False
36
+ other_key = self._get_key(other)
37
+ return other_key < self.hash
38
+
39
+ def __gt__(self, other):
40
+ if other is None:
41
+ if self.hash is None:
42
+ return False
43
+ return True
44
+ other_key = self._get_key(other)
45
+ return other_key > self.hash
46
+
47
+ def __le__(self, other):
48
+ if other is None:
49
+ if self.hash is None:
50
+ return True
51
+ return False
52
+ other_key = self._get_key(other)
53
+ return other_key <= self.hash
54
+
55
+ def __ge__(self, other):
56
+ if other is None:
57
+ return True
58
+ other_key = self._get_key(other)
59
+ return other_key >= self.hash
60
+
61
+ def __repr__(self):
62
+ return "<DbHash>"
63
+
64
+
65
+ class HashValidator(sqlobject.col.StringValidator):
66
+ """Provides formal SQLObject validation services for the HashCol"""
67
+
68
+ def to_python(self, value, state):
69
+ """ Passes out a hash object. """
70
+ if value is None:
71
+ return None
72
+ return DbHash(hash=value, hashMethod=self.hashMethod)
73
+
74
+ def from_python(self, value, state):
75
+ """Store the given value as a MD5 hash, or None if specified"""
76
+ if value is None:
77
+ return None
78
+ return self.hashMethod(value)
79
+
80
+
81
+ class SOHashCol(sqlobject.col.SOStringCol):
82
+ """The internal HashCol definition. By default, enforces a md5 digest"""
83
+
84
+ def __init__(self, **kw):
85
+ if 'hashMethod' not in kw:
86
+ from hashlib import md5
87
+ if PY2:
88
+ self.hashMethod = lambda v: md5(v).hexdigest()
89
+ else:
90
+ self.hashMethod = lambda v: md5(v.encode('utf8')).hexdigest()
91
+ if 'length' not in kw:
92
+ kw['length'] = 32
93
+ else:
94
+ self.hashMethod = kw['hashMethod']
95
+ del kw['hashMethod']
96
+ super(SOHashCol, self).__init__(**kw)
97
+
98
+ def createValidators(self):
99
+ return [HashValidator(name=self.name, hashMethod=self.hashMethod)] + \
100
+ super(SOHashCol, self).createValidators()
101
+
102
+
103
+ class HashCol(sqlobject.col.StringCol):
104
+ """
105
+ End-user HashCol class. May be instantiated with 'hashMethod', a function
106
+ which returns the string hash of any other string (i.e. basestring)
107
+ """
108
+
109
+ baseClass = SOHashCol
@@ -0,0 +1 @@
1
+ #
@@ -0,0 +1,84 @@
1
+ from hashlib import sha256, md5
2
+ from sqlobject import AND, IntCol, OR, SQLObject
3
+ from sqlobject.compat import PY2
4
+ from sqlobject.include import hashcol
5
+ from sqlobject.tests.dbtest import setupClass
6
+
7
+ ########################################
8
+ # HashCol test
9
+ ########################################
10
+
11
+ if PY2:
12
+ def sha256_str(x):
13
+ return sha256(x).hexdigest()
14
+
15
+ def md5_str(x):
16
+ return md5(x).hexdigest()
17
+ else:
18
+ def sha256_str(x):
19
+ return sha256(x.encode('utf8')).hexdigest()
20
+
21
+ def md5_str(x):
22
+ return md5(x.encode('utf8')).hexdigest()
23
+
24
+
25
+ class HashTest(SQLObject):
26
+ so_count = IntCol(alternateID=True)
27
+ col1 = hashcol.HashCol()
28
+ col2 = hashcol.HashCol(hashMethod=sha256_str)
29
+
30
+
31
+ data = ['test', 'This is more text', 'test 2']
32
+ items = []
33
+
34
+
35
+ def setup():
36
+ global items
37
+ items = []
38
+ setupClass(HashTest)
39
+ for i, s in enumerate(data):
40
+ items.append(HashTest(so_count=i, col1=s, col2=s))
41
+
42
+
43
+ def test_create():
44
+ setup()
45
+ for s, item in zip(data, items):
46
+ assert item.col1 == s
47
+ assert item.col2 == s
48
+
49
+ conn = HashTest._connection
50
+ rows = conn.queryAll("""
51
+ SELECT so_count, col1, col2
52
+ FROM hash_test
53
+ ORDER BY so_count
54
+ """)
55
+ for so_count, col1, col2 in rows:
56
+ assert md5_str(data[so_count]) == col1
57
+ assert sha256_str(data[so_count]) == col2
58
+
59
+
60
+ def test_select():
61
+ for i, value in enumerate(data):
62
+ rows = list(HashTest.select(HashTest.q.col1 == value))
63
+ assert len(rows) == 1
64
+ rows = list(HashTest.select(HashTest.q.col2 == value))
65
+ assert len(rows) == 1
66
+ # Passing the hash in directly should fail
67
+ rows = list(HashTest.select(HashTest.q.col2 == sha256_str(value)))
68
+ assert len(rows) == 0
69
+ rows = list(HashTest.select(AND(
70
+ HashTest.q.col1 == value,
71
+ HashTest.q.col2 == value
72
+ )))
73
+ assert len(rows) == 1
74
+ rows = list(HashTest.selectBy(col1=value))
75
+ assert len(rows) == 1
76
+ rows = list(HashTest.selectBy(col2=value))
77
+ assert len(rows) == 1
78
+ rows = list(HashTest.selectBy(col1=value, col2=value))
79
+ assert len(rows) == 1
80
+ rows = list(HashTest.select(OR(
81
+ HashTest.q.col1 == 'test 2',
82
+ HashTest.q.col2 == 'test'
83
+ )))
84
+ assert len(rows) == 2