mmapq 0.1.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 (105) hide show
  1. mmapq-0.1.0/LICENSE +29 -0
  2. mmapq-0.1.0/MANIFEST.in +6 -0
  3. mmapq-0.1.0/PKG-INFO +427 -0
  4. mmapq-0.1.0/README.md +399 -0
  5. mmapq-0.1.0/c/include/batch_payload_iterator.h +141 -0
  6. mmapq-0.1.0/c/include/batch_queue_transfer.h +274 -0
  7. mmapq-0.1.0/c/include/indexed_appender.h +225 -0
  8. mmapq-0.1.0/c/include/indexed_archiver.h +148 -0
  9. mmapq-0.1.0/c/include/indexed_batch_appender.h +295 -0
  10. mmapq-0.1.0/c/include/indexed_batch_archiver.h +125 -0
  11. mmapq-0.1.0/c/include/indexed_batch_poller.h +197 -0
  12. mmapq-0.1.0/c/include/indexed_batch_queue.h +168 -0
  13. mmapq-0.1.0/c/include/indexed_batch_reader.h +244 -0
  14. mmapq-0.1.0/c/include/indexed_poller.h +190 -0
  15. mmapq-0.1.0/c/include/indexed_queue.h +229 -0
  16. mmapq-0.1.0/c/include/indexed_reader.h +172 -0
  17. mmapq-0.1.0/c/include/mmap_platform.h +160 -0
  18. mmapq-0.1.0/c/include/mmapq.h +27 -0
  19. mmapq-0.1.0/c/include/poller.h +54 -0
  20. mmapq-0.1.0/c/include/queue_transfer.h +312 -0
  21. mmapq-0.1.0/c/include/runtime.h +316 -0
  22. mmapq-0.1.0/c/src/appender_id_pool.c +177 -0
  23. mmapq-0.1.0/c/src/appender_meta.c +146 -0
  24. mmapq-0.1.0/c/src/batch_payload_iterator.c +133 -0
  25. mmapq-0.1.0/c/src/batch_queue_latency_probe.c +189 -0
  26. mmapq-0.1.0/c/src/batch_queue_transfer.c +740 -0
  27. mmapq-0.1.0/c/src/file_initialiser.c +40 -0
  28. mmapq-0.1.0/c/src/file_mapper_rolled.c +321 -0
  29. mmapq-0.1.0/c/src/file_mapper_single_ro.c +168 -0
  30. mmapq-0.1.0/c/src/file_mapper_single_rw.c +244 -0
  31. mmapq-0.1.0/c/src/fixed_size_region.c +184 -0
  32. mmapq-0.1.0/c/src/header_reader.c +161 -0
  33. mmapq-0.1.0/c/src/indexed_appender.c +690 -0
  34. mmapq-0.1.0/c/src/indexed_archiver.c +498 -0
  35. mmapq-0.1.0/c/src/indexed_batch_appender.c +507 -0
  36. mmapq-0.1.0/c/src/indexed_batch_archiver.c +421 -0
  37. mmapq-0.1.0/c/src/indexed_batch_poller.c +243 -0
  38. mmapq-0.1.0/c/src/indexed_batch_queue.c +182 -0
  39. mmapq-0.1.0/c/src/indexed_batch_reader.c +298 -0
  40. mmapq-0.1.0/c/src/indexed_poller.c +336 -0
  41. mmapq-0.1.0/c/src/indexed_queue.c +279 -0
  42. mmapq-0.1.0/c/src/indexed_reader.c +309 -0
  43. mmapq-0.1.0/c/src/internal/appender_cache.h +160 -0
  44. mmapq-0.1.0/c/src/internal/appender_id_pool.h +107 -0
  45. mmapq-0.1.0/c/src/internal/appender_meta.h +116 -0
  46. mmapq-0.1.0/c/src/internal/batch_payload_iterator_internal.h +55 -0
  47. mmapq-0.1.0/c/src/internal/batch_queue_latency_probe.h +40 -0
  48. mmapq-0.1.0/c/src/internal/export_meta.h +98 -0
  49. mmapq-0.1.0/c/src/internal/file_initialiser.h +49 -0
  50. mmapq-0.1.0/c/src/internal/file_mapper.h +164 -0
  51. mmapq-0.1.0/c/src/internal/fixed_size_region.h +57 -0
  52. mmapq-0.1.0/c/src/internal/header_codec.h +220 -0
  53. mmapq-0.1.0/c/src/internal/header_reader.h +115 -0
  54. mmapq-0.1.0/c/src/internal/mmap_assert.h +34 -0
  55. mmapq-0.1.0/c/src/internal/mmap_stats.h +62 -0
  56. mmapq-0.1.0/c/src/internal/payload_iterator.h +157 -0
  57. mmapq-0.1.0/c/src/internal/payload_walk.h +54 -0
  58. mmapq-0.1.0/c/src/internal/platform_internal.h +40 -0
  59. mmapq-0.1.0/c/src/internal/queue_files.h +85 -0
  60. mmapq-0.1.0/c/src/internal/queue_latency_probe.h +70 -0
  61. mmapq-0.1.0/c/src/internal/queue_meta.h +227 -0
  62. mmapq-0.1.0/c/src/internal/queue_region_accessor.h +160 -0
  63. mmapq-0.1.0/c/src/internal/region.h +164 -0
  64. mmapq-0.1.0/c/src/internal/region_accessor.h +124 -0
  65. mmapq-0.1.0/c/src/internal/region_ring.h +109 -0
  66. mmapq-0.1.0/c/src/internal/region_ring_accessor.h +78 -0
  67. mmapq-0.1.0/c/src/internal/uncommitted_payload_iterator.h +174 -0
  68. mmapq-0.1.0/c/src/mmapq_strerror.c +19 -0
  69. mmapq-0.1.0/c/src/payload_iterator.c +177 -0
  70. mmapq-0.1.0/c/src/payload_walk.c +87 -0
  71. mmapq-0.1.0/c/src/queue_files.c +70 -0
  72. mmapq-0.1.0/c/src/queue_latency_probe.c +205 -0
  73. mmapq-0.1.0/c/src/queue_meta.c +245 -0
  74. mmapq-0.1.0/c/src/queue_region_accessor.c +301 -0
  75. mmapq-0.1.0/c/src/queue_transfer.c +660 -0
  76. mmapq-0.1.0/c/src/region_async.c +460 -0
  77. mmapq-0.1.0/c/src/region_ring.c +365 -0
  78. mmapq-0.1.0/c/src/region_ring_accessor.c +205 -0
  79. mmapq-0.1.0/c/src/region_sync.c +250 -0
  80. mmapq-0.1.0/c/src/runtime.c +861 -0
  81. mmapq-0.1.0/c/src/uncommitted_payload_iterator.c +383 -0
  82. mmapq-0.1.0/pyproject.toml +42 -0
  83. mmapq-0.1.0/setup.cfg +4 -0
  84. mmapq-0.1.0/setup.py +375 -0
  85. mmapq-0.1.0/src/mmapq/__init__.py +105 -0
  86. mmapq-0.1.0/src/mmapq/_cdata.py +23 -0
  87. mmapq-0.1.0/src/mmapq/_errors.py +78 -0
  88. mmapq-0.1.0/src/mmapq/archiver.py +155 -0
  89. mmapq-0.1.0/src/mmapq/batch_appender.py +123 -0
  90. mmapq-0.1.0/src/mmapq/batch_poller.py +144 -0
  91. mmapq-0.1.0/src/mmapq/batch_queue.py +148 -0
  92. mmapq-0.1.0/src/mmapq/reader.py +328 -0
  93. mmapq-0.1.0/src/mmapq/runtime.py +56 -0
  94. mmapq-0.1.0/src/mmapq/single_appender.py +82 -0
  95. mmapq-0.1.0/src/mmapq/single_poller.py +117 -0
  96. mmapq-0.1.0/src/mmapq/single_queue.py +126 -0
  97. mmapq-0.1.0/src/mmapq/transfer.py +449 -0
  98. mmapq-0.1.0/src/mmapq/types.py +79 -0
  99. mmapq-0.1.0/src/mmapq.egg-info/PKG-INFO +427 -0
  100. mmapq-0.1.0/src/mmapq.egg-info/SOURCES.txt +138 -0
  101. mmapq-0.1.0/src/mmapq.egg-info/dependency_links.txt +1 -0
  102. mmapq-0.1.0/src/mmapq.egg-info/requires.txt +4 -0
  103. mmapq-0.1.0/src/mmapq.egg-info/top_level.txt +1 -0
  104. mmapq-0.1.0/tests/test_poller_lifecycle.py +154 -0
  105. mmapq-0.1.0/tests/test_queue.py +1865 -0
