mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-25 10:23:43 +03:00
RealDictRow inherits from OrderedDict
Now its state is unmodified, so apart from special-casing creation and initial population can work unmodified, and all the desired properties just work (modifiability, picklability...) Close #886.
This commit is contained in:
parent
21d16b6f67
commit
cc815e8e8d
2
NEWS
2
NEWS
|
@ -4,7 +4,7 @@ Current release
|
||||||
What's new in psycopg 2.8.1
|
What's new in psycopg 2.8.1
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
- Fixed `RealDictRow.pop()` (:ticket:`#886`).
|
- Fixed `RealDictRow` modifiability (:ticket:`#886`).
|
||||||
- Fixed "there's no async cursor" error polling a connection with no cursor
|
- Fixed "there's no async cursor" error polling a connection with no cursor
|
||||||
(:ticket:`#887`).
|
(:ticket:`#887`).
|
||||||
|
|
||||||
|
|
|
@ -253,63 +253,39 @@ class RealDictCursor(DictCursorBase):
|
||||||
self._query_executed = False
|
self._query_executed = False
|
||||||
|
|
||||||
|
|
||||||
class RealDictRow(dict):
|
class RealDictRow(OrderedDict):
|
||||||
"""A `!dict` subclass representing a data record."""
|
"""A `!dict` subclass representing a data record."""
|
||||||
|
|
||||||
__slots__ = ('_column_mapping',)
|
def __init__(self, *args, **kwargs):
|
||||||
|
if args and isinstance(args[0], _cursor):
|
||||||
|
cursor = args[0]
|
||||||
|
args = args[1:]
|
||||||
|
else:
|
||||||
|
cursor = None
|
||||||
|
|
||||||
def __init__(self, cursor):
|
super(RealDictRow, self).__init__(*args, **kwargs)
|
||||||
super(RealDictRow, self).__init__()
|
|
||||||
|
if cursor is not None:
|
||||||
# Required for named cursors
|
# Required for named cursors
|
||||||
if cursor.description and not cursor.column_mapping:
|
if cursor.description and not cursor.column_mapping:
|
||||||
cursor._build_index()
|
cursor._build_index()
|
||||||
|
|
||||||
self._column_mapping = cursor.column_mapping
|
# Store the cols mapping in the dict itself until the row is fully
|
||||||
|
# populated, so we don't need to add attributes to the class
|
||||||
|
# (hence keeping its maintenance, special pickle support, etc.)
|
||||||
|
self[RealDictRow] = cursor.column_mapping
|
||||||
|
|
||||||
def __setitem__(self, name, value):
|
def __setitem__(self, key, value):
|
||||||
if type(name) == int:
|
if RealDictRow in self:
|
||||||
name = self._column_mapping[name]
|
# We are in the row building phase
|
||||||
super(RealDictRow, self).__setitem__(name, value)
|
mapping = self[RealDictRow]
|
||||||
|
super(RealDictRow, self).__setitem__(mapping[key], value)
|
||||||
|
if len(self) == len(mapping) + 1:
|
||||||
|
# Row building finished
|
||||||
|
del self[RealDictRow]
|
||||||
|
return
|
||||||
|
|
||||||
def __getstate__(self):
|
super(RealDictRow, self).__setitem__(key, value)
|
||||||
return self.copy(), self._column_mapping[:]
|
|
||||||
|
|
||||||
def __setstate__(self, data):
|
|
||||||
self.update(data[0])
|
|
||||||
self._column_mapping = data[1]
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return iter(self._column_mapping)
|
|
||||||
|
|
||||||
def keys(self):
|
|
||||||
return iter(self._column_mapping)
|
|
||||||
|
|
||||||
def values(self):
|
|
||||||
return (self[k] for k in self._column_mapping)
|
|
||||||
|
|
||||||
def items(self):
|
|
||||||
return ((k, self[k]) for k in self._column_mapping)
|
|
||||||
|
|
||||||
def pop(self, key, *args):
|
|
||||||
found = key in self
|
|
||||||
rv = super(RealDictRow, self).pop(key, *args)
|
|
||||||
if found:
|
|
||||||
self._column_mapping.remove(key)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
if PY2:
|
|
||||||
iterkeys = keys
|
|
||||||
itervalues = values
|
|
||||||
iteritems = items
|
|
||||||
|
|
||||||
def keys(self):
|
|
||||||
return list(self.iterkeys())
|
|
||||||
|
|
||||||
def values(self):
|
|
||||||
return list(self.itervalues())
|
|
||||||
|
|
||||||
def items(self):
|
|
||||||
return list(self.iteritems())
|
|
||||||
|
|
||||||
|
|
||||||
class NamedTupleConnection(_connection):
|
class NamedTupleConnection(_connection):
|
||||||
|
|
|
@ -265,7 +265,6 @@ class ExtrasDictCursorRealTests(_DictCursorBase):
|
||||||
self.assertEqual(r, r1)
|
self.assertEqual(r, r1)
|
||||||
self.assertEqual(r['a'], r1['a'])
|
self.assertEqual(r['a'], r1['a'])
|
||||||
self.assertEqual(r['b'], r1['b'])
|
self.assertEqual(r['b'], r1['b'])
|
||||||
self.assertEqual(r._column_mapping, r1._column_mapping)
|
|
||||||
|
|
||||||
def testDictCursorRealWithNamedCursorFetchOne(self):
|
def testDictCursorRealWithNamedCursorFetchOne(self):
|
||||||
self._testWithNamedCursorReal(lambda curs: curs.fetchone())
|
self._testWithNamedCursorReal(lambda curs: curs.fetchone())
|
||||||
|
@ -377,6 +376,22 @@ class ExtrasDictCursorRealTests(_DictCursorBase):
|
||||||
self.assertEqual(r.pop('b', None), None)
|
self.assertEqual(r.pop('b', None), None)
|
||||||
self.assertRaises(KeyError, r.pop, 'b')
|
self.assertRaises(KeyError, r.pop, 'b')
|
||||||
|
|
||||||
|
def test_mod(self):
|
||||||
|
curs = self.conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||||||
|
curs.execute("select 1 as a, 2 as b, 3 as c")
|
||||||
|
r = curs.fetchone()
|
||||||
|
r['d'] = 4
|
||||||
|
self.assertEqual(list(r), ['a', 'b', 'c', 'd'])
|
||||||
|
self.assertEqual(list(r.keys()), ['a', 'b', 'c', 'd'])
|
||||||
|
self.assertEqual(list(r.values()), [1, 2, 3, 4])
|
||||||
|
self.assertEqual(list(
|
||||||
|
r.items()), [('a', 1), ('b', 2), ('c', 3), ('d', 4)])
|
||||||
|
|
||||||
|
assert r['a'] == 1
|
||||||
|
assert r['b'] == 2
|
||||||
|
assert r['c'] == 3
|
||||||
|
assert r['d'] == 4
|
||||||
|
|
||||||
|
|
||||||
class NamedTupleCursorTest(ConnectingTestCase):
|
class NamedTupleCursorTest(ConnectingTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user