| author | Radek Brich <radek.brich@devl.cz> | 
| Tue, 14 Feb 2012 18:15:56 +0100 | |
| changeset 29 | 8636719a30f6 | 
| parent 26 | 7f219da7ab71 | 
| child 31 | c2e6e24b83d9 | 
| permissions | -rw-r--r-- | 
| 0 | 1 | # -*- coding: utf-8 -*- | 
| 2 | # | |
| 3 | # PgManager - manage database connections | |
| 4 | # | |
| 5 | # Requires: Python 2.6, psycopg2 | |
| 6 | # | |
| 9 
2fcc8ef0b97d
Reorganize again :-) Add setup.py.
 Radek Brich <radek.brich@devl.cz> parents: 
8diff
changeset | 7 | # Part of pgtoolkit | 
| 
2fcc8ef0b97d
Reorganize again :-) Add setup.py.
 Radek Brich <radek.brich@devl.cz> parents: 
8diff
changeset | 8 | # http://hg.devl.cz/pgtoolkit | 
| 
2fcc8ef0b97d
Reorganize again :-) Add setup.py.
 Radek Brich <radek.brich@devl.cz> parents: 
8diff
changeset | 9 | # | 
| 0 | 10 | # Copyright (c) 2010, 2011 Radek Brich <radek.brich@devl.cz> | 
| 11 | # | |
| 12 | # Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 13 | # of this software and associated documentation files (the "Software"), to deal | |
| 14 | # in the Software without restriction, including without limitation the rights | |
| 15 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 16 | # copies of the Software, and to permit persons to whom the Software is | |
| 17 | # furnished to do so, subject to the following conditions: | |
| 18 | # | |
| 19 | # The above copyright notice and this permission notice shall be included in | |
| 20 | # all copies or substantial portions of the Software. | |
| 21 | # | |
| 22 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 23 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 24 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 25 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 26 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 27 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| 28 | # THE SOFTWARE. | |
| 29 | ||
| 30 | """Postgres database connection manager | |
| 31 | ||
| 32 | PgManager wraps psycopg2 connect function, adding following features: | |
| 33 | ||
| 34 | * Manage database connection parameters - link connection parameters | |
| 35 | to an unique identifier, retrieve connection object by this identifier | |
| 36 | ||
| 37 | * Connection pooling - connections with same identifier are pooled and reused | |
| 38 | ||
| 39 | * Easy query using the with statement - retrieve cursor directly by connection | |
| 40 | identifier, don't worry about connections | |
| 41 | ||
| 42 | * Dict rows - cursor has additional methods like fetchall_dict(), which | |
| 43 | returns dict row instead of ordinary list-like row | |
| 44 | ||
| 45 | Example: | |
| 46 | ||
| 47 | import pgmanager | |
| 48 | ||
| 49 | pgm = pgmanager.get_instance() | |
| 50 | pgm.create_conn(hostaddr='127.0.0.1', dbname='postgres') | |
| 51 | ||
| 52 | with pgm.cursor() as curs: | |
| 53 |     curs.execute('SELECT now() AS now')
 | |
| 54 | row = curs.fetchone_dict() | |
| 55 | print row.now | |
| 56 | ||
| 57 | First, we have obtained PgManager instance. This is like calling | |
| 58 | PgManager(), although in our example the instance is global. That means | |
| 59 | getting the instance in another module brings us all the defined connections | |
| 60 | etc. | |
| 61 | ||
| 9 
2fcc8ef0b97d
Reorganize again :-) Add setup.py.
 Radek Brich <radek.brich@devl.cz> parents: 
8diff
changeset | 62 | On second line we have created connection named 'default' (this name can be left out). | 
| 0 | 63 | The with statement obtains connection (actually connects to database when needed), | 
| 9 
2fcc8ef0b97d
Reorganize again :-) Add setup.py.
 Radek Brich <radek.brich@devl.cz> parents: 
8diff
changeset | 64 | then returns cursor for this connection. At the end of with statement, | 
| 
2fcc8ef0b97d
Reorganize again :-) Add setup.py.
 Radek Brich <radek.brich@devl.cz> parents: 
8diff
changeset | 65 | the connection is returned to the pool or closed (depending on number of connections | 
| 
2fcc8ef0b97d
Reorganize again :-) Add setup.py.
 Radek Brich <radek.brich@devl.cz> parents: 
8diff
changeset | 66 | in pool and on setting of keep_open parameter). | 
| 0 | 67 | |
| 68 | The row returned by fetchone_dict() is special dict object, which can be accessed | |
| 69 | using item or attribute access, that is row['now'] or row.now. | |
| 70 | """ | |
| 71 | ||
| 72 | from contextlib import contextmanager | |
| 73 | import logging | |
| 74 | import threading | |
| 24 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 75 | import multiprocessing | 
| 0 | 76 | import select | 
| 8 
2911935c524d
pgmanager: Add keep_alive support.
 Radek Brich <radek.brich@devl.cz> parents: 
