diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/__init__.py | 28 | ||||
-rw-r--r-- | lib/extensions.py | 31 | ||||
-rw-r--r-- | lib/extras.py | 59 | ||||
-rw-r--r-- | lib/pool.py | 184 | ||||
-rw-r--r-- | lib/tz.py | 94 |
5 files changed, 396 insertions, 0 deletions
diff --git a/lib/__init__.py b/lib/__init__.py new file mode 100644 index 0000000..59d0d39 --- /dev/null +++ b/lib/__init__.py @@ -0,0 +1,28 @@ +# psycopg/__init__.py - initialization of the psycopg module +# +# Copyright (C) 2003-2004 Federico Di Gregorio <fog@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +__all__ = ['extensions', 'extras', 'tz', 'pool'] + +# import the DBAPI-2.0 stuff into top-level module +from _psycopg import BINARY, NUMBER, STRING, DATETIME, ROWID + +from _psycopg import Binary, Date, Time, Timestamp +from _psycopg import DateFromTicks, TimeFromTicks, TimestampFromTicks + +from _psycopg import Error, Warning, DataError, DatabaseError, ProgrammingError +from _psycopg import IntegrityError, InterfaceError, InternalError +from _psycopg import NotSupportedError, OperationalError + +from _psycopg import connect, apilevel, threadsafety, paramstyle +from _psycopg import __version__ diff --git a/lib/extensions.py b/lib/extensions.py new file mode 100644 index 0000000..0424d43 --- /dev/null +++ b/lib/extensions.py @@ -0,0 +1,31 @@ +# psycopg/extensions.py - DBAPI-2.0 extensions specific to psycopg +# +# Copyright (C) 2003-2004 Federico Di Gregorio <fog@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +from _psycopg import UNICODE, INTEGER, LONGINTEGER, BOOLEAN, FLOAT +from _psycopg import TIME, DATE, INTERVAL + +from _psycopg import Boolean, QuotedString +try: + from _psycopg import DateFromMx, TimeFromMx, TimestampFromMx + from _psycopg import IntervalFromMx +except: + pass +try: + from _psycopg import DateFromPy, TimeFromPy, TimestampFromPy + from _psycopg import IntervalFromPy +except: + pass + +from _psycopg import adapt, adapters, encodings, connection, cursor +from _psycopg import string_types, binary_types, new_type, register_type diff --git a/lib/extras.py b/lib/extras.py new file mode 100644 index 0000000..b9532f9 --- /dev/null +++ b/lib/extras.py @@ -0,0 +1,59 @@ +# psycopg/extras.py - miscellaneous extra goodies for psycopg +# +# Copyright (C) 2003-2004 Federico Di Gregorio <fog@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +from psycopg.extensions import cursor as _cursor + +class DictCursor(_cursor): + """A cursor that keeps a list of column name -> index mappings.""" + + __query_executed = 0 + + def execute(self, query, vars=None, async=0): + self.tuple_factory = DictRow + self.index = {} + self.__query_executed = 1 + return _cursor.execute(self, query, vars, async) + + def _build_index(self): + if self.description: + for i in range(len(self.description)): + self.index[self.description[i][0]] = i + + def fetchone(self): + if self.__query_executed: + self._build_index() + return _cursor.fetchone(self) + + def fetchmany(self, size=None): + if self.__query_executed: + self._build_index() + return _cursor.fetchmany(self, size) + + def fetchall(self): + if self.__query_executed: + self._build_index() + return _cursor.fetchall(self) + +class DictRow(list): + """A row object that allow by-colun-name access to data.""" + + def __init__(self, cursor): + self._cursor = cursor + self[:] = [None] * len(cursor.description) + print cursor, self + + def __getitem__(self, x): + if type(x) != int: + x = self._cursor.index[x] + return list.__getitem__(self, x) diff --git a/lib/pool.py b/lib/pool.py new file mode 100644 index 0000000..119a0a8 --- /dev/null +++ b/lib/pool.py @@ -0,0 +1,184 @@ +# psycopg/pool.py - pooling code for psycopg +# +# Copyright (C) 2003-2004 Federico Di Gregorio <fog@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +import psycopg + +try: + from zLOG import LOG, DEBUG, INFO + def dbg(*args): + LOG('ZPsycopgDA', DEBUG, "", + ' '.join([str(x) for x in args])+'\n') + LOG('ZPsycopgDA', INFO, "Installed", "Logging using Zope's zLOG\n") +except: + import sys + def dbg(*args): + sys.stderr.write(' '.join(args)+'\n') + + + +class PoolError(psycopg.Error): + pass + + + +class AbstractConnectionPool(object): + """Generic key-based pooling code.""" + + def __init__(self, minconn, maxconn, *args, **kwargs): + """Initialize the connection pool. + + New 'minconn' connections are created immediately calling 'connfunc' + with given parameters. The connection pool will support a maximum of + about 'maxconn' connections. + """ + self.minconn = minconn + self.maxconn = maxconn + self.closed = False + + self._args = args + self._kwargs = kwargs + + self._pool = [] + self._used = {} + self._keys = 0 + + for i in range(self.minconn): + self._connect() + + def _connect(self, key=None): + """Create a new connection and assign it to 'key' if not None.""" + conn = psycopg.connect(*self._args, **self._kwargs) + if key is not None: + self._used[key] = conn + else: + self._pool.append(conn) + return conn + + def _getkey(self): + """Return a new unique key.""" + self._keys += 1 + return self._keys + + def _findkey(self, conn): + """Return the key associated with a connection or None.""" + for o, k in self._used.items(): + if o == conn: + return k + + def _getconn(self, key=None): + """Get a free connection and assign it to 'key' if not None.""" + if self.closed: raise PoolError("connection pool is closed") + if key is None: key = self._getkey() + + if not self._used.has_key(key): + if not self._pool: + if len(self._used) == self.maxconn: + raise PoolError("connection pool exausted") + return self._connect(key) + else: + self._used[key] = self._pool.pop() + return self._used[key] + + def _putconn(self, conn, key=None, close=False): + """Put away a connection.""" + if self.closed: raise PoolError("connection pool is closed") + if key is None: key = self._findkey(conn) + + if not key: + raise PoolError("trying to put unkeyed connection") + + if len(self._pool) < self.minconn and not close: + self._pool.append(conn) + else: + conn.close() + + # here we check for the presence of key because it can happen that a + # thread tries to put back a connection after a call to close + if not self.closed or key in self._used: + del self._used[key] + + def _closeall(self): + """Close all connections. + + Note that this can lead to some code fail badly when trying to use + an already closed connection. If you call .closeall() make sure + your code can deal with it. + """ + if self.closed: raise PoolError("connection pool is closed") + for conn in self._pool + list(self._used.values()): + try: + print "Closing connection", conn + conn.close() + except: + pass + self.closed = True + + + +class SimpleConnectionPool(AbstractConnectionPool): + """A connection pool that can't be shared across different threads.""" + + getconn = AbstractConnectionPool._getconn + putconn = AbstractConnectionPool._putconn + closeall = AbstractConnectionPool._closeall + + + +class ThreadedConnectionPool(AbstractConnectionPool): + """A connection pool that works with the threading module. + + Note that this connection pool generates by itself the required keys + using the current thread id. This means that untill a thread put away + a connection it will always get the same connection object by successive + .getconn() calls. + """ + + def __init__(self, minconn, maxconn, *args, **kwargs): + """Initialize the threading lock.""" + import threading + AbstractConnectionPool.__init__( + self, minconn, maxconn, *args, **kwargs) + self._lock = threading.Lock() + + # we we'll need the thread module, to determine thread ids, so we + # import it here and copy it in an instance variable + import thread + self.__thread = thread + + def getconn(self): + """Generate thread id and return a connection.""" + key = self.__thread.get_ident() + self._lock.acquire() + try: + return self._getconn(key) + finally: + self._lock.release() + + def putconn(self, conn=None, close=False): + """Put away an unused connection.""" + key = self.__thread.get_ident() + self._lock.acquire() + try: + if not conn: conn = self._used[key] + self._putconn(conn, key, close) + finally: + self._lock.release() + + def closeall(self): + """Close all connections (even the one currently in use.""" + self._lock.acquire() + try: + self._closeall() + finally: + self._lock.release() diff --git a/lib/tz.py b/lib/tz.py new file mode 100644 index 0000000..c7a855b --- /dev/null +++ b/lib/tz.py @@ -0,0 +1,94 @@ +# psycopg/tz.py - tzinfo implementation +# +# Copyright (C) 2003-2004 Federico Di Gregorio <fog@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. + +import datetime +import time + + +ZERO = datetime.timedelta(0) + +class FixedOffsetTimezone(datetime.tzinfo): + """Fixed offset in minutes east from UTC. + + This is exactly the implementation found in Python 2.3.x documentation, + with a small change to the __init__ method to allow for pickling and a + default name in the form 'sHH:MM' ('s' is the sign.) + """ + _name = None + _offset = ZERO + + def __init__(self, offset=None, name=None): + if offset is not None: + self._offset = datetime.timedelta(minutes = offset) + if name is not None: + self._name = name + + def utcoffset(self, dt): + return self._offset + + def tzname(self, dt): + if self._name is not None: + return self._name + else: + seconds = self._offset.seconds + self._offset.days * 86400 + hours, seconds = divmod(seconds, 3600) + minutes = seconds/60 + if minutes: + return "%+03d:%d" % (hours, minutes) + else: + return "%+03d" % hours + + def dst(self, dt): + return ZERO + + +STDOFFSET = datetime.timedelta(seconds = -time.timezone) +if time.daylight: + DSTOFFSET = datetime.timedelta(seconds = -time.altzone) +else: + DSTOFFSET = STDOFFSET +DSTDIFF = DSTOFFSET - STDOFFSET + +class LocalTimezone(datetime.tzinfo): + """Platform idea of local timezone. + + This is the exact implementation from the Pyhton 2.3 documentation. + """ + + def utcoffset(self, dt): + if self._isdst(dt): + return DSTOFFSET + else: + return STDOFFSET + + def dst(self, dt): + if self._isdst(dt): + return DSTDIFF + else: + return ZERO + + def tzname(self, dt): + return time.tzname[self._isdst(dt)] + + def _isdst(self, dt): + tt = (dt.year, dt.month, dt.day, + dt.hour, dt.minute, dt.second, + dt.weekday(), 0, -1) + stamp = time.mktime(tt) + tt = time.localtime(stamp) + return tt.tm_isdst > 0 + +LOCAL = LocalTimezone() + +# TODO: pre-generate some interesting time zones? |