TSVZ 3.39__tar.gz → 4.4__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/PKG-INFO +369 -0
- tsvz-4.4/README.md +342 -0
- tsvz-4.4/TSVZ.egg-info/PKG-INFO +369 -0
- {tsvz-3.39 → tsvz-4.4}/TSVZ.egg-info/requires.txt +0 -1
- tsvz-4.4/TSVZ.py +7311 -0
- {tsvz-3.39 → tsvz-4.4}/setup.py +4 -6
- tsvz-3.39/PKG-INFO +0 -396
- tsvz-3.39/README.md +0 -368
- tsvz-3.39/TSVZ.egg-info/PKG-INFO +0 -396
- tsvz-3.39/TSVZ.py +0 -2459
- {tsvz-3.39 → tsvz-4.4}/TSVZ.egg-info/SOURCES.txt +0 -0
- {tsvz-3.39 → tsvz-4.4}/TSVZ.egg-info/dependency_links.txt +0 -0
- {tsvz-3.39 → tsvz-4.4}/TSVZ.egg-info/entry_points.txt +0 -0
- {tsvz-3.39 → tsvz-4.4}/TSVZ.egg-info/top_level.txt +0 -0
- {tsvz-3.39 → tsvz-4.4}/setup.cfg +0 -0
tsvz-4.4/PKG-INFO
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: TSVZ
|
|
3
|
+
Version: 4.4
|
|
4
|
+
Summary: Key-value store kept in a TSV / CSV / NSV / PSV file, with tsvz-spec-v1 support
|
|
5
|
+
Home-page: https://github.com/yufei-pan/TSVZ
|
|
6
|
+
Author: Yufei Pan
|
|
7
|
+
Author-email: pan@zopyr.us
|
|
8
|
+
License: GPLv3+
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
11
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: >=3.6
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Provides-Extra: completion
|
|
16
|
+
Requires-Dist: argcomplete; extra == "completion"
|
|
17
|
+
Dynamic: author
|
|
18
|
+
Dynamic: author-email
|
|
19
|
+
Dynamic: classifier
|
|
20
|
+
Dynamic: description
|
|
21
|
+
Dynamic: description-content-type
|
|
22
|
+
Dynamic: home-page
|
|
23
|
+
Dynamic: license
|
|
24
|
+
Dynamic: provides-extra
|
|
25
|
+
Dynamic: requires-python
|
|
26
|
+
Dynamic: summary
|
|
27
|
+
|
|
28
|
+
# TSVZ
|
|
29
|
+
|
|
30
|
+
**[tsvz.org](https://tsvz.org)**: the format, its specification and its implementations.
|
|
31
|
+
|
|
32
|
+
TSVZ is a small, dependency-free library and CLI that keeps an **ordered
|
|
33
|
+
key-value store in a delimiter-separated text file**. The first column of each
|
|
34
|
+
row is the key; the file behaves like an ordered dictionary that is persisted to
|
|
35
|
+
disk as you change it.
|
|
36
|
+
|
|
37
|
+
**Version 4.4**, Python **3.6+**, one self-contained module (`TSVZ.py`): drop it
|
|
38
|
+
next to your code and `import TSVZ`, or install it.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install tsvz # from PyPI
|
|
42
|
+
pip install -e . # editable, from this directory
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Two file dialects, one API
|
|
46
|
+
|
|
47
|
+
| Extension | Rules |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `.tsv` `.csv` `.nsv` `.psv` (and any other) | The TSVZ **3.39** format, unchanged. A 3.39 host can read every file 4.x writes. |
|
|
50
|
+
| `.tsvz` `.csvz` `.nsvz` `.psvz` | The **[tsvz-spec-v1](tsvz-spec-v1.md)** append-only key-value log. |
|
|
51
|
+
|
|
52
|
+
The delimiter follows the extension: tab, comma, NUL, pipe. A trailing
|
|
53
|
+
compression suffix (`.gz`, `.bz2`, `.xz`, `.zst`) is allowed on both; `.zst`
|
|
54
|
+
needs Python 3.14. For important or sensitive data, keep the part you write to
|
|
55
|
+
uncompressed and compress parts once they no longer change: a damaged
|
|
56
|
+
compressed member can lose every record in it (spec §16.4.2).
|
|
57
|
+
|
|
58
|
+
`TSVZed`, `TSVZedLite`, `readTabularFile`, `appendTabularFile`,
|
|
59
|
+
`appendLinesTabularFile`, `clearTabularFile`, `scrubTabularFile` (and the
|
|
60
|
+
`*TSV` aliases) work on both dialects. `t.dialect` tells you which one a store
|
|
61
|
+
uses.
|
|
62
|
+
|
|
63
|
+
## Quick start
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import TSVZ
|
|
67
|
+
|
|
68
|
+
t = TSVZ.TSVZed('people.tsvz', header='id\tname\tscore')
|
|
69
|
+
t['alice'] = ['alice', 'Alice', '10']
|
|
70
|
+
t['bob'] = 'bob\tBob\t20'
|
|
71
|
+
t['alice'] = ['alice', 'Alice', '11'] # last write wins
|
|
72
|
+
del t['bob'] # appends a tombstone
|
|
73
|
+
print(t['alice']) # ['alice', 'Alice', '11']
|
|
74
|
+
t.close()
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
tsvz set people.tsvz carol Carol 5 # add or update a row (append is the same)
|
|
79
|
+
tsvz get people.tsvz carol # one row; exit 3 if carol is missing
|
|
80
|
+
tsvz read people.tsvz # every row: a table on a terminal, records in a pipe
|
|
81
|
+
tsvz delete people.tsvz carol # tombstone
|
|
82
|
+
tsvz read people.tsvz | tsvz set copy.tsvz - # copy every row
|
|
83
|
+
tsvz clear people.tsvz # empty it, keeping the header and markers
|
|
84
|
+
tsvz scrub people.tsvz # compact it (for .tsvz: archival maintenance)
|
|
85
|
+
tsvz verify people.tsvz # check #_checksum_*_# segments; exit 4 on a mismatch
|
|
86
|
+
tsvz parts events.tsvz # list the parts of a multi-part store
|
|
87
|
+
tsvz has people.tsvz carol # exit 0 if carol is live, 3 if not
|
|
88
|
+
tsvz pop people.tsvz carol # print carol's row and delete it
|
|
89
|
+
tsvz serve people.tsvz & # keep the store loaded; tsvz commands then use it
|
|
90
|
+
tsvz stop people.tsvz
|
|
91
|
+
tsvz people.tsvz append carol Carol 5 # the 3.39 form still works
|
|
92
|
+
tsvz -V
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## What TSVZ 4 does with `.tsvz` files
|
|
96
|
+
|
|
97
|
+
- **Reads** everything a conformant writer can produce: markers
|
|
98
|
+
(`#_defaults_#`, `#_strip_trailing_whites_#`, `#_fill_empty_with_default_#`,
|
|
99
|
+
`#_return_defaults_when_missing_#`, ...), escapes (`<sep>`, `<LF>`, `<lt>`,
|
|
100
|
+
`<#>`), tombstones, `#_checksum_<algo>_#` segments (crc32 and every
|
|
101
|
+
fixed-length hashlib algorithm), compressed parts, and **multi-part stores**
|
|
102
|
+
(`store.tsvz.<hex>`, part 0 first, `.rotated` parts skipped).
|
|
103
|
+
- **Writes** one file: the named file, or the highest-numbered part of a
|
|
104
|
+
multi-part store. Rows are written as given (no padding); a lone key is a
|
|
105
|
+
tombstone; a row of empty cells (`k\t\t`) is a live row.
|
|
106
|
+
- **Compaction is manual.** `scrubTabularFile` / `tsvz scrub f.tsvz` rewrites a
|
|
107
|
+
single-part store in place (same inode). If another writer commits while it
|
|
108
|
+
runs, it reads the store again; after three tries it writes nothing, with a
|
|
109
|
+
warning (`clearTabularFile` does the same). The automatic rewrite options of
|
|
110
|
+
3.39 (`rewrite_on_load`, `rewrite_on_exit`, `rewrite_interval`, `rewrite()`,
|
|
111
|
+
`mapToFile()`, `hardMapToFile()`) are ignored for `.tsvz`, with a warning.
|
|
112
|
+
- **Missing keys:** `t[key]` returns the defaults row while
|
|
113
|
+
`#_return_defaults_when_missing_#` is true (its default); `in`, `get`,
|
|
114
|
+
`setdefault` and `pop` behave like a dict.
|
|
115
|
+
- **`#` keys** are stored (`<#>key`). Keys of the form `#_name_#` are marker
|
|
116
|
+
writes: `t['#_defaults_#'] = [...]` sets the defaults, and so does
|
|
117
|
+
`setDefaults([...])` once the object is constructed.
|
|
118
|
+
|
|
119
|
+
## Command line
|
|
120
|
+
|
|
121
|
+
`tsvz` follows [tsvz-spec-v1 §20](tsvz-spec-v1.md#20-command-line-interface):
|
|
122
|
+
`tsvz [OPTION ...] OPERATION STORE [ARG ...]`, with options anywhere before a
|
|
123
|
+
lone `--`. The 3.39 form `tsvz STORE [OPERATION] [ARG ...]` works too; a store
|
|
124
|
+
named like an operation then needs a path prefix (`./read`).
|
|
125
|
+
|
|
126
|
+
- **stdout carries only rows.** It gets a table on a terminal and records
|
|
127
|
+
otherwise; `--format table|records` overrides. Records use the store's
|
|
128
|
+
delimiter and escaping, so `tsvz read A | tsvz set B -` copies a store of
|
|
129
|
+
the same variant.
|
|
130
|
+
- **stderr carries every message.** `-q` prints errors only; `-v` adds detail.
|
|
131
|
+
- **Exit status:**
|
|
132
|
+
- 0: done.
|
|
133
|
+
- 1: failed or refused. Also a missing store for `read`, `get`, `scrub`, `verify` and `parts`,
|
|
134
|
+
a store that `set`, `append` or `delete` could not create, and a `.tsvz` part that could not
|
|
135
|
+
be read (the rows of the other parts are still printed).
|
|
136
|
+
- 2: usage error.
|
|
137
|
+
- 3: `get` found a missing key.
|
|
138
|
+
- 4: `verify` found a checksum mismatch.
|
|
139
|
+
- **Arguments are literal.** `tsvz set s.tsvz k 'a<b'` stores `a<b`. Values may
|
|
140
|
+
begin with `-` (`-5`); after `--` every argument is positional. `set KEY`
|
|
141
|
+
alone deletes KEY, as in 3.39. `set KEY ''` stores an empty cell in a `.tsvz`
|
|
142
|
+
store; in a `.tsv` file it deletes KEY, as in 3.39.
|
|
143
|
+
- **Bulk input.** `set STORE -` and `delete STORE -` read records or keys from
|
|
144
|
+
stdin and append them in one write. An unterminated last line is ignored,
|
|
145
|
+
with a warning.
|
|
146
|
+
- **TSVZ extensions:**
|
|
147
|
+
- `--x-header H` and `--x-defaults KEY<d>D1<d>...` decode backslash escapes such as `\t`;
|
|
148
|
+
- `--x-strict` / `--x-force` turn 3.39's column and header checks on or off.
|
|
149
|
+
|
|
150
|
+
## Serving a store
|
|
151
|
+
|
|
152
|
+
`tsvz serve STORE` keeps a store loaded in one process and answers requests on a
|
|
153
|
+
local socket ([tsvz-spec-v1 §21](tsvz-spec-v1.md#21-write-handler-protocol)).
|
|
154
|
+
`tsvz` commands and `TSVZ.TSVZClient` find it through the pointer file
|
|
155
|
+
`STORE.serve` and use it instead of re-reading the store. Their output and exit
|
|
156
|
+
status stay the same.
|
|
157
|
+
|
|
158
|
+
- **One appender.** The server batches the writes it receives into single appends.
|
|
159
|
+
Other writers may still append to the files; the server reads what they add
|
|
160
|
+
before it answers.
|
|
161
|
+
- **Acknowledgement.** A write is acknowledged once it is queued (`#_write_ack_#`
|
|
162
|
+
`memory`, the default), or after `fsync` (`#_write_ack_#` `disk`, or
|
|
163
|
+
`TSVZClient(..., sync=True)`). Reads always see acknowledged writes.
|
|
164
|
+
- **Access.** Only the user who started the server can connect (socket mode 0600).
|
|
165
|
+
`--x-group G` or `--x-mode 0660` opens it on purpose. The server listens on a
|
|
166
|
+
Unix-domain socket only; reaching it from other hosts is left to a separate
|
|
167
|
+
adapter (for example TCP with TLS) that would relay to the socket.
|
|
168
|
+
- **Trust.** Clients use `STORE.serve` only when it is a regular file owned by
|
|
169
|
+
them or by the store's owner, not writable by anyone else, naming a socket that
|
|
170
|
+
belongs to that owner. Otherwise they warn and use the
|
|
171
|
+
files. `serve` never writes through a symbolic link or into a file it did not
|
|
172
|
+
create.
|
|
173
|
+
- **Lifetime.** It runs in the foreground until `tsvz stop STORE`, SIGINT or
|
|
174
|
+
SIGTERM, or `--x-idle-timeout S` seconds without connections. It then writes
|
|
175
|
+
and `fsync`s everything and removes `STORE.serve`.
|
|
176
|
+
- **Bypassing it.** `--x-direct` makes a `tsvz` command use the files. Command
|
|
177
|
+
lines with `--x-header`, `--x-defaults` or `--x-strict`, or another delimiter
|
|
178
|
+
than the server's, use the files too.
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
import TSVZ
|
|
182
|
+
|
|
183
|
+
with TSVZ.TSVZClient('people.tsvz') as people: # served, or in this process when no server runs
|
|
184
|
+
people['dave'] = ['dave', 'Dave', '41']
|
|
185
|
+
print(people['dave'], len(people), 'alice' in people)
|
|
186
|
+
print(people.pop('dave'))
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`TSVZClient` has `TSVZed`'s semantics for reading and setting keys, `del`, `in`,
|
|
190
|
+
`len`, iteration, `pop`, `popitem`, `setdefault`, `update`, `clear` and
|
|
191
|
+
`setDefaults`. Its writes return once they are written (raising `OSError` if they
|
|
192
|
+
cannot be), one client may be shared between threads, and without a server it
|
|
193
|
+
warns once and works on the files in-process, finishing its writes when the
|
|
194
|
+
program exits.
|
|
195
|
+
|
|
196
|
+
## Fault tolerance
|
|
197
|
+
|
|
198
|
+
TSVZ keeps your data and keeps going when a file is not what it expects, and
|
|
199
|
+
says so on stderr (or through `teeLogger`) with one summarised line per problem:
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
TSVZ warning: data.tsvz: invalid UTF-8 replaced with U+FFFD (3 occurrences, first at line 17)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Examples: bytes after the last newline (ignored on read; truncated before the
|
|
206
|
+
next append, and quoted), invalid UTF-8, a byte order mark, a newer
|
|
207
|
+
`#_version_#`, a marker with a bad value, a checksum mismatch, a damaged
|
|
208
|
+
compressed member or a zero-filled line (a member cut short by a crash keeps
|
|
209
|
+
the whole lines it decoded; a corrupt member or a zero-filled line is
|
|
210
|
+
recovered, skipped or stops the read, as the store's `#_on_corruption_#` says,
|
|
211
|
+
`recover` by default, spec §4.9, §4.10, §16.6; a scrub that rewrites such a
|
|
212
|
+
part keeps the original as `<part>.damaged-<time>`), an unreadable part, and a
|
|
213
|
+
delimiter or encoding argument that conflicts with the extension.
|
|
214
|
+
|
|
215
|
+
## Breaking changes from 3.39
|
|
216
|
+
|
|
217
|
+
### `.tsv`, `.csv`, `.nsv`, `.psv`: bug fixes only
|
|
218
|
+
|
|
219
|
+
| # | Change |
|
|
220
|
+
|---|---|
|
|
221
|
+
| L1 | Appending after an unterminated last line writes `\n` first; 3.39 merged the two lines. |
|
|
222
|
+
| L2 | Setting a row whose values are all empty deletes it in memory too (it already meant delete on disk), with a warning. |
|
|
223
|
+
| L3 | `TSVZedLite` on a compressed file keeps rows in memory; 3.39 read compressed bytes as text. Rows held in memory (also `#` keys) are returned directly. |
|
|
224
|
+
| L4 | New warnings on stderr (invalid encoding, lines dropped by `strict`); data unchanged. |
|
|
225
|
+
| L5 | A damaged compressed file is read up to the damage instead of raising. |
|
|
226
|
+
| L6 | `mapToFile` no longer glues a row it adds at the end of the file to the next one. |
|
|
227
|
+
|
|
228
|
+
### `.tsvz`, `.csvz`, `.nsvz`, `.psvz`: now tsvz-spec-v1
|
|
229
|
+
|
|
230
|
+
| # | Change |
|
|
231
|
+
|---|---|
|
|
232
|
+
| S1 | `.csvz/.nsvz/.psvz` use `, \0 \|` (3.39: tab). A conflicting `delimiter=` or `encoding=` is overridden, with a warning. |
|
|
233
|
+
| S2 | A file written by 3.39 under one of these names reads differently (header line becomes data, `key\t\t` deletes come back, `</sep/>` stays literal, an unterminated last line is dropped, single-column files read as empty, empty cells are not defaulted, NULs are not stripped). **Rename such a file to `.tsv`.** |
|
|
234
|
+
| S3 | `t[missing]` returns the defaults row instead of raising `KeyError` (see above). |
|
|
235
|
+
| S4 | `#` keys are stored; `#_name_#` keys are marker writes. |
|
|
236
|
+
| S5 | Automatic rewrites are ignored with a warning; `rewrite_on_load` effectively defaults to `False`; `move_to_end` only reorders memory. |
|
|
237
|
+
| S6 | Rows are never trimmed or dropped for their width; `strict` no longer drops rows; `appendLinesTabularFile` does not pad. |
|
|
238
|
+
| S7 | The header is written as a `#` comment; a new file without a header is empty. |
|
|
239
|
+
| S8 | An unterminated tail is ignored on read and truncated on append. |
|
|
240
|
+
| S9 | `clear` keeps the active markers; scrub keeps the header comment and drops other comments, custom markers and checksums. |
|
|
241
|
+
| S10 | Files named `x.tsvz.<hex>` beside `x.tsvz` are read as parts of the store. |
|
|
242
|
+
| S11 | `lastLineOnly` reads forward (slower on large files). |
|
|
243
|
+
| S12 | `TSVZedLite.clear()` and `clear_file()` raise `OSError` when the clear is refused (a part could not be read, the path names one part of a store, or the store kept changing). Memory is reloaded from the unchanged store first. |
|
|
244
|
+
| S13 | Compressed parts are written in members of at most 1 MiB of records (`#_member_size_#`), Zstandard frames with a checksum; an append never reads or repairs the part. |
|
|
245
|
+
| S14 | A zero byte in a value is written `<NUL>`, and a raw zero byte in a record is read as zero-fill damage (spec §4.9): under the default `recover` only the bytes after it are read. **A value that 4.2 or 4.3 wrote with a raw zero byte therefore reads differently in 4.4.** Such values are rare; before upgrading, `grep -c -P '\x00' store.tsvz` (any `.tsvz`, `.csvz` or `.psvz` part) finds them. Stop 4.2 and 4.3 handlers before upgrading: they read `<NUL>` as text, as they read `<CR>`. |
|
|
246
|
+
|
|
247
|
+
### The `tsvz` command
|
|
248
|
+
|
|
249
|
+
3.39 command lines still parse, including abbreviated long options (`--delim`)
|
|
250
|
+
and `-d=,`, except the ones C5 and C9 list.
|
|
251
|
+
|
|
252
|
+
| # | Change |
|
|
253
|
+
|---|---|
|
|
254
|
+
| C1 | `read` into a pipe or a file prints records instead of the table; `--format table` gives the table. |
|
|
255
|
+
| C2 | Every message goes to stderr: "Created …", "File not found", header mismatches, warnings. |
|
|
256
|
+
| C3 | `read` and `scrub` of a missing store exit 1 with a message (3.39: exit 0, and `read` printed "File not found" on stdout). |
|
|
257
|
+
| C4 | `delete` deletes every KEY it is given (3.39: only the first). |
|
|
258
|
+
| C5 | `append` or `delete` without a KEY, or with an empty KEY, is a usage error with exit 2 (3.39 wrote an empty line). |
|
|
259
|
+
| C6 | A `clear` or `scrub` that writes nothing (one part of a multi-part store, a store that kept changing) exits 1. |
|
|
260
|
+
| C7 | `-c/--header`, `--defaults`, `-s/--strict` and `-f/--force` are undocumented aliases of the `--x-` options. `--x-header` and `--x-defaults` keep non-ASCII text. |
|
|
261
|
+
| C8 | Usage errors print tsvz's own message, exit 2; the `-h` text is new. |
|
|
262
|
+
| C9 | Arguments after STORE for `read`, `clear` and `scrub` are a usage error, exit 2 (3.39 ignored them, so `clear junk` cleared the file). |
|
|
263
|
+
| C10 | The table (a terminal, or `--format table`) of `get`, `keys`, `pop`, `popitem`, `setdefault`, and of `read` on a `.tsvz`-family store, shows the rows only: no row is drawn as a header, and no row is cut to the first row's or the terminal's width. `read` of a `.tsv`-family file keeps 3.39's table, whose first row is the file's header line. |
|
|
264
|
+
| C11 | The table shows control characters, bidi controls and line separators as escapes (`\r`, `\n`, `\t`, `\xNN`, `\uNNNN`), so no value can move the cursor, reorder the line or restyle the terminal. It sizes columns in terminal columns, so CJK text (two columns a character) lines up. The table is for reading: a backslash in a value is shown as is, so use records for exact values. |
|
|
265
|
+
|
|
266
|
+
## Spec deviations and interpretations
|
|
267
|
+
|
|
268
|
+
None of these fails a conformance case: `conformance/reference-baseline.json`,
|
|
269
|
+
which lists the cases this implementation does not pass, is empty (see
|
|
270
|
+
Conformance testing).
|
|
271
|
+
|
|
272
|
+
- §17.5: a numbered part opened directly is read alone, with a warning.
|
|
273
|
+
- §14.5: the defaults-on-missing read applies to `t[key]` only.
|
|
274
|
+
- §18.6: locking is one exclusive whole-file lock per write plus an in-process lock; readers do not lock. On Linux it is an open file description lock, which still excludes 3.39's `lockf`; elsewhere it is `lockf`.
|
|
275
|
+
- §19.4: a scrub whose rows would read differently under the final markers writes them with stripping and fill-empty off, then restores the final markers after the rows.
|
|
276
|
+
- §20.7: `-q` also silences informational messages ("Created …"); only errors remain.
|
|
277
|
+
- §20.2: `scrub` of a multi-part store, and `clear` of one part of one, are refused with exit 1; `read`, `get` and `parts` of a numbered part read that part alone, with a warning.
|
|
278
|
+
- §20.2.4: on `.tsv`-family files `get` prints nothing for a missing key, `verify` checks nothing, and `parts` lists the file.
|
|
279
|
+
- §20.3: a bulk input line with an empty key is skipped, with a warning.
|
|
280
|
+
- §20.6: a write whose store cannot be created, and a `read`, `get` or `verify` that cannot read every part, exit 1 with an error that `-q` keeps.
|
|
281
|
+
- §21: a served `.tsv` file with a header line shows that line as a row, as `tsvz read` does (`TSVZed` hides it when given the header).
|
|
282
|
+
- §21.8: the server notices changes with `stat()`. It re-reads the part list while the store's directory changed less than 2 s ago, and reloads a part whose size stayed the same but whose inode or modification time changed. A rewrite in place that keeps a part's size and modification time is not seen until the part changes again.
|
|
283
|
+
- §21.9: `tsvz` commands and `TSVZClient` ask the server to answer writes once they are written (`--x-written`), so a write the server cannot make fails as it would on the files. While writes fail, every answer carries a warning.
|
|
284
|
+
- §21.3: `tsvz serve` needs Unix-domain sockets. Where Python has none (Windows), it exits 1, and `tsvz` commands and `TSVZClient` work on the files.
|
|
285
|
+
- §20.7: on `.tsv`-family files a `-d` longer than one character is used, as in 3.39, with a warning; a `-d` that does not decode is a usage error.
|
|
286
|
+
|
|
287
|
+
## Website
|
|
288
|
+
|
|
289
|
+
`website/` holds the tsvz.org site. `website/tsvz_site.py` (standard library
|
|
290
|
+
only, Python 3.8+) renders `website/index.md`, `website/spec-glance.md` and
|
|
291
|
+
`tsvz-spec-v1.md`, and serves HTML to browsers and markdown to agents.
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
python3 website/tsvz_site.py serve -p 8765 # http://127.0.0.1:8765/
|
|
295
|
+
python3 website/tsvz_site.py build /tmp/site # static files for any host
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
`deploy/tsvz-site.service` runs it under systemd; restart it to publish an edit.
|
|
299
|
+
|
|
300
|
+
## Conformance testing
|
|
301
|
+
|
|
302
|
+
`tsvzconform.py` tests how closely an implementation of tsvz-spec-v1, in any
|
|
303
|
+
language, follows the spec. Its cases are JSON files in `conformance/cases/`,
|
|
304
|
+
each naming the § clauses it tests, and it drives an implementation only
|
|
305
|
+
through the spec's command line (§20) and handler socket (§21).
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
python3 tsvzconform.py --impl ./tsvz --feature crc32,sha256,handler
|
|
309
|
+
python3 tsvzconform.py --impl ./tsvz --class reader --only 13
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Each failure is printed with its input, what was expected, what came back and
|
|
313
|
+
a command line that reruns it alone; the summary gives a verdict for each
|
|
314
|
+
conformance class (reader, writer, cli, handler). `conformance/README.md`
|
|
315
|
+
explains the options and the features to claim, and how to load the cases into
|
|
316
|
+
another language's tests.
|
|
317
|
+
|
|
318
|
+
## Benchmarking implementations
|
|
319
|
+
|
|
320
|
+
`tsvzbench.py` compares implementations of tsvz-spec-v1, in any language, side
|
|
321
|
+
by side on one machine. It drives each one only through the spec's command
|
|
322
|
+
line (§20) and, when it has one, its handler socket (§21), so any conformant
|
|
323
|
+
`tsvz` tool can be measured as it is.
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
python3 tsvzbench.py --impl ref='python3 TSVZ.py' --impl new='../tsvz-rs/target/release/tsvz' \
|
|
327
|
+
--suite quick --work-dir /mnt/fast-disk/tsvzbench
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
- `--impl NAME=COMMAND`, repeatable: the first is the baseline, and every other
|
|
331
|
+
implementation gets a `×` ratio against it (below 1 is faster). A relative
|
|
332
|
+
COMMAND runs from the directory you start tsvzbench in.
|
|
333
|
+
- `--suite smoke|quick|standard|large`: `smoke` checks a setup in seconds,
|
|
334
|
+
`quick` takes about a minute, `standard` (the default, up to 1M rows) about
|
|
335
|
+
five, and `large` (up to 10M rows) twenty or more and ~5 GB of memory for the
|
|
336
|
+
harness itself.
|
|
337
|
+
- `--only cold,bulk,perop,maint` picks families: one-shot replays, `set -` and
|
|
338
|
+
`delete -` batches, single operations (CLI and handler), and scrub/clear.
|
|
339
|
+
- `--work-dir DIR` keeps the generated fixtures between runs. Put it on the disk
|
|
340
|
+
you mean to measure: not a tmpfs, and not a FUSE file system such as mergerfs
|
|
341
|
+
(the harness warns about those). The default is a `tsvzbench-*` directory here.
|
|
342
|
+
- `--json PATH` also writes every raw sample.
|
|
343
|
+
|
|
344
|
+
The harness writes its own input stores, so every implementation reads the same
|
|
345
|
+
bytes, and checks every result against its own replay model: `✓` right, `✗`
|
|
346
|
+
wrong output or files, `! N` an unexpected exit status, `⏱` timed out, `refused`
|
|
347
|
+
an allowed refusal. It exits 1 when any cell failed. Each command runs under a
|
|
348
|
+
small launcher that measures it with `wait4`; a peak RSS shown as `≤8.0 MiB` is
|
|
349
|
+
the launcher's own size, so the true peak is no higher. Compare an
|
|
350
|
+
implementation with itself to see the noise: a few percent per cell, but the
|
|
351
|
+
socket rows, whose latencies are tens of microseconds, can move 10–20% between
|
|
352
|
+
runs, because a single run can land in a slower mode as a whole.
|
|
353
|
+
|
|
354
|
+
## Tests
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
python3 -m pytest TSVZ_test.py -q # everything, incl. 3.39 differential tests
|
|
358
|
+
python3 -m pytest website/tsvz_site_test.py -q # the tsvz.org site
|
|
359
|
+
python3 -m pytest tsvzbench_test.py -q # the benchmark harness
|
|
360
|
+
python3 -m pytest tsvzconform_test.py -q # the conformance runner and cases, against the reference
|
|
361
|
+
python3 benchTSVZ.py /tmp/b.tsv -n 100000 --compare-old # benchmark (not a test)
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
`TSVZ_old.py` is the frozen 3.39 module the differential tests compare against;
|
|
365
|
+
it is not installed.
|
|
366
|
+
|
|
367
|
+
## License
|
|
368
|
+
|
|
369
|
+
GPL-3.0-or-later © Yufei Pan (pan@zopyr.us)
|