7diff
changeset | 77 | import socket | 
| 0 | 78 | |
| 79 | import psycopg2 | |
| 80 | import psycopg2.extensions | |
| 81 | ||
| 19 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 82 | from psycopg2 import DatabaseError, IntegrityError, OperationalError | 
| 0 | 83 | |
| 84 | ||
| 26 
7f219da7ab71
Add logging mechanism for notices from postgres server. Rename SQL log to "pgmanager_sql".
 Radek Brich <radek.brich@devl.cz> parents: 
24diff
changeset | 85 | log_sql = logging.getLogger("pgmanager_sql")
 | 
| 
7f219da7ab71
Add logging mechanism for notices from postgres server. Rename SQL log to "pgmanager_sql".
 Radek Brich <radek.brich@devl.cz> parents: 
24diff
changeset | 86 | log_notices = logging.getLogger("pgmanager_notices")
 | 
| 20 
73f0d53fef6b
PgManager: Do not add NullHandler to logger. Rewrite get_instance(). ToolBase: fix prepare_conns() method.
 Radek Brich <radek.brich@devl.cz> parents: 
19diff
changeset | 87 | |
| 
73f0d53fef6b
PgManager: Do not add NullHandler to logger. Rewrite get_instance(). ToolBase: fix prepare_conns() method.
 Radek Brich <radek.brich@devl.cz> parents: 
19diff
changeset | 88 | |
| 0 | 89 | class PgManagerError(Exception): | 
| 90 | ||
| 91 | pass | |
| 92 | ||
| 93 | ||
| 94 | class ConnectionInfo: | |
| 95 | ||
| 19 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 96 | def __init__(self, dsn, isolation_level=None, keep_alive=True, | 
| 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 97 | init_statement=None, keep_open=1): | 
| 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 98 | |
| 0 | 99 | self.dsn = dsn | 
| 100 | self.isolation_level = isolation_level | |
| 19 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 101 | self.keep_alive = keep_alive | 
| 0 | 102 | self.init_statement = init_statement | 
| 103 | self.keep_open = keep_open | |
| 104 | ||
| 105 | ||
| 106 | class RowDict(dict): | |
| 107 | ||
| 108 | def __getattr__(self, key): | |
| 109 | return self[key] | |
| 110 | ||
| 111 | ||
| 112 | class Cursor(psycopg2.extensions.cursor): | |
| 113 | ||
| 114 | def execute(self, query, args=None): | |
| 115 | try: | |
| 116 | return super(Cursor, self).execute(query, args) | |
| 117 | finally: | |
| 24 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 118 | if self.query: | 
| 26 
7f219da7ab71
Add logging mechanism for notices from postgres server. Rename SQL log to "pgmanager_sql".
 Radek Brich <radek.brich@devl.cz> parents: 
24diff
changeset | 119 |                 log_sql.info(self.query.decode('utf8'))
 | 
