TSVZ 5.0__tar.gz → 5.2__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-5.0 → tsvz-5.2}/PKG-INFO +214 -9
- {tsvz-5.0 → tsvz-5.2}/README.md +213 -8
- {tsvz-5.0 → tsvz-5.2}/TSVZ.egg-info/PKG-INFO +214 -9
- {tsvz-5.0 → tsvz-5.2}/TSVZ.py +388 -104
- {tsvz-5.0 → tsvz-5.2}/LICENSE +0 -0
- {tsvz-5.0 → tsvz-5.2}/TSVZ.egg-info/SOURCES.txt +0 -0
- {tsvz-5.0 → tsvz-5.2}/TSVZ.egg-info/dependency_links.txt +0 -0
- {tsvz-5.0 → tsvz-5.2}/TSVZ.egg-info/entry_points.txt +0 -0
- {tsvz-5.0 → tsvz-5.2}/TSVZ.egg-info/requires.txt +0 -0
- {tsvz-5.0 → tsvz-5.2}/TSVZ.egg-info/top_level.txt +0 -0
- {tsvz-5.0 → tsvz-5.2}/setup.cfg +0 -0
- {tsvz-5.0 → tsvz-5.2}/setup.py +0 -0
{tsvz-5.0 → tsvz-5.2}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: TSVZ
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.2
|
|
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/tsvz-org/TSVZ
|
|
6
6
|
Author: Yufei Pan
|
|
@@ -39,7 +39,7 @@ key-value store in a delimiter-separated text file**. The first column of each
|
|
|
39
39
|
row is the key; the file behaves like an ordered dictionary that is persisted to
|
|
40
40
|
disk as you change it.
|
|
41
41
|
|
|
42
|
-
**Version 5.
|
|
42
|
+
**Version 5.1**, Python **3.6+**, one self-contained module (`TSVZ.py`): drop it
|
|
43
43
|
next to your code and `import TSVZ`, or install it.
|
|
44
44
|
|
|
45
45
|
```bash
|
|
@@ -163,6 +163,20 @@ named like an operation then needs a path prefix (`./read`).
|
|
|
163
163
|
with a warning.
|
|
164
164
|
- **TSVZ extensions:**
|
|
165
165
|
- `--x-header H` and `--x-defaults KEY<d>D1<d>...` decode backslash escapes such as `\t`;
|
|
166
|
+
- `--x-header H` makes line 1 of a `.tsv`-family file its header for every operation
|
|
167
|
+
when line 1 is H: the same number of cells, each equal to H's once the whitespace
|
|
168
|
+
around it is taken off (a trailing empty cell counts: `id\tv\t` is not `id\tv`; a UTF-8
|
|
169
|
+
byte order mark before line 1, as Excel writes one, is skipped). It
|
|
170
|
+
is then never a key, row or count, `scrub` and `clear` keep it, and
|
|
171
|
+
`read --format table` shows it above the rows. When line 1 differs, the file has no
|
|
172
|
+
header and line 1 is a row, with 3.39's "Header mismatch" warning (`--x-strict`: an
|
|
173
|
+
error, exit 1, nothing written). A missing or empty file gets H first. Without
|
|
174
|
+
`--x-header`, line 1 is a row, as in 3.39. (The library's own header check, a
|
|
175
|
+
`header=` given to `readTabularFile`, `appendTabularFile`, `clearTabularFile`,
|
|
176
|
+
`scrubTabularFile` or `TSVZed`, is 3.39's, looser one: each cell of H within the
|
|
177
|
+
cell of line 1 under it, so `idaho\tvalue` passes for `id\tv`.) A rewrite without
|
|
178
|
+
`--x-header` can put a row equal to H on line 1
|
|
179
|
+
([limitations](docs/limitations.md#a-rewrite-without---x-header-can-put-a-row-equal-to-h-on-line-1)).
|
|
166
180
|
- `--x-strict` / `--x-force` turn 3.39's column and header checks on or off.
|
|
167
181
|
|
|
168
182
|
## Serving a store
|
|
@@ -193,7 +207,13 @@ status stay the same.
|
|
|
193
207
|
and `fsync`s everything and removes `STORE.serve`.
|
|
194
208
|
- **Bypassing it.** `--x-direct` makes a `tsvz` command use the files. Command
|
|
195
209
|
lines with `--x-header`, `--x-defaults` or `--x-strict`, or another delimiter
|
|
196
|
-
than the server's, use the files too.
|
|
210
|
+
than the server's, use the files too. A server started with `--x-header H`
|
|
211
|
+
answers every request by the `--x-header` rule above, and one started with
|
|
212
|
+
`--x-strict` too answers as a direct `--x-strict` command does: a line 1 that is
|
|
213
|
+
not H fails the request (status 1, nothing written), and reads and scrubs drop
|
|
214
|
+
the rows of another width. It does not start when line 1 is not H then. The
|
|
215
|
+
table of a routed `read --format table` shows no H line: its first row is set
|
|
216
|
+
as the table's header, as with no `--x-header`.
|
|
197
217
|
|
|
198
218
|
```python
|
|
199
219
|
import TSVZ
|
|
@@ -261,7 +281,9 @@ microseconds as a write starts is followed as a rotation instead); while that re
|
|
|
261
281
|
fails or is refused, the rows are appended to a new file there, and memory keeps all of
|
|
262
282
|
them for the rewrite that gets through. A compressed file another writer left damaged
|
|
263
283
|
is made again too (L5: every read stops at the damage, so rows
|
|
264
|
-
appended after it would be read by no one)
|
|
284
|
+
appended after it would be read by no one): what the read did not get goes, the bytes after
|
|
285
|
+
the damage and also the lines of the damaged member before it that the codec never handed
|
|
286
|
+
over (Python's gzip gives none of a first member cut short, where `zcat` shows them). While that
|
|
265
287
|
rewrite fails, the rows are appended after the damage and memory keeps them for the
|
|
266
288
|
next rewrite that gets through; while it is refused (a row changed in place to text
|
|
267
289
|
the encoding cannot hold), they are appended after it as in 4.9, and the next read
|
|
@@ -301,12 +323,184 @@ empty; the keys it sets or deletes then take its values, and the keys it left ou
|
|
|
301
323
|
memory's. An explicit `reload()` while the file is damaged replaces memory with the
|
|
302
324
|
damaged file, as asked, and warns.
|
|
303
325
|
|
|
326
|
+
## Upgrading to 5.2 from 5.1
|
|
327
|
+
|
|
328
|
+
The file formats are unchanged, and a 3.39 host still reads every `.tsv`-family file
|
|
329
|
+
5.2 writes.
|
|
330
|
+
|
|
331
|
+
- **A scrub of a `.tsv` whose rows were all deleted shrinks it.** `scrubTabularFile`,
|
|
332
|
+
`tsvz scrub` (also through a `tsvz serve` handler) of a `.tsv`-family file (`.tsv`,
|
|
333
|
+
`.csv`, `.nsv`, `.psv` and their compressed forms) in which every key has been deleted
|
|
334
|
+
leaves the file as an empty store: the header line it has, and its `#_defaults_#`
|
|
335
|
+
lines as a scrub of a file with rows keeps them (an empty one after the header, the
|
|
336
|
+
last non-empty one after it, both as wide as the header, or as the column count the
|
|
337
|
+
read took, which caller defaults set when given). A file with no header line keeps its
|
|
338
|
+
empty first line, or becomes 0 bytes when its first line was a row; a first line that
|
|
339
|
+
is no row (a comment, say) becomes an empty line. A defaults line it keeps follows an
|
|
340
|
+
empty first line, which the scrub then writes. Comment lines go, as a scrub of a file
|
|
341
|
+
with rows already drops them. A scrub that is given
|
|
342
|
+
a header (and checks it, the default) keeps the file's first line as its header, an
|
|
343
|
+
empty one included, with a "Header mismatch" warning when it differs; the header it is
|
|
344
|
+
given is written only with `verifyHeader=False`, or when the file's first line cannot be
|
|
345
|
+
decoded (plain text named `.gz`); `tsvz scrub --x-header` follows the `--x-header` rule
|
|
346
|
+
below instead. A scrub of a path that is not there
|
|
347
|
+
makes no file and returns `{}`, as in 5.1, unless `createIfNotExist=True`. A compressed
|
|
348
|
+
file whose read decodes no row (plain text named `.gz`, a first member cut short) is
|
|
349
|
+
made again as an empty store, as a scrub of a damaged file with rows already was in
|
|
350
|
+
5.1: what the read did not get goes, the bytes after the damage and also the lines of the
|
|
351
|
+
damaged member before it that the codec never handed over (Python's gzip gives none of
|
|
352
|
+
a first member cut short, where `zcat` shows them) (see the damaged-stream notes above), and
|
|
353
|
+
`tsvz clear --x-header H --x-strict` (`clearTabularFile(verifyHeader=True)`) now clears
|
|
354
|
+
such a file, where 5.1 exited 1 with the codec's error.
|
|
355
|
+
5.1 and 5.0 left such a file as it was, deletion lines included, so a writer that
|
|
356
|
+
appended keys, deleted them all and scrubbed, over and over, saw the file grow without
|
|
357
|
+
limit (205, 405, 605 bytes, ... for 20 keys a cycle); now it is the same size after
|
|
358
|
+
every scrub. A file with no byte (or a compressed one with only empty streams) is
|
|
359
|
+
still left alone, and so is a file whose strict scrub (`strict=True`, `--x-strict`) read
|
|
360
|
+
dropped a line for its width and found no row: that line is not a deletion, as 5.1 left
|
|
361
|
+
it. A scrub that finds the file changed since its read still reads
|
|
362
|
+
again and writes nothing if it keeps changing, as for a file with rows.
|
|
363
|
+
- **`get_file_obj('ab')` on a compressed store writes its trailer before the lock is let
|
|
364
|
+
go.** A program that calls it on a `.tsv.gz`, `.bz2`, `.xz` or `.zst` store and writes
|
|
365
|
+
through the file now has all of it, the gzip trailer and the bz2, xz and zstd data included,
|
|
366
|
+
in the file when `release_file_obj()` unlocks it. 5.1 and earlier wrote them after the
|
|
367
|
+
unlock, where another writer could append in between and corrupt the stream.
|
|
368
|
+
- **A `TSVZed` no longer lets a file grow with every session of sets and deletes.** It
|
|
369
|
+
rewrites a `.tsv`-family file at open when the file holds a key's row and the deletion
|
|
370
|
+
of it, as 3.39 did only for a key with two values, so a program that sets keys and
|
|
371
|
+
deletes them in each session (default settings, or `rewrite_interval`) keeps the file
|
|
372
|
+
the same size (205, 405, 605 bytes, ... for 20 keys a session before). Every session
|
|
373
|
+
that opens such a file now rewrites it once. With `rewrite_on_load=False` the open
|
|
374
|
+
does not, and the next exit or periodic rewrite does it (`rewrite_on_exit=True`,
|
|
375
|
+
`rewrite_interval`). What such a rewrite does, it now does at a plain open:
|
|
376
|
+
- it cuts a row wider than the header, and the cut stays
|
|
377
|
+
([limitations](docs/limitations.md#scrubs-and-rewrites-read-rows-at-one-column-width-deferred-to-the-width-design)):
|
|
378
|
+
`id\tv\na\t1\t2\nb\t1\nb\t\n` becomes `id\tv\na\t1 \n`, and
|
|
379
|
+
`id\tv\na\t\t7\nb\t1\nb\t\n` becomes `id\tv\na\t \n`, the 7 lost;
|
|
380
|
+
- it writes over the file in place and pads with spaces (`a\t1 ` above): TSVZ readers
|
|
381
|
+
strip them, tools that read the raw bytes (`awk`, `cut`) see them;
|
|
382
|
+
- on a file it cannot write (read-only), it prints `Failed to open file … Permission
|
|
383
|
+
denied` twice and leaves the file as it is, as 5.1 did for a key with two values.
|
|
384
|
+
- **`--x-header H` makes line 1 the header for every `tsvz` operation.** On a
|
|
385
|
+
`.tsv`-family file whose line 1 is H (an exact match: the same cells, each without the
|
|
386
|
+
whitespace around it, a trailing empty cell included), line 1 is the header: `keys`,
|
|
387
|
+
`len`, `read`, `get`, `has`, `pop`, `popitem` and `delete` never list, count, print,
|
|
388
|
+
pop or delete it, and `scrub` and `clear` keep it. When line 1 differs, the file has no
|
|
389
|
+
header and line 1 is a row, with 3.39's "Header mismatch" warning, for the reads and
|
|
390
|
+
the scrub alike, so a scrub keeps the rows a read shows (but for the column-width cases
|
|
391
|
+
below), and in such a file it never writes a row that is H as line 1: it puts an empty
|
|
392
|
+
line first (`a\t1\nid\tv\na\t\n` becomes `\nid\tv\n`), so the reads still take that row
|
|
393
|
+
for a row. With `--x-strict` a line 1 that is not H is an error (exit 1, nothing
|
|
394
|
+
written) for every operation, through a handler too. A missing or empty file gets H
|
|
395
|
+
first at its first write, as in 5.1. A `tsvz serve STORE --x-header H` handler applies
|
|
396
|
+
the rule to every request it answers, routed `tsvz` commands included; with
|
|
397
|
+
`--x-strict` it is strict for them as above (5.1 ignored `--x-strict` at `serve`): such
|
|
398
|
+
a handler refuses to start on a file whose line 1 is not H, its routed `.tsv` reads and
|
|
399
|
+
scrubs drop lines of another column count, and its `.tsvz` reads are strict, as for a
|
|
400
|
+
direct `--x-strict` command. The library's own header check (`header=` to
|
|
401
|
+
`readTabularFile`, `appendTabularFile`, `clearTabularFile`, `scrubTabularFile`,
|
|
402
|
+
`TSVZed`) is unchanged, 3.39's containment check. The CLI's writes check line 1 more
|
|
403
|
+
strictly than 5.1's did: 5.1's `set`, `append`, `delete` and `clear` with `--x-header`
|
|
404
|
+
silently took a line 1 that merely held H's cells (`id\tvalue` for `id\tv`) for the
|
|
405
|
+
header and fitted rows to H's width; now they warn and write the row at its own width,
|
|
406
|
+
and with `--x-strict` exit 1. A rewrite without `--x-header` (a scrub without it,
|
|
407
|
+
`scrubTabularFile`, a `TSVZed` rewrite) can still put a row equal to H on line 1 of a
|
|
408
|
+
header-less file
|
|
409
|
+
([limitations](docs/limitations.md#a-rewrite-without---x-header-can-put-a-row-equal-to-h-on-line-1)).
|
|
410
|
+
In 5.1 only `set`, `append`, `delete` and `clear` used `--x-header`: the reads and
|
|
411
|
+
`scrub` took line 1 as a row (`tsvz keys`
|
|
412
|
+
printed `id a b` for two keys, `len` said 3, `popitem first` popped the header), and
|
|
413
|
+
so did every request to a handler. Without `--x-header` nothing changed: line 1 is a
|
|
414
|
+
row, as in 5.1 and 3.39. In a `.tsvz` store `pop`, `popitem` and `setdefault` now check
|
|
415
|
+
`--x-header` as `set` does; nothing else changed there.
|
|
416
|
+
- **Known, mostly as in 5.1: a scrub, a rewrite or a read takes one column count for every row,
|
|
417
|
+
and can lose cells or keys.** A scrub given defaults narrower than the rows, a row wider
|
|
418
|
+
than the header, and a `.tsv` that begins with a bare `#_defaults_#` line (read without
|
|
419
|
+
defaults) are examples; the entry lists each, and which is new in 5.2
|
|
420
|
+
([limitations](docs/limitations.md#scrubs-and-rewrites-read-rows-at-one-column-width-deferred-to-the-width-design)).
|
|
421
|
+
A strict scrub of a file whose read dropped a line for its width and found no row leaves it,
|
|
422
|
+
so it grows again over append, delete-all and scrub cycles
|
|
423
|
+
([limitations](docs/limitations.md#a-strict-scrub-leaves-a-file-whose-read-dropped-a-line-for-its-width)).
|
|
424
|
+
Both wait for the column-width design after 5.2.
|
|
425
|
+
|
|
426
|
+
## Upgrading to 5.1 from 5.0
|
|
427
|
+
|
|
428
|
+
The file formats are unchanged, and a 3.39 host still reads every `.tsv`-family file
|
|
429
|
+
5.1 writes. Most of the changes are in scrub and clear.
|
|
430
|
+
|
|
431
|
+
- **Scrub and clear of a `.tsv` write over the file.** `scrubTabularFile`,
|
|
432
|
+
`clearTabularFile`, `tsvz scrub` and `tsvz clear` (on the command line or through a
|
|
433
|
+
`tsvz serve` handler), `TSVZClient.clear()` and `TSVZedLite.clear_file()` of a
|
|
434
|
+
`.tsv`-family file write the new content over the file and then cut it, as
|
|
435
|
+
`TSVZed`'s rewrites do, so a reader that takes no lock never meets the file emptied.
|
|
436
|
+
They take the file's lock, so they wait while another writer holds it. 5.0 emptied
|
|
437
|
+
the file first, with no lock (`TSVZedLite` truncated its own descriptor).
|
|
438
|
+
- **A scrub keeps another process's rows.** A scrub of a `.tsv` writes only if the
|
|
439
|
+
file is still what its read saw. Otherwise it reads again, up to three times, and if
|
|
440
|
+
the file kept changing it writes nothing and warns: `tsvz scrub` exits 1 (`scrub
|
|
441
|
+
refused; nothing was written`), as for a `.tsvz` (C6), and a handler answers refused.
|
|
442
|
+
`scrubTabularFile` still returns the rows it read. 5.0 wrote over a row appended
|
|
443
|
+
after its read.
|
|
444
|
+
- **`scrubTabularFile(lastLineOnly=True)` is a read.** It returns the last row and
|
|
445
|
+
writes nothing, as for a `.tsvz`. 5.0 and 3.39 wrote that row back as deletion lines
|
|
446
|
+
(`id\tv\na\t\n3\t\n`), and the store was gone.
|
|
447
|
+
- **A scrub keeps the file's `#_defaults_#` lines.** The defaults in effect at the end
|
|
448
|
+
of the read are written after the rows: the last non-empty defaults line, its empty
|
|
449
|
+
cells filled from the line before it as a reader fills them. An empty defaults line
|
|
450
|
+
that was read (it turns defaults off) is written before the rows, padded to their
|
|
451
|
+
width. Every row reads as it did to a reader given the scrub's defaults and
|
|
452
|
+
`strict`. Defaults a caller passes still fill the rows' empty cells, as in 5.0, but
|
|
453
|
+
are not written as a defaults line, and the caller's list is not changed. 5.0 and
|
|
454
|
+
3.39 dropped the lines, so rows appended later got no defaults.
|
|
455
|
+
- **The header stays on line 1.** A scrub given no header (`tsvz scrub` of a `.tsv`,
|
|
456
|
+
`scrubTabularFile(path)`) and a `TSVZed` given none, of a file
|
|
457
|
+
that has a header line, keep that line first, with a `TSVZed`'s defaults line right
|
|
458
|
+
after it. 5.0 and 3.39 put an empty line in front of it, so a reader given the header
|
|
459
|
+
read the header as a row. A header-less file keeps the empty first line it begins
|
|
460
|
+
with, and new files and clears given no header still get one, as in 5.0.
|
|
461
|
+
- **Interrupts.** A scrub or clear of either dialect keeps a copy of only the bytes it
|
|
462
|
+
overwrites, to put back if it is interrupted before the file is cut to its new
|
|
463
|
+
length; once the cut is made the rewrite is done and the new content stays. 5.0 kept
|
|
464
|
+
a copy of the whole `.tsvz` part and put it all back. (A scrub still reads the whole
|
|
465
|
+
store, and a `.tsvz` clear reads it too, to keep its markers.)
|
|
466
|
+
- **A compressed store cleared to nothing is one empty stream.** A `.tsvz.gz` (or
|
|
467
|
+
`.bz2`, `.xz`, `.zst`) cleared or scrubbed to nothing holds one empty compressed
|
|
468
|
+
stream, which `bzcat`, `xzcat` and Python's `bz2` and `lzma` can read; 5.0 left 0
|
|
469
|
+
bytes.
|
|
470
|
+
- **A header-checking clear of an empty file writes the header it was given.** A clear
|
|
471
|
+
that checks the header (`verifyHeader=True`, `tsvz clear --x-header H --x-strict`) of a
|
|
472
|
+
0-byte `.tsv`, or of a compressed file with no byte or only empty streams (another
|
|
473
|
+
tool's), takes it for an empty store with that header, as every reader does (L9), and
|
|
474
|
+
writes H without a "Header mismatch" warning. 5.0 wrote an empty line and warned of a
|
|
475
|
+
mismatch, so a strict `TSVZed` given H then refused the file, and raised on a 0-byte
|
|
476
|
+
`.bz2`, `.xz` or `.zst`.
|
|
477
|
+
- **`tsvz --help`** says what `clear` keeps: a `.tsvz` its header and markers, a
|
|
478
|
+
`.tsv` the `--x-header` it is given. (With `--x-strict` too, a `.tsv` clear keeps the
|
|
479
|
+
file's own header line.)
|
|
480
|
+
`TSVZed`'s message for a refused rewrite lists `hardMapToFile()` among the rewrites
|
|
481
|
+
a program asks for.
|
|
482
|
+
- **Known in 5.1, fixed in 5.2: a scrub of a `.tsv` whose rows were all deleted did not
|
|
483
|
+
compact it** when the scrub was given the file's header (`scrubTabularFile(path, header=H)`)
|
|
484
|
+
or the file had no header line: it left the file as it was, deletion lines included;
|
|
485
|
+
reads were right, as in 3.39 and 5.0. (`tsvz scrub` of a headed file read the header
|
|
486
|
+
line as a row, so it did compact it.)
|
|
487
|
+
- **Known: a scrub run with other defaults than the store's program can change how
|
|
488
|
+
early rows read.** In a `.tsv` with an empty `#_defaults_#` line and rows with empty
|
|
489
|
+
cells before any defaults line, the reset line the scrub writes before the rows keeps
|
|
490
|
+
those cells empty for a program that opens the store with its own defaults; run the
|
|
491
|
+
scrub with the defaults that program uses
|
|
492
|
+
([limitations](docs/limitations.md#a-scrub-run-with-other-defaults-than-the-stores-program-can-change-how-early-rows-read)).
|
|
493
|
+
- **Running programs keep 5.0** until they are restarted: long-running programs that
|
|
494
|
+
import TSVZ, and running `tsvz serve` handlers.
|
|
495
|
+
|
|
304
496
|
## Upgrading to 5.0 from 4.9
|
|
305
497
|
|
|
306
498
|
- **Writes refuse text they cannot encode.** A key, value, header, defaults line or
|
|
307
499
|
marker the file's encoding cannot hold (UTF-8 for `.tsvz`; `encoding=` for a
|
|
308
500
|
`.tsv`), such as the lone surrogate `os.fsdecode` gives for a non-UTF-8 file name,
|
|
309
|
-
raises `TSVZEncodeError` (a `ValueError`) and writes nothing; 4.9 stored
|
|
501
|
+
raises `TSVZEncodeError` (a `ValueError`) and writes nothing; 4.9 stored `?` (its
|
|
502
|
+
`tsvz` stored U+FFFD for a non-UTF-8 argument, and its `.tsvz` file functions raised
|
|
503
|
+
`UnicodeEncodeError`).
|
|
310
504
|
`errors='replace'` brings `?` back. Under a non-UTF-8 `encoding=`, a key read as
|
|
311
505
|
U+FFFD cannot be deleted or updated. `tsvz` exits 1 with the message. See Fault
|
|
312
506
|
tolerance.
|
|
@@ -324,8 +518,7 @@ damaged file, as asked, and warns.
|
|
|
324
518
|
merged into it (Fault tolerance).
|
|
325
519
|
- **`.tsvz` stores** see another process's append made right after their load, a
|
|
326
520
|
commit or a clear (4.9 missed it until the next change), keep the rows set while
|
|
327
|
-
they reload,
|
|
328
|
-
mismatch at the first read.
|
|
521
|
+
they reload, and raise a strict header mismatch at the first read.
|
|
329
522
|
- **`.tsv` files:** an append to an existing empty file writes the header first, and
|
|
330
523
|
reading one gives an empty store; see L8 to L14 for the rest.
|
|
331
524
|
- **`tsvz`** runs every operation through the engine `tsvz serve` uses, with the same
|
|
@@ -334,8 +527,15 @@ damaged file, as asked, and warns.
|
|
|
334
527
|
file. The 3.39 warning that a file name does not end in `.tsv` is printed only
|
|
335
528
|
when a command creates such a file, and a write that cannot be made reports the
|
|
336
529
|
operating system's error.
|
|
337
|
-
- **
|
|
338
|
-
|
|
530
|
+
- **Running programs keep 4.9.** Long-running programs that import TSVZ, and running
|
|
531
|
+
`tsvz serve` handlers, keep 4.9 until they are restarted. 5.0's `tsvz stop` cannot
|
|
532
|
+
stop a 4.9 handler of a `.tsv` (or other tab- or NUL-delimited) file: 4.9 wrote the
|
|
533
|
+
delimiter into the pointer file's `x-delimiter` as `<sep>` (a NUL as `<NUL>`), which
|
|
534
|
+
5.0 decodes to the TAB or NUL itself and refuses as a control character. Send that
|
|
535
|
+
handler SIGTERM instead: its pid is on the `pid` line of its pointer file
|
|
536
|
+
(`STORE.serve`), and it finishes its writes and exits.
|
|
537
|
+
- **macOS and other systems that lock with `lockf`:** as in 4.9, a read in another
|
|
538
|
+
thread of the process can still drop a write's lock
|
|
339
539
|
([docs/limitations.md](docs/limitations.md)).
|
|
340
540
|
|
|
341
541
|
## Breaking changes from 3.39
|
|
@@ -358,6 +558,11 @@ damaged file, as asked, and warns.
|
|
|
358
558
|
| L12 | A rewrite of a file whose header line has no newline writes the rows on lines of their own; 3.39 wrote the first one onto the header line. |
|
|
359
559
|
| L13 | A compressed file is rewritten in members of at most 1 MiB, as appends already were: the bytes differ, the rows read are the same, and 3.39 reads them. |
|
|
360
560
|
| L14 | `TSVZed` opening a compressed file in a codec this Python lacks (zstd before Python 3.14) raises `ImportError` instead of reading the compressed bytes as text. |
|
|
561
|
+
| L15 | `scrubTabularFile`, `clearTabularFile`, `tsvz scrub`, `tsvz clear` and `TSVZedLite.clear_file()` write the new content over the file and then cut it, under the file's lock; 3.39 emptied the file first, with no lock, so a reader could meet it empty. |
|
|
562
|
+
| L16 | A scrub writes only if the file is still what its read saw: it reads again up to three times, then writes nothing, with a warning (`tsvz scrub` exits 1, C6); 3.39 wrote over rows appended after its read. |
|
|
563
|
+
| L17 | `scrubTabularFile(lastLineOnly=True)` returns the last row and writes nothing; 3.39 wrote that row back as deletion lines, and the store was gone. |
|
|
564
|
+
| L18 | A scrub keeps the file's `#_defaults_#` lines (an empty one before the rows, the defaults in effect after them), so every row reads as it did; 3.39 dropped them, so rows appended later got no defaults. |
|
|
565
|
+
| L19 | A scrub given no header, and a `TSVZed` given none, keep a file's header line first; 3.39 put an empty line in front of it, and a reader given the header then read the header as a row. |
|
|
361
566
|
|
|
362
567
|
### `.tsvz`, `.csvz`, `.nsvz`, `.psvz`: now tsvz-spec-v1
|
|
363
568
|
|
{tsvz-5.0 → tsvz-5.2}/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 5.
|
|
10
|
+
**Version 5.1**, 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
|
|
@@ -131,6 +131,20 @@ named like an operation then needs a path prefix (`./read`).
|
|
|
131
131
|
with a warning.
|
|
132
132
|
- **TSVZ extensions:**
|
|
133
133
|
- `--x-header H` and `--x-defaults KEY<d>D1<d>...` decode backslash escapes such as `\t`;
|
|
134
|
+
- `--x-header H` makes line 1 of a `.tsv`-family file its header for every operation
|
|
135
|
+
when line 1 is H: the same number of cells, each equal to H's once the whitespace
|
|
136
|
+
around it is taken off (a trailing empty cell counts: `id\tv\t` is not `id\tv`; a UTF-8
|
|
137
|
+
byte order mark before line 1, as Excel writes one, is skipped). It
|
|
138
|
+
is then never a key, row or count, `scrub` and `clear` keep it, and
|
|
139
|
+
`read --format table` shows it above the rows. When line 1 differs, the file has no
|
|
140
|
+
header and line 1 is a row, with 3.39's "Header mismatch" warning (`--x-strict`: an
|
|
141
|
+
error, exit 1, nothing written). A missing or empty file gets H first. Without
|
|
142
|
+
`--x-header`, line 1 is a row, as in 3.39. (The library's own header check, a
|
|
143
|
+
`header=` given to `readTabularFile`, `appendTabularFile`, `clearTabularFile`,
|
|
144
|
+
`scrubTabularFile` or `TSVZed`, is 3.39's, looser one: each cell of H within the
|
|
145
|
+
cell of line 1 under it, so `idaho\tvalue` passes for `id\tv`.) A rewrite without
|
|
146
|
+
`--x-header` can put a row equal to H on line 1
|
|
147
|
+
([limitations](docs/limitations.md#a-rewrite-without---x-header-can-put-a-row-equal-to-h-on-line-1)).
|
|
134
148
|
- `--x-strict` / `--x-force` turn 3.39's column and header checks on or off.
|
|
135
149
|
|
|
136
150
|
## Serving a store
|
|
@@ -161,7 +175,13 @@ status stay the same.
|
|
|
161
175
|
and `fsync`s everything and removes `STORE.serve`.
|
|
162
176
|
- **Bypassing it.** `--x-direct` makes a `tsvz` command use the files. Command
|
|
163
177
|
lines with `--x-header`, `--x-defaults` or `--x-strict`, or another delimiter
|
|
164
|
-
than the server's, use the files too.
|
|
178
|
+
than the server's, use the files too. A server started with `--x-header H`
|
|
179
|
+
answers every request by the `--x-header` rule above, and one started with
|
|
180
|
+
`--x-strict` too answers as a direct `--x-strict` command does: a line 1 that is
|
|
181
|
+
not H fails the request (status 1, nothing written), and reads and scrubs drop
|
|
182
|
+
the rows of another width. It does not start when line 1 is not H then. The
|
|
183
|
+
table of a routed `read --format table` shows no H line: its first row is set
|
|
184
|
+
as the table's header, as with no `--x-header`.
|
|
165
185
|
|
|
166
186
|
```python
|
|
167
187
|
import TSVZ
|
|
@@ -229,7 +249,9 @@ microseconds as a write starts is followed as a rotation instead); while that re
|
|
|
229
249
|
fails or is refused, the rows are appended to a new file there, and memory keeps all of
|
|
230
250
|
them for the rewrite that gets through. A compressed file another writer left damaged
|
|
231
251
|
is made again too (L5: every read stops at the damage, so rows
|
|
232
|
-
appended after it would be read by no one)
|
|
252
|
+
appended after it would be read by no one): what the read did not get goes, the bytes after
|
|
253
|
+
the damage and also the lines of the damaged member before it that the codec never handed
|
|
254
|
+
over (Python's gzip gives none of a first member cut short, where `zcat` shows them). While that
|
|
233
255
|
rewrite fails, the rows are appended after the damage and memory keeps them for the
|
|
234
256
|
next rewrite that gets through; while it is refused (a row changed in place to text
|
|
235
257
|
the encoding cannot hold), they are appended after it as in 4.9, and the next read
|
|
@@ -269,12 +291,184 @@ empty; the keys it sets or deletes then take its values, and the keys it left ou
|
|
|
269
291
|
memory's. An explicit `reload()` while the file is damaged replaces memory with the
|
|
270
292
|
damaged file, as asked, and warns.
|
|
271
293
|
|
|
294
|
+
## Upgrading to 5.2 from 5.1
|
|
295
|
+
|
|
296
|
+
The file formats are unchanged, and a 3.39 host still reads every `.tsv`-family file
|
|
297
|
+
5.2 writes.
|
|
298
|
+
|
|
299
|
+
- **A scrub of a `.tsv` whose rows were all deleted shrinks it.** `scrubTabularFile`,
|
|
300
|
+
`tsvz scrub` (also through a `tsvz serve` handler) of a `.tsv`-family file (`.tsv`,
|
|
301
|
+
`.csv`, `.nsv`, `.psv` and their compressed forms) in which every key has been deleted
|
|
302
|
+
leaves the file as an empty store: the header line it has, and its `#_defaults_#`
|
|
303
|
+
lines as a scrub of a file with rows keeps them (an empty one after the header, the
|
|
304
|
+
last non-empty one after it, both as wide as the header, or as the column count the
|
|
305
|
+
read took, which caller defaults set when given). A file with no header line keeps its
|
|
306
|
+
empty first line, or becomes 0 bytes when its first line was a row; a first line that
|
|
307
|
+
is no row (a comment, say) becomes an empty line. A defaults line it keeps follows an
|
|
308
|
+
empty first line, which the scrub then writes. Comment lines go, as a scrub of a file
|
|
309
|
+
with rows already drops them. A scrub that is given
|
|
310
|
+
a header (and checks it, the default) keeps the file's first line as its header, an
|
|
311
|
+
empty one included, with a "Header mismatch" warning when it differs; the header it is
|
|
312
|
+
given is written only with `verifyHeader=False`, or when the file's first line cannot be
|
|
313
|
+
decoded (plain text named `.gz`); `tsvz scrub --x-header` follows the `--x-header` rule
|
|
314
|
+
below instead. A scrub of a path that is not there
|
|
315
|
+
makes no file and returns `{}`, as in 5.1, unless `createIfNotExist=True`. A compressed
|
|
316
|
+
file whose read decodes no row (plain text named `.gz`, a first member cut short) is
|
|
317
|
+
made again as an empty store, as a scrub of a damaged file with rows already was in
|
|
318
|
+
5.1: what the read did not get goes, the bytes after the damage and also the lines of the
|
|
319
|
+
damaged member before it that the codec never handed over (Python's gzip gives none of
|
|
320
|
+
a first member cut short, where `zcat` shows them) (see the damaged-stream notes above), and
|
|
321
|
+
`tsvz clear --x-header H --x-strict` (`clearTabularFile(verifyHeader=True)`) now clears
|
|
322
|
+
such a file, where 5.1 exited 1 with the codec's error.
|
|
323
|
+
5.1 and 5.0 left such a file as it was, deletion lines included, so a writer that
|
|
324
|
+
appended keys, deleted them all and scrubbed, over and over, saw the file grow without
|
|
325
|
+
limit (205, 405, 605 bytes, ... for 20 keys a cycle); now it is the same size after
|
|
326
|
+
every scrub. A file with no byte (or a compressed one with only empty streams) is
|
|
327
|
+
still left alone, and so is a file whose strict scrub (`strict=True`, `--x-strict`) read
|
|
328
|
+
dropped a line for its width and found no row: that line is not a deletion, as 5.1 left
|
|
329
|
+
it. A scrub that finds the file changed since its read still reads
|
|
330
|
+
again and writes nothing if it keeps changing, as for a file with rows.
|
|
331
|
+
- **`get_file_obj('ab')` on a compressed store writes its trailer before the lock is let
|
|
332
|
+
go.** A program that calls it on a `.tsv.gz`, `.bz2`, `.xz` or `.zst` store and writes
|
|
333
|
+
through the file now has all of it, the gzip trailer and the bz2, xz and zstd data included,
|
|
334
|
+
in the file when `release_file_obj()` unlocks it. 5.1 and earlier wrote them after the
|
|
335
|
+
unlock, where another writer could append in between and corrupt the stream.
|
|
336
|
+
- **A `TSVZed` no longer lets a file grow with every session of sets and deletes.** It
|
|
337
|
+
rewrites a `.tsv`-family file at open when the file holds a key's row and the deletion
|
|
338
|
+
of it, as 3.39 did only for a key with two values, so a program that sets keys and
|
|
339
|
+
deletes them in each session (default settings, or `rewrite_interval`) keeps the file
|
|
340
|
+
the same size (205, 405, 605 bytes, ... for 20 keys a session before). Every session
|
|
341
|
+
that opens such a file now rewrites it once. With `rewrite_on_load=False` the open
|
|
342
|
+
does not, and the next exit or periodic rewrite does it (`rewrite_on_exit=True`,
|
|
343
|
+
`rewrite_interval`). What such a rewrite does, it now does at a plain open:
|
|
344
|
+
- it cuts a row wider than the header, and the cut stays
|
|
345
|
+
([limitations](docs/limitations.md#scrubs-and-rewrites-read-rows-at-one-column-width-deferred-to-the-width-design)):
|
|
346
|
+
`id\tv\na\t1\t2\nb\t1\nb\t\n` becomes `id\tv\na\t1 \n`, and
|
|
347
|
+
`id\tv\na\t\t7\nb\t1\nb\t\n` becomes `id\tv\na\t \n`, the 7 lost;
|
|
348
|
+
- it writes over the file in place and pads with spaces (`a\t1 ` above): TSVZ readers
|
|
349
|
+
strip them, tools that read the raw bytes (`awk`, `cut`) see them;
|
|
350
|
+
- on a file it cannot write (read-only), it prints `Failed to open file … Permission
|
|
351
|
+
denied` twice and leaves the file as it is, as 5.1 did for a key with two values.
|
|
352
|
+
- **`--x-header H` makes line 1 the header for every `tsvz` operation.** On a
|
|
353
|
+
`.tsv`-family file whose line 1 is H (an exact match: the same cells, each without the
|
|
354
|
+
whitespace around it, a trailing empty cell included), line 1 is the header: `keys`,
|
|
355
|
+
`len`, `read`, `get`, `has`, `pop`, `popitem` and `delete` never list, count, print,
|
|
356
|
+
pop or delete it, and `scrub` and `clear` keep it. When line 1 differs, the file has no
|
|
357
|
+
header and line 1 is a row, with 3.39's "Header mismatch" warning, for the reads and
|
|
358
|
+
the scrub alike, so a scrub keeps the rows a read shows (but for the column-width cases
|
|
359
|
+
below), and in such a file it never writes a row that is H as line 1: it puts an empty
|
|
360
|
+
line first (`a\t1\nid\tv\na\t\n` becomes `\nid\tv\n`), so the reads still take that row
|
|
361
|
+
for a row. With `--x-strict` a line 1 that is not H is an error (exit 1, nothing
|
|
362
|
+
written) for every operation, through a handler too. A missing or empty file gets H
|
|
363
|
+
first at its first write, as in 5.1. A `tsvz serve STORE --x-header H` handler applies
|
|
364
|
+
the rule to every request it answers, routed `tsvz` commands included; with
|
|
365
|
+
`--x-strict` it is strict for them as above (5.1 ignored `--x-strict` at `serve`): such
|
|
366
|
+
a handler refuses to start on a file whose line 1 is not H, its routed `.tsv` reads and
|
|
367
|
+
scrubs drop lines of another column count, and its `.tsvz` reads are strict, as for a
|
|
368
|
+
direct `--x-strict` command. The library's own header check (`header=` to
|
|
369
|
+
`readTabularFile`, `appendTabularFile`, `clearTabularFile`, `scrubTabularFile`,
|
|
370
|
+
`TSVZed`) is unchanged, 3.39's containment check. The CLI's writes check line 1 more
|
|
371
|
+
strictly than 5.1's did: 5.1's `set`, `append`, `delete` and `clear` with `--x-header`
|
|
372
|
+
silently took a line 1 that merely held H's cells (`id\tvalue` for `id\tv`) for the
|
|
373
|
+
header and fitted rows to H's width; now they warn and write the row at its own width,
|
|
374
|
+
and with `--x-strict` exit 1. A rewrite without `--x-header` (a scrub without it,
|
|
375
|
+
`scrubTabularFile`, a `TSVZed` rewrite) can still put a row equal to H on line 1 of a
|
|
376
|
+
header-less file
|
|
377
|
+
([limitations](docs/limitations.md#a-rewrite-without---x-header-can-put-a-row-equal-to-h-on-line-1)).
|
|
378
|
+
In 5.1 only `set`, `append`, `delete` and `clear` used `--x-header`: the reads and
|
|
379
|
+
`scrub` took line 1 as a row (`tsvz keys`
|
|
380
|
+
printed `id a b` for two keys, `len` said 3, `popitem first` popped the header), and
|
|
381
|
+
so did every request to a handler. Without `--x-header` nothing changed: line 1 is a
|
|
382
|
+
row, as in 5.1 and 3.39. In a `.tsvz` store `pop`, `popitem` and `setdefault` now check
|
|
383
|
+
`--x-header` as `set` does; nothing else changed there.
|
|
384
|
+
- **Known, mostly as in 5.1: a scrub, a rewrite or a read takes one column count for every row,
|
|
385
|
+
and can lose cells or keys.** A scrub given defaults narrower than the rows, a row wider
|
|
386
|
+
than the header, and a `.tsv` that begins with a bare `#_defaults_#` line (read without
|
|
387
|
+
defaults) are examples; the entry lists each, and which is new in 5.2
|
|
388
|
+
([limitations](docs/limitations.md#scrubs-and-rewrites-read-rows-at-one-column-width-deferred-to-the-width-design)).
|
|
389
|
+
A strict scrub of a file whose read dropped a line for its width and found no row leaves it,
|
|
390
|
+
so it grows again over append, delete-all and scrub cycles
|
|
391
|
+
([limitations](docs/limitations.md#a-strict-scrub-leaves-a-file-whose-read-dropped-a-line-for-its-width)).
|
|
392
|
+
Both wait for the column-width design after 5.2.
|
|
393
|
+
|
|
394
|
+
## Upgrading to 5.1 from 5.0
|
|
395
|
+
|
|
396
|
+
The file formats are unchanged, and a 3.39 host still reads every `.tsv`-family file
|
|
397
|
+
5.1 writes. Most of the changes are in scrub and clear.
|
|
398
|
+
|
|
399
|
+
- **Scrub and clear of a `.tsv` write over the file.** `scrubTabularFile`,
|
|
400
|
+
`clearTabularFile`, `tsvz scrub` and `tsvz clear` (on the command line or through a
|
|
401
|
+
`tsvz serve` handler), `TSVZClient.clear()` and `TSVZedLite.clear_file()` of a
|
|
402
|
+
`.tsv`-family file write the new content over the file and then cut it, as
|
|
403
|
+
`TSVZed`'s rewrites do, so a reader that takes no lock never meets the file emptied.
|
|
404
|
+
They take the file's lock, so they wait while another writer holds it. 5.0 emptied
|
|
405
|
+
the file first, with no lock (`TSVZedLite` truncated its own descriptor).
|
|
406
|
+
- **A scrub keeps another process's rows.** A scrub of a `.tsv` writes only if the
|
|
407
|
+
file is still what its read saw. Otherwise it reads again, up to three times, and if
|
|
408
|
+
the file kept changing it writes nothing and warns: `tsvz scrub` exits 1 (`scrub
|
|
409
|
+
refused; nothing was written`), as for a `.tsvz` (C6), and a handler answers refused.
|
|
410
|
+
`scrubTabularFile` still returns the rows it read. 5.0 wrote over a row appended
|
|
411
|
+
after its read.
|
|
412
|
+
- **`scrubTabularFile(lastLineOnly=True)` is a read.** It returns the last row and
|
|
413
|
+
writes nothing, as for a `.tsvz`. 5.0 and 3.39 wrote that row back as deletion lines
|
|
414
|
+
(`id\tv\na\t\n3\t\n`), and the store was gone.
|
|
415
|
+
- **A scrub keeps the file's `#_defaults_#` lines.** The defaults in effect at the end
|
|
416
|
+
of the read are written after the rows: the last non-empty defaults line, its empty
|
|
417
|
+
cells filled from the line before it as a reader fills them. An empty defaults line
|
|
418
|
+
that was read (it turns defaults off) is written before the rows, padded to their
|
|
419
|
+
width. Every row reads as it did to a reader given the scrub's defaults and
|
|
420
|
+
`strict`. Defaults a caller passes still fill the rows' empty cells, as in 5.0, but
|
|
421
|
+
are not written as a defaults line, and the caller's list is not changed. 5.0 and
|
|
422
|
+
3.39 dropped the lines, so rows appended later got no defaults.
|
|
423
|
+
- **The header stays on line 1.** A scrub given no header (`tsvz scrub` of a `.tsv`,
|
|
424
|
+
`scrubTabularFile(path)`) and a `TSVZed` given none, of a file
|
|
425
|
+
that has a header line, keep that line first, with a `TSVZed`'s defaults line right
|
|
426
|
+
after it. 5.0 and 3.39 put an empty line in front of it, so a reader given the header
|
|
427
|
+
read the header as a row. A header-less file keeps the empty first line it begins
|
|
428
|
+
with, and new files and clears given no header still get one, as in 5.0.
|
|
429
|
+
- **Interrupts.** A scrub or clear of either dialect keeps a copy of only the bytes it
|
|
430
|
+
overwrites, to put back if it is interrupted before the file is cut to its new
|
|
431
|
+
length; once the cut is made the rewrite is done and the new content stays. 5.0 kept
|
|
432
|
+
a copy of the whole `.tsvz` part and put it all back. (A scrub still reads the whole
|
|
433
|
+
store, and a `.tsvz` clear reads it too, to keep its markers.)
|
|
434
|
+
- **A compressed store cleared to nothing is one empty stream.** A `.tsvz.gz` (or
|
|
435
|
+
`.bz2`, `.xz`, `.zst`) cleared or scrubbed to nothing holds one empty compressed
|
|
436
|
+
stream, which `bzcat`, `xzcat` and Python's `bz2` and `lzma` can read; 5.0 left 0
|
|
437
|
+
bytes.
|
|
438
|
+
- **A header-checking clear of an empty file writes the header it was given.** A clear
|
|
439
|
+
that checks the header (`verifyHeader=True`, `tsvz clear --x-header H --x-strict`) of a
|
|
440
|
+
0-byte `.tsv`, or of a compressed file with no byte or only empty streams (another
|
|
441
|
+
tool's), takes it for an empty store with that header, as every reader does (L9), and
|
|
442
|
+
writes H without a "Header mismatch" warning. 5.0 wrote an empty line and warned of a
|
|
443
|
+
mismatch, so a strict `TSVZed` given H then refused the file, and raised on a 0-byte
|
|
444
|
+
`.bz2`, `.xz` or `.zst`.
|
|
445
|
+
- **`tsvz --help`** says what `clear` keeps: a `.tsvz` its header and markers, a
|
|
446
|
+
`.tsv` the `--x-header` it is given. (With `--x-strict` too, a `.tsv` clear keeps the
|
|
447
|
+
file's own header line.)
|
|
448
|
+
`TSVZed`'s message for a refused rewrite lists `hardMapToFile()` among the rewrites
|
|
449
|
+
a program asks for.
|
|
450
|
+
- **Known in 5.1, fixed in 5.2: a scrub of a `.tsv` whose rows were all deleted did not
|
|
451
|
+
compact it** when the scrub was given the file's header (`scrubTabularFile(path, header=H)`)
|
|
452
|
+
or the file had no header line: it left the file as it was, deletion lines included;
|
|
453
|
+
reads were right, as in 3.39 and 5.0. (`tsvz scrub` of a headed file read the header
|
|
454
|
+
line as a row, so it did compact it.)
|
|
455
|
+
- **Known: a scrub run with other defaults than the store's program can change how
|
|
456
|
+
early rows read.** In a `.tsv` with an empty `#_defaults_#` line and rows with empty
|
|
457
|
+
cells before any defaults line, the reset line the scrub writes before the rows keeps
|
|
458
|
+
those cells empty for a program that opens the store with its own defaults; run the
|
|
459
|
+
scrub with the defaults that program uses
|
|
460
|
+
([limitations](docs/limitations.md#a-scrub-run-with-other-defaults-than-the-stores-program-can-change-how-early-rows-read)).
|
|
461
|
+
- **Running programs keep 5.0** until they are restarted: long-running programs that
|
|
462
|
+
import TSVZ, and running `tsvz serve` handlers.
|
|
463
|
+
|
|
272
464
|
## Upgrading to 5.0 from 4.9
|
|
273
465
|
|
|
274
466
|
- **Writes refuse text they cannot encode.** A key, value, header, defaults line or
|
|
275
467
|
marker the file's encoding cannot hold (UTF-8 for `.tsvz`; `encoding=` for a
|
|
276
468
|
`.tsv`), such as the lone surrogate `os.fsdecode` gives for a non-UTF-8 file name,
|
|
277
|
-
raises `TSVZEncodeError` (a `ValueError`) and writes nothing; 4.9 stored
|
|
469
|
+
raises `TSVZEncodeError` (a `ValueError`) and writes nothing; 4.9 stored `?` (its
|
|
470
|
+
`tsvz` stored U+FFFD for a non-UTF-8 argument, and its `.tsvz` file functions raised
|
|
471
|
+
`UnicodeEncodeError`).
|
|
278
472
|
`errors='replace'` brings `?` back. Under a non-UTF-8 `encoding=`, a key read as
|
|
279
473
|
U+FFFD cannot be deleted or updated. `tsvz` exits 1 with the message. See Fault
|
|
280
474
|
tolerance.
|
|
@@ -292,8 +486,7 @@ damaged file, as asked, and warns.
|
|
|
292
486
|
merged into it (Fault tolerance).
|
|
293
487
|
- **`.tsvz` stores** see another process's append made right after their load, a
|
|
294
488
|
commit or a clear (4.9 missed it until the next change), keep the rows set while
|
|
295
|
-
they reload,
|
|
296
|
-
mismatch at the first read.
|
|
489
|
+
they reload, and raise a strict header mismatch at the first read.
|
|
297
490
|
- **`.tsv` files:** an append to an existing empty file writes the header first, and
|
|
298
491
|
reading one gives an empty store; see L8 to L14 for the rest.
|
|
299
492
|
- **`tsvz`** runs every operation through the engine `tsvz serve` uses, with the same
|
|
@@ -302,8 +495,15 @@ damaged file, as asked, and warns.
|
|
|
302
495
|
file. The 3.39 warning that a file name does not end in `.tsv` is printed only
|
|
303
496
|
when a command creates such a file, and a write that cannot be made reports the
|
|
304
497
|
operating system's error.
|
|
305
|
-
- **
|
|
306
|
-
|
|
498
|
+
- **Running programs keep 4.9.** Long-running programs that import TSVZ, and running
|
|
499
|
+
`tsvz serve` handlers, keep 4.9 until they are restarted. 5.0's `tsvz stop` cannot
|
|
500
|
+
stop a 4.9 handler of a `.tsv` (or other tab- or NUL-delimited) file: 4.9 wrote the
|
|
501
|
+
delimiter into the pointer file's `x-delimiter` as `<sep>` (a NUL as `<NUL>`), which
|
|
502
|
+
5.0 decodes to the TAB or NUL itself and refuses as a control character. Send that
|
|
503
|
+
handler SIGTERM instead: its pid is on the `pid` line of its pointer file
|
|
504
|
+
(`STORE.serve`), and it finishes its writes and exits.
|
|
505
|
+
- **macOS and other systems that lock with `lockf`:** as in 4.9, a read in another
|
|
506
|
+
thread of the process can still drop a write's lock
|
|
307
507
|
([docs/limitations.md](docs/limitations.md)).
|
|
308
508
|
|
|
309
509
|
## Breaking changes from 3.39
|
|
@@ -326,6 +526,11 @@ damaged file, as asked, and warns.
|
|
|
326
526
|
| L12 | A rewrite of a file whose header line has no newline writes the rows on lines of their own; 3.39 wrote the first one onto the header line. |
|
|
327
527
|
| L13 | A compressed file is rewritten in members of at most 1 MiB, as appends already were: the bytes differ, the rows read are the same, and 3.39 reads them. |
|
|
328
528
|
| L14 | `TSVZed` opening a compressed file in a codec this Python lacks (zstd before Python 3.14) raises `ImportError` instead of reading the compressed bytes as text. |
|
|
529
|
+
| L15 | `scrubTabularFile`, `clearTabularFile`, `tsvz scrub`, `tsvz clear` and `TSVZedLite.clear_file()` write the new content over the file and then cut it, under the file's lock; 3.39 emptied the file first, with no lock, so a reader could meet it empty. |
|
|
530
|
+
| L16 | A scrub writes only if the file is still what its read saw: it reads again up to three times, then writes nothing, with a warning (`tsvz scrub` exits 1, C6); 3.39 wrote over rows appended after its read. |
|
|
531
|
+
| L17 | `scrubTabularFile(lastLineOnly=True)` returns the last row and writes nothing; 3.39 wrote that row back as deletion lines, and the store was gone. |
|
|
532
|
+
| L18 | A scrub keeps the file's `#_defaults_#` lines (an empty one before the rows, the defaults in effect after them), so every row reads as it did; 3.39 dropped them, so rows appended later got no defaults. |
|
|
533
|
+
| L19 | A scrub given no header, and a `TSVZed` given none, keep a file's header line first; 3.39 put an empty line in front of it, and a reader given the header then read the header as a row. |
|
|
329
534
|
|
|
330
535
|
### `.tsvz`, `.csvz`, `.nsvz`, `.psvz`: now tsvz-spec-v1
|
|
331
536
|
|