Merge branch 'fix-namedtuple-cache'

This commit is contained in:
Daniele Varrazzo 2019-06-07 18:19:47 +01:00
commit 2635f43788
3 changed files with 23 additions and 6 deletions

1
NEWS
View File

@ -8,6 +8,7 @@ What's new in psycopg 2.8.3
`~psycopg2.extras.ReplicationCursor.start_replication()` method and other `~psycopg2.extras.ReplicationCursor.start_replication()` method and other
facilities to send automatic replication keepalives at periodic intervals facilities to send automatic replication keepalives at periodic intervals
(:ticket:`#913`). (:ticket:`#913`).
- Fixed namedtuples caching introduced in 2.8 (:ticket:`#928`).
What's new in psycopg 2.8.2 What's new in psycopg 2.8.2

View File

@ -372,10 +372,11 @@ class NamedTupleCursor(_cursor):
key = tuple(d[0] for d in self.description) if self.description else () key = tuple(d[0] for d in self.description) if self.description else ()
return self._cached_make_nt(key) return self._cached_make_nt(key)
def _do_make_nt(self, key): @classmethod
def _do_make_nt(cls, key):
fields = [] fields = []
for s in key: for s in key:
s = self._re_clean.sub('_', s) s = cls._re_clean.sub('_', s)
# Python identifier cannot start with numbers, namedtuple fields # Python identifier cannot start with numbers, namedtuple fields
# cannot start with underscore. So... # cannot start with underscore. So...
if s[0] == '_' or '0' <= s[0] <= '9': if s[0] == '_' or '0' <= s[0] <= '9':
@ -385,9 +386,14 @@ class NamedTupleCursor(_cursor):
nt = namedtuple("Record", fields) nt = namedtuple("Record", fields)
return nt return nt
# Exposed for testability, and if someone wants to monkeypatch to tweak
# the cache size. @lru_cache(512)
_cached_make_nt = lru_cache(512)(_do_make_nt) def _cached_make_nt(cls, key):
return cls._do_make_nt(key)
# Exposed for testability, and if someone wants to monkeypatch to tweak
# the cache size.
NamedTupleCursor._cached_make_nt = classmethod(_cached_make_nt)
class LoggingConnection(_connection): class LoggingConnection(_connection):

View File

@ -615,21 +615,31 @@ class NamedTupleCursorTest(ConnectingTestCase):
self.assertEqual(i + 1, curs.rownumber) self.assertEqual(i + 1, curs.rownumber)
def test_cache(self): def test_cache(self):
NamedTupleCursor._cached_make_nt.cache_clear()
curs = self.conn.cursor() curs = self.conn.cursor()
curs.execute("select 10 as a, 20 as b") curs.execute("select 10 as a, 20 as b")
r1 = curs.fetchone() r1 = curs.fetchone()
curs.execute("select 10 as a, 20 as c") curs.execute("select 10 as a, 20 as c")
r2 = curs.fetchone() r2 = curs.fetchone()
# Get a new cursor to check that the cache works across multiple ones
curs = self.conn.cursor()
curs.execute("select 10 as a, 30 as b") curs.execute("select 10 as a, 30 as b")
r3 = curs.fetchone() r3 = curs.fetchone()
self.assert_(type(r1) is type(r3)) self.assert_(type(r1) is type(r3))
self.assert_(type(r1) is not type(r2)) self.assert_(type(r1) is not type(r2))
cache_info = NamedTupleCursor._cached_make_nt.cache_info()
self.assertEqual(cache_info.hits, 1)
self.assertEqual(cache_info.misses, 2)
self.assertEqual(cache_info.currsize, 2)
def test_max_cache(self): def test_max_cache(self):
old_func = NamedTupleCursor._cached_make_nt old_func = NamedTupleCursor._cached_make_nt
NamedTupleCursor._cached_make_nt = \ NamedTupleCursor._cached_make_nt = \
lru_cache(8)(NamedTupleCursor._do_make_nt) lru_cache(8)(NamedTupleCursor._cached_make_nt.__wrapped__)
try: try:
recs = [] recs = []
curs = self.conn.cursor() curs = self.conn.cursor()