| 0 | 120 | |
| 121 | def callproc(self, procname, args=None): | |
| 122 | try: | |
| 123 | return super(Cursor, self).callproc(procname, args) | |
| 124 | finally: | |
| 24 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 125 | if self.query: | 
| 26 
7f219da7ab71
Add logging mechanism for notices from postgres server. Rename SQL log to "pgmanager_sql".
 Radek Brich <radek.brich@devl.cz> parents: 
24diff
changeset | 126 |                 log_sql.info(self.query.decode('utf8'))
 | 
| 0 | 127 | |
| 128 | def row_dict(self, row, lstrip=None): | |
| 129 | adjustname = lambda a: a | |
| 130 | if lstrip: | |
| 131 | adjustname = lambda a: a.lstrip(lstrip) | |
| 132 | return RowDict(zip([adjustname(desc[0]) for desc in self.description], row)) | |
| 133 | ||
| 134 | def fetchone_dict(self, lstrip=None): | |
| 135 | row = super(Cursor, self).fetchone() | |
| 7 
685b20d2d3ab
Reorganize directories. PgDataDiff - reworked. PgManager - add fetchone_adapted, fetchall_adapted to cursor.
 Radek Brich <radek.brich@devl.cz> parents: 
4diff
changeset | 136 | if row is None: | 
| 
685b20d2d3ab
Reorganize directories. PgDataDiff - reworked. PgManager - add fetchone_adapted, fetchall_adapted to cursor.
 Radek Brich <radek.brich@devl.cz> parents: 
4diff
changeset | 137 | return None | 
| 0 | 138 | return self.row_dict(row, lstrip) | 
| 139 | ||
| 140 | def fetchall_dict(self, lstrip=None): | |
| 141 | rows = super(Cursor, self).fetchall() | |
| 142 | return [self.row_dict(row, lstrip) for row in rows] | |
| 143 | ||
| 7 
685b20d2d3ab
Reorganize directories. PgDataDiff - reworked. PgManager - add fetchone_adapted, fetchall_adapted to cursor.
 Radek Brich <radek.brich@devl.cz> parents: 
4diff
changeset | 144 | def fetchone_adapted(self): | 
| 
685b20d2d3ab
Reorganize directories. PgDataDiff - reworked. PgManager - add fetchone_adapted, fetchall_adapted to cursor.
 Radek Brich <radek.brich@devl.cz> parents: 
4diff
changeset | 145 | '''Like fetchone() but values are quoted for direct inclusion in SQL query. | 
| 
685b20d2d3ab
Reorganize directories. PgDataDiff - reworked. PgManager - add fetchone_adapted, fetchall_adapted to cursor.
 Radek Brich <radek.brich@devl.cz> parents: 
4diff
changeset | 146 | |
| 
685b20d2d3ab
Reorganize directories. PgDataDiff - reworked. PgManager - add fetchone_adapted, fetchall_adapted to cursor.
 Radek Brich <radek.brich@devl.cz> parents: 
4diff
changeset | 147 | This is useful when you need to generate SQL script from data returned | 
| 
685b20d2d3ab
Reorganize directories. PgDataDiff - reworked. PgManager - add fetchone_adapted, fetchall_adapted to cursor.
 Radek Brich <radek.brich@devl.cz> parents: 
4diff
changeset | 148 | by the query. Use mogrify() for simple cases. | 
| 
685b20d2d3ab
Reorganize directories. PgDataDiff - reworked. PgManager - add fetchone_adapted, fetchall_adapted to cursor.
 Radek Brich <radek.brich@devl.cz> parents: 
4diff
changeset | 149 | |
| 
685b20d2d3ab
Reorganize directories. PgDataDiff - reworked. PgManager - add fetchone_adapted, fetchall_adapted to cursor.
 Radek Brich <radek.brich@devl.cz> parents: 
4diff
changeset | 150 | ''' | 
| 
685b20d2d3ab
Reorganize directories. PgDataDiff - reworked. PgManager - add fetchone_adapted, fetchall_adapted to cursor.
 Radek Brich <radek.brich@devl.cz> parents: 
