TSVZ 4.4__tar.gz → 4.6__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.
- {tsvz-4.4 → tsvz-4.6}/PKG-INFO +2 -2
- {tsvz-4.4 → tsvz-4.6}/README.md +1 -1
- {tsvz-4.4 → tsvz-4.6}/TSVZ.egg-info/PKG-INFO +2 -2
- {tsvz-4.4 → tsvz-4.6}/TSVZ.py +165 -31
- {tsvz-4.4 → tsvz-4.6}/TSVZ.egg-info/SOURCES.txt +0 -0
- {tsvz-4.4 → tsvz-4.6}/TSVZ.egg-info/dependency_links.txt +0 -0
- {tsvz-4.4 → tsvz-4.6}/TSVZ.egg-info/entry_points.txt +0 -0
- {tsvz-4.4 → tsvz-4.6}/TSVZ.egg-info/requires.txt +0 -0
- {tsvz-4.4 → tsvz-4.6}/TSVZ.egg-info/top_level.txt +0 -0
- {tsvz-4.4 → tsvz-4.6}/setup.cfg +0 -0
- {tsvz-4.4 → tsvz-4.6}/setup.py +0 -0
{tsvz-4.4 → tsvz-4.6}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: TSVZ
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.6
|
|
4
4
|
Summary: Key-value store kept in a TSV / CSV / NSV / PSV file, with tsvz-spec-v1 support
|
|
5
5
|
Home-page: https://github.com/yufei-pan/TSVZ
|
|
6
6
|
Author: Yufei Pan
|
|
@@ -34,7 +34,7 @@ key-value store in a delimiter-separated text file**. The first column of each
|
|
|
34
34
|
row is the key; the file behaves like an ordered dictionary that is persisted to
|
|
35
35
|
disk as you change it.
|
|
36
36
|
|
|
37
|
-
**Version 4.
|
|
37
|
+
**Version 4.6**, Python **3.6+**, one self-contained module (`TSVZ.py`): drop it
|
|
38
38
|
next to your code and `import TSVZ`, or install it.
|
|
39
39
|
|
|
40
40
|
```bash
|
{tsvz-4.4 → tsvz-4.6}/README.md
RENAMED
|
@@ -7,7 +7,7 @@ key-value store in a delimiter-separated text file**. The first column of each
|
|
|
7
7
|
row is the key; the file behaves like an ordered dictionary that is persisted to
|
|
8
8
|
disk as you change it.
|
|
9
9
|
|
|
10
|
-
**Version 4.
|
|
10
|
+
**Version 4.6**, Python **3.6+**, one self-contained module (`TSVZ.py`): drop it
|
|
11
11
|
next to your code and `import TSVZ`, or install it.
|
|
12
12
|
|
|
13
13
|
```bash
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: TSVZ
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.6
|
|
4
4
|
Summary: Key-value store kept in a TSV / CSV / NSV / PSV file, with tsvz-spec-v1 support
|
|
5
5
|
Home-page: https://github.com/yufei-pan/TSVZ
|
|
6
6
|
Author: Yufei Pan
|
|
@@ -34,7 +34,7 @@ key-value store in a delimiter-separated text file**. The first column of each
|
|
|
34
34
|
row is the key; the file behaves like an ordered dictionary that is persisted to
|
|
35
35
|
disk as you change it.
|
|
36
36
|
|
|
37
|
-
**Version 4.
|
|
37
|
+
**Version 4.6**, Python **3.6+**, one self-contained module (`TSVZ.py`): drop it
|
|
38
38
|
next to your code and `import TSVZ`, or install it.
|
|
39
39
|
|
|
40
40
|
```bash
|
{tsvz-4.4 → tsvz-4.6}/TSVZ.py
RENAMED
|
@@ -40,7 +40,7 @@ if os.name == 'nt':
|
|
|
40
40
|
elif os.name == 'posix':
|
|
41
41
|
import fcntl
|
|
42
42
|
|
|
43
|
-
version = '4.
|
|
43
|
+
version = '4.6'
|
|
44
44
|
__version__ = version
|
|
45
45
|
author = 'pan@zopyr.us'
|
|
46
46
|
COMMIT_DATE = '2026-10-12'
|
|
@@ -179,13 +179,18 @@ def get_terminal_size():
|
|
|
179
179
|
try:
|
|
180
180
|
import os
|
|
181
181
|
_tsize = os.get_terminal_size()
|
|
182
|
+
if not (_tsize[0] and _tsize[1]):
|
|
183
|
+
raise OSError('the terminal has no size set')
|
|
182
184
|
except Exception:
|
|
183
185
|
try:
|
|
184
186
|
import fcntl
|
|
185
187
|
import struct
|
|
186
188
|
import termios
|
|
187
189
|
packed = fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack('HHHH', 0, 0, 0, 0))
|
|
188
|
-
|
|
190
|
+
rows, columns = struct.unpack('HHHH', packed)[:2]
|
|
191
|
+
if not (rows and columns):
|
|
192
|
+
raise OSError('the terminal has no size set')
|
|
193
|
+
_tsize = (columns, rows)
|
|
189
194
|
except Exception:
|
|
190
195
|
import shutil
|
|
191
196
|
_tsize = shutil.get_terminal_size(fallback=(240, 50))
|
|
@@ -1654,6 +1659,34 @@ def _specFormatRecord(cells, delimiter, marker=False):
|
|
|
1654
1659
|
return delimiter.join([first] + [_specEncodeField(cell, delimiter) for cell in cells[1:]])
|
|
1655
1660
|
|
|
1656
1661
|
|
|
1662
|
+
def _specTombstones(keys, delimiter, strip):
|
|
1663
|
+
"""``(cells, marker)`` records that delete ``keys`` (spec §9.2) from a store whose stripping is ``strip``.
|
|
1664
|
+
|
|
1665
|
+
While stripping is on a reader strips a tombstone's key too (spec §9.1): the
|
|
1666
|
+
tombstone of a key whose encoded form ends in a space or tab, stored while
|
|
1667
|
+
stripping was off, would delete the stripped key instead. A batch holding
|
|
1668
|
+
one is written with stripping off, and stripping is turned back on after it
|
|
1669
|
+
(spec §9.6).
|
|
1670
|
+
"""
|
|
1671
|
+
rows = [([key], False) for key in keys]
|
|
1672
|
+
if strip and any(key.endswith((' ', '\t')) and _specEncodeField(key, delimiter, isKey=True).endswith((' ', '\t'))
|
|
1673
|
+
for key in keys):
|
|
1674
|
+
rows.insert(0, (['#_strip_trailing_whites_#', 'false'], True))
|
|
1675
|
+
rows.append((['#_strip_trailing_whites_#', 'true'], True))
|
|
1676
|
+
return rows
|
|
1677
|
+
|
|
1678
|
+
|
|
1679
|
+
def _specFormatRecords(rows, delimiter):
|
|
1680
|
+
"""``rows`` (``[(cells, marker), ...]``) as record lines, each with its '\\n'."""
|
|
1681
|
+
return ''.join(_specFormatRecord(cells, delimiter, marker=marker) + '\n' for cells, marker in rows)
|
|
1682
|
+
|
|
1683
|
+
|
|
1684
|
+
def _specTombstoneLines(key, delimiter, strip):
|
|
1685
|
+
"""The tombstone of ``key`` (spec §9.6) as lines joined by '\\n', without a last '\\n'."""
|
|
1686
|
+
return '\n'.join(_specFormatRecord(cells, delimiter, marker=marker)
|
|
1687
|
+
for cells, marker in _specTombstones([key], delimiter, strip))
|
|
1688
|
+
|
|
1689
|
+
|
|
1657
1690
|
class _SpecState(object):
|
|
1658
1691
|
"""Reader state of spec §12, carried across records and parts."""
|
|
1659
1692
|
|
|
@@ -2007,9 +2040,10 @@ def _gzipUnpad(region):
|
|
|
2007
2040
|
try:
|
|
2008
2041
|
inflater.decompress(stripped[start:])
|
|
2009
2042
|
if not inflater.eof: # its last bytes may be zero bytes of its own
|
|
2010
|
-
# Fed only zeros, compressed data
|
|
2011
|
-
#
|
|
2012
|
-
|
|
2043
|
+
# Fed only zeros, compressed data in a Huffman block ends within a few hundred bytes, fails or
|
|
2044
|
+
# never ends. In a stored block it can end only if the block is final, after the rest of its
|
|
2045
|
+
# NLEN (both bytes zero only when LEN is 65535) and of its data: at most 2 + 65535 zeros.
|
|
2046
|
+
zeros = min(zeros, 2 + 65535)
|
|
2013
2047
|
fed = 0
|
|
2014
2048
|
while fed < zeros: # in pieces, with bounded output
|
|
2015
2049
|
piece = b'\0' * min(1 << 16, zeros - fed)
|
|
@@ -2603,16 +2637,23 @@ def _specBackupPart(path, reporter):
|
|
|
2603
2637
|
_lockFile(f)
|
|
2604
2638
|
try:
|
|
2605
2639
|
first = backup = '{}.damaged-{}'.format(path, time.strftime('%Y%m%dT%H%M%S'))
|
|
2640
|
+
# Readable by whoever could read the part, writable by its owner only, never setuid,
|
|
2641
|
+
# setgid, sticky or executable (the umask still applies on top).
|
|
2642
|
+
st = os.fstat(f.fileno())
|
|
2643
|
+
mode = st.st_mode & 0o644
|
|
2606
2644
|
suffix = 0
|
|
2607
2645
|
while True:
|
|
2608
2646
|
try:
|
|
2609
|
-
|
|
2647
|
+
# never overwrite an earlier backup; O_BINARY: no newline translation on Windows
|
|
2648
|
+
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, 'O_BINARY', 0)
|
|
2649
|
+
copy = os.fdopen(os.open(backup, flags, mode), 'wb')
|
|
2610
2650
|
break
|
|
2611
2651
|
except FileExistsError:
|
|
2612
2652
|
suffix += 1
|
|
2613
2653
|
backup = '{}.{}'.format(first, suffix)
|
|
2614
2654
|
try:
|
|
2615
2655
|
with copy:
|
|
2656
|
+
_matchOwner(copy.fileno(), st)
|
|
2616
2657
|
shutil.copyfileobj(f, copy)
|
|
2617
2658
|
copy.flush()
|
|
2618
2659
|
os.fsync(copy.fileno())
|
|
@@ -2671,6 +2712,22 @@ def _specSnapshot(fileName):
|
|
|
2671
2712
|
return parts, None
|
|
2672
2713
|
|
|
2673
2714
|
|
|
2715
|
+
def _matchOwner(fd, st):
|
|
2716
|
+
"""Give a new copy the owner and group of the file ``st`` describes. Where that is not allowed,
|
|
2717
|
+
a copy left in another group loses its group bits, so no one reads it who could not read the file."""
|
|
2718
|
+
if not hasattr(os, 'fchown'):
|
|
2719
|
+
return
|
|
2720
|
+
for uid in (st.st_uid, -1):
|
|
2721
|
+
try:
|
|
2722
|
+
os.fchown(fd, uid, st.st_gid)
|
|
2723
|
+
return
|
|
2724
|
+
except OSError:
|
|
2725
|
+
pass
|
|
2726
|
+
created = os.fstat(fd)
|
|
2727
|
+
if created.st_gid != st.st_gid:
|
|
2728
|
+
os.fchmod(fd, created.st_mode & 0o7707) # what the umask left, without the group bits
|
|
2729
|
+
|
|
2730
|
+
|
|
2674
2731
|
def _specRewriteInPlace(path, content, expect=None, memberSize=None):
|
|
2675
2732
|
"""Replace a part's bytes in place (same inode, spec §18.7 / §19.8).
|
|
2676
2733
|
|
|
@@ -2690,18 +2747,73 @@ def _specRewriteInPlace(path, content, expect=None, memberSize=None):
|
|
|
2690
2747
|
st = os.fstat(f.fileno())
|
|
2691
2748
|
if expect is not None and _specStamp(st) != expect:
|
|
2692
2749
|
return None
|
|
2693
|
-
|
|
2750
|
+
fd = f.fileno()
|
|
2751
|
+
# The whole part, for a failed rewrite to put back whenever it fails. Read unbuffered, as
|
|
2752
|
+
# the file is written below: a read-ahead in f would leave it a stale position to close at.
|
|
2753
|
+
old = _readFrom0(fd, st.st_size)
|
|
2754
|
+
if old == raw:
|
|
2694
2755
|
return False
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2756
|
+
try:
|
|
2757
|
+
_reserveSpace(fd, len(raw)) # spec §19.8: a full disk refuses the rewrite before any byte changes
|
|
2758
|
+
_writeFrom0(fd, raw)
|
|
2759
|
+
except BaseException as e:
|
|
2760
|
+
# Put the old bytes back rather than leave a half-rewritten part (or one a failed
|
|
2761
|
+
# reservation lengthened).
|
|
2762
|
+
try:
|
|
2763
|
+
_writeFrom0(fd, old)
|
|
2764
|
+
os.fsync(fd)
|
|
2765
|
+
except OSError as undo:
|
|
2766
|
+
damaged = '{}: the rewrite failed ({!r}) and the old bytes could not be put back ({}): the part is damaged'.format(
|
|
2767
|
+
path, e, undo)
|
|
2768
|
+
if isinstance(e, OSError):
|
|
2769
|
+
raise OSError(e.errno, damaged)
|
|
2770
|
+
_warn('TSVZ warning: ' + damaged) # an interrupt stays an interrupt
|
|
2771
|
+
raise
|
|
2772
|
+
os.fsync(fd)
|
|
2700
2773
|
return True
|
|
2701
2774
|
finally:
|
|
2775
|
+
try:
|
|
2776
|
+
_seekRawStart(f) # msvcrt unlocks from the raw position: the range it locked
|
|
2777
|
+
except OSError:
|
|
2778
|
+
pass
|
|
2702
2779
|
_unlockFile(f)
|
|
2703
2780
|
|
|
2704
2781
|
|
|
2782
|
+
def _reserveSpace(fd, length):
|
|
2783
|
+
"""Reserve ``length`` bytes from the start of the file before it is rewritten in place (spec
|
|
2784
|
+
§19.8). Where the platform or the file system cannot reserve space, nothing is reserved."""
|
|
2785
|
+
fallocate = getattr(os, 'posix_fallocate', None)
|
|
2786
|
+
if fallocate is None or length <= 0:
|
|
2787
|
+
return
|
|
2788
|
+
try:
|
|
2789
|
+
fallocate(fd, 0, length)
|
|
2790
|
+
except OSError as e:
|
|
2791
|
+
if e.errno not in (errno.EOPNOTSUPP, getattr(errno, 'ENOTSUP', errno.EOPNOTSUPP), errno.ENOSYS, errno.EINVAL):
|
|
2792
|
+
raise
|
|
2793
|
+
|
|
2794
|
+
|
|
2795
|
+
def _readFrom0(fd, length):
|
|
2796
|
+
"""Up to ``length`` bytes from the start of the file (unbuffered)."""
|
|
2797
|
+
os.lseek(fd, 0, os.SEEK_SET)
|
|
2798
|
+
chunks = []
|
|
2799
|
+
while length > 0:
|
|
2800
|
+
chunk = os.read(fd, length)
|
|
2801
|
+
if not chunk:
|
|
2802
|
+
break
|
|
2803
|
+
chunks.append(chunk)
|
|
2804
|
+
length -= len(chunk)
|
|
2805
|
+
return b''.join(chunks)
|
|
2806
|
+
|
|
2807
|
+
|
|
2808
|
+
def _writeFrom0(fd, data):
|
|
2809
|
+
"""Write ``data`` over the file from offset 0 and cut the file there (unbuffered)."""
|
|
2810
|
+
os.lseek(fd, 0, os.SEEK_SET)
|
|
2811
|
+
view = memoryview(data)
|
|
2812
|
+
while view:
|
|
2813
|
+
view = view[os.write(fd, view):]
|
|
2814
|
+
os.ftruncate(fd, len(data))
|
|
2815
|
+
|
|
2816
|
+
|
|
2705
2817
|
def _specPreamble(state, delimiter, compensate=False, version=True):
|
|
2706
2818
|
"""Marker lines that re-create ``state`` (spec §19.2.3a): version, non-default markers, defaults last.
|
|
2707
2819
|
|
|
@@ -2904,7 +3016,7 @@ def _specClearTabularFile(fileName, teeLogger=None, header='', verifyHeader=Fals
|
|
|
2904
3016
|
attempt.note('refused', None, 'clear skipped: a part of the store could not be read; nothing was written')
|
|
2905
3017
|
return False
|
|
2906
3018
|
if len(load.parts) > 1:
|
|
2907
|
-
tombstones =
|
|
3019
|
+
tombstones = _specFormatRecords(_specTombstones(load.data, delimiter, load.state.strip), delimiter)
|
|
2908
3020
|
if tombstones:
|
|
2909
3021
|
_specAppendPayload(load.active, tombstones.encode('utf-8'), attempt, memberSize=load.state.memberSize)
|
|
2910
3022
|
attempt.note('multipart', None, 'cleared a multi-part store by appending tombstones; older parts are not compacted')
|
|
@@ -3557,8 +3669,8 @@ class TSVZed(OrderedDict):
|
|
|
3557
3669
|
# the mapping by the caller, so self[key] must not be accessed.
|
|
3558
3670
|
self.dirty = True
|
|
3559
3671
|
if self._spec:
|
|
3560
|
-
# Spec §9.2: a tombstone is the lone key, never padded.
|
|
3561
|
-
self.appendQueue.append(
|
|
3672
|
+
# Spec §9.2: a tombstone is the lone key, never padded. One queue item, so it is committed whole.
|
|
3673
|
+
self.appendQueue.append(_specTombstoneLines(key, self.delimiter, self._specState.strip))
|
|
3562
3674
|
return self
|
|
3563
3675
|
if self.correctColumnNum > 1:
|
|
3564
3676
|
emptyLine = [key]+['']*(self.correctColumnNum-1)
|
|
@@ -3780,7 +3892,15 @@ memoryOnly:{self.memoryOnly}
|
|
|
3780
3892
|
return self.close()
|
|
3781
3893
|
|
|
3782
3894
|
def popitem(self, last=True):
|
|
3783
|
-
|
|
3895
|
+
if sys.version_info >= (3, 11):
|
|
3896
|
+
key, value = OrderedDict.popitem(self, last)
|
|
3897
|
+
else:
|
|
3898
|
+
# Before Python 3.11 OrderedDict.popitem goes through self[key] and del self[key], which strip the key.
|
|
3899
|
+
if not OrderedDict.__len__(self):
|
|
3900
|
+
raise KeyError('dictionary is empty')
|
|
3901
|
+
key = next(OrderedDict.__reversed__(self)) if last else next(OrderedDict.__iter__(self))
|
|
3902
|
+
value = OrderedDict.__getitem__(self, key)
|
|
3903
|
+
OrderedDict.__delitem__(self, key)
|
|
3784
3904
|
if not self.memoryOnly:
|
|
3785
3905
|
self.__appendEmptyLine(key)
|
|
3786
3906
|
self.lastUpdateTime = get_time_ns()
|
|
@@ -4485,7 +4605,7 @@ class TSVZedLite(MutableMapping):
|
|
|
4485
4605
|
self._specWrite(_specFormatRecord(value, self.delimiter, marker=True))
|
|
4486
4606
|
|
|
4487
4607
|
def _specWrite(self, line):
|
|
4488
|
-
"""Append one record to the active part; return its offset, or None for a compressed part."""
|
|
4608
|
+
"""Append one record (or lines written together) to the active part; return its offset, or None for a compressed part."""
|
|
4489
4609
|
data = (line + '\n').encode('utf-8', errors='replace')
|
|
4490
4610
|
if self.fileObj is None:
|
|
4491
4611
|
reporter = _Reporter(self._activePath)
|
|
@@ -4658,7 +4778,7 @@ class TSVZedLite(MutableMapping):
|
|
|
4658
4778
|
|
|
4659
4779
|
def __mapDeleteToFile(self,key):
|
|
4660
4780
|
if self._spec:
|
|
4661
|
-
self._specWrite(
|
|
4781
|
+
self._specWrite(_specTombstoneLines(key, self.delimiter, self._specState.strip))
|
|
4662
4782
|
return
|
|
4663
4783
|
# Persist a deletion by appending a tombstone (a lone key with no
|
|
4664
4784
|
# values); on read such a row deletes the key (see _processLine). The
|
|
@@ -4802,7 +4922,7 @@ class TSVZedLite(MutableMapping):
|
|
|
4802
4922
|
eprint(f"Key {key} not found")
|
|
4803
4923
|
return
|
|
4804
4924
|
self.indexes.pop(key, None)
|
|
4805
|
-
self.
|
|
4925
|
+
self.__mapDeleteToFile(key)
|
|
4806
4926
|
return
|
|
4807
4927
|
if key == DEFAULTS_INDICATOR_KEY:
|
|
4808
4928
|
self.__mapDeleteToFile(key)
|
|
@@ -5379,11 +5499,14 @@ class _ServeStore(object):
|
|
|
5379
5499
|
def _payload(self, rows, log):
|
|
5380
5500
|
"""Bytes for ``rows`` (``[(cells, marker), ...]``), formatted as the direct writers format them."""
|
|
5381
5501
|
if self.spec:
|
|
5382
|
-
return
|
|
5383
|
-
for cells, marker in rows).encode('utf-8')
|
|
5502
|
+
return _specFormatRecords(rows, self.delimiter).encode('utf-8')
|
|
5384
5503
|
return _legacyFormatPayload(self.path, [cells for cells, marker in rows], log, [''], True, self.verbose,
|
|
5385
5504
|
'utf8', self.strict, self.delimiter)[0]
|
|
5386
5505
|
|
|
5506
|
+
def _tombstone(self, key):
|
|
5507
|
+
"""Rows that delete ``key``: written with stripping off when it would strip ``key`` (spec §9.6)."""
|
|
5508
|
+
return _specTombstones([key], self.delimiter, self.state.strip) if self.spec else [([key], False)]
|
|
5509
|
+
|
|
5387
5510
|
def _durable(self, sync):
|
|
5388
5511
|
return sync or (self.spec and self.state.writeAck == 'disk')
|
|
5389
5512
|
|
|
@@ -5421,7 +5544,7 @@ class _ServeStore(object):
|
|
|
5421
5544
|
if row is None:
|
|
5422
5545
|
return None
|
|
5423
5546
|
row = list(row)
|
|
5424
|
-
queued = self._submit(
|
|
5547
|
+
queued = self._submit(self._tombstone(key), sync, log)
|
|
5425
5548
|
self._ack(queued, written=True) # the row is only popped once its tombstone is written
|
|
5426
5549
|
return row
|
|
5427
5550
|
|
|
@@ -5435,7 +5558,7 @@ class _ServeStore(object):
|
|
|
5435
5558
|
return None
|
|
5436
5559
|
key = next(reversed(self.data)) if last else next(iter(self.data))
|
|
5437
5560
|
row = list(self.data[key])
|
|
5438
|
-
queued = self._submit(
|
|
5561
|
+
queued = self._submit(self._tombstone(key), sync, log)
|
|
5439
5562
|
self._ack(queued, written=True)
|
|
5440
5563
|
return row
|
|
5441
5564
|
|
|
@@ -5620,21 +5743,32 @@ def _serveDispatch(store, line, bulk, log):
|
|
|
5620
5743
|
if operation in _SERVE_NEED_STORE and store.missing(log):
|
|
5621
5744
|
return 1, [], 'no such store' # spec §20.2.2
|
|
5622
5745
|
unreadable = (1, _SERVE_UNREADABLE)
|
|
5746
|
+
# Each read takes its status under the store lock it read under: another request's reload may
|
|
5747
|
+
# change ``unreadable`` once the lock is free.
|
|
5623
5748
|
if operation == 'read':
|
|
5624
|
-
|
|
5625
|
-
|
|
5749
|
+
with store.lock:
|
|
5750
|
+
rows = store.read(log)
|
|
5751
|
+
status = unreadable if store.unreadable else (0, '')
|
|
5626
5752
|
return status[0], [_specFormatRecord(row, '\t') for row in rows], status[1]
|
|
5627
5753
|
if operation == 'get':
|
|
5628
|
-
|
|
5629
|
-
|
|
5754
|
+
with store.lock:
|
|
5755
|
+
rows, missing = store.get(args, log)
|
|
5756
|
+
status = unreadable if store.unreadable else (3 if missing else 0, '')
|
|
5630
5757
|
return status[0], [_specFormatRecord(row, '\t') for row in rows], status[1]
|
|
5631
5758
|
if operation == 'has':
|
|
5632
|
-
|
|
5633
|
-
|
|
5759
|
+
with store.lock:
|
|
5760
|
+
found = store.has(args[0], log)
|
|
5761
|
+
return (1, [], _SERVE_UNREADABLE) if store.unreadable else (0 if found else 3, [], '')
|
|
5634
5762
|
if operation == 'len':
|
|
5635
|
-
|
|
5763
|
+
with store.lock:
|
|
5764
|
+
count = store.length(log)
|
|
5765
|
+
status = unreadable if store.unreadable else (0, '') # spec §4.10, §20.6: a stopped read failed
|
|
5766
|
+
return status[0], [str(count)], status[1]
|
|
5636
5767
|
if operation == 'keys':
|
|
5637
|
-
|
|
5768
|
+
with store.lock:
|
|
5769
|
+
keys = store.keys(log)
|
|
5770
|
+
status = unreadable if store.unreadable else (0, '')
|
|
5771
|
+
return status[0], [_specEncodeField(key, '\t', isKey=True) for key in keys], status[1]
|
|
5638
5772
|
if operation in ('set', 'append', 'delete'):
|
|
5639
5773
|
keysOnly = operation == 'delete'
|
|
5640
5774
|
if isBulk:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tsvz-4.4 → tsvz-4.6}/setup.cfg
RENAMED
|
File without changes
|
{tsvz-4.4 → tsvz-4.6}/setup.py
RENAMED
|
File without changes
|