TSVZ 3.38__tar.gz → 3.39__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-3.39/PKG-INFO +396 -0
- tsvz-3.39/README.md +368 -0
- tsvz-3.39/TSVZ.egg-info/PKG-INFO +396 -0
- {tsvz-3.38 → tsvz-3.39}/TSVZ.egg-info/SOURCES.txt +1 -2
- {tsvz-3.38 → tsvz-3.39}/TSVZ.egg-info/dependency_links.txt +0 -0
- {tsvz-3.38 → tsvz-3.39}/TSVZ.egg-info/entry_points.txt +0 -0
- {tsvz-3.38 → tsvz-3.39}/TSVZ.egg-info/top_level.txt +0 -0
- {tsvz-3.38 → tsvz-3.39}/TSVZ.py +114 -48
- tsvz-3.38/PKG-INFO +0 -62
- tsvz-3.38/README.md +0 -34
- tsvz-3.38/TSVZ.egg-info/PKG-INFO +0 -62
- tsvz-3.38/tests/test_regression.py +0 -104
- {tsvz-3.38 → tsvz-3.39}/TSVZ.egg-info/requires.txt +0 -0
- {tsvz-3.38 → tsvz-3.39}/setup.cfg +0 -0
- {tsvz-3.38 → tsvz-3.39}/setup.py +0 -0
tsvz-3.39/PKG-INFO
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: TSVZ
|
|
3
|
+
Version: 3.39
|
|
4
|
+
Summary: An simple in memory wrapper around a TSV file to function as a database
|
|
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: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.6
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: argparse
|
|
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-dist
|
|
26
|
+
Dynamic: requires-python
|
|
27
|
+
Dynamic: summary
|
|
28
|
+
|
|
29
|
+
# TSVZ
|
|
30
|
+
|
|
31
|
+
TSVZ is a tiny, dependency-free library and CLI for treating a **tab-separated
|
|
32
|
+
values file as a key-value store**. The first column of every row is a unique
|
|
33
|
+
key; the file behaves like an ordered dictionary that is transparently
|
|
34
|
+
persisted to disk.
|
|
35
|
+
|
|
36
|
+
Two front-ends are provided:
|
|
37
|
+
|
|
38
|
+
- **`TSVZed`** — an in-memory `OrderedDict` backed by the file, with a
|
|
39
|
+
background worker that does **non-blocking, append-only** writes, optional
|
|
40
|
+
periodic / on-exit rewrites, file locking for multi-process access, and
|
|
41
|
+
transparent (de)compression.
|
|
42
|
+
- **`TSVZedLite`** — a `MutableMapping` that keeps only a **key → byte-offset
|
|
43
|
+
index** in memory and reads values from disk on demand. Minimal footprint,
|
|
44
|
+
single-process, append-only.
|
|
45
|
+
|
|
46
|
+
It is a single self-contained module (`TSVZ.py`) — drop it next to your code and
|
|
47
|
+
`import TSVZ`, or install it.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install tsvz # from PyPI
|
|
51
|
+
pip install -e . # editable, from this directory
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Quick start
|
|
57
|
+
|
|
58
|
+
### As a library
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
import TSVZ
|
|
62
|
+
|
|
63
|
+
# Opens (creating if needed), loads into memory, starts the async writer.
|
|
64
|
+
db = TSVZ.TSVZed('data.tsv', header='id\tname\tscore')
|
|
65
|
+
|
|
66
|
+
db['alice'] = ['alice', 'Alice', '10'] # value as a list
|
|
67
|
+
db['bob'] = 'bob\tBob\t20' # ...or a delimited string
|
|
68
|
+
db['alice'] = ['alice', 'Alice', '11'] # last write wins
|
|
69
|
+
del db['bob'] # deletes (and persists the deletion)
|
|
70
|
+
|
|
71
|
+
print(db['alice']) # ['alice', 'Alice', '11']
|
|
72
|
+
db.close() # flush the queue and stop the worker (also runs atexit)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
There are also stateless helper functions if you just want to touch a file once:
|
|
76
|
+
`readTabularFile`, `appendTabularFile`, `appendLinesTabularFile`,
|
|
77
|
+
`clearTabularFile`, `scrubTabularFile` (and `readTSV` / `appendTSV` / … aliases).
|
|
78
|
+
|
|
79
|
+
### From the command line
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
tsvz data.tsv # read + pretty-print
|
|
83
|
+
tsvz data.tsv append alice Alice 10 # append/update a row keyed by "alice"
|
|
84
|
+
tsvz data.tsv delete alice # delete the row keyed by "alice"
|
|
85
|
+
tsvz data.tsv clear # truncate to just the header
|
|
86
|
+
tsvz data.tsv scrub # rewrite compactly (drops comments, applies last-wins)
|
|
87
|
+
|
|
88
|
+
tsvz data.csv -d comma append k v1 v2 # pick a delimiter explicitly
|
|
89
|
+
tsvz -h
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## The TSVZ file format
|
|
95
|
+
|
|
96
|
+
> **Status: draft / evolving spec.** The reference code in this repo does not
|
|
97
|
+
> fully implement it yet (see *Implementation notes*).
|
|
98
|
+
|
|
99
|
+
`.tsvz` / `.csvz` / `.nsvz` / `.psvz` are a **format in their own right** — not
|
|
100
|
+
merely a `.tsv` that TSVZ happens to manage. They define a **strict,
|
|
101
|
+
append-only, write-ahead-log (WAL) style key-value store** that is *also*
|
|
102
|
+
readable by ordinary CSV/TSV tools (which see the rows fine but may
|
|
103
|
+
misinterpret escaped values; see *Escaping*).
|
|
104
|
+
|
|
105
|
+
The plain, un-suffixed `.tsv` / `.csv` / `.nsv` / `.psv` extensions are **not**
|
|
106
|
+
this format: they remain loose delimited tables with no semantic guarantees. The
|
|
107
|
+
same delimiter mapping carries over to both; only the `z` forms carry the
|
|
108
|
+
stricter contract below.
|
|
109
|
+
|
|
110
|
+
The format is defined by the rules below; they are written to be implementable by
|
|
111
|
+
any reader/writer, not just this library.
|
|
112
|
+
|
|
113
|
+
### 0. Model — the file *is* a write-ahead log
|
|
114
|
+
|
|
115
|
+
- **Every mutation is an append.** There is no in-place update during normal
|
|
116
|
+
operation. Current state is reconstructed by **replaying the file forward**,
|
|
117
|
+
**last-wins**.
|
|
118
|
+
- **Key-value, not relational.** First column is the globally-unique key; the
|
|
119
|
+
remaining columns are the value (variable width; a virtual infinite tail of
|
|
120
|
+
empty columns; a defaults line as per-column fallback). No schema, no header
|
|
121
|
+
semantics beyond "a `#` comment."
|
|
122
|
+
- **Delete is a tombstone** (a lone key / all-empty values), never a physical
|
|
123
|
+
removal — consistent with a log.
|
|
124
|
+
- **Minimal overhead vs. an embedded DB.** Plain UTF-8 text, line-oriented,
|
|
125
|
+
human-readable and greppable; no page cache, b-tree, or separate journal. You
|
|
126
|
+
trade random-update efficiency and bounded file size for near-zero write
|
|
127
|
+
overhead and trivial crash recovery (truncate the last partial line). Fits
|
|
128
|
+
logging / event streams / transactional state where overwrites are rare, write
|
|
129
|
+
latency matters, or the full history is worth keeping.
|
|
130
|
+
- **Compaction is occasional maintenance, not normal operation** — the one place
|
|
131
|
+
the append-only rule is deliberately, explicitly broken. See *Compaction*.
|
|
132
|
+
|
|
133
|
+
### 1. Encoding & lines
|
|
134
|
+
|
|
135
|
+
1. The file is **UTF-8** encoded (no BOM).
|
|
136
|
+
2. Records are separated by a line feed (`\n`). A reader **must** tolerate a
|
|
137
|
+
trailing `\r` (i.e. read CRLF files); writers **should** emit bare `\n`.
|
|
138
|
+
3. The file **should** end with a trailing newline. A reader **must** tolerate a
|
|
139
|
+
missing final newline.
|
|
140
|
+
4. An empty file (zero bytes) — or a file containing only comments — is a valid,
|
|
141
|
+
empty TSVZ.
|
|
142
|
+
|
|
143
|
+
### 2. Delimiter & variants
|
|
144
|
+
|
|
145
|
+
5. The variants differ only in the field delimiter. The `z` extension marks the
|
|
146
|
+
strict WAL format; the plain extension is the loose tabular fallback:
|
|
147
|
+
|
|
148
|
+
| Variant | Format ext. | Plain ext. | Delimiter |
|
|
149
|
+
|---------|-------------|------------|-----------|
|
|
150
|
+
| TSVZ | `.tsvz` | `.tsv` | tab `\t` |
|
|
151
|
+
| CSVZ | `.csvz` | `.csv` | comma `,` |
|
|
152
|
+
| NSVZ | `.nsvz` | `.nsv` | NUL `\0` |
|
|
153
|
+
| PSVZ | `.psvz` | `.psv` | pipe `\|` |
|
|
154
|
+
|
|
155
|
+
6. Each line is a list of fields joined by the delimiter. **Column count is not
|
|
156
|
+
fixed** — a reader must accept arbitrarily short or long lines. Conceptually
|
|
157
|
+
every row is its values followed by an infinite tail of empty-string columns,
|
|
158
|
+
so reading a field beyond a row's length yields `''` (subject to the defaults
|
|
159
|
+
rule below). A returned row is therefore **not guaranteed to be a fixed
|
|
160
|
+
length**; a reader is free to additionally validate or pad/trim if it wants.
|
|
161
|
+
|
|
162
|
+
### 3. Keys & values
|
|
163
|
+
|
|
164
|
+
7. The **first column is the key** and is **globally unique** within the file.
|
|
165
|
+
8. **Last-wins:** when the same key appears on multiple lines, the **last**
|
|
166
|
+
occurrence determines the current value. (This is what makes append-only
|
|
167
|
+
updates work.)
|
|
168
|
+
9. A key's **logical position** is the position of its *first* appearance; later
|
|
169
|
+
updates change its value but not its order.
|
|
170
|
+
10. **Append-only:** during normal operation a TSVZ file is only ever appended
|
|
171
|
+
to — an update or a delete is a new line at the end of the file, never an
|
|
172
|
+
in-place edit. (Compaction is a separate, explicit operation; see *scrub*.)
|
|
173
|
+
|
|
174
|
+
### 4. Deletion (tombstones)
|
|
175
|
+
|
|
176
|
+
11. A line consisting of **a lone key with no following values, or whose
|
|
177
|
+
following values are all empty**, is a **tombstone**: it removes that key if
|
|
178
|
+
it currently exists, and is tolerated (no-op) if it does not.
|
|
179
|
+
- e.g. `mykey\n` or `mykey\t\t\n` both delete `mykey`.
|
|
180
|
+
- Edge case: in a strictly single-column file a lone key *is* the value, so
|
|
181
|
+
deletion cannot be represented; such files cannot tombstone.
|
|
182
|
+
|
|
183
|
+
### 5. Whitespace
|
|
184
|
+
|
|
185
|
+
12. **Trailing whitespace is stripped from every field on read** (including the
|
|
186
|
+
key) — trailing whitespace is treated as visual padding and is not
|
|
187
|
+
significant. Leading whitespace **is** significant and preserved. (This does
|
|
188
|
+
not apply to comment lines, which are not parsed.)
|
|
189
|
+
|
|
190
|
+
### 6. Comments & header
|
|
191
|
+
|
|
192
|
+
13. Any line whose first field starts with `#` is a **comment** and is discarded
|
|
193
|
+
by the reader (with the exception of the reserved `#_..._#` keys below).
|
|
194
|
+
14. There is **no formal header**. A header row, if present, is simply a comment
|
|
195
|
+
(a line starting with `#`, e.g. `#id\tname\tscore`) and carries no semantics
|
|
196
|
+
for the data — it is purely human documentation.
|
|
197
|
+
|
|
198
|
+
### 7. Defaults line
|
|
199
|
+
|
|
200
|
+
15. A line whose key is **`#_defaults_#`** (case-insensitive — `#_DEFAULTS_#` is
|
|
201
|
+
equally valid) is a **defaults line**. It is not data; it supplies fallback
|
|
202
|
+
values per column index.
|
|
203
|
+
16. Defaults have an **unbounded** number of columns and are applied **forward
|
|
204
|
+
only**: as a reader streams the file, a field that is empty (or absent) in a
|
|
205
|
+
data row takes the value at the same column index from the **currently
|
|
206
|
+
active** defaults line.
|
|
207
|
+
- Rows that appear *before* a defaults line are unaffected by it (the reader
|
|
208
|
+
does not backtrack).
|
|
209
|
+
- Example: rows have 5 fields and there is no defaults line ⇒ reading field 6
|
|
210
|
+
yields `''`. Then a defaults line with 10 fields appears ⇒ for *subsequent*
|
|
211
|
+
5-field rows, reading field 6 yields the defaults line's field 6.
|
|
212
|
+
17. **Multiple defaults lines are allowed.** A later defaults line **completely
|
|
213
|
+
replaces** the previous one — if the later line is shorter, the extra columns
|
|
214
|
+
of the previous line are discarded, not merged.
|
|
215
|
+
18. A `#_defaults_#` line with **no values** clears the active defaults.
|
|
216
|
+
|
|
217
|
+
### 8. Reserved namespace
|
|
218
|
+
|
|
219
|
+
19. Keys matching **`#_<ascii letters/digits>_#`** are **reserved** for current
|
|
220
|
+
and future internal TSVZ semantics (`#_defaults_#` is the first). Don't use
|
|
221
|
+
that pattern for your own comments. (Conformance is only enforced where a
|
|
222
|
+
given reserved key is actually defined; unknown `#_..._#` keys are treated as
|
|
223
|
+
ordinary comments today.)
|
|
224
|
+
|
|
225
|
+
### 9. Escaping / sanitization
|
|
226
|
+
|
|
227
|
+
The delimiter and newline can't appear literally inside a field, so they are
|
|
228
|
+
encoded with reversible tokens. Reading inverts writing:
|
|
229
|
+
|
|
230
|
+
| In a field (in memory) | Stored on disk | Read back as |
|
|
231
|
+
|---|---|---|
|
|
232
|
+
| the delimiter char (e.g. `\t`) | `<sep>` | the delimiter char |
|
|
233
|
+
| line feed `\n` | `<LF>` | line feed `\n` |
|
|
234
|
+
| literal `<sep>` | `</sep/>` | literal `<sep>` |
|
|
235
|
+
| literal `<LF>` | `</LF/>` | literal `<LF>` |
|
|
236
|
+
|
|
237
|
+
20. Consequently `<sep>` / `<LF>` appearing **in the file** are interpreted as a
|
|
238
|
+
delimiter / line feed on read. The double-wrapped forms `</sep/>` / `</LF/>`
|
|
239
|
+
exist so that the literal strings `<sep>` / `<LF>` (e.g. HTML-ish tags) can
|
|
240
|
+
round-trip.
|
|
241
|
+
21. To keep the mapping finite, the literal strings **`</sep/>` and `</LF/>` are
|
|
242
|
+
invalid as field data**: if they occur in a file they are read back as the
|
|
243
|
+
literal `<sep>` / `<LF>` (i.e. they have no escape of their own and are
|
|
244
|
+
*lossy* — they collapse one level). Writers should avoid emitting them as
|
|
245
|
+
real data.
|
|
246
|
+
|
|
247
|
+
### 10. Compression
|
|
248
|
+
|
|
249
|
+
22. Compression is an **orthogonal stream filter**, not part of the format
|
|
250
|
+
semantics: any TSVZ file (or any single part of a multi-part set, below) may
|
|
251
|
+
be wrapped by appending a compression extension — `.gz`/`.gzip`,
|
|
252
|
+
`.bz2`/`.bzip2`, `.xz`/`.lzma`, `.zst`/`.zstd` (e.g. `log.tsvz.zst`).
|
|
253
|
+
Delimiter/format inference ignores the compression suffix. A compressed part
|
|
254
|
+
is rewritten as a whole, so the append-only optimization applies only to
|
|
255
|
+
uncompressed parts.
|
|
256
|
+
|
|
257
|
+
### 11. Multi-part files
|
|
258
|
+
|
|
259
|
+
23. A logical TSVZ store may be **split across multiple parts**. Parts share the
|
|
260
|
+
same path up to the format extension and add a `.<hex>` ordinal suffix, e.g.
|
|
261
|
+
`events.tsvz.0`, `events.tsvz.1`, … or with timestamps
|
|
262
|
+
`events.tsvz.6650a3c0`, `events.tsvz.66518f10`, ….
|
|
263
|
+
24. Parts are ordered by interpreting the suffix as a **hexadecimal (and hence
|
|
264
|
+
also plain-integer) number, ascending** — smaller = earlier in the log. The
|
|
265
|
+
logical store is the concatenation of the parts in that order; replay forward
|
|
266
|
+
/ last-wins simply spans part boundaries.
|
|
267
|
+
25. **Each part is itself a complete, valid TSVZ file** (comments, defaults, etc.
|
|
268
|
+
all legal per part). Multi-part is purely an addressing convention layered on
|
|
269
|
+
top; a single-file store is just the one-part case.
|
|
270
|
+
26. **Recommended suffix:** the hex (or integer) Unix timestamp of when the part
|
|
271
|
+
was created — naturally chronological and collision-resistant.
|
|
272
|
+
|
|
273
|
+
Operational recommendations for multi-part writers:
|
|
274
|
+
|
|
275
|
+
- On startup, **open a fresh part** named with the current timestamp and append
|
|
276
|
+
new records there; leave older parts untouched (they become immutable).
|
|
277
|
+
- **Do not compress the current (active) part** — it is being appended to.
|
|
278
|
+
- Provide a processor/flag that, on the next launch, **compresses prior parts**
|
|
279
|
+
that aren't already compressed (now immutable, so it's safe and frees space).
|
|
280
|
+
Different parts may use different codecs.
|
|
281
|
+
|
|
282
|
+
### Additional ideas worth considering (not yet normative)
|
|
283
|
+
|
|
284
|
+
These came up while writing the spec; flagging them for your call:
|
|
285
|
+
|
|
286
|
+
- **Format-version marker.** Reserve a `#_tsvz1_#`-style first-line comment so
|
|
287
|
+
future readers can detect the format/version explicitly. Cheap insurance.
|
|
288
|
+
- **Key constraints.** State explicitly that a *data* key must be non-empty
|
|
289
|
+
(after trailing-whitespace strip) and must not start with `#` (that prefix is
|
|
290
|
+
reserved for comments/internal keys).
|
|
291
|
+
- **Append atomicity.** Recommend that a conformant appender write each record
|
|
292
|
+
as a *single* `write()` of a complete, newline-terminated line (open with
|
|
293
|
+
`O_APPEND`) so concurrent appenders never interleave partial lines. This is
|
|
294
|
+
what makes multi-writer last-wins safe without a global rewrite.
|
|
295
|
+
- **Compaction definition.** Define "scrub/compact" formally: produce an
|
|
296
|
+
equivalent file containing, for each live key, exactly one line in
|
|
297
|
+
first-appearance order, dropping comments and tombstones, optionally
|
|
298
|
+
re-emitting a single `#_defaults_#` line. (This is lossy w.r.t. comments — say
|
|
299
|
+
so.)
|
|
300
|
+
- **NUL caveat.** Note that NSVZ (`\0` delimiter) is not friendly to most text
|
|
301
|
+
tools; it's meant for programmatic use.
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## API
|
|
306
|
+
|
|
307
|
+
### `TSVZed(fileName, …)`
|
|
308
|
+
|
|
309
|
+
An `OrderedDict` subclass that auto-syncs to `fileName`. Selected options:
|
|
310
|
+
|
|
311
|
+
| Option | Default | Meaning |
|
|
312
|
+
|---|---|---|
|
|
313
|
+
| `header` | `''` | Header line (string or list) for creation/validation. |
|
|
314
|
+
| `createIfNotExist` | `True` | Create the file if missing. |
|
|
315
|
+
| `verifyHeader` | `True` | Check the file's first line against `header`. |
|
|
316
|
+
| `delimiter` | auto | Delimiter; inferred from the extension if omitted. |
|
|
317
|
+
| `defaults` | `None` | Per-column default values. |
|
|
318
|
+
| `strict` | `False` | Enforce column counts / header; reject malformed rows. |
|
|
319
|
+
| `rewrite_on_load` | `True` | Compact the file once when opened. |
|
|
320
|
+
| `rewrite_on_exit` | `False` | Compact the file when closed. |
|
|
321
|
+
| `rewrite_interval` | `0` | Min seconds between periodic compactions (`0` = never). |
|
|
322
|
+
| `monitor_external_changes` | `True` | Detect / merge other processes' writes. |
|
|
323
|
+
| `encoding` | `'utf8'` | File encoding. |
|
|
324
|
+
|
|
325
|
+
Writes are queued and flushed by a background thread; call `close()` (or use it
|
|
326
|
+
as a context manager) to flush and stop cleanly. Keys starting with `#` are kept
|
|
327
|
+
in memory only (handy for scratch state) and are never written.
|
|
328
|
+
|
|
329
|
+
### `TSVZedLite(fileName, …)`
|
|
330
|
+
|
|
331
|
+
A `MutableMapping` that stores only a key→offset index in RAM and seeks into the
|
|
332
|
+
file for each read. Append-only; single-process; no compression, no background
|
|
333
|
+
thread, no external-change monitoring. Good for very large, mostly-write,
|
|
334
|
+
seek-cheap (SSD) workloads. An external index dict can be supplied to skip the
|
|
335
|
+
initial full scan (turning it into a small key-value store).
|
|
336
|
+
|
|
337
|
+
### Module functions
|
|
338
|
+
|
|
339
|
+
`readTabularFile`, `appendTabularFile`, `appendLinesTabularFile`,
|
|
340
|
+
`clearTabularFile`, `scrubTabularFile`, `get_delimiter`, `pretty_format_table`,
|
|
341
|
+
plus the legacy `readTSV` / `appendTSV` / `clearTSV` / `scrubTSV` aliases.
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Implementation notes (reference behavior vs. the format spec)
|
|
346
|
+
|
|
347
|
+
The format above is the target definition. A few places where the current
|
|
348
|
+
`TSVZed` / `TSVZedLite` implementation differs or only partially implements it —
|
|
349
|
+
useful to know, and candidates for a future alignment pass:
|
|
350
|
+
|
|
351
|
+
- **Column normalization.** Rather than treating rows as variable-length with an
|
|
352
|
+
infinite empty tail (rule 6), the classes detect a column count (from the
|
|
353
|
+
header or first row) and, in non-strict mode, pad/trim rows to it; `strict`
|
|
354
|
+
mode rejects mismatches. Reads therefore return fixed-width rows in practice.
|
|
355
|
+
- **Defaults application.** Empty cells *present* in a row are filled from the
|
|
356
|
+
active defaults at parse time, but rows are not virtually extended, so "read a
|
|
357
|
+
field beyond the row length" (rule 16) isn't exposed as an API yet.
|
|
358
|
+
- **Header.** The reference classes still use a *non*-`#` first line as the
|
|
359
|
+
header (validated via `verifyHeader`), whereas rule 14 defines the header as an
|
|
360
|
+
ordinary `#` comment. Existing files written by older versions use the non-`#`
|
|
361
|
+
header.
|
|
362
|
+
- **`#_DEFAULTS_#` alias.** Only the lowercase `#_defaults_#` key is currently
|
|
363
|
+
recognized by the parser.
|
|
364
|
+
- **No `z` vs. plain distinction, no multi-part.** `get_delimiter` infers from
|
|
365
|
+
`.tsv`/`.csv`/`.nsv`/`.psv` only, so the `z` extensions aren't recognized for
|
|
366
|
+
delimiter inference yet (`.csvz`/`.nsvz`/`.psvz` currently fall back to tab;
|
|
367
|
+
`.tsvz` happens to land on tab correctly). The code does not yet enforce the
|
|
368
|
+
strict append-only WAL contract differently for the `z` forms, and there is no
|
|
369
|
+
multi-part read/write support. These are spec items, not implemented.
|
|
370
|
+
|
|
371
|
+
### Open design questions
|
|
372
|
+
|
|
373
|
+
Raised by the spec clarification, not yet decided:
|
|
374
|
+
|
|
375
|
+
1. **Where does the strict contract live?** Should `TSVZed`/`TSVZedLite` key the
|
|
376
|
+
append-only WAL behavior off the `z` extension (`.tsv` = lenient table,
|
|
377
|
+
`.tsvz` = this format), or treat all forms the same and let the suffix be
|
|
378
|
+
purely documentary?
|
|
379
|
+
2. **Append-only vs. the existing rewrite machinery.** A strict `.tsvz` arguably
|
|
380
|
+
forbids in-place rewrite (`rewrite_on_load` / `mapToFile`) entirely except via
|
|
381
|
+
explicit compaction — which would simplify the class considerably.
|
|
382
|
+
3. **Compaction for history-retention users.** If "keep the full log" is a goal,
|
|
383
|
+
compaction must be an explicit, separate action — and perhaps emit a `.tsv`
|
|
384
|
+
snapshot rather than mutating the `.tsvz` log in place.
|
|
385
|
+
|
|
386
|
+
## Tests
|
|
387
|
+
|
|
388
|
+
```bash
|
|
389
|
+
python -m pytest test_TSVZ.py # regression suite
|
|
390
|
+
python test_TSVZ.py # or run directly
|
|
391
|
+
python benchTSVZ.py data.tsv -n 100000 # throughput benchmark (not a test)
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
## License
|
|
395
|
+
|
|
396
|
+
GPL-3.0-or-later © Yufei Pan (pan@zopyr.us)
|