0001"""
0002Col -- SQLObject columns
0003
0004Note that each column object is named BlahBlahCol, and these are used
0005in class definitions. But there's also a corresponding SOBlahBlahCol
0006object, which is used in SQLObject *classes*.
0007
0008An explanation: when a SQLObject subclass is created, the metaclass
0009looks through your class definition for any subclasses of Col. It
0010collects them together, and indexes them to do all the database stuff
0011you like, like the magic attributes and whatnot. It then asks the Col
0012object to create an SOCol object (usually a subclass, actually). The
0013SOCol object contains all the interesting logic, as well as a record
0014of the attribute name you used and the class it is bound to (set by
0015the metaclass).
0016
0017So, in summary: Col objects are what you define, but SOCol objects
0018are what gets used.
0019"""
0020
0021from array import array
0022from itertools import count
0023import re, time
0024try:
0025 import cPickle as pickle
0026except ImportError:
0027 import pickle
0028
0029
0030from formencode import compound, validators
0031from classregistry import findClass
0032import constraints as constrs
0033import sqlbuilder
0034from styles import capword
0035
0036NoDefault = sqlbuilder.NoDefault
0037
0038import datetime
0039datetime_available = True
0040
0041try:
0042 from mx import DateTime
0043except ImportError:
0044 try:
0045 import DateTime
0046 except ImportError:
0047 mxdatetime_available = False
0048 else:
0049 mxdatetime_available = True
0050else:
0051 mxdatetime_available = True
0052
0053DATETIME_IMPLEMENTATION = "datetime"
0054MXDATETIME_IMPLEMENTATION = "mxDateTime"
0055
0056if mxdatetime_available:
0057 if hasattr(DateTime, "Time"):
0058 DateTimeType = type(DateTime.now())
0059 TimeType = type(DateTime.Time())
0060 else:
0061 DateTimeType = type(DateTime.DateTime())
0062 TimeType = type(DateTime.DateTime.Time(DateTime.DateTime()))
0063
0064default_datetime_implementation = DATETIME_IMPLEMENTATION
0065
0066__all__ = ["datetime_available", "mxdatetime_available",
0067 "default_datetime_implementation", "DATETIME_IMPLEMENTATION"]
0068
0069if mxdatetime_available:
0070 __all__.append("MXDATETIME_IMPLEMENTATION")
0071
0072
0073creationOrder = count()
0074
0075
0076
0077
0078
0079
0080
0081class SOCol(object):
0082
0083 def __init__(self,
0084 name,
0085 soClass,
0086 creationOrder,
0087 dbName=None,
0088 default=NoDefault,
0089 defaultSQL=None,
0090 foreignKey=None,
0091 alternateID=False,
0092 alternateMethodName=None,
0093 constraints=None,
0094 notNull=NoDefault,
0095 notNone=NoDefault,
0096 unique=NoDefault,
0097 sqlType=None,
0098 columnDef=None,
0099 validator=None,
0100 validator2=None,
0101 immutable=False,
0102 cascade=None,
0103 lazy=False,
0104 noCache=False,
0105 forceDBName=False,
0106 title=None,
0107 tags=[],
0108 origName=None,
0109 extra_vars=None):
0110
0111 super(SOCol, self).__init__()
0112
0113
0114
0115
0116
0117
0118 if not forceDBName:
0119 assert sqlbuilder.sqlIdentifier(name), 'Name must be SQL-safe (letters, numbers, underscores): %s (or use forceDBName=True)' % repr(name)
0121 assert name != 'id', 'The column name "id" is reserved for SQLObject use (and is implicitly created).'
0122 assert name, "You must provide a name for all columns"
0123
0124 self.columnDef = columnDef
0125 self.creationOrder = creationOrder
0126
0127 self.immutable = immutable
0128
0129
0130
0131
0132
0133
0134 if isinstance(cascade, str):
0135 assert cascade == 'null', (
0136 "The only string value allowed for cascade is 'null' (you gave: %r)" % cascade)
0137 self.cascade = cascade
0138
0139 if not isinstance(constraints, (list, tuple)):
0140 constraints = [constraints]
0141 self.constraints = self.autoConstraints() + constraints
0142
0143 self.notNone = False
0144 if notNull is not NoDefault:
0145 self.notNone = notNull
0146 assert notNone is NoDefault or (not notNone) == (not notNull), "The notNull and notNone arguments are aliases, and must not conflict. You gave notNull=%r, notNone=%r" % (notNull, notNone)
0149 elif notNone is not NoDefault:
0150 self.notNone = notNone
0151 if self.notNone:
0152 self.constraints = [constrs.notNull] + self.constraints
0153
0154 self.name = name
0155 self.soClass = soClass
0156 self._default = default
0157 self.defaultSQL = defaultSQL
0158 self.customSQLType = sqlType
0159
0160
0161 self.foreignKey = foreignKey
0162 if self.foreignKey:
0163 if origName is not None:
0164 idname = soClass.sqlmeta.style.instanceAttrToIDAttr(origName)
0165 else:
0166 idname = soClass.sqlmeta.style.instanceAttrToIDAttr(name)
0167 if self.name != idname:
0168 self.foreignName = self.name
0169 self.name = idname
0170 else:
0171 self.foreignName = soClass.sqlmeta.style.instanceIDAttrToAttr(self.name)
0172 else:
0173 self.foreignName = None
0174
0175
0176
0177
0178 if dbName is None:
0179 self.dbName = soClass.sqlmeta.style.pythonAttrToDBColumn(self.name)
0180 else:
0181 self.dbName = dbName
0182
0183
0184
0185 self.alternateID = alternateID
0186
0187 if unique is NoDefault:
0188 self.unique = alternateID
0189 else:
0190 self.unique = unique
0191 if self.unique and alternateMethodName is None:
0192 self.alternateMethodName = 'by' + capword(self.name)
0193 else:
0194 self.alternateMethodName = alternateMethodName
0195
0196 _validators = self.createValidators()
0197 if validator: _validators.append(validator)
0198 if validator2: _validators.insert(0, validator2)
0199 _vlen = len(_validators)
0200 if _vlen == 0:
0201 self.validator = None
0202 elif _vlen == 1:
0203 self.validator = _validators[0]
0204 elif _vlen > 1:
0205 self.validator = compound.All.join(_validators[0], *_validators[1:])
0206 self.noCache = noCache
0207 self.lazy = lazy
0208
0209
0210 self.origName = origName or name
0211 self.title = title
0212 self.tags = tags
0213
0214 if extra_vars:
0215 for name, value in extra_vars.items():
0216 setattr(self, name, value)
0217
0218 def _set_validator(self, value):
0219 self._validator = value
0220 if self._validator:
0221 self.to_python = self._validator.to_python
0222 self.from_python = self._validator.from_python
0223 else:
0224 self.to_python = None
0225 self.from_python = None
0226
0227 def _get_validator(self):
0228 return self._validator
0229
0230 validator = property(_get_validator, _set_validator)
0231
0232 def createValidators(self):
0233 """Create a list of validators for the column."""
0234 return []
0235
0236 def autoConstraints(self):
0237 return []
0238
0239 def _get_default(self):
0240
0241
0242 if self._default is NoDefault:
0243 return NoDefault
0244 elif hasattr(self._default, '__sqlrepr__'):
0245 return self._default
0246 elif callable(self._default):
0247 return self._default()
0248 else:
0249 return self._default
0250 default = property(_get_default, None, None)
0251
0252 def _get_joinName(self):
0253 return self.soClass.sqlmeta.style.instanceIDAttrToAttr(self.name)
0254 joinName = property(_get_joinName, None, None)
0255
0256 def __repr__(self):
0257 r = '<%s %s' % (self.__class__.__name__, self.name)
0258 if self.default is not NoDefault:
0259 r += ' default=%s' % repr(self.default)
0260 if self.foreignKey:
0261 r += ' connected to %s' % self.foreignKey
0262 if self.alternateID:
0263 r += ' alternate ID'
0264 if self.notNone:
0265 r += ' not null'
0266 return r + '>'
0267
0268 def createSQL(self):
0269 return ' '.join([self._sqlType()] + self._extraSQL())
0270
0271 def _extraSQL(self):
0272 result = []
0273 if self.notNone or self.alternateID:
0274 result.append('NOT NULL')
0275 if self.unique or self.alternateID:
0276 result.append('UNIQUE')
0277 if self.defaultSQL is not None:
0278 result.append("DEFAULT %s" % self.defaultSQL)
0279 return result
0280
0281 def _sqlType(self):
0282 if self.customSQLType is None:
0283 raise ValueError, ("Col %s (%s) cannot be used for automatic "
0284 "schema creation (too abstract)" %
0285 (self.name, self.__class__))
0286 else:
0287 return self.customSQLType
0288
0289 def _mysqlType(self):
0290 return self._sqlType()
0291
0292 def _postgresType(self):
0293 return self._sqlType()
0294
0295 def _sqliteType(self):
0296
0297
0298 try:
0299 return self._sqlType()
0300 except ValueError:
0301 return ''
0302
0303 def _sybaseType(self):
0304 return self._sqlType()
0305
0306 def _mssqlType(self):
0307 return self._sqlType()
0308
0309 def _firebirdType(self):
0310 return self._sqlType()
0311
0312 def _maxdbType(self):
0313 return self._sqlType()
0314
0315 def mysqlCreateSQL(self):
0316 return ' '.join([self.dbName, self._mysqlType()] + self._extraSQL())
0317
0318 def postgresCreateSQL(self):
0319 return ' '.join([self.dbName, self._postgresType()] + self._extraSQL())
0320
0321 def sqliteCreateSQL(self):
0322 return ' '.join([self.dbName, self._sqliteType()] + self._extraSQL())
0323
0324 def sybaseCreateSQL(self):
0325 return ' '.join([self.dbName, self._sybaseType()] + self._extraSQL())
0326
0327 def mssqlCreateSQL(self, connection=None):
0328 self.connection = connection
0329 return ' '.join([self.dbName, self._mssqlType()] + self._extraSQL())
0330
0331 def firebirdCreateSQL(self):
0332
0333
0334
0335 if not isinstance(self, SOEnumCol):
0336 return ' '.join([self.dbName, self._firebirdType()] + self._extraSQL())
0337 else:
0338 return ' '.join([self.dbName] + [self._firebirdType()[0]] + self._extraSQL() + [self._firebirdType()[1]])
0339
0340 def maxdbCreateSQL(self):
0341 return ' '.join([self.dbName, self._maxdbType()] + self._extraSQL())
0342
0343 def __get__(self, obj, type=None):
0344 if obj is None:
0345
0346 return self
0347 if obj.sqlmeta._obsolete:
0348 raise RuntimeError('The object <%s %s> is obsolete' % (
0349 obj.__class__.__name__, obj.id))
0350 if obj.sqlmeta.cacheColumns:
0351 columns = obj.sqlmeta._columnCache
0352 if columns is None:
0353 obj.sqlmeta.loadValues()
0354 try:
0355 return columns[name]
0356 except KeyError:
0357 return obj.sqlmeta.loadColumn(self)
0358 else:
0359 return obj.sqlmeta.loadColumn(self)
0360
0361 def __set__(self, obj, value):
0362 if self.immutable:
0363 raise AttributeError("The column %s.%s is immutable" %
0364 (obj.__class__.__name__,
0365 self.name))
0366 obj.sqlmeta.setColumn(self, value)
0367
0368 def __delete__(self, obj):
0369 raise AttributeError("I can't be deleted from %r" % obj)
0370
0371
0372class Col(object):
0373
0374 baseClass = SOCol
0375
0376 def __init__(self, name=None, **kw):
0377 super(Col, self).__init__()
0378 self.__dict__['_name'] = name
0379 self.__dict__['_kw'] = kw
0380 self.__dict__['creationOrder'] = creationOrder.next()
0381 self.__dict__['_extra_vars'] = {}
0382
0383 def _set_name(self, value):
0384 assert self._name is None or self._name == value, (
0385 "You cannot change a name after it has already been set "
0386 "(from %s to %s)" % (self.name, value))
0387 self.__dict__['_name'] = value
0388
0389 def _get_name(self):
0390 return self._name
0391
0392 name = property(_get_name, _set_name)
0393
0394 def withClass(self, soClass):
0395 return self.baseClass(soClass=soClass, name=self._name,
0396 creationOrder=self.creationOrder,
0397 columnDef=self,
0398 extra_vars=self._extra_vars,
0399 **self._kw)
0400
0401 def __setattr__(self, var, value):
0402 if var == 'name':
0403 super(Col, self).__setattr__(var, value)
0404 return
0405 self._extra_vars[var] = value
0406
0407 def __repr__(self):
0408 return '<%s %s %s>' % (
0409 self.__class__.__name__, hex(abs(id(self)))[2:],
0410 self._name or '(unnamed)')
0411
0412
0413
0414class SOStringLikeCol(SOCol):
0415 """A common ancestor for SOStringCol and SOUnicodeCol"""
0416 def __init__(self, **kw):
0417 self.length = kw.pop('length', None)
0418 self.varchar = kw.pop('varchar', 'auto')
0419 self.char_binary = kw.pop('char_binary', None)
0420 if not self.length:
0421 assert self.varchar == 'auto' or not self.varchar, "Without a length strings are treated as TEXT, not varchar"
0423 self.varchar = False
0424 elif self.varchar == 'auto':
0425 self.varchar = True
0426
0427 super(SOStringLikeCol, self).__init__(**kw)
0428
0429 def autoConstraints(self):
0430 constraints = [constrs.isString]
0431 if self.length is not None:
0432 constraints += [constrs.MaxLength(self.length)]
0433 return constraints
0434
0435 def _sqlType(self):
0436 if self.customSQLType is not None:
0437 return self.customSQLType
0438 if not self.length:
0439 return 'TEXT'
0440 elif self.varchar:
0441 return 'VARCHAR(%i)' % self.length
0442 else:
0443 return 'CHAR(%i)' % self.length
0444
0445 def _check_case_sensitive(self, db):
0446 if self.char_binary:
0447 raise ValueError, "%s does not support binary character columns" % db
0448
0449 def _mysqlType(self):
0450 type = self._sqlType()
0451 if self.char_binary:
0452 type += " BINARY"
0453 return type
0454
0455 def _postgresType(self):
0456 self._check_case_sensitive("PostgreSQL")
0457 return super(SOStringLikeCol, self)._postgresType()
0458
0459 def _sqliteType(self):
0460 self._check_case_sensitive("SQLite")
0461 return super(SOStringLikeCol, self)._sqliteType()
0462
0463 def _sybaseType(self):
0464 self._check_case_sensitive("SYBASE")
0465 type = self._sqlType()
0466 if not self.notNone and not self.alternateID:
0467 type += ' NULL'
0468 return type
0469
0470 def _mssqlType(self):
0471 if self.customSQLType is not None:
0472 return self.customSQLType
0473 if not self.length:
0474 if self.connection and self.connection.can_use_max_types():
0475 type = 'VARCHAR(MAX)'
0476 else:
0477 type = 'VARCHAR(4000)'
0478 elif self.varchar:
0479 type = 'VARCHAR(%i)' % self.length
0480 else:
0481 type = 'CHAR(%i)' % self.length
0482 if not self.notNone and not self.alternateID:
0483 type += ' NULL'
0484 return type
0485
0486 def _firebirdType(self):
0487 self._check_case_sensitive("FireBird")
0488 if not self.length:
0489 return 'BLOB SUB_TYPE TEXT'
0490 else:
0491 return self._sqlType()
0492
0493 def _maxdbType(self):
0494 self._check_case_sensitive("SAP DB/MaxDB")
0495 if not self.length:
0496 return 'LONG ASCII'
0497 else:
0498 return self._sqlType()
0499
0500
0501class StringValidator(validators.Validator):
0502
0503 def to_python(self, value, state):
0504 if value is None:
0505 return None
0506 try:
0507 connection = state.connection or state.soObject._connection
0508 binaryType = connection._binaryType
0509 except AttributeError:
0510 dbEncoding = "ascii"
0511 binaryType = type(None)
0512 else:
0513 dbEncoding = getattr(connection, "dbEncoding", None) or "ascii"
0514 if isinstance(value, unicode):
0515 return value.encode(dbEncoding)
0516 if self.dataType and isinstance(value, self.dataType):
0517 return value
0518 if isinstance(value, (str, buffer, binaryType, sqlbuilder.SQLExpression)):
0519 return value
0520 if hasattr(value, '__unicode__'):
0521 return unicode(value).encode(dbEncoding)
0522 raise validators.Invalid("expected a str in the StringCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
0524
0525 from_python = to_python
0526
0527class SOStringCol(SOStringLikeCol):
0528
0529 def createValidators(self, dataType=None):
0530 return [StringValidator(name=self.name, dataType=dataType)] + super(SOStringCol, self).createValidators()
0532
0533class StringCol(Col):
0534 baseClass = SOStringCol
0535
0536
0537class NQuoted(sqlbuilder.SQLExpression):
0538 def __init__(self, value):
0539 assert isinstance(value, unicode)
0540 self.value = value
0541 def __hash__(self):
0542 return hash(self.value)
0543 def __sqlrepr__(self, db):
0544 assert db == 'mssql'
0545 return "N" + sqlbuilder.sqlrepr(self.value, db)
0546
0547class UnicodeStringValidator(validators.Validator):
0548
0549 def getDbEncoding(self, state):
0550 try:
0551 return self.dbEncoding
0552 except AttributeError:
0553 return self.soCol.getDbEncoding(state)
0554
0555 def to_python(self, value, state):
0556 if value is None:
0557 return None
0558 if isinstance(value, (unicode, sqlbuilder.SQLExpression)):
0559 return value
0560 if isinstance(value, str):
0561 return unicode(value, self.getDbEncoding(state))
0562 if isinstance(value, array):
0563 return unicode(value.tostring(), self.getDbEncoding(state))
0564 if hasattr(value, '__unicode__'):
0565 return unicode(value)
0566 raise validators.Invalid("expected a str or a unicode in the UnicodeCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
0568
0569 def from_python(self, value, state):
0570 if value is None:
0571 return None
0572 if isinstance(value, (str, sqlbuilder.SQLExpression)):
0573 return value
0574 if isinstance(value, unicode):
0575 try:
0576 connection = state.connection or state.soObject._connection
0577 except AttributeError:
0578 pass
0579 else:
0580 if connection.dbName == 'mssql':
0581 return NQuoted(value)
0582 return value.encode(self.getDbEncoding(state))
0583 if hasattr(value, '__unicode__'):
0584 return unicode(value).encode(self.getDbEncoding(state))
0585 raise validators.Invalid("expected a str or a unicode in the UnicodeCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
0587
0588class SOUnicodeCol(SOStringLikeCol):
0589 def __init__(self, **kw):
0590 self.dbEncoding = kw.pop('dbEncoding', None)
0591 super(SOUnicodeCol, self).__init__(**kw)
0592
0593 def _mssqlType(self):
0594 if self.customSQLType is not None:
0595 return self.customSQLType
0596 return 'N' + super(SOUnicodeCol, self)._mssqlType()
0597
0598 def createValidators(self):
0599 return [UnicodeStringValidator(name=self.name, soCol=self)] + super(SOUnicodeCol, self).createValidators()
0601
0602 def getDbEncoding(self, state):
0603 if self.dbEncoding:
0604 return self.dbEncoding
0605 dbEncoding = state.soObject.sqlmeta.dbEncoding
0606 if dbEncoding:
0607 return dbEncoding
0608 try:
0609 connection = state.connection or state.soObject._connection
0610 except AttributeError:
0611 dbEncoding = None
0612 else:
0613 dbEncoding = getattr(connection, "dbEncoding", None)
0614 if not dbEncoding:
0615 dbEncoding = "utf-8"
0616 return dbEncoding
0617
0618class UnicodeCol(Col):
0619 baseClass = SOUnicodeCol
0620
0621
0622class IntValidator(validators.Validator):
0623
0624 def to_python(self, value, state):
0625 if value is None:
0626 return None
0627 if isinstance(value, (int, long, sqlbuilder.SQLExpression)):
0628 return value
0629 for converter, attr_name in (int, '__int__'), (long, '__long__'):
0630 if hasattr(value, attr_name):
0631 try:
0632 return converter(value)
0633 except:
0634 break
0635 raise validators.Invalid("expected an int in the IntCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
0637
0638 from_python = to_python
0639
0640class SOIntCol(SOCol):
0641
0642 def __init__(self, **kw):
0643 self.length = kw.pop('length', None)
0644 self.unsigned = bool(kw.pop('unsigned', None))
0645 self.zerofill = bool(kw.pop('zerofill', None))
0646 SOCol.__init__(self, **kw)
0647
0648 def autoConstraints(self):
0649 return [constrs.isInt]
0650
0651 def createValidators(self):
0652 return [IntValidator(name=self.name)] + super(SOIntCol, self).createValidators()
0654
0655 def addSQLAttrs(self, str):
0656 _ret = str
0657 if str is None or len(str) < 1:
0658 return None
0659
0660 if self.length >= 1:
0661 _ret = "%s(%d)" % (_ret, self.length)
0662 if self.unsigned:
0663 _ret = _ret + " UNSIGNED"
0664 if self.zerofill:
0665 _ret = _ret + " ZEROFILL"
0666 return _ret
0667
0668 def _sqlType(self):
0669 return self.addSQLAttrs("INT")
0670
0671class IntCol(Col):
0672 baseClass = SOIntCol
0673
0674class SOTinyIntCol(SOIntCol):
0675 def _sqlType(self):
0676 return self.addSQLAttrs("TINYINT")
0677
0678class TinyIntCol(Col):
0679 baseClass = SOTinyIntCol
0680
0681class SOSmallIntCol(SOIntCol):
0682 def _sqlType(self):
0683 return self.addSQLAttrs("SMALLINT")
0684
0685class SmallIntCol(Col):
0686 baseClass = SOSmallIntCol
0687
0688class SOMediumIntCol(SOIntCol):
0689 def _sqlType(self):
0690 return self.addSQLAttrs("MEDIUMINT")
0691
0692class MediumIntCol(Col):
0693 baseClass = SOMediumIntCol
0694
0695class SOBigIntCol(SOIntCol):
0696 def _sqlType(self):
0697 return self.addSQLAttrs("BIGINT")
0698
0699class BigIntCol(Col):
0700 baseClass = SOBigIntCol
0701
0702
0703class BoolValidator(validators.Validator):
0704
0705 def to_python(self, value, state):
0706 if value is None:
0707 return None
0708 if isinstance(value, (bool, sqlbuilder.SQLExpression)):
0709 return value
0710 if isinstance(value, (int, long)) or hasattr(value, '__nonzero__'):
0711 return bool(value)
0712 raise validators.Invalid("expected a bool or an int in the BoolCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
0714
0715 from_python = to_python
0716
0717class SOBoolCol(SOCol):
0718 def autoConstraints(self):
0719 return [constrs.isBool]
0720
0721 def createValidators(self):
0722 return [BoolValidator(name=self.name)] + super(SOBoolCol, self).createValidators()
0724
0725 def _postgresType(self):
0726 return 'BOOL'
0727
0728 def _mysqlType(self):
0729 return "BOOL"
0730
0731 def _sybaseType(self):
0732 return "BIT"
0733
0734 def _mssqlType(self):
0735 return "BIT"
0736
0737 def _firebirdType(self):
0738 return 'INT'
0739
0740 def _maxdbType(self):
0741 return "BOOLEAN"
0742
0743 def _sqliteType(self):
0744 return "BOOLEAN"
0745
0746class BoolCol(Col):
0747 baseClass = SOBoolCol
0748
0749
0750class FloatValidator(validators.Validator):
0751
0752 def to_python(self, value, state):
0753 if value is None:
0754 return None
0755 if isinstance(value, (float, int, long, sqlbuilder.SQLExpression)):
0756 return value
0757 for converter, attr_name in (float, '__float__'), (int, '__int__'), (long, '__long__'):
0758 if hasattr(value, attr_name):
0759 try:
0760 return converter(value)
0761 except:
0762 break
0763 raise validators.Invalid("expected a float in the FloatCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
0765
0766 from_python = to_python
0767
0768class SOFloatCol(SOCol):
0769
0770
0771 def autoConstraints(self):
0772 return [constrs.isFloat]
0773
0774 def createValidators(self):
0775 return [FloatValidator(name=self.name)] + super(SOFloatCol, self).createValidators()
0777
0778 def _sqlType(self):
0779 return 'FLOAT'
0780
0781 def _mysqlType(self):
0782 return "DOUBLE PRECISION"
0783
0784class FloatCol(Col):
0785 baseClass = SOFloatCol
0786
0787
0788class SOKeyCol(SOCol):
0789 key_type = {int: "INT", str: "TEXT"}
0790
0791
0792
0793
0794 def __init__(self, **kw):
0795 self.refColumn = kw.pop('refColumn', None)
0796 super(SOKeyCol, self).__init__(**kw)
0797
0798 def _sqlType(self):
0799 return self.key_type[self.soClass.sqlmeta.idType]
0800
0801 def _sybaseType(self):
0802 key_type = {int: "NUMERIC(18,0) NULL", str: "TEXT"}
0803 return key_type[self.soClass.sqlmeta.idType]
0804
0805 def _mssqlType(self):
0806 key_type = {int: "INT NULL", str: "TEXT"}
0807 return key_type[self.soClass.sqlmeta.idType]
0808
0809class KeyCol(Col):
0810
0811 baseClass = SOKeyCol
0812
0813class SOForeignKey(SOKeyCol):
0814
0815 def __init__(self, **kw):
0816 foreignKey = kw['foreignKey']
0817 style = kw['soClass'].sqlmeta.style
0818 if kw.get('name'):
0819 kw['origName'] = kw['name']
0820 kw['name'] = style.instanceAttrToIDAttr(kw['name'])
0821 else:
0822 kw['name'] = style.instanceAttrToIDAttr(style.pythonClassToAttr(foreignKey))
0823 super(SOForeignKey, self).__init__(**kw)
0824
0825 def sqliteCreateSQL(self):
0826 sql = SOKeyCol.sqliteCreateSQL(self)
0827 other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0828 tName = other.sqlmeta.table
0829 idName = self.refColumn or other.sqlmeta.idName
0830 if self.cascade is not None:
0831 if self.cascade == 'null':
0832 action = 'ON DELETE SET NULL'
0833 elif self.cascade:
0834 action = 'ON DELETE CASCADE'
0835 else:
0836 action = 'ON DELETE RESTRICT'
0837 else:
0838 action = ''
0839 constraint = ('CONSTRAINT %(colName)s_exists '
0840
0841 'REFERENCES %(tName)s(%(idName)s) '
0842 '%(action)s' %
0843 {'tName': tName,
0844 'colName': self.dbName,
0845 'idName': idName,
0846 'action': action})
0847 sql = ' '.join([sql, constraint])
0848 return sql
0849
0850 def postgresCreateSQL(self):
0851 sql = SOKeyCol.postgresCreateSQL(self)
0852 return sql
0853
0854 def postgresCreateReferenceConstraint(self):
0855 sTName = self.soClass.sqlmeta.table
0856 other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0857 tName = other.sqlmeta.table
0858 idName = self.refColumn or other.sqlmeta.idName
0859 if self.cascade is not None:
0860 if self.cascade == 'null':
0861 action = 'ON DELETE SET NULL'
0862 elif self.cascade:
0863 action = 'ON DELETE CASCADE'
0864 else:
0865 action = 'ON DELETE RESTRICT'
0866 else:
0867 action = ''
0868 constraint = ('ALTER TABLE %(sTName)s ADD CONSTRAINT %(colName)s_exists '
0869 'FOREIGN KEY (%(colName)s) '
0870 'REFERENCES %(tName)s (%(idName)s) '
0871 '%(action)s' %
0872 {'tName': tName,
0873 'colName': self.dbName,
0874 'idName': idName,
0875 'action': action,
0876 'sTName': sTName})
0877 return constraint
0878
0879 def mysqlCreateReferenceConstraint(self):
0880 sTName = self.soClass.sqlmeta.table
0881 sTLocalName = sTName.split('.')[-1]
0882 other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0883 tName = other.sqlmeta.table
0884 idName = self.refColumn or other.sqlmeta.idName
0885 if self.cascade is not None:
0886 if self.cascade == 'null':
0887 action = 'ON DELETE SET NULL'
0888 elif self.cascade:
0889 action = 'ON DELETE CASCADE'
0890 else:
0891 action = 'ON DELETE RESTRICT'
0892 else:
0893 action = ''
0894 constraint = ('ALTER TABLE %(sTName)s ADD CONSTRAINT %(sTLocalName)s_%(colName)s_exists '
0895 'FOREIGN KEY (%(colName)s) '
0896 'REFERENCES %(tName)s (%(idName)s) '
0897 '%(action)s' %
0898 {'tName': tName,
0899 'colName': self.dbName,
0900 'idName': idName,
0901 'action': action,
0902 'sTName': sTName,
0903 'sTLocalName': sTLocalName})
0904 return constraint
0905
0906 def mysqlCreateSQL(self):
0907 return SOKeyCol.mysqlCreateSQL(self)
0908
0909 def sybaseCreateSQL(self):
0910 sql = SOKeyCol.sybaseCreateSQL(self)
0911 other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0912 tName = other.sqlmeta.table
0913 idName = self.refColumn or other.sqlmeta.idName
0914 reference = ('REFERENCES %(tName)s(%(idName)s) ' %
0915 {'tName':tName,
0916 'idName':idName})
0917 sql = ' '.join([sql, reference])
0918 return sql
0919
0920 def sybaseCreateReferenceConstraint(self):
0921
0922 return None
0923
0924 def mssqlCreateSQL(self, connection=None):
0925 sql = SOKeyCol.mssqlCreateSQL(self, connection)
0926 other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0927 tName = other.sqlmeta.table
0928 idName = self.refColumn or other.sqlmeta.idName
0929 reference = ('REFERENCES %(tName)s(%(idName)s) ' %
0930 {'tName':tName,
0931 'idName':idName})
0932 sql = ' '.join([sql, reference])
0933 return sql
0934
0935 def mssqlCreateReferenceConstraint(self):
0936
0937 return None
0938
0939 def maxdbCreateSQL(self):
0940 other = findClass(self.foreignKey, self.soClass.sqlmeta.registry)
0941 fidName = self.dbName
0942
0943 sql = ' '.join([fidName, self._maxdbType()])
0944 tName = other.sqlmeta.table
0945 idName = self.refColumn or other.sqlmeta.idName
0946 sql=sql + ',' + '\n'
0947 sql=sql + 'FOREIGN KEY (%s) REFERENCES %s(%s)'%(fidName,tName,idName)
0948 return sql
0949
0950 def maxdbCreateReferenceConstraint(self):
0951
0952 return None
0953
0954class ForeignKey(KeyCol):
0955
0956 baseClass = SOForeignKey
0957
0958 def __init__(self, foreignKey=None, **kw):
0959 super(ForeignKey, self).__init__(foreignKey=foreignKey, **kw)
0960
0961
0962class EnumValidator(validators.Validator):
0963
0964 def to_python(self, value, state):
0965 if value in self.enumValues:
0966 return value
0967 elif not self.notNone and value is None:
0968 return None
0969 raise validators.Invalid("expected a member of %r in the EnumCol '%s', got %r instead" % (self.enumValues, self.name, value), value, state)
0971
0972 from_python = to_python
0973
0974class SOEnumCol(SOCol):
0975
0976 def __init__(self, **kw):
0977 self.enumValues = kw.pop('enumValues', None)
0978 assert self.enumValues is not None, 'You must provide an enumValues keyword argument'
0980 super(SOEnumCol, self).__init__(**kw)
0981
0982 def autoConstraints(self):
0983 return [constrs.isString, constrs.InList(self.enumValues)]
0984
0985 def createValidators(self):
0986 return [EnumValidator(name=self.name, enumValues=self.enumValues,
0987 notNone=self.notNone)] + super(SOEnumCol, self).createValidators()
0989
0990 def _mysqlType(self):
0991
0992
0993 if None in self.enumValues:
0994 return "ENUM(%s)" % ', '.join([sqlbuilder.sqlrepr(v, 'mysql') for v in self.enumValues if v is not None])
0995 else:
0996 return "ENUM(%s) NOT NULL" % ', '.join([sqlbuilder.sqlrepr(v, 'mysql') for v in self.enumValues])
0997
0998 def _postgresType(self):
0999 length = max(map(self._getlength, self.enumValues))
1000 enumValues = ', '.join([sqlbuilder.sqlrepr(v, 'postgres') for v in self.enumValues])
1001 checkConstraint = "CHECK (%s in (%s))" % (self.dbName, enumValues)
1002 return "VARCHAR(%i) %s" % (length, checkConstraint)
1003
1004 _sqliteType = _postgresType
1005
1006 def _sybaseType(self):
1007 return self._postgresType()
1008
1009 def _mssqlType(self):
1010 return self._postgresType()
1011
1012 def _firebirdType(self):
1013 length = max(map(self._getlength, self.enumValues))
1014 enumValues = ', '.join([sqlbuilder.sqlrepr(v, 'firebird') for v in self.enumValues])
1015 checkConstraint = "CHECK (%s in (%s))" % (self.dbName, enumValues)
1016
1017 return "VARCHAR(%i)" % (length), checkConstraint
1018
1019 def _maxdbType(self):
1020 raise TypeError("Enum type is not supported on MAX DB")
1021
1022 def _getlength(self, obj):
1023 """
1024 None counts as 0; everything else uses len()
1025 """
1026 if obj is None:
1027 return 0
1028 else:
1029 return len(obj)
1030
1031class EnumCol(Col):
1032 baseClass = SOEnumCol
1033
1034
1035class SetValidator(validators.Validator):
1036 """
1037 Translates Python tuples into SQL comma-delimited SET strings.
1038 """
1039
1040 def to_python(self, value, state):
1041 if isinstance(value, str):
1042 return tuple(value.split(","))
1043 raise validators.Invalid("expected a string in the SetCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
1045
1046 def from_python(self, value, state):
1047 if isinstance(value, basestring):
1048 value = (value,)
1049 try:
1050 return ",".join(value)
1051 except:
1052 raise validators.Invalid("expected a string or a sequence of stringsin the SetCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
1054
1055class SOSetCol(SOCol):
1056 def __init__(self, **kw):
1057 self.setValues = kw.pop('setValues', None)
1058 assert self.setValues is not None, 'You must provide a setValues keyword argument'
1060 super(SOSetCol, self).__init__(**kw)
1061
1062 def autoConstraints(self):
1063 return [constrs.isString, constrs.InList(self.setValues)]
1064
1065 def createValidators(self):
1066 return [SetValidator(name=self.name, setValues=self.setValues)] + super(SOSetCol, self).createValidators()
1068
1069 def _mysqlType(self):
1070 return "SET(%s)" % ', '.join([sqlbuilder.sqlrepr(v, 'mysql') for v in self.setValues])
1071
1072class SetCol(Col):
1073 baseClass = SOSetCol
1074
1075
1076class DateTimeValidator(validators.DateValidator):
1077 def to_python(self, value, state):
1078 if value is None:
1079 return None
1080 if isinstance(value, (datetime.datetime, datetime.date, datetime.time, sqlbuilder.SQLExpression)):
1081 return value
1082 if mxdatetime_available:
1083 if isinstance(value, DateTimeType):
1084
1085 if (self.format.find("%H") >= 0) or (self.format.find("%T")) >= 0:
1086 return datetime.datetime(value.year, value.month, value.day,
1087 value.hour, value.minute, int(value.second))
1088 else:
1089 return datetime.date(value.year, value.month, value.day)
1090 elif isinstance(value, TimeType):
1091
1092 if self.format.find("%d") >= 0:
1093 return datetime.timedelta(seconds=value.seconds)
1094 else:
1095 return datetime.time(value.hour, value.minute, int(value.second))
1096 try:
1097 stime = time.strptime(value, self.format)
1098 except:
1099 raise validators.Invalid("expected a date/time string of the '%s' format in the DateTimeCol '%s', got %s %r instead" % (self.format, self.name, type(value), value), value, state)
1101 return datetime.datetime(*stime[:6])
1102
1103 def from_python(self, value, state):
1104 if value is None:
1105 return None
1106 if isinstance(value, (datetime.datetime, datetime.date, datetime.time, sqlbuilder.SQLExpression)):
1107 return value
1108 if hasattr(value, "strftime"):
1109 return value.strftime(self.format)
1110 raise validators.Invalid("expected a datetime in the DateTimeCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
1112
1113if mxdatetime_available:
1114 class MXDateTimeValidator(validators.DateValidator):
1115 def to_python(self, value, state):
1116 if value is None:
1117 return None
1118 if isinstance(value, (DateTimeType, TimeType, sqlbuilder.SQLExpression)):
1119 return value
1120 if isinstance(value, datetime.datetime):
1121 return DateTime.DateTime(value.year, value.month, value.day,
1122 value.hour, value.minute, value.second)
1123 elif isinstance(value, datetime.date):
1124 return DateTime.Date(value.year, value.month, value.day)
1125 elif isinstance(value, datetime.time):
1126 return DateTime.Time(value.hour, value.minute, value.second)
1127 try:
1128 stime = time.strptime(value, self.format)
1129 except:
1130 raise validators.Invalid("expected a date/time string of the '%s' format in the DateTimeCol '%s', got %s %r instead" % (self.format, self.name, type(value), value), value, state)
1132 return DateTime.mktime(stime)
1133
1134 def from_python(self, value, state):
1135 if value is None:
1136 return None
1137 if isinstance(value, (DateTimeType, TimeType, sqlbuilder.SQLExpression)):
1138 return value
1139 if hasattr(value, "strftime"):
1140 return value.strftime(self.format)
1141 raise validators.Invalid("expected a mxDateTime in the DateTimeCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
1143
1144class SODateTimeCol(SOCol):
1145 datetimeFormat = '%Y-%m-%d %H:%M:%S'
1146
1147 def __init__(self, **kw):
1148 datetimeFormat = kw.pop('datetimeFormat', None)
1149 if datetimeFormat:
1150 self.datetimeFormat = datetimeFormat
1151 super(SODateTimeCol, self).__init__(**kw)
1152
1153 def createValidators(self):
1154 _validators = super(SODateTimeCol, self).createValidators()
1155 if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1156 validatorClass = DateTimeValidator
1157 elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1158 validatorClass = MXDateTimeValidator
1159 if default_datetime_implementation:
1160 _validators.insert(0, validatorClass(name=self.name, format=self.datetimeFormat))
1161 return _validators
1162
1163 def _mysqlType(self):
1164 return 'DATETIME'
1165
1166 def _postgresType(self):
1167 return 'TIMESTAMP'
1168
1169 def _sybaseType(self):
1170 return 'DATETIME'
1171
1172 def _mssqlType(self):
1173 return 'DATETIME'
1174
1175 def _sqliteType(self):
1176 return 'TIMESTAMP'
1177
1178 def _firebirdType(self):
1179 return 'TIMESTAMP'
1180
1181 def _maxdbType(self):
1182 return 'TIMESTAMP'
1183
1184class DateTimeCol(Col):
1185 baseClass = SODateTimeCol
1186 @staticmethod
1187 def now():
1188 if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1189 return datetime.datetime.now()
1190 elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1191 return DateTime.now()
1192 else:
1193 assert 0, ("No datetime implementation available "
1194 "(DATETIME_IMPLEMENTATION=%r)"
1195 % DATETIME_IMPLEMENTATION)
1196
1197
1198class DateValidator(DateTimeValidator):
1199 def to_python(self, value, state):
1200 if isinstance(value, datetime.datetime):
1201 value = value.date()
1202 if isinstance(value, (datetime.date, sqlbuilder.SQLExpression)):
1203 return value
1204 value = super(DateValidator, self).to_python(value, state)
1205 if isinstance(value, datetime.datetime):
1206 value = value.date()
1207 return value
1208
1209 from_python = to_python
1210
1211class SODateCol(SOCol):
1212 dateFormat = '%Y-%m-%d'
1213
1214 def __init__(self, **kw):
1215 dateFormat = kw.pop('dateFormat', None)
1216 if dateFormat: self.dateFormat = dateFormat
1217 super(SODateCol, self).__init__(**kw)
1218
1219 def createValidators(self):
1220 """Create a validator for the column. Can be overriden in descendants."""
1221 _validators = super(SODateCol, self).createValidators()
1222 if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1223 validatorClass = DateValidator
1224 elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1225 validatorClass = MXDateTimeValidator
1226 if default_datetime_implementation:
1227 _validators.insert(0, validatorClass(name=self.name, format=self.dateFormat))
1228 return _validators
1229
1230 def _mysqlType(self):
1231 return 'DATE'
1232
1233 def _postgresType(self):
1234 return 'DATE'
1235
1236 def _sybaseType(self):
1237 return self._postgresType()
1238
1239 def _mssqlType(self):
1240 """
1241 SQL Server doesn't have a DATE data type, to emulate we use a vc(10)
1242 """
1243 return 'VARCHAR(10)'
1244
1245 def _firebirdType(self):
1246 return 'DATE'
1247
1248 def _maxdbType(self):
1249 return 'DATE'
1250
1251 def _sqliteType(self):
1252 return 'DATE'
1253
1254class DateCol(Col):
1255 baseClass = SODateCol
1256
1257
1258class TimeValidator(DateTimeValidator):
1259 def to_python(self, value, state):
1260 if isinstance(value, (datetime.time, sqlbuilder.SQLExpression)):
1261 return value
1262 if isinstance(value, datetime.timedelta):
1263 if value.days:
1264 raise validators.Invalid(
1265 "the value for the TimeCol '%s' must has days=0, it has days=%d" %
1266 (self.name, value.days), value, state)
1267 return datetime.time(*time.gmtime(value.seconds)[3:6])
1268 value = super(TimeValidator, self).to_python(value, state)
1269 if isinstance(value, datetime.datetime):
1270 value = value.time()
1271 return value
1272
1273 from_python = to_python
1274
1275class SOTimeCol(SOCol):
1276 timeFormat = '%H:%M:%S'
1277
1278 def __init__(self, **kw):
1279 timeFormat = kw.pop('timeFormat', None)
1280 if timeFormat:
1281 self.timeFormat = timeFormat
1282 super(SOTimeCol, self).__init__(**kw)
1283
1284 def createValidators(self):
1285 _validators = super(SOTimeCol, self).createValidators()
1286 if default_datetime_implementation == DATETIME_IMPLEMENTATION:
1287 validatorClass = TimeValidator
1288 elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION:
1289 validatorClass = MXDateTimeValidator
1290 if default_datetime_implementation:
1291 _validators.insert(0, validatorClass(name=self.name, format=self.timeFormat))
1292 return _validators
1293
1294 def _mysqlType(self):
1295 return 'TIME'
1296
1297 def _postgresType(self):
1298 return 'TIME'
1299
1300 def _sybaseType(self):
1301 return 'TIME'
1302
1303 def _sqliteType(self):
1304 return 'TIME'
1305
1306 def _firebirdType(self):
1307 return 'TIME'
1308
1309 def _maxdbType(self):
1310 return 'TIME'
1311
1312class TimeCol(Col):
1313 baseClass = SOTimeCol
1314
1315
1316class SOTimestampCol(SODateTimeCol):
1317 """
1318 Necessary to support MySQL's use of TIMESTAMP versus DATETIME types
1319 """
1320
1321 def __init__(self, **kw):
1322 if 'default' not in kw:
1323 kw['default'] = None
1324 SOCol.__init__(self, **kw)
1325
1326 def _mysqlType(self):
1327 return 'TIMESTAMP'
1328
1329class TimestampCol(Col):
1330 baseClass = SOTimestampCol
1331
1332
1333class TimedeltaValidator(validators.Validator):
1334 def to_python(self, value, state):
1335 return value
1336
1337 from_python = to_python
1338
1339class SOTimedeltaCol(SOCol):
1340 def _postgresType(self):
1341 return 'INTERVAL'
1342
1343 def createValidators(self):
1344 return [TimedeltaValidator(name=self.name)] + super(SOTimedeltaCol, self).createValidators()
1346
1347class TimedeltaCol(Col):
1348 baseClass = SOTimedeltaCol
1349
1350
1351from decimal import Decimal
1352
1353class DecimalValidator(validators.Validator):
1354 def to_python(self, value, state):
1355 if value is None:
1356 return None
1357 if isinstance(value, (int, long, Decimal, sqlbuilder.SQLExpression)):
1358 return value
1359 if isinstance(value, float):
1360 value = str(value)
1361 try:
1362 connection = state.connection or state.soObject._connection
1363 except AttributeError:
1364 pass
1365 else:
1366 if hasattr(connection, "decimalSeparator"):
1367 value = value.replace(connection.decimalSeparator, ".")
1368 try:
1369 return Decimal(value)
1370 except:
1371 raise validators.Invalid("expected a Decimal in the DecimalCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
1373
1374 def from_python(self, value, state):
1375 if value is None:
1376 return None
1377 if isinstance(value, float):
1378 value = str(value)
1379 if isinstance(value, basestring):
1380 try:
1381 connection = state.connection or state.soObject._connection
1382 except AttributeError:
1383 pass
1384 else:
1385 if hasattr(connection, "decimalSeparator"):
1386 value = value.replace(connection.decimalSeparator, ".")
1387 try:
1388 return Decimal(value)
1389 except:
1390 raise validators.Invalid("can not parse Decimal value '%s' in the DecimalCol from '%s'" %
1391 (value, getattr(state, 'soObject', '(unknown)')), value, state)
1392 if isinstance(value, (int, long, Decimal, sqlbuilder.SQLExpression)):
1393 return value
1394 raise validators.Invalid("expected a Decimal in the DecimalCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
1396
1397class SODecimalCol(SOCol):
1398
1399 def __init__(self, **kw):
1400 self.size = kw.pop('size', NoDefault)
1401 assert self.size is not NoDefault, "You must give a size argument"
1403 self.precision = kw.pop('precision', NoDefault)
1404 assert self.precision is not NoDefault, "You must give a precision argument"
1406 super(SODecimalCol, self).__init__(**kw)
1407
1408 def _sqlType(self):
1409 return 'DECIMAL(%i, %i)' % (self.size, self.precision)
1410
1411 def createValidators(self):
1412 return [DecimalValidator(name=self.name)] + super(SODecimalCol, self).createValidators()
1414
1415class DecimalCol(Col):
1416 baseClass = SODecimalCol
1417
1418class SOCurrencyCol(SODecimalCol):
1419
1420 def __init__(self, **kw):
1421 pushKey(kw, 'size', 10)
1422 pushKey(kw, 'precision', 2)
1423 super(SOCurrencyCol, self).__init__(**kw)
1424
1425class CurrencyCol(DecimalCol):
1426 baseClass = SOCurrencyCol
1427
1428
1429class DecimalStringValidator(DecimalValidator):
1430 def to_python(self, value, state):
1431 value = super(DecimalStringValidator, self).to_python(value, state)
1432 if self.precision and isinstance(value, Decimal):
1433 assert value < self.max, "Value must be less than %s" % int(self.max)
1435 value = value.quantize(self.precision)
1436 return value
1437
1438 def from_python(self, value, state):
1439 value = super(DecimalStringValidator, self).from_python(value, state)
1440 if isinstance(value, Decimal):
1441 if self.precision:
1442 assert value < self.max, "Value must be less than %s" % int(self.max)
1444 value = value.quantize(self.precision)
1445 value = value.to_eng_string()
1446 elif isinstance(value, (int, long)):
1447 value = str(value)
1448 return value
1449
1450class SODecimalStringCol(SOStringCol):
1451 def __init__(self, **kw):
1452 self.size = kw.pop('size', NoDefault)
1453 assert (self.size is not NoDefault) and (self.size >= 0), "You must give a size argument as a positive integer"
1455 self.precision = kw.pop('precision', NoDefault)
1456 assert (self.precision is not NoDefault) and (self.precision >= 0), "You must give a precision argument as a positive integer"
1458 kw['length'] = int(self.size) + int(self.precision)
1459 self.quantize = kw.pop('quantize', False)
1460 assert isinstance(self.quantize, bool), "quantize argument must be Boolean True/False"
1462 super(SODecimalStringCol, self).__init__(**kw)
1463
1464 def createValidators(self):
1465 if self.quantize:
1466 v = DecimalStringValidator(name=self.name,
1467 precision=Decimal(10) ** (-1 * int(self.precision)),
1468 max=Decimal(10) ** (int(self.size) - int(self.precision)))
1469 else:
1470 v = DecimalStringValidator(name=self.name, precision=0)
1471 return [v] + super(SODecimalStringCol, self).createValidators(dataType=Decimal)
1473
1474class DecimalStringCol(StringCol):
1475 baseClass = SODecimalStringCol
1476
1477
1478class BinaryValidator(validators.Validator):
1479 """
1480 Validator for binary types.
1481
1482 We're assuming that the per-database modules provide some form
1483 of wrapper type for binary conversion.
1484 """
1485
1486 _cachedValue = None
1487
1488 def to_python(self, value, state):
1489 if value is None:
1490 return None
1491 try:
1492 connection = state.connection or state.soObject._connection
1493 except AttributeError:
1494 dbName = None
1495 binaryType = type(None)
1496 else:
1497 dbName = connection.dbName
1498 binaryType = connection._binaryType
1499 if isinstance(value, str):
1500 if dbName == "sqlite":
1501 value = connection.module.decode(value)
1502 return value
1503 if isinstance(value, (buffer, binaryType)):
1504 cachedValue = self._cachedValue
1505 if cachedValue and cachedValue[1] == value:
1506 return cachedValue[0]
1507 if isinstance(value, array):
1508 return value.tostring()
1509 return str(value)
1510 raise validators.Invalid("expected a string in the BLOBCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
1512
1513 def from_python(self, value, state):
1514 if value is None:
1515 return None
1516 connection = state.connection or state.soObject._connection
1517 binary = connection.createBinary(value)
1518 self._cachedValue = (value, binary)
1519 return binary
1520
1521class SOBLOBCol(SOStringCol):
1522 def __init__(self, **kw):
1523
1524 if 'varchar' not in kw: kw['varchar'] = False
1525 super(SOBLOBCol, self).__init__(**kw)
1526
1527 def createValidators(self):
1528 return [BinaryValidator(name=self.name)] + super(SOBLOBCol, self).createValidators()
1530
1531 def _mysqlType(self):
1532 length = self.length
1533 varchar = self.varchar
1534 if length >= 2**24:
1535 return varchar and "LONGTEXT" or "LONGBLOB"
1536 if length >= 2**16:
1537 return varchar and "MEDIUMTEXT" or "MEDIUMBLOB"
1538 if length >= 2**8:
1539 return varchar and "TEXT" or "BLOB"
1540 return varchar and "TINYTEXT" or "TINYBLOB"
1541
1542 def _postgresType(self):
1543 return 'BYTEA'
1544
1545 def _mssqlType(self):
1546 if self.connection and self.connection.can_use_max_types():
1547 return 'VARBINARY(MAX)'
1548 else:
1549 return "IMAGE"
1550
1551class BLOBCol(StringCol):
1552 baseClass = SOBLOBCol
1553
1554
1555class PickleValidator(BinaryValidator):
1556 """
1557 Validator for pickle types. A pickle type is simply a binary type
1558 with hidden pickling, so that we can simply store any kind of
1559 stuff in a particular column.
1560
1561 The support for this relies directly on the support for binary for
1562 your database.
1563 """
1564
1565 def to_python(self, value, state):
1566 if value is None:
1567 return None
1568 if isinstance(value, unicode):
1569 try:
1570 connection = state.connection or state.soObject._connection
1571 except AttributeError:
1572 dbEncoding = "ascii"
1573 else:
1574 dbEncoding = getattr(connection, "dbEncoding", None) or "ascii"
1575 value = value.encode(dbEncoding)
1576 if isinstance(value, str):
1577 return pickle.loads(value)
1578 raise validators.Invalid("expected a pickle string in the PickleCol '%s', got %s %r instead" % (self.name, type(value), value), value, state)
1580
1581 def from_python(self, value, state):
1582 if value is None:
1583 return None
1584 return pickle.dumps(value, self.pickleProtocol)
1585
1586class SOPickleCol(SOBLOBCol):
1587
1588 def __init__(self, **kw):
1589 self.pickleProtocol = kw.pop('pickleProtocol', pickle.HIGHEST_PROTOCOL)
1590 super(SOPickleCol, self).__init__(**kw)
1591
1592 def createValidators(self):
1593 return [PickleValidator(name=self.name,
1594 pickleProtocol=self.pickleProtocol)] + super(SOPickleCol, self).createValidators()
1596
1597 def _mysqlType(self):
1598 length = self.length
1599 if length >= 2**24:
1600 return "LONGBLOB"
1601 if length >= 2**16:
1602 return "MEDIUMBLOB"
1603 return "BLOB"
1604
1605class PickleCol(BLOBCol):
1606 baseClass = SOPickleCol
1607
1608
1609def pushKey(kw, name, value):
1610 if not name in kw:
1611 kw[name] = value
1612
1613all = []
1614for key, value in globals().items():
1615 if isinstance(value, type) and (issubclass(value, (Col, SOCol))):
1616 all.append(key)
1617__all__.extend(all)
1618del all