Skip to content
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion Doc/library/shelve.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ This includes most class instances, recursive data types, and objects containing
lots of shared sub-objects. The keys are ordinary strings.


.. function:: open(filename, flag='c', protocol=None, writeback=False)
.. function:: open(filename, flag='c', protocol=None, writeback=False, custom_dumps=dumps, custom_loads=loads)

Open a persistent dictionary. The filename specified is the base filename for
the underlying database. As a side-effect, an extension may be added to the
Expand All @@ -41,13 +41,24 @@ lots of shared sub-objects. The keys are ordinary strings.
determine which accessed entries are mutable, nor which ones were actually
mutated).

By default, :mod:`shelve` uses :func:`pickle.dumps` and :func:`pickle.loads`
for pickling and unpickling. However *custom_loads* can be the function
that takes the :term:`bytes-like object` and returns the object. *custom_dumps*
can be the function that takes the object and returns :class:`bytes`. For example,
:keyword:`lambda`, which the :mod:`pickle` does not support, can be used in
:mod:`shelve` using the custom loads and custom dumps functions, which do support
the :keyword:`lambda`.

.. versionchanged:: 3.10
:data:`pickle.DEFAULT_PROTOCOL` is now used as the default pickle
protocol.

.. versionchanged:: 3.11
Accepts :term:`path-like object` for filename.

.. versionchanged:: 3.12
Accepts *custom_dumps* and *custom_loads*.

.. note::

Do not rely on the shelf being closed automatically; always call
Expand Down
24 changes: 12 additions & 12 deletions Lib/shelve.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
the persistent dictionary on disk, if feasible).
"""

from pickle import DEFAULT_PROTOCOL, Pickler, Unpickler
from pickle import DEFAULT_PROTOCOL, Unpickler, dumps, loads
from io import BytesIO

import collections.abc
Expand All @@ -82,14 +82,16 @@ class Shelf(collections.abc.MutableMapping):
"""

def __init__(self, dict, protocol=None, writeback=False,
keyencoding="utf-8"):
keyencoding="utf-8", custom_dumps=dumps, custom_loads=loads):
self.dict = dict
if protocol is None:
protocol = DEFAULT_PROTOCOL
self._protocol = protocol
self.writeback = writeback
self.cache = {}
self.keyencoding = keyencoding
self.custom_dumps = custom_dumps
self.custom_loads = custom_loads

def __iter__(self):
for k in self.dict.keys():
Expand All @@ -110,19 +112,16 @@ def __getitem__(self, key):
try:
value = self.cache[key]
except KeyError:
f = BytesIO(self.dict[key.encode(self.keyencoding)])
value = Unpickler(f).load()
f = self.dict[key.encode(self.keyencoding)]
value = self.custom_loads(f)
if self.writeback:
self.cache[key] = value
return value

def __setitem__(self, key, value):
if self.writeback:
self.cache[key] = value
f = BytesIO()
p = Pickler(f, self._protocol)
p.dump(value)
self.dict[key.encode(self.keyencoding)] = f.getvalue()
self.dict[key.encode(self.keyencoding)] = self.custom_dumps(value, self._protocol)

def __delitem__(self, key):
del self.dict[key.encode(self.keyencoding)]
Expand Down Expand Up @@ -172,6 +171,7 @@ def sync(self):
self.dict.sync()



class BsdDbShelf(Shelf):
"""Shelf implementation using the "BSD" db interface.

Expand Down Expand Up @@ -222,12 +222,12 @@ class DbfilenameShelf(Shelf):
See the module's __doc__ string for an overview of the interface.
"""

def __init__(self, filename, flag='c', protocol=None, writeback=False):
def __init__(self, filename, flag='c', protocol=None, writeback=False, custom_dumps=dumps, custom_loads=loads):
import dbm
Shelf.__init__(self, dbm.open(filename, flag), protocol, writeback)
Shelf.__init__(self, dbm.open(filename, flag), protocol, writeback, custom_dumps=custom_dumps, custom_loads=custom_loads)


def open(filename, flag='c', protocol=None, writeback=False):
def open(filename, flag='c', protocol=None, writeback=False, custom_dumps=dumps, custom_loads=loads):
"""Open a persistent dictionary for reading and writing.

The filename parameter is the base filename for the underlying
Expand All @@ -240,4 +240,4 @@ def open(filename, flag='c', protocol=None, writeback=False):
See the module's __doc__ string for an overview of the interface.
"""

return DbfilenameShelf(filename, flag, protocol, writeback)
return DbfilenameShelf(filename, flag, protocol, writeback, custom_dumps, custom_loads)
18 changes: 18 additions & 0 deletions Lib/test/test_shelve.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import shelve
import pickle
import os
from io import BytesIO
from pydoc import locate

from test.support import os_helper
from collections.abc import MutableMapping
Expand Down Expand Up @@ -165,6 +167,22 @@ def test_default_protocol(self):
with shelve.Shelf({}) as s:
self.assertEqual(s._protocol, pickle.DEFAULT_PROTOCOL)

def test_custom_loads_and_dumps(self):
def custom_dumps(obj, protocol=None):
return bytes(f"{type(obj).__name__}", 'utf-8')

def custom_loads(data):
value = BytesIO(data).read()
return locate(value.decode("utf-8"))

os.mkdir(self.dirname)
self.addCleanup(os_helper.rmtree, self.dirname)

with shelve.open(self.fn, custom_dumps=custom_dumps, custom_loads=custom_loads) as s:
num = 1
s['number'] = num
self.assertEqual(s['number'], type(num))


class TestShelveBase:
type2test = shelve.Shelf
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Shelve module accepts custom_dumps and custom_loads functions.