4diff
changeset | 151 | row = super(Cursor, self).fetchone() | 
| 
685b20d2d3ab
Reorganize directories. PgDataDiff - reworked. PgManager - add fetchone_adapted, fetchall_adapted to cursor.
 Radek Brich <radek.brich@devl.cz> parents: 
4diff
changeset | 152 | if row is None: | 
| 
685b20d2d3ab
Reorganize directories. PgDataDiff - reworked. PgManager - add fetchone_adapted, fetchall_adapted to cursor.
 Radek Brich <radek.brich@devl.cz> parents: 
4diff
changeset | 153 | return None | 
| 
685b20d2d3ab
Reorganize directories. PgDataDiff - reworked. PgManager - add fetchone_adapted, fetchall_adapted to cursor.
 Radek Brich <radek.brich@devl.cz> parents: 
4diff
changeset | 154 |         return [self.mogrify('%s', [x]).decode('utf8') for x in row]
 | 
| 
685b20d2d3ab
Reorganize directories. PgDataDiff - reworked. PgManager - add fetchone_adapted, fetchall_adapted to cursor.
 Radek Brich <radek.brich@devl.cz> parents: 
4diff
changeset | 155 | |
| 
685b20d2d3ab
Reorganize directories. PgDataDiff - reworked. PgManager - add fetchone_adapted, fetchall_adapted to cursor.
 Radek Brich <radek.brich@devl.cz> parents: 
4diff
changeset | 156 | def fetchall_adapted(self): | 
| 
685b20d2d3ab
Reorganize directories. PgDataDiff - reworked. PgManager - add fetchone_adapted, fetchall_adapted to cursor.
 Radek Brich <radek.brich@devl.cz> parents: 
4diff
changeset | 157 | '''Like fetchall() but values are quoted for direct inclusion in SQL query.''' | 
| 
685b20d2d3ab
Reorganize directories. PgDataDiff - reworked. PgManager - add fetchone_adapted, fetchall_adapted to cursor.
 Radek Brich <radek.brich@devl.cz> parents: 
4diff
changeset | 158 | rows = super(Cursor, self).fetchall() | 
| 
685b20d2d3ab
Reorganize directories. PgDataDiff - reworked. PgManager - add fetchone_adapted, fetchall_adapted to cursor.
 Radek Brich <radek.brich@devl.cz> parents: 
4diff
changeset | 159 |         return [[self.mogrify('%s', [x]).decode('utf8') for x in row] for row in rows]
 | 
| 
685b20d2d3ab
Reorganize directories. PgDataDiff - reworked. PgManager - add fetchone_adapted, fetchall_adapted to cursor.
 Radek Brich <radek.brich@devl.cz> parents: 
4diff
changeset | 160 | |
| 0 | 161 | |
| 162 | class Connection(psycopg2.extensions.connection): | |
| 163 | ||
| 164 | def cursor(self, name=None): | |
| 165 | if name is None: | |
| 166 | return super(Connection, self).cursor(cursor_factory=Cursor) | |
| 167 | else: | |
| 168 | return super(Connection, self).cursor(name, cursor_factory=Cursor) | |
| 169 | ||
| 8 
2911935c524d
pgmanager: Add keep_alive support.
 Radek Brich <radek.brich@devl.cz> parents: 
7diff
changeset | 170 | def keep_alive(self): | 
| 
2911935c524d
pgmanager: Add keep_alive support.
 Radek Brich <radek.brich@devl.cz> parents: 
7diff
changeset | 171 | '''Set socket to keepalive mode. Must be called before any query.''' | 
| 
2911935c524d
pgmanager: Add keep_alive support.
 Radek Brich <radek.brich@devl.cz> parents: 
7diff
changeset | 172 | sock = socket.fromfd(self.fileno(), socket.AF_INET, socket.SOCK_STREAM) | 
| 
2911935c524d
pgmanager: Add keep_alive support.
 Radek Brich <radek.brich@devl.cz> parents: 
