把 sqlalchemy pool源代码copy下来,有空看看。
参考配置:http://docs.sqlalchemy.org/en/latest/core/pooling.html
# sqlalchemy/pool.py
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php"""Connection pooling for DB-API connections.Provides a number of connection pool implementations for a variety of
usage scenarios and thread behavior requirements imposed by the
application, DB-API or database itself.Also provides a DB-API 2.0 connection proxying mechanism allowing
regular DB-API connect() methods to be transparently managed by a
SQLAlchemy connection pool.
"""import time
import traceback
import weakreffrom . import exc, log, event, events, interfaces, util
from .util import queue as sqla_queue
from .util import threading, memoized_property, \chop_tracebackproxies = {}def manage(module, **params):"""Return a proxy for a DB-API module that automaticallypools connections.Given a DB-API 2.0 module and pool management parameters, returnsa proxy for the module that will automatically pool connections,creating new connection pools for each distinct set of connectionarguments sent to the decorated module's connect() function.:param module: a DB-API 2.0 database module:param poolclass: the class used by the pool module to providepooling. Defaults to :class:`.QueuePool`.:param \*\*params: will be passed through to *poolclass*"""try:return proxies[module]except KeyError:return proxies.setdefault(module, _DBProxy(module, **params))def clear_managers():"""Remove all current DB-API 2.0 managers.All pools and connections are disposed."""for manager in proxies.itervalues():manager.close()proxies.clear()reset_rollback = util.symbol('reset_rollback')
reset_commit = util.symbol('reset_commit')
reset_none = util.symbol('reset_none')class _ConnDialect(object):"""partial implementation of :class:`.Dialect`which provides DBAPI connection methods.When a :class:`.Pool` is combined with an :class:`.Engine`,the :class:`.Engine` replaces this with its own:class:`.Dialect`."""def do_rollback(self, dbapi_connection):dbapi_connection.rollback()def do_commit(self, dbapi_connection):dbapi_connection.commit()def do_close(self, dbapi_connection):dbapi_connection.close()class Pool(log.Identified):"""Abstract base class for connection pools."""_dialect = _ConnDialect()def __init__(self,creator, recycle=-1, echo=None,use_threadlocal=False,logging_name=None,reset_on_return=True,listeners=None,events=None,_dispatch=None,_dialect=None):"""Construct a Pool.:param creator: a callable function that returns a DB-APIconnection object. The function will be called withparameters.:param recycle: If set to non -1, number of seconds betweenconnection recycling, which means upon checkout, if thistimeout is surpassed the connection will be closed andreplaced with a newly opened connection. Defaults to -1.:param logging_name: String identifier which will be used withinthe "name" field of logging records generated within the"sqlalchemy.pool" logger. Defaults to a hexstring of the object'sid.:param echo: If True, connections being pulled and retrievedfrom the pool will be logged to the standard output, as wellas pool sizing information. Echoing can also be achieved byenabling logging for the "sqlalchemy.pool"namespace. Defaults to False.:param use_threadlocal: If set to True, repeated calls to:meth:`connect` within the same application thread will beguaranteed to return the same connection object, if one hasalready been retrieved from the pool and has not beenreturned yet. Offers a slight performance advantage at thecost of individual transactions by default. The:meth:`unique_connection` method is provided to bypass thethreadlocal behavior installed into :meth:`connect`.:param reset_on_return: If true, reset the database state ofconnections returned to the pool. This is typically aROLLBACK to release locks and transaction resources.Disable at your own peril. Defaults to True.:param events: a list of 2-tuples, each of the form``(callable, target)`` which will be passed to event.listen()upon construction. Provided here so that event listenerscan be assigned via ``create_engine`` before dialect-levellisteners are applied.:param listeners: Deprecated. A list of:class:`~sqlalchemy.interfaces.PoolListener`-like objects ordictionaries of callables that receive events when DB-APIconnections are created, checked out and checked in to thepool. This has been superseded by:func:`~sqlalchemy.event.listen`."""if logging_name:self.logging_name = self._orig_logging_name = logging_nameelse:self._orig_logging_name = Nonelog.instance_logger(self, echoflag=echo)self._threadconns = threading.local()self._creator = creatorself._recycle = recycleself._use_threadlocal = use_threadlocalif reset_on_return in ('rollback', True, reset_rollback):self._reset_on_return = reset_rollbackelif reset_on_return in (None, False, reset_none):self._reset_on_return = reset_noneelif reset_on_return in ('commit', reset_commit):self._reset_on_return = reset_commitelse:raise exc.ArgumentError("Invalid value for 'reset_on_return': %r"% reset_on_return)self.echo = echoif _dispatch:self.dispatch._update(_dispatch, only_propagate=False)if _dialect:self._dialect = _dialectif events:for fn, target in events:event.listen(self, target, fn)if listeners:util.warn_deprecated("The 'listeners' argument to Pool (and ""create_engine()) is deprecated. Use event.listen().")for l in listeners:self.add_listener(l)dispatch = event.dispatcher(events.PoolEvents)def _close_connection(self, connection):self.logger.debug("Closing connection %r", connection)try:self._dialect.do_close(connection)except (SystemExit, KeyboardInterrupt):raiseexcept:self.logger.debug("Exception closing connection %r",connection)@util.deprecated(2.7, "Pool.add_listener is deprecated. Use event.listen()")def add_listener(self, listener):"""Add a :class:`.PoolListener`-like object to this pool.``listener`` may be an object that implements some or all ofPoolListener, or a dictionary of callables containing implementationsof some or all of the named methods in PoolListener."""interfaces.PoolListener._adapt_listener(self, listener)def unique_connection(self):"""Produce a DBAPI connection that is not referenced by anythread-local context.This method is different from :meth:`.Pool.connect` only if the``use_threadlocal`` flag has been set to ``True``."""return _ConnectionFairy(self).checkout()def _create_connection(self):"""Called by subclasses to create a new ConnectionRecord."""return _ConnectionRecord(self)def recreate(self):"""Return a new :class:`.Pool`, of the same class as this oneand configured with identical creation arguments.This method is used in conjunection with :meth:`dispose`to close out an entire :class:`.Pool` and create a new one inits place."""raise NotImplementedError()def dispose(self):"""Dispose of this pool.This method leaves the possibility of checked-out connectionsremaining open, as it only affects connections that areidle in the pool.See also the :meth:`Pool.recreate` method."""raise NotImplementedError()def _replace(self):"""Dispose + recreate this pool.Subclasses may employ special logic tomove threads waiting on this pool to thenew one."""self.dispose()return self.recreate()def connect(self):"""Return a DBAPI connection from the pool.The connection is instrumented such that when its``close()`` method is called, the connection will be returned tothe pool."""if not self._use_threadlocal:return _ConnectionFairy(self).checkout()try:rec = self._threadconns.current()if rec:return rec.checkout()except AttributeError:passagent = _ConnectionFairy(self)self._threadconns.current = weakref.ref(agent)return agent.checkout()def _return_conn(self, record):"""Given a _ConnectionRecord, return it to the :class:`.Pool`.This method is called when an instrumented DBAPI connectionhas its ``close()`` method called."""if self._use_threadlocal:try:del self._threadconns.currentexcept AttributeError:passself._do_return_conn(record)def _do_get(self):"""Implementation for :meth:`get`, supplied by subclasses."""raise NotImplementedError()def _do_return_conn(self, conn):"""Implementation for :meth:`return_conn`, supplied by subclasses."""raise NotImplementedError()def status(self):raise NotImplementedError()class _ConnectionRecord(object):finalize_callback = Nonedef __init__(self, pool):self.__pool = poolself.connection = self.__connect()pool.dispatch.first_connect.\for_modify(pool.dispatch).\exec_once(self.connection, self)pool.dispatch.connect(self.connection, self)@util.memoized_propertydef info(self):return {}def close(self):if self.connection is not None:self.__pool._close_connection(self.connection)def invalidate(self, e=None):if e is not None:self.__pool.logger.info("Invalidate connection %r (reason: %s:%s)",self.connection, e.__class__.__name__, e)else:self.__pool.logger.info("Invalidate connection %r", self.connection)self.__close()self.connection = Nonedef get_connection(self):if self.connection is None:self.connection = self.__connect()self.info.clear()if self.__pool.dispatch.connect:self.__pool.dispatch.connect(self.connection, self)elif self.__pool._recycle > -1 and \time.time() - self.starttime > self.__pool._recycle:self.__pool.logger.info("Connection %r exceeded timeout; recycling",self.connection)self.__close()self.connection = self.__connect()self.info.clear()if self.__pool.dispatch.connect:self.__pool.dispatch.connect(self.connection, self)return self.connectiondef __close(self):self.__pool._close_connection(self.connection)def __connect(self):try:self.starttime = time.time()connection = self.__pool._creator()self.__pool.logger.debug("Created new connection %r", connection)return connectionexcept Exception, e:self.__pool.logger.debug("Error on connect(): %s", e)raisedef _finalize_fairy(connection, connection_record, pool, ref, echo):_refs.discard(connection_record)if ref is not None and \connection_record.fairy is not ref:returnif connection is not None:try:if pool.dispatch.reset:pool.dispatch.reset(connection, connection_record)if pool._reset_on_return is reset_rollback:pool._dialect.do_rollback(connection)elif pool._reset_on_return is reset_commit:pool._dialect.do_commit(connection)# Immediately close detached instancesif connection_record is None:pool._close_connection(connection)except Exception, e:if connection_record is not None:connection_record.invalidate(e=e)if isinstance(e, (SystemExit, KeyboardInterrupt)):raiseif connection_record is not None:connection_record.fairy = Noneif echo:pool.logger.debug("Connection %r being returned to pool",connection)if connection_record.finalize_callback:connection_record.finalize_callback(connection)del connection_record.finalize_callbackif pool.dispatch.checkin:pool.dispatch.checkin(connection, connection_record)pool._return_conn(connection_record)_refs = set()class _ConnectionFairy(object):"""Proxies a DB-API connection and provides return-on-dereferencesupport."""def __init__(self, pool):self._pool = poolself.__counter = 0self._echo = _echo = pool._should_log_debug()try:rec = self._connection_record = pool._do_get()conn = self.connection = self._connection_record.get_connection()rec.fairy = weakref.ref(self,lambda ref: _finalize_fairy and \_finalize_fairy(conn, rec, pool, ref, _echo))_refs.add(rec)except:# helps with endless __getattr__ loops later onself.connection = Noneself._connection_record = Noneraiseif self._echo:self._pool.logger.debug("Connection %r checked out from pool" %self.connection)@propertydef _logger(self):return self._pool.logger@propertydef is_valid(self):return self.connection is not None@util.memoized_propertydef info(self):"""Info dictionary associated with the underlying DBAPI connectionreferred to by this :class:`.ConnectionFairy`, allowing user-defineddata to be associated with the connection.The data here will follow along with the DBAPI connection includingafter it is returned to the connection pool and used againin subsequent instances of :class:`.ConnectionFairy`."""try:return self._connection_record.infoexcept AttributeError:raise exc.InvalidRequestError("This connection is closed")def invalidate(self, e=None):"""Mark this connection as invalidated.The connection will be immediately closed. The containingConnectionRecord will create a new connection when next used."""if self.connection is None:raise exc.InvalidRequestError("This connection is closed")if self._connection_record is not None:self._connection_record.invalidate(e=e)self.connection = Noneself._close()def cursor(self, *args, **kwargs):return self.connection.cursor(*args, **kwargs)def __getattr__(self, key):return getattr(self.connection, key)def checkout(self):if self.connection is None:raise exc.InvalidRequestError("This connection is closed")self.__counter += 1if not self._pool.dispatch.checkout or self.__counter != 1:return self# Pool listeners can trigger a reconnection on checkoutattempts = 2while attempts > 0:try:self._pool.dispatch.checkout(self.connection,self._connection_record,self)return selfexcept exc.DisconnectionError, e:self._pool.logger.info("Disconnection detected on checkout: %s", e)self._connection_record.invalidate(e)self.connection = self._connection_record.get_connection()attempts -= 1self._pool.logger.info("Reconnection attempts exhausted on checkout")self.invalidate()raise exc.InvalidRequestError("This connection is closed")def detach(self):"""Separate this connection from its Pool.This means that the connection will no longer be returned to thepool when closed, and will instead be literally closed. Thecontaining ConnectionRecord is separated from the DB-API connection,and will create a new connection when next used.Note that any overall connection limiting constraints imposed by aPool implementation may be violated after a detach, as the detachedconnection is removed from the pool's knowledge and control."""if self._connection_record is not None:_refs.remove(self._connection_record)self._connection_record.fairy = Noneself._connection_record.connection = Noneself._pool._do_return_conn(self._connection_record)self.info = self.info.copy()self._connection_record = Nonedef close(self):self.__counter -= 1if self.__counter == 0:self._close()def _close(self):_finalize_fairy(self.connection, self._connection_record,self._pool, None, self._echo)self.connection = Noneself._connection_record = Noneclass SingletonThreadPool(Pool):"""A Pool that maintains one connection per thread.Maintains one connection per each thread, never moving a connection to athread other than the one which it was created in.Options are the same as those of :class:`.Pool`, as well as::param pool_size: The number of threads in which to maintain connectionsat once. Defaults to five.:class:`.SingletonThreadPool` is used by the SQLite dialectautomatically when a memory-based database is used.See :ref:`sqlite_toplevel`."""def __init__(self, creator, pool_size=5, **kw):kw['use_threadlocal'] = TruePool.__init__(self, creator, **kw)self._conn = threading.local()self._all_conns = set()self.size = pool_sizedef recreate(self):self.logger.info("Pool recreating")return self.__class__(self._creator,pool_size=self.size,recycle=self._recycle,echo=self.echo,logging_name=self._orig_logging_name,use_threadlocal=self._use_threadlocal,_dispatch=self.dispatch,_dialect=self._dialect)def dispose(self):"""Dispose of this pool."""for conn in self._all_conns:try:conn.close()except (SystemExit, KeyboardInterrupt):raiseexcept:# pysqlite won't even let you close a conn from a thread# that didn't create itpassself._all_conns.clear()def _cleanup(self):while len(self._all_conns) > self.size:c = self._all_conns.pop()c.close()def status(self):return "SingletonThreadPool id:%d size: %d" % \(id(self), len(self._all_conns))def _do_return_conn(self, conn):passdef _do_get(self):try:c = self._conn.current()if c:return cexcept AttributeError:passc = self._create_connection()self._conn.current = weakref.ref(c)self._all_conns.add(c)if len(self._all_conns) > self.size:self._cleanup()return cclass DummyLock(object):def acquire(self, wait=True):return Truedef release(self):passclass QueuePool(Pool):"""A :class:`.Pool` that imposes a limit on the number of open connections.:class:`.QueuePool` is the default pooling implementation used forall :class:`.Engine` objects, unless the SQLite dialect is in use."""def __init__(self, creator, pool_size=5, max_overflow=10, timeout=30,**kw):"""Construct a QueuePool.:param creator: a callable function that returns a DB-APIconnection object. The function will be called withparameters.:param pool_size: The size of the pool to be maintained,defaults to 5. This is the largest number of connections thatwill be kept persistently in the pool. Note that the poolbegins with no connections; once this number of connectionsis requested, that number of connections will remain.``pool_size`` can be set to 0 to indicate no size limit; todisable pooling, use a :class:`~sqlalchemy.pool.NullPool`instead.:param max_overflow: The maximum overflow size of thepool. When the number of checked-out connections reaches thesize set in pool_size, additional connections will bereturned up to this limit. When those additional connectionsare returned to the pool, they are disconnected anddiscarded. It follows then that the total number ofsimultaneous connections the pool will allow is pool_size +`max_overflow`, and the total number of "sleeping"connections the pool will allow is pool_size. `max_overflow`can be set to -1 to indicate no overflow limit; no limitwill be placed on the total number of concurrentconnections. Defaults to 10.:param timeout: The number of seconds to wait before giving upon returning a connection. Defaults to 30.:param recycle: If set to non -1, number of seconds betweenconnection recycling, which means upon checkout, if thistimeout is surpassed the connection will be closed andreplaced with a newly opened connection. Defaults to -1.:param echo: If True, connections being pulled and retrievedfrom the pool will be logged to the standard output, as wellas pool sizing information. Echoing can also be achieved byenabling logging for the "sqlalchemy.pool"namespace. Defaults to False.:param use_threadlocal: If set to True, repeated calls to:meth:`connect` within the same application thread will beguaranteed to return the same connection object, if one hasalready been retrieved from the pool and has not beenreturned yet. Offers a slight performance advantage at thecost of individual transactions by default. The:meth:`unique_connection` method is provided to bypass thethreadlocal behavior installed into :meth:`connect`.:param reset_on_return: Determine steps to take onconnections as they are returned to the pool.reset_on_return can have any of these values:* 'rollback' - call rollback() on the connection,to release locks and transaction resources.This is the default value. The vast majorityof use cases should leave this value set.* True - same as 'rollback', this is here forbackwards compatibility.* 'commit' - call commit() on the connection,to release locks and transaction resources.A commit here may be desirable for databases thatcache query plans if a commit is emitted,such as Microsoft SQL Server. However, thisvalue is more dangerous than 'rollback' becauseany data changes present on the transactionare committed unconditionally.* None - don't do anything on the connection.This setting should only be made on a databasethat has no transaction support at all,namely MySQL MyISAM. By not doing anything,performance can be improved. Thissetting should **never be selected** for adatabase that supports transactions,as it will lead to deadlocks and stalestate.* False - same as None, this is here forbackwards compatibility... versionchanged:: 0.7.6``reset_on_return`` accepts values.:param listeners: A list of:class:`~sqlalchemy.interfaces.PoolListener`-like objects ordictionaries of callables that receive events when DB-APIconnections are created, checked out and checked in to thepool."""Pool.__init__(self, creator, **kw)self._pool = sqla_queue.Queue(pool_size)self._overflow = 0 - pool_sizeself._max_overflow = max_overflowself._timeout = timeoutself._overflow_lock = self._max_overflow > -1 and \threading.Lock() or DummyLock()def _do_return_conn(self, conn):try:self._pool.put(conn, False)except sqla_queue.Full:conn.close()self._overflow_lock.acquire()try:self._overflow -= 1finally:self._overflow_lock.release()def _do_get(self):try:wait = self._max_overflow > -1 and \self._overflow >= self._max_overflowreturn self._pool.get(wait, self._timeout)except sqla_queue.SAAbort, aborted:return aborted.context._do_get()except sqla_queue.Empty:if self._max_overflow > -1 and \self._overflow >= self._max_overflow:if not wait:return self._do_get()else:raise exc.TimeoutError("QueuePool limit of size %d overflow %d reached, ""connection timed out, timeout %d" %(self.size(), self.overflow(), self._timeout))self._overflow_lock.acquire()try:if self._max_overflow > -1 and \self._overflow >= self._max_overflow:return self._do_get()else:con = self._create_connection()self._overflow += 1return confinally:self._overflow_lock.release()def recreate(self):self.logger.info("Pool recreating")return self.__class__(self._creator, pool_size=self._pool.maxsize,max_overflow=self._max_overflow,timeout=self._timeout,recycle=self._recycle, echo=self.echo,logging_name=self._orig_logging_name,use_threadlocal=self._use_threadlocal,_dispatch=self.dispatch,_dialect=self._dialect)def dispose(self):while True:try:conn = self._pool.get(False)conn.close()except sqla_queue.Empty:breakself._overflow = 0 - self.size()self.logger.info("Pool disposed. %s", self.status())def _replace(self):self.dispose()np = self.recreate()self._pool.abort(np)return npdef status(self):return "Pool size: %d Connections in pool: %d "\"Current Overflow: %d Current Checked out "\"connections: %d" % (self.size(),self.checkedin(),self.overflow(),self.checkedout())def size(self):return self._pool.maxsizedef checkedin(self):return self._pool.qsize()def overflow(self):return self._overflowdef checkedout(self):return self._pool.maxsize - self._pool.qsize() + self._overflowclass NullPool(Pool):"""A Pool which does not pool connections.Instead it literally opens and closes the underlying DB-API connectionper each connection open/close.Reconnect-related functions such as ``recycle`` and connectioninvalidation are not supported by this Pool implementation, sinceno connections are held persistently... versionchanged:: 0.7:class:`.NullPool` is used by the SQlite dialect automaticallywhen a file-based database is used. See :ref:`sqlite_toplevel`."""def status(self):return "NullPool"def _do_return_conn(self, conn):conn.close()def _do_get(self):return self._create_connection()def recreate(self):self.logger.info("Pool recreating")return self.__class__(self._creator,recycle=self._recycle,echo=self.echo,logging_name=self._orig_logging_name,use_threadlocal=self._use_threadlocal,_dispatch=self.dispatch,_dialect=self._dialect)def dispose(self):passclass StaticPool(Pool):"""A Pool of exactly one connection, used for all requests.Reconnect-related functions such as ``recycle`` and connectioninvalidation (which is also used to support auto-reconnect) are notcurrently supported by this Pool implementation but may be implementedin a future release."""@memoized_propertydef _conn(self):return self._creator()@memoized_propertydef connection(self):return _ConnectionRecord(self)def status(self):return "StaticPool"def dispose(self):if '_conn' in self.__dict__:self._conn.close()self._conn = Nonedef recreate(self):self.logger.info("Pool recreating")return self.__class__(creator=self._creator,recycle=self._recycle,use_threadlocal=self._use_threadlocal,reset_on_return=self._reset_on_return,echo=self.echo,logging_name=self._orig_logging_name,_dispatch=self.dispatch,_dialect=self._dialect)def _create_connection(self):return self._conndef _do_return_conn(self, conn):passdef _do_get(self):return self.connectionclass AssertionPool(Pool):"""A :class:`.Pool` that allows at most one checked out connection atany given time.This will raise an exception if more than one connection is checked outat a time. Useful for debugging code that is using more connectionsthan desired... versionchanged:: 0.7:class:`.AssertionPool` also logs a traceback of wherethe original connection was checked out, and reportsthis in the assertion error raised."""def __init__(self, *args, **kw):self._conn = Noneself._checked_out = Falseself._store_traceback = kw.pop('store_traceback', True)self._checkout_traceback = NonePool.__init__(self, *args, **kw)def status(self):return "AssertionPool"def _do_return_conn(self, conn):if not self._checked_out:raise AssertionError("connection is not checked out")self._checked_out = Falseassert conn is self._conndef dispose(self):self._checked_out = Falseif self._conn:self._conn.close()def recreate(self):self.logger.info("Pool recreating")return self.__class__(self._creator, echo=self.echo,logging_name=self._orig_logging_name,_dispatch=self.dispatch,_dialect=self._dialect)def _do_get(self):if self._checked_out:if self._checkout_traceback:suffix = ' at:\n%s' % ''.join(chop_traceback(self._checkout_traceback))else:suffix = ''raise AssertionError("connection is already checked out" + suffix)if not self._conn:self._conn = self._create_connection()self._checked_out = Trueif self._store_traceback:self._checkout_traceback = traceback.format_stack()return self._connclass _DBProxy(object):"""Layers connection pooling behavior on top of a standard DB-API module.Proxies a DB-API 2.0 connect() call to a connection pool keyed to thespecific connect parameters. Other functions and attributes are delegatedto the underlying DB-API module."""def __init__(self, module, poolclass=QueuePool, **kw):"""Initializes a new proxy.modulea DB-API 2.0 modulepoolclassa Pool class, defaulting to QueuePoolOther parameters are sent to the Pool object's constructor."""self.module = moduleself.kw = kwself.poolclass = poolclassself.pools = {}self._create_pool_mutex = threading.Lock()def close(self):for key in self.pools.keys():del self.pools[key]def __del__(self):self.close()def __getattr__(self, key):return getattr(self.module, key)def get_pool(self, *args, **kw):key = self._serialize(*args, **kw)try:return self.pools[key]except KeyError:self._create_pool_mutex.acquire()try:if key not in self.pools:kw.pop('sa_pool_key', None)pool = self.poolclass(lambda:self.module.connect(*args, **kw), **self.kw)self.pools[key] = poolreturn poolelse:return self.pools[key]finally:self._create_pool_mutex.release()def connect(self, *args, **kw):"""Activate a connection to the database.Connect to the database using this DBProxy's module and the givenconnect arguments. If the arguments match an existing pool, theconnection will be returned from the pool's current thread-localconnection instance, or if there is no thread-local connectioninstance it will be checked out from the set of pooled connections.If the pool has no available connections and allows new connectionsto be created, a new database connection will be made."""return self.get_pool(*args, **kw).connect()def dispose(self, *args, **kw):"""Dispose the pool referenced by the given connect arguments."""key = self._serialize(*args, **kw)try:del self.pools[key]except KeyError:passdef _serialize(self, *args, **kw):if "sa_pool_key" in kw:return kw['sa_pool_key']return tuple(list(args) +[(k, kw[k]) for k in sorted(kw)])