=======
CHANGES
=======
+
0.4.6
=====
- sql
- Fixed bug with union() when applied to non-Table connected
select statements
-
+
+- engines
+ - Pool listeners can now be provided as a dictionary of
+ callables or a (possibly partial) duck-type of
+ PoolListener, your choice.
+
0.4.5
=====
- orm
from sqlalchemy import exceptions, logging
from sqlalchemy import queue as Queue
-from sqlalchemy.util import thread, threading, pickle
+from sqlalchemy.util import thread, threading, pickle, as_interface
proxies = {}
newly opened connection. Defaults to -1.
listeners
- A list of ``PoolListener``-like objects that receive events when
- DB-API connections are created, checked out and checked in to
- the pool.
+ A list of ``PoolListener``-like objects or dictionaries of callables
+ that receive events when DB-API connections are created, checked out and
+ checked in to the pool.
+
"""
def __init__(self, creator, recycle=-1, echo=None, use_threadlocal=True,
raise NotImplementedError()
def add_listener(self, listener):
- """Add a ``PoolListener``-like object to this pool."""
+ """Add a ``PoolListener``-like object to this pool.
+
+ ``listener`` may be an object that implements some or all of
+ PoolListener, or a dictionary of callables containing implementations
+ of some or all of the named methods in PoolListener.
+
+ """
+
+ listener = as_interface(
+ listener, methods=('connect', 'checkout', 'checkin'))
self.listeners.append(listener)
if hasattr(listener, 'connect'):
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
-import inspect, itertools, new, sets, sys, warnings, weakref
+import inspect, itertools, new, operator, sets, sys, warnings, weakref
import __builtin__
types = __import__('types')
symbol._lock.release()
+def as_interface(obj, cls=None, methods=None, required=None):
+ """Ensure basic interface compliance for an instance or dict of callables.
+
+ Checks that ``obj`` implements public methods of ``cls`` or has members
+ listed in ``methods``. If ``required`` is not supplied, implementing at
+ least one interface method is sufficient. Methods present on ``obj`` that
+ are not in the interface are ignored.
+
+ If ``obj`` is a dict and ``dict`` does not meet the interface
+ requirements, the keys of the dictionary are inspected. Keys present in
+ ``obj`` that are not in the interface will raise TypeErrors.
+
+ Raises TypeError if ``obj`` does not meet the interface criteria.
+
+ In all passing cases, an object with callable members is returned. In the
+ simple case, ``obj`` is returned as-is; if dict processing kicks in then
+ an anonymous class is returned.
+
+ obj
+ A type, instance, or dictionary of callables.
+ cls
+ Optional, a type. All public methods of cls are considered the
+ interface. An ``obj`` instance of cls will always pass, ignoring
+ ``required``..
+ methods
+ Optional, a sequence of method names to consider as the interface.
+ required
+ Optional, a sequence of mandatory implementations. If omitted, an
+ ``obj`` that provides at least one interface method is considered
+ sufficient. As a convenience, required may be a type, in which case
+ all public methods of the type are required.
+
+ """
+ if not cls and not methods:
+ raise TypeError('a class or collection of method names are required')
+
+ if isinstance(cls, type) and isinstance(obj, cls):
+ return obj
+
+ interface = Set(methods or [m for m in dir(cls) if not m.startswith('_')])
+ implemented = Set(dir(obj))
+
+ complies = operator.ge
+ if isinstance(required, type):
+ required = interface
+ elif not required:
+ required = Set()
+ complies = operator.gt
+ else:
+ required = Set(required)
+
+ if complies(implemented.intersection(interface), required):
+ return obj
+
+ # No dict duck typing here.
+ if not type(obj) is dict:
+ qualifier = complies is operator.gt and 'any of' or 'all of'
+ raise TypeError("%r does not implement %s: %s" % (
+ obj, qualifier, ', '.join(interface)))
+
+ class AnonymousInterface(object):
+ """A callable-holding shell."""
+
+ if cls:
+ AnonymousInterface.__name__ = 'Anonymous' + cls.__name__
+ found = Set()
+
+ for method, impl in dictlike_iteritems(obj):
+ if method not in interface:
+ raise TypeError("%r: unknown in this interface" % method)
+ if not callable(impl):
+ raise TypeError("%r=%r is not callable" % (method, impl))
+ setattr(AnonymousInterface, method, staticmethod(impl))
+ found.add(method)
+
+ if complies(found, required):
+ return AnonymousInterface
+
+ raise TypeError("dictionary does not contain required keys %s" %
+ ', '.join(required - found))
+
def function_named(fn, name):
"""Return a function with a given __name__.
assert rt is sym1
assert rt is sym2
+class AsInterfaceTest(TestBase):
+ class Something(object):
+ def _ignoreme(self): pass
+ def foo(self): pass
+ def bar(self): pass
+
+ class Partial(object):
+ def bar(self): pass
+
+ class Object(object): pass
+
+ def test_instance(self):
+ obj = object()
+ self.assertRaises(TypeError, util.as_interface, obj,
+ cls=self.Something)
+
+ self.assertRaises(TypeError, util.as_interface, obj,
+ methods=('foo'))
+
+ self.assertRaises(TypeError, util.as_interface, obj,
+ cls=self.Something, required=('foo'))
+
+ obj = self.Something()
+ self.assertEqual(obj, util.as_interface(obj, cls=self.Something))
+ self.assertEqual(obj, util.as_interface(obj, methods=('foo',)))
+ self.assertEqual(
+ obj, util.as_interface(obj, cls=self.Something,
+ required=('outofband',)))
+ partial = self.Partial()
+
+ slotted = self.Object()
+ slotted.bar = lambda self: 123
+
+ for obj in partial, slotted:
+ self.assertEqual(obj, util.as_interface(obj, cls=self.Something))
+ self.assertRaises(TypeError, util.as_interface, obj,
+ methods=('foo'))
+ self.assertEqual(obj, util.as_interface(obj, methods=('bar',)))
+ self.assertEqual(
+ obj, util.as_interface(obj, cls=self.Something,
+ required=('bar',)))
+ self.assertRaises(TypeError, util.as_interface, obj,
+ cls=self.Something, required=('foo',))
+
+ self.assertRaises(TypeError, util.as_interface, obj,
+ cls=self.Something, required=self.Something)
+
+ def test_dict(self):
+ obj = {}
+
+ self.assertRaises(TypeError, util.as_interface, obj,
+ cls=self.Something)
+ self.assertRaises(TypeError, util.as_interface, obj,
+ methods=('foo'))
+ self.assertRaises(TypeError, util.as_interface, obj,
+ cls=self.Something, required=('foo'))
+
+ def assertAdapted(obj, *methods):
+ assert isinstance(obj, type)
+ found = set([m for m in dir(obj) if not m.startswith('_')])
+ for method in methods:
+ assert method in found
+ found.remove(method)
+ assert not found
+
+ fn = lambda self: 123
+
+ obj = {'foo': fn, 'bar': fn}
+
+ res = util.as_interface(obj, cls=self.Something)
+ assertAdapted(res, 'foo', 'bar')
+
+ res = util.as_interface(obj, cls=self.Something, required=self.Something)
+ assertAdapted(res, 'foo', 'bar')
+
+ res = util.as_interface(obj, cls=self.Something, required=('foo',))
+ assertAdapted(res, 'foo', 'bar')
+
+ res = util.as_interface(obj, methods=('foo', 'bar'))
+ assertAdapted(res, 'foo', 'bar')
+
+ res = util.as_interface(obj, methods=('foo', 'bar', 'baz'))
+ assertAdapted(res, 'foo', 'bar')
+
+ res = util.as_interface(obj, methods=('foo', 'bar'), required=('foo',))
+ assertAdapted(res, 'foo', 'bar')
+
+ self.assertRaises(TypeError, util.as_interface, obj, methods=('foo',))
+
+ self.assertRaises(TypeError, util.as_interface, obj,
+ methods=('foo', 'bar', 'baz'), required=('baz',))
+
+ obj = {'foo': 123}
+ self.assertRaises(TypeError, util.as_interface, obj, cls=self.Something)
if __name__ == "__main__":
testenv.main()
def checkout(self, con, record, proxy, num):
pass
class ListenCheckIn(InstrumentingListener):
- def checkin(self, con, proxy, record):
+ def checkin(self, con, record):
pass
def _pool(**kw):
- return pool.QueuePool(creator=lambda: dbapi.connect('foo.db'), use_threadlocal=False, **kw)
- #, pool_size=1, max_overflow=0, **kw)
+ return pool.QueuePool(creator=lambda: dbapi.connect('foo.db'),
+ use_threadlocal=False, **kw)
def assert_listeners(p, total, conn, cout, cin):
for instance in (p, p.recreate()):
del c
snoop.assert_total(2, 2, 1)
+ def test_listeners_callables(self):
+ dbapi = MockDBAPI()
+
+ counts = [0, 0, 0]
+ def connect(dbapi_con, con_record):
+ counts[0] += 1
+ def checkout(dbapi_con, con_record, con_proxy):
+ counts[1] += 1
+ def checkin(dbapi_con, con_record):
+ counts[2] += 1
+
+ i_all = dict(connect=connect, checkout=checkout, checkin=checkin)
+ i_connect = dict(connect=connect)
+ i_checkout = dict(checkout=checkout)
+ i_checkin = dict(checkin=checkin)
+
+ def _pool(**kw):
+ return pool.QueuePool(creator=lambda: dbapi.connect('foo.db'),
+ use_threadlocal=False, **kw)
+
+ def assert_listeners(p, total, conn, cout, cin):
+ for instance in (p, p.recreate()):
+ self.assert_(len(instance.listeners) == total)
+ self.assert_(len(instance._on_connect) == conn)
+ self.assert_(len(instance._on_checkout) == cout)
+ self.assert_(len(instance._on_checkin) == cin)
+
+ p = _pool()
+ assert_listeners(p, 0, 0, 0, 0)
+
+ p.add_listener(i_all)
+ assert_listeners(p, 1, 1, 1, 1)
+
+ p.add_listener(i_connect)
+ assert_listeners(p, 2, 2, 1, 1)
+
+ p.add_listener(i_checkout)
+ assert_listeners(p, 3, 2, 2, 1)
+
+ p.add_listener(i_checkin)
+ assert_listeners(p, 4, 2, 2, 2)
+ del p
+
+ p = _pool(listeners=[i_all])
+ assert_listeners(p, 1, 1, 1, 1)
+
+ c = p.connect()
+ assert counts == [1, 1, 0]
+ c.close()
+ assert counts == [1, 1, 1]
+
+ c = p.connect()
+ assert counts == [1, 2, 1]
+ p.add_listener(i_checkin)
+ c.close()
+ assert counts == [1, 2, 3]
+
+
+
def tearDown(self):
pool.clear_managers()