7diff
changeset | 173 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) | 
| 19 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 174 | try: | 
| 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 175 | # Maximum keep-alive probes before asuming the connection is lost | 
| 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 176 | sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 5) | 
| 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 177 | # Interval (in seconds) between keep-alive probes | 
| 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 178 | sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 2) | 
| 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 179 | # Maximum idle time (in seconds) before start sending keep-alive probes | 
| 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 180 | sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 10) | 
| 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 181 | except socket.error: | 
| 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 182 | pass | 
| 8 
2911935c524d
pgmanager: Add keep_alive support.
 Radek Brich <radek.brich@devl.cz> parents: 
7diff
changeset | 183 | |
| 0 | 184 | |
| 185 | class PgManager: | |
| 186 | ||
| 187 | def __init__(self): | |
| 188 |         self.conn_known = {}  # available connections
 | |
| 24 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 189 |         self.conn_pool = {}  # active connetions
 | 
| 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 190 | self.lock = threading.Lock() # mutual exclusion for threads | 
| 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 191 | self.pid = multiprocessing.current_process().pid # forking check | 
| 0 | 192 | |
| 193 | def __del__(self): | |
| 194 | for conn in tuple(self.conn_known.keys()): | |
| 195 | self.destroy_conn(conn) | |
| 196 | ||
| 23 
dc2dbe872fc8
Add keep_open parameter to create_conn.
 Radek Brich <brich.radek@ifortuna.cz> parents: 
20diff
changeset | 197 | def create_conn(self, name='default', keep_open=1, isolation_level=None, keep_alive=True, dsn=None, **kw): | 
| 19 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 198 | '''Create named connection. | 
| 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 199 | |
| 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 200 | name -- name for connection (default is "default") | 
| 23 
dc2dbe872fc8
Add keep_open parameter to create_conn.
 Radek Brich <brich.radek@ifortuna.cz> parents: 
20diff
changeset | 201 | keep_open -- how many connections will be kept open in pool (more connections will still be created, | 
| 
dc2dbe872fc8
Add keep_open parameter to create_conn.
 Radek Brich <brich.radek@ifortuna.cz> parents: 
20diff
changeset | 202 | but they will be closed by put_conn) | 
| 19 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 203 | isolation_level -- "autocommit", "read_committed", "serializable" or None for driver default | 
| 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 204 | keep_alive -- set socket to keepalive mode | 
| 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 205 | dsn -- string with connection parameters (dsn means Data Source Name) | 
| 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 206 | |
| 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 207 | Alternative for dsn is keyword args (same names as in dsn). | 
| 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 208 | |
| 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 209 | ''' | 
| 0 | 210 | if name in self.conn_known: | 
| 2 | 211 |             raise PgManagerError('Connection name "%s" already registered.' % name)
 | 
| 0 | 212 | |
| 213 | if dsn is None: | |
| 19 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 214 | dsn = ' '.join([x[0]+'='+str(x[1]) for x in kw.items() if x[1] is not None]) | 
| 0 | 215 | |
| 216 | isolation_level = self._normalize_isolation_level(isolation_level) | |
| 23 
dc2dbe872fc8
Add keep_open parameter to create_conn.
 Radek Brich <brich.radek@ifortuna.cz> parents: 
20diff
changeset | 217 | ci = ConnectionInfo(dsn, isolation_level, keep_alive, keep_open=keep_open) | 
| 0 | 218 | |
| 219 | self.conn_known[name] = ci | |
| 220 | self.conn_pool[name] = [] | |
| 221 | ||
| 222 | def close_conn(self, name='default'): | |
| 223 | '''Close all connections of given name. | |
| 224 | ||
| 225 | Connection credentials are still saved. | |
| 226 | ||
| 227 | ''' | |
| 228 | while len(self.conn_pool[name]): | |
| 229 | conn = self.conn_pool[name].pop() | |
| 230 | conn.close() | |
| 231 | ||
| 232 | def destroy_conn(self, name='default'): | |
| 233 | '''Destroy connection. | |
| 234 | ||
| 235 | Counterpart of create_conn. | |
| 236 | ||
| 237 | ''' | |
| 238 | if not name in self.conn_known: | |
| 2 | 239 |             raise PgManagerError('Connection name "%s" not registered.' % name)
 | 
