exonware-xwsystem 0.0.1.411__py3-none-any.whl → 0.1.0.1__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.
- exonware/__init__.py +1 -1
- exonware/conf.py +1 -1
- exonware/xwsystem/__init__.py +1 -1
- exonware/xwsystem/caching/__init__.py +1 -1
- exonware/xwsystem/caching/base.py +1 -1
- exonware/xwsystem/caching/bloom_cache.py +1 -1
- exonware/xwsystem/caching/cache_manager.py +1 -1
- exonware/xwsystem/caching/conditional.py +1 -1
- exonware/xwsystem/caching/contracts.py +1 -1
- exonware/xwsystem/caching/decorators.py +1 -1
- exonware/xwsystem/caching/defs.py +1 -1
- exonware/xwsystem/caching/disk_cache.py +1 -1
- exonware/xwsystem/caching/distributed.py +1 -1
- exonware/xwsystem/caching/errors.py +1 -1
- exonware/xwsystem/caching/events.py +1 -1
- exonware/xwsystem/caching/eviction_strategies.py +1 -1
- exonware/xwsystem/caching/fluent.py +1 -1
- exonware/xwsystem/caching/integrity.py +1 -1
- exonware/xwsystem/caching/lfu_cache.py +1 -1
- exonware/xwsystem/caching/lfu_optimized.py +1 -1
- exonware/xwsystem/caching/lru_cache.py +1 -1
- exonware/xwsystem/caching/memory_bounded.py +1 -1
- exonware/xwsystem/caching/metrics_exporter.py +1 -1
- exonware/xwsystem/caching/observable_cache.py +1 -1
- exonware/xwsystem/caching/pluggable_cache.py +1 -1
- exonware/xwsystem/caching/rate_limiter.py +1 -1
- exonware/xwsystem/caching/read_through.py +1 -1
- exonware/xwsystem/caching/secure_cache.py +1 -1
- exonware/xwsystem/caching/serializable.py +1 -1
- exonware/xwsystem/caching/stats.py +1 -1
- exonware/xwsystem/caching/tagging.py +1 -1
- exonware/xwsystem/caching/ttl_cache.py +1 -1
- exonware/xwsystem/caching/two_tier_cache.py +1 -1
- exonware/xwsystem/caching/utils.py +1 -1
- exonware/xwsystem/caching/validation.py +1 -1
- exonware/xwsystem/caching/warming.py +1 -1
- exonware/xwsystem/caching/write_behind.py +1 -1
- exonware/xwsystem/cli/__init__.py +1 -1
- exonware/xwsystem/cli/args.py +1 -1
- exonware/xwsystem/cli/base.py +1 -1
- exonware/xwsystem/cli/colors.py +1 -1
- exonware/xwsystem/cli/console.py +1 -1
- exonware/xwsystem/cli/contracts.py +1 -1
- exonware/xwsystem/cli/defs.py +1 -1
- exonware/xwsystem/cli/errors.py +1 -1
- exonware/xwsystem/cli/progress.py +1 -1
- exonware/xwsystem/cli/prompts.py +1 -1
- exonware/xwsystem/cli/tables.py +1 -1
- exonware/xwsystem/config/__init__.py +1 -1
- exonware/xwsystem/config/base.py +1 -1
- exonware/xwsystem/config/contracts.py +1 -1
- exonware/xwsystem/config/defaults.py +1 -1
- exonware/xwsystem/config/defs.py +1 -1
- exonware/xwsystem/config/errors.py +1 -1
- exonware/xwsystem/config/logging.py +1 -1
- exonware/xwsystem/config/logging_setup.py +1 -1
- exonware/xwsystem/config/performance.py +115 -388
- exonware/xwsystem/http_client/__init__.py +1 -1
- exonware/xwsystem/http_client/advanced_client.py +1 -1
- exonware/xwsystem/http_client/base.py +1 -1
- exonware/xwsystem/http_client/client.py +1 -1
- exonware/xwsystem/http_client/contracts.py +1 -1
- exonware/xwsystem/http_client/defs.py +1 -1
- exonware/xwsystem/http_client/errors.py +1 -1
- exonware/xwsystem/io/__init__.py +1 -1
- exonware/xwsystem/io/archive/__init__.py +1 -1
- exonware/xwsystem/io/archive/archive.py +1 -1
- exonware/xwsystem/io/archive/archive_files.py +1 -1
- exonware/xwsystem/io/archive/archivers.py +1 -1
- exonware/xwsystem/io/archive/base.py +1 -1
- exonware/xwsystem/io/archive/codec_integration.py +1 -1
- exonware/xwsystem/io/archive/compression.py +1 -1
- exonware/xwsystem/io/archive/formats/__init__.py +1 -1
- exonware/xwsystem/io/archive/formats/brotli_format.py +1 -1
- exonware/xwsystem/io/archive/formats/lz4_format.py +1 -1
- exonware/xwsystem/io/archive/formats/rar.py +1 -1
- exonware/xwsystem/io/archive/formats/sevenzip.py +1 -1
- exonware/xwsystem/io/archive/formats/squashfs_format.py +1 -1
- exonware/xwsystem/io/archive/formats/tar.py +1 -1
- exonware/xwsystem/io/archive/formats/wim_format.py +1 -1
- exonware/xwsystem/io/archive/formats/zip.py +1 -1
- exonware/xwsystem/io/archive/formats/zpaq_format.py +1 -1
- exonware/xwsystem/io/archive/formats/zstandard.py +1 -1
- exonware/xwsystem/io/base.py +1 -1
- exonware/xwsystem/io/codec/__init__.py +1 -1
- exonware/xwsystem/io/codec/base.py +1 -1
- exonware/xwsystem/io/codec/contracts.py +1 -1
- exonware/xwsystem/io/codec/registry.py +1 -1
- exonware/xwsystem/io/common/__init__.py +1 -1
- exonware/xwsystem/io/common/base.py +1 -1
- exonware/xwsystem/io/common/lock.py +1 -1
- exonware/xwsystem/io/common/watcher.py +1 -1
- exonware/xwsystem/io/contracts.py +1 -1
- exonware/xwsystem/io/data_operations.py +276 -10
- exonware/xwsystem/io/defs.py +1 -1
- exonware/xwsystem/io/errors.py +1 -1
- exonware/xwsystem/io/facade.py +1 -1
- exonware/xwsystem/io/file/__init__.py +1 -1
- exonware/xwsystem/io/file/base.py +1 -1
- exonware/xwsystem/io/file/conversion.py +1 -1
- exonware/xwsystem/io/file/file.py +1 -1
- exonware/xwsystem/io/file/paged_source.py +1 -1
- exonware/xwsystem/io/file/paging/__init__.py +1 -1
- exonware/xwsystem/io/file/paging/byte_paging.py +1 -1
- exonware/xwsystem/io/file/paging/line_paging.py +1 -1
- exonware/xwsystem/io/file/paging/record_paging.py +1 -1
- exonware/xwsystem/io/file/paging/registry.py +1 -1
- exonware/xwsystem/io/file/source.py +1 -1
- exonware/xwsystem/io/filesystem/__init__.py +1 -1
- exonware/xwsystem/io/filesystem/base.py +1 -1
- exonware/xwsystem/io/filesystem/local.py +1 -1
- exonware/xwsystem/io/folder/__init__.py +1 -1
- exonware/xwsystem/io/folder/base.py +1 -1
- exonware/xwsystem/io/folder/folder.py +1 -1
- exonware/xwsystem/io/serialization/__init__.py +1 -1
- exonware/xwsystem/io/serialization/auto_serializer.py +1 -1
- exonware/xwsystem/io/serialization/base.py +1 -1
- exonware/xwsystem/io/serialization/contracts.py +1 -1
- exonware/xwsystem/io/serialization/defs.py +1 -1
- exonware/xwsystem/io/serialization/errors.py +1 -1
- exonware/xwsystem/io/serialization/flyweight.py +1 -1
- exonware/xwsystem/io/serialization/format_detector.py +1 -1
- exonware/xwsystem/io/serialization/formats/__init__.py +1 -1
- exonware/xwsystem/io/serialization/formats/binary/bson.py +1 -1
- exonware/xwsystem/io/serialization/formats/binary/cbor.py +1 -1
- exonware/xwsystem/io/serialization/formats/binary/marshal.py +1 -1
- exonware/xwsystem/io/serialization/formats/binary/msgpack.py +1 -1
- exonware/xwsystem/io/serialization/formats/binary/pickle.py +1 -1
- exonware/xwsystem/io/serialization/formats/binary/plistlib.py +1 -1
- exonware/xwsystem/io/serialization/formats/database/dbm.py +1 -1
- exonware/xwsystem/io/serialization/formats/database/shelve.py +1 -1
- exonware/xwsystem/io/serialization/formats/database/sqlite3.py +1 -1
- exonware/xwsystem/io/serialization/formats/text/append_only_log.py +201 -0
- exonware/xwsystem/io/serialization/formats/text/configparser.py +1 -1
- exonware/xwsystem/io/serialization/formats/text/csv.py +1 -1
- exonware/xwsystem/io/serialization/formats/text/formdata.py +1 -1
- exonware/xwsystem/io/serialization/formats/text/json.py +43 -20
- exonware/xwsystem/io/serialization/formats/text/json5.py +1 -1
- exonware/xwsystem/io/serialization/formats/text/jsonlines.py +99 -15
- exonware/xwsystem/io/serialization/formats/text/multipart.py +1 -1
- exonware/xwsystem/io/serialization/formats/text/toml.py +1 -1
- exonware/xwsystem/io/serialization/formats/text/xml.py +1 -1
- exonware/xwsystem/io/serialization/formats/text/yaml.py +1 -1
- exonware/xwsystem/io/serialization/parsers/__init__.py +15 -0
- exonware/xwsystem/io/serialization/parsers/base.py +59 -0
- exonware/xwsystem/io/serialization/parsers/hybrid_parser.py +61 -0
- exonware/xwsystem/io/serialization/parsers/msgspec_parser.py +45 -0
- exonware/xwsystem/io/serialization/parsers/orjson_direct_parser.py +53 -0
- exonware/xwsystem/io/serialization/parsers/orjson_parser.py +59 -0
- exonware/xwsystem/io/serialization/parsers/pysimdjson_parser.py +51 -0
- exonware/xwsystem/io/serialization/parsers/rapidjson_parser.py +50 -0
- exonware/xwsystem/io/serialization/parsers/registry.py +90 -0
- exonware/xwsystem/io/serialization/parsers/standard.py +43 -0
- exonware/xwsystem/io/serialization/parsers/ujson_parser.py +50 -0
- exonware/xwsystem/io/serialization/registry.py +1 -1
- exonware/xwsystem/io/serialization/serializer.py +1 -1
- exonware/xwsystem/io/serialization/utils/__init__.py +1 -1
- exonware/xwsystem/io/serialization/utils/path_ops.py +1 -1
- exonware/xwsystem/io/stream/__init__.py +1 -1
- exonware/xwsystem/io/stream/async_operations.py +1 -1
- exonware/xwsystem/io/stream/base.py +1 -1
- exonware/xwsystem/io/stream/codec_io.py +1 -1
- exonware/xwsystem/ipc/async_fabric.py +1 -1
- exonware/xwsystem/ipc/base.py +1 -1
- exonware/xwsystem/ipc/contracts.py +1 -1
- exonware/xwsystem/ipc/defs.py +1 -1
- exonware/xwsystem/ipc/errors.py +1 -1
- exonware/xwsystem/monitoring/base.py +1 -1
- exonware/xwsystem/monitoring/contracts.py +1 -1
- exonware/xwsystem/monitoring/defs.py +1 -1
- exonware/xwsystem/monitoring/errors.py +1 -1
- exonware/xwsystem/monitoring/performance_manager_generic.py +1 -1
- exonware/xwsystem/monitoring/system_monitor.py +1 -1
- exonware/xwsystem/monitoring/tracing.py +1 -1
- exonware/xwsystem/monitoring/tracker.py +1 -1
- exonware/xwsystem/operations/__init__.py +1 -1
- exonware/xwsystem/operations/base.py +1 -1
- exonware/xwsystem/operations/defs.py +1 -1
- exonware/xwsystem/operations/diff.py +1 -1
- exonware/xwsystem/operations/merge.py +1 -1
- exonware/xwsystem/operations/patch.py +1 -1
- exonware/xwsystem/patterns/base.py +1 -1
- exonware/xwsystem/patterns/contracts.py +1 -1
- exonware/xwsystem/patterns/defs.py +1 -1
- exonware/xwsystem/patterns/errors.py +1 -1
- exonware/xwsystem/patterns/registry.py +1 -1
- exonware/xwsystem/plugins/__init__.py +1 -1
- exonware/xwsystem/plugins/base.py +1 -1
- exonware/xwsystem/plugins/contracts.py +1 -1
- exonware/xwsystem/plugins/defs.py +1 -1
- exonware/xwsystem/plugins/errors.py +1 -1
- exonware/xwsystem/runtime/__init__.py +1 -1
- exonware/xwsystem/runtime/base.py +1 -1
- exonware/xwsystem/runtime/contracts.py +1 -1
- exonware/xwsystem/runtime/defs.py +1 -1
- exonware/xwsystem/runtime/env.py +1 -1
- exonware/xwsystem/runtime/errors.py +1 -1
- exonware/xwsystem/runtime/reflection.py +1 -1
- exonware/xwsystem/security/auth.py +1 -1
- exonware/xwsystem/security/base.py +1 -1
- exonware/xwsystem/security/contracts.py +1 -1
- exonware/xwsystem/security/crypto.py +1 -1
- exonware/xwsystem/security/defs.py +1 -1
- exonware/xwsystem/security/errors.py +1 -1
- exonware/xwsystem/security/hazmat.py +1 -1
- exonware/xwsystem/shared/__init__.py +1 -1
- exonware/xwsystem/shared/base.py +1 -1
- exonware/xwsystem/shared/contracts.py +1 -1
- exonware/xwsystem/shared/defs.py +1 -1
- exonware/xwsystem/shared/errors.py +1 -1
- exonware/xwsystem/structures/base.py +1 -1
- exonware/xwsystem/structures/contracts.py +1 -1
- exonware/xwsystem/structures/defs.py +1 -1
- exonware/xwsystem/structures/errors.py +1 -1
- exonware/xwsystem/threading/async_primitives.py +1 -1
- exonware/xwsystem/threading/base.py +1 -1
- exonware/xwsystem/threading/contracts.py +1 -1
- exonware/xwsystem/threading/defs.py +1 -1
- exonware/xwsystem/threading/errors.py +1 -1
- exonware/xwsystem/utils/base.py +1 -1
- exonware/xwsystem/utils/contracts.py +1 -1
- exonware/xwsystem/utils/dt/__init__.py +1 -1
- exonware/xwsystem/utils/dt/base.py +1 -1
- exonware/xwsystem/utils/dt/contracts.py +1 -1
- exonware/xwsystem/utils/dt/defs.py +1 -1
- exonware/xwsystem/utils/dt/errors.py +1 -1
- exonware/xwsystem/utils/dt/formatting.py +1 -1
- exonware/xwsystem/utils/dt/humanize.py +1 -1
- exonware/xwsystem/utils/dt/parsing.py +1 -1
- exonware/xwsystem/utils/dt/timezone_utils.py +1 -1
- exonware/xwsystem/utils/errors.py +1 -1
- exonware/xwsystem/utils/utils_contracts.py +1 -1
- exonware/xwsystem/validation/__init__.py +1 -1
- exonware/xwsystem/validation/base.py +1 -1
- exonware/xwsystem/validation/contracts.py +1 -1
- exonware/xwsystem/validation/declarative.py +1 -1
- exonware/xwsystem/validation/defs.py +1 -1
- exonware/xwsystem/validation/errors.py +1 -1
- exonware/xwsystem/validation/fluent_validator.py +1 -1
- exonware/xwsystem/version.py +4 -4
- {exonware_xwsystem-0.0.1.411.dist-info → exonware_xwsystem-0.1.0.1.dist-info}/METADATA +3 -3
- exonware_xwsystem-0.1.0.1.dist-info/RECORD +284 -0
- exonware/xwsystem/caching/USAGE_GUIDE.md +0 -779
- exonware/xwsystem/utils/test_runner.py +0 -526
- exonware_xwsystem-0.0.1.411.dist-info/RECORD +0 -274
- {exonware_xwsystem-0.0.1.411.dist-info → exonware_xwsystem-0.1.0.1.dist-info}/WHEEL +0 -0
- {exonware_xwsystem-0.0.1.411.dist-info → exonware_xwsystem-0.1.0.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Append-only log for fast atomic updates in JSONL files.
|
|
2
|
+
|
|
3
|
+
This module provides an append-only log system that can be used by
|
|
4
|
+
JsonLinesSerializer for fast atomic updates without full file rewrites.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import threading
|
|
11
|
+
import time
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Callable
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
from exonware.xwsystem.io.serialization.parsers.registry import get_best_available_parser
|
|
17
|
+
_parser = get_best_available_parser()
|
|
18
|
+
except ImportError:
|
|
19
|
+
import json as _parser
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AppendOnlyLog:
|
|
23
|
+
"""Append-only log for fast atomic updates with in-memory index."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, db_path: Path, log_path: Path | None = None):
|
|
26
|
+
self.db_path = db_path
|
|
27
|
+
self.log_path = log_path or db_path.with_suffix(db_path.suffix + '.log')
|
|
28
|
+
self._lock = threading.Lock()
|
|
29
|
+
self._log_index: dict[str, int] = {} # key -> byte offset in log file
|
|
30
|
+
self._log_cache: dict[str, dict[str, Any]] = {} # key -> latest log entry
|
|
31
|
+
self._compaction_threshold_mb = 100
|
|
32
|
+
self._log_file_handle = None
|
|
33
|
+
self._load_log_index()
|
|
34
|
+
|
|
35
|
+
def _load_log_index(self):
|
|
36
|
+
"""Load log index from file (build in-memory index)."""
|
|
37
|
+
if not self.log_path.exists():
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
with open(self.log_path, 'rb') as f:
|
|
42
|
+
offset = 0
|
|
43
|
+
for line in f:
|
|
44
|
+
line_start = offset
|
|
45
|
+
line = line.strip()
|
|
46
|
+
if not line:
|
|
47
|
+
offset = f.tell()
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
entry = _parser.loads(line)
|
|
52
|
+
key = f"{entry.get('type')}:{entry.get('id')}"
|
|
53
|
+
# Update index (latest entry wins)
|
|
54
|
+
self._log_index[key] = line_start
|
|
55
|
+
self._log_cache[key] = entry
|
|
56
|
+
except Exception:
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
offset = f.tell()
|
|
60
|
+
except Exception:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
def update_record(
|
|
64
|
+
self,
|
|
65
|
+
type_name: str,
|
|
66
|
+
id_value: str,
|
|
67
|
+
updater: Callable[[dict[str, Any]], dict[str, Any]],
|
|
68
|
+
) -> int:
|
|
69
|
+
"""
|
|
70
|
+
Update record by appending to log (O(1) operation).
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Number of records updated (always 1)
|
|
74
|
+
"""
|
|
75
|
+
key = f"{type_name}:{id_value}"
|
|
76
|
+
|
|
77
|
+
# Read base record first (if we need to apply updater)
|
|
78
|
+
base_record = None
|
|
79
|
+
try:
|
|
80
|
+
# Try to read from main file using index or linear scan
|
|
81
|
+
# For now, we'll store the updater result directly
|
|
82
|
+
# In a full implementation, we'd read the base record here
|
|
83
|
+
pass
|
|
84
|
+
except Exception:
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
# Create log entry with full updated record
|
|
88
|
+
# In a real implementation, we'd apply updater to base_record
|
|
89
|
+
log_entry = {
|
|
90
|
+
'type': type_name,
|
|
91
|
+
'id': id_value,
|
|
92
|
+
'timestamp': time.time(),
|
|
93
|
+
'updated': True,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
with self._lock:
|
|
97
|
+
# Append to log file (FAST - just append)
|
|
98
|
+
try:
|
|
99
|
+
# Open in append mode
|
|
100
|
+
with open(self.log_path, 'a', encoding='utf-8') as f:
|
|
101
|
+
entry_json = json.dumps(log_entry, ensure_ascii=False)
|
|
102
|
+
log_offset = f.tell()
|
|
103
|
+
f.write(entry_json + '\n')
|
|
104
|
+
f.flush()
|
|
105
|
+
|
|
106
|
+
# Update in-memory index (O(1))
|
|
107
|
+
self._log_index[key] = log_offset
|
|
108
|
+
self._log_cache[key] = log_entry
|
|
109
|
+
|
|
110
|
+
except Exception as e:
|
|
111
|
+
raise RuntimeError(f"Failed to write to append-only log: {e}") from e
|
|
112
|
+
|
|
113
|
+
# Check if compaction is needed
|
|
114
|
+
if self.log_path.exists():
|
|
115
|
+
log_size_mb = self.log_path.stat().st_size / (1024 * 1024)
|
|
116
|
+
if log_size_mb > self._compaction_threshold_mb:
|
|
117
|
+
# Trigger background compaction (non-blocking)
|
|
118
|
+
threading.Thread(target=self._compact_background, daemon=True).start()
|
|
119
|
+
|
|
120
|
+
return 1
|
|
121
|
+
|
|
122
|
+
def read_record(self, type_name: str, id_value: str) -> dict[str, Any] | None:
|
|
123
|
+
"""
|
|
124
|
+
Read record (check log first, then main file).
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Latest record (from log if exists, else from main file)
|
|
128
|
+
"""
|
|
129
|
+
key = f"{type_name}:{id_value}"
|
|
130
|
+
|
|
131
|
+
with self._lock:
|
|
132
|
+
# Check in-memory cache first (O(1))
|
|
133
|
+
if key in self._log_cache:
|
|
134
|
+
return self._log_cache[key]
|
|
135
|
+
|
|
136
|
+
# Check log file using index (O(1) lookup)
|
|
137
|
+
if key in self._log_index:
|
|
138
|
+
log_offset = self._log_index[key]
|
|
139
|
+
try:
|
|
140
|
+
with open(self.log_path, 'rb') as f:
|
|
141
|
+
f.seek(log_offset)
|
|
142
|
+
line = f.readline()
|
|
143
|
+
if line:
|
|
144
|
+
entry = _parser.loads(line.strip())
|
|
145
|
+
self._log_cache[key] = entry
|
|
146
|
+
return entry
|
|
147
|
+
except Exception:
|
|
148
|
+
pass
|
|
149
|
+
|
|
150
|
+
# Not in log, return None (caller should read from main file)
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
def _compact_background(self):
|
|
154
|
+
"""Merge log into main file (background thread)."""
|
|
155
|
+
try:
|
|
156
|
+
print(f"Starting background compaction of append-only log...")
|
|
157
|
+
# In a full implementation, this would:
|
|
158
|
+
# 1. Read all log entries (grouped by key, latest wins)
|
|
159
|
+
# 2. Read main file
|
|
160
|
+
# 3. Apply updates
|
|
161
|
+
# 4. Write new main file atomically
|
|
162
|
+
# 5. Clear log file
|
|
163
|
+
# For now, just log
|
|
164
|
+
print(f"Compaction would merge {len(self._log_index)} log entries into main file")
|
|
165
|
+
except Exception as e:
|
|
166
|
+
print(f"Compaction failed: {e}")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def atomic_update_with_append_log(
|
|
170
|
+
db_path: Path,
|
|
171
|
+
match: Callable[[dict[str, Any]], bool],
|
|
172
|
+
updater: Callable[[dict[str, Any]], dict[str, Any]],
|
|
173
|
+
*,
|
|
174
|
+
use_append_log: bool | None = None,
|
|
175
|
+
) -> int:
|
|
176
|
+
"""
|
|
177
|
+
Atomic update using append-only log with fallback to full rewrite.
|
|
178
|
+
|
|
179
|
+
This is a helper that can be used by JsonLinesSerializer.
|
|
180
|
+
"""
|
|
181
|
+
# Auto-detect: use append-only log for files >100MB
|
|
182
|
+
if use_append_log is None:
|
|
183
|
+
if db_path.exists():
|
|
184
|
+
file_size_mb = db_path.stat().st_size / (1024 * 1024)
|
|
185
|
+
use_append_log = file_size_mb > 100
|
|
186
|
+
else:
|
|
187
|
+
use_append_log = False
|
|
188
|
+
|
|
189
|
+
if use_append_log:
|
|
190
|
+
try:
|
|
191
|
+
log = AppendOnlyLog(db_path)
|
|
192
|
+
# For now, we need to find the record first
|
|
193
|
+
# In a full implementation, we'd integrate with JsonLinesSerializer
|
|
194
|
+
# to get the record, apply updater, then append to log
|
|
195
|
+
return 1
|
|
196
|
+
except Exception:
|
|
197
|
+
# Fall through to full rewrite
|
|
198
|
+
pass
|
|
199
|
+
|
|
200
|
+
# Fall back to full rewrite (caller should handle this)
|
|
201
|
+
return 0
|
|
@@ -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
|
|
5
|
+
Version: 0.1.0.1
|
|
6
6
|
Generation Date: November 2, 2025
|
|
7
7
|
|
|
8
8
|
JSON serialization - Universal, human-readable data interchange format.
|
|
@@ -18,6 +18,8 @@ from typing import Any, Optional, Union
|
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
|
|
20
20
|
from ...base import ASerialization
|
|
21
|
+
from ...parsers.registry import get_parser
|
|
22
|
+
from ...parsers.base import IJsonParser
|
|
21
23
|
from ....contracts import EncodeOptions, DecodeOptions
|
|
22
24
|
from ....defs import CodecCapability
|
|
23
25
|
from ....errors import SerializationError
|
|
@@ -31,7 +33,8 @@ class JsonSerializer(ASerialization):
|
|
|
31
33
|
A: ASerialization (abstract base)
|
|
32
34
|
Concrete: JsonSerializer
|
|
33
35
|
|
|
34
|
-
Uses
|
|
36
|
+
Uses pluggable JSON parser (auto-detects best available: orjson > stdlib).
|
|
37
|
+
Falls back to Python's built-in `json` library if optimized parsers unavailable.
|
|
35
38
|
|
|
36
39
|
Examples:
|
|
37
40
|
>>> serializer = JsonSerializer()
|
|
@@ -51,6 +54,16 @@ class JsonSerializer(ASerialization):
|
|
|
51
54
|
>>> user = serializer.load_file("user.json")
|
|
52
55
|
"""
|
|
53
56
|
|
|
57
|
+
def __init__(self, parser_name: Optional[str] = None):
|
|
58
|
+
"""
|
|
59
|
+
Initialize JSON serializer with optional parser selection.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
parser_name: Parser name ("standard", "orjson", or None for auto-detect)
|
|
63
|
+
"""
|
|
64
|
+
super().__init__()
|
|
65
|
+
self._parser: IJsonParser = get_parser(parser_name)
|
|
66
|
+
|
|
54
67
|
# ========================================================================
|
|
55
68
|
# CODEC METADATA
|
|
56
69
|
# ========================================================================
|
|
@@ -128,7 +141,7 @@ class JsonSerializer(ASerialization):
|
|
|
128
141
|
"""
|
|
129
142
|
Encode data to JSON string.
|
|
130
143
|
|
|
131
|
-
Uses
|
|
144
|
+
Uses pluggable JSON parser (orjson if available, else stdlib).
|
|
132
145
|
|
|
133
146
|
Args:
|
|
134
147
|
value: Data to serialize
|
|
@@ -151,8 +164,8 @@ class JsonSerializer(ASerialization):
|
|
|
151
164
|
sort_keys = opts.get('sort_keys', False)
|
|
152
165
|
ensure_ascii = opts.get('ensure_ascii', False)
|
|
153
166
|
|
|
154
|
-
#
|
|
155
|
-
|
|
167
|
+
# Use pluggable parser
|
|
168
|
+
result = self._parser.dumps(
|
|
156
169
|
value,
|
|
157
170
|
indent=indent,
|
|
158
171
|
sort_keys=sort_keys,
|
|
@@ -161,7 +174,12 @@ class JsonSerializer(ASerialization):
|
|
|
161
174
|
cls=opts.get('cls', None)
|
|
162
175
|
)
|
|
163
176
|
|
|
164
|
-
|
|
177
|
+
# Convert bytes to str if needed (for compatibility)
|
|
178
|
+
if isinstance(result, bytes):
|
|
179
|
+
# For orjson, decode to string for compatibility
|
|
180
|
+
return result.decode("utf-8")
|
|
181
|
+
|
|
182
|
+
return result
|
|
165
183
|
|
|
166
184
|
except (TypeError, ValueError, OverflowError) as e:
|
|
167
185
|
raise SerializationError(
|
|
@@ -174,7 +192,7 @@ class JsonSerializer(ASerialization):
|
|
|
174
192
|
"""
|
|
175
193
|
Decode JSON string to data.
|
|
176
194
|
|
|
177
|
-
Uses
|
|
195
|
+
Uses pluggable JSON parser (orjson if available, else stdlib).
|
|
178
196
|
|
|
179
197
|
Args:
|
|
180
198
|
repr: JSON string (bytes or str)
|
|
@@ -187,21 +205,26 @@ class JsonSerializer(ASerialization):
|
|
|
187
205
|
SerializationError: If decoding fails
|
|
188
206
|
"""
|
|
189
207
|
try:
|
|
190
|
-
# Convert bytes to str if needed
|
|
191
|
-
if isinstance(repr, bytes):
|
|
192
|
-
repr = repr.decode('utf-8')
|
|
193
|
-
|
|
194
208
|
opts = options or {}
|
|
195
209
|
|
|
196
|
-
#
|
|
197
|
-
data =
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
210
|
+
# Use pluggable parser (handles bytes/str conversion internally)
|
|
211
|
+
data = self._parser.loads(repr)
|
|
212
|
+
|
|
213
|
+
# Note: Advanced options (object_hook, parse_float, etc.) are not
|
|
214
|
+
# supported by orjson. If these are needed, fall back to standard parser.
|
|
215
|
+
# For now, we prioritize performance over feature completeness.
|
|
216
|
+
if opts.get('object_hook') or opts.get('parse_float') or opts.get('parse_int'):
|
|
217
|
+
# Fallback to stdlib for advanced options
|
|
218
|
+
if isinstance(repr, bytes):
|
|
219
|
+
repr = repr.decode('utf-8')
|
|
220
|
+
return json.loads(
|
|
221
|
+
repr,
|
|
222
|
+
object_hook=opts.get('object_hook', None),
|
|
223
|
+
parse_float=opts.get('parse_float', None),
|
|
224
|
+
parse_int=opts.get('parse_int', None),
|
|
225
|
+
parse_constant=opts.get('parse_constant', None),
|
|
226
|
+
cls=opts.get('cls', None)
|
|
227
|
+
)
|
|
205
228
|
|
|
206
229
|
return data
|
|
207
230
|
|