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