Skip to content

Commit 0c09dca

Browse files
committed
Changes:
- persistent class can be passed as class or object to List/Deque. This change allows to pass custom modes to persistent class - add test cases from README.rst - update README.rst examples - add safer writing when serializer is in byte mode or not Signed-off-by: Andrzej <6695650+thegrymek@users.noreply.github.com>
1 parent 46f04b7 commit 0c09dca

7 files changed

Lines changed: 196 additions & 53 deletions

File tree

README.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,11 @@ There are available more ways to serialize items.
6969
JsonZLibSerializer # convert to json + compress items
7070
)
7171
>>> from functools import partial
72-
>>> JsonFileList = partial(List, serializer_class=JsonHandler)
72+
>>> JsonFileList = partial(FileList, serializer_class=JsonSerializer)
7373
>>> flist = JsonFileList()
7474
>>> flist.append({'a': 1, 'b': 2, 'c': 3})
7575
>>> flist[0]
76-
{u'a': 1, u'b': 2, u'c': 3}
76+
{'a': 1, 'b': 2, 'c': 3}
7777
7878
7979
Installation
@@ -112,7 +112,7 @@ Exactly this object `{'a': 1, 'b': 2, 'c': 3}` will serialized and compressed an
112112
.. code-block:: python
113113
114114
>>> flist[0]
115-
{u'a': 1, u'b': 2, u'c': 3}
115+
{'a': 1, 'b': 2, 'c': 3}
116116
117117
Getting an item will read a file and because `JsonZLibSerializer` is used: then content will be decompressed and tried
118118
to loaded from json.

src/diskcollections/iterables/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010
serializer_class=PickleZLibSerializer,
1111
)
1212

13+
1314
FileDeque = partial(
1415
Deque,
1516
client_class=TemporaryDirectoryClient,
1617
serializer_class=PickleZLibSerializer,
1718
)
1819