| 0 | 240 | |
| 241 | self.close_conn(name) | |
| 242 | ||
| 243 | del self.conn_known[name] | |
| 244 | del self.conn_pool[name] | |
| 245 | ||
| 246 | def get_conn(self, name='default'): | |
| 247 | '''Get connection of name 'name' from pool.''' | |
| 24 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 248 | self._check_fork() | 
| 0 | 249 | self.lock.acquire() | 
| 250 | try: | |
| 251 | if not name in self.conn_known: | |
| 2 | 252 |                 raise PgManagerError("Connection name '%s' not registered." % name)
 | 
| 0 | 253 | |
| 254 | conn = None | |
| 255 | while len(self.conn_pool[name]) and conn is None: | |
| 256 | conn = self.conn_pool[name].pop() | |
| 257 | if conn.closed: | |
| 258 | conn = None | |
| 259 | ||
| 260 | if conn is None: | |
| 261 | ci = self.conn_known[name] | |
| 8 
2911935c524d
pgmanager: Add keep_alive support.
 Radek Brich <radek.brich@devl.cz> parents: 
7diff
changeset | 262 | conn = self._connect(ci) | 
| 0 | 263 | finally: | 
| 264 | self.lock.release() | |
| 265 | return conn | |
| 266 | ||
| 267 | def put_conn(self, conn, name='default'): | |
| 268 | '''Put connection back to pool. | |
| 269 | ||
| 270 | Name must be same as used for get_conn, | |
| 271 | otherwise things become broken. | |
| 272 | ||
| 273 | ''' | |
| 274 | self.lock.acquire() | |
| 275 | try: | |
| 276 | if not name in self.conn_known: | |
| 2 | 277 |                 raise PgManagerError("Connection name '%s' not registered." % name)
 | 
| 0 | 278 | |
| 279 | if len(self.conn_pool[name]) >= self.conn_known[name].keep_open: | |
| 280 | conn.close() | |
| 281 | return | |
| 282 | ||
| 283 | if conn.get_transaction_status() == psycopg2.extensions.TRANSACTION_STATUS_UNKNOWN: | |
| 284 | conn.close() | |
| 285 | return | |
| 286 | ||
| 287 | # connection returned to the pool must not be in transaction | |
| 288 | if conn.get_transaction_status() != psycopg2.extensions.TRANSACTION_STATUS_IDLE: | |
| 289 | conn.rollback() | |
| 290 | ||
| 291 | self.conn_pool[name].append(conn) | |
| 292 | finally: | |
| 293 | self.lock.release() | |
| 294 | ||
| 295 | @contextmanager | |
| 296 | def cursor(self, name='default'): | |
| 297 | '''Cursor context. | |
| 298 | ||
| 299 | Uses any connection of name 'name' from pool | |
| 300 | and returns cursor for that connection. | |
| 301 | ||
| 302 | ''' | |
| 303 | conn = self.get_conn(name) | |
| 304 | ||
| 305 | try: | |
| 306 | curs = conn.cursor() | |
| 307 | yield curs | |
| 308 | finally: | |
| 309 | curs.close() | |
| 26 
7f219da7ab71
Add logging mechanism for notices from postgres server. Rename SQL log to "pgmanager_sql".
 Radek Brich <radek.brich@devl.cz> parents: 
24diff
changeset | 310 | self.log_notices(conn) | 
| 0 | 311 | self.put_conn(conn, name) | 
| 312 | ||
| 26 
7f219da7ab71
Add logging mechanism for notices from postgres server. Rename SQL log to "pgmanager_sql".
 Radek Brich <radek.brich@devl.cz> parents: 
