0001from metasqlobject.declarative import Declarative, counter
0002from metasqlobject.classinst import classinstancemethod
0003from sqlapi import sql
0004from sqlapi import exceptions
0005from classreg import class_registry
0006import events
0007
0008class Col(object):
0009
0010 _standard_sql_type = None
0011
0012
0013
0014 sql_type = None
0015 sql_constraints = None
0016 not_null = False
0017 auto_increment = False
0018 primary_key = False
0019
0020 get = None
0021 set = None
0022
0023 db_name = None
0024 name = None
0025 soclass = None
0026
0027 def __init__(self, soclass=None, name=None,
0028 copyfrom=None, **kw):
0029 if copyfrom is not None:
0030 for k, v in copyfrom.__dict__.items():
0031 if not k.startswith('_'):
0032 setattr(self, k, v)
0033 self.declarative_count = counter.next()
0034 if name is not None:
0035 self.name = name
0036 for k, v in kw.items():
0037 setattr(self, k, v)
0038 if soclass is not None:
0039 self.soclass = soclass
0040 self.sqlexpr = sql.Column(
0041 sql.Table(soclass.sqlmeta.table),
0042 self.db_name or self.name)
0043
0044
0045 def __repr__(self):
0046 s = '<%s object %s' % (
0047 self.__class__.__name__, self.declarative_count)
0048 if self.soclass:
0049 s += ' for %s.%s' % (
0050 self.soclass.__module__,
0051 self.soclass.__name__)
0052 if self.name:
0053 s += ' name=%r' % self.name
0054 else:
0055 s += ' unnamed'
0056 if self.db_name:
0057 s += ' db_name=%r' % self.db_name
0058 s += '>'
0059 return s
0060
0061 def sql_type_parameters(self):
0062 cur = {}
0063 if self.not_null:
0064 cur['not_null'] = True
0065 if self.auto_increment:
0066 cur['auto_increment'] = True
0067 if self.primary_key:
0068 cur['primary_key'] = True
0069 return cur
0070
0071 def standard_sql_type(self, ops):
0072 return self._standard_sql_type
0073
0074 def __addtoclass__(self, cls, soclass, name):
0075 if self is None:
0076 col = cls(soclass=soclass, name=name)
0077 else:
0078 col = self.__class__(soclass=soclass, name=name,
0079 copyfrom=self)
0080 setattr(soclass, name, col)
0081 soclass.sqlmeta.add_column(col)
0082 soclass.__addtosubclass__.append(name)
0083
0084 __addtoclass__ = classinstancemethod(__addtoclass__)
0085
0086 def __set__(self, obj, value):
0087 if self.set is not None:
0088 self.set(obj, value, self.raw_set)
0089 else:
0090 self.raw_set(obj, value)
0091
0092 def raw_set(self, obj, value):
0093 from_python = self.from_python
0094 to_python = self.to_python
0095 if from_python is not Col.from_python:
0096 db_value = from_python(value)
0097 else:
0098 db_value = value
0099 if to_python is not Col.to_python:
0100 value = to_python(db_value)
0101 obj.sqlmeta._db_values[self.name] = db_value
0102 obj.sqlmeta._python_values[self.name] = value
0103 obj.sqlmeta._dirty_columns.append(self.name)
0104 if not obj.sqlmeta.lazy:
0105 obj.sqlmeta.write_updates()
0106
0107 def __get__(self, obj, type=None):
0108 if obj is None:
0109 return self.sqlexpr
0110 if self.get is not None:
0111 return self.get(obj, self.raw_get)
0112 else:
0113 return self.raw_get(obj)
0114
0115 def raw_get(self, obj):
0116 return obj.sqlmeta._python_values[self.name]
0117
0118 def to_python(self, value, state=None):
0119 return value
0120
0121 def from_python(self, value, state=None):
0122 return value
0123
0124 def create_sql(self, conn):
0125 ops = self.sql_type_parameters()
0126 return sql.ColumnDefinition(
0127 self.db_name or self.name,
0128 self.sql_type or self.standard_sql_type(ops),
0129 **ops), []
0130
0131class StringCol(Col):
0132
0133 _standard_sql_type = 'TEXT'
0134 length = None
0135
0136 def sql_type_parameters(self):
0137 cur = Col.sql_type_parameters(self)
0138 if self.length is not None:
0139 cur['length'] = self.length
0140 return cur
0141
0142 def standard_sql_type(self, ops):
0143 if 'length' in ops:
0144 return 'VARCHAR(%i)' % ops['length']
0145 else:
0146 return 'TEXT'
0147
0148class IntCol(Col):
0149
0150 _standard_sql_type = 'INT'
0151
0152
0153KeyCol = IntCol
0154
0155class FloatCol(Col):
0156
0157 _standard_sql_type = 'FLOAT'
0158
0159class DateCol(Col):
0160
0161 _standard_sql_type = 'DATE'
0162
0163class DateTimeCol(Col):
0164
0165 _standard_sql_type = 'TIMESTAMP'
0166
0167class BoolCol(Col):
0168
0169 _standard_sql_type = 'BOOLEAN'
0170
0171 def from_python(self, value, state=None):
0172 return bool(value)
0173
0174
0175
0176class ForeignKey(object):
0177
0178 cascade = None
0179
0180 def __init__(self, foreign_class=None,
0181 soclass=None, name=None, copyfrom=None,
0182 cascade=None, **args):
0183 if copyfrom is not None:
0184 self.__dict__.update(copyfrom.__dict__)
0185 if foreign_class is not None:
0186 self.foreign_class = foreign_class
0187 self.soclass = soclass
0188 self.name = name
0189 if cascade is not None:
0190 self.cascade = cascade
0191 if name:
0192 self.column_name = name + '_id'
0193 if getattr(self, 'column_args', None):
0194 self.column_args.update(args)
0195 else:
0196 self.column_args = args
0197 else:
0198 self.column_args = args
0199 self.declarative_count = counter.next()
0200
0201 def create_column(self):
0202 return IntCol(soclass=self.soclass, name=self.column_name,
0203 **self.column_args)
0204
0205 def create_reference(self):
0206 return Reference(self.foreign_class, self.column_name,
0207 soclass=self.soclass, name=self.name,
0208 cascade=self.cascade)
0209
0210 def __addtoclass__(self, cls, soclass, name):
0211 if self is None:
0212 me = cls(soclass=soclass, name=name)
0213 else:
0214 me = self.__class__(soclass=soclass, name=name,
0215 copyfrom=self)
0216
0217 ref = me.create_reference()
0218 col = me.create_column()
0219 setattr(soclass, col.name, col)
0220 col.__addtoclass__(soclass, col.name)
0221 setattr(soclass, ref.name, ref)
0222 ref.__addtoclass__(soclass, ref.name)
0223
0224 __addtoclass__ = classinstancemethod(__addtoclass__)
0225
0226class Reference(object):
0227
0228 get = None
0229 set = None
0230 cascade = None
0231
0232 def __init__(self, foreign_class=None, column_name=None,
0233 soclass=None, name=None, copyfrom=None,
0234 cascade=None,
0235 **args):
0236 if copyfrom is not None:
0237 self.__dict__.update(copyfrom.__dict__)
0238 self.declarative_count = counter.next()
0239 if foreign_class is not None:
0240 self.foreign_class = foreign_class
0241 if column_name is not None:
0242 self.column_name = column_name
0243 if soclass is not None:
0244 self.soclass = soclass
0245 self.relative_module = self.soclass.__module__
0246 if name is not None:
0247 self.name = name
0248 if cascade is not None:
0249 assert cascade in (True, False, 'null')
0250 self.cascade = cascade
0251 if self.cascade is not None and self.soclass is not None:
0252 class_registry.register_callback(
0253 self.set_cascade,
0254 self.foreign_class, soclass.__module__)
0255
0256 def __repr__(self):
0257 if self.soclass:
0258 soclass = self.soclass.__name__
0259 else:
0260 soclass = '(no class set)'
0261 if self.column_name:
0262 soclass += '.'+self.column_name
0263 if self.foreign_class:
0264 foreign_class = self.foreign_class
0265 else:
0266 foreign_class = '(no dest set)'
0267 return '<%s %i for %s->%s as %s>' % (
0268 self.__class__.__name__,
0269 self.declarative_count,
0270 soclass, foreign_class,
0271 self.name or '(no attribute set)')
0272
0273 def set_cascade(self, joined_class):
0274 joined_class.event_hub.listen(
0275 events.RowDestroySignal, self, 'joined_destroyed')
0276 joined_class.event_hub.listen(
0277 events.DropTableSignal, self, 'joined_drop_table')
0278
0279 def joined_destroyed(self, instance, post_funcs):
0280 current = self.soclass.select_by(
0281 **{self.column_name: instance.id})
0282 current = list(current)
0283 if (self.cascade is not None and not self.cascade
0284 and current):
0285 raise exceptions.IntegrityError(
0286 "Cannot delete %r; instances depend on it: %r"
0287 % (instance, current))
0288 def do_cascade(soclass, del_id):
0289 for item in current:
0290 if self.cascade == 'null':
0291 setattr(item, self.column_name, None)
0292 else:
0293 item.destroy_self()
0294 post_funcs.append(do_cascade)
0295
0296 def joined_drop_table(self, soclass, connection,
0297 cascade, post_funcs):
0298 if cascade and self.soclass.sqlmeta.table_exists(connection):
0299 self.soclass.sqlmeta.drop_table(cascade=cascade)
0300
0301 def __get__(self, obj, type=None):
0302 if obj is None:
0303 return SQLForeignKeyExpr(self)
0304 if self.get is not None:
0305 return self.get(obj, self.raw_get)
0306 else:
0307 return self.raw_get(obj)
0308
0309 def raw_get(self, obj):
0310 try:
0311 key = obj.sqlmeta._python_values[self.column_name]
0312 except KeyError:
0313 raise AttributeError(
0314 "Cannot retrieve .%s until .%s is set"
0315 % (self.name, self.column_name))
0316 if key is None:
0317 return None
0318 return class_registry.get_class(
0319 self.foreign_class, self.relative_module).get(key)
0320
0321 def __set__(self, obj, value):
0322 if self.set is not None:
0323 self.set(obj, value, self.raw_set)
0324 else:
0325 self.raw_set(obj, value)
0326
0327 def raw_set(self, obj, value):
0328 if isinstance(value, (int, long, str)):
0329 raise ValueError(
0330 "You can only set a reference to an object with an "
0331 ".id attribute, not the actual id (got: %r)"
0332 % value)
0333 key = value.id
0334 setattr(obj, self.column_name, key)
0335
0336 def __addtoclass__(self, cls, soclass, name):
0337 if self is None:
0338 me = cls(soclass=soclass, name=name)
0339 else:
0340 me = self.__class__(soclass=soclass, name=name,
0341 copyfrom=self)
0342 setattr(soclass, name, me)
0343
0344 __addtoclass__ = classinstancemethod(__addtoclass__)
0345
0346class Compound(object):
0347
0348 def __init__(self, *columns, **kw):
0349 if 'copyfrom' in kw:
0350 self.__dict__.update(kw['copyfrom'].__dict__)
0351 del kw['copyfrom']
0352 actual_columns = []
0353 for name in columns:
0354 if isinstance(name, (list, tuple)):
0355 actual_columns.extend(name)
0356 else:
0357 actual_columns.append(name)
0358 self.columns = tuple(actual_columns)
0359 for name, value in kw.items():
0360 setattr(self, name, value)
0361
0362 def __addtoclass__(self, cls, soclass, name):
0363 if self is None:
0364 me = cls(soclass=soclass, name=name)
0365 else:
0366 me = self.__class__(soclass=soclass, name=name,
0367 copyfrom=self)
0368 setattr(soclass, name, me)
0369
0370 __addtoclass__ = classinstancemethod(__addtoclass__)
0371
0372 def __get__(self, obj, type=None):
0373 if obj is None:
0374 return sql.CompoundExpression(
0375 *[getattr(type, name) for name in self.columns])
0376 return tuple(
0377 [getattr(obj, name) for name in self.columns])
0378
0379 def __set__(self, obj, value):
0380 if len(value) != len(self.columns):
0381 raise ValueError(
0382 "You must pass a tuple of length %i to %r (you gave: "
0383 "%r)" % (
0384 len(self.columns), self, value))
0385 obj.set(**dict(zip(self.columns, value)))
0386
0387class SQLForeignKeyExpr(object):
0388
0389 def __init__(self, reference):
0390 self.reference = reference
0391
0392 def __repr__(self):
0393 return '<%s %s>' % (
0394 self.__class__.__name__, sql.sqlrepr(self))
0395
0396 def __sql_components__(self, dbsql):
0397 return [getattr(self.reference.soclass, self.reference.column_name)]
0398
0399 def __eq__(self, other):
0400 if other is None:
0401 return sql.SQLPostfixOp(self, 'IS NULL')
0402 else:
0403 return sql.SQLOp(self, '=', other.id)
0404
0405 def __ne__(self, other):
0406 if other is None:
0407 return sql.SQLPostfixOp(self, 'IS NOT NULL')
0408 else:
0409 return sql.SQLOp(self, '<>', other.id)
0410
0411
0412 def __and__(self, other):
0413 return SQLOp(self, 'AND', other)
0414 def __rand__(self, other):
0415 return SQLOp(other, 'AND', self)
0416 def __or__(self, other):
0417 return SQLOp(self, 'OR', other)
0418 def __ror__(self, other):
0419 return SQLOp(other, 'OR', self)
0420 def __invert__(self):
0421 return SQLUnaryOp('NOT', self)
0422
0423__all__ = ['ForeignKey']
0424for name, value in globals().items():
0425 if (isinstance(value, type)
0426 and issubclass(value, Col)):
0427 __all__.append(name)