20+
1921
__all__ = (
2022
"List",
2123
"Deque",

src/diskcollections/iterables/clients.py

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import os.path
22
import tempfile
3+
from typing import AnyStr, Optional
34

45
from diskcollections.interfaces import IClientSequence
56
from diskcollections.py2to3 import TemporaryDirectory
67

8+
mode_str = "w+"
9+
mode_bytes = "w+b"
10+
modes = {mode_str, mode_bytes}
11+
712

813
class TemporaryDirectoryClient(IClientSequence):
914
"""
@@ -15,9 +20,10 @@ class TemporaryDirectoryClient(IClientSequence):
1520
new content.
1621
"""
1722

18-
def __init__(self, iterable=(), mode="w+b"):
23+
def __init__(self, iterable=(), mode=mode_bytes):
1924
super(TemporaryDirectoryClient, self).__init__()
2025
self.__mode = mode
26+
self.__available_modes = modes - {mode}
2127
self.__files = []
2228
self.__directory = TemporaryDirectory()
2329
self.extend(iterable)
@@ -49,22 +55,35 @@ def __getitem__(self, index):
4955
return file.read()
5056

5157
def __setitem__(self, index, value):
52-
file = tempfile.TemporaryFile(
53-
mode=self.__mode, dir=self.__directory.name
54-
)
55-
file.write(bytes(value))
58+
file = self.safe_write(value)
5659
self.__files[index] = file
5760

5861
def __len__(self):
5962
return len(self.__files)
6063

6164
def insert(self, index, value):
62-
file = tempfile.TemporaryFile(
63-
mode=self.__mode, dir=self.__directory.name
64-
)
65-
file.write(value)
65+
file = self.safe_write(value)
6666
self.__files.insert(index, file)
6767

68+
def __write(self, value, mode: Optional[str] = None):
69+
mode = mode or self.__mode
70+
file = tempfile.TemporaryFile(mode=mode, dir=self.__directory.name)
71+
file.write(value)
72+
return file
73+
74+
def safe_write(self, value):
75+
try:
76+
return self.__write(value)
77+
except TypeError:
78+
pass
79+
80+
for mode in self.__available_modes:
81+
try:
82+
return self.__write(value, mode=mode)
83+
except TypeError as e:
84+
pass
85+
raise e
86+
6887

6988
class PersistentDirectoryClient(IClientSequence):
7089
"""
@@ -79,6 +98,7 @@ class PersistentDirectoryClient(IClientSequence):
7998
def __init__(self, directory, iterable=()):
8099
super(PersistentDirectoryClient, self).__init__()
81100
self.__mode = "w+"
101+
self.__available_modes = modes - {self.__mode}
82102
self.__files = []
83103

84104
if not os.path.exists(directory):
@@ -102,9 +122,9 @@ def __delitem__(self, index):
102122
"""Delete item from given index.
103123
104124
Delete means here:
105-
- delete file undex `files[index]`
125+
- delete file under `files[index]`
106126
- when item is deleted then list become smaller
107-
- rename and reopen higher then index files
127+
- rename and reopen higher than index files
108128
"""
109129
file = self.__files[index]
110130
del self.__files[index]
@@ -144,16 +164,14 @@ def __getitem__(self, index):
144164

145165
def __setitem__(self, index, value):
146166
file_path = self.get_file_path(index)
147-
file = open(file_path, mode=self.__mode)
148-
file.write(value)
149-
file.seek(0)
167+
file = self.safe_write(file_path, value)
150168
self.__files[index] = file
151169

152170
def __len__(self):
153171
return len(self.__files)
154172

155173
def get_file_path(self, index):
156-
return "%s/%s" % (self.__directory, index)
174+
return f"{self.__directory}/{index}"
157175

158176
def insert(self, index, value):
159177
"""Insert value to index.
@@ -170,9 +188,7 @@ def insert(self, index, value):
170188
"""
171189
if index >= len(self.__files):
172190
file_path = self.get_file_path(index)
173-
file = open(file_path, mode=self.__mode)
174-
file.write(value)
175-
file.seek(0)
191+
file = self.safe_write(file_path, value)
176192
self.__files.insert(index, file)
177193
return
178194

@@ -186,9 +202,7 @@ def insert(self, index, value):
186202
os.rename(old_file_path, new_file_path)
187203

188204
file_path = self.get_file_path(index)
189-
file = open(file_path, mode=self.__mode)
190-
file.write(value)
191-
file.seek(0)
205+
file = self.safe_write(file_path, value)
192206
self.__files.insert(index, file)
193207

194208
for i in range(len(self.__files)):
@@ -200,3 +214,23 @@ def insert(self, index, value):
200214
file = open(file_path, mode="r+")
201215

202216
self.__files[i] = file
217+
218+
def __write(self, file_path, value, mode: Optional[str] = None):
219+
mode = mode or self.__mode
220+
file = open(file_path, mode=mode)
221+
file.write(value)
222+
file.seek(0)
223+
return file
224+
225+
def safe_write(self, file_path, value):
226+
try:
227+
return self.__write(file_path, value)
228+
except TypeError:
229+
pass
230+
231+
for mode in self.__available_modes:
232+
try:
233+
return self.__write(file_path, value, mode=mode)
234+
except TypeError as e:
235+
pass
236+
raise e

src/diskcollections/iterables/iterables.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import collections
2-
2+
import inspect
3+
from functools import partial
34
from diskcollections.py2to3 import izip
45

56

@@ -8,7 +9,14 @@ def __init__(
89
self, iterable=None, client_class=None, serializer_class=None
910
):
1011
super(List, self).__init__()
11-
self.__client = client_class()
12+
13+
if inspect.isclass(client_class):
14+
self.__client = client_class()
15+
elif isinstance(client_class, partial):
16+
self.__client = client_class()
17+
else:
18+
self.__client = client_class
19+
1220
self.__serializer = serializer_class
1321

1422
iterable = iterable or []
@@ -76,7 +84,13 @@ def __init__(
7684
client_class=None,
7785
serializer_class=None,
7886
):
79-
self.__client = client_class()
87+
if inspect.isclass(client_class):
88+
self.__client = client_class()
89+
elif isinstance(client_class, partial):
90+
self.__client = client_class()
91+
else:
92+
self.__client = client_class
93+
8094
self.__serializer = serializer_class
8195
self.__max_length = maxlen
8296
self.extend(iterable)

tests/conftest.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import random
2+
import shutil
3+
import uuid
4+
from functools import partial
5+
6+
import pathlib
7+
import pytest
8+
9+
from diskcollections import serializers
10+
from diskcollections.iterables import clients, List
11+
12+
from diskcollections.iterables import FileDeque
13+
14+
here = pathlib.Path(__file__).parent.absolute()
15+
test_persistent_dir = here / "persistent_dir"
16+
17+
18+
primitive_values = ["a", 1, [1, 2, 3], {"a": 1, "b": 2, "c": [1, 2, 3]}]
19+
20+
21+
@pytest.fixture(params=primitive_values, ids=list(map(str, primitive_values)))
22+
def primitive_value(request):
23+
return request.param
24+
25+
26+
serializers_classes = [
27+
serializers.JsonSerializer,
28+
serializers.JsonZLibSerializer,
29+
serializers.PickleSerializer,
30+
serializers.PickleZLibSerializer,
31+
]
32+
33+
34+
@pytest.fixture(
35+
params=serializers_classes, ids=list(map(str, serializers_classes))
36+
)
37+
def serializer_class(request):
38+
return request.param
39+
40+
41+
@pytest.fixture(
42+
params=[
43+
clients.TemporaryDirectoryClient,
44+
partial(
45+
clients.PersistentDirectoryClient,
46+
test_persistent_dir / str(uuid.uuid4()),
47+
),
48+
],
49+
ids=["TemporaryDirectoryClient", "PersistentDirectoryClient"],
50+
)
51+
def client_class(request):
52+
test_persistent_dir.mkdir(exist_ok=True)
53+
yield request.param
54+
shutil.rmtree(test_persistent_dir.absolute())
55+
56+
57+
@pytest.fixture(
58+
params=[
59+
partial(
60+
List,
61+
client_class=partial(clients.TemporaryDirectoryClient, mode="w+b"),
62+
serializer_class=serializers.PickleZLibSerializer,
63+
)
64+
],
65+
ids=["FileList"],
66+
)
67+
def list_class(request):
68+
return request.param
69+
70+
71+
@pytest.fixture(params=[FileDeque], ids=["FileDeque"])
72+
def deque_class(request):
73+
return request.param

tests/test_examples.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from diskcollections.iterables import FileDeque, FileList, List, Deque
2+
3+
4+
def test_file_list() -> None:
5+
flist = FileList()
6+
flist.extend([1, 2, 3])
7+
flist.append(4)
8+
assert all(i in flist for i in [1, 2, 3, 4])
9+
10+
flist2 = flist[:]
11+
assert isinstance(flist2, List)
12+
13+
my_list = list(flist)
14+
assert isinstance(my_list, list)
15+
16+
17+
def test_file_deque() -> None:
18+
fdeque = FileDeque()
19+
fdeque.extend([1, 2, 3])
20+
fdeque.append(4)
21+
assert all(i in fdeque for i in [1, 2, 3, 4])
22+
23+
fdeque = FileDeque([1, 2, 3, 4])
24+
assert fdeque.pop() == 4
25+
fdeque.appendleft(0)
26+
assert fdeque.popleft() == 0
27+
28+
29+
def test_list_serializers(client_class, serializer_class) -> None:
30+
expected = [{"a": 1, "b": 2, "c": 3}, "a", 1]
31+
flist = List(client_class=client_class, serializer_class=serializer_class)
32+
flist.extend(expected)
33+
assert all(i in flist for i in expected)
34+
assert flist == expected
35+
assert list(flist) == expected
36+
37+
38+
def test_deque_serializers(client_class, serializer_class) -> None:
39+
expected = [{"a": 1, "b": 2, "c": 3}, "a", 1]
40+
fdeque = Deque(
41+
client_class=client_class, serializer_class=serializer_class
42+
)
43+
fdeque.extend(expected)
44+
assert all(i in fdeque for i in expected)
45+
assert fdeque == expected
46+
assert list(fdeque) == expected

tests/test_serializers.py

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,3 @@
1-
import pytest
2-
3-
from diskcollections import serializers
4-
5-
primitive_values = ["a", 1, [1, 2, 3], {"a": 1, "b": 2, "c": [1, 2, 3]}]
6-
7-
serializers_classes = [
8-
serializers.JsonSerializer,
9-
serializers.JsonZLibSerializer,
10-
serializers.PickleSerializer,
11-
serializers.PickleZLibSerializer,
12-
]
13-
14-
15-
@pytest.fixture(params=primitive_values, ids=list(map(str, primitive_values)))
16-
def primitive_value(request):
17-
return request.param
18-
19-
20-
@pytest.fixture(
21-
params=serializers_classes, ids=list(map(str, serializers_classes))
22-
)
23-
def serializer_class(request):
24-
return request.param
25-
26-
271
def test_encode_decode(primitive_value, serializer_class):
282
encoded = serializer_class.dumps(primitive_value)
293
decoded = serializer_class.loads(encoded)

0 commit comments

Comments
 (0)