24diff
changeset | 313 | def log_notices(self, conn): | 
| 
7f219da7ab71
Add logging mechanism for notices from postgres server. Rename SQL log to "pgmanager_sql".
 Radek Brich <radek.brich@devl.cz> parents: 
24diff
changeset | 314 | for notice in conn.notices: | 
| 
7f219da7ab71
Add logging mechanism for notices from postgres server. Rename SQL log to "pgmanager_sql".
 Radek Brich <radek.brich@devl.cz> parents: 
24diff
changeset | 315 | log_notices.info(notice.rstrip()) | 
| 
7f219da7ab71
Add logging mechanism for notices from postgres server. Rename SQL log to "pgmanager_sql".
 Radek Brich <radek.brich@devl.cz> parents: 
24diff
changeset | 316 | conn.notices[:] = [] | 
| 
7f219da7ab71
Add logging mechanism for notices from postgres server. Rename SQL log to "pgmanager_sql".
 Radek Brich <radek.brich@devl.cz> parents: 
24diff
changeset | 317 | |
| 0 | 318 | def wait_for_notify(self, name='default', timeout=5): | 
| 319 | '''Wait for asynchronous notifies, return the last one. | |
| 320 | ||
| 321 | Returns None on timeout. | |
| 322 | ||
| 323 | ''' | |
| 324 | conn = self.get_conn(name) | |
| 325 | ||
| 326 | try: | |
| 327 | # any residual notify? | |
| 328 | # then return it, that should not break anything | |
| 329 | if conn.notifies: | |
| 330 | return conn.notifies.pop() | |
| 331 | ||
| 332 | if select.select([conn], [], [], timeout) == ([], [], []): | |
| 333 | # timeout | |
| 334 | return None | |
| 335 | else: | |
| 336 | conn.poll() | |
| 337 | ||
| 338 | # return just the last notify (we do not care for older ones) | |
| 339 | if conn.notifies: | |
| 340 | return conn.notifies.pop() | |
| 341 | return None | |
| 342 | finally: | |
| 343 | # clean notifies | |
| 344 | while conn.notifies: | |
| 345 | conn.notifies.pop() | |
| 346 | self.put_conn(conn, name) | |
| 347 | ||
| 8 
2911935c524d
pgmanager: Add keep_alive support.
 Radek Brich <radek.brich@devl.cz> parents: 
7diff
changeset | 348 | def _connect(self, ci): | 
| 
2911935c524d
pgmanager: Add keep_alive support.
 Radek Brich <radek.brich@devl.cz> parents: 
7diff
changeset | 349 | conn = psycopg2.connect(ci.dsn, connection_factory=Connection) | 
| 19 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 350 | if ci.keep_alive: | 
| 
e526ca146fa9
Add documentation for create_conn(). Fix keep_alive - do not crash if socket settings are not supported.
 Radek Brich <radek.brich@devl.cz> parents: 
9diff
changeset | 351 | conn.keep_alive() | 
| 8 
2911935c524d
pgmanager: Add keep_alive support.
 Radek Brich <radek.brich@devl.cz> parents: 
7diff
changeset | 352 | if not ci.isolation_level is None: | 
| 
2911935c524d
pgmanager: Add keep_alive support.
 Radek Brich <radek.brich@devl.cz> parents: 
7diff
changeset | 353 | conn.set_isolation_level(ci.isolation_level) | 
| 
2911935c524d
pgmanager: Add keep_alive support.
 Radek Brich <radek.brich@devl.cz> parents: 
7diff
changeset | 354 | if ci.init_statement: | 
| 
2911935c524d
pgmanager: Add keep_alive support.
 Radek Brich <radek.brich@devl.cz> parents: 
7diff
changeset | 355 | curs = conn.cursor() | 
| 
2911935c524d
pgmanager: Add keep_alive support.
 Radek Brich <radek.brich@devl.cz> parents: 
