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.
Files changed (80) hide show
  1. {kedis_python-0.1.3/kedis_python.egg-info → kedis_python-0.2.0}/PKG-INFO +34 -10
  2. kedis_python-0.1.3/PKG-INFO → kedis_python-0.2.0/README.md +27 -23
  3. kedis_python-0.2.0/kedis_python/aof.py +138 -0
  4. kedis_python-0.2.0/kedis_python/commands.py +1202 -0
  5. kedis_python-0.2.0/kedis_python/help_data.py +711 -0
  6. kedis_python-0.2.0/kedis_python/hyperloglog.py +78 -0
  7. {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python/main.py +348 -158
  8. kedis_python-0.2.0/kedis_python/network.py +114 -0
  9. kedis_python-0.2.0/kedis_python/parser.py +491 -0
  10. {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python/server.py +292 -65
  11. kedis_python-0.2.0/kedis_python/store.py +1838 -0
  12. kedis_python-0.2.0/kedis_python/stream.py +416 -0
  13. kedis_python-0.2.0/kedis_python/textcodec.py +32 -0
  14. kedis_python-0.2.0/kedis_python/ui.py +366 -0
  15. kedis_python-0.1.3/README.md → kedis_python-0.2.0/kedis_python.egg-info/PKG-INFO +47 -9
  16. kedis_python-0.2.0/kedis_python.egg-info/SOURCES.txt +72 -0
  17. {kedis_python-0.1.3 → kedis_python-0.2.0}/pyproject.toml +11 -6
  18. kedis_python-0.2.0/tests/test_aof_persistence.py +280 -0
  19. kedis_python-0.2.0/tests/test_aof_records.py +140 -0
  20. {kedis_python-0.1.3 → kedis_python-0.2.0}/tests/test_bench.py +2 -2
  21. kedis_python-0.2.0/tests/test_binary_safety.py +340 -0
  22. kedis_python-0.2.0/tests/test_bitmap_persistence.py +280 -0
  23. kedis_python-0.2.0/tests/test_bitmaps.py +189 -0
  24. kedis_python-0.2.0/tests/test_cli_housekeep.py +121 -0
  25. kedis_python-0.2.0/tests/test_cli_stream_view.py +118 -0
  26. kedis_python-0.2.0/tests/test_data_replies.py +123 -0
  27. kedis_python-0.2.0/tests/test_del_exists.py +107 -0
  28. kedis_python-0.2.0/tests/test_docs_data_types.py +152 -0
  29. kedis_python-0.2.0/tests/test_docs_streams.py +91 -0
  30. kedis_python-0.2.0/tests/test_help_data.py +211 -0
  31. kedis_python-0.2.0/tests/test_history_setup.py +74 -0
  32. kedis_python-0.2.0/tests/test_housekeeping.py +282 -0
  33. kedis_python-0.2.0/tests/test_hyperloglog.py +419 -0
  34. kedis_python-0.2.0/tests/test_inline_block.py +112 -0
  35. kedis_python-0.2.0/tests/test_kesp_decoder.py +254 -0
  36. kedis_python-0.2.0/tests/test_kesp_encoder.py +208 -0
  37. {kedis_python-0.1.3 → kedis_python-0.2.0}/tests/test_lists.py +2 -2
  38. kedis_python-0.2.0/tests/test_lrange.py +88 -0
  39. kedis_python-0.2.0/tests/test_parser_casing.py +87 -0
  40. kedis_python-0.2.0/tests/test_parser_framing.py +175 -0
  41. kedis_python-0.2.0/tests/test_parser_harden.py +150 -0
  42. kedis_python-0.2.0/tests/test_parser_limits.py +138 -0
  43. kedis_python-0.2.0/tests/test_pubsub.py +220 -0
  44. kedis_python-0.2.0/tests/test_reply_limits.py +123 -0
  45. kedis_python-0.2.0/tests/test_server_limits.py +345 -0
  46. kedis_python-0.2.0/tests/test_server_protocol_errors.py +112 -0
  47. kedis_python-0.2.0/tests/test_stream_commands.py +299 -0
  48. kedis_python-0.2.0/tests/test_stream_core.py +219 -0
  49. kedis_python-0.2.0/tests/test_stream_groups_commands.py +433 -0
  50. kedis_python-0.2.0/tests/test_stream_groups_core.py +285 -0
  51. kedis_python-0.2.0/tests/test_stream_groups_server.py +438 -0
  52. kedis_python-0.2.0/tests/test_stream_groups_store.py +609 -0
  53. kedis_python-0.2.0/tests/test_stream_server.py +326 -0
  54. kedis_python-0.2.0/tests/test_stream_store.py +374 -0
  55. kedis_python-0.2.0/tests/test_type.py +77 -0
  56. kedis_python-0.2.0/tests/test_version.py +34 -0
  57. kedis_python-0.2.0/tests/test_zadd.py +98 -0
  58. kedis_python-0.1.3/kedis_python/commands.py +0 -512
  59. kedis_python-0.1.3/kedis_python/network.py +0 -60
  60. kedis_python-0.1.3/kedis_python/parser.py +0 -132
  61. kedis_python-0.1.3/kedis_python/store.py +0 -873
  62. kedis_python-0.1.3/kedis_python/ui.py +0 -189
  63. kedis_python-0.1.3/kedis_python.egg-info/SOURCES.txt +0 -29
  64. {kedis_python-0.1.3 → kedis_python-0.2.0}/LICENSE +0 -0
  65. {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python/__init__.py +0 -0
  66. {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python/benchmark.py +0 -0
  67. {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python/benchmark_asyncio.py +0 -0
  68. {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python/kesp_cli.py +0 -0
  69. {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python/replica.py +0 -0
  70. {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python/simulator.py +0 -0
  71. {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python/skiplist.py +0 -0
  72. {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python.egg-info/dependency_links.txt +0 -0
  73. {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python.egg-info/entry_points.txt +0 -0
  74. {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python.egg-info/requires.txt +0 -0
  75. {kedis_python-0.1.3 → kedis_python-0.2.0}/kedis_python.egg-info/top_level.txt +0 -0
  76. {kedis_python-0.1.3 → kedis_python-0.2.0}/setup.cfg +0 -0
  77. {kedis_python-0.1.3 → kedis_python-0.2.0}/tests/test_kesp.py +0 -0
  78. {kedis_python-0.1.3 → kedis_python-0.2.0}/tests/test_kesp2.py +0 -0
  79. {kedis_python-0.1.3 → kedis_python-0.2.0}/tests/test_lru.py +0 -0
  80. {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.1.3
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
- * [Persistence & I/O Drivetrain](docs/persistence.md)
50
- * [Command Spellbook](docs/commands.md)
51
- * [TTL & Lifespan Mechanics](docs/ttl.md)
52
- * [Pub/Sub Broadcasts](docs/pubsub.md)
53
- * [Benchmarks & Telemetry](docs/benchmarks.md)
54
- * [Future Roadmap](docs/roadmap.md)
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
- * [Persistence & I/O Drivetrain](docs/persistence.md)
50
- * [Command Spellbook](docs/commands.md)
51
- * [TTL & Lifespan Mechanics](docs/ttl.md)
52
- * [Pub/Sub Broadcasts](docs/pubsub.md)
53
- * [Benchmarks & Telemetry](docs/benchmarks.md)
54
- * [Future Roadmap](docs/roadmap.md)
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