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/col.py ADDED
@@ -0,0 +1,2187 @@
1
+ """
2
+ Col -- SQLObject columns
3
+
4
+ Note that each column object is named BlahBlahCol, and these are used
5
+ in class definitions. But there's also a corresponding SOBlahBlahCol
6
+ object, which is used in SQLObject *classes*.
7
+
8
+ An explanation: when a SQLObject subclass is created, the metaclass
9
+ looks through your class definition for any subclasses of Col. It
10
+ collects them together, and indexes them to do all the database stuff
11
+ you like, like the magic attributes and whatnot. It then asks the Col
12
+ object to create an SOCol object (usually a subclass, actually). The
13
+ SOCol object contains all the interesting logic, as well as a record
14
+ of the attribute name you used and the class it is bound to (set by
15
+ the metaclass).
16
+
17
+ So, in summary: Col objects are what you define, but SOCol objects
18
+ are what gets used.
19
+ """
20
+
21
+ from array import array
22
+ from decimal import Decimal
23
+ from itertools import count
24
+ import json
25
+ try:
26
+ import cPickle as pickle
27
+ except ImportError:
28
+ import pickle
29
+ import time
30
+ from uuid import UUID
31
+ import weakref
32
+
33
+ from formencode import compound, validators
34
+ from .classregistry import findClass
35
+ # Sadly the name "constraints" conflicts with many of the function
36
+ # arguments in this module, so we rename it:
37
+ from . import constraints as constrs
38
+ from . import converters
39
+ from . import sqlbuilder
40
+ from .styles import capword
41
+ from .compat import PY2, string_type, unicode_type, buffer_type
42
+
43
+ import datetime
44
+ datetime_available = True
45
+
46
+ try:
47
+ from mx import DateTime as mxDateTime
48
+ except ImportError:
49
+ mxdatetime_available = False
50
+ else:
51
+ mxdatetime_available = True
52
+
53
+ try:
54
+ # DateTime from Zope
55
+ import DateTime as zopeDateTime
56
+ except ImportError:
57
+ zope_datetime_available = False
58
+ else:
59
+ zope_datetime_available = True
60
+
61
+ DATETIME_IMPLEMENTATION = "datetime"
62
+ MXDATETIME_IMPLEMENTATION = "mxDateTime"
63
+ ZOPE_DATETIME_IMPLEMENTATION = "zopeDateTime"
64
+
65
+ if mxdatetime_available and hasattr(mxDateTime, "Time"):
66
+ mxDateTimeType = type(mxDateTime.now())
67
+ mxTimeType = type(mxDateTime.Time())
68
+
69
+ if zope_datetime_available:
70
+ zopeDateTimeType = type(zopeDateTime.DateTime())
71
+
72
+ __all__ = ["datetime_available", "mxdatetime_available",
73
+ "default_datetime_implementation", "DATETIME_IMPLEMENTATION"]
74
+
75
+ if mxdatetime_available:
76
+ __all__.append("MXDATETIME_IMPLEMENTATION")
77
+
78
+ if zope_datetime_available:
79
+ __all__.append("ZOPE_DATETIME_IMPLEMENTATION")
80
+
81
+ default_datetime_implementation = DATETIME_IMPLEMENTATION
82
+
83
+ if not PY2:
84
+ # alias for python 3 compatibility
85
+ long = int
86
+ # This is to satisfy flake8 under python 3
87
+ unicode = str
88
+
89
+ NoDefault = sqlbuilder.NoDefault
90
+
91
+
92
+ def use_microseconds(use=True):
93
+ if use:
94
+ SODateTimeCol.datetimeFormat = '%Y-%m-%d %H:%M:%S.%f'
95
+ SOTimeCol.timeFormat = '%H:%M:%S.%f'
96
+ dt_types = [(datetime.datetime, converters.DateTimeConverterMS),
97
+ (datetime.time, converters.TimeConverterMS)]
98
+ else:
99
+ SODateTimeCol.datetimeFormat = '%Y-%m-%d %H:%M:%S'
100
+ SOTimeCol.timeFormat = '%H:%M:%S'
101
+ dt_types = [(datetime.datetime, converters.DateTimeConverter),
102
+ (datetime.time, converters.TimeConverter)]
103
+ for dt_type, converter in dt_types:
104
+ converters.registerConverter(dt_type, converter)
105
+
106
+
107
+ __all__.append("use_microseconds")
108
+
109
+
110
+ creationOrder = count()
111
+
112
+ ########################################
113
+ # Columns
114
+ ########################################
115
+
116
+ # Col is essentially a column definition, it doesn't have much logic to it.
117
+
118
+
119
+ class SOCol(object):
120
+
121
+ def __init__(self,
122
+ name,
123
+ soClass,
124
+ creationOrder,
125
+ dbName=None,
126
+ default=NoDefault,
127
+ defaultSQL=None,
128
+ foreignKey=None,
129
+ alternateID=False,
130
+ alternateMethodName=None,
131
+ constraints=None,
132
+ notNull=NoDefault,
133
+ notNone=NoDefault,
134
+ unique=NoDefault,
135
+ sqlType=None,
136
+ columnDef=None,
137
+ validator=None,
138
+ validator2=None,
139
+ immutable=False,
140
+ cascade=None,
141
+ lazy=False,
142
+ noCache=False,
143
+ forceDBName=False,
144
+ title=None,
145
+ tags=[],
146
+ origName=None,
147
+ dbEncoding=None,
148
+ extra_vars=None):
149
+
150
+ super(SOCol, self).__init__()
151
+
152
+ # This isn't strictly true, since we *could* use backquotes or
153
+ # " or something (database-specific) around column names, but
154
+ # why would anyone *want* to use a name like that?
155
+ # @@: I suppose we could actually add backquotes to the
156
+ # dbName if we needed to...
157
+ if not forceDBName:
158
+ assert sqlbuilder.sqlIdentifier(name), (
159
+ 'Name must be SQL-safe '
160
+ '(letters, numbers, underscores): %s (or use forceDBName=True)'
161
+ % repr(name))
162
+ assert name != 'id', (
163
+ 'The column name "id" is reserved for SQLObject use '
164
+ '(and is implicitly created).')
165
+ assert name, "You must provide a name for all columns"
166
+
167
+ self.columnDef = columnDef
168
+ self.creationOrder = creationOrder
169
+
170
+ self.immutable = immutable
171
+
172
+ # cascade can be one of:
173
+ # None: no constraint is generated
174
+ # True: a CASCADE constraint is generated
175
+ # False: a RESTRICT constraint is generated
176
+ # 'null': a SET NULL trigger is generated
177
+ if not isinstance(cascade, (bool, string_type, type(None))):
178
+ raise TypeError(
179
+ 'Expected cascade to be True, False, None or "null", '
180
+ "(you gave: %r %r)" % (type(cascade), cascade)
181
+ )
182
+ if isinstance(cascade, str) and (cascade != 'null'):
183
+ raise ValueError(
184
+ "The only string value allowed for cascade is 'null' "
185
+ "(you gave: %r)" % cascade)
186
+ self.cascade = cascade
187
+
188
+ if not isinstance(constraints, (list, tuple)):
189
+ constraints = [constraints]
190
+ self.constraints = self.autoConstraints() + constraints
191
+
192
+ self.notNone = False
193
+ if notNull is not NoDefault:
194
+ self.notNone = notNull
195
+ assert notNone is NoDefault or (not notNone) == (not notNull), (
196
+ "The notNull and notNone arguments are aliases, "
197
+ "and must not conflict. "
198
+ "You gave notNull=%r, notNone=%r" % (notNull, notNone))
199
+ elif notNone is not NoDefault:
200
+ self.notNone = notNone
201
+ if self.notNone:
202
+ self.constraints = [constrs.notNull] + self.constraints
203
+
204
+ self.name = name
205
+ self.soClass = soClass
206
+ self._default = default
207
+ self.defaultSQL = defaultSQL
208
+ self.customSQLType = sqlType
209
+
210
+ # deal with foreign keys
211
+ self.foreignKey = foreignKey
212
+ if self.foreignKey:
213
+ if origName is not None:
214
+ idname = soClass.sqlmeta.style.instanceAttrToIDAttr(origName)
215
+ else:
216
+ idname = soClass.sqlmeta.style.instanceAttrToIDAttr(name)
217
+ if self.name != idname:
218
+ self.foreignName = self.name
219
+ self.name = idname
220
+ else:
221
+ self.foreignName = soClass.sqlmeta.style.\
222
+ instanceIDAttrToAttr(self.name)
223
+ else:
224
+ self.foreignName = None
225
+
226
+ # if they don't give us a specific database name for
227
+ # the column, we separate the mixedCase into mixed_case
228
+ # and assume that.
229
+ if dbName is None:
230
+ self.dbName = soClass.sqlmeta.style.pythonAttrToDBColumn(self.name)
231
+ else:
232
+ self.dbName = dbName
233
+
234
+ # alternateID means that this is a unique column that
235
+ # can be used to identify rows
236
+ self.alternateID = alternateID
237
+
238
+ if unique is NoDefault:
239
+ self.unique = alternateID
240
+ else:
241
+ self.unique = unique
242
+ if self.unique and alternateMethodName is None:
243
+ self.alternateMethodName = 'by' + capword(self.name)
244
+ else:
245
+ self.alternateMethodName = alternateMethodName
246
+
247
+ _validators = self.createValidators()
248
+ if validator:
249
+ _validators.append(validator)
250
+ if validator2:
251
+ _validators.insert(0, validator2)
252
+ _vlen = len(_validators)
253
+ if _vlen:
254
+ for _validator in _validators:
255
+ _validator.soCol = weakref.proxy(self)
256
+ if _vlen == 0:
257
+ self.validator = None # Set sef.{from,to}_python
258
+ elif _vlen == 1:
259
+ self.validator = _validators[0]
260
+ elif _vlen > 1:
261
+ self.validator = compound.All.join(
262
+ _validators[0], *_validators[1:])
263
+ self.noCache = noCache
264
+ self.lazy = lazy
265
+ # this is in case of ForeignKey, where we rename the column
266
+ # and append an ID
267
+ self.origName = origName or name
268
+ self.title = title
269
+ self.tags = tags
270
+ self.dbEncoding = dbEncoding
271
+
272
+ if extra_vars:
273
+ for name, value in extra_vars.items():
274
+ setattr(self, name, value)
275
+
276
+ def _set_validator(self, value):
277
+ self._validator = value
278
+ if self._validator:
279
+ self.to_python = self._validator.to_python
280
+ self.from_python = self._validator.from_python
281
+ else:
282
+ self.to_python = None
283
+ self.from_python = None
284
+
285
+ def _get_validator(self):
286
+ return self._validator
287
+
288
+ validator = property(_get_validator, _set_validator)
289
+
290
+ def createValidators(self):
291
+ """Create a list of validators for the column."""
292
+ return []
293
+
294
+ def autoConstraints(self):
295
+ return []
296
+
297
+ def _get_default(self):
298
+ # A default can be a callback or a plain value,
299
+ # here we resolve the callback
300
+ if self._default is NoDefault:
301
+ return NoDefault
302
+ elif hasattr(self._default, '__sqlrepr__'):
303
+ return self._default
304
+ elif callable(self._default):
305
+ return self._default()
306
+ else:
307
+ return self._default
308
+ default = property(_get_default, None, None)
309
+
310
+ def _get_joinName(self):
311
+ return self.soClass.sqlmeta.style.instanceIDAttrToAttr(self.name)
312
+ joinName = property(_get_joinName, None, None)
313
+
314
+ def __repr__(self):
315
+ r = '<%s %s' % (self.__class__.__name__, self.name)
316
+ if self.default is not NoDefault:
317
+ r += ' default=%s' % repr(self.default)
318
+ if self.foreignKey:
319
+ r += ' connected to %s' % self.foreignKey
320
+ if self.alternateID:
321
+ r += ' alternate ID'
322
+ if self.notNone:
323
+ r += ' not null'
324
+ return r + '>'
325
+
326
+ def createSQL(self):
327
+ return ' '.join([self._sqlType()] + self._extraSQL())
328
+
329
+ def _extraSQL(self):
330
+ result = []
331
+ if self.notNone or self.alternateID:
332
+ result.append('NOT NULL')
333
+ if self.unique or self.alternateID:
334
+ result.append('UNIQUE')
335
+ if self.defaultSQL is not None:
336
+ result.append("DEFAULT %s" % self.defaultSQL)
337
+ return result
338
+
339
+ def _sqlType(self):
340
+ if self.customSQLType is None:
341
+ raise ValueError("Col %s (%s) cannot be used for automatic "
342
+ "schema creation (too abstract)" %
343
+ (self.name, self.__class__))
344
+ else:
345
+ return self.customSQLType
346
+
347
+ def _mysqlType(self):
348
+ return self._sqlType()
349
+
350
+ def _postgresType(self):
351
+ return self._sqlType()
352
+
353
+ def _sqliteType(self):
354
+ # SQLite is naturally typeless, so as a fallback it uses
355
+ # no type.
356
+ try:
357
+ return self._sqlType()
358
+ except ValueError:
359
+ return ''
360
+
361
+ def _sybaseType(self):
362
+ return self._sqlType()
363
+
364
+ def _mssqlType(self):
365
+ return self._sqlType()
366
+
367
+ def _firebirdType(self):
368
+ return self._sqlType()
369
+
370
+ def _maxdbType(self):
371
+ return self._sqlType()
372
+
373
+ def mysqlCreateSQL(self, connection=None):
374
+ self.connection = connection
375
+ return ' '.join([self.dbName, self._mysqlType()] + self._extraSQL())
376
+
377
+ def postgresCreateSQL(self):
378
+ return ' '.join([self.dbName, self._postgresType()] + self._extraSQL())
379
+
380
+ def sqliteCreateSQL(self):
381
+ return ' '.join([self.dbName, self._sqliteType()] + self._extraSQL())
382
+
383
+ def sybaseCreateSQL(self):
384
+ return ' '.join([self.dbName, self._sybaseType()] + self._extraSQL())
385
+
386
+ def mssqlCreateSQL(self, connection=None):
387
+ self.connection = connection
388
+ return ' '.join([self.dbName, self._mssqlType()] + self._extraSQL())
389
+
390
+ def firebirdCreateSQL(self):
391
+ # Ian Sparks pointed out that fb is picky about the order
392
+ # of the NOT NULL clause in a create statement. So, we handle
393
+ # them differently for Enum columns.
394
+ if not isinstance(self, SOEnumCol):
395
+ return ' '.join(
396
+ [self.dbName, self._firebirdType()] + self._extraSQL())
397
+ else:
398
+ return ' '.join(
399
+ [self.dbName] + [self._firebirdType()[0]]
400
+ + self._extraSQL() + [self._firebirdType()[1]])
401
+
402
+ def maxdbCreateSQL(self):
403
+ return ' '.join([self.dbName, self._maxdbType()] + self._extraSQL())
404
+
405
+ def __get__(self, obj, type=None):
406
+ if obj is None:
407
+ # class attribute, return the descriptor itself
408
+ return self
409
+ if obj.sqlmeta._obsolete:
410
+ raise RuntimeError('The object <%s %s> is obsolete' % (
411
+ obj.__class__.__name__, obj.id))
412
+ if obj.sqlmeta.cacheColumns:
413
+ columns = obj.sqlmeta._columnCache
414
+ if columns is None:
415
+ obj.sqlmeta.loadValues()
416
+ try:
417
+ return columns[name] # noqa
418
+ except KeyError:
419
+ return obj.sqlmeta.loadColumn(self)
420
+ else:
421
+ return obj.sqlmeta.loadColumn(self)
422
+
423
+ def __set__(self, obj, value):
424
+ if self.immutable:
425
+ raise AttributeError("The column %s.%s is immutable" %
426
+ (obj.__class__.__name__,
427
+ self.name))
428
+ obj.sqlmeta.setColumn(self, value)
429
+
430
+ def __delete__(self, obj):
431
+ raise AttributeError("I can't be deleted from %r" % obj)
432
+
433
+ def getDbEncoding(self, state, default='utf-8'):
434
+ if self.dbEncoding:
435
+ return self.dbEncoding
436
+ dbEncoding = state.soObject.sqlmeta.dbEncoding
437
+ if dbEncoding:
438
+ return dbEncoding
439
+ try:
440
+ connection = state.connection or state.soObject._connection
441
+ except AttributeError:
442
+ dbEncoding = None
443
+ else:
444
+ dbEncoding = getattr(connection, "dbEncoding", None)
445
+ if not dbEncoding:
446
+ dbEncoding = default
447
+ return dbEncoding
448
+
449
+
450
+ class Col(object):
451
+
452
+ baseClass = SOCol
453
+
454
+ def __init__(self, name=None, **kw):
455
+ super(Col, self).__init__()
456
+ self.__dict__['_name'] = name
457
+ self.__dict__['_kw'] = kw
458
+ self.__dict__['creationOrder'] = next(creationOrder)
459
+ self.__dict__['_extra_vars'] = {}
460
+
461
+ def _set_name(self, value):
462
+ assert self._name is None or self._name == value, (
463
+ "You cannot change a name after it has already been set "
464
+ "(from %s to %s)" % (self.name, value))
465
+ self.__dict__['_name'] = value
466
+
467
+ def _get_name(self):
468
+ return self._name
469
+
470
+ name = property(_get_name, _set_name)
471
+
472
+ def withClass(self, soClass):
473
+ return self.baseClass(soClass=soClass, name=self._name,
474
+ creationOrder=self.creationOrder,
475
+ columnDef=self,
476
+ extra_vars=self._extra_vars,
477
+ **self._kw)
478
+
479
+ def __setattr__(self, var, value):
480
+ if var == 'name':
481
+ super(Col, self).__setattr__(var, value)
482
+ return
483
+ self._extra_vars[var] = value
484
+
485
+ def __repr__(self):
486
+ return '<%s %s %s>' % (
487
+ self.__class__.__name__, hex(abs(id(self)))[2:],
488
+ self._name or '(unnamed)')
489
+
490
+
491
+ class SOValidator(validators.Validator):
492
+ def getDbEncoding(self, state, default='utf-8'):
493
+ try:
494
+ return self.dbEncoding
495
+ except AttributeError:
496
+ return self.soCol.getDbEncoding(state, default=default)
497
+
498
+
499
+ class SOStringLikeCol(SOCol):
500
+ """A common ancestor for SOStringCol and SOUnicodeCol"""
501
+ def __init__(self, **kw):
502
+ self.length = kw.pop('length', None)
503
+ self.varchar = kw.pop('varchar', 'auto')
504
+ self.char_binary = kw.pop('char_binary', None) # A hack for MySQL
505
+ if not self.length:
506
+ assert self.varchar == 'auto' or not self.varchar, \
507
+ "Without a length strings are treated as TEXT, not varchar"
508
+ self.varchar = False
509
+ elif self.varchar == 'auto':
510
+ self.varchar = True
511
+
512
+ super(SOStringLikeCol, self).__init__(**kw)
513
+
514
+ def autoConstraints(self):
515
+ constraints = [constrs.isString]
516
+ if self.length is not None:
517
+ constraints += [constrs.MaxLength(self.length)]
518
+ return constraints
519
+
520
+ def _sqlType(self):
521
+ if self.customSQLType is not None:
522
+ return self.customSQLType
523
+ if not self.length:
524
+ return 'TEXT'
525
+ elif self.varchar:
526
+ return 'VARCHAR(%i)' % self.length
527
+ else:
528
+ return 'CHAR(%i)' % self.length
529
+
530
+ def _check_case_sensitive(self, db):
531
+ if self.char_binary:
532
+ raise ValueError("%s does not support "
533
+ "binary character columns" % db)
534
+
535
+ def _mysqlType(self):
536
+ type = self._sqlType()
537
+ if self.char_binary:
538
+ type += " BINARY"
539
+ return type
540
+
541
+ def _postgresType(self):
542
+ self._check_case_sensitive("PostgreSQL")
543
+ return super(SOStringLikeCol, self)._postgresType()
544
+
545
+ def _sqliteType(self):
546
+ self._check_case_sensitive("SQLite")
547
+ return super(SOStringLikeCol, self)._sqliteType()
548
+
549
+ def _sybaseType(self):
550
+ self._check_case_sensitive("SYBASE")
551
+ type = self._sqlType()
552
+ return type
553
+
554
+ def _mssqlType(self):
555
+ if self.customSQLType is not None:
556
+ return self.customSQLType
557
+ if not self.length:
558
+ if self.connection and self.connection.can_use_max_types():
559
+ type = 'VARCHAR(MAX)'
560
+ else:
561
+ type = 'VARCHAR(4000)'
562
+ elif self.varchar:
563
+ type = 'VARCHAR(%i)' % self.length
564
+ else:
565
+ type = 'CHAR(%i)' % self.length
566
+ return type
567
+
568
+ def _firebirdType(self):
569
+ self._check_case_sensitive("FireBird")
570
+ if not self.length:
571
+ return 'BLOB SUB_TYPE TEXT'
572
+ else:
573
+ return self._sqlType()
574
+
575
+ def _maxdbType(self):
576
+ self._check_case_sensitive("SAP DB/MaxDB")
577
+ if not self.length:
578
+ return 'LONG ASCII'
579
+ else:
580
+ return self._sqlType()
581
+
582
+
583
+ class StringValidator(SOValidator):
584
+
585
+ def to_python(self, value, state):
586
+ if value is None:
587
+ return None
588
+ try:
589
+ connection = state.connection or state.soObject._connection
590
+ binaryType = connection._binaryType
591
+ dbName = connection.dbName
592
+ except AttributeError:
593
+ binaryType = type(None) # Just a simple workaround
594
+ dbEncoding = self.getDbEncoding(state, default='ascii')
595
+ if isinstance(value, unicode_type):
596
+ if PY2:
597
+ return value.encode(dbEncoding)
598
+ return value
599
+ if self.dataType and isinstance(value, self.dataType):
600
+ return value
601
+ if isinstance(value,
602
+ (str, bytes, buffer_type, binaryType,
603
+ sqlbuilder.SQLExpression)):
604
+ return value
605
+ if hasattr(value, '__unicode__'):
606
+ return unicode(value).encode(dbEncoding)
607
+ if dbName == 'mysql':
608
+ if isinstance(value, bytearray):
609
+ if PY2:
610
+ return bytes(value)
611
+ else:
612
+ return value.decode(dbEncoding, errors='surrogateescape')
613
+ if not PY2 and isinstance(value, bytes):
614
+ return value.decode('ascii', errors='surrogateescape')
615
+ raise validators.Invalid(
616
+ "expected a str in the StringCol '%s', got %s %r instead" % (
617
+ self.name, type(value), value), value, state)
618
+
619
+ from_python = to_python
620
+
621
+
622
+ class SOStringCol(SOStringLikeCol):
623
+
624
+ def createValidators(self, dataType=None):
625
+ return [StringValidator(name=self.name, dataType=dataType)] + \
626
+ super(SOStringCol, self).createValidators()
627
+
628
+
629
+ class StringCol(Col):
630
+ baseClass = SOStringCol
631
+
632
+
633
+ class NQuoted(sqlbuilder.SQLExpression):
634
+ def __init__(self, value):
635
+ self.value = value
636
+
637
+ def __hash__(self):
638
+ return hash(self.value)
639
+
640
+ def __sqlrepr__(self, db):
641
+ assert db == 'mssql'
642
+ return "N" + sqlbuilder.sqlrepr(self.value, db)
643
+
644
+
645
+ class UnicodeStringValidator(SOValidator):
646
+
647
+ def to_python(self, value, state):
648
+ if value is None:
649
+ return None
650
+ if isinstance(value, (unicode_type, sqlbuilder.SQLExpression)):
651
+ return value
652
+ if isinstance(value, str):
653
+ return value.decode(self.getDbEncoding(state))
654
+ if isinstance(value, array): # MySQL
655
+ return value.tostring().decode(self.getDbEncoding(state))
656
+ if hasattr(value, '__unicode__'):
657
+ return unicode(value)
658
+ raise validators.Invalid(
659
+ "expected a str or a unicode in the UnicodeCol '%s', "
660
+ "got %s %r instead" % (
661
+ self.name, type(value), value), value, state)
662
+
663
+ def from_python(self, value, state):
664
+ if value is None:
665
+ return None
666
+ if isinstance(value, (str, sqlbuilder.SQLExpression)):
667
+ return value
668
+ if isinstance(value, unicode_type):
669
+ try:
670
+ connection = state.connection or state.soObject._connection
671
+ except AttributeError:
672
+ pass
673
+ else:
674
+ if connection.dbName == 'mssql':
675
+ if PY2:
676
+ value = value.encode(self.getDbEncoding(state))
677
+ return NQuoted(value)
678
+ return value.encode(self.getDbEncoding(state))
679
+ if hasattr(value, '__unicode__'):
680
+ return unicode(value).encode(self.getDbEncoding(state))
681
+ raise validators.Invalid(
682
+ "expected a str or a unicode in the UnicodeCol '%s', "
683
+ "got %s %r instead" % (
684
+ self.name, type(value), value), value, state)
685
+
686
+
687
+ class SOUnicodeCol(SOStringLikeCol):
688
+ def _mssqlType(self):
689
+ if self.customSQLType is not None:
690
+ return self.customSQLType
691
+ return 'N' + super(SOUnicodeCol, self)._mssqlType()
692
+
693
+ def createValidators(self):
694
+ return [UnicodeStringValidator(name=self.name)] + \
695
+ super(SOUnicodeCol, self).createValidators()
696
+
697
+
698
+ class UnicodeCol(Col):
699
+ baseClass = SOUnicodeCol
700
+
701
+
702
+ class IntValidator(SOValidator):
703
+
704
+ def to_python(self, value, state):
705
+ if value is None:
706
+ return None
707
+ if isinstance(value, (int, long, sqlbuilder.SQLExpression)):
708
+ return value
709
+ for converter, attr_name in (int, '__int__'), (long, '__long__'):
710
+ if hasattr(value, attr_name):
711
+ try:
712
+ return converter(value)
713
+ except Exception:
714
+ break
715
+ raise validators.Invalid(
716
+ "expected an int in the IntCol '%s', got %s %r instead" % (
717
+ self.name, type(value), value), value, state)
718
+
719
+ from_python = to_python
720
+
721
+
722
+ class SOIntCol(SOCol):
723
+ # 3-03 @@: support precision, maybe max and min directly
724
+ def __init__(self, **kw):
725
+ self.length = kw.pop('length', None)
726
+ self.unsigned = bool(kw.pop('unsigned', None))
727
+ self.zerofill = bool(kw.pop('zerofill', None))
728
+ SOCol.__init__(self, **kw)
729
+
730
+ def autoConstraints(self):
731
+ return [constrs.isInt]
732
+
733
+ def createValidators(self):
734
+ return [IntValidator(name=self.name)] + \
735
+ super(SOIntCol, self).createValidators()
736
+
737
+ def addSQLAttrs(self, str):
738
+ _ret = str
739
+ if str is None or len(str) < 1:
740
+ return None
741
+
742
+ if self.length and self.length >= 1:
743
+ _ret = "%s(%d)" % (_ret, self.length)
744
+ if self.unsigned:
745
+ _ret += " UNSIGNED"
746
+ if self.zerofill:
747
+ _ret += " ZEROFILL"
748
+ return _ret
749
+
750
+ def _sqlType(self):
751
+ return self.addSQLAttrs("INT")
752
+
753
+
754
+ class IntCol(Col):
755
+ baseClass = SOIntCol
756
+
757
+
758
+ class SOTinyIntCol(SOIntCol):
759
+ def _sqlType(self):
760
+ return self.addSQLAttrs("TINYINT")
761
+
762
+
763
+ class TinyIntCol(Col):
764
+ baseClass = SOTinyIntCol
765
+
766
+
767
+ class SOSmallIntCol(SOIntCol):
768
+ def _sqlType(self):
769
+ return self.addSQLAttrs("SMALLINT")
770
+
771
+
772
+ class SmallIntCol(Col):
773
+ baseClass = SOSmallIntCol
774
+
775
+
776
+ class SOMediumIntCol(SOIntCol):
777
+ def _sqlType(self):
778
+ return self.addSQLAttrs("MEDIUMINT")
779
+
780
+
781
+ class MediumIntCol(Col):
782
+ baseClass = SOMediumIntCol
783
+
784
+
785
+ class SOBigIntCol(SOIntCol):
786
+ def _sqlType(self):
787
+ return self.addSQLAttrs("BIGINT")
788
+
789
+
790
+ class BigIntCol(Col):
791
+ baseClass = SOBigIntCol
792
+
793
+
794
+ class BoolValidator(SOValidator):
795
+
796
+ def to_python(self, value, state):
797
+ if value is None:
798
+ return None
799
+ if isinstance(value, (bool, sqlbuilder.SQLExpression)):
800
+ return value
801
+ if PY2 and hasattr(value, '__nonzero__') \
802
+ or not PY2 and hasattr(value, '__bool__'):
803
+ return bool(value)
804
+ try:
805
+ connection = state.connection or state.soObject._connection
806
+ except AttributeError:
807
+ pass
808
+ else:
809
+ if connection.dbName == 'postgres' and \
810
+ connection.driver in ('odbc', 'pyodbc', 'pypyodbc') and \
811
+ isinstance(value, string_type):
812
+ return bool(int(value))
813
+ raise validators.Invalid(
814
+ "expected a bool or an int in the BoolCol '%s', "
815
+ "got %s %r instead" % (
816
+ self.name, type(value), value), value, state)
817
+
818
+ from_python = to_python
819
+
820
+
821
+ class SOBoolCol(SOCol):
822
+ def autoConstraints(self):
823
+ return [constrs.isBool]
824
+
825
+ def createValidators(self):
826
+ return [BoolValidator(name=self.name)] + \
827
+ super(SOBoolCol, self).createValidators()
828
+
829
+ def _postgresType(self):
830
+ return 'BOOL'
831
+
832
+ def _mysqlType(self):
833
+ return "BOOL"
834
+
835
+ def _sybaseType(self):
836
+ return "BIT"
837
+
838
+ def _mssqlType(self):
839
+ return "BIT"
840
+
841
+ def _firebirdType(self):
842
+ return 'INT'
843
+
844
+ def _maxdbType(self):
845
+ return "BOOLEAN"
846
+
847
+ def _sqliteType(self):
848
+ return "BOOLEAN"
849
+
850
+
851
+ class BoolCol(Col):
852
+ baseClass = SOBoolCol
853
+
854
+
855
+ class FloatValidator(SOValidator):
856
+
857
+ def to_python(self, value, state):
858
+ if value is None:
859
+ return None
860
+ if isinstance(value, (float, int, long, sqlbuilder.SQLExpression)):
861
+ return value
862
+ for converter, attr_name in (
863
+ (float, '__float__'), (int, '__int__'), (long, '__long__')):
864
+ if hasattr(value, attr_name):
865
+ try:
866
+ return converter(value)
867
+ except Exception:
868
+ break
869
+ raise validators.Invalid(
870
+ "expected a float in the FloatCol '%s', got %s %r instead" % (
871
+ self.name, type(value), value), value, state)
872
+
873
+ from_python = to_python
874
+
875
+
876
+ class SOFloatCol(SOCol):
877
+ # 3-03 @@: support precision (e.g., DECIMAL)
878
+
879
+ def autoConstraints(self):
880
+ return [constrs.isFloat]
881
+
882
+ def createValidators(self):
883
+ return [FloatValidator(name=self.name)] + \
884
+ super(SOFloatCol, self).createValidators()
885
+
886
+ def _sqlType(self):
887
+ return 'FLOAT'
888
+
889
+ def _mysqlType(self):
890
+ return "DOUBLE PRECISION"
891
+
892
+
893
+ class FloatCol(Col):
894
+ baseClass = SOFloatCol
895
+
896
+
897
+ class SOKeyCol(SOCol):
898
+ key_type = {int: "INT", str: "TEXT"}
899
+
900
+ # 3-03 @@: this should have a simplified constructor
901
+ # Should provide foreign key information for other DBs.
902
+
903
+ def __init__(self, **kw):
904
+ self.refColumn = kw.pop('refColumn', None)
905
+ super(SOKeyCol, self).__init__(**kw)
906
+
907
+ def _idType(self):
908
+ return self.soClass.sqlmeta.idType
909
+
910
+ def _sqlType(self):
911
+ return self.key_type[self._idType()]
912
+
913
+ def _sybaseType(self):
914
+ key_type = {int: "NUMERIC(18,0)", str: "TEXT"}
915
+ return key_type[self._idType()]
916
+
917
+ def _mssqlType(self):
918
+ key_type = {int: "INT", str: "TEXT"}
919
+ return key_type[self._idType()]
920
+
921
+ def _firebirdType(self):
922
+ key_type = {int: "INT", str: "VARCHAR(255)"}
923
+ return key_type[self._idType()]
924
+
925
+
926
+ class KeyCol(Col):
927
+
928
+ baseClass = SOKeyCol
929
+
930
+
931
+ class ForeignKeyValidator(SOValidator):
932
+
933
+ def __init__(self, *args, **kw):
934
+ super(ForeignKeyValidator, self).__init__(*args, **kw)
935
+ self.fkIDType = None
936
+
937
+ def from_python(self, value, state):
938
+ if value is None:
939
+ return None
940
+ # Avoid importing the main module
941
+ # to get the SQLObject class for isinstance
942
+ if hasattr(value, 'sqlmeta'):
943
+ return value
944
+ if self.fkIDType is None:
945
+ otherTable = findClass(self.soCol.foreignKey,
946
+ self.soCol.soClass.sqlmeta.registry)
947
+ self.fkIDType = otherTable.sqlmeta.idType
948
+ try:
949
+ value = self.fkIDType(value)
950
+ return value
951
+ except (ValueError, TypeError):
952
+ pass
953
+ raise validators.Invalid("expected a %r for the ForeignKey '%s', "
954
+ "got %s %r instead" %
955
+ (self.fkIDType, self.name,
956
+ type(value), value), value, state)
957
+
958
+
959
+ class SOForeignKey(SOKeyCol):
960
+
961
+ def __init__(self, **kw):
962
+ foreignKey = kw['foreignKey']
963
+ style = kw['soClass'].sqlmeta.style
964
+ if kw.get('name'):
965
+ kw['origName'] = kw['name']
966
+ kw['name'] = style.instanceAttrToIDAttr(kw['name'])
967
+ else:
968
+ kw['name'] = style.instanceAttrToIDAttr(
969
+ style.pythonClassToAttr(foreignKey))
970
+ super(SOForeignKey, self).__init__(**kw)
971
+
972
+ def createValidators(self):
973
+ return [ForeignKeyValidator(name=self.name)] + \
974
+ super(SOForeignKey, self).createValidators()
975
+
976
+ def _idType(self):
977
+ other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
978
+ return other.sqlmeta.idType
979
+
980
+ def sqliteCreateSQL(self):
981
+ sql = SOKeyCol.sqliteCreateSQL(self)
982
+ other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
983
+ tName = other.sqlmeta.table
984
+ idName = self.refColumn or other.sqlmeta.idName
985
+ if self.cascade is not None:
986
+ if self.cascade == 'null':
987
+ action = 'ON DELETE SET NULL'
988
+ elif self.cascade:
989
+ action = 'ON DELETE CASCADE'
990
+ else:
991
+ action = 'ON DELETE RESTRICT'
992
+ else:
993
+ action = ''
994
+ constraint = ('CONSTRAINT %(colName)s_exists '
995
+ # 'FOREIGN KEY(%(colName)s) '
996
+ 'REFERENCES %(tName)s(%(idName)s) '
997
+ '%(action)s' %
998
+ {'tName': tName,
999
+ 'colName': self.dbName,
1000
+ 'idName': idName,
1001
+ 'action': action})
1002
+ sql = ' '.join([sql, constraint])
1003
+ return sql
1004
+
1005
+ def postgresCreateSQL(self):
1006
+ sql = SOKeyCol.postgresCreateSQL(self)
1007
+ return sql
1008
+
1009
+ def postgresCreateReferenceConstraint(self):
1010
+ sTName = self.soClass.sqlmeta.table
1011
+ other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
1012
+ tName = other.sqlmeta.table
1013
+ idName = self.refColumn or other.sqlmeta.idName
1014
+ if self.cascade is not None:
1015
+ if self.cascade == 'null':
1016
+ action = 'ON DELETE SET NULL'
1017
+ elif self.cascade:
1018
+ action = 'ON DELETE CASCADE'
1019
+ else:
1020
+ action = 'ON DELETE RESTRICT'
1021
+ else:
1022
+ action = ''
1023
+ constraint = ('ALTER TABLE %(sTName)s '
1024
+ 'ADD CONSTRAINT %(colName)s_exists '
1025
+ 'FOREIGN KEY (%(colName)s) '
1026
+ 'REFERENCES %(tName)s (%(idName)s) '
1027
+ '%(action)s' %
1028
+ {'tName': tName,
1029
+ 'colName': self.dbName,
1030
+ 'idName': idName,
1031
+ 'action': action,
1032
+ 'sTName': sTName})
1033
+ return constraint
1034
+
1035
+ def mysqlCreateReferenceConstraint(self):
1036
+ sTName = self.soClass.sqlmeta.table
1037
+ sTLocalName = sTName.split('.')[-1]
1038
+ other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
1039
+ tName = other.sqlmeta.table
1040
+ idName = self.refColumn or other.sqlmeta.idName
1041
+ if self.cascade is not None:
1042
+ if self.cascade == 'null':
1043
+ action = 'ON DELETE SET NULL'
1044
+ elif self.cascade:
1045
+ action = 'ON DELETE CASCADE'
1046
+ else:
1047
+ action = 'ON DELETE RESTRICT'
1048
+ else:
1049
+ action = ''
1050
+ constraint = ('ALTER TABLE %(sTName)s '
1051
+ 'ADD CONSTRAINT %(sTLocalName)s_%(colName)s_exists '
1052
+ 'FOREIGN KEY (%(colName)s) '
1053
+ 'REFERENCES %(tName)s (%(idName)s) '
1054
+ '%(action)s' %
1055
+ {'tName': tName,
1056
+ 'colName': self.dbName,
1057
+ 'idName': idName,
1058
+ 'action': action,
1059
+ 'sTName': sTName,
1060
+ 'sTLocalName': sTLocalName})
1061
+ return constraint
1062
+
1063
+ def mysqlCreateSQL(self, connection=None):
1064
+ return SOKeyCol.mysqlCreateSQL(self, connection)
1065
+
1066
+ def sybaseCreateSQL(self):
1067
+ sql = SOKeyCol.sybaseCreateSQL(self)
1068
+ other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
1069
+ tName = other.sqlmeta.table
1070
+ idName = self.refColumn or other.sqlmeta.idName
1071
+ reference = ('REFERENCES %(tName)s(%(idName)s) ' %
1072
+ {'tName': tName,
1073
+ 'idName': idName})
1074
+ sql = ' '.join([sql, reference])
1075
+ return sql
1076
+
1077
+ def sybaseCreateReferenceConstraint(self):
1078
+ # @@: Code from above should be moved here
1079
+ return None
1080
+
1081
+ def mssqlCreateSQL(self, connection=None):
1082
+ sql = SOKeyCol.mssqlCreateSQL(self, connection)
1083
+ other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
1084
+ tName = other.sqlmeta.table
1085
+ idName = self.refColumn or other.sqlmeta.idName
1086
+ reference = ('REFERENCES %(tName)s(%(idName)s) ' %
1087
+ {'tName': tName,
1088
+ 'idName': idName})
1089
+ sql = ' '.join([sql, reference])
1090
+ return sql
1091
+
1092
+ def mssqlCreateReferenceConstraint(self):
1093
+ # @@: Code from above should be moved here
1094
+ return None
1095
+
1096
+ def maxdbCreateSQL(self):
1097
+ other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
1098
+ fidName = self.dbName
1099
+ # I assume that foreign key name is identical
1100
+ # to the id of the reference table
1101
+ sql = ' '.join([fidName, self._maxdbType()])
1102
+ tName = other.sqlmeta.table
1103
+ idName = self.refColumn or other.sqlmeta.idName
1104
+ sql += ',\nFOREIGN KEY (%s) REFERENCES %s(%s)' % (fidName, tName,
1105
+ idName)
1106
+ return sql
1107
+
1108
+ def maxdbCreateReferenceConstraint(self):
1109
+ # @@: Code from above should be moved here
1110
+ return None
1111
+
1112
+
1113
+ class ForeignKey(KeyCol):
1114
+
1115
+ baseClass = SOForeignKey
1116
+
1117
+ def __init__(self, foreignKey=None, **kw):
1118
+ super(ForeignKey, self).__init__(foreignKey=foreignKey, **kw)
1119
+
1120
+
1121
+ class EnumValidator(SOValidator):
1122
+
1123
+ def to_python(self, value, state):
1124
+ if value in self.enumValues:
1125
+ # Only encode on python 2 - on python 3, the database driver
1126
+ # will handle this
1127
+ if isinstance(value, unicode_type) and PY2:
1128
+ dbEncoding = self.getDbEncoding(state)
1129
+ value = value.encode(dbEncoding)
1130
+ return value
1131
+ elif not self.notNone and value is None:
1132
+ return None
1133
+ raise validators.Invalid(
1134
+ "expected a member of %r in the EnumCol '%s', got %r instead" % (
1135
+ self.enumValues, self.name, value), value, state)
1136
+
1137
+ from_python = to_python
1138
+
1139
+
1140
+ class SOEnumCol(SOCol):
1141
+
1142
+ def __init__(self, **kw):
1143
+ self.enumValues = kw.pop('enumValues', None)
1144
+ assert self.enumValues is not None, \
1145
+ 'You must provide an enumValues keyword argument'
1146
+ super(SOEnumCol, self).__init__(**kw)
1147
+
1148
+ def autoConstraints(self):
1149
+ return [constrs.isString, constrs.InList(self.enumValues)]
1150
+
1151
+ def createValidators(self):
1152
+ return [EnumValidator(name=self.name, enumValues=self.enumValues,
1153
+ notNone=self.notNone)] + \
1154
+ super(SOEnumCol, self).createValidators()
1155
+
1156
+ def _mysqlType(self):
1157
+ # We need to map None in the enum expression to an appropriate
1158
+ # condition on NULL
1159
+ if None in self.enumValues:
1160
+ return "ENUM(%s)" % ', '.join(
1161
+ [sqlbuilder.sqlrepr(v, 'mysql') for v in self.enumValues
1162
+ if v is not None])
1163
+ else:
1164
+ return "ENUM(%s) NOT NULL" % ', '.join(
1165
+ [sqlbuilder.sqlrepr(v, 'mysql') for v in self.enumValues])
1166
+
1167
+ def _postgresType(self):
1168
+ length = max(map(self._getlength, self.enumValues))
1169
+ enumValues = ', '.join(
1170
+ [sqlbuilder.sqlrepr(v, 'postgres') for v in self.enumValues])
1171
+ checkConstraint = "CHECK (%s in (%s))" % (self.dbName, enumValues)
1172
+ return "VARCHAR(%i) %s" % (length, checkConstraint)
1173
+
1174
+ _sqliteType = _postgresType
1175
+
1176
+ def _sybaseType(self):
1177
+ return self._postgresType()
1178
+
1179
+ def _mssqlType(self):
1180
+ return self._postgresType()
1181
+
1182
+ def _firebirdType(self):
1183
+ length = max(map(self._getlength, self.enumValues))
1184
+ enumValues = ', '.join(
1185
+ [sqlbuilder.sqlrepr(v, 'firebird') for v in self.enumValues])
1186
+ checkConstraint = "CHECK (%s in (%s))" % (self.dbName, enumValues)
1187
+ # NB. Return a tuple, not a string here
1188
+ return "VARCHAR(%i)" % (length), checkConstraint
1189
+
1190
+ def _maxdbType(self):
1191
+ raise TypeError("Enum type is not supported on MAX DB")
1192
+
1193
+ def _getlength(self, obj):
1194
+ """
1195
+ None counts as 0; everything else uses len()
1196
+ """
1197
+ if obj is None:
1198
+ return 0
1199
+ else:
1200
+ return len(obj)
1201
+
1202
+
1203
+ class EnumCol(Col):
1204
+ baseClass = SOEnumCol
1205
+
1206
+
1207
+ class SetValidator(SOValidator):
1208
+ """
1209
+ Translates Python tuples into SQL comma-delimited SET strings.
1210
+ """
1211
+
1212
+ def to_python(self, value, state):
1213
+ if isinstance(value, str):
1214
+ return tuple(value.split(","))
1215
+ raise validators.Invalid(
1216
+ "expected a string in the SetCol '%s', got %s %r instead" % (
1217
+ self.name, type(value), value), value, state)
1218
+
1219
+ def from_python(self, value, state):
1220
+ if isinstance(value, string_type):
1221
+ value = (value,)
1222
+ try:
1223
+ return ",".join(value)
1224
+ except Exception:
1225
+ raise validators.Invalid(
1226
+ "expected a string or a sequence of strings "
1227
+ "in the SetCol '%s', got %s %r instead" % (
1228
+ self.name, type(value), value), value, state)
1229
+
1230
+
1231
+ class SOSetCol(SOCol):
1232
+ def __init__(self, **kw):
1233
+ self.setValues = kw.pop('setValues', None)
1234
+ assert self.setValues is not None, \
1235
+ 'You must provide a setValues keyword argument'
1236
+ super(SOSetCol, self).__init__(**kw)
1237
+
1238
+ def autoConstraints(self):
1239
+ return [constrs.isString, constrs.InList(self.setValues)]
1240
+
1241
+ def createValidators(self):
1242
+ return [SetValidator(name=self.name, setValues=self.setValues)] + \
1243
+ super(SOSetCol, self).createValidators()
1244
+
1245
+ def _mysqlType(self):
1246
+ return "SET(%s)" % ', '.join(
1247
+ [sqlbuilder.sqlrepr(v, 'mysql') for v in self.setValues])
1248
+
1249
+
1250
+ class SetCol(Col):
1251
+ baseClass = SOSetCol
1252
+
1253
+
1254
+ class DateTimeValidator(validators.DateValidator):
1255
+ def to_python(self, value, state):
1256
+ if value is None:
1257
+ return None
1258
+ if isinstance(value,
1259
+ (datetime.datetime, datetime.date,
1260
+ datetime.time, sqlbuilder.SQLExpression)):
1261
+ return value
1262
+ if mxdatetime_available:
1263
+ if isinstance(value, mxDateTimeType):
1264
+ # convert mxDateTime instance to datetime
1265
+ if (self.format.find("%H") >= 0) or \
1266
+ (self.format.find("%T")) >= 0:
1267
+ return datetime.datetime(value.year, value.month,
1268
+ value.day,
1269
+ value.hour, value.minute,
1270
+ int(value.second))
1271
+ else:
1272
+ return datetime.date(value.year, value.month, value.day)
1273
+ elif isinstance(value, mxTimeType):
1274
+ # convert mxTime instance to time
1275
+ if self.format.find("%d") >= 0:
1276
+ return datetime.timedelta(seconds=value.seconds)
1277
+ else:
1278
+ return datetime.time(value.hour, value.minute,
1279
+ int(value.second))
1280
+ if zope_datetime_available:
1281
+ if isinstance(value, zopeDateTimeType):
1282
+ # convert zopeDateTime instance to datetime
1283
+ if (self.format.find("%H") >= 0) or \
1284
+ (self.format.find("%T")) >= 0:
1285
+ return datetime.datetime(
1286
+ value.year(), value.month(), value.day(),
1287
+ value.hour(), value.minute(), int(value.second()))
1288
+ else:
1289
+ return datetime.date(value.year, value.month, value.day)
1290
+ try:
1291
+ if self.format.find(".%f") >= 0:
1292
+ if '.' in value:
1293
+ _value = value.split('.')
1294
+ microseconds = _value[-1]
1295
+ _l = len(microseconds)
1296
+ if _l < 6:
1297
+ _value[-1] = microseconds + '0' * (6 - _l)
1298
+ elif _l > 6:
1299
+ _value[-1] = microseconds[:6]
1300
+ if _l != 6:
1301
+ value = '.'.join(_value)
1302
+ else:
1303
+ value += '.0'
1304
+ return datetime.datetime.strptime(value, self.format)
1305
+ except Exception:
1306
+ raise validators.Invalid(
1307
+ "expected a date/time string of the '%s' format "
1308
+ "in the DateTimeCol '%s', got %s %r instead" % (
1309
+ self.format, self.name, type(value), value), value, state)
1310
+
1311
+ def from_python(self, value, state):
1312
+ if value is None:
1313
+ return None
1314
+ if isinstance(value,
1315
+ (datetime.datetime, datetime.date,
1316
+ datetime.time, sqlbuilder.SQLExpression)):
1317
+ return value
1318
+ if hasattr(value, "strftime"):
1319
+ return value.strftime(self.format)
1320
+ raise validators.Invalid(
1321
+ "expected a datetime in the DateTimeCol '%s', "
1322
+ "got %s %r instead" % (
1323
+ self.name, type(value), value), value, state)
1324
+
1325
+ if mxdatetime_available:
1326
+ class MXDateTimeValidator(validators.DateValidator):
1327
+ def to_python(self, value, state):
1328
+ if value is None:
1329
+ return None
1330
+ if isinstance(value,
1331
+ (mxDateTimeType, mxTimeType,
1332
+ sqlbuilder.SQLExpression)):
1333
+ return value
1334
+ if isinstance(value, datetime.datetime):
1335
+ return mxDateTime.DateTime(value.year, value.month, value.day,
1336
+ value.hour, value.minute,
1337
+ value.second)
1338
+ elif isinstance(value, datetime.date):
1339
+ return mxDateTime.Date(value.year, value.month, value.day)
1340
+ elif isinstance(value, datetime.time):
1341
+ return mxDateTime.Time(value.hour, value.minute, value.second)
1342
+ elif isinstance(value, datetime.timedelta):
1343
+ if value.days:
1344
+ raise validators.Invalid(
1345
+ "the value for the TimeCol '%s' must has days=0, "
1346
+ "it has days=%d" % (self.name, value.days),
1347
+ value, state)
1348
+ return mxDateTime.Time(seconds=value.seconds)
1349
+ if zope_datetime_available:
1350
+ if isinstance(value, zopeDateTimeType):
1351
+ # convert zopeDateTime instance to mxdatetime
1352
+ return mxDateTime.DateTime(
1353
+ value.year(), value.month(), value.day(),
1354
+ value.hour(), value.minute(), int(value.second()))
1355
+ try:
1356
+ if self.format.find(".%f") >= 0:
1357
+ if '.' in value:
1358
+ _value = value.split('.')
1359
+ microseconds = _value[-1]
1360
+ _l = len(microseconds)
1361
+ if _l < 6:
1362
+ _value[-1] = microseconds + '0' * (6 - _l)
1363
+ elif _l > 6:
1364
+ _value[-1] = microseconds[:6]
1365
+ if _l != 6:
1366
+ value = '.'.join(_value)
1367
+ else:
1368
+ value += '.0'
1369
+ value = datetime.datetime.strptime(value, self.format)
1370
+ return mxDateTime.DateTime(value.year, value.month, value.day,
1371
+ value.hour, value.minute,
1372
+ value.second)
1373
+ except Exception:
1374
+ raise validators.Invalid(
1375
+ "expected a date/time string of the '%s' format "
1376
+ "in the DateTimeCol '%s', got %s %r instead" % (
1377
+ self.format, self.name, type(value), value),
1378
+ value, state)
1379
+
1380
+ def from_python(self, value, state):
1381
+ if value is None:
1382
+ return None
1383
+ if isinstance(value,
1384
+ (mxDateTimeType, mxTimeType,
1385
+ sqlbuilder.SQLExpression)):
1386
+ return value
1387
+ if hasattr(value, "strftime"):
1388
+ return value.strftime(self.format)
1389
+ raise validators.Invalid(
1390
+ "expected a mxDateTime in the DateTimeCol '%s', "
1391
+ "got %s %r instead" % (
1392
+ self.name, type(value), value), value, state)
1393
+
1394
+
1395
+ if zope_datetime_available:
1396
+ class ZopeDateTimeValidator(validators.DateValidator):
1397
+ def to_python(self, value, state):
1398
+ if value is None:
1399
+ return None
1400
+ if isinstance(value,
1401
+ (zopeDateTimeType, sqlbuilder.SQLExpression)):
1402
+ return value
1403
+ if isinstance(value, datetime.datetime):
1404
+ return zopeDateTime.DateTime(
1405
+ value.year, value.month, value.day,
1406
+ value.hour, value.minute, value.second)
1407
+ elif isinstance(value, datetime.date):
1408
+ return zopeDateTime.DateTime(
1409
+ value.year, value.month, value.day)
1410
+ elif isinstance(value, datetime.time):
1411
+ return zopeDateTime.DateTime(
1412
+ value.hour, value.minute, value.second)
1413
+ elif isinstance(value, datetime.timedelta):
1414
+ if value.days:
1415
+ raise validators.Invalid(
1416
+ "the value for the TimeCol '%s' must has days=0, "
1417
+ "it has days=%d" % (self.name, value.days),
1418
+ value, state)
1419
+ return zopeDateTime.DateTime(seconds=value.seconds)
1420
+ if mxdatetime_available:
1421
+ if isinstance(value, mxDateTimeType):
1422
+ # convert mxDateTime instance to zopeDateTime
1423
+ return zopeDateTime.DateTime(
1424
+ value.year, value.month, value.day,
1425
+ value.hour, value.minute, value.second)
1426
+ try:
1427
+ if self.format.find(".%f") >= 0:
1428
+ if '.' in value:
1429
+ _value = value.split('.')
1430
+ microseconds = _value[-1]
1431
+ _l = len(microseconds)
1432
+ if _l < 6:
1433
+ _value[-1] = microseconds + '0' * (6 - _l)
1434
+ elif _l > 6:
1435
+ _value[-1] = microseconds[:6]
1436
+ if _l != 6:
1437
+ value = '.'.join(_value)
1438
+ else:
1439
+ value += '.0'
1440
+ value = datetime.datetime.strptime(value, self.format)
1441
+ return zopeDateTime.DateTime(
1442
+ value.year, value.month, value.day,
1443
+ value.hour, value.minute, value.second)
1444
+ except Exception:
1445
+ raise validators.Invalid(
1446
+ "expected a date/time string of the '%s' format "
1447
+ "in the DateTimeCol '%s', got %s %r instead" % (
1448
+ self.format, self.name, type(value), value),
1449
+ value, state)
1450
+
1451
+ def from_python(self, value, state):
1452
+ if value is None:
1453
+ return None
1454
+ if isinstance(value,
1455
+ (zopeDateTimeType, sqlbuilder.SQLExpression)):
1456
+ return value
1457
+ if hasattr(value, "strftime"):
1458
+ return value.strftime(self.format)
1459
+ raise validators.Invalid(
1460
+ "expected a zopeDateTime in the DateTimeCol '%s', "
1461
+ "got %s %r instead" % (
1462
+ self.name, type(value), value), value, state)
1463
+
1464
+
1465
+ class SODateTimeCol(SOCol):
1466
+ datetimeFormat = '%Y-%m-%d %H:%M:%S.%f'
1467
+
1468
+ def __init__(self, **kw):
1469
+ datetimeFormat = kw.pop('datetimeFormat', None)
1470
+ if datetimeFormat:
1471
+ self.datetimeFormat = datetimeFormat
1472
+ super(SODateTimeCol, self).__init__(**kw)
1473
+
1474
+ def createValidators(self):
1475
+ _validators = super(SODateTimeCol, self).createValidators()
1476
+ if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1477
+ validatorClass = DateTimeValidator
1478
+ elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1479
+ validatorClass = MXDateTimeValidator
1480
+ elif default_datetime_implementation == ZOPE_DATETIME_IMPLEMENTATION:
1481
+ validatorClass = ZopeDateTimeValidator
1482
+ if default_datetime_implementation:
1483
+ _validators.insert(0, validatorClass(name=self.name,
1484
+ format=self.datetimeFormat))
1485
+ return _validators
1486
+
1487
+ def _mysqlType(self):
1488
+ if self.connection and self.connection.can_use_microseconds():
1489
+ return 'DATETIME(6)'
1490
+ else:
1491
+ return 'DATETIME'
1492
+
1493
+ def _postgresType(self):
1494
+ return 'TIMESTAMP'
1495
+
1496
+ def _sybaseType(self):
1497
+ return 'DATETIME'
1498
+
1499
+ def _mssqlType(self):
1500
+ if self.connection and self.connection.can_use_microseconds():
1501
+ return 'DATETIME2(6)'
1502
+ else:
1503
+ return 'DATETIME'
1504
+
1505
+ def _sqliteType(self):
1506
+ return 'TIMESTAMP'
1507
+
1508
+ def _firebirdType(self):
1509
+ return 'TIMESTAMP'
1510
+
1511
+ def _maxdbType(self):
1512
+ return 'TIMESTAMP'
1513
+
1514
+
1515
+ class DateTimeCol(Col):
1516
+ baseClass = SODateTimeCol
1517
+
1518
+ @staticmethod
1519
+ def now():
1520
+ if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1521
+ return datetime.datetime.now()
1522
+ elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1523
+ return mxDateTime.now()
1524
+ elif default_datetime_implementation == ZOPE_DATETIME_IMPLEMENTATION:
1525
+ return zopeDateTime.DateTimr()
1526
+ else:
1527
+ assert 0, ("No datetime implementation available "
1528
+ "(DATETIME_IMPLEMENTATION=%r)"
1529
+ % DATETIME_IMPLEMENTATION)
1530
+
1531
+
1532
+ class DateValidator(DateTimeValidator):
1533
+ def to_python(self, value, state):
1534
+ if isinstance(value, datetime.datetime):
1535
+ value = value.date()
1536
+ if isinstance(value, (datetime.date, sqlbuilder.SQLExpression)):
1537
+ return value
1538
+ value = super(DateValidator, self).to_python(value, state)
1539
+ if isinstance(value, datetime.datetime):
1540
+ value = value.date()
1541
+ return value
1542
+
1543
+ from_python = to_python
1544
+
1545
+
1546
+ class SODateCol(SOCol):
1547
+ dateFormat = '%Y-%m-%d'
1548
+
1549
+ def __init__(self, **kw):
1550
+ dateFormat = kw.pop('dateFormat', None)
1551
+ if dateFormat:
1552
+ self.dateFormat = dateFormat
1553
+ super(SODateCol, self).__init__(**kw)
1554
+
1555
+ def createValidators(self):
1556
+ """Create a validator for the column.
1557
+
1558
+ Can be overriden in descendants.
1559
+
1560
+ """
1561
+ _validators = super(SODateCol, self).createValidators()
1562
+ if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1563
+ validatorClass = DateValidator
1564
+ elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1565
+ validatorClass = MXDateTimeValidator
1566
+ elif default_datetime_implementation == ZOPE_DATETIME_IMPLEMENTATION:
1567
+ validatorClass = ZopeDateTimeValidator
1568
+ if default_datetime_implementation:
1569
+ _validators.insert(0, validatorClass(name=self.name,
1570
+ format=self.dateFormat))
1571
+ return _validators
1572
+
1573
+ def _mysqlType(self):
1574
+ return 'DATE'
1575
+
1576
+ def _postgresType(self):
1577
+ return 'DATE'
1578
+
1579
+ def _sybaseType(self):
1580
+ return self._postgresType()
1581
+
1582
+ def _mssqlType(self):
1583
+ """
1584
+ SQL Server doesn't have a DATE data type, to emulate we use a vc(10)
1585
+ """
1586
+ return 'VARCHAR(10)'
1587
+
1588
+ def _firebirdType(self):
1589
+ return 'DATE'
1590
+
1591
+ def _maxdbType(self):
1592
+ return 'DATE'
1593
+
1594
+ def _sqliteType(self):
1595
+ return 'DATE'
1596
+
1597
+
1598
+ class DateCol(Col):
1599
+ baseClass = SODateCol
1600
+
1601
+
1602
+ class TimeValidator(DateTimeValidator):
1603
+ def to_python(self, value, state):
1604
+ if isinstance(value, (datetime.time, sqlbuilder.SQLExpression)):
1605
+ return value
1606
+ if isinstance(value, datetime.timedelta):
1607
+ if value.days:
1608
+ raise validators.Invalid(
1609
+ "the value for the TimeCol '%s' must has days=0, "
1610
+ "it has days=%d" % (self.name, value.days), value, state)
1611
+ return datetime.time(
1612
+ *time.gmtime(value.seconds)[3:6],
1613
+ microsecond=value.microseconds
1614
+ )
1615
+ value = super(TimeValidator, self).to_python(value, state)
1616
+ if isinstance(value, datetime.datetime):
1617
+ value = value.time()
1618
+ return value
1619
+
1620
+ from_python = to_python
1621
+
1622
+
1623
+ class SOTimeCol(SOCol):
1624
+ timeFormat = '%H:%M:%S.%f'
1625
+
1626
+ def __init__(self, **kw):
1627
+ timeFormat = kw.pop('timeFormat', None)
1628
+ if timeFormat:
1629
+ self.timeFormat = timeFormat
1630
+ super(SOTimeCol, self).__init__(**kw)
1631
+
1632
+ def createValidators(self):
1633
+ _validators = super(SOTimeCol, self).createValidators()
1634
+ if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1635
+ validatorClass = TimeValidator
1636
+ elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1637
+ validatorClass = MXDateTimeValidator
1638
+ elif default_datetime_implementation == ZOPE_DATETIME_IMPLEMENTATION:
1639
+ validatorClass = ZopeDateTimeValidator
1640
+ if default_datetime_implementation:
1641
+ _validators.insert(0, validatorClass(name=self.name,
1642
+ format=self.timeFormat))
1643
+ return _validators
1644
+
1645
+ def _mysqlType(self):
1646
+ if self.connection and self.connection.can_use_microseconds():
1647
+ return 'TIME(6)'
1648
+ else:
1649
+ return 'TIME'
1650
+
1651
+ def _postgresType(self):
1652
+ return 'TIME'
1653
+
1654
+ def _sybaseType(self):
1655
+ return 'TIME'
1656
+
1657
+ def _mssqlType(self):
1658
+ if self.connection and self.connection.can_use_microseconds():
1659
+ return 'TIME(6)'
1660
+ else:
1661
+ return 'TIME'
1662
+
1663
+ def _sqliteType(self):
1664
+ return 'TIME'
1665
+
1666
+ def _firebirdType(self):
1667
+ return 'TIME'
1668
+
1669
+ def _maxdbType(self):
1670
+ return 'TIME'
1671
+
1672
+
1673
+ class TimeCol(Col):
1674
+ baseClass = SOTimeCol
1675
+
1676
+
1677
+ class SOTimestampCol(SODateTimeCol):
1678
+ """
1679
+ Necessary to support MySQL's use of TIMESTAMP versus DATETIME types
1680
+ """
1681
+
1682
+ def __init__(self, **kw):
1683
+ if 'default' not in kw:
1684
+ kw['default'] = None
1685
+ SOCol.__init__(self, **kw)
1686
+
1687
+ def _mysqlType(self):
1688
+ if self.connection and self.connection.can_use_microseconds():
1689
+ return 'TIMESTAMP(6)'
1690
+ else:
1691
+ return 'TIMESTAMP'
1692
+
1693
+
1694
+ class TimestampCol(Col):
1695
+ baseClass = SOTimestampCol
1696
+
1697
+
1698
+ class TimedeltaValidator(SOValidator):
1699
+ def to_python(self, value, state):
1700
+ return value
1701
+
1702
+ from_python = to_python
1703
+
1704
+
1705
+ class SOTimedeltaCol(SOCol):
1706
+ def _postgresType(self):
1707
+ return 'INTERVAL'
1708
+
1709
+ def createValidators(self):
1710
+ return [TimedeltaValidator(name=self.name)] + \
1711
+ super(SOTimedeltaCol, self).createValidators()
1712
+
1713
+
1714
+ class TimedeltaCol(Col):
1715
+ baseClass = SOTimedeltaCol
1716
+
1717
+
1718
+ class DecimalValidator(SOValidator):
1719
+ def to_python(self, value, state):
1720
+ if value is None:
1721
+ return None
1722
+ if isinstance(value, (int, long, Decimal, sqlbuilder.SQLExpression)):
1723
+ return value
1724
+ if isinstance(value, float):
1725
+ value = str(value)
1726
+ try:
1727
+ connection = state.connection or state.soObject._connection
1728
+ except AttributeError:
1729
+ pass
1730
+ else:
1731
+ if hasattr(connection, "decimalSeparator"):
1732
+ value = value.replace(connection.decimalSeparator, ".")
1733
+ try:
1734
+ return Decimal(value)
1735
+ except Exception:
1736
+ raise validators.Invalid(
1737
+ "expected a Decimal in the DecimalCol '%s', "
1738
+ "got %s %r instead" % (
1739
+ self.name, type(value), value), value, state)
1740
+
1741
+ def from_python(self, value, state):
1742
+ if value is None:
1743
+ return None
1744
+ if isinstance(value, float):
1745
+ value = str(value)
1746
+ if isinstance(value, string_type):
1747
+ try:
1748
+ connection = state.connection or state.soObject._connection
1749
+ except AttributeError:
1750
+ pass
1751
+ else:
1752
+ if hasattr(connection, "decimalSeparator"):
1753
+ value = value.replace(connection.decimalSeparator, ".")
1754
+ try:
1755
+ return Decimal(value)
1756
+ except Exception:
1757
+ raise validators.Invalid(
1758
+ "can not parse Decimal value '%s' "
1759
+ "in the DecimalCol from '%s'" % (
1760
+ value, getattr(state, 'soObject', '(unknown)')),
1761
+ value, state)
1762
+ if isinstance(value, (int, long, Decimal, sqlbuilder.SQLExpression)):
1763
+ return value
1764
+ raise validators.Invalid(
1765
+ "expected a Decimal in the DecimalCol '%s', got %s %r instead" % (
1766
+ self.name, type(value), value), value, state)
1767
+
1768
+
1769
+ class SODecimalCol(SOCol):
1770
+
1771
+ def __init__(self, **kw):
1772
+ self.size = kw.pop('size', NoDefault)
1773
+ assert self.size is not NoDefault, \
1774
+ "You must give a size argument"
1775
+ self.precision = kw.pop('precision', NoDefault)
1776
+ assert self.precision is not NoDefault, \
1777
+ "You must give a precision argument"
1778
+ super(SODecimalCol, self).__init__(**kw)
1779
+
1780
+ def _sqlType(self):
1781
+ return 'DECIMAL(%i, %i)' % (self.size, self.precision)
1782
+
1783
+ def createValidators(self):
1784
+ return [DecimalValidator(name=self.name)] + \
1785
+ super(SODecimalCol, self).createValidators()
1786
+
1787
+
1788
+ class DecimalCol(Col):
1789
+ baseClass = SODecimalCol
1790
+
1791
+
1792
+ class SOCurrencyCol(SODecimalCol):
1793
+
1794
+ def __init__(self, **kw):
1795
+ pushKey(kw, 'size', 10)
1796
+ pushKey(kw, 'precision', 2)
1797
+ super(SOCurrencyCol, self).__init__(**kw)
1798
+
1799
+
1800
+ class CurrencyCol(DecimalCol):
1801
+ baseClass = SOCurrencyCol
1802
+
1803
+
1804
+ class DecimalStringValidator(DecimalValidator):
1805
+ def to_python(self, value, state):
1806
+ value = super(DecimalStringValidator, self).to_python(value, state)
1807
+ if self.precision and isinstance(value, Decimal):
1808
+ assert value < self.max, \
1809
+ "Value must be less than %s" % int(self.max)
1810
+ value = value.quantize(self.precision)
1811
+ return value
1812
+
1813
+ def from_python(self, value, state):
1814
+ value = super(DecimalStringValidator, self).from_python(value, state)
1815
+ if isinstance(value, Decimal):
1816
+ if self.precision:
1817
+ assert value < self.max, \
1818
+ "Value must be less than %s" % int(self.max)
1819
+ value = value.quantize(self.precision)
1820
+ value = value.to_eng_string()
1821
+ elif isinstance(value, (int, long)):
1822
+ value = str(value)
1823
+ return value
1824
+
1825
+
1826
+ class SODecimalStringCol(SOStringCol):
1827
+ def __init__(self, **kw):
1828
+ self.size = kw.pop('size', NoDefault)
1829
+ assert (self.size is not NoDefault) and (self.size >= 0), \
1830
+ "You must give a size argument as a positive integer"
1831
+ self.precision = kw.pop('precision', NoDefault)
1832
+ assert (self.precision is not NoDefault) and (self.precision >= 0), \
1833
+ "You must give a precision argument as a positive integer"
1834
+ kw['length'] = int(self.size) + int(self.precision)
1835
+ self.quantize = kw.pop('quantize', False)
1836
+ assert isinstance(self.quantize, bool), \
1837
+ "quantize argument must be Boolean True/False"
1838
+ super(SODecimalStringCol, self).__init__(**kw)
1839
+
1840
+ def createValidators(self):
1841
+ if self.quantize:
1842
+ v = DecimalStringValidator(
1843
+ name=self.name,
1844
+ precision=Decimal(10) ** (-1 * int(self.precision)),
1845
+ max=Decimal(10) ** (int(self.size) - int(self.precision)))
1846
+ else:
1847
+ v = DecimalStringValidator(name=self.name, precision=0)
1848
+ return [v] + \
1849
+ super(SODecimalStringCol, self).createValidators(dataType=Decimal)
1850
+
1851
+
1852
+ class DecimalStringCol(StringCol):
1853
+ baseClass = SODecimalStringCol
1854
+
1855
+
1856
+ class BinaryValidator(SOValidator):
1857
+ """
1858
+ Validator for binary types.
1859
+
1860
+ We're assuming that the per-database modules provide some form
1861
+ of wrapper type for binary conversion.
1862
+ """
1863
+
1864
+ _cachedValue = None
1865
+
1866
+ def to_python(self, value, state):
1867
+ if value is None:
1868
+ return None
1869
+ try:
1870
+ connection = state.connection or state.soObject._connection
1871
+ except AttributeError:
1872
+ dbName = None
1873
+ binaryType = type(None) # Just a simple workaround
1874
+ else:
1875
+ dbName = connection.dbName
1876
+ binaryType = connection._binaryType
1877
+ if isinstance(value, str):
1878
+ if not PY2 and dbName == "mysql":
1879
+ value = value.encode('ascii', errors='surrogateescape')
1880
+ if dbName == "sqlite":
1881
+ if not PY2:
1882
+ value = bytes(value, 'ascii')
1883
+ value = connection.module.decode(value)
1884
+ return value
1885
+ if isinstance(value, bytes):
1886
+ return value
1887
+ if isinstance(value, (buffer_type, binaryType)):
1888
+ cachedValue = self._cachedValue
1889
+ if cachedValue and cachedValue[1] == value:
1890
+ return cachedValue[0]
1891
+ if isinstance(value, array): # MySQL
1892
+ return value.tostring()
1893
+ if not PY2 and isinstance(value, memoryview):
1894
+ return value.tobytes()
1895
+ return str(value) # buffer => string
1896
+ raise validators.Invalid(
1897
+ "expected a string in the BLOBCol '%s', got %s %r instead" % (
1898
+ self.name, type(value), value), value, state)
1899
+
1900
+ def from_python(self, value, state):
1901
+ if value is None:
1902
+ return None
1903
+ connection = state.connection or state.soObject._connection
1904
+ binary = connection.createBinary(value)
1905
+ if not PY2 and isinstance(binary, memoryview):
1906
+ binary = str(binary.tobytes(), 'ascii')
1907
+ self._cachedValue = (value, binary)
1908
+ return binary
1909
+
1910
+
1911
+ class SOBLOBCol(SOStringCol):
1912
+ def __init__(self, **kw):
1913
+ # Change the default from 'auto' to False -
1914
+ # this is a (mostly) binary column
1915
+ if 'varchar' not in kw:
1916
+ kw['varchar'] = False
1917
+ super(SOBLOBCol, self).__init__(**kw)
1918
+
1919
+ def createValidators(self):
1920
+ return [BinaryValidator(name=self.name)] + \
1921
+ super(SOBLOBCol, self).createValidators()
1922
+
1923
+ def _mysqlType(self):
1924
+ length = self.length
1925
+ varchar = self.varchar
1926
+ if length:
1927
+ if length >= 2 ** 24:
1928
+ return varchar and "LONGTEXT" or "LONGBLOB"
1929
+ if length >= 2 ** 16:
1930
+ return varchar and "MEDIUMTEXT" or "MEDIUMBLOB"
1931
+ if length >= 2 ** 8:
1932
+ return varchar and "TEXT" or "BLOB"
1933
+ return varchar and "TINYTEXT" or "TINYBLOB"
1934
+
1935
+ def _postgresType(self):
1936
+ return 'BYTEA'
1937
+
1938
+ def _mssqlType(self):
1939
+ if self.connection and self.connection.can_use_max_types():
1940
+ return 'VARBINARY(MAX)'
1941
+ else:
1942
+ return "IMAGE"
1943
+
1944
+
1945
+ class BLOBCol(StringCol):
1946
+ baseClass = SOBLOBCol
1947
+
1948
+
1949
+ class PickleValidator(BinaryValidator):
1950
+ """
1951
+ Validator for pickle types. A pickle type is simply a binary type
1952
+ with hidden pickling, so that we can simply store any kind of
1953
+ stuff in a particular column.
1954
+
1955
+ The support for this relies directly on the support for binary for
1956
+ your database.
1957
+ """
1958
+
1959
+ def to_python(self, value, state):
1960
+ if value is None:
1961
+ return None
1962
+ if isinstance(value, unicode_type):
1963
+ dbEncoding = self.getDbEncoding(state, default='ascii')
1964
+ if PY2:
1965
+ value = value.encode(dbEncoding)
1966
+ else:
1967
+ value = value.encode(dbEncoding, errors='surrogateescape')
1968
+ if isinstance(value, bytes):
1969
+ return pickle.loads(value)
1970
+ raise validators.Invalid(
1971
+ "expected a pickle string in the PickleCol '%s', "
1972
+ "got %s %r instead" % (
1973
+ self.name, type(value), value), value, state)
1974
+
1975
+ def from_python(self, value, state):
1976
+ if value is None:
1977
+ return None
1978
+ return pickle.dumps(value, self.pickleProtocol)
1979
+
1980
+
1981
+ class SOPickleCol(SOBLOBCol):
1982
+
1983
+ def __init__(self, **kw):
1984
+ self.pickleProtocol = kw.pop('pickleProtocol', pickle.HIGHEST_PROTOCOL)
1985
+ super(SOPickleCol, self).__init__(**kw)
1986
+
1987
+ def createValidators(self):
1988
+ return [PickleValidator(name=self.name,
1989
+ pickleProtocol=self.pickleProtocol)] + \
1990
+ super(SOPickleCol, self).createValidators()
1991
+
1992
+ def _mysqlType(self):
1993
+ length = self.length
1994
+ if length:
1995
+ if length >= 2 ** 24:
1996
+ return "LONGBLOB"
1997
+ if length >= 2 ** 16:
1998
+ return "MEDIUMBLOB"
1999
+ return "BLOB"
2000
+
2001
+
2002
+ class PickleCol(BLOBCol):
2003
+ baseClass = SOPickleCol
2004
+
2005
+
2006
+ class UuidValidator(SOValidator):
2007
+
2008
+ def to_python(self, value, state):
2009
+ if value is None:
2010
+ return None
2011
+ if PY2 and isinstance(value, unicode):
2012
+ value = value.encode('ascii')
2013
+ if isinstance(value, str):
2014
+ return UUID(value)
2015
+ if isinstance(value, UUID):
2016
+ return value
2017
+ raise validators.Invalid(
2018
+ "expected string in the UuidCol '%s', "
2019
+ "got %s %r instead" % (
2020
+ self.name, type(value), value), value, state)
2021
+
2022
+ def from_python(self, value, state):
2023
+ if value is None:
2024
+ return None
2025
+ if isinstance(value, str):
2026
+ return value
2027
+ if isinstance(value, UUID):
2028
+ return str(value)
2029
+ raise validators.Invalid(
2030
+ "expected uuid in the UuidCol '%s', "
2031
+ "got %s %r instead" % (
2032
+ self.name, type(value), value), value, state)
2033
+
2034
+
2035
+ class SOUuidCol(SOCol):
2036
+ def createValidators(self):
2037
+ return [UuidValidator(name=self.name)] + \
2038
+ super(SOUuidCol, self).createValidators()
2039
+
2040
+ def _sqlType(self):
2041
+ return 'VARCHAR(36)'
2042
+
2043
+ def _postgresType(self):
2044
+ return 'UUID'
2045
+
2046
+
2047
+ class UuidCol(Col):
2048
+ baseClass = SOUuidCol
2049
+
2050
+
2051
+ class JsonbValidator(SOValidator):
2052
+
2053
+ def to_python(self, value, state):
2054
+ if isinstance(value, string_type):
2055
+ return json.loads(value)
2056
+ return value
2057
+
2058
+ def from_python(self, value, state):
2059
+ if value is None:
2060
+ return json.dumps(None)
2061
+ if isinstance(value, (dict, list, unicode, int, long, float, bool)):
2062
+ return json.dumps(value)
2063
+ raise validators.Invalid(
2064
+ "expect one of the following types "
2065
+ "(dict, list, unicode, int, long, float, bool) for '%s', "
2066
+ "got %s %r instead" % (
2067
+ self.name, type(value), value), value, state)
2068
+
2069
+
2070
+ class SOJsonbCol(SOCol):
2071
+ def createValidators(self):
2072
+ return [JsonbValidator(name=self.name)] + \
2073
+ super(SOJsonbCol, self).createValidators()
2074
+
2075
+ def _postgresType(self):
2076
+ return 'JSONB'
2077
+
2078
+
2079
+ class JsonbCol(Col):
2080
+ baseClass = SOJsonbCol
2081
+
2082
+
2083
+ class JSONValidator(SOValidator):
2084
+
2085
+ def to_python(self, value, state):
2086
+ if value is None:
2087
+ return None
2088
+ if isinstance(value, (bool, int, float, long, dict, list)):
2089
+ return value
2090
+ if isinstance(value, string_type):
2091
+ return json.loads(value)
2092
+ raise validators.Invalid(
2093
+ "expected a JSON str in the JSONCol '%s', "
2094
+ "got %s %r instead" % (
2095
+ self.name, type(value), value), value, state)
2096
+
2097
+ def from_python(self, value, state):
2098
+ if value is None:
2099
+ return None
2100
+ if isinstance(value,
2101
+ (bool, int, float, long, dict, list, string_type)):
2102
+ return json.dumps(value)
2103
+ raise validators.Invalid(
2104
+ "expected an object suitable for JSON in the JSONCol '%s', "
2105
+ "got %s %r instead" % (
2106
+ self.name, type(value), value), value, state)
2107
+
2108
+
2109
+ class SOJSONCol(SOStringCol):
2110
+
2111
+ def createValidators(self):
2112
+ return [JSONValidator(name=self.name)]
2113
+
2114
+ # Doesn't work, especially with Postgres
2115
+ # def _sqlType(self):
2116
+ # return 'JSON'
2117
+
2118
+
2119
+ class JSONCol(StringCol):
2120
+ baseClass = SOJSONCol
2121
+
2122
+
2123
+ #############################################
2124
+ #
2125
+ # Added by Tippett
2126
+ #
2127
+
2128
+ #
2129
+ # Convenience stuff Monkey-Patched onto sqlbuilder.SQLObjectField,
2130
+ # so you can do:
2131
+ #
2132
+ # Table.q.column.json_extract("key") == value
2133
+ # Table.q.column.json_contains("item")
2134
+ # Table.q.column.json_length("key")
2135
+ #
2136
+ # instead of
2137
+ #
2138
+ # func.JSON_EXTRACT(Table.q.column, '$.key') == value
2139
+ # func.JSON_CONTAINS(Table.q.column, json.dumps("item"))
2140
+ # func.JSON_LENGTH(Table.q.column, '$.key')
2141
+ #
2142
+
2143
+ def _json_extract(col, key, path=None):
2144
+ """
2145
+ """
2146
+ assert isinstance(col.column, SOJSONCol), \
2147
+ "{!r} is not a JSONCol".format(col)
2148
+ if path is None:
2149
+ return sqlbuilder.func.JSON_EXTRACT(col, '$.%s' % key)
2150
+ else:
2151
+ return sqlbuilder.func.JSON_EXTRACT(col, '$.%s' % key, path)
2152
+
2153
+
2154
+ def _json_contains(col, value, path=None):
2155
+ assert isinstance(col.column, SOJSONCol), \
2156
+ "{!r} is not a JSONCol".format(col)
2157
+ if path is None:
2158
+ return sqlbuilder.func.JSON_CONTAINS(col, json.dumps(value))
2159
+ else:
2160
+ return sqlbuilder.func.JSON_CONTAINS(col, json.dumps(value), path)
2161
+
2162
+
2163
+ def _json_length(col, path=None):
2164
+ assert isinstance(col.column, SOJSONCol), \
2165
+ "{!r} is not a JSONCol".format(col)
2166
+ if path is None:
2167
+ return sqlbuilder.func.JSON_LENGTH(col)
2168
+ else:
2169
+ return sqlbuilder.func.JSON_LENGTH(col, path)
2170
+
2171
+
2172
+ sqlbuilder.SQLObjectField.json_extract = _json_extract
2173
+ sqlbuilder.SQLObjectField.json_contains = _json_contains
2174
+ sqlbuilder.SQLObjectField.json_length = _json_length
2175
+
2176
+
2177
+ def pushKey(kw, name, value):
2178
+ if name not in kw:
2179
+ kw[name] = value
2180
+
2181
+ all = []
2182
+ # Use copy() to avoid 'dictionary changed' issues on python 3
2183
+ for key, value in globals().copy().items():
2184
+ if isinstance(value, type) and (issubclass(value, (Col, SOCol))):
2185
+ all.append(key)
2186
+ __all__.extend(all)
2187
+ del all