7diff
changeset | 356 | curs.execute(ci.init_statement) | 
| 
2911935c524d
pgmanager: Add keep_alive support.
 Radek Brich <radek.brich@devl.cz> parents: 
7diff
changeset | 357 | curs.close() | 
| 
2911935c524d
pgmanager: Add keep_alive support.
 Radek Brich <radek.brich@devl.cz> parents: 
7diff
changeset | 358 | return conn | 
| 
2911935c524d
pgmanager: Add keep_alive support.
 Radek Brich <radek.brich@devl.cz> parents: 
7diff
changeset | 359 | |
| 0 | 360 | def _normalize_isolation_level(self, level): | 
| 361 | if type(level) == str: | |
| 362 | if level.lower() == 'autocommit': | |
| 363 | return psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT | |
| 364 | if level.lower() == 'read_committed': | |
| 365 | return psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED | |
| 366 | if level.lower() == 'serializable': | |
| 367 | return psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE | |
| 368 |             raise PgManagerError('Unknown isolation level name: "%s"', level)
 | |
| 369 | return level | |
| 24 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 370 | |
| 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 371 | def _check_fork(self): | 
| 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 372 | '''Check if process was forked (PID has changed). | 
| 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 373 | |
| 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 374 | If it was, clean parent's connections. | 
| 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 375 | New connections are created for children. | 
| 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 376 | Known connection credentials are inherited, but not shared. | 
| 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 377 | |
| 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 378 | ''' | 
| 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 379 | if self.pid == multiprocessing.current_process().pid: | 
| 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 380 | # PID has not changed | 
| 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 381 | return | 
| 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 382 | |
| 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 383 | # update saved PID | 
| 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 384 | self.pid = multiprocessing.current_process().pid | 
| 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 385 | # reinitialize lock | 
| 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 386 | self.lock = threading.Lock() | 
| 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 387 | # clean parent's connections | 
| 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 388 | for name in self.conn_pool: | 
| 
5664afa530e5
PgManager: Add partial support for multiprocessing.
 Radek Brich <radek.brich@devl.cz> parents: 
23diff
changeset | 389 | self.conn_pool[name] = [] | 
| 0 | 390 | |
| 20 
73f0d53fef6b
PgManager: Do not add NullHandler to logger. Rewrite get_instance(). ToolBase: fix prepare_conns() method.
 Radek Brich <radek.brich@devl.cz> parents: 
19diff
changeset | 391 | @classmethod | 
| 
73f0d53fef6b
PgManager: Do not add NullHandler to logger. Rewrite get_instance(). ToolBase: fix prepare_conns() method.
 Radek Brich <radek.brich@devl.cz> parents: 
19diff
changeset | 392 | def get_instance(cls): | 
| 
73f0d53fef6b
PgManager: Do not add NullHandler to logger. Rewrite get_instance(). ToolBase: fix prepare_conns() method.
 Radek Brich <radek.brich@devl.cz> parents: 
19diff
changeset | 393 | if not hasattr(cls, '_instance'): | 
| 
73f0d53fef6b
PgManager: Do not add NullHandler to logger. Rewrite get_instance(). ToolBase: fix prepare_conns() method.
 Radek Brich <radek.brich@devl.cz> parents: 
19diff
changeset | 394 | cls._instance = cls() | 
| 
73f0d53fef6b
PgManager: Do not add NullHandler to logger. Rewrite get_instance(). ToolBase: fix prepare_conns() method.
 Radek Brich <radek.brich@devl.cz> parents: 
19diff
changeset | 395 | return cls._instance | 
| 0 | 396 | |
| 397 | ||
| 398 | def get_instance(): | |
| 20 
73f0d53fef6b
PgManager: Do not add NullHandler to logger. Rewrite get_instance(). ToolBase: fix prepare_conns() method.
 Radek Brich <radek.brich@devl.cz> parents: 
19diff
changeset | 399 | return PgManager.get_instance() | 
| 0 | 400 |