diskcache-community 5.8.1__tar.gz → 5.8.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {diskcache_community-5.8.1/diskcache_community.egg-info → diskcache_community-5.8.3}/PKG-INFO +4 -3
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/README.md +2 -1
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/diskcache_community/__init__.py +4 -2
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/diskcache_community/__init__.pyi +2 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/diskcache_community/_types.pyi +10 -2
- diskcache_community-5.8.3/diskcache_community/arrays.py +329 -0
- diskcache_community-5.8.3/diskcache_community/arrays.pyi +8 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/diskcache_community/core.py +460 -118
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/diskcache_community/core.pyi +11 -2
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/diskcache_community/recipes.py +1 -1
- {diskcache_community-5.8.1 → diskcache_community-5.8.3/diskcache_community.egg-info}/PKG-INFO +4 -3
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/diskcache_community.egg-info/SOURCES.txt +5 -1
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/pyproject.toml +1 -1
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/tests/test_core.py +160 -5
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/tests/test_default_disk.py +20 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/tests/test_entry_errors.py +2 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/tests/test_golden.py +7 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/tests/test_readonly.py +10 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/tests/test_recipes.py +20 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/tests/test_safedisk.py +106 -15
- diskcache_community-5.8.3/tests/test_safedisk_numpy.py +1438 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/tests/test_safedisk_types.py +74 -0
- diskcache_community-5.8.3/tests/test_safejsondisk.py +390 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/LICENSE +0 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/NOTICE +0 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/diskcache_community/cli.py +0 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/diskcache_community/djangocache.py +0 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/diskcache_community/djangocache.pyi +0 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/diskcache_community/fanout.py +0 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/diskcache_community/fanout.pyi +0 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/diskcache_community/persistent.py +0 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/diskcache_community/persistent.pyi +0 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/diskcache_community/py.typed +0 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/diskcache_community/recipes.pyi +0 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/diskcache_community.egg-info/dependency_links.txt +0 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/diskcache_community.egg-info/top_level.txt +0 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/setup.cfg +0 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/tests/test_deque.py +0 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/tests/test_djangocache.py +0 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/tests/test_doctest.py +0 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/tests/test_fanout.py +0 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/tests/test_index.py +0 -0
- {diskcache_community-5.8.1 → diskcache_community-5.8.3}/tests/test_init.py +0 -0
{diskcache_community-5.8.1/diskcache_community.egg-info → diskcache_community-5.8.3}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: diskcache-community
|
|
3
|
-
Version: 5.8.
|
|
3
|
+
Version: 5.8.3
|
|
4
4
|
Summary: Disk Cache -- Disk and file backed persistent cache.
|
|
5
5
|
Author: Grant Jenks
|
|
6
6
|
Maintainer: diskcache-community contributors
|
|
@@ -22,7 +22,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.13
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.14
|
|
24
24
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
25
|
-
Requires-Python: >=3.
|
|
25
|
+
Requires-Python: >=3.8
|
|
26
26
|
Description-Content-Type: text/markdown
|
|
27
27
|
License-File: LICENSE
|
|
28
28
|
License-File: NOTICE
|
|
@@ -142,7 +142,8 @@ cache.
|
|
|
142
142
|
semaphores and throttling
|
|
143
143
|
- Thread-safe and process-safe, with fork-safe connection handling
|
|
144
144
|
- Read-only mode, automatic when the directory is not writable
|
|
145
|
-
- Opt-in `SafeDisk` for caches whose directory other
|
|
145
|
+
- Opt-in `SafeDisk` and `SafeJSONDisk` for caches whose directory other
|
|
146
|
+
users can write to, with `SafeDisk.with_numpy()` for numpy arrays
|
|
146
147
|
- Pure Python on top of the standard library `sqlite3`; no server, no compiler
|
|
147
148
|
- Tested on CPython 3.10 to 3.14 on Linux, macOS and Windows
|
|
148
149
|
- Gets in about 2.6 µs and sets in about 19 µs on an Apple M5 Pro in the
|
|
@@ -112,7 +112,8 @@ cache.
|
|
|
112
112
|
semaphores and throttling
|
|
113
113
|
- Thread-safe and process-safe, with fork-safe connection handling
|
|
114
114
|
- Read-only mode, automatic when the directory is not writable
|
|
115
|
-
- Opt-in `SafeDisk` for caches whose directory other
|
|
115
|
+
- Opt-in `SafeDisk` and `SafeJSONDisk` for caches whose directory other
|
|
116
|
+
users can write to, with `SafeDisk.with_numpy()` for numpy arrays
|
|
116
117
|
- Pure Python on top of the standard library `sqlite3`; no server, no compiler
|
|
117
118
|
- Tested on CPython 3.10 to 3.14 on Linux, macOS and Windows
|
|
118
119
|
- Gets in about 2.6 µs and sets in about 19 µs on an Apple M5 Pro in the
|
|
@@ -16,6 +16,7 @@ from .core import (
|
|
|
16
16
|
JSONDisk,
|
|
17
17
|
ReadOnlyError,
|
|
18
18
|
SafeDisk,
|
|
19
|
+
SafeJSONDisk,
|
|
19
20
|
Timeout,
|
|
20
21
|
UnknownFileWarning,
|
|
21
22
|
configure,
|
|
@@ -50,6 +51,7 @@ __all__ = [
|
|
|
50
51
|
'RLock',
|
|
51
52
|
'ReadOnlyError',
|
|
52
53
|
'SafeDisk',
|
|
54
|
+
'SafeJSONDisk',
|
|
53
55
|
'Timeout',
|
|
54
56
|
'UNKNOWN',
|
|
55
57
|
'UnknownFileWarning',
|
|
@@ -70,8 +72,8 @@ except Exception: # pylint: disable=broad-except # pragma: no cover
|
|
|
70
72
|
|
|
71
73
|
|
|
72
74
|
__title__ = 'diskcache-community'
|
|
73
|
-
__version__ = '5.8.
|
|
74
|
-
__build__ =
|
|
75
|
+
__version__ = '5.8.3'
|
|
76
|
+
__build__ = 0x050803
|
|
75
77
|
__author__ = 'Grant Jenks'
|
|
76
78
|
__license__ = 'Apache 2.0'
|
|
77
79
|
__copyright__ = 'Copyright 2016-2023 Grant Jenks'
|
|
@@ -8,6 +8,7 @@ from diskcache_community.core import EmptyDirWarning as EmptyDirWarning
|
|
|
8
8
|
from diskcache_community.core import JSONDisk as JSONDisk
|
|
9
9
|
from diskcache_community.core import ReadOnlyError as ReadOnlyError
|
|
10
10
|
from diskcache_community.core import SafeDisk as SafeDisk
|
|
11
|
+
from diskcache_community.core import SafeJSONDisk as SafeJSONDisk
|
|
11
12
|
from diskcache_community.core import Timeout as Timeout
|
|
12
13
|
from diskcache_community.core import UnknownFileWarning as UnknownFileWarning
|
|
13
14
|
from diskcache_community.core import configure as configure
|
|
@@ -40,6 +41,7 @@ __all__ = [
|
|
|
40
41
|
'RLock',
|
|
41
42
|
'ReadOnlyError',
|
|
42
43
|
'SafeDisk',
|
|
44
|
+
'SafeJSONDisk',
|
|
43
45
|
'Timeout',
|
|
44
46
|
'UNKNOWN',
|
|
45
47
|
'UnknownFileWarning',
|
|
@@ -173,10 +173,18 @@ class BaseCache(Protocol):
|
|
|
173
173
|
retry: bool = ...,
|
|
174
174
|
) -> bool: ...
|
|
175
175
|
def incr(
|
|
176
|
-
self,
|
|
176
|
+
self,
|
|
177
|
+
key: Any,
|
|
178
|
+
delta: int = ...,
|
|
179
|
+
default: int | None = ...,
|
|
180
|
+
retry: bool = ...,
|
|
177
181
|
) -> int: ...
|
|
178
182
|
def decr(
|
|
179
|
-
self,
|
|
183
|
+
self,
|
|
184
|
+
key: Any,
|
|
185
|
+
delta: int = ...,
|
|
186
|
+
default: int | None = ...,
|
|
187
|
+
retry: bool = ...,
|
|
180
188
|
) -> int: ...
|
|
181
189
|
def get( # noqa: PLR0913
|
|
182
190
|
self,
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"""Disk that stores numpy arrays without pickling them."""
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
import math
|
|
5
|
+
import pickle
|
|
6
|
+
import warnings
|
|
7
|
+
|
|
8
|
+
from .core import SAFE_PICKLE_NAMES, Disk, SafeUnpickler, _ReaderMissing
|
|
9
|
+
|
|
10
|
+
# Pickle protocol 5 is the first that stores a buffer in place, so that
|
|
11
|
+
# writing an array copies it once, into the row, and reading a file-backed
|
|
12
|
+
# entry needs no second copy of it.
|
|
13
|
+
_MIN_PROTOCOL = 5
|
|
14
|
+
|
|
15
|
+
# The numpy scalar types that are stored, by the name written to the row, with
|
|
16
|
+
# the Python type that ``item()`` gives for each and the reader insists on. The
|
|
17
|
+
# names do not vary by platform or numpy version, unlike the types' own names.
|
|
18
|
+
_SCALARS = {
|
|
19
|
+
'bool': ('bool_', bool),
|
|
20
|
+
'int8': ('int8', int),
|
|
21
|
+
'int16': ('int16', int),
|
|
22
|
+
'int32': ('int32', int),
|
|
23
|
+
'int64': ('int64', int),
|
|
24
|
+
'uint8': ('uint8', int),
|
|
25
|
+
'uint16': ('uint16', int),
|
|
26
|
+
'uint32': ('uint32', int),
|
|
27
|
+
'uint64': ('uint64', int),
|
|
28
|
+
'float16': ('float16', float),
|
|
29
|
+
'float32': ('float32', float),
|
|
30
|
+
'float64': ('float64', float),
|
|
31
|
+
'complex64': ('complex64', complex),
|
|
32
|
+
'complex128': ('complex128', complex),
|
|
33
|
+
'str': ('str_', str),
|
|
34
|
+
'bytes': ('bytes_', bytes),
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _import_numpy():
|
|
39
|
+
# pylint: disable=import-error,import-outside-toplevel
|
|
40
|
+
try:
|
|
41
|
+
import numpy
|
|
42
|
+
except ImportError as error:
|
|
43
|
+
raise _ReaderMissing(
|
|
44
|
+
'SafeDisk needs numpy to read this entry'
|
|
45
|
+
) from error
|
|
46
|
+
return numpy
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _read_header(numpy, header, size):
|
|
50
|
+
"""Return shape, Fortran order and dtype from the ``.npy`` `header`.
|
|
51
|
+
|
|
52
|
+
Raise UnpicklingError unless the header is consistent with `size` bytes
|
|
53
|
+
of data. The sizes are checked because a forged shape must not decide
|
|
54
|
+
what gets read. Anything that goes wrong while parsing, including a
|
|
55
|
+
MemoryError from the Python parser on a crafted header, which
|
|
56
|
+
``on_error='miss'`` would not treat as an unreadable entry, is reported
|
|
57
|
+
as UnpicklingError.
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
# pylint: disable=broad-except,no-member
|
|
61
|
+
try:
|
|
62
|
+
reader = io.BytesIO(header)
|
|
63
|
+
version = numpy.lib.format.read_magic(reader)
|
|
64
|
+
if version == (1, 0):
|
|
65
|
+
fields = numpy.lib.format.read_array_header_1_0(reader)
|
|
66
|
+
elif version == (2, 0):
|
|
67
|
+
fields = numpy.lib.format.read_array_header_2_0(reader)
|
|
68
|
+
else:
|
|
69
|
+
raise ValueError('unsupported .npy version %r' % (version,))
|
|
70
|
+
except Exception as error:
|
|
71
|
+
raise pickle.UnpicklingError(
|
|
72
|
+
'SafeDisk refuses the array header'
|
|
73
|
+
) from error
|
|
74
|
+
|
|
75
|
+
shape, fortran, dtype = fields
|
|
76
|
+
count = math.prod(shape)
|
|
77
|
+
|
|
78
|
+
# A zero-width dtype would let any shape match any amount of data.
|
|
79
|
+
if (
|
|
80
|
+
dtype.hasobject
|
|
81
|
+
or reader.tell() != len(header)
|
|
82
|
+
or dtype.itemsize * count != size
|
|
83
|
+
or (count and not dtype.itemsize)
|
|
84
|
+
):
|
|
85
|
+
raise pickle.UnpicklingError('SafeDisk refuses the array header')
|
|
86
|
+
|
|
87
|
+
return shape, fortran, dtype
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _pack_array(numpy, array):
|
|
91
|
+
"""Return the ``.npy`` header of `array` and a buffer of its data.
|
|
92
|
+
|
|
93
|
+
The buffer is the array's own memory, so pickle copies it once, into
|
|
94
|
+
the row, as a bytearray. An array that is not contiguous or not
|
|
95
|
+
writable is copied first, since pickle stores a read-only buffer as
|
|
96
|
+
bytes, which the reader refuses. The buffer is a flat view of bytes,
|
|
97
|
+
since numpy exports no buffer for ``datetime64`` and ``timedelta64``
|
|
98
|
+
data; the dtype is in the header.
|
|
99
|
+
|
|
100
|
+
"""
|
|
101
|
+
# pylint: disable=no-member
|
|
102
|
+
flags = array.flags
|
|
103
|
+
if not (flags.writeable and (flags.c_contiguous or flags.f_contiguous)):
|
|
104
|
+
array = array.copy(order='A')
|
|
105
|
+
|
|
106
|
+
buffer = io.BytesIO()
|
|
107
|
+
try:
|
|
108
|
+
with warnings.catch_warnings():
|
|
109
|
+
# numpy warns about dtypes it would save as pickles or in a
|
|
110
|
+
# format the reader refuses; both are refused below.
|
|
111
|
+
warnings.simplefilter('ignore')
|
|
112
|
+
# Version 1 holds every header the reader accepts: numpy refuses
|
|
113
|
+
# a header over 10000 bytes, and version 1 holds 65535.
|
|
114
|
+
fields = numpy.lib.format.header_data_from_array_1_0(array)
|
|
115
|
+
numpy.lib.format.write_array_header_1_0(buffer, fields)
|
|
116
|
+
header = buffer.getvalue()
|
|
117
|
+
_read_header(numpy, header, array.nbytes)
|
|
118
|
+
except (ValueError, pickle.UnpicklingError) as error:
|
|
119
|
+
message = 'SafeDisk cannot store an array of dtype %s' % (array.dtype,)
|
|
120
|
+
if array.dtype.hasobject:
|
|
121
|
+
message += (
|
|
122
|
+
'; a pandas DataFrame holds its labels and text columns in'
|
|
123
|
+
' such arrays, so store its records or columns instead'
|
|
124
|
+
)
|
|
125
|
+
raise TypeError(message) from error
|
|
126
|
+
|
|
127
|
+
data = array.reshape(-1, order='A').view(numpy.uint8)
|
|
128
|
+
return header, pickle.PickleBuffer(data)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _unpack_array(header, data):
|
|
132
|
+
"""Return the array in the bytearray `data`, which it keeps as its data.
|
|
133
|
+
|
|
134
|
+
A view takes no second copy of the array, and stays writable because
|
|
135
|
+
`data` is. `header` and `data` may have been tampered with.
|
|
136
|
+
|
|
137
|
+
"""
|
|
138
|
+
# pylint: disable=broad-except
|
|
139
|
+
numpy = _import_numpy()
|
|
140
|
+
shape, fortran, dtype = _read_header(numpy, header, len(data))
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
if not math.prod(shape):
|
|
144
|
+
return numpy.empty(shape, dtype)
|
|
145
|
+
array = numpy.frombuffer(data, dtype)
|
|
146
|
+
return array.reshape(shape, order='F' if fortran else 'C')
|
|
147
|
+
except Exception as error:
|
|
148
|
+
raise pickle.UnpicklingError(
|
|
149
|
+
'SafeDisk refuses the array data'
|
|
150
|
+
) from error
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _scalar_name(scalar):
|
|
154
|
+
"""Return the name to store numpy `scalar` under, or raise TypeError."""
|
|
155
|
+
dtype = scalar.dtype
|
|
156
|
+
name = {'U': 'str', 'S': 'bytes'}.get(dtype.kind, dtype.name)
|
|
157
|
+
if name not in _SCALARS:
|
|
158
|
+
raise TypeError(
|
|
159
|
+
'SafeDisk cannot store a scalar of type numpy.%s'
|
|
160
|
+
% (type(scalar).__name__,)
|
|
161
|
+
)
|
|
162
|
+
return name
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _unpack_scalar(name, value):
|
|
166
|
+
"""Return the numpy scalar `value` of type `name`, which may be forged.
|
|
167
|
+
|
|
168
|
+
The value must be of the Python type the name is built from: numpy
|
|
169
|
+
would otherwise build something else, such as ``bytes_(n)``, which is
|
|
170
|
+
`n` zero bytes.
|
|
171
|
+
|
|
172
|
+
"""
|
|
173
|
+
# pylint: disable=broad-except,unidiomatic-typecheck
|
|
174
|
+
entry = _SCALARS.get(name) if type(name) is str else None
|
|
175
|
+
if entry is None or type(value) is not entry[1]:
|
|
176
|
+
raise pickle.UnpicklingError('SafeDisk refuses a scalar')
|
|
177
|
+
|
|
178
|
+
numpy = _import_numpy()
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
return getattr(numpy, entry[0])(value)
|
|
182
|
+
except Exception as error:
|
|
183
|
+
raise pickle.UnpicklingError('SafeDisk refuses a scalar') from error
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class _ArrayPickler(pickle.Pickler):
|
|
187
|
+
"""Pickler that writes arrays and numpy scalars as persistent IDs."""
|
|
188
|
+
|
|
189
|
+
def __init__(self, file, protocol, numpy):
|
|
190
|
+
super().__init__(file, protocol=protocol)
|
|
191
|
+
self._numpy = numpy
|
|
192
|
+
self._arrays = {}
|
|
193
|
+
self._scalar_names = {}
|
|
194
|
+
self.reduced = False
|
|
195
|
+
|
|
196
|
+
def reducer_override(self, obj):
|
|
197
|
+
"""Note that an object was reduced, which can look up a name.
|
|
198
|
+
|
|
199
|
+
CPython skips this hook only for None, booleans and exact ints,
|
|
200
|
+
floats, bytes, strings, dicts, sets, frozensets, lists and tuples,
|
|
201
|
+
none of which pickle a name, so every pickle that looks one up is
|
|
202
|
+
noted here and scanned.
|
|
203
|
+
|
|
204
|
+
"""
|
|
205
|
+
# pylint: disable=unused-argument
|
|
206
|
+
self.reduced = True
|
|
207
|
+
return NotImplemented
|
|
208
|
+
|
|
209
|
+
def persistent_id(self, obj):
|
|
210
|
+
# pylint: disable=unidiomatic-typecheck
|
|
211
|
+
numpy = self._numpy
|
|
212
|
+
if isinstance(obj, numpy.ndarray):
|
|
213
|
+
if type(obj) is not numpy.ndarray:
|
|
214
|
+
raise TypeError(
|
|
215
|
+
'SafeDisk cannot store a subclass of ndarray: %s.%s'
|
|
216
|
+
% (type(obj).__module__, type(obj).__qualname__)
|
|
217
|
+
)
|
|
218
|
+
# pickle's own memo skips persistent IDs, and a value may hand it
|
|
219
|
+
# temporary arrays. The buffer in the ID keeps `obj` alive, so
|
|
220
|
+
# its id is not reused while this memo holds the ID.
|
|
221
|
+
pid = self._arrays.get(id(obj))
|
|
222
|
+
if pid is None:
|
|
223
|
+
pid = self._arrays[id(obj)] = ('npy',) + _pack_array(
|
|
224
|
+
numpy, obj
|
|
225
|
+
)
|
|
226
|
+
return pid
|
|
227
|
+
if isinstance(obj, numpy.generic):
|
|
228
|
+
kind = type(obj)
|
|
229
|
+
name = self._scalar_names.get(kind)
|
|
230
|
+
if name is None:
|
|
231
|
+
name = self._scalar_names[kind] = _scalar_name(obj)
|
|
232
|
+
value = obj.item()
|
|
233
|
+
if isinstance(value, numpy.generic):
|
|
234
|
+
raise TypeError(
|
|
235
|
+
'SafeDisk cannot store a scalar of type numpy.%s'
|
|
236
|
+
% (kind.__name__,)
|
|
237
|
+
)
|
|
238
|
+
return ('scalar', name, value)
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class _ArrayUnpickler(SafeUnpickler):
|
|
243
|
+
"""Unpickler that reads back what _ArrayPickler wrote.
|
|
244
|
+
|
|
245
|
+
Array state is rebuilt from the checked ``.npy`` header alone: the dtype
|
|
246
|
+
slot of a trailing ``BUILD`` accepts only a ``numpy.dtype``, which
|
|
247
|
+
neither the allowlist nor a persistent ID can produce.
|
|
248
|
+
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
def __init__(self, *args, **kwargs):
|
|
252
|
+
super().__init__(*args, **kwargs)
|
|
253
|
+
self._arrays = {}
|
|
254
|
+
self._strings = {}
|
|
255
|
+
|
|
256
|
+
def persistent_load(self, pid):
|
|
257
|
+
# pylint: disable=unidiomatic-typecheck
|
|
258
|
+
if type(pid) is tuple and len(pid) == 3:
|
|
259
|
+
if pid[0] == 'scalar':
|
|
260
|
+
value = pid[2]
|
|
261
|
+
if type(value) not in (str, bytes):
|
|
262
|
+
return _unpack_scalar(pid[1], value)
|
|
263
|
+
# A string the pickle memoized is copied into a numpy scalar
|
|
264
|
+
# once, however many IDs name it, as an array buffer is.
|
|
265
|
+
entry = self._strings.get(id(value))
|
|
266
|
+
if entry is None:
|
|
267
|
+
entry = (value, _unpack_scalar(pid[1], value))
|
|
268
|
+
self._strings[id(value)] = entry
|
|
269
|
+
return entry[1]
|
|
270
|
+
if pid[0] == 'npy':
|
|
271
|
+
header, payload = pid[1], pid[2]
|
|
272
|
+
if type(header) is bytes and type(payload) is bytearray:
|
|
273
|
+
# Decoding each buffer once keeps what a row can decode to
|
|
274
|
+
# within its own size, and shared arrays shared. Holding
|
|
275
|
+
# the pair keeps their ids from being reused.
|
|
276
|
+
key = (id(header), id(payload))
|
|
277
|
+
entry = self._arrays.get(key)
|
|
278
|
+
if entry is None:
|
|
279
|
+
array = _unpack_array(header, payload)
|
|
280
|
+
entry = (header, payload, array)
|
|
281
|
+
self._arrays[key] = entry
|
|
282
|
+
return entry[2]
|
|
283
|
+
raise pickle.UnpicklingError('SafeDisk refuses a persistent ID')
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def unpickler(disk, file):
|
|
287
|
+
"""Return the unpickler of `disk`, a SafeDisk with ``safe_numpy`` set."""
|
|
288
|
+
return _ArrayUnpickler(
|
|
289
|
+
file,
|
|
290
|
+
SAFE_PICKLE_NAMES,
|
|
291
|
+
disk.safe_types,
|
|
292
|
+
disk.safe_modules,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def store(disk, numpy, value, key):
|
|
297
|
+
"""Return the Cache table fields of `value`, which may hold numpy data.
|
|
298
|
+
|
|
299
|
+
`disk` is a SafeDisk with ``safe_numpy`` set and `numpy` the imported
|
|
300
|
+
module. A value with no numpy in it is stored as an ordinary pickle.
|
|
301
|
+
|
|
302
|
+
"""
|
|
303
|
+
# pylint: disable=protected-access
|
|
304
|
+
try:
|
|
305
|
+
disk._check(value, 'value')
|
|
306
|
+
except TypeError:
|
|
307
|
+
pass
|
|
308
|
+
else:
|
|
309
|
+
return Disk.store(disk, value, False, key=key)
|
|
310
|
+
|
|
311
|
+
buffer = io.BytesIO()
|
|
312
|
+
protocol = max(disk.pickle_protocol, _MIN_PROTOCOL)
|
|
313
|
+
pickler = _ArrayPickler(buffer, protocol, numpy)
|
|
314
|
+
|
|
315
|
+
try:
|
|
316
|
+
pickler.dump(value)
|
|
317
|
+
except (pickle.PicklingError, AttributeError) as error:
|
|
318
|
+
raise TypeError(
|
|
319
|
+
'SafeDisk cannot store value: %s' % (error,)
|
|
320
|
+
) from error
|
|
321
|
+
|
|
322
|
+
data = buffer.getvalue()
|
|
323
|
+
|
|
324
|
+
# Only a reduced object can look up a name, and scanning the pickle
|
|
325
|
+
# would copy every array in it.
|
|
326
|
+
if pickler.reduced:
|
|
327
|
+
disk._check_pickle(data, value, 'value')
|
|
328
|
+
|
|
329
|
+
return disk._store_pickle(data, value, key)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from typing import IO, Any
|
|
2
|
+
|
|
3
|
+
from diskcache_community.core import SafeDisk, SafeUnpickler
|
|
4
|
+
|
|
5
|
+
def unpickler(disk: SafeDisk, file: IO[bytes]) -> SafeUnpickler: ...
|
|
6
|
+
def store(
|
|
7
|
+
disk: SafeDisk, numpy: Any, value: Any, key: Any
|
|
8
|
+
) -> tuple[int, int, str | None, Any]: ...
|