mmapq-0.1.0/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Anton Anufriev, Algoteq Pty Ltd, and contributors
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice,
9
+ this list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
+ POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,6 @@
1
+ recursive-include c/src *.c
2
+ recursive-include c/src *.h
3
+ recursive-include c/include *.h
4
+ include LICENSE README.md
5
+ prune c/test
6
+ prune c/doc
mmapq-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,427 @@
1
+ Metadata-Version: 2.4
2
+ Name: mmapq
3
+ Version: 0.1.0
4
+ Summary: Python bindings for mmapq — low-latency memory-mapped queue
5
+ Author: Anton Anufriev
6
+ License: BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/algoteq-labs/mmapq
8
+ Project-URL: Source, https://github.com/algoteq-labs/mmapq-py
9
+ Project-URL: BugTracker, https://github.com/algoteq-labs/mmapq-py/issues
10
+ Keywords: mmap,shared-memory,queue,lock-free,high-performance
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: C
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: System :: Distributed Computing
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: cffi
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest; extra == "dev"
26
+ Dynamic: license-file
27
+ Dynamic: requires-python
28
+
29
+ # mmapq
30
+
31
+ [![License: BSD-3-Clause](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](LICENSE)
32
+
33
+ **Python bindings for [mmapq](https://github.com/algoteq-labs/mmapq)** — a persistent,
34
+ indexed, replayable inter-process queue backed by memory-mapped files. No
35
+ broker, no daemon; the queue is just files.
36
+
37
+ ## Why Python for a sub-microsecond queue?
38
+
39
+ Not for the hot path — for the *read* side. The pattern mmapq is built for
40
+ fast producers (C or Rust: market data, sequenced commands, sensor and
41
+ telemetry streams) on one side of a persistent, indexed log, and lets any
42
+ number of consumers read that log independently. Python is the natural
43
+ language for the consumer: replay, analysis, notebooks, dashboards,
44
+ projections into pandas — over the exact same queue files the fast
45
+ producers write, with no serialization bridge, no message broker, and no
46
+ copy out of shared memory beyond the one `bytes()` you ask for.
47
+
48
+ Because the queue is indexed and replayable, the Python side gets the
49
+ capability the whole architecture exists for: **replay is the product.**
50
+ Copy a queue directory from production to a laptop and re-read it from index
51
+ zero — the same operation that drives recovery and replication is also how
52
+ you debug, backtest, and audit. "What did command K do" is a single indexed
53
+ read from a Python script. `doc/event-sourcing-guide.md` in the
54
+ [mmapq](https://github.com/algoteq-labs/mmapq) repository describes the
55
+ full pattern; `doc/feature-matrix.md` gives exact coverage across C, Rust,
56
+ and Python.
57
+
58
+ ## Quick start
59
+
60
+ ### Single-message queue
61
+
62
+ ```python
63
+ from mmapq import IndexedQueue, NextMove
64
+
65
+ q = IndexedQueue.create("/dev/shm", "my_queue")
66
+
67
+ appender = q.create_appender()
68
+ appender.append(b"hello world") # committed atomically
69
+
70
+ def handler(index, appender_id, data):
71
+ print(f"[{index}] {data!r}")
72
+ return NextMove.ADVANCE
73
+
74
+ poller = q.create_poller()
75
+ poller.poll(handler)
76
+ ```
77
+
78
+ ### Batch queue (atomic multi-message commit)
79
+
80
+ ```python
81
+ from mmapq import IndexedBatchQueue, NextMove
82
+
83
+ q = IndexedBatchQueue.create("/dev/shm", "events")
84
+ appender = q.create_appender()
85
+
86
+ with appender.start_batch() as batch: # context manager = transaction
87
+ batch.append(b"event 1")
88
+ batch.append(b"event 2")
89
+ batch.append(b"event 3")
90
+ # leaving the block discards the batch — call batch.commit() to persist;
91
+ # an exception inside the block also discards it, so uncommitted data
92
+ # never becomes visible. This is the command/event rollback semantics.
93
+ ```
94
+
95
+ ### Reading and replay (random access)
96
+
97
+ ```python
98
+ from mmapq import IndexedReader, export_queue
99
+
100
+ reader = IndexedReader.create("/dev/shm", "events")
101
+ first = reader.read_first() # bytes | None
102
+ last = reader.read_last()
103
+ msg = reader.read(42) # read any committed index
104
+ print(reader.last_index())
105
+
106
+ # Export the durable stream for long-term storage or offline analysis:
107
+ export_queue("/dev/shm", "events", "/data/events-2026-07.mmapq")
108
+ ```
109
+
110
+ ## A note on validation and safety
111
+
112
+ The Python extension compiles the C library with `-DNDEBUG`, so it runs the
113
+ *release* build with internal asserts stripped. Input validation therefore
114
+ happens in the Python layer and is mandatory, not defensive decoration:
115
+ empty appends, out-of-range indices, and closed-handle use are checked
116
+ before the FFI call and raise `ValueError` / `QueueClosedError`. Corruption
117
+ detected on open or during a poll raises `QueueCorruptError` (distinct from
118
+ a transient timeout) rather than returning silently. Every wrapper object
119
+ supports explicit `close()`, is a context manager, and is finalized safely
120
+ at interpreter shutdown.
121
+
122
+ The raw cffi `FFI`/`lib` objects are deliberately **not** exported as
123
+ `mmapq.ffi` / `mmapq.lib`. Every C symbol reachable through them has no
124
+ argument checking — one bad call segfaults the interpreter. If you need
125
+ low-level access (e.g. iterator access), reach the raw objects only through
126
+ the documented escape hatches, and only when you know what you are doing:
127
+
128
+ ```python
129
+ from mmapq import unsafe_ffi, unsafe_lib
130
+ ffi = unsafe_ffi() # cffi FFI object
131
+ lib = unsafe_lib() # loaded C library
132
+ ```
133
+
134
+ ## Error messages
135
+
136
+ Exception messages are the single source of truth in the C library
137
+ (`src/mmapq_strerror.c`): the Python layer maps return codes to exception
138
+ *types* only (e.g. `QueueCorruptError`), and the message text comes from
139
+ `mmapq_strerror` via cffi. This guarantees the text in Python matches C
140
+ by construction and never drifts independently.
141
+
142
+ | Return code | Exception type |
143
+ |---|---:|
144
+ | `MMAPQ_ETIMEDOUT` (-1) | `MmapqError` |
145
+ | `MMAPQ_EINVAL` (-2) | `ValueError` |
146
+ | `MMAPQ_ECLOSED` (-3) | `QueueClosedError` |
147
+ | `MMAPQ_ECORRUPT` (-4) | `QueueCorruptError` |
148
+ | `MMAPQ_ESCHEMA` (-5) | `SchemaMismatchError` |
149
+ | `MMAPQ_EEXIST` (-6) | `QueueExistsError` |
150
+ | `MMAPQ_ENOENT` (-7) | `FileNotFoundError` |
151
+ | `MMAPQ_EIDCONFLICT` (-8) | `AppenderIdConflictError` |
152
+ | `MMAPQ_ENOMEM` (-9) | `MmapOutOfMemoryError` |
153
+ | `MMAPQ_EIO` (-10) | `MmapqError` |
154
+ | `MMAPQ_ENOTREADY` (-11) | `MmapqError` |
155
+
156
+ ## Installation
157
+
158
+ ```bash
159
+ pip install mmapq
160
+ ```
161
+
162
+ ### From source
163
+
164
+ ```bash
165
+ pip install -e . # builds the cffi extension against the C sources
166
+ ```
167
+
168
+ ## API
169
+
170
+ ### Exports
171
+
172
+ ```python
173
+ from mmapq import (
174
+ # Shared types
175
+ NextMove, # Handler return: ADVANCE, STOP, RETREAT
176
+ PollResult, # Poll result: END_OF_STREAM, ADVANCED, etc.
177
+ BatchPollResult, # Alias for PollResult
178
+ BatchEntryStatus, # Batch availability
179
+ EntryStatus, # Entry availability (reader)
180
+
181
+ # Single-message queue
182
+ IndexedQueue, # Queue factory
183
+ IndexedAppender, # Producer
184
+ IndexedPoller, # Sequential consumer
185
+
186
+ # Batch queue
187
+ IndexedBatchQueue, # Queue factory
188
+ IndexedBatchPoller, # Sequential batch consumer
189
+ IndexedBatchAppender, # Batch producer
190
+ BatchWriter, # In-progress batch (context manager)
191
+
192
+ # Random-access readers
193
+ IndexedReader, # Single-message reader
194
+ IndexedBatchReader, # Batch reader
195
+
196
+ # Exceptions
197
+ MmapqError, # Base exception
198
+ QueueClosedError, # Closed handle
199
+ QueueCorruptError, # On-disk corruption
200
+ SchemaMismatchError, # Schema/type mismatch
201
+ QueueExistsError, # Destination already exists
202
+ AppenderIdConflictError,# Appender ID conflict
203
+ MmapOutOfMemoryError, # Allocation failure
204
+
205
+ # Runtime
206
+ Runtime, # Async region mapper
207
+
208
+ # Archiver
209
+ IndexedArchiver, # Start-index management (single-message)
210
+
211
+ # Transfer (export/import/copy)
212
+ export_queue, # Export queue to flat stream
213
+ export_batch_queue, # Export batch queue to flat stream
214
+ import_queue, # Import flat stream to queue
215
+ import_batch_stream, # Import batch stream to flat queue
216
+ import_batch_queue, # Import batch stream to batch queue
217
+ import_flat_to_batch, # Import flat stream to batch queue
218
+ copy_queue, # Copy queue to new directory
219
+ copy_batch_queue, # Copy batch queue to new directory
220
+ copy_normal_to_batch, # Copy flat queue as batch queue
221
+ copy_batch_to_normal, # Copy batch queue as flat queue
222
+ )
223
+ ```
224
+
225
+ ### Single-message handler callback
226
+
227
+ The handler passed to `poller.poll()` receives `(index: int, appender_id: int, data: bytes)`:
228
+
229
+ ```python
230
+ def handler(index, appender_id, data):
231
+ print(f"[{index}] {data}")
232
+ return NextMove.ADVANCE
233
+ ```
234
+
235
+ ### Batch handler callback
236
+
237
+ The handler passed to `poller.poll()` receives `(batch_index: int, messages: list[bytes])`:
238
+
239
+ ```python
240
+ def handler(batch_index, messages):
241
+ # messages is a list of payload bytes, one per message in the batch
242
+ for msg in messages:
243
+ print(f"[{batch_index}] {msg}")
244
+ return NextMove.ADVANCE
245
+ ```
246
+
247
+ ### Additional appender methods
248
+
249
+ `IndexedAppender` (single-message) also provides:
250
+
251
+ - **`append_uncommitted(data)`** — writes payload without committing. Invisible to readers,
252
+ discarded by crash recovery. Returns a payload offset for later use with `commit()`.
253
+ - **`commit(payload_offset)`** — publishes a previously uncommitted payload. Returns the
254
+ committed index.
255
+
256
+ `IndexedQueue` (single-message) also provides:
257
+
258
+ - **`create_appender_with_id(appender_id)`** — creates an appender with a specific lane ID
259
+ (0–255). Raises `ValueError` if the ID is out of range.
260
+
261
+ `IndexedBatchQueue` (batch) also provides:
262
+
263
+ - **`create_appender_with_id(appender_id)`** — creates a batch appender with a specific lane ID
264
+ (0–255). Raises `ValueError` if the ID is out of range.
265
+ - **`create(directory, queue_name, *, region_size=..., ring_size=..., max_file_size=..., start_index=..., timeout_ns=..., archivable=..., runtime=...)`**
266
+ — full-configuration create. All params match the C library's `INDEXED_QUEUE_DEFAULT_*` constants.
267
+ Existing queues ignore sizing params (on-disk config is authoritative).
268
+
269
+ `IndexedBatchAppender` (batch) also provides:
270
+
271
+ - **`id()`** — returns this appender's lane ID (0–255).
272
+
273
+ ### Sync vs async mapping
274
+
275
+ By default mapping happens synchronously on the hot path. For async background
276
+ region mapping, pass a `Runtime`:
277
+
278
+ ```python
279
+ from mmapq import Runtime, IndexedQueue
280
+
281
+ rt = Runtime.region_mapper()
282
+ q = IndexedQueue.create("/dev/shm", "rt_queue", runtime=rt)
283
+
284
+ # Runtime is kept alive by the queue — safe to drop the local reference
285
+ del rt
286
+ ```
287
+
288
+ ### Archiving
289
+
290
+ An archivable queue (`archivable=True`) can advance its start index so old
291
+ entries become inaccessible and their backing files can be safely deleted:
292
+
293
+ ```python
294
+ from mmapq import IndexedArchiver, IndexedQueue
295
+
296
+ q = IndexedQueue.create("/dev/shm", "arch_q", archivable=True)
297
+ # ... produce messages ...
298
+
299
+ arch = IndexedArchiver.create("/dev/shm", "arch_q")
300
+ arch.archive_up_to_index(1000) # archive entries < 1000
301
+ files = arch.archivable_files() # paths safe to delete
302
+ for path in files:
303
+ # delete, compress, or move them
304
+ pass
305
+ ```
306
+
307
+ ### Transfer (export/import/copy)
308
+
309
+ The library provides 10 transfer functions across single-message and batch
310
+ queues:
311
+
312
+ ```python
313
+ from mmapq import (
314
+ export_queue, export_batch_queue,
315
+ import_queue, import_batch_stream, import_batch_queue,
316
+ import_flat_to_batch,
317
+ copy_queue, copy_batch_queue,
318
+ copy_normal_to_batch, copy_batch_to_normal,
319
+ )
320
+
321
+ # Export a queue to a portable flat file (single-message)
322
+ n = export_queue("/dev/shm", "src", "/tmp/export.bin")
323
+
324
+ # Import flat file into a new queue (allow_append=True appends to existing)
325
+ n = import_queue("/tmp/export.bin", "/dev/shm", "restored")
326
+ n = import_queue("/tmp/export.bin", "/dev/shm", "restored", allow_append=True)
327
+
328
+ # Copy queue files directly (avoids intermediate export file)
329
+ n = copy_queue("/dev/shm", "src", "/dev/shm", "copy")
330
+
331
+ # Preserve index space (start_index, appender IDs)
332
+ n = export_queue("/dev/shm", "src", "/tmp/preserve.bin", preserve=True)
333
+ n = import_queue("/tmp/preserve.bin", "/dev/shm", "preserved", preserve=True)
334
+
335
+ # Batch queue transfers
336
+ n = export_batch_queue("/dev/shm", "batch_src", "/tmp/batch_export.bin")
337
+ n = import_batch_queue("/tmp/batch_export.bin", "/dev/shm", "batch_dst")
338
+ n = import_batch_stream("/tmp/batch_export.bin", "/dev/shm", "flat_stream")
339
+
340
+ # Cross-type conversions (flat to batch, batch to flat)
341
+ n = import_flat_to_batch("/tmp/export.bin", "/dev/shm", "batch_queue")
342
+ n = copy_normal_to_batch("/dev/shm", "flat_src", "/dev/shm", "batch_dst")
343
+ n = copy_batch_to_normal("/dev/shm", "batch_src", "/dev/shm", "flat_dst")
344
+
345
+ # All copy/import functions accept:
346
+ # allow_append=True — append to existing queue instead of refusing
347
+ # preserve=True — keep original index space from source
348
+ # mapper=callable — map source appender IDs to destination IDs
349
+ # on_conflict=<enum> — ConflictBehavior.AUTO or .ABORT
350
+ ```
351
+
352
+ ## Examples
353
+
354
+ ### Batch echo server
355
+
356
+ ```bash
357
+ rm -rf /tmp/request /tmp/response
358
+ python3 examples/batch_echo_server.py /tmp request response &
359
+ python3 examples/batch_latency_bench.py 100000 10000 64 --external
360
+ ```
361
+
362
+ ### Internal benchmark (single process)
363
+
364
+ ```bash
365
+ rm -rf /tmp/request /tmp/response
366
+ python3 examples/batch_latency_bench.py 100000 10000 64
367
+ ```
368
+
369
+ ## Performance
370
+
371
+ The Python layer adds roughly **1–2 µs** per `poll()` call through the cffi
372
+ callback trampoline (C → Python → C dispatch); the C library itself delivers
373
+ ~300 ns median round-trip, so the interpreter dominates the hot path.
374
+ Expected throughput on isolated cores is **100,000–200,000 ops/sec** against
375
+ ~2.8M for C. This is by design and not the point: Python is the consumer and
376
+ analysis language here, not the low-latency producer.
377
+
378
+ Reach for the Python bindings when you want:
379
+
380
+ - A Python consumer, projection, or analysis process reading a log that C or
381
+ Rust producers write — the replay/notebook/dashboard side of an
382
+ event-sourced pipeline.
383
+ - Cross-process compatibility with existing C, Rust, or Algoteq Java
384
+ deployments sharing a queue.
385
+ - Replay, export, and offline forensics over recorded queues.
386
+ - Prototyping and testing with the real wire format.
387
+
388
+ For low-latency *production* on the hot path, produce from the C or Rust
389
+ bindings; let Python read.
390
+
391
+ ## Compatibility
392
+
393
+ The wire format is identical to the C library and the Rust bindings, and
394
+ compatible with Algoteq's Java implementation (tools4j-derived; **not**
395
+ compatible with upstream tools4j — the formats have diverged). Batch queues
396
+ are a C-ecosystem feature and are not read by Java.
397
+
398
+ ## Stability
399
+
400
+ The wire format and feature set are **frozen**: queue files written today
401
+ remain readable by every future version, and the API grows only to complete
402
+ parity with the Algoteq Java reference. The Python bindings expose the
403
+ producer/consumer and replay surface; some low-level batch construction
404
+ primitives remain C/Rust only (see `doc/feature-matrix.md`).
405
+
406
+ ## Testing
407
+
408
+ ```bash
409
+ python3 -m unittest discover tests
410
+ ```
411
+
412
+ ## Acknowledgements
413
+
414
+ `mmapq-py` wraps Algoteq's mmapq, originally derived from the
415
+ [tools4j/mmap](https://github.com/tools4j/mmap) Java library. The
416
+ event-sourcing architecture follows
417
+ [tools4j/event-sourcing](https://github.com/tools4j/event-sourcing) and
418
+ [elara](https://github.com/tools4j/elara).
419
+
420
+ ## License
421
+
422
+ BSD 3-Clause — see [LICENSE](LICENSE).
423
+
424
+ ## Platform
425
+
426
+ Linux only (x86-64; ARM64 untested). Requires naturally aligned lock-free
427
+ atomics over `MAP_SHARED` mmap.