kedis-python 0.1.3__tar.gz → 0.2.0__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.
- {kedis_python-0.1.3/kedis_python.egg-info → kedis_python-0.2.0}/PKG-INFO +34 -10
- kedis_python-0.1.3/PKG-INFO → kedis_python-0.2.0/README.md +27 -23
- kedis_python-0.2.0/kedis_python/aof.py +138 -0
- kedis_python-0.2.0/kedis_python/commands.py +1202 -0
- kedis_python-0.2.0/kedis_python/help_data.py +711 -0
- kedis_python-0.2.0/kedis_python/hyperloglog.py +78 -0
- {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python/main.py +348 -158
- kedis_python-0.2.0/kedis_python/network.py +114 -0
- kedis_python-0.2.0/kedis_python/parser.py +491 -0
- {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python/server.py +292 -65
- kedis_python-0.2.0/kedis_python/store.py +1838 -0
- kedis_python-0.2.0/kedis_python/stream.py +416 -0
- kedis_python-0.2.0/kedis_python/textcodec.py +32 -0
- kedis_python-0.2.0/kedis_python/ui.py +366 -0
- kedis_python-0.1.3/README.md → kedis_python-0.2.0/kedis_python.egg-info/PKG-INFO +47 -9
- kedis_python-0.2.0/kedis_python.egg-info/SOURCES.txt +72 -0
- {kedis_python-0.1.3 → kedis_python-0.2.0}/pyproject.toml +11 -6
- kedis_python-0.2.0/tests/test_aof_persistence.py +280 -0
- kedis_python-0.2.0/tests/test_aof_records.py +140 -0
- {kedis_python-0.1.3 → kedis_python-0.2.0}/tests/test_bench.py +2 -2
- kedis_python-0.2.0/tests/test_binary_safety.py +340 -0
- kedis_python-0.2.0/tests/test_bitmap_persistence.py +280 -0
- kedis_python-0.2.0/tests/test_bitmaps.py +189 -0
- kedis_python-0.2.0/tests/test_cli_housekeep.py +121 -0
- kedis_python-0.2.0/tests/test_cli_stream_view.py +118 -0
- kedis_python-0.2.0/tests/test_data_replies.py +123 -0
- kedis_python-0.2.0/tests/test_del_exists.py +107 -0
- kedis_python-0.2.0/tests/test_docs_data_types.py +152 -0
- kedis_python-0.2.0/tests/test_docs_streams.py +91 -0
- kedis_python-0.2.0/tests/test_help_data.py +211 -0
- kedis_python-0.2.0/tests/test_history_setup.py +74 -0
- kedis_python-0.2.0/tests/test_housekeeping.py +282 -0
- kedis_python-0.2.0/tests/test_hyperloglog.py +419 -0
- kedis_python-0.2.0/tests/test_inline_block.py +112 -0
- kedis_python-0.2.0/tests/test_kesp_decoder.py +254 -0
- kedis_python-0.2.0/tests/test_kesp_encoder.py +208 -0
- {kedis_python-0.1.3 → kedis_python-0.2.0}/tests/test_lists.py +2 -2
- kedis_python-0.2.0/tests/test_lrange.py +88 -0
- kedis_python-0.2.0/tests/test_parser_casing.py +87 -0
- kedis_python-0.2.0/tests/test_parser_framing.py +175 -0
- kedis_python-0.2.0/tests/test_parser_harden.py +150 -0
- kedis_python-0.2.0/tests/test_parser_limits.py +138 -0
- kedis_python-0.2.0/tests/test_pubsub.py +220 -0
- kedis_python-0.2.0/tests/test_reply_limits.py +123 -0
- kedis_python-0.2.0/tests/test_server_limits.py +345 -0
- kedis_python-0.2.0/tests/test_server_protocol_errors.py +112 -0
- kedis_python-0.2.0/tests/test_stream_commands.py +299 -0
- kedis_python-0.2.0/tests/test_stream_core.py +219 -0
- kedis_python-0.2.0/tests/test_stream_groups_commands.py +433 -0
- kedis_python-0.2.0/tests/test_stream_groups_core.py +285 -0
- kedis_python-0.2.0/tests/test_stream_groups_server.py +438 -0
- kedis_python-0.2.0/tests/test_stream_groups_store.py +609 -0
- kedis_python-0.2.0/tests/test_stream_server.py +326 -0
- kedis_python-0.2.0/tests/test_stream_store.py +374 -0
- kedis_python-0.2.0/tests/test_type.py +77 -0
- kedis_python-0.2.0/tests/test_version.py +34 -0
- kedis_python-0.2.0/tests/test_zadd.py +98 -0
- kedis_python-0.1.3/kedis_python/commands.py +0 -512
- kedis_python-0.1.3/kedis_python/network.py +0 -60
- kedis_python-0.1.3/kedis_python/parser.py +0 -132
- kedis_python-0.1.3/kedis_python/store.py +0 -873
- kedis_python-0.1.3/kedis_python/ui.py +0 -189
- kedis_python-0.1.3/kedis_python.egg-info/SOURCES.txt +0 -29
- {kedis_python-0.1.3 → kedis_python-0.2.0}/LICENSE +0 -0
- {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python/__init__.py +0 -0
- {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python/benchmark.py +0 -0
- {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python/benchmark_asyncio.py +0 -0
- {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python/kesp_cli.py +0 -0
- {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python/replica.py +0 -0
- {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python/simulator.py +0 -0
- {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python/skiplist.py +0 -0
- {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python.egg-info/dependency_links.txt +0 -0
- {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python.egg-info/entry_points.txt +0 -0
- {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python.egg-info/requires.txt +0 -0
- {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python.egg-info/top_level.txt +0 -0
- {kedis_python-0.1.3 → kedis_python-0.2.0}/setup.cfg +0 -0
- {kedis_python-0.1.3 → kedis_python-0.2.0}/tests/test_kesp.py +0 -0
- {kedis_python-0.1.3 → kedis_python-0.2.0}/tests/test_kesp2.py +0 -0
- {kedis_python-0.1.3 → kedis_python-0.2.0}/tests/test_lru.py +0 -0
- {kedis_python-0.1.3 → kedis_python-0.2.0}/tests/test_replica.py +0 -0
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kedis-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: A Redis-inspired in-memory store in Python.
|
|
5
5
|
Author: V SS Karthik
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/Hogwarts-coder10/kedis-python
|
|
8
|
+
Keywords: redis,in-memory,key-value,database,streams,pub-sub
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Topic :: Database
|
|
8
14
|
Requires-Python: >=3.9
|
|
9
15
|
Description-Content-Type: text/markdown
|
|
10
16
|
License-File: LICENSE
|
|
@@ -43,15 +49,17 @@ Rather than focusing on Redis compatibility, Kedis focuses on understanding the
|
|
|
43
49
|
## 📚 Engine Documentation
|
|
44
50
|
|
|
45
51
|
Dive into the internal grimoire to understand how Kedis achieves microsecond latency:
|
|
46
|
-
* [Architecture & Pipeline](docs/architecture.md)
|
|
47
|
-
* [KESP Wire Protocol](docs/protocol.md)
|
|
48
|
-
* [Networking & Surge Tank](docs/networking.md)
|
|
49
|
-
* [
|
|
50
|
-
* [
|
|
51
|
-
* [
|
|
52
|
-
* [
|
|
53
|
-
* [
|
|
54
|
-
* [
|
|
52
|
+
* [Architecture & Pipeline](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/architecture.md)
|
|
53
|
+
* [KESP Wire Protocol](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/protocol.md)
|
|
54
|
+
* [Networking & Surge Tank](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/networking.md)
|
|
55
|
+
* [Limits & Budgets](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/limits.md)
|
|
56
|
+
* [Persistence & I/O Drivetrain](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/persistence.md)
|
|
57
|
+
* [Command Spellbook](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/commands.md)
|
|
58
|
+
* [TTL & Lifespan Mechanics](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/ttl.md)
|
|
59
|
+
* [Streams & Consumer Groups](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/streams.md)
|
|
60
|
+
* [Pub/Sub Broadcasts](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/pubsub.md)
|
|
61
|
+
* [Benchmarks & Telemetry](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/benchmarks.md)
|
|
62
|
+
* [Future Roadmap](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/roadmap.md)
|
|
55
63
|
|
|
56
64
|
---
|
|
57
65
|
|
|
@@ -107,6 +115,20 @@ ZRANGE leaderboard
|
|
|
107
115
|
|
|
108
116
|
Implemented using a custom Skip List inspired by Redis sorted set internals, with rank-aware span tracking for fast positional queries.
|
|
109
117
|
|
|
118
|
+
### Streams & Consumer Groups
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
XADD orders * item book qty 2
|
|
122
|
+
XREAD BLOCK 5000 STREAMS orders $
|
|
123
|
+
|
|
124
|
+
XGROUP CREATE orders shipping 0
|
|
125
|
+
XREADGROUP GROUP shipping worker1 COUNT 10 STREAMS orders >
|
|
126
|
+
XPENDING orders shipping
|
|
127
|
+
XACK orders shipping 1718000000000-0
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Append-only event logs with blocking reads, and consumer groups that share a stream between workers with a pending list and acknowledgements. Streams persist through the AOF, `COMPACT` and snapshots, and replicate with the exact IDs and group state the master chose. See [Streams & Consumer Groups](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/streams.md).
|
|
131
|
+
|
|
110
132
|
---
|
|
111
133
|
|
|
112
134
|
## ⏳ Expiration Support
|
|
@@ -290,6 +312,7 @@ The project includes automated tests covering:
|
|
|
290
312
|
* data structures
|
|
291
313
|
* persistence recovery
|
|
292
314
|
* expiration behavior
|
|
315
|
+
* streams and consumer groups, from the data structure up to live TCP sessions
|
|
293
316
|
|
|
294
317
|
Additional coverage is actively being expanded as the project evolves.
|
|
295
318
|
|
|
@@ -361,6 +384,7 @@ Current limitations include:
|
|
|
361
384
|
* limited fault tolerance compared to production databases
|
|
362
385
|
* no memory-based eviction ceiling (LRU tracks usage, but isn't yet tied to a hard memory limit)
|
|
363
386
|
* AOF persistence is not buffered — writes can trigger a sync depending on config
|
|
387
|
+
* consumer groups have no `XCLAIM`/`XAUTOCLAIM` yet; the full list of stream limits is in [docs/streams.md](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/streams.md#-known-limits)
|
|
364
388
|
|
|
365
389
|
These limitations are intentional learning opportunities and areas of active development.
|
|
366
390
|
|
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: kedis-python
|
|
3
|
-
Version: 0.1.3
|
|
4
|
-
Summary: A Redis-inspired in-memory store in Python.
|
|
5
|
-
Author: V SS Karthik
|
|
6
|
-
License-Expression: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/Hogwarts-coder10/kedis-python
|
|
8
|
-
Requires-Python: >=3.9
|
|
9
|
-
Description-Content-Type: text/markdown
|
|
10
|
-
License-File: LICENSE
|
|
11
|
-
Requires-Dist: rich
|
|
12
|
-
Requires-Dist: pyfiglet
|
|
13
|
-
Dynamic: license-file
|
|
14
|
-
|
|
15
1
|
# 🚀 Kedis-Python
|
|
16
2
|
|
|
17
3
|
**A Redis-inspired in-memory datastore built in pure Python to explore storage engines, networking, persistence, caching, and systems architecture.**
|
|
@@ -43,15 +29,17 @@ Rather than focusing on Redis compatibility, Kedis focuses on understanding the
|
|
|
43
29
|
## 📚 Engine Documentation
|
|
44
30
|
|
|
45
31
|
Dive into the internal grimoire to understand how Kedis achieves microsecond latency:
|
|
46
|
-
* [Architecture & Pipeline](docs/architecture.md)
|
|
47
|
-
* [KESP Wire Protocol](docs/protocol.md)
|
|
48
|
-
* [Networking & Surge Tank](docs/networking.md)
|
|
49
|
-
* [
|
|
50
|
-
* [
|
|
51
|
-
* [
|
|
52
|
-
* [
|
|
53
|
-
* [
|
|
54
|
-
* [
|
|
32
|
+
* [Architecture & Pipeline](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/architecture.md)
|
|
33
|
+
* [KESP Wire Protocol](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/protocol.md)
|
|
34
|
+
* [Networking & Surge Tank](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/networking.md)
|
|
35
|
+
* [Limits & Budgets](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/limits.md)
|
|
36
|
+
* [Persistence & I/O Drivetrain](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/persistence.md)
|
|
37
|
+
* [Command Spellbook](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/commands.md)
|
|
38
|
+
* [TTL & Lifespan Mechanics](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/ttl.md)
|
|
39
|
+
* [Streams & Consumer Groups](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/streams.md)
|
|
40
|
+
* [Pub/Sub Broadcasts](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/pubsub.md)
|
|
41
|
+
* [Benchmarks & Telemetry](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/benchmarks.md)
|
|
42
|
+
* [Future Roadmap](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/roadmap.md)
|
|
55
43
|
|
|
56
44
|
---
|
|
57
45
|
|
|
@@ -107,6 +95,20 @@ ZRANGE leaderboard
|
|
|
107
95
|
|
|
108
96
|
Implemented using a custom Skip List inspired by Redis sorted set internals, with rank-aware span tracking for fast positional queries.
|
|
109
97
|
|
|
98
|
+
### Streams & Consumer Groups
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
XADD orders * item book qty 2
|
|
102
|
+
XREAD BLOCK 5000 STREAMS orders $
|
|
103
|
+
|
|
104
|
+
XGROUP CREATE orders shipping 0
|
|
105
|
+
XREADGROUP GROUP shipping worker1 COUNT 10 STREAMS orders >
|
|
106
|
+
XPENDING orders shipping
|
|
107
|
+
XACK orders shipping 1718000000000-0
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Append-only event logs with blocking reads, and consumer groups that share a stream between workers with a pending list and acknowledgements. Streams persist through the AOF, `COMPACT` and snapshots, and replicate with the exact IDs and group state the master chose. See [Streams & Consumer Groups](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/streams.md).
|
|
111
|
+
|
|
110
112
|
---
|
|
111
113
|
|
|
112
114
|
## ⏳ Expiration Support
|
|
@@ -290,6 +292,7 @@ The project includes automated tests covering:
|
|
|
290
292
|
* data structures
|
|
291
293
|
* persistence recovery
|
|
292
294
|
* expiration behavior
|
|
295
|
+
* streams and consumer groups, from the data structure up to live TCP sessions
|
|
293
296
|
|
|
294
297
|
Additional coverage is actively being expanded as the project evolves.
|
|
295
298
|
|
|
@@ -361,6 +364,7 @@ Current limitations include:
|
|
|
361
364
|
* limited fault tolerance compared to production databases
|
|
362
365
|
* no memory-based eviction ceiling (LRU tracks usage, but isn't yet tied to a hard memory limit)
|
|
363
366
|
* AOF persistence is not buffered — writes can trigger a sync depending on config
|
|
367
|
+
* consumer groups have no `XCLAIM`/`XAUTOCLAIM` yet; the full list of stream limits is in [docs/streams.md](https://github.com/Hogwarts-coder10/kedis-python/blob/main/docs/streams.md#-known-limits)
|
|
364
368
|
|
|
365
369
|
These limitations are intentional learning opportunities and areas of active development.
|
|
366
370
|
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""On-disk record format for the append-only file (AOF).
|
|
2
|
+
|
|
3
|
+
Every logged command is one KESP-framed record, the same shape the wire
|
|
4
|
+
protocol uses for commands:
|
|
5
|
+
|
|
6
|
+
A<count>\\n then, for each token: S<length>\\n<payload>\\n
|
|
7
|
+
|
|
8
|
+
Lengths count bytes of the UTF-8 payload, so a token can hold anything: spaces,
|
|
9
|
+
newlines, tabs, an empty string, text that looks like another command. The old
|
|
10
|
+
format joined tokens with spaces on one line, so a value could not survive a
|
|
11
|
+
restart and a value containing a newline could plant commands in the log.
|
|
12
|
+
|
|
13
|
+
Strings are encoded with "surrogatepass", so every Python str round-trips
|
|
14
|
+
exactly, lone surrogates included.
|
|
15
|
+
|
|
16
|
+
Files written by older versions hold one whitespace-separated command per line.
|
|
17
|
+
scan() still reads those, line by line, and a file may mix both: old lines
|
|
18
|
+
first, framed records after.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import re
|
|
22
|
+
|
|
23
|
+
_COUNT = re.compile(rb"(?:0|[1-9][0-9]*)")
|
|
24
|
+
MAX_HEADER_LINE = 32 # "A<n>" / "S<n>" lines are never longer than this
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def encode_record(tokens) -> bytes:
|
|
28
|
+
"""One command as a framed record."""
|
|
29
|
+
parts = [b"A%d\n" % len(tokens)]
|
|
30
|
+
for token in tokens:
|
|
31
|
+
raw = str(token).encode("utf-8", "surrogatepass")
|
|
32
|
+
parts.append(b"S%d\n" % len(raw))
|
|
33
|
+
parts.append(raw)
|
|
34
|
+
parts.append(b"\n")
|
|
35
|
+
return b"".join(parts)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ScanResult:
|
|
39
|
+
"""How a scan ended.
|
|
40
|
+
|
|
41
|
+
status: "ok" every byte was consumed
|
|
42
|
+
"torn" the file ends in the middle of a record (a crash while
|
|
43
|
+
writing); everything before good_end is intact
|
|
44
|
+
"corrupt" a record is malformed; see reason
|
|
45
|
+
good_end: offset just past the last complete record or line
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
__slots__ = ("good_end", "reason", "status")
|
|
49
|
+
|
|
50
|
+
def __init__(self):
|
|
51
|
+
self.status = "ok"
|
|
52
|
+
self.good_end = 0
|
|
53
|
+
self.reason = ""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _count(buf, start: int, end: int):
|
|
57
|
+
field = bytes(buf[start:end])
|
|
58
|
+
return int(field) if _COUNT.fullmatch(field) else None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def scan(buf, result: ScanResult):
|
|
62
|
+
"""Yields (tokens, end_offset) for each command in buf, a bytes-like object
|
|
63
|
+
that supports find() and slicing (bytes or mmap). Stops at the first
|
|
64
|
+
incomplete or malformed record and says why in result."""
|
|
65
|
+
total = len(buf)
|
|
66
|
+
pos = 0
|
|
67
|
+
|
|
68
|
+
def stop(status: str, reason: str):
|
|
69
|
+
result.status = status
|
|
70
|
+
result.reason = reason
|
|
71
|
+
|
|
72
|
+
while pos < total:
|
|
73
|
+
if buf[pos : pos + 1] == b"A":
|
|
74
|
+
following = buf[pos + 1 : pos + 2]
|
|
75
|
+
if following == b"":
|
|
76
|
+
return stop("torn", "file ends inside a record header")
|
|
77
|
+
# A legacy command word starts "A" + a letter (AUTH, ...). Anything
|
|
78
|
+
# else after the "A" is a record header and must validate as one.
|
|
79
|
+
framed = not following.isalpha()
|
|
80
|
+
else:
|
|
81
|
+
framed = False
|
|
82
|
+
|
|
83
|
+
if not framed:
|
|
84
|
+
# Legacy line: whitespace-separated tokens, as older versions wrote.
|
|
85
|
+
nl = buf.find(b"\n", pos)
|
|
86
|
+
line_end = total if nl == -1 else nl
|
|
87
|
+
next_pos = total if nl == -1 else nl + 1
|
|
88
|
+
tokens = bytes(buf[pos:line_end]).decode("utf-8", "replace").split()
|
|
89
|
+
result.good_end = next_pos
|
|
90
|
+
if tokens:
|
|
91
|
+
yield tokens, next_pos
|
|
92
|
+
pos = next_pos
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
nl = buf.find(b"\n", pos)
|
|
96
|
+
if nl == -1:
|
|
97
|
+
if total - pos > MAX_HEADER_LINE:
|
|
98
|
+
return stop("corrupt", "array header too long")
|
|
99
|
+
return stop("torn", "file ends inside a record header")
|
|
100
|
+
if nl - pos > MAX_HEADER_LINE:
|
|
101
|
+
return stop("corrupt", "array header too long")
|
|
102
|
+
count = _count(buf, pos + 1, nl)
|
|
103
|
+
if count is None:
|
|
104
|
+
return stop("corrupt", "invalid array header")
|
|
105
|
+
|
|
106
|
+
cursor = nl + 1
|
|
107
|
+
tokens = []
|
|
108
|
+
for _ in range(count):
|
|
109
|
+
if cursor >= total:
|
|
110
|
+
return stop("torn", "file ends between tokens")
|
|
111
|
+
if buf[cursor : cursor + 1] != b"S":
|
|
112
|
+
return stop("corrupt", "expected a string header")
|
|
113
|
+
nl = buf.find(b"\n", cursor)
|
|
114
|
+
if nl == -1:
|
|
115
|
+
if total - cursor > MAX_HEADER_LINE:
|
|
116
|
+
return stop("corrupt", "string header too long")
|
|
117
|
+
return stop("torn", "file ends inside a string header")
|
|
118
|
+
if nl - cursor > MAX_HEADER_LINE:
|
|
119
|
+
return stop("corrupt", "string header too long")
|
|
120
|
+
length = _count(buf, cursor + 1, nl)
|
|
121
|
+
if length is None:
|
|
122
|
+
return stop("corrupt", "invalid string length")
|
|
123
|
+
start = nl + 1
|
|
124
|
+
end = start + length
|
|
125
|
+
if end + 1 > total:
|
|
126
|
+
return stop("torn", "file ends inside a string")
|
|
127
|
+
if buf[end : end + 1] != b"\n":
|
|
128
|
+
return stop("corrupt", "string is not newline-terminated")
|
|
129
|
+
try:
|
|
130
|
+
tokens.append(bytes(buf[start:end]).decode("utf-8", "surrogatepass"))
|
|
131
|
+
except UnicodeDecodeError:
|
|
132
|
+
return stop("corrupt", "string is not valid UTF-8")
|
|
133
|
+
cursor = end + 1
|
|
134
|
+
|
|
135
|
+
result.good_end = cursor
|
|
136
|
+
if tokens:
|
|
137
|
+
yield tokens, cursor
|
|
138
|
+
pos = cursor
|