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