exonware-xwsystem 0.0.1.410__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.
- exonware/__init__.py +1 -1
- exonware/conf.py +1 -1
- exonware/xwsystem/__init__.py +2 -2
- exonware/xwsystem/caching/__init__.py +1 -1
- exonware/xwsystem/caching/base.py +2 -2
- exonware/xwsystem/caching/bloom_cache.py +2 -2
- exonware/xwsystem/caching/cache_manager.py +1 -1
- exonware/xwsystem/caching/conditional.py +2 -2
- exonware/xwsystem/caching/contracts.py +1 -1
- exonware/xwsystem/caching/decorators.py +2 -2
- 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 +2 -2
- 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 +2 -2
- exonware/xwsystem/caching/lfu_optimized.py +3 -3
- exonware/xwsystem/caching/lru_cache.py +2 -2
- exonware/xwsystem/caching/memory_bounded.py +2 -2
- exonware/xwsystem/caching/metrics_exporter.py +2 -2
- exonware/xwsystem/caching/observable_cache.py +1 -1
- exonware/xwsystem/caching/pluggable_cache.py +2 -2
- exonware/xwsystem/caching/rate_limiter.py +1 -1
- exonware/xwsystem/caching/read_through.py +2 -2
- exonware/xwsystem/caching/secure_cache.py +1 -1
- exonware/xwsystem/caching/serializable.py +2 -2
- exonware/xwsystem/caching/stats.py +1 -1
- exonware/xwsystem/caching/tagging.py +2 -2
- 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 +2 -2
- exonware/xwsystem/caching/write_behind.py +2 -2
- 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 +2 -2
- 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 +2 -2
- exonware/xwsystem/config/logging.py +1 -1
- exonware/xwsystem/config/logging_setup.py +2 -2
- exonware/xwsystem/config/performance.py +1 -1
- exonware/xwsystem/http_client/__init__.py +1 -1
- exonware/xwsystem/http_client/advanced_client.py +2 -2
- exonware/xwsystem/http_client/base.py +2 -2
- exonware/xwsystem/http_client/client.py +2 -2
- exonware/xwsystem/http_client/contracts.py +1 -1
- exonware/xwsystem/http_client/defs.py +1 -1
- exonware/xwsystem/http_client/errors.py +2 -2
- 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 +2 -2
- exonware/xwsystem/io/archive/base.py +6 -6
- 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 +6 -3
- exonware/xwsystem/io/archive/formats/lz4_format.py +6 -3
- exonware/xwsystem/io/archive/formats/rar.py +6 -3
- exonware/xwsystem/io/archive/formats/sevenzip.py +6 -3
- 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 +6 -3
- 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 +6 -3
- exonware/xwsystem/io/base.py +1 -1
- exonware/xwsystem/io/codec/__init__.py +1 -1
- exonware/xwsystem/io/codec/base.py +6 -6
- exonware/xwsystem/io/codec/contracts.py +1 -1
- exonware/xwsystem/io/codec/registry.py +5 -5
- 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 +480 -0
- exonware/xwsystem/io/defs.py +1 -1
- exonware/xwsystem/io/errors.py +1 -1
- exonware/xwsystem/io/facade.py +2 -2
- 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 +8 -6
- exonware/xwsystem/io/file/paged_source.py +8 -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 +4 -4
- exonware/xwsystem/io/file/source.py +20 -9
- exonware/xwsystem/io/filesystem/__init__.py +1 -1
- exonware/xwsystem/io/filesystem/base.py +1 -1
- exonware/xwsystem/io/filesystem/local.py +9 -1
- exonware/xwsystem/io/folder/__init__.py +1 -1
- exonware/xwsystem/io/folder/base.py +1 -1
- exonware/xwsystem/io/folder/folder.py +2 -2
- exonware/xwsystem/io/serialization/__init__.py +1 -1
- exonware/xwsystem/io/serialization/auto_serializer.py +52 -39
- exonware/xwsystem/io/serialization/base.py +165 -1
- exonware/xwsystem/io/serialization/contracts.py +88 -1
- exonware/xwsystem/io/serialization/defs.py +1 -1
- exonware/xwsystem/io/serialization/errors.py +1 -1
- exonware/xwsystem/io/serialization/flyweight.py +10 -10
- exonware/xwsystem/io/serialization/format_detector.py +8 -5
- 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 +53 -1
- exonware/xwsystem/io/serialization/formats/database/shelve.py +48 -1
- exonware/xwsystem/io/serialization/formats/database/sqlite3.py +85 -1
- 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 +1 -1
- exonware/xwsystem/io/serialization/formats/text/json5.py +7 -5
- exonware/xwsystem/io/serialization/formats/text/jsonlines.py +229 -19
- exonware/xwsystem/io/serialization/formats/text/multipart.py +1 -1
- exonware/xwsystem/io/serialization/formats/text/toml.py +19 -3
- exonware/xwsystem/io/serialization/formats/text/xml.py +8 -1
- exonware/xwsystem/io/serialization/formats/text/yaml.py +52 -2
- exonware/xwsystem/io/serialization/registry.py +1 -1
- exonware/xwsystem/io/serialization/serializer.py +175 -3
- 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 -2
- exonware/xwsystem/ipc/base.py +2 -2
- exonware/xwsystem/ipc/contracts.py +2 -2
- exonware/xwsystem/ipc/defs.py +1 -1
- exonware/xwsystem/ipc/errors.py +2 -2
- exonware/xwsystem/ipc/pipes.py +2 -2
- exonware/xwsystem/ipc/shared_memory.py +2 -2
- exonware/xwsystem/monitoring/base.py +2 -2
- exonware/xwsystem/monitoring/contracts.py +1 -1
- exonware/xwsystem/monitoring/defs.py +1 -1
- exonware/xwsystem/monitoring/error_recovery.py +2 -2
- exonware/xwsystem/monitoring/errors.py +2 -2
- exonware/xwsystem/monitoring/memory_monitor.py +1 -1
- exonware/xwsystem/monitoring/performance_manager_generic.py +2 -2
- exonware/xwsystem/monitoring/performance_validator.py +1 -1
- exonware/xwsystem/monitoring/system_monitor.py +2 -2
- exonware/xwsystem/monitoring/tracing.py +2 -2
- 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 +2 -2
- exonware/xwsystem/patterns/context_manager.py +2 -2
- exonware/xwsystem/patterns/contracts.py +9 -9
- exonware/xwsystem/patterns/defs.py +1 -1
- exonware/xwsystem/patterns/dynamic_facade.py +8 -8
- exonware/xwsystem/patterns/errors.py +5 -5
- exonware/xwsystem/patterns/handler_factory.py +6 -6
- exonware/xwsystem/patterns/object_pool.py +7 -7
- exonware/xwsystem/patterns/registry.py +3 -3
- exonware/xwsystem/plugins/__init__.py +1 -1
- exonware/xwsystem/plugins/base.py +5 -5
- exonware/xwsystem/plugins/contracts.py +5 -5
- exonware/xwsystem/plugins/defs.py +1 -1
- exonware/xwsystem/plugins/errors.py +4 -4
- exonware/xwsystem/runtime/__init__.py +1 -1
- exonware/xwsystem/runtime/base.py +6 -6
- exonware/xwsystem/runtime/contracts.py +6 -6
- exonware/xwsystem/runtime/defs.py +1 -1
- exonware/xwsystem/runtime/env.py +2 -2
- exonware/xwsystem/runtime/errors.py +1 -1
- exonware/xwsystem/runtime/reflection.py +8 -8
- exonware/xwsystem/security/auth.py +1 -1
- exonware/xwsystem/security/base.py +2 -2
- exonware/xwsystem/security/contracts.py +1 -1
- exonware/xwsystem/security/crypto.py +2 -2
- exonware/xwsystem/security/defs.py +1 -1
- exonware/xwsystem/security/errors.py +2 -2
- exonware/xwsystem/security/hazmat.py +2 -2
- 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/__init__.py +1 -1
- exonware/xwsystem/structures/base.py +2 -2
- exonware/xwsystem/structures/contracts.py +1 -1
- exonware/xwsystem/structures/defs.py +1 -1
- exonware/xwsystem/structures/errors.py +2 -2
- exonware/xwsystem/threading/async_primitives.py +2 -2
- exonware/xwsystem/threading/base.py +2 -2
- exonware/xwsystem/threading/contracts.py +1 -1
- exonware/xwsystem/threading/defs.py +1 -1
- exonware/xwsystem/threading/errors.py +2 -2
- exonware/xwsystem/threading/safe_factory.py +6 -6
- exonware/xwsystem/utils/base.py +2 -2
- exonware/xwsystem/utils/contracts.py +1 -1
- exonware/xwsystem/utils/dt/__init__.py +1 -1
- exonware/xwsystem/utils/dt/base.py +2 -2
- exonware/xwsystem/utils/dt/contracts.py +1 -1
- exonware/xwsystem/utils/dt/defs.py +1 -1
- exonware/xwsystem/utils/dt/errors.py +2 -2
- exonware/xwsystem/utils/dt/formatting.py +1 -1
- exonware/xwsystem/utils/dt/humanize.py +2 -2
- exonware/xwsystem/utils/dt/parsing.py +1 -1
- exonware/xwsystem/utils/dt/timezone_utils.py +1 -1
- exonware/xwsystem/utils/errors.py +2 -2
- exonware/xwsystem/utils/test_runner.py +1 -1
- exonware/xwsystem/utils/utils_contracts.py +1 -1
- exonware/xwsystem/validation/__init__.py +1 -1
- exonware/xwsystem/validation/base.py +15 -15
- exonware/xwsystem/validation/contracts.py +1 -1
- exonware/xwsystem/validation/data_validator.py +10 -0
- exonware/xwsystem/validation/declarative.py +9 -9
- exonware/xwsystem/validation/defs.py +1 -1
- exonware/xwsystem/validation/errors.py +2 -2
- exonware/xwsystem/validation/fluent_validator.py +4 -4
- exonware/xwsystem/version.py +2 -2
- {exonware_xwsystem-0.0.1.410.dist-info → exonware_xwsystem-0.0.1.411.dist-info}/METADATA +3 -3
- exonware_xwsystem-0.0.1.411.dist-info/RECORD +274 -0
- exonware_xwsystem-0.0.1.410.dist-info/RECORD +0 -273
- {exonware_xwsystem-0.0.1.410.dist-info → exonware_xwsystem-0.0.1.411.dist-info}/WHEEL +0 -0
- {exonware_xwsystem-0.0.1.410.dist-info → exonware_xwsystem-0.0.1.411.dist-info}/licenses/LICENSE +0 -0
|
@@ -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.
|
|
7
|
+
Version: 0.0.1.411
|
|
8
8
|
Generation Date: 30-Oct-2025
|
|
9
9
|
|
|
10
10
|
File-based data source implementation.
|
|
@@ -66,12 +66,14 @@ class FileDataSource(IDataSource[Union[bytes, str]]):
|
|
|
66
66
|
self._validate_path = validate_path
|
|
67
67
|
|
|
68
68
|
if validate_path:
|
|
69
|
-
# Use
|
|
69
|
+
# Use PathValidator for validation if available
|
|
70
70
|
try:
|
|
71
|
-
from
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
from ...security.path_validator import PathValidator
|
|
72
|
+
# Don't check existence during initialization - file may be created later
|
|
73
|
+
pv = PathValidator(check_existence=False)
|
|
74
|
+
# For write modes, allow creating the file (path doesn't exist yet)
|
|
75
|
+
for_writing = mode and ('w' in mode or 'a' in mode or '+' in mode)
|
|
76
|
+
pv.validate_path(str(self._path), for_writing=for_writing, create_dirs=True)
|
|
75
77
|
except ImportError:
|
|
76
78
|
pass
|
|
77
79
|
|
|
@@ -131,10 +133,19 @@ class FileDataSource(IDataSource[Union[bytes, str]]):
|
|
|
131
133
|
if atomic:
|
|
132
134
|
# Use AtomicFileWriter from common
|
|
133
135
|
from ..common.atomic import AtomicFileWriter
|
|
134
|
-
|
|
136
|
+
# Determine mode based on data type and stored mode
|
|
137
|
+
if isinstance(data, bytes):
|
|
138
|
+
# Binary mode
|
|
139
|
+
atomic_mode = 'wb'
|
|
140
|
+
encoding = None
|
|
141
|
+
else:
|
|
142
|
+
# Text mode
|
|
143
|
+
atomic_mode = 'w'
|
|
144
|
+
encoding = options.get('encoding', self._encoding or 'utf-8')
|
|
145
|
+
|
|
146
|
+
with AtomicFileWriter(self._path, mode=atomic_mode, encoding=encoding, backup=backup) as writer:
|
|
135
147
|
if isinstance(data, str):
|
|
136
|
-
|
|
137
|
-
writer.write(data.encode(encoding))
|
|
148
|
+
writer.write(data)
|
|
138
149
|
else:
|
|
139
150
|
writer.write(data)
|
|
140
151
|
else:
|
|
@@ -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.
|
|
7
|
+
Version: 0.0.1.411
|
|
8
8
|
Generation Date: 30-Oct-2025
|
|
9
9
|
|
|
10
10
|
Local filesystem implementation.
|
|
@@ -144,4 +144,12 @@ class LocalFileSystem(IFileSystem):
|
|
|
144
144
|
p = self._resolve_path(path)
|
|
145
145
|
p.parent.mkdir(parents=True, exist_ok=True)
|
|
146
146
|
p.write_bytes(content)
|
|
147
|
+
|
|
148
|
+
def read(self, path: str) -> bytes:
|
|
149
|
+
"""Read file contents."""
|
|
150
|
+
return self.read_bytes(path)
|
|
151
|
+
|
|
152
|
+
def write(self, path: str, content: bytes) -> None:
|
|
153
|
+
"""Write file contents."""
|
|
154
|
+
self.write_bytes(path, content)
|
|
147
155
|
|
|
@@ -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.0.1.411
|
|
6
6
|
Generation Date: September 04, 2025
|
|
7
7
|
|
|
8
8
|
XWFolder - Concrete implementation of folder operations.
|
|
@@ -65,7 +65,7 @@ class XWFolder(AFolder):
|
|
|
65
65
|
def create(self, parents: bool = True, exist_ok: bool = True) -> bool:
|
|
66
66
|
"""Create directory with validation."""
|
|
67
67
|
if self.validate_paths:
|
|
68
|
-
self._path_validator.validate_path(self.dir_path)
|
|
68
|
+
self._path_validator.validate_path(self.dir_path, for_writing=True, create_dirs=parents)
|
|
69
69
|
|
|
70
70
|
with performance_monitor("directory_create"):
|
|
71
71
|
try:
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
Company: eXonware.com
|
|
4
4
|
Author: Eng. Muhammad AlShehri
|
|
5
5
|
Email: connect@exonware.com
|
|
6
|
-
Version: 0.0.1.
|
|
6
|
+
Version: 0.0.1.411
|
|
7
7
|
Generation Date: September 04, 2025
|
|
8
8
|
|
|
9
9
|
Automatic serializer that detects format and delegates to appropriate serializer.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
from pathlib import Path
|
|
13
|
-
from typing import Any, Optional,
|
|
13
|
+
from typing import Any, Optional, Union
|
|
14
14
|
|
|
15
15
|
from .format_detector import FormatDetector, detect_format
|
|
16
16
|
from .contracts import ISerialization
|
|
@@ -39,7 +39,7 @@ class AutoSerializer:
|
|
|
39
39
|
self._serializer_cache: dict[str, ISerialization] = {}
|
|
40
40
|
self._default_format = default_format
|
|
41
41
|
|
|
42
|
-
def _get_serializer_class(self, format_name: str) ->
|
|
42
|
+
def _get_serializer_class(self, format_name: str) -> type[ISerialization]:
|
|
43
43
|
"""
|
|
44
44
|
Get serializer class for format name.
|
|
45
45
|
|
|
@@ -52,36 +52,45 @@ class AutoSerializer:
|
|
|
52
52
|
Raises:
|
|
53
53
|
ImportError: If format not available
|
|
54
54
|
"""
|
|
55
|
-
# Dynamic import to avoid circular dependencies
|
|
55
|
+
# Dynamic import to avoid circular dependencies.
|
|
56
|
+
# Root cause fix: import concrete serializers from the canonical
|
|
57
|
+
# xwsystem.io.serialization.formats packages instead of a parallel
|
|
58
|
+
# exonware.xwsystem.serialization namespace (which should not exist).
|
|
56
59
|
module_map = {
|
|
57
|
-
|
|
58
|
-
'
|
|
59
|
-
'
|
|
60
|
-
'
|
|
61
|
-
'
|
|
62
|
-
'
|
|
63
|
-
'
|
|
64
|
-
'
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
'
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
'
|
|
71
|
-
'
|
|
72
|
-
'
|
|
73
|
-
'
|
|
74
|
-
'
|
|
75
|
-
'
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
'
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
'
|
|
60
|
+
# Text formats
|
|
61
|
+
'JSON': ('io.serialization.formats.text.json', 'JsonSerializer'),
|
|
62
|
+
'JSONL': ('io.serialization.formats.text.jsonlines', 'JsonLinesSerializer'),
|
|
63
|
+
'NDJSON': ('io.serialization.formats.text.jsonlines', 'JsonLinesSerializer'),
|
|
64
|
+
'YAML': ('io.serialization.formats.text.yaml', 'YamlSerializer'),
|
|
65
|
+
'TOML': ('io.serialization.formats.text.toml', 'TomlSerializer'),
|
|
66
|
+
'XML': ('io.serialization.formats.text.xml', 'XmlSerializer'),
|
|
67
|
+
'CSV': ('io.serialization.formats.text.csv', 'CsvSerializer'),
|
|
68
|
+
'ConfigParser': ('io.serialization.formats.text.configparser', 'ConfigParserSerializer'),
|
|
69
|
+
'FormData': ('io.serialization.formats.text.formdata', 'FormDataSerializer'),
|
|
70
|
+
'Multipart': ('io.serialization.formats.text.multipart', 'MultipartSerializer'),
|
|
71
|
+
|
|
72
|
+
# Binary / database formats
|
|
73
|
+
'BSON': ('io.serialization.formats.binary.bson', 'BsonSerializer'),
|
|
74
|
+
'MessagePack': ('io.serialization.formats.binary.msgpack', 'MsgPackSerializer'),
|
|
75
|
+
'CBOR': ('io.serialization.formats.binary.cbor', 'CborSerializer'),
|
|
76
|
+
'Pickle': ('io.serialization.formats.binary.pickle', 'PickleSerializer'),
|
|
77
|
+
'Marshal': ('io.serialization.formats.binary.marshal', 'MarshalSerializer'),
|
|
78
|
+
'SQLite3': ('io.serialization.formats.database.sqlite3', 'Sqlite3Serializer'),
|
|
79
|
+
'DBM': ('io.serialization.formats.database.dbm', 'DbmSerializer'),
|
|
80
|
+
'Shelve': ('io.serialization.formats.database.shelve', 'ShelveSerializer'),
|
|
81
|
+
'Plistlib': ('io.serialization.formats.binary.plistlib', 'PlistSerializer'),
|
|
82
|
+
|
|
83
|
+
# Schema-based / advanced formats (placeholders for future modules)
|
|
84
|
+
# These entries intentionally point to non-existent modules today.
|
|
85
|
+
# The lazy installation system will handle installing/adding them
|
|
86
|
+
# when the corresponding format implementations are introduced.
|
|
87
|
+
'Avro': ('io.serialization.formats.schema.avro', 'AvroSerializer'),
|
|
88
|
+
'Protobuf': ('io.serialization.formats.schema.protobuf', 'ProtobufSerializer'),
|
|
89
|
+
'Thrift': ('io.serialization.formats.schema.thrift', 'ThriftSerializer'),
|
|
90
|
+
'Parquet': ('io.serialization.formats.scientific.parquet', 'ParquetSerializer'),
|
|
91
|
+
'ORC': ('io.serialization.formats.scientific.orc', 'OrcSerializer'),
|
|
92
|
+
'CapnProto': ('io.serialization.formats.schema.capnproto', 'CapnProtoSerializer'),
|
|
93
|
+
'FlatBuffers': ('io.serialization.formats.schema.flatbuffers', 'FlatBuffersSerializer'),
|
|
85
94
|
}
|
|
86
95
|
|
|
87
96
|
if format_name not in module_map:
|
|
@@ -90,9 +99,9 @@ class AutoSerializer:
|
|
|
90
99
|
module_name, class_name = module_map[format_name]
|
|
91
100
|
|
|
92
101
|
try:
|
|
93
|
-
# Import from
|
|
94
|
-
module = __import__(f'exonware.xwsystem.
|
|
95
|
-
|
|
102
|
+
# Import from canonical xwsystem.io serialization path
|
|
103
|
+
module = __import__(f'exonware.xwsystem.{module_name}',
|
|
104
|
+
fromlist=[class_name])
|
|
96
105
|
return getattr(module, class_name)
|
|
97
106
|
except (ImportError, AttributeError) as e:
|
|
98
107
|
# Lazy installation system will handle missing dependencies
|
|
@@ -119,7 +128,8 @@ class AutoSerializer:
|
|
|
119
128
|
self,
|
|
120
129
|
data: Any,
|
|
121
130
|
file_path: Optional[Union[str, Path]] = None,
|
|
122
|
-
format_hint: Optional[str] = None
|
|
131
|
+
format_hint: Optional[str] = None,
|
|
132
|
+
**opts
|
|
123
133
|
) -> Union[str, bytes]:
|
|
124
134
|
"""
|
|
125
135
|
Auto-detect format and serialize data.
|
|
@@ -128,6 +138,7 @@ class AutoSerializer:
|
|
|
128
138
|
data: Data to serialize
|
|
129
139
|
file_path: Optional file path for format detection
|
|
130
140
|
format_hint: Optional format hint to use
|
|
141
|
+
**opts: Additional serializer-specific options (pretty, indent, etc.)
|
|
131
142
|
|
|
132
143
|
Returns:
|
|
133
144
|
Serialized data
|
|
@@ -145,7 +156,7 @@ class AutoSerializer:
|
|
|
145
156
|
logger.debug(f"Using default format: {format_name}")
|
|
146
157
|
|
|
147
158
|
serializer = self._get_serializer(format_name)
|
|
148
|
-
return serializer.dumps(data)
|
|
159
|
+
return serializer.dumps(data, **opts)
|
|
149
160
|
|
|
150
161
|
def detect_and_deserialize(
|
|
151
162
|
self,
|
|
@@ -362,7 +373,8 @@ _global_auto_serializer = AutoSerializer()
|
|
|
362
373
|
def auto_serialize(
|
|
363
374
|
data: Any,
|
|
364
375
|
file_path: Optional[Union[str, Path]] = None,
|
|
365
|
-
format_hint: Optional[str] = None
|
|
376
|
+
format_hint: Optional[str] = None,
|
|
377
|
+
**opts
|
|
366
378
|
) -> Union[str, bytes]:
|
|
367
379
|
"""
|
|
368
380
|
Convenience function for auto-serialization.
|
|
@@ -371,11 +383,12 @@ def auto_serialize(
|
|
|
371
383
|
data: Data to serialize
|
|
372
384
|
file_path: Optional file path for format detection
|
|
373
385
|
format_hint: Optional format hint
|
|
386
|
+
**opts: Additional serializer options
|
|
374
387
|
|
|
375
388
|
Returns:
|
|
376
389
|
Serialized data
|
|
377
390
|
"""
|
|
378
|
-
return _global_auto_serializer.detect_and_serialize(data, file_path, format_hint)
|
|
391
|
+
return _global_auto_serializer.detect_and_serialize(data, file_path, format_hint, **opts)
|
|
379
392
|
|
|
380
393
|
def auto_deserialize(
|
|
381
394
|
data: Union[str, bytes],
|
|
@@ -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.0.1.411
|
|
6
6
|
Generation Date: November 2, 2025
|
|
7
7
|
|
|
8
8
|
Serialization base classes - ASerialization abstract base.
|
|
@@ -275,6 +275,27 @@ class ASerialization(ACodec[Any, Union[bytes, str]], ISerialization):
|
|
|
275
275
|
True if lazy loading is supported
|
|
276
276
|
"""
|
|
277
277
|
return False
|
|
278
|
+
|
|
279
|
+
@property
|
|
280
|
+
def supports_record_streaming(self) -> bool:
|
|
281
|
+
"""
|
|
282
|
+
Whether this serializer exposes record-level streaming operations
|
|
283
|
+
(stream_read_record / stream_update_record).
|
|
284
|
+
|
|
285
|
+
Default: False. Override in subclasses that provide efficient record
|
|
286
|
+
streaming on top of their underlying format.
|
|
287
|
+
"""
|
|
288
|
+
return False
|
|
289
|
+
|
|
290
|
+
@property
|
|
291
|
+
def supports_record_paging(self) -> bool:
|
|
292
|
+
"""
|
|
293
|
+
Whether this serializer supports efficient record-level paging.
|
|
294
|
+
|
|
295
|
+
Default: False. Override in subclasses that can page records without
|
|
296
|
+
loading the entire dataset.
|
|
297
|
+
"""
|
|
298
|
+
return False
|
|
278
299
|
|
|
279
300
|
# ========================================================================
|
|
280
301
|
# FILE I/O METHODS (Default implementations using encode/decode)
|
|
@@ -373,6 +394,149 @@ class ASerialization(ACodec[Any, Union[bytes, str]], ISerialization):
|
|
|
373
394
|
format_name=self.format_name,
|
|
374
395
|
original_error=e
|
|
375
396
|
)
|
|
397
|
+
|
|
398
|
+
# ========================================================================
|
|
399
|
+
# RECORD-LEVEL OPERATIONS (Generic defaults – can be overridden)
|
|
400
|
+
# ========================================================================
|
|
401
|
+
|
|
402
|
+
def stream_read_record(
|
|
403
|
+
self,
|
|
404
|
+
file_path: Union[str, Path],
|
|
405
|
+
match: callable,
|
|
406
|
+
projection: Optional[list[Any]] = None,
|
|
407
|
+
**options: Any,
|
|
408
|
+
) -> Any:
|
|
409
|
+
"""
|
|
410
|
+
Default record-level read:
|
|
411
|
+
- Load the entire file via load_file().
|
|
412
|
+
- If top-level is a list, scan until match(record) is True.
|
|
413
|
+
- Apply optional projection and return the first matching record.
|
|
414
|
+
|
|
415
|
+
This is format-agnostic but may be expensive for huge files. Formats
|
|
416
|
+
that support true streaming should override this method.
|
|
417
|
+
"""
|
|
418
|
+
data = self.load_file(file_path, **options)
|
|
419
|
+
|
|
420
|
+
if isinstance(data, list):
|
|
421
|
+
for record in data:
|
|
422
|
+
if match(record):
|
|
423
|
+
return self._apply_projection(record, projection)
|
|
424
|
+
|
|
425
|
+
# Fallback: treat entire object as a single "record"
|
|
426
|
+
if match(data):
|
|
427
|
+
return self._apply_projection(data, projection)
|
|
428
|
+
|
|
429
|
+
raise KeyError("No matching record found")
|
|
430
|
+
|
|
431
|
+
def stream_update_record(
|
|
432
|
+
self,
|
|
433
|
+
file_path: Union[str, Path],
|
|
434
|
+
match: callable,
|
|
435
|
+
updater: callable,
|
|
436
|
+
*,
|
|
437
|
+
atomic: bool = True,
|
|
438
|
+
**options: Any,
|
|
439
|
+
) -> int:
|
|
440
|
+
"""
|
|
441
|
+
Default record-level update:
|
|
442
|
+
- Load entire file via load_file().
|
|
443
|
+
- If top-level is a list, apply updater() to matching records.
|
|
444
|
+
- Save the modified data back via save_file().
|
|
445
|
+
|
|
446
|
+
This is generic and honours the serializer's existing atomic/write
|
|
447
|
+
behavior, but may be expensive for huge files. Formats that support
|
|
448
|
+
streaming/partial updates should override this method.
|
|
449
|
+
"""
|
|
450
|
+
data = self.load_file(file_path, **options)
|
|
451
|
+
updated = 0
|
|
452
|
+
|
|
453
|
+
if isinstance(data, list):
|
|
454
|
+
new_records: list[Any] = []
|
|
455
|
+
for record in data:
|
|
456
|
+
if match(record):
|
|
457
|
+
record = updater(record)
|
|
458
|
+
updated += 1
|
|
459
|
+
new_records.append(record)
|
|
460
|
+
data = new_records
|
|
461
|
+
else:
|
|
462
|
+
if match(data):
|
|
463
|
+
data = updater(data)
|
|
464
|
+
updated = 1
|
|
465
|
+
|
|
466
|
+
# Delegate to existing save_file() which may already be atomic.
|
|
467
|
+
self.save_file(data, file_path, **options)
|
|
468
|
+
return updated
|
|
469
|
+
|
|
470
|
+
def get_record_page(
|
|
471
|
+
self,
|
|
472
|
+
file_path: Union[str, Path],
|
|
473
|
+
page_number: int,
|
|
474
|
+
page_size: int,
|
|
475
|
+
**options: Any,
|
|
476
|
+
) -> list[Any]:
|
|
477
|
+
"""
|
|
478
|
+
Default record-level paging:
|
|
479
|
+
- Load entire file via load_file().
|
|
480
|
+
- If top-level is a list, return a slice corresponding to the requested page.
|
|
481
|
+
|
|
482
|
+
Formats that support indexed or streaming paging should override this
|
|
483
|
+
method for better performance on very large datasets.
|
|
484
|
+
"""
|
|
485
|
+
if page_number < 1 or page_size <= 0:
|
|
486
|
+
raise ValueError("Invalid page_number or page_size")
|
|
487
|
+
|
|
488
|
+
data = self.load_file(file_path, **options)
|
|
489
|
+
|
|
490
|
+
if isinstance(data, list):
|
|
491
|
+
start = (page_number - 1) * page_size
|
|
492
|
+
end = start + page_size
|
|
493
|
+
return data[start:end]
|
|
494
|
+
|
|
495
|
+
# Non-list data: treat as a single record page
|
|
496
|
+
if page_number == 1 and page_size > 0:
|
|
497
|
+
return [data]
|
|
498
|
+
|
|
499
|
+
return []
|
|
500
|
+
|
|
501
|
+
def get_record_by_id(
|
|
502
|
+
self,
|
|
503
|
+
file_path: Union[str, Path],
|
|
504
|
+
id_value: Any,
|
|
505
|
+
*,
|
|
506
|
+
id_field: str = "id",
|
|
507
|
+
**options: Any,
|
|
508
|
+
) -> Any:
|
|
509
|
+
"""
|
|
510
|
+
Default record lookup by id:
|
|
511
|
+
- Load entire file via load_file().
|
|
512
|
+
- If top-level is a list of dict-like records, scan for record[id_field].
|
|
513
|
+
"""
|
|
514
|
+
data = self.load_file(file_path, **options)
|
|
515
|
+
|
|
516
|
+
if isinstance(data, list):
|
|
517
|
+
for record in data:
|
|
518
|
+
if isinstance(record, dict) and record.get(id_field) == id_value:
|
|
519
|
+
return record
|
|
520
|
+
|
|
521
|
+
# Single-record fallback
|
|
522
|
+
if isinstance(data, dict) and data.get(id_field) == id_value:
|
|
523
|
+
return data
|
|
524
|
+
|
|
525
|
+
raise KeyError(f"Record with {id_field}={id_value!r} not found")
|
|
526
|
+
|
|
527
|
+
# Small helper for projection handling
|
|
528
|
+
def _apply_projection(self, record: Any, projection: Optional[list[Any]]) -> Any:
|
|
529
|
+
if not projection:
|
|
530
|
+
return record
|
|
531
|
+
current = record
|
|
532
|
+
for key in projection:
|
|
533
|
+
if isinstance(current, dict) and isinstance(key, str):
|
|
534
|
+
current = current[key]
|
|
535
|
+
elif isinstance(current, list) and isinstance(key, int):
|
|
536
|
+
current = current[key]
|
|
537
|
+
else:
|
|
538
|
+
raise KeyError(key)
|
|
539
|
+
return current
|
|
376
540
|
|
|
377
541
|
# ========================================================================
|
|
378
542
|
# VALIDATION METHODS (Default implementations)
|
|
@@ -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.0.1.411
|
|
6
6
|
Generation Date: November 2, 2025
|
|
7
7
|
|
|
8
8
|
Serialization contracts - ISerialization interface extending ICodec.
|
|
@@ -435,3 +435,90 @@ class ISerialization(ICodec[Any, Union[bytes, str]]):
|
|
|
435
435
|
"""
|
|
436
436
|
pass
|
|
437
437
|
|
|
438
|
+
# ========================================================================
|
|
439
|
+
# RECORD-LEVEL OPERATIONS (Optional, generic defaults in ASerialization)
|
|
440
|
+
# ========================================================================
|
|
441
|
+
|
|
442
|
+
def stream_read_record(
|
|
443
|
+
self,
|
|
444
|
+
file_path: Union[str, Path],
|
|
445
|
+
match: callable,
|
|
446
|
+
projection: Optional[list[Any]] = None,
|
|
447
|
+
**options: Any,
|
|
448
|
+
) -> Any:
|
|
449
|
+
"""
|
|
450
|
+
Stream-style read of a single logical record from a file.
|
|
451
|
+
|
|
452
|
+
Semantics:
|
|
453
|
+
- Treat the underlying representation as a sequence of logical records
|
|
454
|
+
(e.g., list elements, table rows, NDJSON records).
|
|
455
|
+
- Return the first record that satisfies `match(record)`.
|
|
456
|
+
- If `projection` is provided, return only that sub-structure.
|
|
457
|
+
|
|
458
|
+
Concrete serializers may override this for efficient, true streaming
|
|
459
|
+
(e.g., NDJSON line-by-line). The default implementation in ASerialization
|
|
460
|
+
is allowed to load the full file and scan in memory.
|
|
461
|
+
"""
|
|
462
|
+
raise NotImplementedError
|
|
463
|
+
|
|
464
|
+
def stream_update_record(
|
|
465
|
+
self,
|
|
466
|
+
file_path: Union[str, Path],
|
|
467
|
+
match: callable,
|
|
468
|
+
updater: callable,
|
|
469
|
+
*,
|
|
470
|
+
atomic: bool = True,
|
|
471
|
+
**options: Any,
|
|
472
|
+
) -> int:
|
|
473
|
+
"""
|
|
474
|
+
Stream-style update of logical records in a file.
|
|
475
|
+
|
|
476
|
+
Semantics:
|
|
477
|
+
- Apply `updater(record)` to each record for which `match(record)` is True.
|
|
478
|
+
- When `atomic=True`, must preserve atomicity guarantees (temp file +
|
|
479
|
+
replace, or equivalent) provided by the underlying serializer/I/O.
|
|
480
|
+
- Returns the number of updated records.
|
|
481
|
+
|
|
482
|
+
Concrete serializers may override this to avoid loading the full file.
|
|
483
|
+
The default implementation in ASerialization may be full-load.
|
|
484
|
+
"""
|
|
485
|
+
raise NotImplementedError
|
|
486
|
+
|
|
487
|
+
def get_record_page(
|
|
488
|
+
self,
|
|
489
|
+
file_path: Union[str, Path],
|
|
490
|
+
page_number: int,
|
|
491
|
+
page_size: int,
|
|
492
|
+
**options: Any,
|
|
493
|
+
) -> list[Any]:
|
|
494
|
+
"""
|
|
495
|
+
Retrieve a logical page of records from a file.
|
|
496
|
+
|
|
497
|
+
Semantics:
|
|
498
|
+
- page_number is 1-based.
|
|
499
|
+
- page_size is the number of records.
|
|
500
|
+
- Returns a list of native records.
|
|
501
|
+
|
|
502
|
+
Concrete serializers may override this to use indexes or streaming.
|
|
503
|
+
The default implementation in ASerialization may load the entire file
|
|
504
|
+
and slice a top-level list.
|
|
505
|
+
"""
|
|
506
|
+
raise NotImplementedError
|
|
507
|
+
|
|
508
|
+
def get_record_by_id(
|
|
509
|
+
self,
|
|
510
|
+
file_path: Union[str, Path],
|
|
511
|
+
id_value: Any,
|
|
512
|
+
*,
|
|
513
|
+
id_field: str = "id",
|
|
514
|
+
**options: Any,
|
|
515
|
+
) -> Any:
|
|
516
|
+
"""
|
|
517
|
+
Retrieve a logical record by identifier (e.g., record[id_field] == id_value).
|
|
518
|
+
|
|
519
|
+
Concrete serializers may override this to use an index or format-specific
|
|
520
|
+
mechanisms. The default implementation in ASerialization may perform a
|
|
521
|
+
linear scan over a top-level list.
|
|
522
|
+
"""
|
|
523
|
+
raise NotImplementedError
|
|
524
|
+
|