exonware-xwsystem 0.0.1.409__py3-none-any.whl → 0.0.1.411__py3-none-any.whl

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 (260) hide show
  1. exonware/__init__.py +2 -2
  2. exonware/conf.py +10 -20
  3. exonware/xwsystem/__init__.py +6 -16
  4. exonware/xwsystem/caching/__init__.py +1 -1
  5. exonware/xwsystem/caching/base.py +16 -16
  6. exonware/xwsystem/caching/bloom_cache.py +5 -5
  7. exonware/xwsystem/caching/cache_manager.py +2 -2
  8. exonware/xwsystem/caching/conditional.py +4 -4
  9. exonware/xwsystem/caching/contracts.py +12 -12
  10. exonware/xwsystem/caching/decorators.py +2 -2
  11. exonware/xwsystem/caching/defs.py +1 -1
  12. exonware/xwsystem/caching/disk_cache.py +4 -4
  13. exonware/xwsystem/caching/distributed.py +1 -1
  14. exonware/xwsystem/caching/errors.py +1 -1
  15. exonware/xwsystem/caching/events.py +8 -8
  16. exonware/xwsystem/caching/eviction_strategies.py +9 -9
  17. exonware/xwsystem/caching/fluent.py +1 -1
  18. exonware/xwsystem/caching/integrity.py +1 -1
  19. exonware/xwsystem/caching/lfu_cache.py +24 -9
  20. exonware/xwsystem/caching/lfu_optimized.py +21 -21
  21. exonware/xwsystem/caching/lru_cache.py +14 -7
  22. exonware/xwsystem/caching/memory_bounded.py +8 -8
  23. exonware/xwsystem/caching/metrics_exporter.py +6 -6
  24. exonware/xwsystem/caching/observable_cache.py +1 -1
  25. exonware/xwsystem/caching/pluggable_cache.py +9 -9
  26. exonware/xwsystem/caching/rate_limiter.py +1 -1
  27. exonware/xwsystem/caching/read_through.py +6 -6
  28. exonware/xwsystem/caching/secure_cache.py +1 -1
  29. exonware/xwsystem/caching/serializable.py +3 -3
  30. exonware/xwsystem/caching/stats.py +7 -7
  31. exonware/xwsystem/caching/tagging.py +11 -11
  32. exonware/xwsystem/caching/ttl_cache.py +21 -6
  33. exonware/xwsystem/caching/two_tier_cache.py +5 -5
  34. exonware/xwsystem/caching/utils.py +3 -3
  35. exonware/xwsystem/caching/validation.py +1 -1
  36. exonware/xwsystem/caching/warming.py +9 -9
  37. exonware/xwsystem/caching/write_behind.py +5 -5
  38. exonware/xwsystem/cli/__init__.py +1 -1
  39. exonware/xwsystem/cli/args.py +10 -10
  40. exonware/xwsystem/cli/base.py +15 -15
  41. exonware/xwsystem/cli/colors.py +1 -1
  42. exonware/xwsystem/cli/console.py +1 -1
  43. exonware/xwsystem/cli/contracts.py +5 -5
  44. exonware/xwsystem/cli/defs.py +1 -1
  45. exonware/xwsystem/cli/errors.py +1 -1
  46. exonware/xwsystem/cli/progress.py +1 -1
  47. exonware/xwsystem/cli/prompts.py +1 -1
  48. exonware/xwsystem/cli/tables.py +7 -7
  49. exonware/xwsystem/config/__init__.py +1 -1
  50. exonware/xwsystem/config/base.py +14 -14
  51. exonware/xwsystem/config/contracts.py +22 -22
  52. exonware/xwsystem/config/defaults.py +2 -2
  53. exonware/xwsystem/config/defs.py +1 -1
  54. exonware/xwsystem/config/errors.py +2 -2
  55. exonware/xwsystem/config/logging.py +1 -1
  56. exonware/xwsystem/config/logging_setup.py +2 -2
  57. exonware/xwsystem/config/performance.py +7 -7
  58. exonware/xwsystem/config/performance_modes.py +20 -20
  59. exonware/xwsystem/config/version_manager.py +4 -4
  60. exonware/xwsystem/{http → http_client}/__init__.py +1 -1
  61. exonware/xwsystem/{http → http_client}/advanced_client.py +20 -20
  62. exonware/xwsystem/{http → http_client}/base.py +13 -13
  63. exonware/xwsystem/{http → http_client}/client.py +43 -43
  64. exonware/xwsystem/{http → http_client}/contracts.py +5 -5
  65. exonware/xwsystem/{http → http_client}/defs.py +2 -2
  66. exonware/xwsystem/{http → http_client}/errors.py +2 -2
  67. exonware/xwsystem/io/__init__.py +1 -1
  68. exonware/xwsystem/io/archive/__init__.py +1 -1
  69. exonware/xwsystem/io/archive/archive.py +5 -5
  70. exonware/xwsystem/io/archive/archive_files.py +8 -8
  71. exonware/xwsystem/io/archive/archivers.py +3 -3
  72. exonware/xwsystem/io/archive/base.py +17 -17
  73. exonware/xwsystem/io/archive/codec_integration.py +1 -1
  74. exonware/xwsystem/io/archive/compression.py +1 -1
  75. exonware/xwsystem/io/archive/formats/__init__.py +1 -1
  76. exonware/xwsystem/io/archive/formats/brotli_format.py +12 -9
  77. exonware/xwsystem/io/archive/formats/lz4_format.py +12 -9
  78. exonware/xwsystem/io/archive/formats/rar.py +12 -9
  79. exonware/xwsystem/io/archive/formats/sevenzip.py +12 -9
  80. exonware/xwsystem/io/archive/formats/squashfs_format.py +7 -7
  81. exonware/xwsystem/io/archive/formats/tar.py +8 -8
  82. exonware/xwsystem/io/archive/formats/wim_format.py +12 -9
  83. exonware/xwsystem/io/archive/formats/zip.py +8 -8
  84. exonware/xwsystem/io/archive/formats/zpaq_format.py +7 -7
  85. exonware/xwsystem/io/archive/formats/zstandard.py +12 -9
  86. exonware/xwsystem/io/base.py +17 -17
  87. exonware/xwsystem/io/codec/__init__.py +1 -1
  88. exonware/xwsystem/io/codec/base.py +261 -14
  89. exonware/xwsystem/io/codec/contracts.py +3 -6
  90. exonware/xwsystem/io/codec/registry.py +29 -29
  91. exonware/xwsystem/io/common/__init__.py +1 -1
  92. exonware/xwsystem/io/common/atomic.py +2 -2
  93. exonware/xwsystem/io/common/base.py +1 -1
  94. exonware/xwsystem/io/common/lock.py +1 -1
  95. exonware/xwsystem/io/common/watcher.py +4 -4
  96. exonware/xwsystem/io/contracts.py +34 -39
  97. exonware/xwsystem/io/data_operations.py +480 -0
  98. exonware/xwsystem/io/defs.py +2 -2
  99. exonware/xwsystem/io/errors.py +32 -3
  100. exonware/xwsystem/io/facade.py +4 -4
  101. exonware/xwsystem/io/file/__init__.py +1 -1
  102. exonware/xwsystem/io/file/base.py +2 -2
  103. exonware/xwsystem/io/file/conversion.py +1 -1
  104. exonware/xwsystem/io/file/file.py +10 -8
  105. exonware/xwsystem/io/file/paged_source.py +8 -1
  106. exonware/xwsystem/io/file/paging/__init__.py +1 -1
  107. exonware/xwsystem/io/file/paging/byte_paging.py +1 -1
  108. exonware/xwsystem/io/file/paging/line_paging.py +1 -1
  109. exonware/xwsystem/io/file/paging/record_paging.py +1 -1
  110. exonware/xwsystem/io/file/paging/registry.py +5 -5
  111. exonware/xwsystem/io/file/source.py +22 -11
  112. exonware/xwsystem/io/filesystem/__init__.py +1 -1
  113. exonware/xwsystem/io/filesystem/base.py +1 -1
  114. exonware/xwsystem/io/filesystem/local.py +9 -1
  115. exonware/xwsystem/io/folder/__init__.py +1 -1
  116. exonware/xwsystem/io/folder/base.py +2 -2
  117. exonware/xwsystem/io/folder/folder.py +6 -6
  118. exonware/xwsystem/io/serialization/__init__.py +1 -1
  119. exonware/xwsystem/io/serialization/auto_serializer.py +53 -40
  120. exonware/xwsystem/io/serialization/base.py +248 -35
  121. exonware/xwsystem/io/serialization/contracts.py +93 -4
  122. exonware/xwsystem/io/serialization/defs.py +1 -1
  123. exonware/xwsystem/io/serialization/errors.py +1 -1
  124. exonware/xwsystem/io/serialization/flyweight.py +22 -22
  125. exonware/xwsystem/io/serialization/format_detector.py +18 -15
  126. exonware/xwsystem/io/serialization/formats/__init__.py +1 -1
  127. exonware/xwsystem/io/serialization/formats/binary/bson.py +1 -1
  128. exonware/xwsystem/io/serialization/formats/binary/cbor.py +1 -1
  129. exonware/xwsystem/io/serialization/formats/binary/marshal.py +1 -1
  130. exonware/xwsystem/io/serialization/formats/binary/msgpack.py +1 -1
  131. exonware/xwsystem/io/serialization/formats/binary/pickle.py +1 -1
  132. exonware/xwsystem/io/serialization/formats/binary/plistlib.py +1 -1
  133. exonware/xwsystem/io/serialization/formats/database/dbm.py +53 -1
  134. exonware/xwsystem/io/serialization/formats/database/shelve.py +48 -1
  135. exonware/xwsystem/io/serialization/formats/database/sqlite3.py +85 -1
  136. exonware/xwsystem/io/serialization/formats/text/configparser.py +2 -2
  137. exonware/xwsystem/io/serialization/formats/text/csv.py +2 -2
  138. exonware/xwsystem/io/serialization/formats/text/formdata.py +2 -2
  139. exonware/xwsystem/io/serialization/formats/text/json.py +23 -5
  140. exonware/xwsystem/io/serialization/formats/text/json5.py +98 -13
  141. exonware/xwsystem/io/serialization/formats/text/jsonlines.py +230 -20
  142. exonware/xwsystem/io/serialization/formats/text/multipart.py +2 -2
  143. exonware/xwsystem/io/serialization/formats/text/toml.py +65 -4
  144. exonware/xwsystem/io/serialization/formats/text/xml.py +451 -69
  145. exonware/xwsystem/io/serialization/formats/text/yaml.py +52 -2
  146. exonware/xwsystem/io/serialization/registry.py +5 -5
  147. exonware/xwsystem/io/serialization/serializer.py +184 -12
  148. exonware/xwsystem/io/serialization/utils/__init__.py +1 -1
  149. exonware/xwsystem/io/serialization/utils/path_ops.py +3 -3
  150. exonware/xwsystem/io/stream/__init__.py +1 -1
  151. exonware/xwsystem/io/stream/async_operations.py +3 -3
  152. exonware/xwsystem/io/stream/base.py +3 -7
  153. exonware/xwsystem/io/stream/codec_io.py +4 -7
  154. exonware/xwsystem/ipc/async_fabric.py +7 -8
  155. exonware/xwsystem/ipc/base.py +9 -9
  156. exonware/xwsystem/ipc/contracts.py +5 -5
  157. exonware/xwsystem/ipc/defs.py +1 -1
  158. exonware/xwsystem/ipc/errors.py +2 -2
  159. exonware/xwsystem/ipc/message_queue.py +4 -6
  160. exonware/xwsystem/ipc/pipes.py +2 -2
  161. exonware/xwsystem/ipc/process_manager.py +7 -7
  162. exonware/xwsystem/ipc/process_pool.py +8 -8
  163. exonware/xwsystem/ipc/shared_memory.py +7 -7
  164. exonware/xwsystem/monitoring/base.py +33 -33
  165. exonware/xwsystem/monitoring/contracts.py +27 -27
  166. exonware/xwsystem/monitoring/defs.py +1 -1
  167. exonware/xwsystem/monitoring/error_recovery.py +16 -16
  168. exonware/xwsystem/monitoring/errors.py +2 -2
  169. exonware/xwsystem/monitoring/memory_monitor.py +12 -12
  170. exonware/xwsystem/monitoring/metrics.py +8 -8
  171. exonware/xwsystem/monitoring/performance_manager_generic.py +20 -20
  172. exonware/xwsystem/monitoring/performance_monitor.py +11 -11
  173. exonware/xwsystem/monitoring/performance_validator.py +21 -21
  174. exonware/xwsystem/monitoring/system_monitor.py +17 -17
  175. exonware/xwsystem/monitoring/tracing.py +20 -20
  176. exonware/xwsystem/monitoring/tracker.py +7 -7
  177. exonware/xwsystem/operations/__init__.py +5 -5
  178. exonware/xwsystem/operations/base.py +3 -3
  179. exonware/xwsystem/operations/contracts.py +3 -3
  180. exonware/xwsystem/operations/defs.py +5 -5
  181. exonware/xwsystem/operations/diff.py +5 -5
  182. exonware/xwsystem/operations/merge.py +2 -2
  183. exonware/xwsystem/operations/patch.py +5 -5
  184. exonware/xwsystem/patterns/base.py +4 -4
  185. exonware/xwsystem/patterns/context_manager.py +7 -7
  186. exonware/xwsystem/patterns/contracts.py +29 -31
  187. exonware/xwsystem/patterns/defs.py +1 -1
  188. exonware/xwsystem/patterns/dynamic_facade.py +9 -9
  189. exonware/xwsystem/patterns/errors.py +10 -10
  190. exonware/xwsystem/patterns/handler_factory.py +15 -14
  191. exonware/xwsystem/patterns/import_registry.py +22 -22
  192. exonware/xwsystem/patterns/object_pool.py +14 -13
  193. exonware/xwsystem/patterns/registry.py +45 -32
  194. exonware/xwsystem/plugins/__init__.py +1 -1
  195. exonware/xwsystem/plugins/base.py +25 -25
  196. exonware/xwsystem/plugins/contracts.py +28 -28
  197. exonware/xwsystem/plugins/defs.py +1 -1
  198. exonware/xwsystem/plugins/errors.py +9 -9
  199. exonware/xwsystem/runtime/__init__.py +1 -1
  200. exonware/xwsystem/runtime/base.py +42 -42
  201. exonware/xwsystem/runtime/contracts.py +9 -9
  202. exonware/xwsystem/runtime/defs.py +1 -1
  203. exonware/xwsystem/runtime/env.py +9 -9
  204. exonware/xwsystem/runtime/errors.py +1 -1
  205. exonware/xwsystem/runtime/reflection.py +15 -15
  206. exonware/xwsystem/security/auth.py +47 -15
  207. exonware/xwsystem/security/base.py +17 -17
  208. exonware/xwsystem/security/contracts.py +30 -30
  209. exonware/xwsystem/security/crypto.py +8 -8
  210. exonware/xwsystem/security/defs.py +1 -1
  211. exonware/xwsystem/security/errors.py +2 -2
  212. exonware/xwsystem/security/hazmat.py +7 -7
  213. exonware/xwsystem/security/path_validator.py +1 -1
  214. exonware/xwsystem/shared/__init__.py +1 -1
  215. exonware/xwsystem/shared/base.py +14 -14
  216. exonware/xwsystem/shared/contracts.py +6 -6
  217. exonware/xwsystem/shared/defs.py +1 -1
  218. exonware/xwsystem/shared/errors.py +1 -1
  219. exonware/xwsystem/structures/__init__.py +1 -1
  220. exonware/xwsystem/structures/base.py +29 -29
  221. exonware/xwsystem/structures/circular_detector.py +15 -15
  222. exonware/xwsystem/structures/contracts.py +9 -9
  223. exonware/xwsystem/structures/defs.py +1 -1
  224. exonware/xwsystem/structures/errors.py +2 -2
  225. exonware/xwsystem/structures/tree_walker.py +8 -8
  226. exonware/xwsystem/threading/async_primitives.py +7 -7
  227. exonware/xwsystem/threading/base.py +19 -19
  228. exonware/xwsystem/threading/contracts.py +13 -13
  229. exonware/xwsystem/threading/defs.py +1 -1
  230. exonware/xwsystem/threading/errors.py +2 -2
  231. exonware/xwsystem/threading/safe_factory.py +13 -12
  232. exonware/xwsystem/utils/base.py +34 -34
  233. exonware/xwsystem/utils/contracts.py +9 -9
  234. exonware/xwsystem/utils/dt/__init__.py +1 -1
  235. exonware/xwsystem/utils/dt/base.py +6 -6
  236. exonware/xwsystem/utils/dt/contracts.py +2 -2
  237. exonware/xwsystem/utils/dt/defs.py +1 -1
  238. exonware/xwsystem/utils/dt/errors.py +2 -2
  239. exonware/xwsystem/utils/dt/formatting.py +3 -3
  240. exonware/xwsystem/utils/dt/humanize.py +2 -2
  241. exonware/xwsystem/utils/dt/parsing.py +2 -2
  242. exonware/xwsystem/utils/dt/timezone_utils.py +5 -5
  243. exonware/xwsystem/utils/errors.py +2 -2
  244. exonware/xwsystem/utils/test_runner.py +6 -6
  245. exonware/xwsystem/utils/utils_contracts.py +1 -1
  246. exonware/xwsystem/validation/__init__.py +1 -1
  247. exonware/xwsystem/validation/base.py +48 -48
  248. exonware/xwsystem/validation/contracts.py +8 -8
  249. exonware/xwsystem/validation/data_validator.py +10 -0
  250. exonware/xwsystem/validation/declarative.py +15 -15
  251. exonware/xwsystem/validation/defs.py +1 -1
  252. exonware/xwsystem/validation/errors.py +2 -2
  253. exonware/xwsystem/validation/fluent_validator.py +10 -10
  254. exonware/xwsystem/version.py +2 -2
  255. {exonware_xwsystem-0.0.1.409.dist-info → exonware_xwsystem-0.0.1.411.dist-info}/METADATA +9 -11
  256. exonware_xwsystem-0.0.1.411.dist-info/RECORD +274 -0
  257. {exonware_xwsystem-0.0.1.409.dist-info → exonware_xwsystem-0.0.1.411.dist-info}/WHEEL +1 -1
  258. exonware/xwsystem/lazy_bootstrap.py +0 -79
  259. exonware_xwsystem-0.0.1.409.dist-info/RECORD +0 -274
  260. {exonware_xwsystem-0.0.1.409.dist-info → exonware_xwsystem-0.0.1.411.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,480 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ #exonware/xwsystem/src/exonware/xwsystem/io/data_operations.py
4
+
5
+ Generic data-operations layer for large, file-backed datasets.
6
+
7
+ This module provides:
8
+ - A small indexing model for line-oriented files (e.g. NDJSON / JSONL)
9
+ - Streaming read / update helpers with atomic guarantees
10
+ - Paging helpers built on top of line offsets
11
+
12
+ The goal is to expose these capabilities in a format-agnostic way so that
13
+ higher-level libraries (xwdata, xwnode, xwentity, etc.) can build powerful
14
+ lazy, paged, and atomic access features without re-implementing I/O logic.
15
+
16
+ Company: eXonware.com
17
+ Author: Eng. Muhammad AlShehri
18
+ Email: connect@exonware.com
19
+ Version: 0.0.1.411
20
+ Generation Date: 15-Dec-2025
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ from dataclasses import dataclass
26
+ from pathlib import Path
27
+ from typing import Any, Callable, Optional
28
+ from abc import ABC, abstractmethod
29
+ import json
30
+ import os
31
+ import tempfile
32
+
33
+ from .serialization.auto_serializer import AutoSerializer
34
+ from ..config.logging_setup import get_logger
35
+
36
+
37
+ logger = get_logger(__name__)
38
+
39
+
40
+ JsonMatchFn = Callable[[Any], bool]
41
+ JsonUpdateFn = Callable[[Any], Any]
42
+
43
+
44
+ @dataclass
45
+ class JsonIndexMeta:
46
+ """
47
+ Minimal metadata for a JSONL/NDJSON index.
48
+
49
+ This intentionally mirrors the capabilities used in the x5 examples
50
+ without pulling in any of the example code directly.
51
+ """
52
+
53
+ path: str
54
+ size: int
55
+ mtime: float
56
+ version: int = 1
57
+
58
+
59
+ @dataclass
60
+ class JsonIndex:
61
+ """
62
+ Simple index for line-oriented JSON files.
63
+
64
+ - line_offsets: byte offset of each JSON line
65
+ - id_index: optional mapping id_value -> line_number
66
+ """
67
+
68
+ meta: JsonIndexMeta
69
+ line_offsets: list[int]
70
+ id_index: Optional[dict[str, int]] = None
71
+
72
+
73
+ class ADataOperations(ABC):
74
+ """
75
+ Abstract, format-agnostic interface for large, file-backed data operations.
76
+
77
+ Concrete implementations may target specific physical layouts
78
+ (NDJSON/JSONL, multi-document YAML, binary record stores, etc.), but MUST
79
+ conform to these semantics:
80
+
81
+ - Streaming, record-by-record read with a match predicate.
82
+ - Streaming, atomic update using a temp file + replace pattern.
83
+ - Optional indexing for random access and paging.
84
+ """
85
+
86
+ @abstractmethod
87
+ def stream_read(
88
+ self,
89
+ file_path: str | Path,
90
+ match: JsonMatchFn,
91
+ path: Optional[list[object]] = None,
92
+ encoding: str = "utf-8",
93
+ ) -> Any:
94
+ """Return the first record (or sub-path) that matches the predicate."""
95
+ raise NotImplementedError
96
+
97
+ @abstractmethod
98
+ def stream_update(
99
+ self,
100
+ file_path: str | Path,
101
+ match: JsonMatchFn,
102
+ updater: JsonUpdateFn,
103
+ *,
104
+ encoding: str = "utf-8",
105
+ newline: str = "\n",
106
+ atomic: bool = True,
107
+ ) -> int:
108
+ """
109
+ Stream-copy the backing store, applying `updater` to matching records.
110
+
111
+ MUST use atomic replace semantics when `atomic=True`.
112
+ Returns number of updated records.
113
+ """
114
+ raise NotImplementedError
115
+
116
+ @abstractmethod
117
+ def build_index(
118
+ self,
119
+ file_path: str | Path,
120
+ *,
121
+ encoding: str = "utf-8",
122
+ id_field: str | None = None,
123
+ max_id_index: int | None = None,
124
+ ) -> JsonIndex:
125
+ """Build an index structure suitable for random access and paging."""
126
+ raise NotImplementedError
127
+
128
+ @abstractmethod
129
+ def indexed_get_by_line(
130
+ self,
131
+ file_path: str | Path,
132
+ line_number: int,
133
+ *,
134
+ encoding: str = "utf-8",
135
+ index: Optional[JsonIndex] = None,
136
+ ) -> Any:
137
+ """Random-access a specific logical record by its index position."""
138
+ raise NotImplementedError
139
+
140
+ @abstractmethod
141
+ def indexed_get_by_id(
142
+ self,
143
+ file_path: str | Path,
144
+ id_value: Any,
145
+ *,
146
+ encoding: str = "utf-8",
147
+ id_field: str = "id",
148
+ index: Optional[JsonIndex] = None,
149
+ ) -> Any:
150
+ """Random-access a record by logical identifier, with optional index."""
151
+ raise NotImplementedError
152
+
153
+ @abstractmethod
154
+ def get_page(
155
+ self,
156
+ file_path: str | Path,
157
+ page_number: int,
158
+ page_size: int,
159
+ *,
160
+ encoding: str = "utf-8",
161
+ index: Optional[JsonIndex] = None,
162
+ ) -> list[Any]:
163
+ """Return a page of logical records using an index for efficiency."""
164
+ raise NotImplementedError
165
+
166
+
167
+ class NDJSONDataOperations(ADataOperations):
168
+ """
169
+ Generic data-operations helper for NDJSON / JSONL style files.
170
+
171
+ This class is deliberately low-level and works directly with paths and
172
+ native Python data. XWData and other libraries can wrap it to provide
173
+ higher-level, type-agnostic facades.
174
+ """
175
+
176
+ def __init__(self, serializer: Optional[AutoSerializer] = None):
177
+ # Reuse xwsystem's AutoSerializer so we do not re-implement parsing.
178
+ self._serializer = serializer or AutoSerializer(default_format="JSON")
179
+
180
+ # ------------------------------------------------------------------
181
+ # Streaming read
182
+ # ------------------------------------------------------------------
183
+
184
+ def stream_read(
185
+ self,
186
+ file_path: str | Path,
187
+ match: JsonMatchFn,
188
+ path: Optional[list[object]] = None,
189
+ encoding: str = "utf-8",
190
+ ) -> Any:
191
+ """
192
+ Stream a huge NDJSON file and return the first record (or sub-path)
193
+ matching `match`.
194
+
195
+ This is intentionally simple and focused:
196
+ - Reads one line at a time
197
+ - Uses AutoSerializer(JSON) for parsing
198
+ - Optional path extraction
199
+ """
200
+ target = Path(file_path)
201
+ if not target.exists():
202
+ raise FileNotFoundError(str(target))
203
+
204
+ with target.open("r", encoding=encoding) as f:
205
+ for line in f:
206
+ line = line.strip()
207
+ if not line:
208
+ continue
209
+ obj = self._serializer.detect_and_deserialize(
210
+ line, file_path=target, format_hint="JSON"
211
+ )
212
+ if match(obj):
213
+ return self._extract_path(obj, path)
214
+
215
+ raise KeyError("No matching record found")
216
+
217
+ # ------------------------------------------------------------------
218
+ # Streaming update with atomic replace
219
+ # ------------------------------------------------------------------
220
+
221
+ def stream_update(
222
+ self,
223
+ file_path: str | Path,
224
+ match: JsonMatchFn,
225
+ updater: JsonUpdateFn,
226
+ *,
227
+ encoding: str = "utf-8",
228
+ newline: str = "\n",
229
+ atomic: bool = True,
230
+ ) -> int:
231
+ """
232
+ Stream-copy a huge NDJSON file, applying `updater` to records
233
+ where `match(obj)` is True.
234
+
235
+ Only matching records are fully materialized. All writes go to a
236
+ temporary file, which is atomically replaced on success.
237
+
238
+ Returns the number of updated records.
239
+ """
240
+ target = Path(file_path)
241
+ if not target.exists():
242
+ raise FileNotFoundError(str(target))
243
+
244
+ updated = 0
245
+ dir_path = target.parent
246
+
247
+ # Write to a temp file in the same directory for atomic replace.
248
+ fd, tmp_path_str = tempfile.mkstemp(
249
+ prefix=f".{target.name}.tmp.", dir=str(dir_path)
250
+ )
251
+ tmp_path = Path(tmp_path_str)
252
+
253
+ try:
254
+ with os.fdopen(fd, "w", encoding=encoding, newline=newline) as out_f, target.open(
255
+ "r", encoding=encoding
256
+ ) as in_f:
257
+ for line in in_f:
258
+ raw = line.rstrip("\n")
259
+ if not raw:
260
+ out_f.write(line)
261
+ continue
262
+
263
+ obj = self._serializer.detect_and_deserialize(
264
+ raw, file_path=target, format_hint="JSON"
265
+ )
266
+ if match(obj):
267
+ updated_obj = updater(obj)
268
+ updated_line = json.dumps(updated_obj, ensure_ascii=False)
269
+ out_f.write(updated_line + newline)
270
+ updated += 1
271
+ else:
272
+ out_f.write(line)
273
+
274
+ if atomic:
275
+ os.replace(tmp_path, target)
276
+ else:
277
+ tmp_path.replace(target)
278
+
279
+ return updated
280
+ finally:
281
+ # Ensure temp file is removed on error
282
+ if tmp_path.exists():
283
+ try:
284
+ tmp_path.unlink()
285
+ except OSError:
286
+ # Best-effort cleanup; do not mask original error.
287
+ logger.debug("Failed to cleanup temp file %s", tmp_path)
288
+
289
+ # ------------------------------------------------------------------
290
+ # Indexing and paging
291
+ # ------------------------------------------------------------------
292
+
293
+ def build_index(
294
+ self,
295
+ file_path: str | Path,
296
+ *,
297
+ encoding: str = "utf-8",
298
+ id_field: str | None = None,
299
+ max_id_index: int | None = None,
300
+ ) -> JsonIndex:
301
+ """
302
+ One-time full scan to build an index:
303
+ - line_offsets: byte offset of each JSON line
304
+ - optional id_index: obj[id_field] -> line_number
305
+ """
306
+ target = Path(file_path)
307
+ if not target.exists():
308
+ raise FileNotFoundError(str(target))
309
+
310
+ line_offsets: list[int] = []
311
+ id_index: dict[str, int] | None = {} if id_field else None
312
+
313
+ size = target.stat().st_size
314
+ mtime = target.stat().st_mtime
315
+
316
+ offset = 0
317
+ with target.open("rb") as f:
318
+ line_no = 0
319
+ while True:
320
+ line = f.readline()
321
+ if not line:
322
+ break
323
+ line_offsets.append(offset)
324
+
325
+ if id_index is not None:
326
+ try:
327
+ text = line.decode(encoding).strip()
328
+ if text:
329
+ obj = self._serializer.detect_and_deserialize(
330
+ text, file_path=target, format_hint="JSON"
331
+ )
332
+ if isinstance(obj, dict) and id_field in obj:
333
+ id_val = str(obj[id_field])
334
+ if max_id_index is None or len(id_index) < max_id_index:
335
+ id_index[id_val] = line_no
336
+ except Exception:
337
+ # Index should be best-effort and robust to bad lines.
338
+ logger.debug("Skipping line %s while building id index", line_no)
339
+
340
+ offset += len(line)
341
+ line_no += 1
342
+
343
+ meta = JsonIndexMeta(path=str(target), size=size, mtime=mtime, version=1)
344
+ return JsonIndex(meta=meta, line_offsets=line_offsets, id_index=id_index)
345
+
346
+ def indexed_get_by_line(
347
+ self,
348
+ file_path: str | Path,
349
+ line_number: int,
350
+ *,
351
+ encoding: str = "utf-8",
352
+ index: Optional[JsonIndex] = None,
353
+ ) -> Any:
354
+ """
355
+ Random-access a specific record by line_number (0-based) using index.
356
+ """
357
+ target = Path(file_path)
358
+ if index is None:
359
+ index = self.build_index(target, encoding=encoding)
360
+
361
+ if line_number < 0 or line_number >= len(index.line_offsets):
362
+ raise IndexError("line_number out of range")
363
+
364
+ offset = index.line_offsets[line_number]
365
+ with target.open("rb") as f:
366
+ f.seek(offset)
367
+ line = f.readline()
368
+ text = line.decode(encoding).strip()
369
+ if not text:
370
+ raise ValueError("Empty line at indexed position")
371
+ return self._serializer.detect_and_deserialize(
372
+ text, file_path=target, format_hint="JSON"
373
+ )
374
+
375
+ def indexed_get_by_id(
376
+ self,
377
+ file_path: str | Path,
378
+ id_value: Any,
379
+ *,
380
+ encoding: str = "utf-8",
381
+ id_field: str = "id",
382
+ index: Optional[JsonIndex] = None,
383
+ ) -> Any:
384
+ """
385
+ Random-access a record by logical id using id_index if available.
386
+ Falls back to linear scan if id_index missing or incomplete.
387
+ """
388
+ target = Path(file_path)
389
+ if index is None:
390
+ index = self.build_index(target, encoding=encoding, id_field=id_field)
391
+
392
+ id_index = index.id_index
393
+ if id_index is not None:
394
+ key = str(id_value)
395
+ if key in id_index:
396
+ return self.indexed_get_by_line(
397
+ target, id_index[key], encoding=encoding, index=index
398
+ )
399
+
400
+ # Fallback: linear scan using stream_read semantics
401
+ def _match(obj: Any) -> bool:
402
+ return isinstance(obj, dict) and obj.get(id_field) == id_value
403
+
404
+ return self.stream_read(target, _match, path=None, encoding=encoding)
405
+
406
+ def get_page(
407
+ self,
408
+ file_path: str | Path,
409
+ page_number: int,
410
+ page_size: int,
411
+ *,
412
+ encoding: str = "utf-8",
413
+ index: Optional[JsonIndex] = None,
414
+ ) -> list[Any]:
415
+ """
416
+ Paging helper using index:
417
+ - page_number: 1-based
418
+ - page_size: number of records per page
419
+ """
420
+ target = Path(file_path)
421
+ if index is None:
422
+ index = self.build_index(target, encoding=encoding)
423
+
424
+ if page_number < 1 or page_size <= 0:
425
+ raise ValueError("Invalid page_number or page_size")
426
+
427
+ start = (page_number - 1) * page_size
428
+ end = start + page_size
429
+
430
+ if start >= len(index.line_offsets):
431
+ return []
432
+
433
+ end = min(end, len(index.line_offsets))
434
+
435
+ results: list[Any] = []
436
+ with target.open("rb") as f:
437
+ for line_no in range(start, end):
438
+ offset = index.line_offsets[line_no]
439
+ f.seek(offset)
440
+ line = f.readline()
441
+ text = line.decode(encoding).strip()
442
+ if not text:
443
+ continue
444
+ obj = self._serializer.detect_and_deserialize(
445
+ text, file_path=target, format_hint="JSON"
446
+ )
447
+ results.append(obj)
448
+
449
+ return results
450
+
451
+ # ------------------------------------------------------------------
452
+ # Helpers
453
+ # ------------------------------------------------------------------
454
+
455
+ def _extract_path(self, obj: Any, path: Optional[list[object]]) -> Any:
456
+ """Extract a nested path like ['user', 'email'] or ['tags', 0]."""
457
+ if not path:
458
+ return obj
459
+
460
+ current = obj
461
+ for part in path:
462
+ if isinstance(current, dict) and isinstance(part, str):
463
+ if part not in current:
464
+ raise KeyError(part)
465
+ current = current[part]
466
+ elif isinstance(current, list) and isinstance(part, int):
467
+ current = current[part]
468
+ else:
469
+ raise KeyError(part)
470
+ return current
471
+
472
+
473
+ __all__ = [
474
+ "JsonIndexMeta",
475
+ "JsonIndex",
476
+ "ADataOperations",
477
+ "NDJSONDataOperations",
478
+ ]
479
+
480
+
@@ -2,7 +2,7 @@
2
2
  Company: eXonware.com
3
3
  Author: Eng. Muhammad AlShehri
4
4
  Email: connect@exonware.com
5
- Version: 0.0.1.409
5
+ Version: 0.0.1.411
6
6
  Generation Date: 30-Oct-2025
7
7
 
8
8
  IO module definitions - ALL enums and types in ONE place.
@@ -11,7 +11,7 @@ Consolidated from all submodules for maintainability.
11
11
  """
12
12
 
13
13
  from enum import Enum, IntEnum, Flag, IntFlag, auto
14
- from typing import Any, Dict, Optional
14
+ from typing import Any, Optional
15
15
  from dataclasses import dataclass
16
16
 
17
17
 
@@ -2,7 +2,7 @@
2
2
  Company: eXonware.com
3
3
  Author: Eng. Muhammad AlShehri
4
4
  Email: connect@exonware.com
5
- Version: 0.0.1.409
5
+ Version: 0.0.1.411
6
6
  Generation Date: 30-Oct-2025
7
7
 
8
8
  IO module errors - ALL exceptions in ONE place.
@@ -10,7 +10,7 @@ IO module errors - ALL exceptions in ONE place.
10
10
  Consolidated from all submodules for maintainability.
11
11
  """
12
12
 
13
- from typing import Any, Dict, Optional, Union
13
+ from typing import Any, Optional, Union
14
14
  from pathlib import Path
15
15
 
16
16
 
@@ -359,8 +359,37 @@ class SerializationError(Exception):
359
359
 
360
360
  Root cause fixed: Added missing SerializationError class that was being
361
361
  imported by serialization/base.py but didn't exist.
362
+
363
+ Root cause fixed: Added __init__ to accept format_name and original_error
364
+ parameters that are used throughout the serialization codebase.
362
365
  """
363
- pass
366
+
367
+ def __init__(
368
+ self,
369
+ message: str,
370
+ format_name: Optional[str] = None,
371
+ original_error: Optional[Exception] = None
372
+ ):
373
+ """
374
+ Initialize SerializationError.
375
+
376
+ Args:
377
+ message: Error message
378
+ format_name: Optional format name (e.g., "JSON", "XML")
379
+ original_error: Optional original exception that caused this error
380
+ """
381
+ super().__init__(message)
382
+ self.format_name = format_name
383
+ self.original_error = original_error
384
+
385
+ def __str__(self) -> str:
386
+ """Format error message with optional context."""
387
+ parts = [super().__str__()]
388
+ if self.format_name:
389
+ parts.append(f"[format: {self.format_name}]")
390
+ if self.original_error:
391
+ parts.append(f"[caused by: {type(self.original_error).__name__}]")
392
+ return " ".join(parts)
364
393
 
365
394
 
366
395
  class EncodeError(CodecError):
@@ -2,7 +2,7 @@
2
2
  Company: eXonware.com
3
3
  Author: Eng. Muhammad AlShehri
4
4
  Email: connect@exonware.com
5
- Version: 0.0.1.409
5
+ Version: 0.0.1.411
6
6
  Generation Date: September 04, 2025
7
7
 
8
8
  XWIO - Main facade for all I/O operations (MANDATORY facade pattern).
@@ -15,7 +15,7 @@ import shutil
15
15
  import tempfile
16
16
  import time
17
17
  from pathlib import Path
18
- from typing import Any, Dict, List, Optional, Union, BinaryIO, TextIO
18
+ from typing import Any, Optional, Union, BinaryIO, TextIO
19
19
 
20
20
  from .base import AUnifiedIO
21
21
  from .contracts import FileMode, FileType, PathType, OperationResult, LockType, IUnifiedIO
@@ -92,7 +92,7 @@ class XWIO(AUnifiedIO):
92
92
 
93
93
  with performance_monitor("file_open"):
94
94
  # Ensure parent directory exists
95
- if self.auto_create_dirs and mode in [FileMode.WRITE, FileMode.APPEND, FileMode.WRITE_PLUS]:
95
+ if self.auto_create_dirs and mode in [FileMode.WRITE, FileMode.APPEND, FileMode.WRITE_READ]:
96
96
  target_path.parent.mkdir(parents=True, exist_ok=True)
97
97
 
98
98
  # Open file
@@ -873,7 +873,7 @@ class XWIO(AUnifiedIO):
873
873
  # UTILITY METHODS
874
874
  # ============================================================================
875
875
 
876
- def get_info(self) -> Dict[str, Any]:
876
+ def get_info(self) -> dict[str, Any]:
877
877
  """Get comprehensive I/O information."""
878
878
  return {
879
879
  'file_path': str(self.file_path) if self.file_path else None,
@@ -4,7 +4,7 @@
4
4
  Company: eXonware.com
5
5
  Author: Eng. Muhammad AlShehri
6
6
  Email: connect@exonware.com
7
- Version: 0.0.1.409
7
+ Version: 0.0.1.411
8
8
  Generation Date: 30-Oct-2025
9
9
 
10
10
  File-specific implementations and utilities.
@@ -4,7 +4,7 @@
4
4
  Company: eXonware.com
5
5
  Author: Eng. Muhammad AlShehri
6
6
  Email: connect@exonware.com
7
- Version: 0.0.1.409
7
+ Version: 0.0.1.411
8
8
  Generation Date: 30-Oct-2025
9
9
 
10
10
  Base classes for file operations.
@@ -20,7 +20,7 @@ Priority 5 (Extensibility): Ready for new file types
20
20
 
21
21
  from abc import ABC, abstractmethod
22
22
  from pathlib import Path
23
- from typing import Union, Optional, Dict, Any
23
+ from typing import Union, Optional, Any
24
24
 
25
25
  from ..contracts import IFileSource, IPagedSource, IPagingStrategy
26
26
  from ..defs import PagingMode
@@ -4,7 +4,7 @@
4
4
  Company: eXonware.com
5
5
  Author: Eng. Muhammad AlShehri
6
6
  Email: connect@exonware.com
7
- Version: 0.0.1.409
7
+ Version: 0.0.1.411
8
8
  Generation Date: November 1, 2025
9
9
 
10
10
  File Format Conversion - Convert between compatible formats.
@@ -2,7 +2,7 @@
2
2
  Company: eXonware.com
3
3
  Author: Eng. Muhammad AlShehri
4
4
  Email: connect@exonware.com
5
- Version: 0.0.1.409
5
+ Version: 0.0.1.411
6
6
  Generation Date: September 04, 2025
7
7
 
8
8
  XWFile - Concrete implementation of file operations.
@@ -10,7 +10,7 @@ XWFile - Concrete implementation of file operations.
10
10
 
11
11
  import os
12
12
  from pathlib import Path
13
- from typing import Any, Dict, Optional, Union, BinaryIO, TextIO
13
+ from typing import Any, Optional, Union, BinaryIO, TextIO
14
14
 
15
15
  from ..base import AFile
16
16
  from ..contracts import FileMode, OperationResult, IFile
@@ -72,11 +72,12 @@ class XWFile(AFile):
72
72
  def open(self, mode: FileMode = FileMode.READ) -> None:
73
73
  """Open file with validation and monitoring."""
74
74
  if self.validate_paths:
75
- self._path_validator.validate_path(self.file_path)
75
+ for_writing = mode in [FileMode.WRITE, FileMode.APPEND, FileMode.WRITE_READ, FileMode.BINARY_WRITE, FileMode.BINARY_APPEND, FileMode.BINARY_WRITE_READ]
76
+ self._path_validator.validate_path(self.file_path, for_writing=for_writing, create_dirs=self.auto_create_dirs)
76
77
 
77
78
  with performance_monitor("file_open"):
78
79
  # Ensure parent directory exists
79
- if self.auto_create_dirs and mode in [FileMode.WRITE, FileMode.APPEND, FileMode.WRITE_PLUS]:
80
+ if self.auto_create_dirs and mode in [FileMode.WRITE, FileMode.APPEND, FileMode.WRITE_READ]:
80
81
  self.file_path.parent.mkdir(parents=True, exist_ok=True)
81
82
 
82
83
  # Open file
@@ -110,7 +111,7 @@ class XWFile(AFile):
110
111
  def save(self, data: Any, **kwargs) -> bool:
111
112
  """Save data to file with atomic operations."""
112
113
  if self.validate_paths:
113
- self._path_validator.validate_path(self.file_path)
114
+ self._path_validator.validate_path(self.file_path, for_writing=True, create_dirs=True)
114
115
 
115
116
  if self.validate_data:
116
117
  self._data_validator.validate_data(data)
@@ -119,9 +120,10 @@ class XWFile(AFile):
119
120
  try:
120
121
  if self.use_atomic_operations:
121
122
  # Use atomic file writer
122
- with AtomicFileWriter(self.file_path, backup=self.auto_backup) as writer:
123
+ mode = 'wb' if isinstance(data, bytes) else 'w'
124
+ with AtomicFileWriter(self.file_path, mode=mode, backup=self.auto_backup) as writer:
123
125
  if isinstance(data, str):
124
- writer.write(data.encode('utf-8'))
126
+ writer.write(data)
125
127
  else:
126
128
  writer.write(data)
127
129
  else:
@@ -208,7 +210,7 @@ class XWFile(AFile):
208
210
  # UTILITY METHODS
209
211
  # ============================================================================
210
212
 
211
- def get_info(self) -> Dict[str, Any]:
213
+ def get_info(self) -> dict[str, Any]:
212
214
  """Get comprehensive file information."""
213
215
  return {
214
216
  'file_path': str(self.file_path),