sqlobject-cvs Mailing List for SQLObject (Page 171)
SQLObject is a Python ORM.
Brought to you by:
ianbicking,
phd
You can subscribe to this list here.
| 2003 |
Jan
|
Feb
|
Mar
(9) |
Apr
(74) |
May
(29) |
Jun
(16) |
Jul
(28) |
Aug
(10) |
Sep
(57) |
Oct
(9) |
Nov
(29) |
Dec
(12) |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2004 |
Jan
(7) |
Feb
(14) |
Mar
(6) |
Apr
(3) |
May
(12) |
Jun
(34) |
Jul
(9) |
Aug
(29) |
Sep
(22) |
Oct
(2) |
Nov
(15) |
Dec
(52) |
| 2005 |
Jan
(47) |
Feb
(78) |
Mar
(14) |
Apr
(35) |
May
(33) |
Jun
(16) |
Jul
(26) |
Aug
(63) |
Sep
(40) |
Oct
(96) |
Nov
(96) |
Dec
(123) |
| 2006 |
Jan
(159) |
Feb
(144) |
Mar
(64) |
Apr
(31) |
May
(88) |
Jun
(48) |
Jul
(16) |
Aug
(64) |
Sep
(87) |
Oct
(92) |
Nov
(56) |
Dec
(76) |
| 2007 |
Jan
(94) |
Feb
(103) |
Mar
(126) |
Apr
(123) |
May
(85) |
Jun
(11) |
Jul
(130) |
Aug
(47) |
Sep
(65) |
Oct
(70) |
Nov
(12) |
Dec
(11) |
| 2008 |
Jan
(30) |
Feb
(55) |
Mar
(88) |
Apr
(20) |
May
(50) |
Jun
|
Jul
(38) |
Aug
(1) |
Sep
(9) |
Oct
(5) |
Nov
(6) |
Dec
(39) |
| 2009 |
Jan
(8) |
Feb
(16) |
Mar
(3) |
Apr
(33) |
May
(44) |
Jun
(1) |
Jul
(10) |
Aug
(33) |
Sep
(74) |
Oct
(22) |
Nov
|
Dec
(15) |
| 2010 |
Jan
(28) |
Feb
(22) |
Mar
(46) |
Apr
(29) |
May
(1) |
Jun
(1) |
Jul
(27) |
Aug
(8) |
Sep
(5) |
Oct
(33) |
Nov
(24) |
Dec
(41) |
| 2011 |
Jan
(4) |
Feb
(12) |
Mar
(35) |
Apr
(29) |
May
(19) |
Jun
(16) |
Jul
(32) |
Aug
(25) |
Sep
(5) |
Oct
(11) |
Nov
(21) |
Dec
(12) |
| 2012 |
Jan
(3) |
Feb
(4) |
Mar
(20) |
Apr
(4) |
May
(25) |
Jun
(13) |
Jul
|
Aug
|
Sep
(2) |
Oct
(25) |
Nov
(9) |
Dec
(1) |
| 2013 |
Jan
(6) |
Feb
(8) |
Mar
|
Apr
(10) |
May
(31) |
Jun
(7) |
Jul
(18) |
Aug
(33) |
Sep
(4) |
Oct
(16) |
Nov
|
Dec
(27) |
| 2014 |
Jan
(2) |
Feb
|
Mar
|
Apr
(11) |
May
(39) |
Jun
(8) |
Jul
(11) |
Aug
(4) |
Sep
|
Oct
(27) |
Nov
|
Dec
(71) |
| 2015 |
Jan
(17) |
Feb
(47) |
Mar
(33) |
Apr
|
May
|
Jun
(9) |
Jul
(7) |
Aug
|
Sep
|
Oct
|
Nov
|
Dec
(8) |
| 2016 |
Jan
(4) |
Feb
(4) |
Mar
|
Apr
|
May
(12) |
Jun
(7) |
Jul
(9) |
Aug
(31) |
Sep
(8) |
Oct
(3) |
Nov
(15) |
Dec
(1) |
| 2017 |
Jan
(13) |
Feb
(7) |
Mar
(14) |
Apr
(8) |
May
(10) |
Jun
(4) |
Jul
(2) |
Aug
(1) |
Sep
|
Oct
(8) |
Nov
(4) |
Dec
(5) |
| 2018 |
Jan
(2) |
Feb
(8) |
Mar
|
Apr
(4) |
May
|
Jun
(6) |
Jul
|
Aug
(1) |
Sep
|
Oct
|
Nov
(1) |
Dec
|
| 2019 |
Jan
(1) |
Feb
(16) |
Mar
(1) |
Apr
(3) |
May
(5) |
Jun
(1) |
Jul
|
Aug
|
Sep
(2) |
Oct
|
Nov
(1) |
Dec
(3) |
| 2020 |
Jan
|
Feb
|
Mar
|
Apr
(1) |
May
(1) |
Jun
|
Jul
|
Aug
(1) |
Sep
|
Oct
(2) |
Nov
|
Dec
(2) |
| 2021 |
Jan
|
Feb
(2) |
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
(1) |
Nov
(1) |
Dec
|
| 2022 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
(6) |
Oct
(1) |
Nov
(1) |
Dec
(4) |
| 2023 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
(1) |
Aug
(3) |
Sep
(2) |
Oct
(2) |
Nov
(4) |
Dec
|
| 2024 |
Jan
|
Feb
(2) |
Mar
|
Apr
|
May
|
Jun
|
Jul
(1) |
Aug
|
Sep
(1) |
Oct
|
Nov
|
Dec
(9) |
| 2025 |
Jan
|
Feb
(4) |
Mar
(2) |
Apr
|
May
|
Jun
|
Jul
|
Aug
(1) |
Sep
|
Oct
|
Nov
(2) |
Dec
(2) |
|
From: <sub...@co...> - 2005-02-08 07:59:41
|
Author: ianb
Date: 2005-02-08 07:59:38 +0000 (Tue, 08 Feb 2005)
New Revision: 573
Modified:
trunk/SQLObject/sqlobject/tests/test_blob.py
Log:
Added note about SQLite
Modified: trunk/SQLObject/sqlobject/tests/test_blob.py
===================================================================
--- trunk/SQLObject/sqlobject/tests/test_blob.py 2005-02-08 07:45:24 UTC (rev 572)
+++ trunk/SQLObject/sqlobject/tests/test_blob.py 2005-02-08 07:59:38 UTC (rev 573)
@@ -19,4 +19,7 @@
ImageData._connection.cache.clear()
prof2 = ImageData.get(iid)
+ # @@: This seems to fail in SQLite, which trims off the
+ # first and last character (\x00 and \xff). We should write
+ # a test for the upstream driver, as the error might be there.
assert prof2.image == data
|
|
From: <sub...@co...> - 2005-02-08 07:45:29
|
Author: ianb
Date: 2005-02-08 07:45:24 +0000 (Tue, 08 Feb 2005)
New Revision: 572
Added:
trunk/SQLObject/sqlobject/boundattributes.py
trunk/SQLObject/sqlobject/declarative.py
trunk/SQLObject/sqlobject/tests/test_boundattributes.py
trunk/SQLObject/sqlobject/tests/test_declarative.py
Log:
Added declarative and boundattributes, which will be the basis for
the new SQLObject metaclass
Added: trunk/SQLObject/sqlobject/boundattributes.py
===================================================================
--- trunk/SQLObject/sqlobject/boundattributes.py 2005-02-07 11:02:13 UTC (rev 571)
+++ trunk/SQLObject/sqlobject/boundattributes.py 2005-02-08 07:45:24 UTC (rev 572)
@@ -0,0 +1,124 @@
+"""
+Bound attributes are attributes that are bound to a specific class and
+a specific name. In SQLObject a typical example is a column object,
+which knows its name and class.
+
+A bound attribute should define a method ``__addtoclass__(added_class,
+name)`` (attributes without this method will simply be treated as
+normal). The return value is ignored; if the attribute wishes to
+change the value in the class, it must call ``setattr(added_class,
+name, new_value)``.
+
+BoundAttribute is a class that facilitates lazy attribute creation.
+
+``bind_attributes(cls, new_attrs)`` is a function that looks for
+attributes with this special method. ``new_attrs`` is a dictionary,
+as typically passed into ``__classinit__`` with declarative (calling
+``bind_attributes`` in ``__classinit__`` would be typical).
+
+Note if you do this that attributes defined in a superclass will not
+be rebound in subclasses. If you want to rebind attributes in
+subclasses, use ``bind_attributes_local``, which adds a
+``__bound_attributes__`` variable to your class to track these active
+attributes.
+"""
+
+__all__ = ['BoundAttribute', 'BoundFactory', 'bind_attributes',
+ 'bind_attributes_local']
+
+import declarative
+
+class BoundAttribute(declarative.Declarative):
+
+ """
+ This is a declarative class that passes all the values given to it
+ to another object. So you can pass it arguments (via
+ __init__/__call__) or give it the equivalent of keyword arguments
+ through subclassing. Then a bound object will be added in its
+ place.
+
+ To hook this other object in, override ``make_object(added_class,
+ name, **attrs)`` and maybe ``set_object(added_class, name,
+ **attrs)`` (the default implementation of ``set_object``
+ just resets the attribute to whatever ``make_object`` returned).
+ """
+
+ _private_variables = (
+ '_private_variables',
+ '_all_attributes',
+ '__classinit__',
+ '__addtoclass__',
+ '_add_attrs',
+ 'set_object',
+ 'make_object',
+ )
+
+ _all_attrs = ()
+
+ def __classinit__(cls, new_attrs):
+ declarative.Declarative.__classinit__(cls, new_attrs)
+ cls._all_attrs = cls._add_attrs(cls, new_attrs)
+
+ def __instanceinit__(self, new_attrs):
+ declarative.Declarative.__instanceinit__(self, new_attrs)
+ self._all_attrs = self._add_attrs(self, new_attrs)
+
+ def _add_attrs(this_object, new_attrs):
+ private = this_object._private_variables
+ all_attrs = list(this_object._all_attrs)
+ for key in new_attrs.keys():
+ if key.startswith('_') or key in private:
+ continue
+ if key not in all_attrs:
+ all_attrs.append(key)
+ return tuple(all_attrs)
+ _add_attrs = staticmethod(_add_attrs)
+
+ def __addtoclass__(self, cls, added_class, attr_name):
+ me = self or cls
+ attrs = {}
+ for name in me._all_attrs:
+ attrs[name] = getattr(me, name)
+ attrs['added_class'] = added_class
+ attrs['attr_name'] = attr_name
+ obj = me.make_object(**attrs)
+ me.set_object(added_class, attr_name, obj)
+
+ __addtoclass__ = declarative.classinstancemethod(__addtoclass__)
+
+ def set_object(cls, added_class, attr_name, obj):
+ setattr(added_class, attr_name, obj)
+
+ set_object = classmethod(set_object)
+
+ def make_object(cls, added_class, attr_name, *args, **attrs):
+ raise NotImplementedError
+
+ make_object = classmethod(make_object)
+
+class BoundFactory(BoundAttribute):
+
+ factory_class = None
+
+ def make_object(cls, *args, **kw):
+ return cls.factory_class(*args, **kw)
+
+def bind_attributes(cls, new_attrs):
+ for name, value in new_attrs.items():
+ if hasattr(value, '__addtoclass__'):
+ value.__addtoclass__(cls, name)
+
+def bind_attributes_local(cls, new_attrs):
+ new_bound_attributes = {}
+ for name, value in getattr(cls, '__bound_attributes__', {}).items():
+ if new_attrs.has_key(name):
+ # The attribute is being REbound, so don't try to bind it
+ # again.
+ continue
+ value.__addtoclass__(cls, name)
+ new_bound_attributes[name] = value
+ for name, value in new_attrs.items():
+ if hasattr(value, '__addtoclass__'):
+ value.__addtoclass__(cls, name)
+ new_bound_attributes[name] = value
+ cls.__bound_attributes__ = new_bound_attributes
Copied: trunk/SQLObject/sqlobject/declarative.py (from rev 552, trunk/Validator/validator/declarative.py)
===================================================================
--- trunk/Validator/validator/declarative.py 2005-01-24 12:10:39 UTC (rev 552)
+++ trunk/SQLObject/sqlobject/declarative.py 2005-02-08 07:45:24 UTC (rev 572)
@@ -0,0 +1,192 @@
+"""
+Declarative objects.
+
+Declarative objects have a simple protocol: you can use classes in
+lieu of instances and they are equivalent, and any keyword arguments
+you give to the constructor will override those instance variables.
+(So if a class is received, we'll simply instantiate an instance with
+no arguments).
+
+You can provide a variable __unpackargs__ (a list of strings), and if
+the constructor is called with non-keyword arguments they will be
+interpreted as the given keyword arguments.
+
+If __unpackargs__ is ('*', name), then all the arguments will be put
+in a variable by that name.
+
+You can define a __classinit__(cls, new_attrs) method, which will be
+called when the class is created (including subclasses). Note: you
+can't use super() in __classinit__ because the class isn't bound to a
+name. As an analog to __classinit__, Declarative adds
+__instanceinit__ which is called with the same argument (new_attrs).
+This is like __init__, but after __unpackargs__ and other factors have
+been taken into account.
+
+If __mutableattributes__ is defined as a sequence of strings, these
+attributes will not be shared between superclasses and their
+subclasses. E.g., if you have a class variable that contains a list
+and you append to that list, changes to subclasses will effect
+superclasses unless you add the attribute here.
+
+Also defines classinstancemethod, which acts as either a class method
+or an instance method depending on where it is called.
+"""
+
+from __future__ import generators
+
+__all__ = ('classinstancemethod', 'DeclarativeMeta', 'Declarative')
+
+import copy
+
+try:
+ import itertools
+ counter = itertools.count()
+except ImportError:
+ def _counter():
+ i = 0
+ while 1:
+ i += 1
+ yield i
+ counter = _counter()
+
+class classinstancemethod(object):
+ """
+ Acts like a class method when called from a class, like an
+ instance method when called by an instance. The method should
+ take two arguments, 'self' and 'cls'; one of these will be None
+ depending on how the method was called.
+ """
+
+ def __init__(self, func):
+ self.func = func
+
+ def __get__(self, obj, type=None):
+ return _methodwrapper(self.func, obj=obj, type=type)
+
+class _methodwrapper(object):
+
+ def __init__(self, func, obj, type):
+ self.func = func
+ self.obj = obj
+ self.type = type
+
+ def __call__(self, *args, **kw):
+ assert not kw.has_key('self') and not kw.has_key('cls'), (
+ "You cannot use 'self' or 'cls' arguments to a "
+ "classinstancemethod")
+ return self.func(*((self.obj, self.type) + args), **kw)
+
+ def __repr__(self):
+ if self.obj is None:
+ return ('<bound class method %s.%s>'
+ % (self.type.__name__, self.func.func_name))
+ else:
+ return ('<bound method %s.%s of %r>'
+ % (self.type.__name__, self.func.func_name, self.obj))
+
+
+class DeclarativeMeta(type):
+
+ def __new__(meta, class_name, bases, new_attrs):
+ cls = type.__new__(meta, class_name, bases, new_attrs)
+ if new_attrs.has_key('__classinit__'):
+ cls.__classinit__ = staticmethod(cls.__classinit__.im_func)
+ cls.declarative_count = counter.next()
+ cls.__classinit__(cls, new_attrs)
+ return cls
+
+class Declarative(object):
+
+ __unpackargs__ = ()
+
+ __mutableattributes__ = ()
+
+ __metaclass__ = DeclarativeMeta
+
+ def __classinit__(cls, new_attrs):
+ for name in cls.__mutableattributes__:
+ if not new_attrs.has_key(name):
+ setattr(cls, copy.copy(getattr(cls, name)))
+
+ def __instanceinit__(self, new_attrs):
+ for name, value in new_attrs.items():
+ setattr(self, name, value)
+ if not new_attrs.has_key('declarative_count'):
+ self.declarative_count = counter.next()
+
+ def __init__(self, *args, **kw):
+ if self.__unpackargs__ and self.__unpackargs__[0] == '*':
+ assert len(self.__unpackargs__) == 2, \
+ "When using __unpackargs__ = ('*', varname), you must only provide a single variable name (you gave %r)" % self.__unpackargs__
+ name = self.__unpackargs__[1]
+ if kw.has_key(name):
+ raise TypeError(
+ "keyword parameter '%s' was given by position and name"
+ % name)
+ kw[name] = args
+ else:
+ if len(args) > len(self.__unpackargs__):
+ raise TypeError(
+ '%s() takes at most %i arguments (%i given)'
+ % (self.__class__.__name__,
+ len(self.__unpackargs__),
+ len(args)))
+ for name, arg in zip(self.__unpackargs__, args):
+ if kw.has_key(name):
+ raise TypeError(
+ "keyword parameter '%s' was given by position and name"
+ % name)
+ kw[name] = arg
+ if kw.has_key('__alsocopy'):
+ for name, value in kw['__alsocopy'].items():
+ if not kw.has_key(name):
+ if name in self.__mutableattributes__:
+ value = copy.copy(value)
+ kw[name] = value
+ del kw['__alsocopy']
+ self.__instanceinit__(kw)
+
+ def __call__(self, *args, **kw):
+ kw['__alsocopy'] = self.__dict__
+ return self.__class__(*args, **kw)
+
+ def singleton(self, cls):
+ if self:
+ return self
+ name = '_%s__singleton' % cls.__name__
+ if not hasattr(cls, name):
+ setattr(cls, name, cls(declarative_count=cls.declarative_count))
+ return getattr(cls, name)
+ singleton = classinstancemethod(singleton)
+
+ def __repr__(self, cls):
+ if self:
+ name = '%s object' % self.__class__.__name__
+ v = self.__dict__.copy()
+ else:
+ name = '%s class' % cls.__name__
+ v = cls.__dict__.copy()
+ if v.has_key('declarative_count'):
+ name = '%s %i' % (name, v['declarative_count'])
+ del v['declarative_count']
+ # @@: simplifying repr:
+ #v = {}
+ names = v.keys()
+ args = []
+ for n in self._repr_vars(names):
+ args.append('%s=%r' % (n, v[n]))
+ if not args:
+ return '<%s>' % name
+ else:
+ return '<%s %s>' % (name, ' '.join(args))
+
+ def _repr_vars(dictNames):
+ names = [n for n in dictNames
+ if not n.startswith('_')
+ and n != 'declarative_count']
+ names.sort()
+ return names
+ _repr_vars = staticmethod(_repr_vars)
+
+ __repr__ = classinstancemethod(__repr__)
+
Added: trunk/SQLObject/sqlobject/tests/test_boundattributes.py
===================================================================
--- trunk/SQLObject/sqlobject/tests/test_boundattributes.py 2005-02-07 11:02:13 UTC (rev 571)
+++ trunk/SQLObject/sqlobject/tests/test_boundattributes.py 2005-02-08 07:45:24 UTC (rev 572)
@@ -0,0 +1,57 @@
+from sqlobject import declarative
+from sqlobject import boundattributes
+
+class TestMe(object):
+
+ __metaclass__ = declarative.DeclarativeMeta
+ __classinit__ = boundattributes.bind_attributes_local
+
+class AttrReplace(boundattributes.BoundAttribute):
+
+ __unpackargs__ = ('replace',)
+
+ replace = None
+
+ def make_object(self, cls, added_class, attr_name, **attrs):
+ if not self:
+ return cls.singleton().make_object(
+ added_class, attr_name, **attrs)
+ self.replace.added_class = added_class
+ self.replace.name = attr_name
+ assert attrs['replace'] is self.replace
+ del attrs['replace']
+ self.replace.attrs = attrs
+ return self.replace
+
+ make_object = declarative.classinstancemethod(make_object)
+
+class Holder:
+ def __init__(self, name):
+ self.holder_name = name
+ def __repr__(self):
+ return '<Holder %s>' % self.holder_name
+
+def test_1():
+ v1 = Holder('v1')
+ v2 = Holder('v2')
+ v3 = Holder('v3')
+ class V2Class(AttrReplace):
+ arg1 = 'nothing'
+ arg2 = ['something']
+ class A1(TestMe):
+ a = AttrReplace(v1)
+ v = V2Class(v2)
+ class inline(AttrReplace):
+ replace = v3
+ arg3 = 'again'
+ arg4 = 'so there'
+ for n in ('a', 'v', 'inline'):
+ assert getattr(A1, n).name == n
+ assert getattr(A1, n).added_class is A1
+ assert A1.a is v1
+ assert A1.a.attrs == {}
+ assert A1.v is v2
+ assert A1.v.attrs == {'arg1': 'nothing', 'arg2': ['something']}
+ assert A1.inline is v3
+ assert A1.inline.attrs == {'arg3': 'again', 'arg4': 'so there'}
+
Added: trunk/SQLObject/sqlobject/tests/test_declarative.py
===================================================================
--- trunk/SQLObject/sqlobject/tests/test_declarative.py 2005-02-07 11:02:13 UTC (rev 571)
+++ trunk/SQLObject/sqlobject/tests/test_declarative.py 2005-02-08 07:45:24 UTC (rev 572)
@@ -0,0 +1,78 @@
+from sqlobject.declarative import *
+
+class A1(Declarative):
+
+ a = 1
+ b = []
+
+class A2(A1):
+
+ a = 5
+
+A3 = A2(b=5)
+
+def test_a_classes():
+ assert A1.a == 1
+ assert A1.singleton().a == 1
+ assert A1.b is A2.b
+ assert A3.b == 5
+ assert A1.declarative_count == A1.singleton().declarative_count
+ assert A1.declarative_count < A2.declarative_count
+ assert A2.singleton() is not A1.singleton()
+ assert A3.singleton().b == A3.b
+
+class B1(Declarative):
+
+ attrs = []
+
+ def __classinit__(cls, new_attrs):
+ Declarative.__classinit__(cls, new_attrs)
+ cls.attrs = cls.add_attrs(cls.attrs, new_attrs)
+
+ def __instanceinit__(self, new_attrs):
+ Declarative.__instanceinit__(self, new_attrs)
+ self.attrs = self.add_attrs(self.attrs, new_attrs)
+
+ def add_attrs(old_attrs, new_attrs):
+ old_attrs = old_attrs[:]
+ for name in new_attrs.keys():
+ if (name in old_attrs
+ or name.startswith('_')
+ or name in ('add_attrs', 'declarative_count', 'attrs')):
+ continue
+ old_attrs.append(name)
+ old_attrs.sort()
+ return old_attrs
+ add_attrs = staticmethod(add_attrs)
+
+ c = 1
+
+class B2(B1):
+
+ g = 3
+
+ def __classinit__(cls, new_attrs):
+ new_attrs['test'] = 'whatever'
+ B1.__classinit__(cls, new_attrs)
+
+B3 = B2(c=5, d=3)
+B4 = B3(d=5)
+B5 = B1(a=1)
+
+def test_b_classes():
+ assert B1.attrs == ['c']
+ assert B1.c == 1
+ assert B2.attrs == ['c', 'g', 'test']
+ assert B3.d == 3
+ assert B4.d == 5
+ assert B5.a == 1
+ assert B5.attrs == ['a', 'c']
+ assert B3.attrs == ['c', 'd', 'g', 'test']
+ assert B4.attrs == ['c', 'd', 'g', 'test']
+ order = [B1, B1.singleton(), B2, B2.singleton(),
+ B3, B3.singleton(), B4, B4.singleton(),
+ B5, B5.singleton()]
+ last = 0
+ for obj in order:
+ assert obj.declarative_count >= last
+ last = obj.declarative_count
|
|
From: <sub...@co...> - 2005-02-07 11:02:19
|
Author: phd
Date: 2005-02-07 11:02:13 +0000 (Mon, 07 Feb 2005)
New Revision: 571
Modified:
home/phd/SQLObject/inheritance/sqlobject/inheritance.py
home/phd/SQLObject/inheritance/sqlobject/main.py
Log:
Fixed destroySelf() - removed inheritance-related code from SQLObject.
Modified: home/phd/SQLObject/inheritance/sqlobject/inheritance.py
===================================================================
--- home/phd/SQLObject/inheritance/sqlobject/inheritance.py 2005-02-07 10:43:44 UTC (rev 570)
+++ home/phd/SQLObject/inheritance/sqlobject/inheritance.py 2005-02-07 11:02:13 UTC (rev 571)
@@ -183,7 +183,6 @@
super(InheritableSQLObject, self)._create(id, **kw)
def destroySelf(self):
- # Kills this object. Kills it dead!
#DSM: If this object has parents, recursivly kill them
if hasattr(self, '_parent') and self._parent:
self._parent.destroySelf()
Modified: home/phd/SQLObject/inheritance/sqlobject/main.py
===================================================================
--- home/phd/SQLObject/inheritance/sqlobject/main.py 2005-02-07 10:43:44 UTC (rev 570)
+++ home/phd/SQLObject/inheritance/sqlobject/main.py 2005-02-07 11:02:13 UTC (rev 571)
@@ -1325,9 +1325,6 @@
def destroySelf(self):
# Kills this object. Kills it dead!
- #DSM: If this object has parents, recursivly kill them
- if hasattr(self, '_parent') and self._parent:
- self._parent.destroySelf()
depends = []
klass = self.__class__
depends = self._SO_depends()
|
|
From: <sub...@co...> - 2005-02-07 10:44:01
|
Author: phd Date: 2005-02-07 10:43:44 +0000 (Mon, 07 Feb 2005) New Revision: 570 Added: home/phd/SQLObject/inheritance/sqlobject/inheritance.py home/phd/SQLObject/inheritance/sqlobject/tests/ Removed: home/phd/SQLObject/inheritance/test home/phd/SQLObject/inheritance/tests/ Modified: home/phd/SQLObject/inheritance/docs/DeveloperGuide.txt home/phd/SQLObject/inheritance/docs/FAQ.txt home/phd/SQLObject/inheritance/sqlobject/dbconnection.py home/phd/SQLObject/inheritance/sqlobject/main.py home/phd/SQLObject/inheritance/sqlobject/sqlbuilder.py Log: Split sqlobject/main.py and moved inheritance-related classes into sqlobject/inheritance.py. Merged patches from the revisions 559:569 from the trunk. Modified: home/phd/SQLObject/inheritance/docs/DeveloperGuide.txt =================================================================== --- home/phd/SQLObject/inheritance/docs/DeveloperGuide.txt 2005-02-03 12:14:28 UTC (rev 569) +++ home/phd/SQLObject/inheritance/docs/DeveloperGuide.txt 2005-02-07 10:43:44 UTC (rev 570) @@ -26,16 +26,19 @@ * I don't stress too much on line length. But try to break lines up by grouping with parenthesis instead of with backslashes (if you - can). + can). Do asserts like:: + assert some_condition(a, b), ( + "Some condition failed, %r isn't right!" % a) + * But if you are having problems with line length, maybe you should just break the expression up into multiple statements. * Blank lines between methods, unless they are very small and closely bound to each other. -* Never use the form ``condition and trueValue or falseValue``. Break - it out and use a variable. +* *Never* use the form ``condition and trueValue or falseValue``. + Break it out and use a variable. * Careful of namespace pollution. SQLObject does allow for ``from sqlobject import *`` so names should be fairly distinct, or they @@ -122,27 +125,42 @@ Tests are important. Tests keep everything from falling apart. All new additions should have tests. -Right now all the tests are in one big file ``tests.py``. Which is -unfortunate, but that's the way it is. They may seem complex, but -they aren't so bad really. They all subclass from ``SQLObjectTest``. +Testing uses `py.test`__, an alternative to ``unittest``. It is +available via subversion at http://codespeak.net/svn/py/dist. Read +its `getting started`_ document for more. -The ``classes`` attribute is special in a test class. This is a list -of `SQLObject` subclasses that this test uses. `SQLObjectTest` will -create the tables before the tests are run, and destroy them after. +.. __: http://codespeak.net/py/current/doc/test.html +.. _getting started: http://codespeak.net/py/current/doc/getting_started.html -You may also define an ``.inserts()`` method. This method sets up the -basic data. When doing verbose input (``-vv``) you won't see these -inserts, since they may be overwhelming. Use the command-line options -``--inserts`` to show them (as well as the create statement. +To actually run the test, you have to give it a database to connect +to. You do this with the ``TESTDB`` environmental variable (right now +py.test doesn't have a better way to add custom options). You can +give the complete URI to access your test database, or you can give it +a shortcut like ``mysql`` (these shortcuts are defined in the top of +``tests/dbtest.py``. -When running tests, use ``-ddbname`` to test with ``dbname`` (e.g., -``-dmysql``, ``-dpostgres``, etc), or ``-dall`` to use Postgres, -MySQL, Firebird, and SQLite (all the core supported databases; -everything I have installed on my computer). Please try to test as -many databases as you can. At least SQLite and one other should be -easy, though if you can test both Postgres and MySQL that would be -much better. +All the tests are modules in ``sqlobject/tests``. Each module tests +one kind of feature, more or less. If you are testing a module, call +the test module ``tests/test_modulename.py`` -- only modules that +start with ``test_`` will be picked up by py.test. +The "framework" for testing is in ``tests/dbtest``. There's a couple +important functions: + +``setupClass(soClass)`` creates the tables for the class. It tries to +avoid recreating tables if not necessary. + +``supports(featureName)`` checks if the database backend supports the +named feature. What backends support what is defined at the top of +``dbtest``. + +If you ``import *`` you'll also get py.test's version of raises_, an +``inserts`` function that can create instances for you, and a couple +miscellaneous functions. + +.. _raises: http://codespeak.net/py/current/doc/test.html#id4 + + If you submit a patch or implement a feature without a test, I'll be forced to write the test. That's no fun for me, to just be writing tests. So please, write tests; everything at least needs to be Modified: home/phd/SQLObject/inheritance/docs/FAQ.txt =================================================================== --- home/phd/SQLObject/inheritance/docs/FAQ.txt 2005-02-03 12:14:28 UTC (rev 569) +++ home/phd/SQLObject/inheritance/docs/FAQ.txt 2005-02-07 10:43:44 UTC (rev 570) @@ -182,3 +182,12 @@ For this reason and several others, reloading modules is highly error-prone and difficult to support. + +Python Keywords +--------------- + +If you have a table column that is a Python keyword, you should know +that the Python attribute doesn't have to match the name of the +column. See `Irregular Naming`_ in the documentation. + +.. _Irregular Naming: SQLObject.html#irregular-naming Modified: home/phd/SQLObject/inheritance/sqlobject/dbconnection.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/dbconnection.py 2005-02-03 12:14:28 UTC (rev 569) +++ home/phd/SQLObject/inheritance/sqlobject/dbconnection.py 2005-02-07 10:43:44 UTC (rev 570) @@ -51,7 +51,7 @@ auth = auth + '@' + self.password auth = auth + ':' else: - assert not password, 'URIs cannot express passwords without usernames' + assert not self.password, 'URIs cannot express passwords without usernames' uri = '%s://%s' % (self.dbName, auth) if self.host: uri += self.host + '/' @@ -481,6 +481,10 @@ self.close() def close(self): + if not hasattr(self, '_pool'): + # Probably there was an exception while creating this + # instance, so it is incomplete. + return if not self._pool: return self._poolLock.acquire() Added: home/phd/SQLObject/inheritance/sqlobject/inheritance.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/inheritance.py 2005-02-03 12:14:28 UTC (rev 569) +++ home/phd/SQLObject/inheritance/sqlobject/inheritance.py 2005-02-07 10:43:44 UTC (rev 570) @@ -0,0 +1,193 @@ +import sqlbuilder +import dbconnection +import classregistry +from main import SQLObject, SelectResults, True, False, makeProperties, getterName, setterName + + +class InheritableSelectResults(SelectResults): + IterationClass = dbconnection.InheritableIteration + + def __init__(self, sourceClass, clause, clauseTables=None, + **ops): + if clause is None or isinstance(clause, str) and clause == 'all': + clause = sqlbuilder.SQLTrueClause + tablesDict = sqlbuilder.tablesUsedDict(clause) + tablesDict[sourceClass._table] = 1 + orderBy = ops.get('orderBy') + if orderBy and not isinstance(orderBy, basestring): + tablesDict.update(sqlbuilder.tablesUsedDict(orderBy)) + #DSM: if this class has a parent, we need to link it + #DSM: and be sure the parent is in the table list. + #DSM: The following code is before clauseTables + #DSM: because if the user uses clauseTables + #DSM: (and normal string SELECT), he must know what he wants + #DSM: and will do himself the relationship between classes. + if type(clause) is not str: + tableRegistry = {} + allClasses = classregistry.registry(sourceClass._registry).allClasses() + for registryClass in allClasses: + if registryClass._table in tablesDict: + #DSM: By default, no parents are needed for the clauses + tableRegistry[registryClass] = registryClass + for registryClass in allClasses: + if registryClass._table in tablesDict: + currentClass = registryClass + while currentClass._parentClass: + currentClass = currentClass._parentClass + if tableRegistry.has_key(currentClass): + #DSM: Must keep the last parent needed + #DSM: (to limit the number of join needed) + tableRegistry[registryClass] = currentClass + #DSM: Remove this class as it is a parent one + #DSM: of a needed children + del tableRegistry[currentClass] + #DSM: Table registry contains only the last children + #DSM: or standalone classes + parentClause = [] + for (currentClass, minParentClass) in tableRegistry.items(): + while currentClass != minParentClass and currentClass._parentClass: + parentClass = currentClass._parentClass + parentClause.append(currentClass.q.id == parentClass.q.id) + currentClass = parentClass + tablesDict[currentClass._table] = 1 + clause = reduce(sqlbuilder.AND, parentClause, clause) + + super(InheritableSelectResults, self).__init__(sourceClass, clause, clauseTables, + **ops) + + +class InheritableSQLObject(SQLObject): + SelectResultsClass = InheritableSelectResults + + def get(cls, id, connection=None, selectResults=None, childResults=None, childUpdate=False): + + val = super(InheritableSQLObject, cls).get(id, connection, selectResults) + + #DSM: If we are updating a child, we should never return a child... + if childUpdate: return val + #DSM: If this class has a child, return the child + if hasattr(val, 'childName'): + childName = val.childName + if childName is not None: + return val._childClasses[childName].get(id, selectResults=childResults) + #DSM: Now, we know we are alone or the last child in a family... + #DSM: It's time to find our parents + inst = val + while inst._parentClass and not inst._parent: + inst._parent = inst._parentClass.get(id, childUpdate=True) + inst = inst._parent + #DSM: We can now return ourself + return val + + get = classmethod(get) + + def addColumn(cls, columnDef, changeSchema=False, connection=None, childUpdate=False): + #DSM: Try to add parent properties to the current class + #DSM: Only do this once if possible at object creation and once for + #DSM: each new dynamic column to refresh the current class + if childUpdate or cls._parentClass: + for col in cls._parentClass._columns: + cname = col.name + if cname == 'childName': continue + setattr(cls, getterName(cname), eval( + 'lambda self: self._parent.%s' % cname)) + if not col.kw.has_key('immutable') or not col.kw['immutable']: + setattr(cls, setterName(cname), eval( + 'lambda self, val: setattr(self._parent, %s, val)' + % repr(cname))) + if childUpdate: + makeProperties(cls) + return + + super(InheritableSQLObject, cls).addColumn(columnDef, changeSchema, connection) + + #DSM: Update each child class if needed and existing (only for new + #DSM: dynamic column as no child classes exists at object creation) + for c in cls._childClasses.values(): + c.addColumn(columnDef, childUpdate=True) + + addColumn = classmethod(addColumn) + + def delColumn(cls, column, changeSchema=False, connection=None): + super(InheritableSQLObject, cls).delColumn(column, changeSchema, connection) + + #DSM: Update each child class if needed + #DSM: and delete properties for this column + for c in cls._childClasses.values(): + delattr(c, name) + + delColumn = classmethod(delColumn) + + def addJoin(cls, joinDef, childUpdate=False): + #DSM: Try to add parent properties to the current class + #DSM: Only do this once if possible at object creation and once for + #DSM: each new dynamic join to refresh the current class + if childUpdate or cls._parentClass: + for jdef in cls._parentClass._joins: + join = jdef.withClass(cls) + jname = join.joinMethodName + jarn = join.addRemoveName + setattr(cls, getterName(jname), + eval('lambda self: self._parent.%s' % jname)) + if hasattr(join, 'remove'): + setattr(cls, 'remove' + jarn, + eval('lambda self,o: self._parent.remove%s(o)' % jarn)) + if hasattr(join, 'add'): + setattr(cls, 'add' + jarn, + eval('lambda self,o: self._parent.add%s(o)' % jarn)) + if childUpdate: + makeProperties(cls) + return + + super(InheritableSQLObject, cls).addJoin(joinDef) + + #DSM: Update each child class if needed and existing (only for new + #DSM: dynamic join as no child classes exists at object creation) + for c in cls._childClasses.values(): + c.addJoin(joinDef, childUpdate=True) + + addJoin = classmethod(addJoin) + + def delJoin(cls, joinDef): + super(InheritableSQLObject, cls).delJoin(joinDef) + + #DSM: Update each child class if needed + #DSM: and delete properties for this join + for c in cls._childClasses.values(): + delattr(c, meth) + + delJoin = classmethod(delJoin) + + def _create(self, id, **kw): + + #DSM: If we were called by a children class, + #DSM: we must retreive the properties dictionary. + #DSM: Note: we can't use the ** call paremeter directly + #DSM: as we must be able to delete items from the dictionary + #DSM: (and our children must know that the items were removed!) + if kw.has_key('kw'): + kw = kw['kw'] + #DSM: If we are the children of an inheritable class, + #DSM: we must first create our parent + if self._parentClass: + parentClass = self._parentClass + parent_kw = dict( + [(name, value) for (name, value) in kw.items() + if hasattr(parentClass, name) + ] + ) + self._parent = parentClass(kw=parent_kw) + self._parent.childName = self._className + id = self._parent.id + + super(InheritableSQLObject, self)._create(id, **kw) + + def destroySelf(self): + # Kills this object. Kills it dead! + #DSM: If this object has parents, recursivly kill them + if hasattr(self, '_parent') and self._parent: + self._parent.destroySelf() + super(InheritableSQLObject, self).destroySelf() + + +__all__ = ['InheritableSQLObject'] Modified: home/phd/SQLObject/inheritance/sqlobject/main.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/main.py 2005-02-03 12:14:28 UTC (rev 569) +++ home/phd/SQLObject/inheritance/sqlobject/main.py 2005-02-07 10:43:44 UTC (rev 570) @@ -511,58 +511,7 @@ expression = sqlbuilder.func.SUM(attribute) return self.accumulate(expression) -class InheritableSelectResults(SelectResults): - IterationClass = dbconnection.InheritableIteration - def __init__(self, sourceClass, clause, clauseTables=None, - **ops): - if clause is None or isinstance(clause, str) and clause == 'all': - clause = sqlbuilder.SQLTrueClause - tablesDict = sqlbuilder.tablesUsedDict(clause) - tablesDict[sourceClass._table] = 1 - orderBy = ops.get('orderBy') - if orderBy and not isinstance(orderBy, basestring): - tablesDict.update(sqlbuilder.tablesUsedDict(orderBy)) - #DSM: if this class has a parent, we need to link it - #DSM: and be sure the parent is in the table list. - #DSM: The following code is before clauseTables - #DSM: because if the user uses clauseTables - #DSM: (and normal string SELECT), he must know what he wants - #DSM: and will do himself the relationship between classes. - if type(clause) is not str: - tableRegistry = {} - allClasses = classregistry.registry(sourceClass._registry).allClasses() - for registryClass in allClasses: - if registryClass._table in tablesDict: - #DSM: By default, no parents are needed for the clauses - tableRegistry[registryClass] = registryClass - for registryClass in allClasses: - if registryClass._table in tablesDict: - currentClass = registryClass - while currentClass._parentClass: - currentClass = currentClass._parentClass - if tableRegistry.has_key(currentClass): - #DSM: Must keep the last parent needed - #DSM: (to limit the number of join needed) - tableRegistry[registryClass] = currentClass - #DSM: Remove this class as it is a parent one - #DSM: of a needed children - del tableRegistry[currentClass] - #DSM: Table registry contains only the last children - #DSM: or standalone classes - parentClause = [] - for (currentClass, minParentClass) in tableRegistry.items(): - while currentClass != minParentClass and currentClass._parentClass: - parentClass = currentClass._parentClass - parentClause.append(currentClass.q.id == parentClass.q.id) - currentClass = parentClass - tablesDict[currentClass._table] = 1 - clause = reduce(sqlbuilder.AND, parentClause, clause) - - super(InheritableSelectResults, self).__init__(sourceClass, clause, clauseTables, - **ops) - - # SQLObject is the superclass for all SQLObject classes, of # course. All the deeper magic is done in MetaSQLObject, and # only lesser magic is done here. All the actual work is done @@ -1447,139 +1396,7 @@ cls._connection = value setConnection = classmethod(setConnection) -class InheritableSQLObject(SQLObject): - SelectResultsClass = InheritableSelectResults - def get(cls, id, connection=None, selectResults=None, childResults=None, childUpdate=False): - - val = super(InheritableSQLObject, cls).get(id, connection, selectResults) - - #DSM: If we are updating a child, we should never return a child... - if childUpdate: return val - #DSM: If this class has a child, return the child - if hasattr(val, 'childName'): - childName = val.childName - if childName is not None: - return val._childClasses[childName].get(id, selectResults=childResults) - #DSM: Now, we know we are alone or the last child in a family... - #DSM: It's time to find our parents - inst = val - while inst._parentClass and not inst._parent: - inst._parent = inst._parentClass.get(id, childUpdate=True) - inst = inst._parent - #DSM: We can now return ourself - return val - - get = classmethod(get) - - def addColumn(cls, columnDef, changeSchema=False, connection=None, childUpdate=False): - #DSM: Try to add parent properties to the current class - #DSM: Only do this once if possible at object creation and once for - #DSM: each new dynamic column to refresh the current class - if childUpdate or cls._parentClass: - for col in cls._parentClass._columns: - cname = col.name - if cname == 'childName': continue - setattr(cls, getterName(cname), eval( - 'lambda self: self._parent.%s' % cname)) - if not col.kw.has_key('immutable') or not col.kw['immutable']: - setattr(cls, setterName(cname), eval( - 'lambda self, val: setattr(self._parent, %s, val)' - % repr(cname))) - if childUpdate: - makeProperties(cls) - return - - super(InheritableSQLObject, cls).addColumn(columnDef, changeSchema, connection) - - #DSM: Update each child class if needed and existing (only for new - #DSM: dynamic column as no child classes exists at object creation) - for c in cls._childClasses.values(): - c.addColumn(columnDef, childUpdate=True) - - addColumn = classmethod(addColumn) - - def delColumn(cls, column, changeSchema=False, connection=None): - super(InheritableSQLObject, cls).delColumn(column, changeSchema, connection) - - #DSM: Update each child class if needed - #DSM: and delete properties for this column - for c in cls._childClasses.values(): - delattr(c, name) - - delColumn = classmethod(delColumn) - - def addJoin(cls, joinDef, childUpdate=False): - #DSM: Try to add parent properties to the current class - #DSM: Only do this once if possible at object creation and once for - #DSM: each new dynamic join to refresh the current class - if childUpdate or cls._parentClass: - for jdef in cls._parentClass._joins: - join = jdef.withClass(cls) - jname = join.joinMethodName - jarn = join.addRemoveName - setattr(cls, getterName(jname), - eval('lambda self: self._parent.%s' % jname)) - if hasattr(join, 'remove'): - setattr(cls, 'remove' + jarn, - eval('lambda self,o: self._parent.remove%s(o)' % jarn)) - if hasattr(join, 'add'): - setattr(cls, 'add' + jarn, - eval('lambda self,o: self._parent.add%s(o)' % jarn)) - if childUpdate: - makeProperties(cls) - return - - super(InheritableSQLObject, cls).addJoin(joinDef) - - #DSM: Update each child class if needed and existing (only for new - #DSM: dynamic join as no child classes exists at object creation) - for c in cls._childClasses.values(): - c.addJoin(joinDef, childUpdate=True) - - addJoin = classmethod(addJoin) - - def delJoin(cls, joinDef): - super(InheritableSQLObject, cls).delJoin(joinDef) - - #DSM: Update each child class if needed - #DSM: and delete properties for this join - for c in cls._childClasses.values(): - delattr(c, meth) - - delJoin = classmethod(delJoin) - - def _create(self, id, **kw): - - #DSM: If we were called by a children class, - #DSM: we must retreive the properties dictionary. - #DSM: Note: we can't use the ** call paremeter directly - #DSM: as we must be able to delete items from the dictionary - #DSM: (and our children must know that the items were removed!) - if kw.has_key('kw'): - kw = kw['kw'] - #DSM: If we are the children of an inheritable class, - #DSM: we must first create our parent - if self._parentClass: - parentClass = self._parentClass - parent_kw = dict( - [(name, value) for (name, value) in kw.items() - if hasattr(parentClass, name) - ] - ) - self._parent = parentClass(kw=parent_kw) - self._parent.childName = self._className - id = self._parent.id - - super(InheritableSQLObject, self)._create(id, **kw) - - def destroySelf(self): - # Kills this object. Kills it dead! - #DSM: If this object has parents, recursivly kill them - if hasattr(self, '_parent') and self._parent: - self._parent.destroySelf() - super(InheritableSQLObject, self).destroySelf() - def capitalize(name): return name[0].capitalize() + name[1:] @@ -1634,6 +1451,6 @@ else: return obj -__all__ = ['SQLObject', 'InheritableSQLObject', - 'NoDefault', 'getID', 'getObject', +__all__ = ['NoDefault', 'SQLObject', + 'getID', 'getObject', 'SQLObjectNotFound'] Modified: home/phd/SQLObject/inheritance/sqlobject/sqlbuilder.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/sqlbuilder.py 2005-02-03 12:14:28 UTC (rev 569) +++ home/phd/SQLObject/inheritance/sqlobject/sqlbuilder.py 2005-02-07 10:43:44 UTC (rev 570) @@ -152,9 +152,14 @@ return SQLCall(self, args) def __repr__(self): - return self.__sqlrepr__(None) + try: + return self.__sqlrepr__(None) + except AssertionError: + return '<%s %s>' % ( + self.__class__.__name__, hex(id(self))[2:]) + def __str__(self): - return self.__sqlrepr__(None) + return repr(self) def __cmp__(self, other): raise VersionError, "Python 2.1+ required" Copied: home/phd/SQLObject/inheritance/sqlobject/tests (from rev 569, trunk/SQLObject/sqlobject/tests) Deleted: home/phd/SQLObject/inheritance/test =================================================================== --- home/phd/SQLObject/inheritance/test 2005-02-03 12:14:28 UTC (rev 569) +++ home/phd/SQLObject/inheritance/test 2005-02-07 10:43:44 UTC (rev 570) @@ -1,18 +0,0 @@ -#!/bin/sh -VERSION="" -if [ "$1" = "2.3" ] ; then - VERSION=2.3 - shift -elif [ "$1" = "2.2" ] ; then - VERSION=2.2 - shift -fi - -if [ "$1" = "cover" ] ; then - shift - sudo python$VERSION setup.py -q install - python$VERSION ./tests/coverage.py -x tests/test_sqlobject.py $* - ./tests/coverage.py -a `find tests SQLObject -name '*.py'` -else - sudo python$VERSION setup.py -q install && python$VERSION tests/test_sqlobject.py $* -fi |
|
From: <sub...@co...> - 2005-02-03 12:14:35
|
Author: phd
Date: 2005-02-03 12:14:28 +0000 (Thu, 03 Feb 2005)
New Revision: 569
Modified:
home/phd/SQLObject/inheritance/sqlobject/dbconnection.py
home/phd/SQLObject/inheritance/sqlobject/main.py
home/phd/SQLObject/inheritance/tests/test_inheritance.py
Log:
Split inheritance support into a number of separate classes - InheritableSQLObject at al.
Modified: home/phd/SQLObject/inheritance/sqlobject/dbconnection.py
===================================================================
--- home/phd/SQLObject/inheritance/sqlobject/dbconnection.py 2005-02-03 12:13:26 UTC (rev 568)
+++ home/phd/SQLObject/inheritance/sqlobject/dbconnection.py 2005-02-03 12:14:28 UTC (rev 569)
@@ -244,7 +244,7 @@
return self._runWithConnection(self._queryInsertID, soInstance, id, names, values)
def iterSelect(self, select):
- return Iteration(self, self.getConnection(),
+ return select.IterationClass(self, self.getConnection(),
select, keepConnection=False)
def accumulateSelect(self, select, expression):
@@ -498,20 +498,52 @@
self._poolLock.release()
class Iteration(object):
- #phd: default array size for cursor.fetchmany()
- defaultArraySize = 10000
def __init__(self, dbconn, rawconn, select, keepConnection=False):
self.dbconn = dbconn
self.rawconn = rawconn
self.select = select
self.keepConnection = keepConnection
- self.cursor = cursor = rawconn.cursor()
+ self.cursor = rawconn.cursor()
self.query = self.dbconn.queryForSelect(select)
if dbconn.debug:
dbconn.printDebug(rawconn, self.query, 'Select')
- self.dbconn._executeRetry(self.rawconn, cursor, self.query)
- cursor.arraysize = self.defaultArraySize
+ self.dbconn._executeRetry(self.rawconn, self.cursor, self.query)
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ result = self.cursor.fetchone()
+ if result is None:
+ self._cleanup()
+ raise StopIteration
+ if self.select.ops.get('lazyColumns', 0):
+ obj = self.select.sourceClass.get(result[0], connection=self.dbconn)
+ return obj
+ else:
+ obj = self.select.sourceClass.get(result[0], selectResults=result[1:], connection=self.dbconn)
+ return obj
+
+ def _cleanup(self):
+ if getattr(self, 'query', None) is None:
+ # already cleaned up
+ return
+ self.query = None
+ if not self.keepConnection:
+ self.dbconn.releaseConnection(self.rawconn)
+ self.dbconn = self.rawconn = self.select = self.cursor = None
+
+ def __del__(self):
+ self._cleanup()
+
+class InheritableIteration(Iteration):
+ #phd: default array size for cursor.fetchmany()
+ defaultArraySize = 10000
+
+ def __init__(self, dbconn, rawconn, select, keepConnection=False):
+ super(InheritableIteration, self).__init__(dbconn, rawconn, select, keepConnection)
+ self.cursor.arraysize = self.defaultArraySize
self._results = []
#phd: find the index of the childName column
childNameIdx = None
@@ -522,9 +554,6 @@
break
self._childNameIdx = childNameIdx
- def __iter__(self):
- return self
-
def next(self):
lazyColumns = self.select.ops.get('lazyColumns', 0)
if not self._results:
@@ -581,20 +610,7 @@
for result in cursor.fetchall():
self._childrenResults[result[0]] = result[1:]
- def _cleanup(self):
- if getattr(self, 'query', None) is None:
- # already cleaned up
- return
- self.query = None
- if not self.keepConnection:
- self.dbconn.releaseConnection(self.rawconn)
- self.dbconn = self.rawconn = self.select = self.cursor = None
- def __del__(self):
- self._cleanup()
-
-
-
class Transaction(object):
def __init__(self, dbConnection):
@@ -631,7 +647,7 @@
# still iterating through the results.
# @@: But would it be okay for psycopg, with threadsafety
# level 2?
- return iter(list(Iteration(self, self._connection,
+ return iter(list(select.IterationClass(self, self._connection,
select, keepConnection=True)))
def commit(self):
Modified: home/phd/SQLObject/inheritance/sqlobject/main.py
===================================================================
--- home/phd/SQLObject/inheritance/sqlobject/main.py 2005-02-03 12:13:26 UTC (rev 568)
+++ home/phd/SQLObject/inheritance/sqlobject/main.py 2005-02-03 12:14:28 UTC (rev 569)
@@ -8,6 +8,10 @@
Daniel Savard, Xsoli Inc <sqlobject xsoli.com> 7 Feb 2004
- Added support for simple table inheritance.
+ Oleg Broytmann, SIA "ANK" <ph...@ph...> 3 Feb 2005
+ - Split inheritance support into a number of separate classes -
+ InheritableSQLObject at al.
+
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
@@ -339,6 +343,226 @@
"""
pass
+
+try:
+ basestring
+except NameError: # Python 2.2
+ basestring = (types.StringType, types.UnicodeType)
+
+class SelectResults(object):
+ IterationClass = dbconnection.Iteration
+
+ def __init__(self, sourceClass, clause, clauseTables=None,
+ **ops):
+ self.sourceClass = sourceClass
+ if clause is None or isinstance(clause, str) and clause == 'all':
+ clause = sqlbuilder.SQLTrueClause
+ self.clause = clause
+ tablesDict = sqlbuilder.tablesUsedDict(self.clause)
+ tablesDict[sourceClass._table] = 1
+ orderBy = ops.get('orderBy')
+ if orderBy and not isinstance(orderBy, basestring):
+ tablesDict.update(sqlbuilder.tablesUsedDict(orderBy))
+
+ if clauseTables:
+ for table in clauseTables:
+ tablesDict[table] = 1
+ self.clauseTables = clauseTables
+ self.tables = tablesDict.keys()
+ self.ops = ops
+ if self.ops.get('orderBy', NoDefault) is NoDefault:
+ self.ops['orderBy'] = sourceClass._defaultOrder
+ orderBy = self.ops['orderBy']
+ if isinstance(orderBy, list) or isinstance(orderBy, tuple):
+ orderBy = map(self._mungeOrderBy, orderBy)
+ else:
+ orderBy = self._mungeOrderBy(orderBy)
+ #print "OUT: %r; in: %r" % (sourceClass.sqlrepr(orderBy), sourceClass.sqlrepr(self.ops['orderBy']))
+ self.ops['dbOrderBy'] = orderBy
+ if ops.has_key('connection') and ops['connection'] is None:
+ del ops['connection']
+
+ def _mungeOrderBy(self, orderBy):
+ if isinstance(orderBy, str) and orderBy.startswith('-'):
+ orderBy = orderBy[1:]
+ desc = True
+ else:
+ desc = False
+ if isinstance(orderBy, (str, unicode)):
+ if self.sourceClass._SO_columnDict.has_key(orderBy):
+ val = self.sourceClass._SO_columnDict[orderBy].dbName
+ if desc:
+ return '-' + val
+ else:
+ return val
+ else:
+ if desc:
+ return '-' + orderBy
+ else:
+ return orderBy
+ else:
+ return orderBy
+
+ def clone(self, **newOps):
+ ops = self.ops.copy()
+ ops.update(newOps)
+ return self.__class__(self.sourceClass, self.clause,
+ self.clauseTables, **ops)
+
+ def orderBy(self, orderBy):
+ return self.clone(orderBy=orderBy)
+
+ def connection(self, conn):
+ return self.clone(connection=conn)
+
+ def limit(self, limit):
+ return self[:limit]
+
+ def lazyColumns(self, value):
+ return self.clone(lazyColumns=value)
+
+ def reversed(self):
+ return self.clone(reversed=not self.ops.get('reversed', False))
+
+ def distinct(self):
+ return self.clone(distinct=True)
+
+ def __getitem__(self, value):
+ if type(value) is type(slice(1)):
+ assert not value.step, "Slices do not support steps"
+ if not value.start and not value.stop:
+ # No need to copy, I'm immutable
+ return self
+
+ # Negative indexes aren't handled (and everything we
+ # don't handle ourselves we just create a list to
+ # handle)
+ if (value.start and value.start < 0) \
+ or (value.stop and value.stop < 0):
+ if value.start:
+ if value.stop:
+ return list(self)[value.start:value.stop]
+ return list(self)[value.start:]
+ return list(self)[:value.stop]
+
+
+ if value.start:
+ assert value.start >= 0
+ start = self.ops.get('start', 0) + value.start
+ if value.stop is not None:
+ assert value.stop >= 0
+ if value.stop < value.start:
+ # an empty result:
+ end = start
+ else:
+ end = value.stop + self.ops.get('start', 0)
+ if self.ops.get('end', None) is not None \
+ and value['end'] < end:
+ # truncated by previous slice:
+ end = self.ops['end']
+ else:
+ end = self.ops.get('end', None)
+ else:
+ start = self.ops.get('start', 0)
+ end = value.stop + start
+ if self.ops.get('end', None) is not None \
+ and self.ops['end'] < end:
+ end = self.ops['end']
+ return self.clone(start=start, end=end)
+ else:
+ if value < 0:
+ return list(iter(self))[value]
+ else:
+ start = self.ops.get('start', 0) + value
+ return list(self.clone(start=start, end=start+1))[0]
+
+ def __iter__(self):
+ conn = self.ops.get('connection', self.sourceClass._connection)
+ return conn.iterSelect(self)
+
+ def accumulate(self, expression):
+ """ Use an accumulate expression to select result
+ using another SQL select through current
+ connection.
+ Return the accumulate result
+ """
+ conn = self.ops.get('connection', self.sourceClass._connection)
+ return conn.accumulateSelect(self,expression)
+
+ def count(self):
+ """ Counting elements of current select results """
+ assert not self.ops.get('distinct'), "It is not currently supported to count distinct objects"
+
+ count = self.accumulate('COUNT(*)')
+ if self.ops.get('start'):
+ count -= self.ops['start']
+ if self.ops.get('end'):
+ count = min(self.ops['end'] - self.ops.get('start', 0), count)
+ return count
+
+ def sum(self, attribute):
+ """ Making the sum of a given select result attribute.
+ `attribute` can be a column name (like 'a_column')
+ or a dot-q attribute (like Table.q.aColumn)
+ """
+ if type(attribute) == type(''):
+ expression = 'SUM(%s)' % attribute
+ else:
+ expression = sqlbuilder.func.SUM(attribute)
+ return self.accumulate(expression)
+
+class InheritableSelectResults(SelectResults):
+ IterationClass = dbconnection.InheritableIteration
+
+ def __init__(self, sourceClass, clause, clauseTables=None,
+ **ops):
+ if clause is None or isinstance(clause, str) and clause == 'all':
+ clause = sqlbuilder.SQLTrueClause
+ tablesDict = sqlbuilder.tablesUsedDict(clause)
+ tablesDict[sourceClass._table] = 1
+ orderBy = ops.get('orderBy')
+ if orderBy and not isinstance(orderBy, basestring):
+ tablesDict.update(sqlbuilder.tablesUsedDict(orderBy))
+ #DSM: if this class has a parent, we need to link it
+ #DSM: and be sure the parent is in the table list.
+ #DSM: The following code is before clauseTables
+ #DSM: because if the user uses clauseTables
+ #DSM: (and normal string SELECT), he must know what he wants
+ #DSM: and will do himself the relationship between classes.
+ if type(clause) is not str:
+ tableRegistry = {}
+ allClasses = classregistry.registry(sourceClass._registry).allClasses()
+ for registryClass in allClasses:
+ if registryClass._table in tablesDict:
+ #DSM: By default, no parents are needed for the clauses
+ tableRegistry[registryClass] = registryClass
+ for registryClass in allClasses:
+ if registryClass._table in tablesDict:
+ currentClass = registryClass
+ while currentClass._parentClass:
+ currentClass = currentClass._parentClass
+ if tableRegistry.has_key(currentClass):
+ #DSM: Must keep the last parent needed
+ #DSM: (to limit the number of join needed)
+ tableRegistry[registryClass] = currentClass
+ #DSM: Remove this class as it is a parent one
+ #DSM: of a needed children
+ del tableRegistry[currentClass]
+ #DSM: Table registry contains only the last children
+ #DSM: or standalone classes
+ parentClause = []
+ for (currentClass, minParentClass) in tableRegistry.items():
+ while currentClass != minParentClass and currentClass._parentClass:
+ parentClass = currentClass._parentClass
+ parentClause.append(currentClass.q.id == parentClass.q.id)
+ currentClass = parentClass
+ tablesDict[currentClass._table] = 1
+ clause = reduce(sqlbuilder.AND, parentClause, clause)
+
+ super(InheritableSelectResults, self).__init__(sourceClass, clause, clauseTables,
+ **ops)
+
+
# SQLObject is the superclass for all SQLObject classes, of
# course. All the deeper magic is done in MetaSQLObject, and
# only lesser magic is done here. All the actual work is done
@@ -405,8 +629,11 @@
# aren't using integer IDs
_idType = int
- def get(cls, id, connection=None, selectResults=None, childResults=None, childUpdate=False):
+ # The law of Demeter: the class should not call another classes by name
+ SelectResultsClass = SelectResults
+ def get(cls, id, connection=None, selectResults=None):
+
assert id is not None, 'None is not a possible id for %s' % cls.__name
id = cls._idType(id)
@@ -427,20 +654,6 @@
cache.put(id, cls, val)
finally:
cache.finishPut(cls)
- #DSM: If we are updating a child, we should never return a child...
- if childUpdate: return val
- #DSM: If this class has a child, return the child
- if hasattr(val, 'childName'):
- childName = val.childName
- if childName is not None:
- return val._childClasses[childName].get(id, selectResults=childResults)
- #DSM: Now, we know we are alone or the last child in a family...
- #DSM: It's time to find our parents
- inst = val
- while inst._parentClass and not inst._parent:
- inst._parent = inst._parentClass.get(id, childUpdate=True)
- inst = inst._parent
- #DSM: We can now return ourself
return val
get = classmethod(get)
@@ -450,24 +663,7 @@
cls._SO_indexList.append(index)
addIndex = classmethod(addIndex)
- def addColumn(cls, columnDef, changeSchema=False, connection=None, childUpdate=False):
- #DSM: Try to add parent properties to the current class
- #DSM: Only do this once if possible at object creation and once for
- #DSM: each new dynamic column to refresh the current class
- if childUpdate or cls._parentClass:
- for col in cls._parentClass._columns:
- cname = col.name
- if cname == 'childName': continue
- setattr(cls, getterName(cname), eval(
- 'lambda self: self._parent.%s' % cname))
- if not col.kw.has_key('immutable') or not col.kw['immutable']:
- setattr(cls, setterName(cname), eval(
- 'lambda self, val: setattr(self._parent, %s, val)'
- % repr(cname)))
- if childUpdate:
- makeProperties(cls)
- return
-
+ def addColumn(cls, columnDef, changeSchema=False, connection=None):
column = columnDef.withClass(cls)
name = column.name
assert name != 'id', "The 'id' column is implicit, and should not be defined as a column"
@@ -582,11 +778,6 @@
if cls._SO_finishedClassCreation:
makeProperties(cls)
- #DSM: Update each child class if needed and existing (only for new
- #DSM: dynamic column as no child classes exists at object creation)
- for c in cls._childClasses.values():
- c.addColumn(columnDef, childUpdate=True)
-
addColumn = classmethod(addColumn)
def addColumnsFromDatabase(cls, connection=None):
@@ -637,34 +828,9 @@
if cls._SO_finishedClassCreation:
delattr(cls, name)
- #DSM: Update each child class if needed
- #DSM: and delete properties for this column
- for c in cls._childClasses.values():
- delattr(c, name)
-
delColumn = classmethod(delColumn)
- def addJoin(cls, joinDef, childUpdate=False):
- #DSM: Try to add parent properties to the current class
- #DSM: Only do this once if possible at object creation and once for
- #DSM: each new dynamic join to refresh the current class
- if childUpdate or cls._parentClass:
- for jdef in cls._parentClass._joins:
- join = jdef.withClass(cls)
- jname = join.joinMethodName
- jarn = join.addRemoveName
- setattr(cls, getterName(jname),
- eval('lambda self: self._parent.%s' % jname))
- if hasattr(join, 'remove'):
- setattr(cls, 'remove' + jarn,
- eval('lambda self,o: self._parent.remove%s(o)' % jarn))
- if hasattr(join, 'add'):
- setattr(cls, 'add' + jarn,
- eval('lambda self,o: self._parent.add%s(o)' % jarn))
- if childUpdate:
- makeProperties(cls)
- return
-
+ def addJoin(cls, joinDef):
# The name of the method we'll create. If it's
# automatically generated, it's generated by the
# join class.
@@ -713,11 +879,6 @@
if cls._SO_finishedClassCreation:
makeProperties(cls)
- #DSM: Update each child class if needed and existing (only for new
- #DSM: dynamic join as no child classes exists at object creation)
- for c in cls._childClasses.values():
- c.addJoin(joinDef, childUpdate=True)
-
addJoin = classmethod(addJoin)
def delJoin(cls, joinDef):
@@ -745,11 +906,6 @@
if cls._SO_finishedClassCreation:
delattr(cls, meth)
- #DSM: Update each child class if needed
- #DSM: and delete properties for this join
- for c in cls._childClasses.values():
- delattr(c, meth)
-
delJoin = classmethod(delJoin)
def _init(self, id, connection=None, selectResults=None):
@@ -1014,29 +1170,6 @@
self._create(id, **kw)
def _create(self, id, **kw):
-
- #DSM: If we were called by a children class,
- #DSM: we must retreive the properties dictionary.
- #DSM: Note: we can't use the ** call paremeter directly
- #DSM: as we must be able to delete items from the dictionary
- #DSM: (and our children must know that the items were removed!)
- fromChild = False
- if kw.has_key('kw'):
- kw = kw['kw']
- fromChild = True
- #DSM: If we are the children of an inheritable class,
- #DSM: we must first create our parent
- if self._parentClass:
- parentClass = self._parentClass
- parent_kw = dict(
- [(name, value) for (name, value) in kw.items()
- if hasattr(parentClass, name)
- ]
- )
- self._parent = parentClass(kw=parent_kw)
- self._parent.childName = self._className
- id = self._parent.id
-
self._SO_creating = True
self._SO_createValues = {}
self._SO_validatorState = SQLObjectState(self)
@@ -1129,7 +1262,7 @@
lazyColumns=False, reversed=False,
distinct=False,
connection=None):
- return SelectResults(cls, clause,
+ return cls.SelectResultsClass(cls, clause,
clauseTables=clauseTables,
orderBy=orderBy,
limit=limit,
@@ -1141,7 +1274,7 @@
def selectBy(cls, connection=None, **kw):
conn = connection or cls._connection
- return SelectResults(cls,
+ return cls.SelectResultsClass(cls,
conn._SO_columnClause(cls, kw),
connection=conn)
@@ -1314,222 +1447,154 @@
cls._connection = value
setConnection = classmethod(setConnection)
-def capitalize(name):
- return name[0].capitalize() + name[1:]
+class InheritableSQLObject(SQLObject):
+ SelectResultsClass = InheritableSelectResults
-def setterName(name):
- return '_set_%s' % name
-def rawSetterName(name):
- return '_SO_set_%s' % name
-def getterName(name):
- return '_get_%s' % name
-def rawGetterName(name):
- return '_SO_get_%s' % name
-def instanceName(name):
- return '_SO_val_%s' % name
+ def get(cls, id, connection=None, selectResults=None, childResults=None, childUpdate=False):
+ val = super(InheritableSQLObject, cls).get(id, connection, selectResults)
-try:
- basestring
-except NameError: # Python 2.2
- basestring = (types.StringType, types.UnicodeType)
+ #DSM: If we are updating a child, we should never return a child...
+ if childUpdate: return val
+ #DSM: If this class has a child, return the child
+ if hasattr(val, 'childName'):
+ childName = val.childName
+ if childName is not None:
+ return val._childClasses[childName].get(id, selectResults=childResults)
+ #DSM: Now, we know we are alone or the last child in a family...
+ #DSM: It's time to find our parents
+ inst = val
+ while inst._parentClass and not inst._parent:
+ inst._parent = inst._parentClass.get(id, childUpdate=True)
+ inst = inst._parent
+ #DSM: We can now return ourself
+ return val
-class SelectResults(object):
+ get = classmethod(get)
- def __init__(self, sourceClass, clause, clauseTables=None,
- **ops):
- self.sourceClass = sourceClass
- if clause is None or isinstance(clause, str) and clause == 'all':
- clause = sqlbuilder.SQLTrueClause
- self.clause = clause
- tablesDict = sqlbuilder.tablesUsedDict(self.clause)
- tablesDict[sourceClass._table] = 1
- orderBy = ops.get('orderBy')
- if orderBy and not isinstance(orderBy, basestring):
- tablesDict.update(sqlbuilder.tablesUsedDict(orderBy))
- #DSM: if this class has a parent, we need to link it
- #DSM: and be sure the parent is in the table list.
- #DSM: The following code is before clauseTables
- #DSM: because if the user uses clauseTables
- #DSM: (and normal string SELECT), he must know what he wants
- #DSM: and will do himself the relationship between classes.
- if type(self.clause) is not str:
- tableRegistry = {}
- allClasses = classregistry.registry(sourceClass._registry).allClasses()
- for registryClass in allClasses:
- if registryClass._table in tablesDict:
- #DSM: By default, no parents are needed for the clauses
- tableRegistry[registryClass] = registryClass
- for registryClass in allClasses:
- if registryClass._table in tablesDict:
- currentClass = registryClass
- while currentClass._parentClass:
- currentClass = currentClass._parentClass
- if tableRegistry.has_key(currentClass):
- #DSM: Must keep the last parent needed
- #DSM: (to limit the number of join needed)
- tableRegistry[registryClass] = currentClass
- #DSM: Remove this class as it is a parent one
- #DSM: of a needed children
- del tableRegistry[currentClass]
- #DSM: Table registry contains only the last children
- #DSM: or standalone classes
- parentClause = []
- for (currentClass, minParentClass) in tableRegistry.items():
- while currentClass != minParentClass and currentClass._parentClass:
- parentClass = currentClass._parentClass
- parentClause.append(currentClass.q.id == parentClass.q.id)
- currentClass = parentClass
- tablesDict[currentClass._table] = 1
- self.clause = reduce(sqlbuilder.AND, parentClause, clause)
+ def addColumn(cls, columnDef, changeSchema=False, connection=None, childUpdate=False):
+ #DSM: Try to add parent properties to the current class
+ #DSM: Only do this once if possible at object creation and once for
+ #DSM: each new dynamic column to refresh the current class
+ if childUpdate or cls._parentClass:
+ for col in cls._parentClass._columns:
+ cname = col.name
+ if cname == 'childName': continue
+ setattr(cls, getterName(cname), eval(
+ 'lambda self: self._parent.%s' % cname))
+ if not col.kw.has_key('immutable') or not col.kw['immutable']:
+ setattr(cls, setterName(cname), eval(
+ 'lambda self, val: setattr(self._parent, %s, val)'
+ % repr(cname)))
+ if childUpdate:
+ makeProperties(cls)
+ return
- if clauseTables:
- for table in clauseTables:
- tablesDict[table] = 1
- self.clauseTables = clauseTables
- self.tables = tablesDict.keys()
- self.ops = ops
- if self.ops.get('orderBy', NoDefault) is NoDefault:
- self.ops['orderBy'] = sourceClass._defaultOrder
- orderBy = self.ops['orderBy']
- if isinstance(orderBy, list) or isinstance(orderBy, tuple):
- orderBy = map(self._mungeOrderBy, orderBy)
- else:
- orderBy = self._mungeOrderBy(orderBy)
- #print "OUT: %r; in: %r" % (sourceClass.sqlrepr(orderBy), sourceClass.sqlrepr(self.ops['orderBy']))
- self.ops['dbOrderBy'] = orderBy
- if ops.has_key('connection') and ops['connection'] is None:
- del ops['connection']
+ super(InheritableSQLObject, cls).addColumn(columnDef, changeSchema, connection)
- def _mungeOrderBy(self, orderBy):
- if isinstance(orderBy, str) and orderBy.startswith('-'):
- orderBy = orderBy[1:]
- desc = True
- else:
- desc = False
- if isinstance(orderBy, (str, unicode)):
- if self.sourceClass._SO_columnDict.has_key(orderBy):
- val = self.sourceClass._SO_columnDict[orderBy].dbName
- if desc:
- return '-' + val
- else:
- return val
- else:
- if desc:
- return '-' + orderBy
- else:
- return orderBy
- else:
- return orderBy
+ #DSM: Update each child class if needed and existing (only for new
+ #DSM: dynamic column as no child classes exists at object creation)
+ for c in cls._childClasses.values():
+ c.addColumn(columnDef, childUpdate=True)
- def clone(self, **newOps):
- ops = self.ops.copy()
- ops.update(newOps)
- return self.__class__(self.sourceClass, self.clause,
- self.clauseTables, **ops)
+ addColumn = classmethod(addColumn)
- def orderBy(self, orderBy):
- return self.clone(orderBy=orderBy)
+ def delColumn(cls, column, changeSchema=False, connection=None):
+ super(InheritableSQLObject, cls).delColumn(column, changeSchema, connection)
- def connection(self, conn):
- return self.clone(connection=conn)
+ #DSM: Update each child class if needed
+ #DSM: and delete properties for this column
+ for c in cls._childClasses.values():
+ delattr(c, name)
- def limit(self, limit):
- return self[:limit]
+ delColumn = classmethod(delColumn)
- def lazyColumns(self, value):
- return self.clone(lazyColumns=value)
+ def addJoin(cls, joinDef, childUpdate=False):
+ #DSM: Try to add parent properties to the current class
+ #DSM: Only do this once if possible at object creation and once for
+ #DSM: each new dynamic join to refresh the current class
+ if childUpdate or cls._parentClass:
+ for jdef in cls._parentClass._joins:
+ join = jdef.withClass(cls)
+ jname = join.joinMethodName
+ jarn = join.addRemoveName
+ setattr(cls, getterName(jname),
+ eval('lambda self: self._parent.%s' % jname))
+ if hasattr(join, 'remove'):
+ setattr(cls, 'remove' + jarn,
+ eval('lambda self,o: self._parent.remove%s(o)' % jarn))
+ if hasattr(join, 'add'):
+ setattr(cls, 'add' + jarn,
+ eval('lambda self,o: self._parent.add%s(o)' % jarn))
+ if childUpdate:
+ makeProperties(cls)
+ return
- def reversed(self):
- return self.clone(reversed=not self.ops.get('reversed', False))
+ super(InheritableSQLObject, cls).addJoin(joinDef)
- def distinct(self):
- return self.clone(distinct=True)
+ #DSM: Update each child class if needed and existing (only for new
+ #DSM: dynamic join as no child classes exists at object creation)
+ for c in cls._childClasses.values():
+ c.addJoin(joinDef, childUpdate=True)
- def __getitem__(self, value):
- if type(value) is type(slice(1)):
- assert not value.step, "Slices do not support steps"
- if not value.start and not value.stop:
- # No need to copy, I'm immutable
- return self
+ addJoin = classmethod(addJoin)
- # Negative indexes aren't handled (and everything we
- # don't handle ourselves we just create a list to
- # handle)
- if (value.start and value.start < 0) \
- or (value.stop and value.stop < 0):
- if value.start:
- if value.stop:
- return list(self)[value.start:value.stop]
- return list(self)[value.start:]
- return list(self)[:value.stop]
+ def delJoin(cls, joinDef):
+ super(InheritableSQLObject, cls).delJoin(joinDef)
+ #DSM: Update each child class if needed
+ #DSM: and delete properties for this join
+ for c in cls._childClasses.values():
+ delattr(c, meth)
- if value.start:
- assert value.start >= 0
- start = self.ops.get('start', 0) + value.start
- if value.stop is not None:
- assert value.stop >= 0
- if value.stop < value.start:
- # an empty result:
- end = start
- else:
- end = value.stop + self.ops.get('start', 0)
- if self.ops.get('end', None) is not None \
- and value['end'] < end:
- # truncated by previous slice:
- end = self.ops['end']
- else:
- end = self.ops.get('end', None)
- else:
- start = self.ops.get('start', 0)
- end = value.stop + start
- if self.ops.get('end', None) is not None \
- and self.ops['end'] < end:
- end = self.ops['end']
- return self.clone(start=start, end=end)
- else:
- if value < 0:
- return list(iter(self))[value]
- else:
- start = self.ops.get('start', 0) + value
- return list(self.clone(start=start, end=start+1))[0]
+ delJoin = classmethod(delJoin)
- def __iter__(self):
- conn = self.ops.get('connection', self.sourceClass._connection)
- return conn.iterSelect(self)
+ def _create(self, id, **kw):
- def accumulate(self, expression):
- """ Use an accumulate expression to select result
- using another SQL select through current
- connection.
- Return the accumulate result
- """
- conn = self.ops.get('connection', self.sourceClass._connection)
- return conn.accumulateSelect(self,expression)
+ #DSM: If we were called by a children class,
+ #DSM: we must retreive the properties dictionary.
+ #DSM: Note: we can't use the ** call paremeter directly
+ #DSM: as we must be able to delete items from the dictionary
+ #DSM: (and our children must know that the items were removed!)
+ if kw.has_key('kw'):
+ kw = kw['kw']
+ #DSM: If we are the children of an inheritable class,
+ #DSM: we must first create our parent
+ if self._parentClass:
+ parentClass = self._parentClass
+ parent_kw = dict(
+ [(name, value) for (name, value) in kw.items()
+ if hasattr(parentClass, name)
+ ]
+ )
+ self._parent = parentClass(kw=parent_kw)
+ self._parent.childName = self._className
+ id = self._parent.id
- def count(self):
- """ Counting elements of current select results """
- assert not self.ops.get('distinct'), "It is not currently supported to count distinct objects"
+ super(InheritableSQLObject, self)._create(id, **kw)
- count = self.accumulate('COUNT(*)')
- if self.ops.get('start'):
- count -= self.ops['start']
- if self.ops.get('end'):
- count = min(self.ops['end'] - self.ops.get('start', 0), count)
- return count
+ def destroySelf(self):
+ # Kills this object. Kills it dead!
+ #DSM: If this object has parents, recursivly kill them
+ if hasattr(self, '_parent') and self._parent:
+ self._parent.destroySelf()
+ super(InheritableSQLObject, self).destroySelf()
- def sum(self, attribute):
- """ Making the sum of a given select result attribute.
- `attribute` can be a column name (like 'a_column')
- or a dot-q attribute (like Table.q.aColumn)
- """
- if type(attribute) == type(''):
- expression = 'SUM(%s)' % attribute
- else:
- expression = sqlbuilder.func.SUM(attribute)
- return self.accumulate(expression)
+def capitalize(name):
+ return name[0].capitalize() + name[1:]
+def setterName(name):
+ return '_set_%s' % name
+def rawSetterName(name):
+ return '_SO_set_%s' % name
+def getterName(name):
+ return '_get_%s' % name
+def rawGetterName(name):
+ return '_SO_get_%s' % name
+def instanceName(name):
+ return '_SO_val_%s' % name
+
+
class SQLObjectState(object):
def __init__(self, soObject):
@@ -1569,6 +1634,6 @@
else:
return obj
-__all__ = ['NoDefault', 'SQLObject',
- 'getID', 'getObject',
+__all__ = ['SQLObject', 'InheritableSQLObject',
+ 'NoDefault', 'getID', 'getObject',
'SQLObjectNotFound']
Modified: home/phd/SQLObject/inheritance/tests/test_inheritance.py
===================================================================
--- home/phd/SQLObject/inheritance/tests/test_inheritance.py 2005-02-03 12:13:26 UTC (rev 568)
+++ home/phd/SQLObject/inheritance/tests/test_inheritance.py 2005-02-03 12:14:28 UTC (rev 569)
@@ -1,9 +1,9 @@
-from sqlobject import SQLObject, StringCol
+from sqlobject import InheritableSQLObject, StringCol
from test_sqlobject import SQLObjectTest, main
# 'i' prepended to distinguish it from test_sqlobject.Person
-class iPerson(SQLObject):
+class iPerson(InheritableSQLObject):
_inheritable = 1 # I want this class to be inherited
firstName = StringCol()
lastName = StringCol()
|
|
From: <sub...@co...> - 2005-02-03 12:13:33
|
Author: phd
Date: 2005-02-03 12:13:26 +0000 (Thu, 03 Feb 2005)
New Revision: 568
Modified:
home/phd/SQLObject/inheritance/sqlobject/postgres/pgconnection.py
Log:
Merged binary support from the trunk.
Modified: home/phd/SQLObject/inheritance/sqlobject/postgres/pgconnection.py
===================================================================
--- home/phd/SQLObject/inheritance/sqlobject/postgres/pgconnection.py 2005-02-03 12:06:16 UTC (rev 567)
+++ home/phd/SQLObject/inheritance/sqlobject/postgres/pgconnection.py 2005-02-03 12:13:26 UTC (rev 568)
@@ -29,6 +29,10 @@
registerConverter(type(psycopg.Binary('')),
PsycoBinaryConverter)
+ self.user = user
+ self.host = host
+ self.db = db
+ self.password = passwd
if dsn is None:
dsn = []
if db:
|
|
From: <sub...@co...> - 2005-02-03 12:06:35
|
Author: phd Date: 2005-02-03 12:06:16 +0000 (Thu, 03 Feb 2005) New Revision: 567 Removed: home/phd/SQLObject/inheritance/docs/Plan06.txt Modified: home/phd/SQLObject/inheritance/docs/News.txt home/phd/SQLObject/inheritance/sqlobject/col.py home/phd/SQLObject/inheritance/sqlobject/dbconnection.py Log: Merged changes from the trunk, version 0.6.1. Modified: home/phd/SQLObject/inheritance/docs/News.txt =================================================================== --- home/phd/SQLObject/inheritance/docs/News.txt 2005-02-01 08:01:49 UTC (rev 566) +++ home/phd/SQLObject/inheritance/docs/News.txt 2005-02-03 12:06:16 UTC (rev 567) @@ -7,8 +7,8 @@ .. _start: -SVN trunk -========= +SQLObject 0.6.1 +=============== Interface Changes ----------------- @@ -75,7 +75,7 @@ rows (like class instantiation used to do). * We're now using a Subversion repository instead of CVS. It is - located at svn://colorstudy.com/trunk/SQLObject + located at http://svn.colorstudy.com/trunk/SQLObject * If you pass ``forceDBName=True`` to the ``*Col`` constructors, then your column name doesn't have to be restricted to a-z, 0-9, and _. Deleted: home/phd/SQLObject/inheritance/docs/Plan06.txt =================================================================== --- home/phd/SQLObject/inheritance/docs/Plan06.txt 2005-02-01 08:01:49 UTC (rev 566) +++ home/phd/SQLObject/inheritance/docs/Plan06.txt 2005-02-03 12:06:16 UTC (rev 567) @@ -1,306 +0,0 @@ -SQLObject 0.6 -============= - -*A tentative plan, 20 Jan 2004* - -Introduction ------------- - -During vacation I thought about some changes that I might like to make -to SQLObject. Several of these change the API, but not too -drastically, and I think they change the API for the better. And we'd -not at 1.0 yet, changes are still allowed! Here's my ideas... - -Editing Context ---------------- - -Taken from Modeling, the "editing context" is essentially a -transaction, though it also encompasses some other features. -Typically it is used to distinguish between separate contexts in a -multi-threaded program. - -This is intended to separate several distinct concepts: - -* The database backend (MySQL, PostgreSQL, etc), coupled with the - driver (MySQLdb, psycopg, etc). (Should the driver be part of the - connection parameters?) -* The connection parameters. Typically these are the server host, - username, and password, but they could also be a filename or other - path. Perhaps this could be represented with a URI, ala PEAK, but - I also dislike taking structured data and destructuring it (i.e., - packing it into a string). OTOH, URLs are structured, even if they - require some parsing. Serialization of URLs is free and highly - transparent. Python syntax is well structured and - *programmatically* considerably more transparent (in a robust - fashion), but also programmatically fairly read-only (because it is - embedded in the structure of Python source code). We can also have - both. -* The database transactional context. -* The application transactional context (preferably these two would - be seemless, but they still represent somewhat distinct entities, - and a portability layer might be nice). The application's - transactional context may include other transactions -- e.g., - multiple databases, a ZODB transaction, etc. -* The cache policy. There are many different kinds of caches - potentially involved, include write batching, and per-object and - per-table caches, connection pooling, and so on. -* Classes, which on the database side are typically tables. (This - proposal does not attempt to de-couple classes and tables) - -Example:: - - from SQLObject import EditingContext - ec = EditingContext() - # every editing context automatically picks up all the SQLObject - # classes, all magic like. - person = ec.Person.get(1) # by ID - ec2 = EditingContext() # separate transaction - person2 = ec.Person.get(1) - assert person is not person2 - assert person.id == person2.id - assert person.fname == 'Guy' - person.fname = 'Gerald' - assert person2.fname == 'Guy' - ec.commit() # SQL is not sent to server - assert person2.fname == 'Guy' # Doesn't see changes - person2.fname = 'Norm' - # raises exception if locking is turned on; overwrites if locking - # is not turned on. (Locking enabled on a per-class level) - -I'm not at all sure about that example. Mostly the confusing parts -relate to locking and when the database lookup occurs (and how late a -conflict exception may be raised). - -Somewhere in here, process-level transactions might fit in. That is, -even on a backend that doesn't support transactions, we can still -delay SQL statements until a commit/rollback is performed. In turn, -we can create temporary "memory" objects, which is any object which -hasn't been committed to the database in any way. To do this we'll -need sequences -- to preallocate IDs -- which MySQL and SQLite don't -really provide :( - -Nested transactions...? Maybe they'd fall out of this fairly easily, -especially if we define a global context, with global caches etc., -then further levels of context will come for free. - -We still need to think about an auto-commit mode. Maybe the global -context would be auto-commit. - -Caching -------- - -Really doing transactions right means making caching significantly -more complex. If the cache is purely transaction-specific, then we'll -really be limiting the effectiveness of the cache. With that in mind, -a copy-on-write style of object is really called for -- when you fetch -an object in a transaction, you can use the globally cached instance -until you write to the object. - -Really this isn't copy-on-write, it's more like a proxy object. Until -the object is changed, it can delegate all its columns to its global -object for which it is a proxy. Of course, traversal via foreign keys -or joins must also return proxied objects. As the object is changed --- perhaps on a column-by-column basis, or as a whole on the first -change -- the object takes on the personality of a full SQLObject -instance. - -When the transaction is committed, this transactional object copies -itself to the global object, and becomes a full proxy. These -transactional caches themselves should be pooled -- so that when -another transaction comes along you have a potentially useful set of -proxy objects already created for you. This is a common use case for -web applications, which have lots of short transactions, which are -often very repetitive. - -In addition to this, there should be more cache control. This means -explicit ways to control things like: - -1. Caching of instances: - - * Application/process-global definition. - * Database-level definition. - * Transaction/EditingContext-level definition. - * Class-level definition. - -2. Caching of columns: - - * Class-level. - -3. Cache sweep frequency: - - * Application/process-global. - * Database-level. - * Class-level. - * Doesn't need to be as complete as 1; maybe on the class level you - could only indicate that a certain class should not be sweeped. - * Sweep during a fetch (e.g., every 100 fetches), by time or fetch - frequency, or sweep with an explicit call (e.g., to do sweeps in - a separate thread). - -4. Cache sweep policy: - - * Maximum age. - * Least-recently-used (actually, least-recently-fetched). - * Random (the current policy). - * Multi-level (randomly move objects to a lower-priority cache, - raise level when the object is fetched again). - * Target cache size (keep trimming until the cache is small - enough). - * Simple policy (if enough objects qualify, cache can be of any - size). - * Percentage culling (e.g., kill 33% of objects for each sweep; - this is the current policy). - -5. Batching of updates (whether updates should immediately go to the - database, or whether it would be batched until a commit or other - signal). - -6. Natural expiring of objects. Even if an object must persist - because there are still references, we could expire it so that - future accesses re-query the database. To avoid stale data. - -Expose some methods of the cache, like getting all objects currently -in memory. These would probably be exposed on a class level, e.g., -all the Addresses currently in memory via -``Address.cache.current()`` or something. What about when there's a -cached instance in the parent context, but not in the present -transaction? - -Columns as Descriptors ----------------------- - -Each column will become a descriptor. That is, ``Col`` and subclasses -will return an object with ``__get__`` and ``__set__`` methods. The -metaclass will not itself generate methods. - -A metaclass will still be used so that the descriptor can be tied to -its name, e.g., that with ``fname = StringCol()``, the resultant -descriptor will know that it is bound to ``fname``. - -By using descriptors, introspection should become a bit easier -- or -at least more uniform with respect to other new-style classes. -Various class-wide indexes of columns will still be necessary, but -these should be able to remain mostly private. - -To customize getters or setters (which you currently do by defining a -``_get_columnName`` or ``_set_columnName`` method), you will pass -arguments to the ``Col`` object, like:: - - def _get_name(self, dbGetter): - return dbGetter().strip() - - name = StringCol(getter=_get_name) - -This gets rid of ``_SO_get_columnName`` as well. We can -transitionally add something to the metaclass to signal an error if a -spurious ``_get_columnName`` method is sitting around. - -Construction and Fetching -------------------------- - -Currently you fetch an object with class instantiation, e.g., -``Address(1)``. This may or may not create a new instance, and does -not create a table row. If you want to create a table row, you do -something like ``Address.new(city='New York', ...)``. This is -somewhat in contrast to normal Python, where class instantiation -(calling a class) will create a new object, while objects are fetched -otherwise (with no particular standard interface). - -To make SQLObject classes more normal in this case, ``new`` will -become ``__init__`` (more or less), and classes will have a ``get`` -method that gets an already-existant row. E.g., ``Address.get(1)`` -vs. ``Address(city='New York', ...)``. This is perhaps the most -significant change in SQLObject usage. Because of the different -signatures, if you forget to make a change someplace you will get an -immediate exception, so updating code should not be too hard. - -Extra Table Information ------------------------ - -People have increasingly used SQLObject to create tables, and while it -can make a significant number of schemas, there are several extensions -of table generation that people occasionally want. Since these occur -later in development, it would be convenient if SQLObject could grow -as the complexity of the programs using it grow. Some of these -extensions are: - -* Table name (``_table``). -* Table type for MySQL (e.g., MyISAM vs. InnoDB). -* Multi-column unique constraints. (Other constraints?) -* Indexes. (Function or multi-column indexes?) -* Primary key type. (Primary key generation?) -* Primary key sequence names (for Postgres, Firebird, Oracle, etc). -* Multi-column primary keys. -* Naming scheme. -* Permissions. -* Locking (e.g., optimistic locking). -* Inheritance (see Daniel Savard's recent patch). -* Anything else? - -Some of these may be globally defined, or defined for an entire -database. For example, typically you'll want to use a common MySQL -table type for your entire database, even though its defined on a -per-table basis. And while MySQL allows global permission -declarations, Postgres does not and requires tedious repetitions of -the permissions for each table -- so while it's applied on a per-table -basis, it's likely that (at least to some degree) a per-database -declaration is called for. Naming schemes are also usually -database-wide. - -As these accumulate -- and by partitioning this list differently, the -list could be even longer -- it's messy to do these all as special -class variables (``_idName``, etc). It also makes the class logic and -its database implementation details difficult to distinguish. Some -of these can be handled elegantly like ``id = StringCol()`` or ``id -= ("fname", "lname")``. But the others perhaps should be put into a -single instance variable, perhaps itself a class:: - - class Address(SQLObject): - class SQLMeta: - mysqlType = 'InnoDB' - naming = Underscore - permission = {'bob': ['select', 'insert'], - 'joe': ['select', 'insert', 'update'], - 'public': ['select']} - street = StringCol() - .... - -The metadata is found by its name (``SQLMeta``), and is simply a -container. The class syntax is easier to write and read than a -dictionary-like syntax. Or, it could be a proper class/instance and -provide a partitioned way to handle introspection. E.g., -``Address.SQLMeta.permission.get('bob')`` or -``Address.SQLMeta.columns``. In this case values that weren't -overridden would be calculated from defaults (like the default naming -scheme and so on). - -I'm not at all certain about how this should look, or if there are -other things that should go into the class-meta-data object. - -Joins, Foreign Keys -------------------- - -First, the poorly-named ``MultipleJoin`` and ``RelatedJoin`` (which -are rather ambiguous) will be renamed ``ManyToOneJoin`` and -``ManyToManyJoin``. ``OneToOneJoin`` will also be added, while -``ForeignKey`` remains the related column type. (Many2Many? -Many2many? many2many?) - -ForeignKey will be driven by a special validator/converter. (But will -this make ID access more difficult?) - -Joins will return smart objects which can be iterated across. These -smart objects will be related to ``SelectResults``, and allow the -same features like ordering. In both cases, an option to retrieve -IDs instead of objects will be allowed. - -These smarter objects will allow, in the case of ManyToManyJoin, -``Set`` like operations to relate (or unrelate) objects. For -ManyToOneJoin the list/set operations are not really appropriate, -because they would reassign the relation, not just add or remove -relations. - -It would be nice to make the Join protocol more explicit and public, -so other kinds of joins (e.g., three-way) could be more accessible. - - Modified: home/phd/SQLObject/inheritance/sqlobject/col.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/col.py 2005-02-01 08:01:49 UTC (rev 566) +++ home/phd/SQLObject/inheritance/sqlobject/col.py 2005-02-03 12:06:16 UTC (rev 567) @@ -52,11 +52,10 @@ else: mxdatetime_available = True -if datetime_available: - DATETIME_IMPLEMENTATION = "datetime" +DATETIME_IMPLEMENTATION = "datetime" +MXDATETIME_IMPLEMENTATION = "mxDateTime" if mxdatetime_available: - MXDATETIME_IMPLEMENTATION = "mxDateTime" DateTimeType = type(DateTime.now()) if datetime_available: @@ -716,7 +715,7 @@ def fromPython(self, value, state): if value is None: return None - if isinstance(value, (datetime.date, datetime.datetime)): + if isinstance(value, (datetime.date, datetime.datetime, sqlbuilder.SQLExpression)): return value if hasattr(value, "strftime"): return value.strftime(self.format) @@ -726,7 +725,7 @@ def toPython(self, value, state): if value is None: return None - if isinstance(value, (datetime.date, datetime.datetime)): + if isinstance(value, (datetime.date, datetime.datetime, sqlbuilder.SQLExpression)): return value if mxdatetime_available and isinstance(value, DateTimeType): # convert mxDateTime instance to datetime @@ -748,7 +747,7 @@ def fromPython(self, value, state): if value is None: return None - if isinstance(value, DateTimeType): + if isinstance(value, (DateTimeType, sqlbuilder.SQLExpression)): return value if hasattr(value, "strftime"): return value.strftime(self.format) @@ -758,7 +757,7 @@ def toPython(self, value, state): if value is None: return None - if isinstance(value, DateTimeType): + if isinstance(value, (DateTimeType, sqlbuilder.SQLExpression)): return value if datetime_available: # convert datetime instance to mxDateTime if isinstance(value, datetime.datetime): Modified: home/phd/SQLObject/inheritance/sqlobject/dbconnection.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/dbconnection.py 2005-02-01 08:01:49 UTC (rev 566) +++ home/phd/SQLObject/inheritance/sqlobject/dbconnection.py 2005-02-03 12:06:16 UTC (rev 567) @@ -132,17 +132,17 @@ self._poolLock.acquire() try: if not self._pool: - newConn = self.makeConnection() - self._pool.append(newConn) - self._connectionNumbers[id(newConn)] = self._connectionCount + conn = self.makeConnection() + self._connectionNumbers[id(conn)] = self._connectionCount self._connectionCount += 1 - val = self._pool.pop() + else: + conn = self._pool.pop() if self.debug: s = 'ACQUIRE' if self._pool is not None: s += ' pool=[%s]' % ', '.join([str(self._connectionNumbers[id(v)]) for v in self._pool]) - self.printDebug(val, s, 'Pool') - return val + self.printDebug(conn, s, 'Pool') + return conn finally: self._poolLock.release() @@ -177,6 +177,8 @@ # the __del__ in Iteration (unfortunately, not sure why # it happens) self._pool.append(conn) + else: + conn.close() def printDebug(self, conn, s, name, type='query'): if type == 'query': @@ -479,12 +481,21 @@ self.close() def close(self): - if self._pool: - for conn in self._pool: + if not self._pool: + return + self._poolLock.acquire() + try: + conns = self._pool[:] + self._pool[:] = [] + for conn in conns: try: conn.close() except self.module.Error: pass + del conn + del conns + finally: + self._poolLock.release() class Iteration(object): #phd: default array size for cursor.fetchmany() |
|
From: <sub...@co...> - 2005-02-01 08:01:54
|
Author: ianb Date: 2005-02-01 08:01:49 +0000 (Tue, 01 Feb 2005) New Revision: 566 Removed: trunk/SQLObject/test trunk/SQLObject/tests/ Log: Removed tests that are redundant with the new py.test tests Deleted: trunk/SQLObject/test =================================================================== --- trunk/SQLObject/test 2005-02-01 07:59:43 UTC (rev 565) +++ trunk/SQLObject/test 2005-02-01 08:01:49 UTC (rev 566) @@ -1,18 +0,0 @@ -#!/bin/sh -VERSION="" -if [ "$1" = "2.3" ] ; then - VERSION=2.3 - shift -elif [ "$1" = "2.2" ] ; then - VERSION=2.2 - shift -fi - -if [ "$1" = "cover" ] ; then - shift - sudo python$VERSION setup.py -q install - python$VERSION ./tests/coverage.py -x tests/test_sqlobject.py $* - ./tests/coverage.py -a `find tests SQLObject -name '*.py'` -else - sudo python$VERSION setup.py -q install && python$VERSION tests/test_sqlobject.py $* -fi |
|
From: <sub...@co...> - 2005-02-01 07:59:45
|
Author: ianb Date: 2005-02-01 07:59:43 +0000 (Tue, 01 Feb 2005) New Revision: 565 Added: trunk/SQLObject/sqlobject/tests/ trunk/SQLObject/sqlobject/tests/__init__.py trunk/SQLObject/sqlobject/tests/dbtest.py trunk/SQLObject/sqlobject/tests/test_auto.py trunk/SQLObject/sqlobject/tests/test_basic.py trunk/SQLObject/sqlobject/tests/test_blob.py trunk/SQLObject/sqlobject/tests/test_cache.py trunk/SQLObject/sqlobject/tests/test_constraints.py trunk/SQLObject/sqlobject/tests/test_converters.py trunk/SQLObject/sqlobject/tests/test_datetime.py trunk/SQLObject/sqlobject/tests/test_delete.py trunk/SQLObject/sqlobject/tests/test_distinct.py trunk/SQLObject/sqlobject/tests/test_enum.py trunk/SQLObject/sqlobject/tests/test_expire.py trunk/SQLObject/sqlobject/tests/test_indexes.py trunk/SQLObject/sqlobject/tests/test_inheritance.py trunk/SQLObject/sqlobject/tests/test_joins.py trunk/SQLObject/sqlobject/tests/test_lazy.py trunk/SQLObject/sqlobject/tests/test_picklecol.py trunk/SQLObject/sqlobject/tests/test_select.py trunk/SQLObject/sqlobject/tests/test_slice.py trunk/SQLObject/sqlobject/tests/test_sorting.py trunk/SQLObject/sqlobject/tests/test_stringid.py trunk/SQLObject/sqlobject/tests/test_style.py trunk/SQLObject/sqlobject/tests/test_transactions.py trunk/SQLObject/sqlobject/tests/test_unicode.py trunk/SQLObject/sqlobject/tests/test_validation.py Modified: trunk/SQLObject/docs/DeveloperGuide.txt Log: Refactored tests into py.test framework; included documentation about how to use the new tests. Modified: trunk/SQLObject/docs/DeveloperGuide.txt =================================================================== --- trunk/SQLObject/docs/DeveloperGuide.txt 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/docs/DeveloperGuide.txt 2005-02-01 07:59:43 UTC (rev 565) @@ -26,16 +26,19 @@ * I don't stress too much on line length. But try to break lines up by grouping with parenthesis instead of with backslashes (if you - can). + can). Do asserts like:: + assert some_condition(a, b), ( + "Some condition failed, %r isn't right!" % a) + * But if you are having problems with line length, maybe you should just break the expression up into multiple statements. * Blank lines between methods, unless they are very small and closely bound to each other. -* Never use the form ``condition and trueValue or falseValue``. Break - it out and use a variable. +* *Never* use the form ``condition and trueValue or falseValue``. + Break it out and use a variable. * Careful of namespace pollution. SQLObject does allow for ``from sqlobject import *`` so names should be fairly distinct, or they @@ -122,27 +125,42 @@ Tests are important. Tests keep everything from falling apart. All new additions should have tests. -Right now all the tests are in one big file ``tests.py``. Which is -unfortunate, but that's the way it is. They may seem complex, but -they aren't so bad really. They all subclass from ``SQLObjectTest``. +Testing uses `py.test`__, an alternative to ``unittest``. It is +available via subversion at http://codespeak.net/svn/py/dist. Read +its `getting started`_ document for more. -The ``classes`` attribute is special in a test class. This is a list -of `SQLObject` subclasses that this test uses. `SQLObjectTest` will -create the tables before the tests are run, and destroy them after. +.. __: http://codespeak.net/py/current/doc/test.html +.. _getting started: http://codespeak.net/py/current/doc/getting_started.html -You may also define an ``.inserts()`` method. This method sets up the -basic data. When doing verbose input (``-vv``) you won't see these -inserts, since they may be overwhelming. Use the command-line options -``--inserts`` to show them (as well as the create statement. +To actually run the test, you have to give it a database to connect +to. You do this with the ``TESTDB`` environmental variable (right now +py.test doesn't have a better way to add custom options). You can +give the complete URI to access your test database, or you can give it +a shortcut like ``mysql`` (these shortcuts are defined in the top of +``tests/dbtest.py``. -When running tests, use ``-ddbname`` to test with ``dbname`` (e.g., -``-dmysql``, ``-dpostgres``, etc), or ``-dall`` to use Postgres, -MySQL, Firebird, and SQLite (all the core supported databases; -everything I have installed on my computer). Please try to test as -many databases as you can. At least SQLite and one other should be -easy, though if you can test both Postgres and MySQL that would be -much better. +All the tests are modules in ``sqlobject/tests``. Each module tests +one kind of feature, more or less. If you are testing a module, call +the test module ``tests/test_modulename.py`` -- only modules that +start with ``test_`` will be picked up by py.test. +The "framework" for testing is in ``tests/dbtest``. There's a couple +important functions: + +``setupClass(soClass)`` creates the tables for the class. It tries to +avoid recreating tables if not necessary. + +``supports(featureName)`` checks if the database backend supports the +named feature. What backends support what is defined at the top of +``dbtest``. + +If you ``import *`` you'll also get py.test's version of raises_, an +``inserts`` function that can create instances for you, and a couple +miscellaneous functions. + +.. _raises: http://codespeak.net/py/current/doc/test.html#id4 + + If you submit a patch or implement a feature without a test, I'll be forced to write the test. That's no fun for me, to just be writing tests. So please, write tests; everything at least needs to be Added: trunk/SQLObject/sqlobject/tests/__init__.py =================================================================== --- trunk/SQLObject/sqlobject/tests/__init__.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/__init__.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1 @@ +# Added: trunk/SQLObject/sqlobject/tests/dbtest.py =================================================================== --- trunk/SQLObject/sqlobject/tests/dbtest.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/dbtest.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,218 @@ +""" +The framework for making database tests. +""" + +import os +import re +import sqlobject +from py.test import raises + +connectionShortcuts = { + 'mysql': 'mysql://test@localhost/test', + 'dbm': 'dbm:///data', + 'postgres': 'postgres:///test', + 'postgresql': 'postgres:///test', + 'pygresql': 'pygresql://localhost/test', + 'sqlite': 'sqlite:///%s/data/sqlite.data' % os.getcwd(), + 'sybase': 'sybase://test:test123@sybase/test?autoCommit=0', + 'firebird': 'firebird://sysdba:masterkey@localhost/var/lib/firebird/data/test.gdb', + } + +""" +supportsMatrix defines what database backends support what features. +Each feature has a name, if you see a key like '+featureName' then +only the databases listed support the feature. Conversely, +'-featureName' means all databases *except* the ones listed support +the feature. The databases are given by their SQLObject string name, +separated by spaces. + +The function supports(featureName) returns True or False based on this, +and you can use it like:: + + def test_featureX(): + if not supports('featureX'): + return +""" +supportsMatrix = { + '+restrictedEnum': 'postgres', + '-transactions': 'mysql', + '-dropTableCascade': 'sybase', + '-dynamicColumn': 'sqlite sybase', + '-fromDatabase': 'sqlite sybase firebird', + '-expressionIndex': 'mysql sqlite firebird', + } + + +def setupClass(soClass): + """ + Makes sure the class has a corresponding and correct table. + This won't recreate the table if it already exists. It will + check that the table is properly defined (in case you change + your table definition). + """ + connection = getConnection() + soClass._connection = connection + installOrClear(soClass) + return soClass + +installedDBFilename = os.path.join(os.getcwd(), 'dbs_data.tmp') + +installedDBTracker = sqlobject.connectionForURI( + 'sqlite:///' + installedDBFilename) + +def getConnection(): + name = os.environ.get('TESTDB') + assert name, 'You must set $TESTDB to do database operations' + if connectionShortcuts.has_key(name): + name = connectionShortcuts[name] + return sqlobject.connectionForURI(name) + +connection = getConnection() + +class InstalledTestDatabase(sqlobject.SQLObject): + """ + This table is set up in SQLite (always, regardless of $TESTDB) and + tracks what tables have been set up in the 'real' database. This + way we don't keep recreating the tables over and over when there + are multiple tests that use a table. + """ + + _connection = installedDBTracker + tableName = sqlobject.StringCol(notNull=True) + createSQL = sqlobject.StringCol(notNull=True) + connectionURI = sqlobject.StringCol(notNull=True) + + def installOrClear(cls, soClass): + cls.setup() + table = soClass._table + if not soClass._connection.tableExists(table): + cls.install(soClass) + items = list(cls.selectBy( + tableName=table, + connectionURI=soClass._connection.uri())) + if items: + instance = items[0] + sql = instance.createSQL + else: + sql = None + newSQL = soClass.createTableSQL() + if sql != newSQL: + if sql is not None: + instance.destroySelf() + cls.drop(soClass) + cls.install(soClass) + else: + cls.clear(soClass) + installOrClear = classmethod(installOrClear) + + def install(cls, soClass): + """ + Creates the given table in its database. + """ + sql = getattr(soClass, soClass._connection.dbName + 'Create', + None) + if sql: + soClass._connection.query(sql) + else: + sql = soClass.createTableSQL() + soClass.createTable() + cls(tableName=soClass._table, + createSQL=sql, + connectionURI=soClass._connection.uri()) + install = classmethod(install) + + def drop(cls, soClass): + """ + Drops a the given table from its database + """ + sql = getattr(soClass, soClass._connection.dbName + 'Drop', None) + if sql: + soClass._connection.query(sql) + else: + soClass.dropTable() + drop = classmethod(drop) + + def clear(cls, soClass): + """ + Removes all the rows from a table. + """ + soClass.clearTable() + clear = classmethod(clear) + + def setup(cls): + """ + This sets up *this* table. + """ + if not cls._connection.tableExists(cls._table): + cls.createTable() + setup = classmethod(setup) + +installOrClear = InstalledTestDatabase.installOrClear + +class Dummy(object): + + """ + Used for creating fake objects; a really poor 'mock object'. + """ + + def __init__(self, **kw): + for name, value in kw.items(): + setattr(self, name, value) + +def d(**kw): + """ + Because dict(**kw) doesn't work in Python 2.2, this is a + replacement. + """ + return kw + +def inserts(cls, data, schema=None): + """ + Creates a bunch of rows. You can use it like:: + + inserts(Person, [{'fname': 'blah', 'lname': 'doe'}, ...]) + + Or:: + + inserts(Person, [('blah', 'doe')], schema= + ['fname', 'lname']) + + If you give a single string for the `schema` then it'll split + that string to get the list of column names. + """ + if schema: + if isinstance(schema, str): + schema = schema.split() + keywordData = [] + for item in data: + itemDict = {} + for name, value in zip(schema, item): + itemDict[name] = value + keywordData.append(itemDict) + data = keywordData + results = [] + for args in data: + results.append(cls(**args)) + return results + +def supports(feature): + dbName = connection.dbName + support = supportsMatrix.get('+' + feature, None) + notSupport = supportsMatrix.get('-' + feature, None) + if support is not None and dbName in support.split(): + return True + else: + return False + if notSupport is not None and dbName in notSupport.split(): + return False + else: + return True + assert notSupport is not None or support is not None, ( + "The supportMatrix does not list this feature: %r" + % feature) + +# To avoid name clashes: +_inserts = inserts + +__all__ = ['getConnection', 'setupClass', 'Dummy', 'raises', + 'd', 'inserts', 'supports'] Added: trunk/SQLObject/sqlobject/tests/test_auto.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_auto.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_auto.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,136 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * +from sqlobject import classregistry + +######################################## +## Dynamic column tests +######################################## + +class Person(SQLObject): + + _columns = [StringCol('name', length=100, dbName='name_col')] + _defaultOrder = 'name' + +class Phone(SQLObject): + + _columns = [StringCol('phone', length=12)] + _defaultOrder = 'phone' + +class TestPeople: + + def setup_method(self, meth): + setupClass(Person) + setupClass(Phone) + for n in ['jane', 'tim', 'bob', 'jake']: + Person(name=n) + for p in ['555-555-5555', '555-394-2930', + '444-382-4854']: + Phone(phone=p) + + def test_defaultOrder(self): + assert (list(Person.select('all')) == + list(Person.select('all', orderBy=Person._defaultOrder))) + + def test_dynamicColumn(self): + if not supports('dynamicColumn'): + return + nickname = StringCol('nickname', length=10) + Person.addColumn(nickname, changeSchema=True) + n = Person(name='robert', nickname='bob') + assert ([p.name for p in Person.select('all')] + == ['bob', 'jake', 'jane', 'robert', 'tim']) + Person.delColumn(nickname, changeSchema=True) + + def test_dynamicJoin(self): + if not supports('dynamicColumn'): + return + col = KeyCol('personID', foreignKey='Person') + Phone.addColumn(col, changeSchema=True) + join = MultipleJoin('Phone') + Person.addJoin(join) + for phone in Phone.select('all'): + if phone.phone.startswith('555'): + phone.person = Person.selectBy(name='tim')[0] + else: + phone.person = Person.selectBy(name='bob')[0] + l = [p.phone for p in Person.selectBy(name='tim')[0].phones] + l.sort() + assert l == ['555-394-2930', '555-555-5555'] + Phone.delColumn(col, changeSchema=True) + Person.delJoin(join) + +######################################## +## Auto class generation +######################################## + +class TestAuto: + + mysqlCreate = """ + CREATE TABLE IF NOT EXISTS auto_test ( + auto_id INT AUTO_INCREMENT PRIMARY KEY, + first_name VARCHAR(100), + last_name VARCHAR(200) NOT NULL, + age INT DEFAULT NULL, + created DATETIME NOT NULL, + happy char(1) DEFAULT 'Y' NOT NULL, + wannahavefun TINYINT DEFAULT 0 NOT NULL + ) + """ + + postgresCreate = """ + CREATE TABLE auto_test ( + auto_id SERIAL PRIMARY KEY, + first_name VARCHAR(100), + last_name VARCHAR(200) NOT NULL, + age INT DEFAULT 0, + created VARCHAR(40) NOT NULL, + happy char(1) DEFAULT 'Y' NOT NULL, + wannahavefun BOOL DEFAULT FALSE NOT NULL + ) + """ + + sybaseCreate = """ + CREATE TABLE auto_test ( + auto_id integer, + first_name VARCHAR(100), + last_name VARCHAR(200) NOT NULL, + age INT DEFAULT 0, + created VARCHAR(40) NOT NULL, + happy char(1) DEFAULT 'Y' NOT NULL + ) + """ + + mysqlDrop = """ + DROP TABLE IF EXISTS auto_test + """ + + postgresDrop = """ + DROP TABLE auto_test + """ + + sybaseDrop = """ + DROP TABLE auto_test + """ + + _table = 'auto_test' + + def test_classCreate(self): + if not supports('fromDatabase'): + return + class AutoTest(SQLObject): + _fromDatabase = True + _idName = 'auto_id' + _connection = connection() + john = AutoTest(firstName='john', + lastName='doe', + age=10, + created=now(), + wannahavefun=False) + jane = AutoTest(firstName='jane', + lastName='doe', + happy='N', + created=now(), + wannahavefun=True) + assert not john.wannahavefun + assert jane.wannahavefun + del classregistry.registry(AutoTest._registry).classes['AutoTest'] Added: trunk/SQLObject/sqlobject/tests/test_basic.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_basic.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_basic.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,230 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +class TestSO1(SQLObject): + + name = StringCol(length=50, dbName='name_col') + _cacheValues = False + _columns = [ + StringCol('passwd', length=10), + ] + + def _set_passwd(self, passwd): + self._SO_set_passwd(passwd.encode('rot13')) + +def setupGetters(cls): + setupClass(cls) + inserts(cls, [('bob', 'god'), ('sally', 'sordid'), + ('dave', 'dremel'), ('fred', 'forgo')], + 'name passwd') + +def test_case1(): + setupGetters(TestSO1) + bob = TestSO1.selectBy(name='bob')[0] + assert bob.name == 'bob' + assert bob.passwd == 'god'.encode('rot13') + +def test_newline(): + setupGetters(TestSO1) + bob = TestSO1.selectBy(name='bob')[0] + testString = 'hey\nyou\\can\'t you see me?\t' + bob.name = testString + bob.expire() + assert bob.name == testString + +def test_count(): + setupGetters(TestSO1) + assert TestSO1.selectBy(name='bob').count() == 1 + assert TestSO1.select(TestSO1.q.name == 'bob').count() == 1 + assert TestSO1.select().count() == len(list(TestSO1.select())) + +def test_getset(): + setupGetters(TestSO1) + bob = TestSO1.selectBy(name='bob')[0] + assert bob.name == 'bob' + bob.name = 'joe' + assert bob.name == 'joe' + +class TestSO2(SQLObject): + name = StringCol(length=50, dbName='name_col') + passwd = StringCol(length=10) + + def _set_passwd(self, passwd): + self._SO_set_passwd(passwd.encode('rot13')) + +def test_case2(): + setupGetters(TestSO2) + bob = TestSO2.selectBy(name='bob')[0] + assert bob.name == 'bob' + assert bob.passwd == 'god'.encode('rot13') + +class Student(SQLObject): + is_smart = BoolCol() + +def test_boolCol(): + setupClass(Student) + student = Student(is_smart=False) + assert student.is_smart == False + student2 = Student(is_smart='false') + assert student2.is_smart == True + +class TestSO3(SQLObject): + name = StringCol(length=10, dbName='name_col') + other = ForeignKey('TestSO4', default=None) + other2 = KeyCol(foreignKey='TestSO4', default=None) + +class TestSO4(SQLObject): + me = StringCol(length=10) + +def test_foreignKey(): + setupClass(TestSO3) + setupClass(TestSO4) + tc3 = TestSO3(name='a') + assert tc3.other is None + assert tc3.other2 is None + assert tc3.otherID is None + assert tc3.other2ID is None + tc4a = TestSO4(me='1') + tc3.other = tc4a + assert tc3.other == tc4a + assert tc3.otherID == tc4a.id + tc4b = TestSO4(me='2') + tc3.other = tc4b.id + assert tc3.other == tc4b + assert tc3.otherID == tc4b.id + tc4c = TestSO4(me='3') + tc3.other2 = tc4c + assert tc3.other2 == tc4c + assert tc3.other2ID == tc4c.id + tc4d = TestSO4(me='4') + tc3.other2 = tc4d.id + assert tc3.other2 == tc4d + assert tc3.other2ID == tc4d.id + tcc = TestSO3(name='b', other=tc4a) + assert tcc.other == tc4a + tcc2 = TestSO3(name='c', other=tc4a.id) + assert tcc2.other == tc4a + +class TestSO5(SQLObject): + name = StringCol(length=10, dbName='name_col') + other = ForeignKey('TestSO6', default=None, cascade=True) + another = ForeignKey('TestSO7', default=None, cascade=True) + +class TestSO6(SQLObject): + name = StringCol(length=10, dbName='name_col') + other = ForeignKey('TestSO7', default=None, cascade=True) + +class TestSO7(SQLObject): + name = StringCol(length=10, dbName='name_col') + +def test_foreignKeyDestroySelfCascade(): + setupClass(TestSO7) + setupClass(TestSO6) + setupClass(TestSO5) + + tc5 = TestSO5(name='a') + tc6a = TestSO6(name='1') + tc5.other = tc6a + tc7a = TestSO7(name='2') + tc6a.other = tc7a + tc5.another = tc7a + assert tc5.other == tc6a + assert tc5.otherID == tc6a.id + assert tc6a.other == tc7a + assert tc6a.otherID == tc7a.id + assert tc5.other.other == tc7a + assert tc5.other.otherID == tc7a.id + assert tc5.another == tc7a + assert tc5.anotherID == tc7a.id + assert tc5.other.other == tc5.another + assert TestSO5.select().count() == 1 + assert TestSO6.select().count() == 1 + assert TestSO7.select().count() == 1 + tc6b = TestSO6(name='3') + tc6c = TestSO6(name='4') + tc7b = TestSO7(name='5') + tc6b.other = tc7b + tc6c.other = tc7b + assert TestSO5.select().count() == 1 + assert TestSO6.select().count() == 3 + assert TestSO7.select().count() == 2 + tc6b.destroySelf() + assert TestSO5.select().count() == 1 + assert TestSO6.select().count() == 2 + assert TestSO7.select().count() == 2 + tc7b.destroySelf() + assert TestSO5.select().count() == 1 + assert TestSO6.select().count() == 1 + assert TestSO7.select().count() == 1 + tc7a.destroySelf() + assert TestSO5.select().count() == 0 + assert TestSO6.select().count() == 0 + assert TestSO7.select().count() == 0 + +def testForeignKeyDropTableCascade(): + if not supports('dropTableCascade'): + return + setupClass(TestSO7) + setupClass(TestSO6) + setupClass(TestSO5) + + tc5a = TestSO5(name='a') + tc6a = TestSO6(name='1') + tc5a.other = tc6a + tc7a = TestSO7(name='2') + tc6a.other = tc7a + tc5a.another = tc7a + tc5b = TestSO5(name='b') + tc5c = TestSO5(name='c') + tc6b = TestSO6(name='3') + tc5c.other = tc6b + assert TestSO5.select().count() == 3 + assert TestSO6.select().count() == 2 + assert TestSO7.select().count() == 1 + TestSO7.dropTable(cascade=True) + assert TestSO5.select().count() == 3 + assert TestSO6.select().count() == 2 + tc6a.destroySelf() + assert TestSO5.select().count() == 2 + assert TestSO6.select().count() == 1 + tc6b.destroySelf() + assert TestSO5.select().count() == 1 + assert TestSO6.select().count() == 0 + assert iter(TestSO5.select()).next() == tc5b + tc6c = TestSO6(name='3') + tc5b.other = tc6c + assert TestSO5.select().count() == 1 + assert TestSO6.select().count() == 1 + tc6c.destroySelf() + assert TestSO5.select().count() == 0 + assert TestSO6.select().count() == 0 + +class TestSO8(SQLObject): + name = StringCol(length=10, dbName='name_col') + other = ForeignKey('TestSO9', default=None, cascade=False) + +class TestSO9(SQLObject): + name = StringCol(length=10, dbName='name_col') + +def testForeignKeyDestroySelfRestrict(): + setupClass(TestSO9) + setupClass(TestSO8) + + tc8a = TestSO8(name='a') + tc9a = TestSO9(name='1') + tc8a.other = tc9a + tc8b = TestSO8(name='b') + tc9b = TestSO9(name='2') + assert tc8a.other == tc9a + assert tc8a.otherID == tc9a.id + assert TestSO8.select().count() == 2 + assert TestSO9.select().count() == 2 + raises(Exception, tc9a.destroySelf) + tc9b.destroySelf() + assert TestSO8.select().count() == 2 + assert TestSO9.select().count() == 1 + tc8a.destroySelf() + tc8b.destroySelf() + tc9a.destroySelf() + assert TestSO8.select().count() == 0 + assert TestSO9.select().count() == 0 Added: trunk/SQLObject/sqlobject/tests/test_blob.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_blob.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_blob.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,22 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +######################################## +## BLOB columns +######################################## + +class ImageData(SQLObject): + image = BLOBCol(default='emptydata', length=65535) + +def test_BLOBCol(): + setupClass(ImageData) + data = ''.join([chr(x) for x in range(256)]) + + prof = ImageData() + prof.image = data + iid = prof.id + + ImageData._connection.cache.clear() + + prof2 = ImageData.get(iid) + assert prof2.image == data Copied: trunk/SQLObject/sqlobject/tests/test_cache.py (from rev 560, trunk/SQLObject/tests/test_cache.py) =================================================================== --- trunk/SQLObject/tests/test_cache.py 2005-01-31 22:57:35 UTC (rev 560) +++ trunk/SQLObject/sqlobject/tests/test_cache.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,21 @@ +from sqlobject.cache import CacheSet + +class Something(object): + pass + +def test_purge1(): + x = CacheSet() + y = Something() + obj = x.get(1, y.__class__) + assert obj is None + x.put(1, y.__class__, y) + x.finishPut(y.__class__) + j = x.get(1, y.__class__) + assert j == y + x.expire(1, y.__class__) + j = x.get(1, y.__class__) + assert j == None + x.finishPut(y.__class__) + j = x.get(1, y.__class__) + assert j == None + x.finishPut(y.__class__) Added: trunk/SQLObject/sqlobject/tests/test_constraints.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_constraints.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_constraints.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,34 @@ +from sqlobject.constraints import * +from sqlobject.tests.dbtest import * + +def test_constraints(): + obj = 'Test object' + col = Dummy(name='col') + isString(obj, col, 'blah') + raises(BadValue, isString, obj, col, 1) + # @@: Should this really be an error? + raises(BadValue, isString, obj, col, u'test!') + #isString(obj, col, u'test!') + + raises(BadValue, notNull, obj, col, None) + raises(BadValue, isInt, obj, col, 1.1) + isInt(obj, col, 1) + isInt(obj, col, 1L) + isFloat(obj, col, 1) + isFloat(obj, col, 1L) + isFloat(obj, col, 1.2) + raises(BadValue, isFloat, obj, col, '1.0') + + # @@: Should test isBool, but I don't think isBool is right + + lst = InList(('a', 'b', 'c')) + lst(obj, col, 'a') + raises(BadValue, lst, obj, col, ('a', 'b', 'c')) + raises(BadValue, lst, obj, col, 'A') + + maxlen = MaxLength(2) + raises(BadValue, maxlen, obj, col, '123') + maxlen(obj, col, '12') + maxlen(obj, col, (1,)) + raises(BadValue, maxlen, obj, col, 1) + Copied: trunk/SQLObject/sqlobject/tests/test_converters.py (from rev 560, trunk/SQLObject/tests/test_converters.py) =================================================================== --- trunk/SQLObject/tests/test_converters.py 2005-01-31 22:57:35 UTC (rev 560) +++ trunk/SQLObject/sqlobject/tests/test_converters.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,139 @@ +from sqlobject.sqlbuilder import sqlrepr, TRUE, FALSE +from sqlobject.sqlbuilder import SQLExpression, SQLObjectField, \ + Select, Insert, Update, Delete, Replace, \ + SQLTrueClauseClass, SQLConstant, SQLPrefix, SQLCall, SQLOp +from sqlobject.converters import registerConverter + +class TestClass: + + def __repr__(self): + return '<TestClass>' + +def TestClassConverter(value, db): + return repr(value) + +registerConverter(TestClass, TestClassConverter) + +class NewTestClass: + + __metaclass__ = type + + def __repr__(self): + return '<NewTestClass>' + +def NewTestClassConverter(value, db): + return repr(value) + +registerConverter(NewTestClass, NewTestClassConverter) + +def _sqlrepr(self, db): + return '<%s>' % self.__class__.__name__ + +SQLExpression.__sqlrepr__ = _sqlrepr + +############################################################ +## Tests +############################################################ + +def test_simple_string(): + assert sqlrepr('A String', 'firebird') == "'A String'" + +def test_string_newline(): + assert sqlrepr('A String\nAnother', 'postgres') == "'A String\\nAnother'" + assert sqlrepr('A String\nAnother', 'sqlite') == "'A String\nAnother'" + +def test_string_tab(): + assert sqlrepr('A String\tAnother', 'postgres') == "'A String\\tAnother'" + +def test_string_r(): + assert sqlrepr('A String\rAnother', 'postgres') == "'A String\\rAnother'" + +def test_string_b(): + assert sqlrepr('A String\bAnother', 'postgres') == "'A String\\bAnother'" + +def test_string_000(): + assert sqlrepr('A String\000Another', 'postgres') == "'A String\\0Another'" + +def test_string_(): + assert sqlrepr('A String\'Another', 'postgres') == "'A String\\'Another'" + assert sqlrepr('A String\'Another', 'firebird') == "'A String''Another'" + +def test_simple_unicode(): + assert sqlrepr(u'A String', 'postgres') == "'A String'" + +def test_integer(): + assert sqlrepr(10) == "10" + +def test_float(): + assert sqlrepr(10.01) == "10.01" + +def test_none(): + assert sqlrepr(None) == "NULL" + +def test_list(): + assert sqlrepr(['one','two','three'], 'postgres') == "('one', 'two', 'three')" + +def test_tuple(): + assert sqlrepr(('one','two','three'), 'postgres') == "('one', 'two', 'three')" + +def test_bool(): + assert sqlrepr(TRUE, 'postgres') == "'t'" + assert sqlrepr(FALSE, 'postgres') == "'f'" + assert sqlrepr(TRUE, 'mysql') == "1" + assert sqlrepr(FALSE, 'mysql') == "0" + +def test_instance(): + instance = TestClass() + assert sqlrepr(instance) == repr(instance) + +def test_newstyle(): + instance = NewTestClass() + assert sqlrepr(instance) == repr(instance) + +def test_sqlexpr(): + instance = SQLExpression() + assert sqlrepr(instance) == repr(instance) + +def test_sqlobjectfield(): + instance = SQLObjectField('test', 'test', 'test') + assert sqlrepr(instance) == repr(instance) + +def test_select(): + instance = Select('test') + assert sqlrepr(instance, 'mysql') == "SELECT 'test'" + +def test_insert(): + instance = Insert('test', [('test',)]) + assert sqlrepr(instance, 'mysql') == "INSERT INTO test VALUES ('test')" + +def test_update(): + instance = Update('test', {'test':'test'}) + assert sqlrepr(instance, 'mysql') == "UPDATE test SET test='test'" + +def test_delete(): + instance = Delete('test', None) + assert sqlrepr(instance, 'mysql') == "DELETE FROM test" + +def test_replace(): + instance = Replace('test', {'test':'test'}) + assert sqlrepr(instance, 'mysql') == "REPLACE test SET test='test'" + +def test_trueclause(): + instance = SQLTrueClauseClass() + assert sqlrepr(instance) == repr(instance) + +def test_op(): + instance = SQLOp('and', 'this', 'that') + assert sqlrepr(instance, 'mysql') == "('this' AND 'that')" + +def test_call(): + instance = SQLCall('test', ('test',)) + assert sqlrepr(instance, 'mysql') == "'test'('test')" + +def test_constant(): + instance = SQLConstant('test') + assert sqlrepr(instance) == repr(instance) + +def test_prefix(): + instance = SQLPrefix('test', 'test') + assert sqlrepr(instance, 'mysql') == "test 'test'" Added: trunk/SQLObject/sqlobject/tests/test_datetime.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_datetime.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_datetime.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,52 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * +from sqlobject import col +if default_datetime_implementation == DATETIME_IMPLEMENTATION: + from datetime import datetime + now = datetime.now +elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION: + from mx.DateTime import now + +######################################## +## Date/time columns +######################################## + +if datetime_available: + col.default_datetime_implementation = DATETIME_IMPLEMENTATION + from datetime import date, datetime + + class DateTime1(SQLObject): + col1 = DateCol() + col2 = DateTimeCol() + + def test_dateTime(): + setupClass(DateTime1) + _now = now() + dt1 = DateTime1(col1=_now, col2=_now) + assert isinstance(dt1.col1, date) + assert isinstance(dt1.col2, datetime) + + today_str = _now.strftime("%Y-%m-%d") + now_str = _now.strftime("%Y-%m-%d %T") + assert str(dt1.col1) == today_str + assert str(dt1.col2) == now_str + +if mxdatetime_available: + col.default_datetime_implementation = MXDATETIME_IMPLEMENTATION + + class DateTime2(SQLObject): + col1 = DateCol() + col2 = DateTimeCol() + + def test_mxDateTime(): + setupClass(DateTime2) + _now = now() + dt2 = DateTime2(col1=_now, col2=_now) + assert isinstance(dt2.col1, col.DateTimeType) + assert isinstance(dt2.col2, col.DateTimeType) + + today_str = _now.strftime("%Y-%m-%d 00:00:00.00") + now_str = _now.strftime("%Y-%m-%d %T.00") + assert str(dt2.col1) == today_str + assert str(dt2.col2) == now_str + Added: trunk/SQLObject/sqlobject/tests/test_delete.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_delete.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_delete.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,29 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * +from test_basic import TestSO1, setupGetters + +######################################## +## Delete during select +######################################## + +def testSelect(): + setupGetters(TestSO1) + for obj in TestSO1.select('all'): + obj.destroySelf() + assert list(TestSO1.select('all')) == [] + +######################################## +## Delete without caching +######################################## + +class NoCache(SQLObject): + name = StringCol() + +def testDestroySelf(): + setupClass(NoCache) + old = NoCache._connection.cache + NoCache._connection.cache = cache.CacheSet(cache=False) + value = NoCache(name='test') + value.destroySelf() + NoCache._connection.cache = old + Added: trunk/SQLObject/sqlobject/tests/test_distinct.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_distinct.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_distinct.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,32 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +######################################## +## Distinct +######################################## + +class Distinct1(SQLObject): + n = IntCol() + +class Distinct2(SQLObject): + other = ForeignKey('Distinct1') + +def count(select): + result = {} + for ob in select: + result[int(ob.n)] = result.get(int(ob.n), 0)+1 + return result + +def test_distinct(): + setupClass(Distinct1) + setupClass(Distinct2) + obs = [Distinct1(n=i) for i in range(3)] + Distinct2(other=obs[0]) + Distinct2(other=obs[0]) + Distinct2(other=obs[1]) + + query = (Distinct2.q.otherID==Distinct1.q.id) + sel = Distinct1.select(query) + assert count(sel) == {0: 2, 1: 1} + sel = Distinct1.select(query, distinct=True) + assert count(sel) == {0: 1, 1:1} Added: trunk/SQLObject/sqlobject/tests/test_enum.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_enum.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_enum.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,19 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +######################################## +## Enum test +######################################## + +class Enum1(SQLObject): + + _columns = [ + EnumCol('l', enumValues=['a', 'bcd', 'e']), + ] + +def testBad(): + setupClass(Enum1) + for l in ['a', 'bcd', 'a', 'e']: + Enum1(l=l) + if supports('restrictedEnum'): + raises(Enum1._connection.module.IntegrityError, Enum1, l='b') Added: trunk/SQLObject/sqlobject/tests/test_expire.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_expire.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_expire.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,26 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +######################################## +## Expiring, syncing +######################################## + +class SyncTest(SQLObject): + name = StringCol(length=50, alternateID=True, dbName='name_col') + +def test_expire(): + setupClass(SyncTest) + SyncTest(name='bob') + SyncTest(name='tim') + + conn = SyncTest._connection + b = SyncTest.byName('bob') + conn.query("UPDATE sync_test SET name_col = 'robert' WHERE id = %i" + % b.id) + assert b.name == 'bob' + b.expire() + assert b.name == 'robert' + conn.query("UPDATE sync_test SET name_col = 'bobby' WHERE id = %i" + % b.id) + b.sync() + assert b.name == 'bobby' Added: trunk/SQLObject/sqlobject/tests/test_indexes.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_indexes.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_indexes.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,42 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +######################################## +## Indexes +######################################## + +class SOIndex1(SQLObject): + name = StringCol(length=100) + number = IntCol() + + nameIndex = DatabaseIndex('name', unique=True) + nameIndex2 = DatabaseIndex(name, number) + nameIndex3 = DatabaseIndex({'column': name, + 'length': 3}) + +class SOIndex2(SQLObject): + + name = StringCol() + + nameIndex = DatabaseIndex({'expression': 'lower(name)'}) + +def test_1(): + setupClass(SOIndex1) + n = 0 + for name in 'blah blech boring yep yort snort'.split(): + n += 1 + SOIndex1(name=name, number=n) + mod = SOIndex1._connection.module + try: + SOIndex1(name='blah', number=0) + except (mod.ProgrammingError, mod.IntegrityError): + # expected + pass + else: + assert 0, "Exception expected." + +def test_2(): + if not supports('expressionIndex'): + return + setupClass(SOIndex2) + SOIndex2(name='') Added: trunk/SQLObject/sqlobject/tests/test_inheritance.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_inheritance.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_inheritance.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,31 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +######################################## +## Inheritance +######################################## + +class Super(SQLObject): + + _columns = [StringCol('name', length=10)] + +class Sub(Super): + + _columns = Super._columns + [StringCol('name2', length=10)] + +def test_super(): + setupClass(Super) + setupClass(Sub) + s1 = Super(name='one') + s2 = Super(name='two') + s3 = Super.get(s1.id) + assert s1 == s3 + +def test_sub(): + setupClass(Super) + setupClass(Sub) + s1 = Sub(name='one', name2='1') + s2 = Sub(name='two', name2='2') + s3 = Sub.get(s1.id) + assert s1 == s3 + Added: trunk/SQLObject/sqlobject/tests/test_joins.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_joins.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_joins.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,106 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +######################################## +## Joins +######################################## + +class PersonJoiner(SQLObject): + + _columns = [StringCol('name', length=40, alternateID=True)] + _joins = [RelatedJoin('AddressJoiner')] + +class AddressJoiner(SQLObject): + + _columns = [StringCol('zip', length=5, alternateID=True)] + _joins = [RelatedJoin('PersonJoiner')] + +class ImplicitJoiningSO(SQLObject): + foo = RelatedJoin('Bar') + +class ExplicitJoiningSO(SQLObject): + _joins = [MultipleJoin('Bar', joinMethodName='foo')] + +class TestJoin: + + def setup_method(self, meth): + setupClass(PersonJoiner) + setupClass(AddressJoiner) + for n in ['bob', 'tim', 'jane', 'joe', 'fred', 'barb']: + PersonJoiner(name=n) + for z in ['11111', '22222', '33333', '44444']: + AddressJoiner(zip=z) + + def test_join(self): + b = PersonJoiner.byName('bob') + assert b.addressJoiners == [] + z = AddressJoiner.byZip('11111') + b.addAddressJoiner(z) + self.assertZipsEqual(b.addressJoiners, ['11111']) + self.assertNamesEqual(z.personJoiners, ['bob']) + z2 = AddressJoiner.byZip('22222') + b.addAddressJoiner(z2) + self.assertZipsEqual(b.addressJoiners, ['11111', '22222']) + self.assertNamesEqual(z2.personJoiners, ['bob']) + b.removeAddressJoiner(z) + self.assertZipsEqual(b.addressJoiners, ['22222']) + self.assertNamesEqual(z.personJoiners, []) + + def assertZipsEqual(self, zips, dest): + assert [a.zip for a in zips] == dest + + def assertNamesEqual(self, people, dest): + assert [p.name for p in people] == dest + + def test_joinAttributeWithUnderscores(self): + # Make sure that the implicit setting of joinMethodName works + assert hasattr(ImplicitJoiningSO, 'foo') + assert not hasattr(ImplicitJoiningSO, 'bars') + + # And make sure explicit setting also works + assert hasattr(ExplicitJoiningSO, 'foo') + assert not hasattr(ExplicitJoiningSO, 'bars') + +class PersonJoiner2(SQLObject): + + _columns = [StringCol('name', length=40, alternateID=True)] + _joins = [MultipleJoin('AddressJoiner2')] + +class AddressJoiner2(SQLObject): + + _columns = [StringCol('zip', length=5), + StringCol('plus4', length=4, default=None), + ForeignKey('PersonJoiner2')] + _defaultOrder = ['-zip', 'plus4'] + +class TestJoin2: + + def setup_method(self, meth): + setupClass(PersonJoiner2) + setupClass(AddressJoiner2) + p1 = PersonJoiner2(name='bob') + p2 = PersonJoiner2(name='sally') + for z in ['11111', '22222', '33333']: + a = AddressJoiner2(zip=z, personJoiner2=p1) + #p1.addAddressJoiner2(a) + AddressJoiner2(zip='00000', personJoiner2=p2) + + def test_basic(self): + bob = PersonJoiner2.byName('bob') + sally = PersonJoiner2.byName('sally') + assert len(bob.addressJoiner2s) == 3 + assert len(sally.addressJoiner2s) == 1 + bob.addressJoiner2s[0].destroySelf() + assert len(bob.addressJoiner2s) == 2 + z = bob.addressJoiner2s[0] + z.zip = 'xxxxx' + id = z.id + del z + z = AddressJoiner2.get(id) + assert z.zip == 'xxxxx' + + def test_defaultOrder(self): + p1 = PersonJoiner2.byName('bob') + assert ([i.zip for i in p1.addressJoiner2s] + == ['33333', '22222', '11111']) + Added: trunk/SQLObject/sqlobject/tests/test_lazy.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_lazy.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_lazy.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,161 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +######################################## +## Lazy updates +######################################## + +class Lazy(SQLObject): + + _lazyUpdate = True + name = StringCol() + other = StringCol(default='nothing') + third = StringCol(default='third') + +class TestLazyTest: + + def setup_method(self, meth): + # All this stuff is so that we can track when the connection + # does an actual update; we put in a new _SO_update method + # that calls the original and sets an instance variable that + # we can later check. + setupClass(Lazy) + self.conn = Lazy._connection + self.conn.didUpdate = False + self._oldUpdate = self.conn._SO_update + newUpdate = ( + lambda so, values, s=self, c=self.conn, o=self._oldUpdate: + self._alternateUpdate(so, values, c, o)) + self.conn._SO_update = newUpdate + + def teardown_method(self, meth): + self.conn._SO_update = self._oldUpdate + del self._oldUpdate + + def _alternateUpdate(self, so, values, conn, oldUpdate): + conn.didUpdate = True + return oldUpdate(so, values) + + def test_lazy(self): + assert not self.conn.didUpdate + obj = Lazy(name='tim') + # We just did an insert, but not an update: + assert not self.conn.didUpdate + obj.set(name='joe') + assert obj.dirty + assert obj.name == 'joe' + assert not self.conn.didUpdate + obj.syncUpdate() + assert obj.name == 'joe' + assert self.conn.didUpdate + assert not obj.dirty + assert obj.name == 'joe' + self.conn.didUpdate = False + + obj = Lazy(name='frank') + obj.name = 'joe' + assert not self.conn.didUpdate + assert obj.dirty + assert obj.name == 'joe' + obj.name = 'joe2' + assert not self.conn.didUpdate + assert obj.dirty + assert obj.name == 'joe2' + obj.syncUpdate() + assert obj.name == 'joe2' + assert not obj.dirty + assert self.conn.didUpdate + self.conn.didUpdate = False + + obj = Lazy(name='loaded') + assert not obj.dirty + assert not self.conn.didUpdate + assert obj.name == 'loaded' + obj.name = 'unloaded' + assert obj.dirty + assert obj.name == 'unloaded' + assert not self.conn.didUpdate + obj.sync() + assert not obj.dirty + assert obj.name == 'unloaded' + assert self.conn.didUpdate + self.conn.didUpdate = False + obj.name = 'whatever' + assert obj.dirty + assert obj.name == 'whatever' + assert not self.conn.didUpdate + obj._SO_loadValue('name') + assert obj.dirty + assert obj.name == 'whatever' + assert not self.conn.didUpdate + obj._SO_loadValue('other') + assert obj.name == 'whatever' + assert not self.conn.didUpdate + obj.syncUpdate() + assert self.conn.didUpdate + self.conn.didUpdate = False + + # Now, check that get() doesn't screw + # cached objects' validator state. + obj_id = obj.id + old_state = obj._SO_validatorState + obj = Lazy.get(obj_id) + assert not obj.dirty + assert not self.conn.didUpdate + assert obj._SO_validatorState is old_state + assert obj.name == 'whatever' + obj.name = 'unloaded' + assert obj.name == 'unloaded' + assert obj.dirty + assert not self.conn.didUpdate + # Fetch the object again with get() and + # make sure dirty is still set, as the + # object should come from the cache. + obj = Lazy.get(obj_id) + assert obj.dirty + assert not self.conn.didUpdate + assert obj.name == 'unloaded' + obj.syncUpdate() + assert self.conn.didUpdate + assert not obj.dirty + self.conn.didUpdate = False + + # Then clear the cache, and try a get() + # again, to make sure stuf like _SO_createdValues + # is properly initialized. + self.conn.cache.clear() + obj = Lazy.get(obj_id) + assert not obj.dirty + assert not self.conn.didUpdate + assert obj.name == 'unloaded' + obj.name = 'spongebob' + assert obj.name == 'spongebob' + assert obj.dirty + assert not self.conn.didUpdate + obj.syncUpdate() + assert self.conn.didUpdate + assert not obj.dirty + self.conn.didUpdate = False + + obj = Lazy(name='last') + assert not obj.dirty + obj.syncUpdate() + assert not self.conn.didUpdate + assert not obj.dirty + # Check that setting multiple values + # actually works. This was broken + # and just worked because we were testing + # only one value at a time, so 'name' + # had the right value after the for loop *wink* + # Also, check that passing a name that is not + # a valid column doesn't break, but instead + # just does a plain setattr. + obj.set(name='first', other='who', third='yes') + assert obj.name == 'first' + assert obj.other == 'who' + assert obj.third == 'yes' + assert obj.dirty + assert not self.conn.didUpdate + obj.syncUpdate() + assert self.conn.didUpdate + assert not obj.dirty Added: trunk/SQLObject/sqlobject/tests/test_picklecol.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_picklecol.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_picklecol.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,36 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +######################################## +## Pickle columns +######################################## + +class PickleData: + pi = 3.14156 + def __init__(self): + self.question = 'The Ulimate Question of Life, the Universe and Everything' + self.answer = 42 + +class PickleContainer(SQLObject): + pickledata = PickleCol(default=None, length=65535) + +def test_pickleCol(): + setupClass(PickleContainer) + mypickledata = PickleData() + + ctnr = PickleContainer(pickledata=mypickledata) + iid = ctnr.id + + PickleContainer._connection.cache.clear() + + ctnr2 = PickleContainer.get(iid) + s2 = ctnr2.pickledata + + assert isinstance(s2, PickleData) + assert isinstance(s2.pi, float) + assert isinstance(s2.question, str) + assert isinstance(s2.answer, int) + assert s2.pi == mypickledata.pi + assert s2.question == mypickledata.question + assert s2.answer == mypickledata.answer + Added: trunk/SQLObject/sqlobject/tests/test_select.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_select.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_select.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,101 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +try: + enumerate +except NameError: + def enumerate(iterable): + i = 0 + for obj in iterable: + yield i, obj + i += 1 + +class IterTest(SQLObject): + name = StringCol(dbName='name_col') + +names = ('a', 'b', 'c') +def setupIter(): + setupClass(IterTest) + for n in names: + IterTest(name=n) + +def test_00_normal(): + setupIter() + count = 0 + for test in IterTest.select(): + count += 1 + assert count == len(names) + +def test_01_turn_to_list(): + count = 0 + for test in list(IterTest.select()): + count += 1 + assert count == len(names) + +def test_02_generator(): + all = IterTest.select() + count = 0 + for i, test in enumerate(all): + count += 1 + assert count == len(names) + +def test_03_ranged_indexed(): + all = IterTest.select() + count = 0 + for i in range(all.count()): + test = all[i] + count += 1 + assert count == len(names) + +def test_04_indexed_ended_by_exception(): + all = IterTest.select() + count = 0 + try: + while 1: + test = all[count] + count = count+1 + # Stop the test if it's gone on too long + if count > len(names): + break + except IndexError: + pass + assert count == len(names) + + + + +class Counter2(SQLObject): + + _columns = [ + IntCol('n1', notNull=True), + IntCol('n2', notNull=True), + ] + +class TestSelect: + + def setup_method(self, meth): + setupClass(Counter2) + for i in range(10): + for j in range(10): + Counter2(n1=i, n2=j) + + def counterEqual(self, counters, value): + assert [(c.n1, c.n2) for c in counters] == value + + def accumulateEqual(self, func, counters, value): + assert func([c.n1 for c in counters]) == value + + def test_1(self): + try: + sum + except NameError: + def sum(sequence): + s = 0 + for item in sequence: + s = s + item + return s + self.accumulateEqual(sum,Counter2.select(orderBy='n1'), + sum(range(10)) * 10) + + def test_2(self): + self.accumulateEqual(len,Counter2.select('all'), 100) Added: trunk/SQLObject/sqlobject/tests/test_slice.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_slice.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_slice.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,51 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +######################################## +## Slicing tests +######################################## + +class Counter(SQLObject): + + _columns = [ + IntCol('number', notNull=True), + ] + +class TestSlice: + + def setup_method(self, meth): + setupClass(Counter) + for i in range(100): + Counter(number=i) + + def counterEqual(self, counters, value): + assert [c.number for c in counters] == value + + def test_1(self): + self.counterEqual( + Counter.select(None, orderBy='number'), range(100)) + + def test_2(self): + self.counterEqual( + Counter.select(None, orderBy='number')[10:20], + range(10, 20)) + + def test_3(self): + self.counterEqual( + Counter.select(None, orderBy='number')[20:30][:5], + range(20, 25)) + + def test_4(self): + self.counterEqual( + Counter.select(None, orderBy='number')[:-10], + range(0, 90)) + + def test_5(self): + self.counterEqual( + Counter.select(None, orderBy='number', reversed=True), + range(99, -1, -1)) + + def test_6(self): + self.counterEqual( + Counter.select(None, orderBy='-number'), + range(99, -1, -1)) Added: trunk/SQLObject/sqlobject/tests/test_sorting.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_sorting.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_sorting.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,62 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +class Names(SQLObject): + + _table = 'names_table' + + firstName = StringCol(length=30) + lastName = StringCol(length=30) + + _defaultOrder = ['lastName', 'firstName'] + +def setupNames(): + setupClass(Names) + inserts(Names, [('aj', 'baker'), ('joe', 'robbins'), + ('tim', 'jackson'), ('joe', 'baker'), + ('zoe', 'robbins')], + schema='firstName lastName') + +def nameList(names): + result = [] + for name in names: + result.append('%s %s' % (name.firstName, name.lastName)) + return result + +def firstList(names): + return [n.firstName for n in names] + +def test_defaultOrder(): + setupNames() + assert nameList(Names.select()) == \ + ['aj baker', 'joe baker', + 'tim jackson', 'joe robbins', + 'zoe robbins'] + +def test_otherOrder(): + setupNames() + assert nameList(Names.select().orderBy(['firstName', 'lastName'])) == \ + ['aj baker', 'joe baker', + 'joe robbins', 'tim jackson', + 'zoe robbins'] + +def test_untranslatedColumnOrder(): + setupNames() + assert nameList(Names.select().orderBy(['first_name', 'last_name'])) == \ + ['aj baker', 'joe baker', + 'joe robbins', 'tim jackson', + 'zoe robbins'] + +def test_singleUntranslatedColumnOrder(): + setupNames() + assert firstList(Names.select().orderBy('firstName')) == \ + ['aj', 'joe', 'joe', 'tim', 'zoe'] + assert firstList(Names.select().orderBy('first_name')) == \ + ['aj', 'joe', 'joe', 'tim', 'zoe'] + assert firstList(Names.select().orderBy('-firstName')) == \ + ['zoe', 'tim', 'joe', 'joe', 'aj'] + assert firstList(Names.select().orderBy('-first_name')) == \ + ['zoe', 'tim', 'joe', 'joe', 'aj'] + assert firstList(Names.select().orderBy(Names.q.firstName)) == \ + ['aj', 'joe', 'joe', 'tim', 'zoe'] + Added: trunk/SQLObject/sqlobject/tests/test_stringid.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_stringid.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_stringid.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,63 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +######################################## +## String ID test +######################################## + +class SOStringID(SQLObject): + + _table = 'so_string_id' + _idType = str + val = StringCol(alternateID=True) + + mysqlCreate = """ + CREATE TABLE IF NOT EXISTS so_string_id ( + id VARCHAR(50) PRIMARY KEY, + val TEXT + ) + """ + + postgresCreate = """ + CREATE TABLE so_string_id ( + id VARCHAR(50) PRIMARY KEY, + val TEXT + ) + """ + + sybaseCreate = """ + CREATE TABLE so_string_id ( + id VARCHAR(50) UNIQUE, + val VARCHAR(50) NULL + ) + """ + + firebirdCreate = """ + CREATE TABLE so_string_id ( + id VARCHAR(50) NOT NULL PRIMARY KEY, + val BLOB SUB_TYPE TEXT + ) + """ + + sqliteCreate = postgresCreate + + mysqlDrop = """ + DROP TABLE IF EXISTS so_string_id + """ + + postgresDrop = """ + DROP TABLE so_string_id + """ + + sqliteDrop = postgresDrop + firebirdDrop = postgresDrop + + +def test_stringID(): + setupClass(SOStringID) + t = SOStringID(id='hey', val='whatever') + t2 = SOStringID.byVal('whatever') + assert t == t2 + t3 = SOStringID(id='you', val='nowhere') + t4 = SOStringID.get('you') + assert t3 == t4 Added: trunk/SQLObject/sqlobject/tests/test_style.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_style.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_style.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,28 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * +from sqlobject import styles + +class Another... [truncated message content] |
|
From: <sub...@co...> - 2005-02-01 07:58:08
|
Author: ianb Date: 2005-02-01 07:58:02 +0000 (Tue, 01 Feb 2005) New Revision: 564 Modified: trunk/SQLObject/docs/FAQ.txt Log: Noted what to do with Python keywords for columns Modified: trunk/SQLObject/docs/FAQ.txt =================================================================== --- trunk/SQLObject/docs/FAQ.txt 2005-02-01 06:45:18 UTC (rev 563) +++ trunk/SQLObject/docs/FAQ.txt 2005-02-01 07:58:02 UTC (rev 564) @@ -226,3 +226,12 @@ For this reason and several others, reloading modules is highly error-prone and difficult to support. + +Python Keywords +--------------- + +If you have a table column that is a Python keyword, you should know +that the Python attribute doesn't have to match the name of the +column. See `Irregular Naming`_ in the documentation. + +.. _Irregular Naming: SQLObject.html#irregular-naming |
|
From: <sub...@co...> - 2005-02-01 06:45:27
|
Author: ianb
Date: 2005-02-01 06:45:18 +0000 (Tue, 01 Feb 2005)
New Revision: 563
Modified:
trunk/SQLObject/sqlobject/dbconnection.py
trunk/SQLObject/sqlobject/postgres/pgconnection.py
Log:
* Add attributes like user, host, db to postgres connections, so that
we can recreate the URI
* Fixed an assert in dbconnection (NameError)
Modified: trunk/SQLObject/sqlobject/dbconnection.py
===================================================================
--- trunk/SQLObject/sqlobject/dbconnection.py 2005-02-01 06:19:19 UTC (rev 562)
+++ trunk/SQLObject/sqlobject/dbconnection.py 2005-02-01 06:45:18 UTC (rev 563)
@@ -50,7 +50,7 @@
auth = auth + '@' + self.password
auth = auth + ':'
else:
- assert not password, 'URIs cannot express passwords without usernames'
+ assert not self.password, 'URIs cannot express passwords without usernames'
uri = '%s://%s' % (self.dbName, auth)
if self.host:
uri += self.host + '/'
Modified: trunk/SQLObject/sqlobject/postgres/pgconnection.py
===================================================================
--- trunk/SQLObject/sqlobject/postgres/pgconnection.py 2005-02-01 06:19:19 UTC (rev 562)
+++ trunk/SQLObject/sqlobject/postgres/pgconnection.py 2005-02-01 06:45:18 UTC (rev 563)
@@ -29,6 +29,10 @@
registerConverter(type(psycopg.Binary('')),
PsycoBinaryConverter)
+ self.user = user
+ self.host = host
+ self.db = db
+ self.password = passwd
if dsn is None:
dsn = []
if db:
|
|
From: <sub...@co...> - 2005-02-01 06:19:25
|
Author: ianb
Date: 2005-02-01 06:19:19 +0000 (Tue, 01 Feb 2005)
New Revision: 562
Modified:
trunk/SQLObject/sqlobject/sqlbuilder.py
Log:
Make its __repr__ more robust (so it doesn't raise exceptions
when a value is database-dependent)
Modified: trunk/SQLObject/sqlobject/sqlbuilder.py
===================================================================
--- trunk/SQLObject/sqlobject/sqlbuilder.py 2005-02-01 05:59:29 UTC (rev 561)
+++ trunk/SQLObject/sqlobject/sqlbuilder.py 2005-02-01 06:19:19 UTC (rev 562)
@@ -152,9 +152,14 @@
return SQLCall(self, args)
def __repr__(self):
- return self.__sqlrepr__(None)
+ try:
+ return self.__sqlrepr__(None)
+ except AssertionError:
+ return '<%s %s>' % (
+ self.__class__.__name__, hex(id(self))[2:])
+
def __str__(self):
- return self.__sqlrepr__(None)
+ return repr(self)
def __cmp__(self, other):
raise VersionError, "Python 2.1+ required"
|
|
From: <sub...@co...> - 2005-02-01 05:59:34
|
Author: ianb
Date: 2005-02-01 05:59:29 +0000 (Tue, 01 Feb 2005)
New Revision: 561
Modified:
trunk/SQLObject/sqlobject/dbconnection.py
Log:
Be careful when __del__ happens not to throw an exception
Modified: trunk/SQLObject/sqlobject/dbconnection.py
===================================================================
--- trunk/SQLObject/sqlobject/dbconnection.py 2005-01-31 22:57:35 UTC (rev 560)
+++ trunk/SQLObject/sqlobject/dbconnection.py 2005-02-01 05:59:29 UTC (rev 561)
@@ -480,6 +480,10 @@
self.close()
def close(self):
+ if not hasattr(self, '_pool'):
+ # Probably there was an exception while creating this
+ # instance, so it is incomplete.
+ return
if not self._pool:
return
self._poolLock.acquire()
|
|
From: <sub...@co...> - 2005-01-31 06:39:35
|
Author: phd
Date: 2005-01-31 06:39:21 +0000 (Mon, 31 Jan 2005)
New Revision: 559
Modified:
trunk/SQLObject/sqlobject/col.py
Log:
Fixed a bug in date/time validators: allow sqlbuilder.SQLExpression.
Modified: trunk/SQLObject/sqlobject/col.py
===================================================================
--- trunk/SQLObject/sqlobject/col.py 2005-01-28 21:01:13 UTC (rev 558)
+++ trunk/SQLObject/sqlobject/col.py 2005-01-31 06:39:21 UTC (rev 559)
@@ -717,7 +717,7 @@
def fromPython(self, value, state):
if value is None:
return None
- if isinstance(value, (datetime.date, datetime.datetime)):
+ if isinstance(value, (datetime.date, datetime.datetime, sqlbuilder.SQLExpression)):
return value
if hasattr(value, "strftime"):
return value.strftime(self.format)
@@ -727,7 +727,7 @@
def toPython(self, value, state):
if value is None:
return None
- if isinstance(value, (datetime.date, datetime.datetime)):
+ if isinstance(value, (datetime.date, datetime.datetime, sqlbuilder.SQLExpression)):
return value
if mxdatetime_available and isinstance(value, DateTimeType):
# convert mxDateTime instance to datetime
@@ -749,7 +749,7 @@
def fromPython(self, value, state):
if value is None:
return None
- if isinstance(value, DateTimeType):
+ if isinstance(value, (DateTimeType, sqlbuilder.SQLExpression)):
return value
if hasattr(value, "strftime"):
return value.strftime(self.format)
@@ -759,7 +759,7 @@
def toPython(self, value, state):
if value is None:
return None
- if isinstance(value, DateTimeType):
+ if isinstance(value, (DateTimeType, sqlbuilder.SQLExpression)):
return value
if datetime_available: # convert datetime instance to mxDateTime
if isinstance(value, datetime.datetime):
|
|
From: <sub...@co...> - 2005-01-28 21:01:23
|
Author: ianb
Date: 2005-01-28 21:01:13 +0000 (Fri, 28 Jan 2005)
New Revision: 558
Modified:
trunk/SQLObject/sqlobject/dbconnection.py
Log:
Clean up getConnection a little -- if we need to make a connection,
don't put it in the pool just to take it out again.
Modified: trunk/SQLObject/sqlobject/dbconnection.py
===================================================================
--- trunk/SQLObject/sqlobject/dbconnection.py 2005-01-28 20:57:05 UTC (rev 557)
+++ trunk/SQLObject/sqlobject/dbconnection.py 2005-01-28 21:01:13 UTC (rev 558)
@@ -131,17 +131,17 @@
self._poolLock.acquire()
try:
if not self._pool:
- newConn = self.makeConnection()
- self._pool.append(newConn)
- self._connectionNumbers[id(newConn)] = self._connectionCount
+ conn = self.makeConnection()
+ self._connectionNumbers[id(conn)] = self._connectionCount
self._connectionCount += 1
- val = self._pool.pop()
+ else:
+ conn = self._pool.pop()
if self.debug:
s = 'ACQUIRE'
if self._pool is not None:
s += ' pool=[%s]' % ', '.join([str(self._connectionNumbers[id(v)]) for v in self._pool])
- self.printDebug(val, s, 'Pool')
- return val
+ self.printDebug(conn, s, 'Pool')
+ return conn
finally:
self._poolLock.release()
|
|
From: <sub...@co...> - 2005-01-28 20:58:00
|
Author: ianb
Date: 2005-01-28 20:57:05 +0000 (Fri, 28 Jan 2005)
New Revision: 557
Modified:
trunk/SQLObject/sqlobject/col.py
Log:
Handle case where datetime isn't defined (it will result in a NameError
otherwise, as there's a test against DATETIME_IMPLEMENTATION which
isn't defined if datetime isn't importable)
Modified: trunk/SQLObject/sqlobject/col.py
===================================================================
--- trunk/SQLObject/sqlobject/col.py 2005-01-28 20:48:00 UTC (rev 556)
+++ trunk/SQLObject/sqlobject/col.py 2005-01-28 20:57:05 UTC (rev 557)
@@ -51,11 +51,10 @@
else:
mxdatetime_available = True
-if datetime_available:
- DATETIME_IMPLEMENTATION = "datetime"
+DATETIME_IMPLEMENTATION = "datetime"
+MXDATETIME_IMPLEMENTATION = "mxDateTime"
if mxdatetime_available:
- MXDATETIME_IMPLEMENTATION = "mxDateTime"
DateTimeType = type(DateTime.now())
if datetime_available:
|
|
From: <sub...@co...> - 2005-01-28 20:48:30
|
Author: ianb
Date: 2005-01-28 20:48:00 +0000 (Fri, 28 Jan 2005)
New Revision: 556
Modified:
trunk/SQLObject/sqlobject/dbconnection.py
Log:
* Be a little more robust about closing a DBConnection instance,
and closing all its direct connections
* When there's no pooling (typically in a transaction) make sure
you explicitly close connections that are released.
Modified: trunk/SQLObject/sqlobject/dbconnection.py
===================================================================
--- trunk/SQLObject/sqlobject/dbconnection.py 2005-01-26 06:07:52 UTC (rev 555)
+++ trunk/SQLObject/sqlobject/dbconnection.py 2005-01-28 20:48:00 UTC (rev 556)
@@ -176,6 +176,8 @@
# the __del__ in Iteration (unfortunately, not sure why
# it happens)
self._pool.append(conn)
+ else:
+ conn.close()
def printDebug(self, conn, s, name, type='query'):
if type == 'query':
@@ -478,12 +480,21 @@
self.close()
def close(self):
- if self._pool:
- for conn in self._pool:
+ if not self._pool:
+ return
+ self._poolLock.acquire()
+ try:
+ conns = self._pool[:]
+ self._pool[:] = []
+ for conn in conns:
try:
conn.close()
except self.module.Error:
pass
+ del conn
+ del conns
+ finally:
+ self._poolLock.release()
class Iteration(object):
|
|
From: <sub...@co...> - 2005-01-26 06:07:55
|
Author: ianb
Date: 2005-01-26 06:07:52 +0000 (Wed, 26 Jan 2005)
New Revision: 555
Removed:
trunk/SQLObject/docs/Plan06.txt
Log:
Doesn't really apply anymore; historically relevant, but not
current.
Deleted: trunk/SQLObject/docs/Plan06.txt
===================================================================
--- trunk/SQLObject/docs/Plan06.txt 2005-01-26 05:49:13 UTC (rev 554)
+++ trunk/SQLObject/docs/Plan06.txt 2005-01-26 06:07:52 UTC (rev 555)
@@ -1,306 +0,0 @@
-SQLObject 0.6
-=============
-
-*A tentative plan, 20 Jan 2004*
-
-Introduction
-------------
-
-During vacation I thought about some changes that I might like to make
-to SQLObject. Several of these change the API, but not too
-drastically, and I think they change the API for the better. And we'd
-not at 1.0 yet, changes are still allowed! Here's my ideas...
-
-Editing Context
----------------
-
-Taken from Modeling, the "editing context" is essentially a
-transaction, though it also encompasses some other features.
-Typically it is used to distinguish between separate contexts in a
-multi-threaded program.
-
-This is intended to separate several distinct concepts:
-
-* The database backend (MySQL, PostgreSQL, etc), coupled with the
- driver (MySQLdb, psycopg, etc). (Should the driver be part of the
- connection parameters?)
-* The connection parameters. Typically these are the server host,
- username, and password, but they could also be a filename or other
- path. Perhaps this could be represented with a URI, ala PEAK, but
- I also dislike taking structured data and destructuring it (i.e.,
- packing it into a string). OTOH, URLs are structured, even if they
- require some parsing. Serialization of URLs is free and highly
- transparent. Python syntax is well structured and
- *programmatically* considerably more transparent (in a robust
- fashion), but also programmatically fairly read-only (because it is
- embedded in the structure of Python source code). We can also have
- both.
-* The database transactional context.
-* The application transactional context (preferably these two would
- be seemless, but they still represent somewhat distinct entities,
- and a portability layer might be nice). The application's
- transactional context may include other transactions -- e.g.,
- multiple databases, a ZODB transaction, etc.
-* The cache policy. There are many different kinds of caches
- potentially involved, include write batching, and per-object and
- per-table caches, connection pooling, and so on.
-* Classes, which on the database side are typically tables. (This
- proposal does not attempt to de-couple classes and tables)
-
-Example::
-
- from SQLObject import EditingContext
- ec = EditingContext()
- # every editing context automatically picks up all the SQLObject
- # classes, all magic like.
- person = ec.Person.get(1) # by ID
- ec2 = EditingContext() # separate transaction
- person2 = ec.Person.get(1)
- assert person is not person2
- assert person.id == person2.id
- assert person.fname == 'Guy'
- person.fname = 'Gerald'
- assert person2.fname == 'Guy'
- ec.commit() # SQL is not sent to server
- assert person2.fname == 'Guy' # Doesn't see changes
- person2.fname = 'Norm'
- # raises exception if locking is turned on; overwrites if locking
- # is not turned on. (Locking enabled on a per-class level)
-
-I'm not at all sure about that example. Mostly the confusing parts
-relate to locking and when the database lookup occurs (and how late a
-conflict exception may be raised).
-
-Somewhere in here, process-level transactions might fit in. That is,
-even on a backend that doesn't support transactions, we can still
-delay SQL statements until a commit/rollback is performed. In turn,
-we can create temporary "memory" objects, which is any object which
-hasn't been committed to the database in any way. To do this we'll
-need sequences -- to preallocate IDs -- which MySQL and SQLite don't
-really provide :(
-
-Nested transactions...? Maybe they'd fall out of this fairly easily,
-especially if we define a global context, with global caches etc.,
-then further levels of context will come for free.
-
-We still need to think about an auto-commit mode. Maybe the global
-context would be auto-commit.
-
-Caching
--------
-
-Really doing transactions right means making caching significantly
-more complex. If the cache is purely transaction-specific, then we'll
-really be limiting the effectiveness of the cache. With that in mind,
-a copy-on-write style of object is really called for -- when you fetch
-an object in a transaction, you can use the globally cached instance
-until you write to the object.
-
-Really this isn't copy-on-write, it's more like a proxy object. Until
-the object is changed, it can delegate all its columns to its global
-object for which it is a proxy. Of course, traversal via foreign keys
-or joins must also return proxied objects. As the object is changed
--- perhaps on a column-by-column basis, or as a whole on the first
-change -- the object takes on the personality of a full SQLObject
-instance.
-
-When the transaction is committed, this transactional object copies
-itself to the global object, and becomes a full proxy. These
-transactional caches themselves should be pooled -- so that when
-another transaction comes along you have a potentially useful set of
-proxy objects already created for you. This is a common use case for
-web applications, which have lots of short transactions, which are
-often very repetitive.
-
-In addition to this, there should be more cache control. This means
-explicit ways to control things like:
-
-1. Caching of instances:
-
- * Application/process-global definition.
- * Database-level definition.
- * Transaction/EditingContext-level definition.
- * Class-level definition.
-
-2. Caching of columns:
-
- * Class-level.
-
-3. Cache sweep frequency:
-
- * Application/process-global.
- * Database-level.
- * Class-level.
- * Doesn't need to be as complete as 1; maybe on the class level you
- could only indicate that a certain class should not be sweeped.
- * Sweep during a fetch (e.g., every 100 fetches), by time or fetch
- frequency, or sweep with an explicit call (e.g., to do sweeps in
- a separate thread).
-
-4. Cache sweep policy:
-
- * Maximum age.
- * Least-recently-used (actually, least-recently-fetched).
- * Random (the current policy).
- * Multi-level (randomly move objects to a lower-priority cache,
- raise level when the object is fetched again).
- * Target cache size (keep trimming until the cache is small
- enough).
- * Simple policy (if enough objects qualify, cache can be of any
- size).
- * Percentage culling (e.g., kill 33% of objects for each sweep;
- this is the current policy).
-
-5. Batching of updates (whether updates should immediately go to the
- database, or whether it would be batched until a commit or other
- signal).
-
-6. Natural expiring of objects. Even if an object must persist
- because there are still references, we could expire it so that
- future accesses re-query the database. To avoid stale data.
-
-Expose some methods of the cache, like getting all objects currently
-in memory. These would probably be exposed on a class level, e.g.,
-all the Addresses currently in memory via
-``Address.cache.current()`` or something. What about when there's a
-cached instance in the parent context, but not in the present
-transaction?
-
-Columns as Descriptors
-----------------------
-
-Each column will become a descriptor. That is, ``Col`` and subclasses
-will return an object with ``__get__`` and ``__set__`` methods. The
-metaclass will not itself generate methods.
-
-A metaclass will still be used so that the descriptor can be tied to
-its name, e.g., that with ``fname = StringCol()``, the resultant
-descriptor will know that it is bound to ``fname``.
-
-By using descriptors, introspection should become a bit easier -- or
-at least more uniform with respect to other new-style classes.
-Various class-wide indexes of columns will still be necessary, but
-these should be able to remain mostly private.
-
-To customize getters or setters (which you currently do by defining a
-``_get_columnName`` or ``_set_columnName`` method), you will pass
-arguments to the ``Col`` object, like::
-
- def _get_name(self, dbGetter):
- return dbGetter().strip()
-
- name = StringCol(getter=_get_name)
-
-This gets rid of ``_SO_get_columnName`` as well. We can
-transitionally add something to the metaclass to signal an error if a
-spurious ``_get_columnName`` method is sitting around.
-
-Construction and Fetching
--------------------------
-
-Currently you fetch an object with class instantiation, e.g.,
-``Address(1)``. This may or may not create a new instance, and does
-not create a table row. If you want to create a table row, you do
-something like ``Address.new(city='New York', ...)``. This is
-somewhat in contrast to normal Python, where class instantiation
-(calling a class) will create a new object, while objects are fetched
-otherwise (with no particular standard interface).
-
-To make SQLObject classes more normal in this case, ``new`` will
-become ``__init__`` (more or less), and classes will have a ``get``
-method that gets an already-existant row. E.g., ``Address.get(1)``
-vs. ``Address(city='New York', ...)``. This is perhaps the most
-significant change in SQLObject usage. Because of the different
-signatures, if you forget to make a change someplace you will get an
-immediate exception, so updating code should not be too hard.
-
-Extra Table Information
------------------------
-
-People have increasingly used SQLObject to create tables, and while it
-can make a significant number of schemas, there are several extensions
-of table generation that people occasionally want. Since these occur
-later in development, it would be convenient if SQLObject could grow
-as the complexity of the programs using it grow. Some of these
-extensions are:
-
-* Table name (``_table``).
-* Table type for MySQL (e.g., MyISAM vs. InnoDB).
-* Multi-column unique constraints. (Other constraints?)
-* Indexes. (Function or multi-column indexes?)
-* Primary key type. (Primary key generation?)
-* Primary key sequence names (for Postgres, Firebird, Oracle, etc).
-* Multi-column primary keys.
-* Naming scheme.
-* Permissions.
-* Locking (e.g., optimistic locking).
-* Inheritance (see Daniel Savard's recent patch).
-* Anything else?
-
-Some of these may be globally defined, or defined for an entire
-database. For example, typically you'll want to use a common MySQL
-table type for your entire database, even though its defined on a
-per-table basis. And while MySQL allows global permission
-declarations, Postgres does not and requires tedious repetitions of
-the permissions for each table -- so while it's applied on a per-table
-basis, it's likely that (at least to some degree) a per-database
-declaration is called for. Naming schemes are also usually
-database-wide.
-
-As these accumulate -- and by partitioning this list differently, the
-list could be even longer -- it's messy to do these all as special
-class variables (``_idName``, etc). It also makes the class logic and
-its database implementation details difficult to distinguish. Some
-of these can be handled elegantly like ``id = StringCol()`` or ``id
-= ("fname", "lname")``. But the others perhaps should be put into a
-single instance variable, perhaps itself a class::
-
- class Address(SQLObject):
- class SQLMeta:
- mysqlType = 'InnoDB'
- naming = Underscore
- permission = {'bob': ['select', 'insert'],
- 'joe': ['select', 'insert', 'update'],
- 'public': ['select']}
- street = StringCol()
- ....
-
-The metadata is found by its name (``SQLMeta``), and is simply a
-container. The class syntax is easier to write and read than a
-dictionary-like syntax. Or, it could be a proper class/instance and
-provide a partitioned way to handle introspection. E.g.,
-``Address.SQLMeta.permission.get('bob')`` or
-``Address.SQLMeta.columns``. In this case values that weren't
-overridden would be calculated from defaults (like the default naming
-scheme and so on).
-
-I'm not at all certain about how this should look, or if there are
-other things that should go into the class-meta-data object.
-
-Joins, Foreign Keys
--------------------
-
-First, the poorly-named ``MultipleJoin`` and ``RelatedJoin`` (which
-are rather ambiguous) will be renamed ``ManyToOneJoin`` and
-``ManyToManyJoin``. ``OneToOneJoin`` will also be added, while
-``ForeignKey`` remains the related column type. (Many2Many?
-Many2many? many2many?)
-
-ForeignKey will be driven by a special validator/converter. (But will
-this make ID access more difficult?)
-
-Joins will return smart objects which can be iterated across. These
-smart objects will be related to ``SelectResults``, and allow the
-same features like ordering. In both cases, an option to retrieve
-IDs instead of objects will be allowed.
-
-These smarter objects will allow, in the case of ManyToManyJoin,
-``Set`` like operations to relate (or unrelate) objects. For
-ManyToOneJoin the list/set operations are not really appropriate,
-because they would reassign the relation, not just add or remove
-relations.
-
-It would be nice to make the Join protocol more explicit and public,
-so other kinds of joins (e.g., three-way) could be more accessible.
-
-
|
|
From: <sub...@co...> - 2005-01-26 05:48:51
|
Author: ianb Date: 2005-01-26 05:48:44 +0000 (Wed, 26 Jan 2005) New Revision: 553 Modified: trunk/SQLObject/docs/News.txt Log: Changed version numbers Modified: trunk/SQLObject/docs/News.txt =================================================================== --- trunk/SQLObject/docs/News.txt 2005-01-24 12:10:39 UTC (rev 552) +++ trunk/SQLObject/docs/News.txt 2005-01-26 05:48:44 UTC (rev 553) @@ -7,8 +7,8 @@ .. _start: -SVN trunk -========= +SQLObject 0.6.1 +=============== Interface Changes ----------------- @@ -75,7 +75,7 @@ rows (like class instantiation used to do). * We're now using a Subversion repository instead of CVS. It is - located at svn://colorstudy.com/trunk/SQLObject + located at http://svn.colorstudy.com/trunk/SQLObject * If you pass ``forceDBName=True`` to the ``*Col`` constructors, then your column name doesn't have to be restricted to a-z, 0-9, and _. |
|
From: <sub...@co...> - 2005-01-24 12:10:49
|
Author: phd
Date: 2005-01-24 12:10:39 +0000 (Mon, 24 Jan 2005)
New Revision: 552
Modified:
home/phd/SQLObject/inheritance/sqlobject/classregistry.py
home/phd/SQLObject/inheritance/sqlobject/col.py
home/phd/SQLObject/inheritance/sqlobject/dbconnection.py
home/phd/SQLObject/inheritance/sqlobject/main.py
Log:
Optimize inheritance - prefetch children using cursor.fetchmany()
instead of selecting them one by one.
Modified: home/phd/SQLObject/inheritance/sqlobject/classregistry.py
===================================================================
--- home/phd/SQLObject/inheritance/sqlobject/classregistry.py 2005-01-15 17:00:08 UTC (rev 551)
+++ home/phd/SQLObject/inheritance/sqlobject/classregistry.py 2005-01-24 12:10:39 UTC (rev 552)
@@ -108,3 +108,6 @@
MasterRegistry = _MasterRegistry()
registry = MasterRegistry.registry
+
+def findClass(name, class_registry=None):
+ return registry(class_registry).getClass(name)
Modified: home/phd/SQLObject/inheritance/sqlobject/col.py
===================================================================
--- home/phd/SQLObject/inheritance/sqlobject/col.py 2005-01-15 17:00:08 UTC (rev 551)
+++ home/phd/SQLObject/inheritance/sqlobject/col.py 2005-01-24 12:10:39 UTC (rev 552)
@@ -28,6 +28,7 @@
# arguments in this module, so we rename it:
import constraints as consts
from include import validators
+from classregistry import findClass
NoDefault = sqlbuilder.NoDefault
True, False = 1==1, 0==1
@@ -617,7 +618,6 @@
SOKeyCol.__init__(self, **kw)
def postgresCreateSQL(self):
- from main import findClass
sql = SOKeyCol.postgresCreateSQL(self)
other = findClass(self.foreignKey)
tName = other._table
@@ -643,7 +643,6 @@
return sql
def sybaseCreateSQL(self):
- from sqlobject.main import findClass
sql = SOKeyCol.sybaseCreateSQL(self)
other = findClass(self.foreignKey)
tName = other._table
@@ -655,7 +654,6 @@
return sql
def maxdbCreateSQL(self):
- from main import findClass
other = findClass(self.foreignKey)
fidName = self.dbName
#I assume that foreign key name is identical to the id of the reference table
Modified: home/phd/SQLObject/inheritance/sqlobject/dbconnection.py
===================================================================
--- home/phd/SQLObject/inheritance/sqlobject/dbconnection.py 2005-01-15 17:00:08 UTC (rev 551)
+++ home/phd/SQLObject/inheritance/sqlobject/dbconnection.py 2005-01-24 12:10:39 UTC (rev 552)
@@ -15,6 +15,7 @@
from converters import sqlrepr
import urllib
import weakref
+from classregistry import findClass
warnings.filterwarnings("ignore", "DB-API extension cursor.lastrowid used")
@@ -486,33 +487,89 @@
pass
class Iteration(object):
+ #phd: default array size for cursor.fetchmany()
+ defaultArraySize = 10000
def __init__(self, dbconn, rawconn, select, keepConnection=False):
self.dbconn = dbconn
self.rawconn = rawconn
self.select = select
self.keepConnection = keepConnection
- self.cursor = rawconn.cursor()
+ self.cursor = cursor = rawconn.cursor()
self.query = self.dbconn.queryForSelect(select)
if dbconn.debug:
dbconn.printDebug(rawconn, self.query, 'Select')
- self.dbconn._executeRetry(self.rawconn, self.cursor, self.query)
+ self.dbconn._executeRetry(self.rawconn, cursor, self.query)
+ cursor.arraysize = self.defaultArraySize
+ self._results = []
+ #phd: find the index of the childName column
+ childNameIdx = None
+ columns = select.sourceClass._SO_columns
+ for i in range(len(columns)): #phd: enumerate() is unavailable python 2.2
+ if columns[i].name == "childName":
+ childNameIdx = i
+ break
+ self._childNameIdx = childNameIdx
def __iter__(self):
return self
def next(self):
- result = self.cursor.fetchone()
- if result is None:
+ lazyColumns = self.select.ops.get('lazyColumns', 0)
+ if not self._results:
+ self._results = list(self.cursor.fetchmany())
+ if not lazyColumns: self.fetchChildren()
+ if not self._results:
self._cleanup()
raise StopIteration
- if self.select.ops.get('lazyColumns', 0):
+ result = self._results[0]
+ del self._results[0]
+ if lazyColumns:
obj = self.select.sourceClass.get(result[0], connection=self.dbconn)
return obj
else:
- obj = self.select.sourceClass.get(result[0], selectResults=result[1:], connection=self.dbconn)
+ id = result[0]
+ if id in self._childrenResults:
+ childResults = self._childrenResults[id]
+ del self._childrenResults[id]
+ else:
+ childResults = None
+ obj = self.select.sourceClass.get(id, selectResults=result[1:],
+ childResults=childResults, connection=self.dbconn)
return obj
+ def fetchChildren(self):
+ """Prefetch childrens' data
+
+ Fetch childrens' data for every subclass in one big .select()
+ to avoid .get() fetching it one by one.
+ """
+ self._childrenResults = {}
+ if self._childNameIdx is None:
+ return
+ childIdsNames = {}
+ childNameIdx = self._childNameIdx
+ for result in self._results:
+ childName = result[childNameIdx+1]
+ if childName:
+ ids = childIdsNames.get(childName)
+ if ids is None:
+ ids = childIdsNames[childName] = []
+ ids.append(result[0])
+ dbconn = self.dbconn
+ rawconn = self.rawconn
+ cursor = rawconn.cursor()
+ registry = self.select.sourceClass._registry
+ for childName, ids in childIdsNames.items():
+ klass = findClass(childName, registry)
+ select = klass.select(sqlbuilder.IN(sqlbuilder.SQLConstant("id"), ids))
+ query = dbconn.queryForSelect(select)
+ if dbconn.debug:
+ dbconn.printDebug(rawconn, query, 'Select children of the class %s' % childName)
+ self.dbconn._executeRetry(rawconn, cursor, query)
+ for result in cursor.fetchall():
+ self._childrenResults[result[0]] = result[1:]
+
def _cleanup(self):
if getattr(self, 'query', None) is None:
# already cleaned up
Modified: home/phd/SQLObject/inheritance/sqlobject/main.py
===================================================================
--- home/phd/SQLObject/inheritance/sqlobject/main.py 2005-01-15 17:00:08 UTC (rev 551)
+++ home/phd/SQLObject/inheritance/sqlobject/main.py 2005-01-24 12:10:39 UTC (rev 552)
@@ -34,6 +34,7 @@
import joins
import index
import classregistry
+findClass = classregistry.findClass # for those who imported findClass from sqlobject.main
import sys
if sys.version_info[:3] < (2, 2, 0):
@@ -315,9 +316,7 @@
delFunc(obj, var)
break
-def findClass(name, registry=None):
- #assert classRegistry.get(registry, {}).has_key(name), "No class by the name %s found (I have %s)" % (repr(name), ', '.join(map(str, classRegistry.keys())))
- return classregistry.registry(registry).getClass(name)
+#phd: findClass has been moved to classregistry to avoid circular import
def findDependencies(name, registry=None):
depends = []
@@ -406,7 +405,7 @@
# aren't using integer IDs
_idType = int
- def get(cls, id, connection=None, selectResults=None, childUpdate=False):
+ def get(cls, id, connection=None, selectResults=None, childResults=None, childUpdate=False):
assert id is not None, 'None is not a possible id for %s' % cls.__name
@@ -434,7 +433,7 @@
if hasattr(val, 'childName'):
childName = val.childName
if childName is not None:
- return val._childClasses[childName].get(id)
+ return val._childClasses[childName].get(id, selectResults=childResults)
#DSM: Now, we know we are alone or the last child in a family...
#DSM: It's time to find our parents
inst = val
|
|
From: <sub...@co...> - 2005-01-15 17:00:12
|
Author: phd
Date: 2005-01-15 17:00:08 +0000 (Sat, 15 Jan 2005)
New Revision: 551
Modified:
home/phd/SQLObject/inheritance/sqlobject/main.py
Log:
Emulated basestring for Python 2.2.
Modified: home/phd/SQLObject/inheritance/sqlobject/main.py
===================================================================
--- home/phd/SQLObject/inheritance/sqlobject/main.py 2005-01-15 14:32:28 UTC (rev 550)
+++ home/phd/SQLObject/inheritance/sqlobject/main.py 2005-01-15 17:00:08 UTC (rev 551)
@@ -1330,6 +1330,11 @@
return '_SO_val_%s' % name
+try:
+ basestring
+except NameError: # Python 2.2
+ basestring = (types.StringType, types.UnicodeType)
+
class SelectResults(object):
def __init__(self, sourceClass, clause, clauseTables=None,
@@ -1341,7 +1346,7 @@
tablesDict = sqlbuilder.tablesUsedDict(self.clause)
tablesDict[sourceClass._table] = 1
orderBy = ops.get('orderBy')
- if orderBy and type(orderBy) <> type(''):
+ if orderBy and not isinstance(orderBy, basestring):
tablesDict.update(sqlbuilder.tablesUsedDict(orderBy))
#DSM: if this class has a parent, we need to link it
#DSM: and be sure the parent is in the table list.
|
|
From: <sub...@co...> - 2005-01-15 14:32:33
|
Author: phd
Date: 2005-01-15 14:32:28 +0000 (Sat, 15 Jan 2005)
New Revision: 550
Modified:
home/phd/SQLObject/inheritance/sqlobject/col.py
home/phd/SQLObject/inheritance/sqlobject/joins.py
home/phd/SQLObject/inheritance/sqlobject/main.py
home/phd/SQLObject/inheritance/tests/test_sqlobject.py
Log:
Merged patches from revisions 546:549 from the trunk:
546: Added FloatValidator and a test for it.
547: PickleCol now has a parameter pickleProtocol.
Default is 1 (binary) for compatibility with Python 2.2.
548: Fixed incompatibility with Python 2.2: used col.popKey() instead of kw.pop().
549: Fixed more incompatibility with Python 2.2: use string.find(),
emulate sum() and enumerate() if they aren't available.
550: Fixed even more incompatibility with Python 2.2:
Use type(orderBy) instead of isinstance(orderBy, basestring).
Modified: home/phd/SQLObject/inheritance/sqlobject/col.py
===================================================================
--- home/phd/SQLObject/inheritance/sqlobject/col.py 2005-01-15 14:25:16 UTC (rev 549)
+++ home/phd/SQLObject/inheritance/sqlobject/col.py 2005-01-15 14:32:28 UTC (rev 550)
@@ -528,13 +528,44 @@
class BoolCol(Col):
baseClass = SOBoolCol
+
+class FloatValidator(validators.Validator):
+
+ def fromPython(self, value, state):
+ if value is None:
+ return None
+ if not isinstance(value, (int, long, float, sqlbuilder.SQLExpression)):
+ raise validators.InvalidField("expected a float in the FloatCol '%s', got %s instead" % \
+ (self.name, type(value)), value, state)
+ return value
+
+ def toPython(self, value, state):
+ if value is None:
+ return None
+ if isinstance(value, (int, long, float, sqlbuilder.SQLExpression)):
+ return value
+ try:
+ return float(value)
+ except:
+ raise validators.InvalidField("expected a float in the FloatCol '%s', got %s instead" % \
+ (self.name, type(value)), value, state)
+
class SOFloatCol(SOCol):
+ validatorClass = FloatValidator # can be overriden in descendants
# 3-03 @@: support precision (e.g., DECIMAL)
+ def __init__(self, **kw):
+ SOCol.__init__(self, **kw)
+ self.validator = validators.All.join(self.createValidator(), self.validator)
+
def autoConstraints(self):
return [consts.isFloat]
+ def createValidator(self):
+ """Create a validator for the column. Can be overriden in descendants."""
+ return self.validatorClass(name=self.name)
+
def _sqlType(self):
return 'FLOAT'
@@ -544,6 +575,7 @@
class FloatCol(Col):
baseClass = SOFloatCol
+
class SOKeyCol(SOCol):
# 3-03 @@: this should have a simplified constructor
@@ -700,7 +732,7 @@
return value
if mxdatetime_available and isinstance(value, DateTimeType):
# convert mxDateTime instance to datetime
- if ("%H" in self.format) or ("%T" in self.format):
+ if (self.format.find("%H") >= 0) or (self.format.find("%T")) >= 0:
return datetime.datetime(value.year, value.month, value.day,
value.hour, value.minute, int(value.second))
else:
@@ -904,18 +936,25 @@
return None
if isinstance(value, str):
return pickle.loads(value)
- return value
+ raise validators.InvalidField("expected a pickle string in the PickleCol '%s', got %s instead" % \
+ (self.name, type(value)), value, state)
def fromPython(self, value, state):
if value is None:
return None
- pickled = pickle.dumps(value)
- return BinaryValidator.fromPython(self, pickled, state)
+ return pickle.dumps(value)
-
class SOPickleCol(SOBLOBCol):
validatorClass = PickleValidator # can be overriden in descendants
+ def __init__(self, **kw):
+ self.pickleProtocol = popKey(kw, 'pickleProtocol', 1)
+ SOBLOBCol.__init__(self, **kw)
+
+ def createValidator(self):
+ """Create a validator for the column. Can be overriden in descendants."""
+ return self.validatorClass(name=self.name, pickleProtocol=self.pickleProtocol)
+
class PickleCol(BLOBCol):
baseClass = SOPickleCol
Modified: home/phd/SQLObject/inheritance/sqlobject/joins.py
===================================================================
--- home/phd/SQLObject/inheritance/sqlobject/joins.py 2005-01-15 14:25:16 UTC (rev 549)
+++ home/phd/SQLObject/inheritance/sqlobject/joins.py 2005-01-15 14:32:28 UTC (rev 550)
@@ -2,6 +2,7 @@
NoDefault = sqlbuilder.NoDefault
import styles
import classregistry
+from col import popKey
__all__ = ['MultipleJoin', 'RelatedJoin']
@@ -18,7 +19,7 @@
kw['joinDef'] = self
self.kw = kw
if self.kw.has_key('joinMethodName'):
- self._joinMethodName = self.kw.pop('joinMethodName')
+ self._joinMethodName = popKey(self.kw, 'joinMethodName')
else:
self._joinMethodName = None
Modified: home/phd/SQLObject/inheritance/sqlobject/main.py
===================================================================
--- home/phd/SQLObject/inheritance/sqlobject/main.py 2005-01-15 14:25:16 UTC (rev 549)
+++ home/phd/SQLObject/inheritance/sqlobject/main.py 2005-01-15 14:32:28 UTC (rev 550)
@@ -1341,7 +1341,7 @@
tablesDict = sqlbuilder.tablesUsedDict(self.clause)
tablesDict[sourceClass._table] = 1
orderBy = ops.get('orderBy')
- if orderBy and not isinstance(orderBy, basestring):
+ if orderBy and type(orderBy) <> type(''):
tablesDict.update(sqlbuilder.tablesUsedDict(orderBy))
#DSM: if this class has a parent, we need to link it
#DSM: and be sure the parent is in the table list.
Modified: home/phd/SQLObject/inheritance/tests/test_sqlobject.py
===================================================================
--- home/phd/SQLObject/inheritance/tests/test_sqlobject.py 2005-01-15 14:25:16 UTC (rev 549)
+++ home/phd/SQLObject/inheritance/tests/test_sqlobject.py 2005-01-15 14:32:28 UTC (rev 550)
@@ -341,6 +341,14 @@
########################################
## Select results
########################################
+try:
+ enumerate
+except NameError:
+ def enumerate(iterable):
+ i = 0
+ for obj in iterable:
+ yield i, obj
+ i += 1
class IterTest(SQLObject):
name = StringCol(dbName='name_col')
@@ -369,11 +377,6 @@
self.failIf(count != len(self.names))
def test_02_generator(self):
- def enumerate(iterable):
- i = 0
- for obj in iterable:
- yield i, obj
- i += 1
all = IterTest.select()
count = 0
for i, test in enumerate(all):
@@ -579,6 +582,14 @@
self.assertEqual(func([ c.n1 for c in counters]), value)
def test1(self):
+ try:
+ sum
+ except NameError:
+ def sum(sequence):
+ s = 0
+ for item in sequence:
+ s = s + item
+ return s
self.accumulateEqual(sum,Counter2.select(orderBy='n1'),
sum(range(10)) * 10)
@@ -890,6 +901,7 @@
name = StringCol(validator=validators.PlainText(), default='x', dbName='name_col')
name2 = StringCol(validator=validators.ConfirmType(str), default='y')
name3 = IntCol(validator=validators.Wrapper(fromPython=int), default=100)
+ name4 = FloatCol(default=2.718)
class ValidationTest(SQLObjectTest):
@@ -905,6 +917,10 @@
t = SOValidation(name2='hey')
self.assertRaises(validators.InvalidField, setattr, t,
'name2', 1)
+ self.assertRaises(validators.InvalidField, setattr, t,
+ 'name3', '1')
+ self.assertRaises(validators.InvalidField, setattr, t,
+ 'name4', '1')
t.name2 = 'you'
def testWrapType(self):
|
|
From: <sub...@co...> - 2005-01-15 14:25:21
|
Author: phd
Date: 2005-01-15 14:25:16 +0000 (Sat, 15 Jan 2005)
New Revision: 549
Modified:
trunk/SQLObject/sqlobject/col.py
trunk/SQLObject/tests/test_sqlobject.py
Log:
Fixed more incompatibility with Python 2.2: use string.find(),
emulate sum() and enumerate() if they aren't available.
Modified: trunk/SQLObject/sqlobject/col.py
===================================================================
--- trunk/SQLObject/sqlobject/col.py 2005-01-15 13:21:28 UTC (rev 548)
+++ trunk/SQLObject/sqlobject/col.py 2005-01-15 14:25:16 UTC (rev 549)
@@ -732,7 +732,7 @@
return value
if mxdatetime_available and isinstance(value, DateTimeType):
# convert mxDateTime instance to datetime
- if ("%H" in self.format) or ("%T" in self.format):
+ if (self.format.find("%H") >= 0) or (self.format.find("%T")) >= 0:
return datetime.datetime(value.year, value.month, value.day,
value.hour, value.minute, int(value.second))
else:
Modified: trunk/SQLObject/tests/test_sqlobject.py
===================================================================
--- trunk/SQLObject/tests/test_sqlobject.py 2005-01-15 13:21:28 UTC (rev 548)
+++ trunk/SQLObject/tests/test_sqlobject.py 2005-01-15 14:25:16 UTC (rev 549)
@@ -341,6 +341,14 @@
########################################
## Select results
########################################
+try:
+ enumerate
+except NameError:
+ def enumerate(iterable):
+ i = 0
+ for obj in iterable:
+ yield i, obj
+ i += 1
class IterTest(SQLObject):
name = StringCol(dbName='name_col')
@@ -369,11 +377,6 @@
self.failIf(count != len(self.names))
def test_02_generator(self):
- def enumerate(iterable):
- i = 0
- for obj in iterable:
- yield i, obj
- i += 1
all = IterTest.select()
count = 0
for i, test in enumerate(all):
@@ -579,6 +582,14 @@
self.assertEqual(func([ c.n1 for c in counters]), value)
def test1(self):
+ try:
+ sum
+ except NameError:
+ def sum(sequence):
+ s = 0
+ for item in sequence:
+ s = s + item
+ return s
self.accumulateEqual(sum,Counter2.select(orderBy='n1'),
sum(range(10)) * 10)
|
|
From: <sub...@co...> - 2005-01-15 13:21:30
|
Author: phd
Date: 2005-01-15 13:21:28 +0000 (Sat, 15 Jan 2005)
New Revision: 548
Modified:
trunk/SQLObject/sqlobject/joins.py
Log:
Fixed incompatibility with Python 2.2: used col.popKey() instead of kw.pop().
Modified: trunk/SQLObject/sqlobject/joins.py
===================================================================
--- trunk/SQLObject/sqlobject/joins.py 2005-01-15 12:50:16 UTC (rev 547)
+++ trunk/SQLObject/sqlobject/joins.py 2005-01-15 13:21:28 UTC (rev 548)
@@ -2,6 +2,7 @@
NoDefault = sqlbuilder.NoDefault
import styles
import classregistry
+from col import popKey
__all__ = ['MultipleJoin', 'RelatedJoin']
@@ -18,7 +19,7 @@
kw['joinDef'] = self
self.kw = kw
if self.kw.has_key('joinMethodName'):
- self._joinMethodName = self.kw.pop('joinMethodName')
+ self._joinMethodName = popKey(self.kw, 'joinMethodName')
else:
self._joinMethodName = None
|
|
From: <sub...@co...> - 2005-01-15 12:50:23
|
Author: phd
Date: 2005-01-15 12:50:16 +0000 (Sat, 15 Jan 2005)
New Revision: 547
Modified:
trunk/SQLObject/sqlobject/col.py
Log:
PickleCol now has a parameter pickleProtocol.
Default is 1 (binary) for compatibility with Python 2.2.
Modified: trunk/SQLObject/sqlobject/col.py
===================================================================
--- trunk/SQLObject/sqlobject/col.py 2005-01-15 11:55:33 UTC (rev 546)
+++ trunk/SQLObject/sqlobject/col.py 2005-01-15 12:50:16 UTC (rev 547)
@@ -936,18 +936,25 @@
return None
if isinstance(value, str):
return pickle.loads(value)
- return value
+ raise validators.InvalidField("expected a pickle string in the PickleCol '%s', got %s instead" % \
+ (self.name, type(value)), value, state)
def fromPython(self, value, state):
if value is None:
return None
- pickled = pickle.dumps(value)
- return BinaryValidator.fromPython(self, pickled, state)
+ return pickle.dumps(value)
-
class SOPickleCol(SOBLOBCol):
validatorClass = PickleValidator # can be overriden in descendants
+ def __init__(self, **kw):
+ self.pickleProtocol = popKey(kw, 'pickleProtocol', 1)
+ SOBLOBCol.__init__(self, **kw)
+
+ def createValidator(self):
+ """Create a validator for the column. Can be overriden in descendants."""
+ return self.validatorClass(name=self.name, pickleProtocol=self.pickleProtocol)
+
class PickleCol(BLOBCol):
baseClass = SOPickleCol
|