zmc
2023-08-08 e792e9a60d958b93aef96050644f369feb25d61b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
"""SimplePooledPg - a very simple classic PyGreSQL connection pool.
 
Implements a pool of threadsafe cached connections
to a PostgreSQL database which are transparently reused,
using the classic (not DB-API 2 compliant) PyGreSQL pg API.
 
This should result in a speedup for persistent applications
such as the "Webware for Python" AppServer.
 
For more information on PostgreSQL, see:
    https://www.postgresql.org/
For more information on PyGreSQL, see:
    http://www.pygresql.org
For more information on Webware for Python, see:
    https://webwareforpython.github.io/w4py/
 
Measures are taken to make the pool of connections threadsafe
regardless of the fact that the PyGreSQL pg module itself is
not threadsafe at the connection level.  Connections will never be
shared between threads, so you can safely use transactions.
 
Usage:
 
The idea behind SimplePooledPg is that it's completely transparent.
After you have established your connection pool, stating the
number of connections to be cached in the pool and the
connection parameters, e.g.
 
    from dbutils.simple_pooled_pg import PooledPg
    dbpool = PooledPg(5, host=..., database=..., user=..., ...)
 
you can demand database connections from that pool,
 
    db = dbpool.connection()
 
and use them just as if they were ordinary PyGreSQL pg API
connections.  It's really just a proxy class.
 
db.close() will return the connection to the pool, it will not
actually close it.  This is so your existing code works nicely.
 
Ideas for improvement:
 
* Do not create the maximum number of connections on startup
already, but only a certain number and the rest on demand.
* Detect and transparently reset "bad" connections.  The PyGreSQL
pg API provides a status attribute and a reset() method for that.
* Connections should have some sort of "maximum usage limit"
after which they should be automatically closed and reopened.
* Prefer or enforce thread affinity for the connections.
 
Please note that these and other ideas have been already
implemented in in PooledPg, a more sophisticated version
of SimplePooledPg.  You might also consider using PersistentPg
instead for thread-affine persistent PyGreSQL connections.
SimplePooledPg may still serve as a very simple reference
and example implementation for developers.
 
 
Copyright, credits and license:
 
* Contributed as supplement for Webware for Python and PyGreSQL
  by Christoph Zwerschke in September 2005
* Based on the code of DBPool, contributed to Webware for Python
  by Dan Green in December 2000
 
Licensed under the MIT license.
"""
 
from pg import DB as PgConnection
 
from . import __version__
 
 
class PooledPgConnection:
    """A proxy class for pooled PostgreSQL connections.
 
    You don't normally deal with this class directly,
    but use PooledPg to get new connections.
    """
 
    def __init__(self, pool, con):
        self._con = con
        self._pool = pool
 
    def close(self):
        """Close the pooled connection."""
        # Instead of actually closing the connection,
        # return it to the pool so it can be reused.
        if self._con is not None:
            self._pool.cache(self._con)
            self._con = None
 
    def __getattr__(self, name):
        # All other members are the same.
        return getattr(self._con, name)
 
    def __del__(self):
        self.close()
 
 
class PooledPg:
    """A very simple PostgreSQL connection pool.
 
    After you have created the connection pool,
    you can get connections using getConnection().
    """
 
    version = __version__
 
    def __init__(self, maxconnections, *args, **kwargs):
        """Set up the PostgreSQL connection pool.
 
        maxconnections: the number of connections cached in the pool
        args, kwargs: the parameters that shall be used to establish
            the PostgreSQL connections using pg.connect()
        """
        # Since there is no connection level safety, we
        # build the pool using the synchronized queue class
        # that implements all the required locking semantics.
        try:
            from Queue import Queue
        except ImportError:  # Python 3
            from queue import Queue
        self._queue = Queue(maxconnections)
        # Establish all database connections (it would be better to
        # only establish a part of them now, and the rest on demand).
        for i in range(maxconnections):
            self.cache(PgConnection(*args, **kwargs))
 
    def cache(self, con):
        """Add or return a connection to the pool."""
        self._queue.put(con)
 
    def connection(self):
        """Get a connection from the pool."""
        return PooledPgConnection(self, self._queue.get())