exonware-xwsystem 0.1.0.1__py3-none-any.whl → 0.1.0.4__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 +2 -1
- exonware/conf.py +2 -2
- exonware/xwsystem/__init__.py +115 -43
- exonware/xwsystem/base.py +30 -0
- exonware/xwsystem/caching/__init__.py +39 -13
- exonware/xwsystem/caching/base.py +24 -6
- exonware/xwsystem/caching/bloom_cache.py +2 -2
- exonware/xwsystem/caching/cache_manager.py +2 -1
- exonware/xwsystem/caching/conditional.py +2 -2
- exonware/xwsystem/caching/contracts.py +85 -139
- exonware/xwsystem/caching/decorators.py +6 -19
- exonware/xwsystem/caching/defs.py +2 -1
- exonware/xwsystem/caching/disk_cache.py +2 -1
- exonware/xwsystem/caching/distributed.py +2 -1
- exonware/xwsystem/caching/errors.py +2 -1
- exonware/xwsystem/caching/events.py +110 -27
- exonware/xwsystem/caching/eviction_strategies.py +2 -2
- exonware/xwsystem/caching/external_caching_python.py +701 -0
- exonware/xwsystem/caching/facade.py +253 -0
- exonware/xwsystem/caching/factory.py +300 -0
- exonware/xwsystem/caching/fluent.py +14 -12
- exonware/xwsystem/caching/integrity.py +21 -6
- exonware/xwsystem/caching/lfu_cache.py +2 -1
- exonware/xwsystem/caching/lfu_optimized.py +18 -6
- exonware/xwsystem/caching/lru_cache.py +7 -4
- exonware/xwsystem/caching/memory_bounded.py +2 -2
- exonware/xwsystem/caching/metrics_exporter.py +2 -2
- exonware/xwsystem/caching/observable_cache.py +2 -2
- exonware/xwsystem/caching/pluggable_cache.py +2 -2
- exonware/xwsystem/caching/rate_limiter.py +2 -2
- exonware/xwsystem/caching/read_through.py +2 -2
- exonware/xwsystem/caching/secure_cache.py +81 -28
- exonware/xwsystem/caching/serializable.py +9 -7
- exonware/xwsystem/caching/stats.py +2 -2
- exonware/xwsystem/caching/tagging.py +2 -2
- exonware/xwsystem/caching/ttl_cache.py +4 -3
- exonware/xwsystem/caching/two_tier_cache.py +6 -3
- exonware/xwsystem/caching/utils.py +30 -12
- exonware/xwsystem/caching/validation.py +2 -2
- exonware/xwsystem/caching/warming.py +6 -3
- exonware/xwsystem/caching/write_behind.py +15 -6
- exonware/xwsystem/config/__init__.py +11 -17
- exonware/xwsystem/config/base.py +5 -5
- exonware/xwsystem/config/contracts.py +93 -153
- exonware/xwsystem/config/defaults.py +3 -2
- exonware/xwsystem/config/defs.py +3 -2
- exonware/xwsystem/config/errors.py +2 -5
- exonware/xwsystem/config/logging.py +12 -8
- exonware/xwsystem/config/logging_setup.py +3 -2
- exonware/xwsystem/config/performance.py +1 -46
- exonware/xwsystem/config/performance_modes.py +9 -8
- exonware/xwsystem/config/version_manager.py +1 -0
- exonware/xwsystem/config.py +27 -0
- exonware/xwsystem/console/__init__.py +53 -0
- exonware/xwsystem/console/base.py +133 -0
- exonware/xwsystem/console/cli/__init__.py +61 -0
- exonware/xwsystem/{cli → console/cli}/args.py +27 -24
- exonware/xwsystem/{cli → console/cli}/base.py +18 -87
- exonware/xwsystem/{cli → console/cli}/colors.py +15 -13
- exonware/xwsystem/console/cli/console.py +98 -0
- exonware/xwsystem/{cli → console/cli}/contracts.py +51 -69
- exonware/xwsystem/console/cli/defs.py +87 -0
- exonware/xwsystem/console/cli/encoding.py +69 -0
- exonware/xwsystem/{cli → console/cli}/errors.py +8 -3
- exonware/xwsystem/console/cli/event_logger.py +166 -0
- exonware/xwsystem/{cli → console/cli}/progress.py +25 -21
- exonware/xwsystem/{cli → console/cli}/prompts.py +3 -2
- exonware/xwsystem/{cli → console/cli}/tables.py +27 -24
- exonware/xwsystem/console/contracts.py +113 -0
- exonware/xwsystem/console/defs.py +154 -0
- exonware/xwsystem/console/errors.py +34 -0
- exonware/xwsystem/console/event_logger.py +385 -0
- exonware/xwsystem/console/writer.py +132 -0
- exonware/xwsystem/contracts.py +28 -0
- exonware/xwsystem/data_structures/__init__.py +23 -0
- exonware/xwsystem/data_structures/trie.py +34 -0
- exonware/xwsystem/data_structures/union_find.py +144 -0
- exonware/xwsystem/defs.py +17 -0
- exonware/xwsystem/errors.py +23 -0
- exonware/xwsystem/facade.py +62 -0
- exonware/xwsystem/http_client/__init__.py +22 -1
- exonware/xwsystem/http_client/advanced_client.py +8 -5
- exonware/xwsystem/http_client/base.py +3 -2
- exonware/xwsystem/http_client/client.py +7 -4
- exonware/xwsystem/http_client/contracts.py +42 -56
- exonware/xwsystem/http_client/defs.py +2 -1
- exonware/xwsystem/http_client/errors.py +2 -1
- exonware/xwsystem/http_client/facade.py +156 -0
- exonware/xwsystem/io/__init__.py +22 -3
- exonware/xwsystem/io/archive/__init__.py +8 -2
- exonware/xwsystem/io/archive/archive.py +1 -1
- exonware/xwsystem/io/archive/archive_files.py +4 -7
- exonware/xwsystem/io/archive/archivers.py +120 -10
- exonware/xwsystem/io/archive/base.py +4 -5
- exonware/xwsystem/io/archive/codec_integration.py +1 -2
- exonware/xwsystem/io/archive/compression.py +1 -2
- exonware/xwsystem/io/archive/facade.py +263 -0
- exonware/xwsystem/io/archive/formats/__init__.py +2 -3
- exonware/xwsystem/io/archive/formats/brotli_format.py +20 -7
- exonware/xwsystem/io/archive/formats/lz4_format.py +20 -7
- exonware/xwsystem/io/archive/formats/rar.py +11 -5
- exonware/xwsystem/io/archive/formats/sevenzip.py +12 -6
- exonware/xwsystem/io/archive/formats/squashfs_format.py +1 -2
- exonware/xwsystem/io/archive/formats/tar.py +52 -7
- exonware/xwsystem/io/archive/formats/wim_format.py +11 -5
- exonware/xwsystem/io/archive/formats/zip.py +1 -2
- exonware/xwsystem/io/archive/formats/zpaq_format.py +1 -2
- exonware/xwsystem/io/archive/formats/zstandard.py +20 -7
- exonware/xwsystem/io/base.py +119 -115
- exonware/xwsystem/io/codec/__init__.py +4 -2
- exonware/xwsystem/io/codec/base.py +19 -13
- exonware/xwsystem/io/codec/contracts.py +59 -2
- exonware/xwsystem/io/codec/registry.py +67 -21
- exonware/xwsystem/io/common/__init__.py +1 -1
- exonware/xwsystem/io/common/atomic.py +29 -16
- exonware/xwsystem/io/common/base.py +11 -10
- exonware/xwsystem/io/common/lock.py +6 -5
- exonware/xwsystem/io/common/path_manager.py +2 -1
- exonware/xwsystem/io/common/watcher.py +1 -2
- exonware/xwsystem/io/contracts.py +301 -433
- exonware/xwsystem/io/contracts_1.py +1180 -0
- exonware/xwsystem/io/data_operations.py +19 -20
- exonware/xwsystem/io/defs.py +4 -3
- exonware/xwsystem/io/errors.py +3 -2
- exonware/xwsystem/io/facade.py +87 -61
- exonware/xwsystem/io/file/__init__.py +1 -1
- exonware/xwsystem/io/file/base.py +8 -9
- exonware/xwsystem/io/file/conversion.py +2 -3
- exonware/xwsystem/io/file/file.py +61 -18
- exonware/xwsystem/io/file/paged_source.py +8 -8
- exonware/xwsystem/io/file/paging/__init__.py +1 -2
- exonware/xwsystem/io/file/paging/byte_paging.py +4 -5
- exonware/xwsystem/io/file/paging/line_paging.py +2 -3
- exonware/xwsystem/io/file/paging/record_paging.py +2 -3
- exonware/xwsystem/io/file/paging/registry.py +1 -2
- exonware/xwsystem/io/file/source.py +13 -17
- exonware/xwsystem/io/filesystem/__init__.py +1 -1
- exonware/xwsystem/io/filesystem/base.py +1 -2
- exonware/xwsystem/io/filesystem/local.py +3 -4
- exonware/xwsystem/io/folder/__init__.py +1 -1
- exonware/xwsystem/io/folder/base.py +1 -2
- exonware/xwsystem/io/folder/folder.py +16 -7
- exonware/xwsystem/io/indexing/__init__.py +14 -0
- exonware/xwsystem/io/indexing/facade.py +443 -0
- exonware/xwsystem/io/path_parser.py +98 -0
- exonware/xwsystem/io/serialization/__init__.py +21 -3
- exonware/xwsystem/io/serialization/auto_serializer.py +146 -20
- exonware/xwsystem/io/serialization/base.py +84 -34
- exonware/xwsystem/io/serialization/contracts.py +50 -73
- exonware/xwsystem/io/serialization/defs.py +2 -1
- exonware/xwsystem/io/serialization/errors.py +2 -1
- exonware/xwsystem/io/serialization/flyweight.py +154 -7
- exonware/xwsystem/io/serialization/format_detector.py +15 -14
- exonware/xwsystem/io/serialization/formats/__init__.py +8 -5
- exonware/xwsystem/io/serialization/formats/binary/bson.py +15 -6
- exonware/xwsystem/io/serialization/formats/binary/cbor.py +5 -5
- exonware/xwsystem/io/serialization/formats/binary/marshal.py +5 -5
- exonware/xwsystem/io/serialization/formats/binary/msgpack.py +5 -5
- exonware/xwsystem/io/serialization/formats/binary/pickle.py +5 -5
- exonware/xwsystem/io/serialization/formats/binary/plistlib.py +5 -5
- exonware/xwsystem/io/serialization/formats/database/dbm.py +7 -7
- exonware/xwsystem/io/serialization/formats/database/shelve.py +7 -7
- exonware/xwsystem/io/serialization/formats/database/sqlite3.py +7 -7
- exonware/xwsystem/io/serialization/formats/tabular/__init__.py +27 -0
- exonware/xwsystem/io/serialization/formats/tabular/base.py +89 -0
- exonware/xwsystem/io/serialization/formats/tabular/csv.py +319 -0
- exonware/xwsystem/io/serialization/formats/tabular/df.py +249 -0
- exonware/xwsystem/io/serialization/formats/tabular/excel.py +291 -0
- exonware/xwsystem/io/serialization/formats/tabular/googlesheets.py +374 -0
- exonware/xwsystem/io/serialization/formats/text/__init__.py +1 -1
- exonware/xwsystem/io/serialization/formats/text/append_only_log.py +5 -7
- exonware/xwsystem/io/serialization/formats/text/configparser.py +5 -5
- exonware/xwsystem/io/serialization/formats/text/csv.py +7 -5
- exonware/xwsystem/io/serialization/formats/text/formdata.py +5 -5
- exonware/xwsystem/io/serialization/formats/text/json.py +27 -18
- exonware/xwsystem/io/serialization/formats/text/json5.py +8 -4
- exonware/xwsystem/io/serialization/formats/text/jsonlines.py +18 -14
- exonware/xwsystem/io/serialization/formats/text/multipart.py +5 -5
- exonware/xwsystem/io/serialization/formats/text/toml.py +8 -6
- exonware/xwsystem/io/serialization/formats/text/xml.py +25 -20
- exonware/xwsystem/io/serialization/formats/text/yaml.py +8 -6
- exonware/xwsystem/io/serialization/parsers/__init__.py +3 -2
- exonware/xwsystem/io/serialization/parsers/base.py +6 -5
- exonware/xwsystem/io/serialization/parsers/hybrid_parser.py +7 -6
- exonware/xwsystem/io/serialization/parsers/msgspec_parser.py +10 -7
- exonware/xwsystem/io/serialization/parsers/orjson_direct_parser.py +7 -6
- exonware/xwsystem/io/serialization/parsers/orjson_parser.py +11 -8
- exonware/xwsystem/io/serialization/parsers/pysimdjson_parser.py +13 -9
- exonware/xwsystem/io/serialization/parsers/rapidjson_parser.py +10 -7
- exonware/xwsystem/io/serialization/parsers/registry.py +11 -10
- exonware/xwsystem/io/serialization/parsers/standard.py +7 -6
- exonware/xwsystem/io/serialization/parsers/ujson_parser.py +10 -7
- exonware/xwsystem/io/serialization/registry.py +4 -4
- exonware/xwsystem/io/serialization/serializer.py +168 -79
- exonware/xwsystem/io/serialization/universal_options.py +367 -0
- exonware/xwsystem/io/serialization/utils/__init__.py +1 -2
- exonware/xwsystem/io/serialization/utils/path_ops.py +5 -6
- exonware/xwsystem/io/source_reader.py +223 -0
- exonware/xwsystem/io/stream/__init__.py +1 -1
- exonware/xwsystem/io/stream/async_operations.py +61 -14
- exonware/xwsystem/io/stream/base.py +1 -2
- exonware/xwsystem/io/stream/codec_io.py +6 -7
- exonware/xwsystem/ipc/__init__.py +1 -0
- exonware/xwsystem/ipc/async_fabric.py +4 -4
- exonware/xwsystem/ipc/base.py +6 -5
- exonware/xwsystem/ipc/contracts.py +41 -66
- exonware/xwsystem/ipc/defs.py +2 -1
- exonware/xwsystem/ipc/errors.py +2 -1
- exonware/xwsystem/ipc/message_queue.py +5 -2
- exonware/xwsystem/ipc/pipes.py +70 -34
- exonware/xwsystem/ipc/process_manager.py +7 -5
- exonware/xwsystem/ipc/process_pool.py +6 -5
- exonware/xwsystem/ipc/shared_memory.py +64 -11
- exonware/xwsystem/monitoring/__init__.py +7 -0
- exonware/xwsystem/monitoring/base.py +11 -8
- exonware/xwsystem/monitoring/contracts.py +86 -144
- exonware/xwsystem/monitoring/defs.py +2 -1
- exonware/xwsystem/monitoring/error_recovery.py +16 -3
- exonware/xwsystem/monitoring/errors.py +2 -1
- exonware/xwsystem/monitoring/facade.py +183 -0
- exonware/xwsystem/monitoring/memory_monitor.py +1 -0
- exonware/xwsystem/monitoring/metrics.py +1 -0
- exonware/xwsystem/monitoring/performance_manager_generic.py +7 -7
- exonware/xwsystem/monitoring/performance_monitor.py +1 -0
- exonware/xwsystem/monitoring/performance_validator.py +1 -0
- exonware/xwsystem/monitoring/system_monitor.py +6 -5
- exonware/xwsystem/monitoring/tracing.py +18 -16
- exonware/xwsystem/monitoring/tracker.py +2 -1
- exonware/xwsystem/operations/__init__.py +5 -50
- exonware/xwsystem/operations/base.py +3 -44
- exonware/xwsystem/operations/contracts.py +25 -15
- exonware/xwsystem/operations/defs.py +1 -1
- exonware/xwsystem/operations/diff.py +5 -4
- exonware/xwsystem/operations/errors.py +1 -1
- exonware/xwsystem/operations/merge.py +6 -4
- exonware/xwsystem/operations/patch.py +5 -4
- exonware/xwsystem/patterns/__init__.py +1 -0
- exonware/xwsystem/patterns/base.py +2 -1
- exonware/xwsystem/patterns/context_manager.py +2 -1
- exonware/xwsystem/patterns/contracts.py +215 -256
- exonware/xwsystem/patterns/defs.py +2 -1
- exonware/xwsystem/patterns/dynamic_facade.py +1 -0
- exonware/xwsystem/patterns/errors.py +2 -4
- exonware/xwsystem/patterns/handler_factory.py +2 -3
- exonware/xwsystem/patterns/import_registry.py +1 -0
- exonware/xwsystem/patterns/object_pool.py +1 -0
- exonware/xwsystem/patterns/registry.py +4 -43
- exonware/xwsystem/plugins/__init__.py +2 -1
- exonware/xwsystem/plugins/base.py +6 -5
- exonware/xwsystem/plugins/contracts.py +94 -158
- exonware/xwsystem/plugins/defs.py +2 -1
- exonware/xwsystem/plugins/errors.py +2 -1
- exonware/xwsystem/py.typed +3 -0
- exonware/xwsystem/query/__init__.py +36 -0
- exonware/xwsystem/query/contracts.py +56 -0
- exonware/xwsystem/query/errors.py +22 -0
- exonware/xwsystem/query/registry.py +128 -0
- exonware/xwsystem/runtime/__init__.py +2 -1
- exonware/xwsystem/runtime/base.py +4 -3
- exonware/xwsystem/runtime/contracts.py +39 -60
- exonware/xwsystem/runtime/defs.py +2 -1
- exonware/xwsystem/runtime/env.py +11 -9
- exonware/xwsystem/runtime/errors.py +2 -1
- exonware/xwsystem/runtime/reflection.py +3 -2
- exonware/xwsystem/security/__init__.py +68 -11
- exonware/xwsystem/security/audit.py +167 -0
- exonware/xwsystem/security/base.py +121 -24
- exonware/xwsystem/security/contracts.py +91 -146
- exonware/xwsystem/security/crypto.py +17 -16
- exonware/xwsystem/security/defs.py +2 -1
- exonware/xwsystem/security/errors.py +2 -1
- exonware/xwsystem/security/facade.py +321 -0
- exonware/xwsystem/security/file_security.py +330 -0
- exonware/xwsystem/security/hazmat.py +11 -8
- exonware/xwsystem/security/monitor.py +372 -0
- exonware/xwsystem/security/path_validator.py +140 -18
- exonware/xwsystem/security/policy.py +357 -0
- exonware/xwsystem/security/resource_limits.py +1 -0
- exonware/xwsystem/security/validator.py +455 -0
- exonware/xwsystem/shared/__init__.py +14 -1
- exonware/xwsystem/shared/base.py +285 -2
- exonware/xwsystem/shared/contracts.py +415 -126
- exonware/xwsystem/shared/defs.py +2 -1
- exonware/xwsystem/shared/errors.py +2 -2
- exonware/xwsystem/shared/xwobject.py +316 -0
- exonware/xwsystem/structures/__init__.py +1 -0
- exonware/xwsystem/structures/base.py +3 -2
- exonware/xwsystem/structures/circular_detector.py +15 -14
- exonware/xwsystem/structures/contracts.py +53 -76
- exonware/xwsystem/structures/defs.py +2 -1
- exonware/xwsystem/structures/errors.py +2 -1
- exonware/xwsystem/structures/tree_walker.py +2 -1
- exonware/xwsystem/threading/__init__.py +21 -4
- exonware/xwsystem/threading/async_primitives.py +6 -5
- exonware/xwsystem/threading/base.py +3 -2
- exonware/xwsystem/threading/contracts.py +87 -143
- exonware/xwsystem/threading/defs.py +2 -1
- exonware/xwsystem/threading/errors.py +2 -1
- exonware/xwsystem/threading/facade.py +175 -0
- exonware/xwsystem/threading/locks.py +1 -0
- exonware/xwsystem/threading/safe_factory.py +1 -0
- exonware/xwsystem/utils/__init__.py +40 -0
- exonware/xwsystem/utils/base.py +22 -21
- exonware/xwsystem/utils/contracts.py +50 -73
- exonware/xwsystem/utils/dt/__init__.py +19 -3
- exonware/xwsystem/utils/dt/base.py +5 -4
- exonware/xwsystem/utils/dt/contracts.py +22 -29
- exonware/xwsystem/utils/dt/defs.py +2 -1
- exonware/xwsystem/utils/dt/errors.py +2 -5
- exonware/xwsystem/utils/dt/formatting.py +88 -2
- exonware/xwsystem/utils/dt/humanize.py +10 -9
- exonware/xwsystem/utils/dt/parsing.py +56 -5
- exonware/xwsystem/utils/dt/timezone_utils.py +2 -24
- exonware/xwsystem/utils/errors.py +2 -4
- exonware/xwsystem/utils/paths.py +1 -0
- exonware/xwsystem/utils/string.py +49 -0
- exonware/xwsystem/utils/test_runner.py +185 -0
- exonware/xwsystem/utils/utils_contracts.py +2 -1
- exonware/xwsystem/utils/web.py +110 -0
- exonware/xwsystem/validation/__init__.py +25 -1
- exonware/xwsystem/validation/base.py +6 -5
- exonware/xwsystem/validation/contracts.py +29 -41
- exonware/xwsystem/validation/data_validator.py +1 -0
- exonware/xwsystem/validation/declarative.py +11 -8
- exonware/xwsystem/validation/defs.py +2 -1
- exonware/xwsystem/validation/errors.py +2 -1
- exonware/xwsystem/validation/facade.py +198 -0
- exonware/xwsystem/validation/fluent_validator.py +22 -19
- exonware/xwsystem/validation/schema_discovery.py +210 -0
- exonware/xwsystem/validation/type_safety.py +2 -1
- exonware/xwsystem/version.py +2 -2
- {exonware_xwsystem-0.1.0.1.dist-info → exonware_xwsystem-0.1.0.4.dist-info}/METADATA +71 -4
- exonware_xwsystem-0.1.0.4.dist-info/RECORD +337 -0
- exonware/xwsystem/cli/__init__.py +0 -43
- exonware/xwsystem/cli/console.py +0 -113
- exonware/xwsystem/cli/defs.py +0 -134
- exonware/xwsystem/conf.py +0 -44
- exonware/xwsystem/security/auth.py +0 -484
- exonware_xwsystem-0.1.0.1.dist-info/RECORD +0 -284
- {exonware_xwsystem-0.1.0.1.dist-info → exonware_xwsystem-0.1.0.4.dist-info}/WHEEL +0 -0
- {exonware_xwsystem-0.1.0.1.dist-info → exonware_xwsystem-0.1.0.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
+
#exonware/xwsystem/src/exonware/xwsystem/io/codec/__init__.py
|
|
2
3
|
# exonware/xwsystem/io/codec/__init__.py
|
|
3
4
|
"""
|
|
4
5
|
Company: eXonware.com
|
|
5
6
|
Author: Eng. Muhammad AlShehri
|
|
6
7
|
Email: connect@exonware.com
|
|
7
|
-
Version: 0.1.0.
|
|
8
|
+
Version: 0.1.0.4
|
|
8
9
|
Generation Date: October 30, 2025
|
|
9
10
|
|
|
10
11
|
Universal Codec Abstraction for eXonware.
|
|
@@ -71,6 +72,7 @@ from ..contracts import (
|
|
|
71
72
|
DecodeOptions,
|
|
72
73
|
ICodecMetadata,
|
|
73
74
|
)
|
|
75
|
+
from .contracts import IFormatConfig
|
|
74
76
|
from .base import (
|
|
75
77
|
ACodec,
|
|
76
78
|
MediaKey,
|
|
@@ -167,6 +169,7 @@ __all__ = [
|
|
|
167
169
|
'EncodeOptions',
|
|
168
170
|
'DecodeOptions',
|
|
169
171
|
'ICodecMetadata',
|
|
172
|
+
'IFormatConfig',
|
|
170
173
|
|
|
171
174
|
# Capabilities
|
|
172
175
|
'CodecCapability',
|
|
@@ -193,4 +196,3 @@ __all__ = [
|
|
|
193
196
|
'DecodeError',
|
|
194
197
|
'CodecNotFoundError',
|
|
195
198
|
]
|
|
196
|
-
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
+
#exonware/xwsystem/src/exonware/xwsystem/io/codec/base.py
|
|
2
3
|
# exonware/xwsystem/io/codec/base.py
|
|
3
4
|
"""
|
|
4
5
|
Company: eXonware.com
|
|
5
6
|
Author: Eng. Muhammad AlShehri
|
|
6
7
|
Email: connect@exonware.com
|
|
7
|
-
Version: 0.1.0.
|
|
8
|
+
Version: 0.1.0.4
|
|
8
9
|
Generation Date: October 30, 2025
|
|
9
10
|
|
|
10
11
|
Base classes, registry, adapters, and helper functions for codec system.
|
|
11
12
|
"""
|
|
12
13
|
|
|
13
14
|
from __future__ import annotations
|
|
14
|
-
from typing import Optional, Any, IO
|
|
15
|
+
from typing import Optional, Any, IO
|
|
15
16
|
# Root cause: Migrating to Python 3.12 built-in generic syntax for consistency
|
|
16
17
|
# Priority #3: Maintainability - Modern type annotations improve code clarity
|
|
17
18
|
from pathlib import Path
|
|
@@ -415,7 +416,9 @@ class ACodec[T, R](ICodec[T, R], ICodecMetadata, ABC):
|
|
|
415
416
|
self._get_data_depth(v, cache, visited, current_depth + 1) - current_depth - 1
|
|
416
417
|
for v in data.values()
|
|
417
418
|
]
|
|
418
|
-
|
|
419
|
+
# Get max relative depth from children (how deep from this dict)
|
|
420
|
+
# Add 1 for the dict itself
|
|
421
|
+
max_relative_depth = max(child_depths) + 1 if child_depths else 1
|
|
419
422
|
else:
|
|
420
423
|
max_relative_depth = 1 # Empty dict still counts as one level
|
|
421
424
|
|
|
@@ -425,7 +428,9 @@ class ACodec[T, R](ICodec[T, R], ICodecMetadata, ABC):
|
|
|
425
428
|
self._get_data_depth(item, cache, visited, current_depth + 1) - current_depth - 1
|
|
426
429
|
for item in data
|
|
427
430
|
]
|
|
428
|
-
|
|
431
|
+
# Get max relative depth from children (how deep from this list)
|
|
432
|
+
# Add 1 for the list itself
|
|
433
|
+
max_relative_depth = max(child_depths) + 1 if child_depths else 1
|
|
429
434
|
else:
|
|
430
435
|
max_relative_depth = 1 # Empty list still counts as one level
|
|
431
436
|
|
|
@@ -435,7 +440,9 @@ class ACodec[T, R](ICodec[T, R], ICodecMetadata, ABC):
|
|
|
435
440
|
self._get_data_depth(v, cache, visited, current_depth + 1) - current_depth - 1
|
|
436
441
|
for v in vars(data).values()
|
|
437
442
|
]
|
|
438
|
-
|
|
443
|
+
# Get max relative depth from children (how deep from this object)
|
|
444
|
+
# Add 1 for the object itself
|
|
445
|
+
max_relative_depth = max(child_depths) + 1 if child_depths else 1
|
|
439
446
|
|
|
440
447
|
# Cache the result (maximum relative depth from this object)
|
|
441
448
|
cache[obj_id] = max_relative_depth
|
|
@@ -507,7 +514,7 @@ class ACodec[T, R](ICodec[T, R], ICodecMetadata, ABC):
|
|
|
507
514
|
self,
|
|
508
515
|
data: Any,
|
|
509
516
|
operation: str = "encode",
|
|
510
|
-
file_path: Optional[
|
|
517
|
+
file_path: Optional[str | Path] = None,
|
|
511
518
|
skip_size_check: bool = False
|
|
512
519
|
) -> None:
|
|
513
520
|
"""
|
|
@@ -536,7 +543,7 @@ class ACodec[T, R](ICodec[T, R], ICodecMetadata, ABC):
|
|
|
536
543
|
self._depth_cache.clear()
|
|
537
544
|
self._size_cache.clear()
|
|
538
545
|
|
|
539
|
-
#
|
|
546
|
+
# Check depth - prevents infinite recursion which is the security issue
|
|
540
547
|
depth = self._get_data_depth(data)
|
|
541
548
|
if depth > self._max_depth:
|
|
542
549
|
raise SerializationError(
|
|
@@ -558,7 +565,7 @@ class ACodec[T, R](ICodec[T, R], ICodecMetadata, ABC):
|
|
|
558
565
|
if path_obj.exists():
|
|
559
566
|
file_size_mb = path_obj.stat().st_size / (1024 * 1024)
|
|
560
567
|
# If file is > 1GB, assume it's meant to be large and skip size validation
|
|
561
|
-
# Large files
|
|
568
|
+
# Large files use lazy loading/streaming features
|
|
562
569
|
if file_size_mb > 1024: # 1GB threshold
|
|
563
570
|
return # Skip size check for large files
|
|
564
571
|
except (OSError, ValueError):
|
|
@@ -576,7 +583,7 @@ class ACodec[T, R](ICodec[T, R], ICodecMetadata, ABC):
|
|
|
576
583
|
)
|
|
577
584
|
|
|
578
585
|
# ========================================================================
|
|
579
|
-
# CORE METHODS (
|
|
586
|
+
# CORE METHODS (Implement in subclasses)
|
|
580
587
|
# ========================================================================
|
|
581
588
|
|
|
582
589
|
@abstractmethod
|
|
@@ -590,7 +597,7 @@ class ACodec[T, R](ICodec[T, R], ICodecMetadata, ABC):
|
|
|
590
597
|
pass
|
|
591
598
|
|
|
592
599
|
# ========================================================================
|
|
593
|
-
# METADATA PROPERTIES (
|
|
600
|
+
# METADATA PROPERTIES (Implement in subclasses)
|
|
594
601
|
# ========================================================================
|
|
595
602
|
|
|
596
603
|
@property
|
|
@@ -689,8 +696,8 @@ class ACodec[T, R](ICodec[T, R], ICodecMetadata, ABC):
|
|
|
689
696
|
if isinstance(self._get_repr_type_hint(), str):
|
|
690
697
|
# Text codec, decode bytes to str
|
|
691
698
|
repr = repr.decode('utf-8')
|
|
692
|
-
except:
|
|
693
|
-
# Fall back to text
|
|
699
|
+
except (OSError, UnicodeDecodeError, FileNotFoundError, PermissionError):
|
|
700
|
+
# Fall back to text - catch specific exceptions only
|
|
694
701
|
repr = path.read_text(encoding='utf-8')
|
|
695
702
|
|
|
696
703
|
return self.decode(repr, options=opts or None)
|
|
@@ -879,4 +886,3 @@ class SerializerToFormatter[T]:
|
|
|
879
886
|
"""Decode from string via bytes."""
|
|
880
887
|
data = repr.encode(self._encoding, errors=self._errors)
|
|
881
888
|
return self._serializer.decode(data, options=options)
|
|
882
|
-
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
#exonware/xwsystem/src/exonware/xwsystem/io/codec/contracts.py
|
|
1
2
|
"""
|
|
2
3
|
Company: eXonware.com
|
|
3
4
|
Author: Eng. Muhammad AlShehri
|
|
4
5
|
Email: connect@exonware.com
|
|
5
|
-
Version: 0.1.0.
|
|
6
|
+
Version: 0.1.0.4
|
|
6
7
|
Generation Date: September 04, 2025
|
|
7
8
|
|
|
8
9
|
Codec module contracts - interfaces for codec operations.
|
|
9
10
|
"""
|
|
10
11
|
|
|
11
|
-
from typing import Protocol, Optional, runtime_checkable
|
|
12
|
+
from typing import Protocol, Optional, runtime_checkable, Any
|
|
12
13
|
from pathlib import Path
|
|
13
14
|
|
|
14
15
|
from ..contracts import EncodeOptions, DecodeOptions, CodecCapability
|
|
@@ -211,3 +212,59 @@ class ICodecMetadata(Protocol):
|
|
|
211
212
|
"""
|
|
212
213
|
...
|
|
213
214
|
|
|
215
|
+
|
|
216
|
+
@runtime_checkable
|
|
217
|
+
class IFormatConfig(Protocol):
|
|
218
|
+
"""
|
|
219
|
+
Interface for format configuration.
|
|
220
|
+
|
|
221
|
+
This interface represents configuration for codec/format selection
|
|
222
|
+
and is used across xwsystem for format-aware operations. It provides
|
|
223
|
+
a standardized way to specify and validate codec/format configurations
|
|
224
|
+
with integration to the codec registry.
|
|
225
|
+
|
|
226
|
+
Originally defined in xwstorage, moved to xwsystem.io.codec as it
|
|
227
|
+
is a general-purpose codec configuration abstraction.
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def format_type(self) -> str:
|
|
232
|
+
"""
|
|
233
|
+
Get the format type identifier (codec_id).
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
Format type identifier (e.g., "json", "yaml", "xml")
|
|
237
|
+
"""
|
|
238
|
+
...
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def codec_id(self) -> str:
|
|
242
|
+
"""
|
|
243
|
+
Get the codec identifier from xwsystem codec registry.
|
|
244
|
+
|
|
245
|
+
This should match a codec_id in the UniversalCodecRegistry.
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Codec identifier (typically same as format_type)
|
|
249
|
+
"""
|
|
250
|
+
...
|
|
251
|
+
|
|
252
|
+
def to_dict(self) -> dict[str, Any]:
|
|
253
|
+
"""
|
|
254
|
+
Convert configuration to dictionary.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
Dictionary representation of the format configuration
|
|
258
|
+
"""
|
|
259
|
+
...
|
|
260
|
+
|
|
261
|
+
def validate(self) -> bool:
|
|
262
|
+
"""
|
|
263
|
+
Validate format configuration.
|
|
264
|
+
|
|
265
|
+
Checks if the codec_id is available in the xwsystem codec registry.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
True if format is valid and available in codec registry
|
|
269
|
+
"""
|
|
270
|
+
...
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
#exonware/xwsystem/src/exonware/xwsystem/io/codec/registry.py
|
|
1
2
|
"""
|
|
2
3
|
Company: eXonware.com
|
|
3
4
|
Author: Eng. Muhammad AlShehri
|
|
4
5
|
Email: connect@exonware.com
|
|
5
|
-
Version: 0.1.0.
|
|
6
|
+
Version: 0.1.0.4
|
|
6
7
|
Generation Date: November 04, 2025
|
|
7
8
|
|
|
8
9
|
Universal Codec Registry - High-performance registry for all codec types.
|
|
9
10
|
"""
|
|
10
11
|
|
|
11
|
-
from typing import Optional,
|
|
12
|
+
from typing import Optional, Any, Callable
|
|
12
13
|
from pathlib import Path
|
|
13
14
|
from threading import RLock
|
|
14
15
|
from functools import lru_cache
|
|
@@ -103,7 +104,7 @@ class UniversalCodecRegistry:
|
|
|
103
104
|
|
|
104
105
|
def __init__(self):
|
|
105
106
|
"""Initialize the universal codec registry."""
|
|
106
|
-
# Core mappings (codec_id is
|
|
107
|
+
# Core mappings (codec_id is lowercase)
|
|
107
108
|
self._by_id: dict[str, type[ICodec]] = {}
|
|
108
109
|
self._by_extension: dict[str, list[tuple[str, int]]] = {} # ext -> [(codec_id, priority)]
|
|
109
110
|
self._by_mime_type: dict[str, list[tuple[str, int]]] = {} # mime -> [(codec_id, priority)]
|
|
@@ -324,7 +325,11 @@ class UniversalCodecRegistry:
|
|
|
324
325
|
|
|
325
326
|
def get_by_id(self, codec_id: str) -> Optional[ICodec]:
|
|
326
327
|
"""
|
|
327
|
-
Get codec by ID (unique lookup).
|
|
328
|
+
Get codec by ID with O(1) caching (unique lookup).
|
|
329
|
+
|
|
330
|
+
OPTIMIZED: Lockless fast path for cached instances.
|
|
331
|
+
First call: validates and instantiates (10-100μs)
|
|
332
|
+
Subsequent calls: cache lookup (< 1μs) ✅
|
|
328
333
|
|
|
329
334
|
Args:
|
|
330
335
|
codec_id: Codec identifier
|
|
@@ -332,10 +337,15 @@ class UniversalCodecRegistry:
|
|
|
332
337
|
Returns:
|
|
333
338
|
Codec instance or None
|
|
334
339
|
"""
|
|
340
|
+
codec_id_lower = codec_id.lower()
|
|
341
|
+
|
|
342
|
+
# FAST PATH: Lockless cache check (O(1))
|
|
343
|
+
if codec_id_lower in self._instances:
|
|
344
|
+
return self._instances[codec_id_lower] # O(1) cache hit! ✅
|
|
345
|
+
|
|
346
|
+
# SLOW PATH: Need to instantiate (acquire lock)
|
|
335
347
|
with self._lock:
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
# Check instance cache first
|
|
348
|
+
# Double-check cache after acquiring lock
|
|
339
349
|
if codec_id_lower in self._instances:
|
|
340
350
|
return self._instances[codec_id_lower]
|
|
341
351
|
|
|
@@ -344,13 +354,19 @@ class UniversalCodecRegistry:
|
|
|
344
354
|
if not codec_class:
|
|
345
355
|
return None
|
|
346
356
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
357
|
+
try:
|
|
358
|
+
instance = codec_class()
|
|
359
|
+
self._instances[codec_id_lower] = instance
|
|
360
|
+
return instance
|
|
361
|
+
except Exception:
|
|
362
|
+
# Failed to instantiate - don't cache the failure
|
|
363
|
+
return None
|
|
350
364
|
|
|
351
365
|
def get_by_extension(self, ext: str) -> Optional[ICodec]:
|
|
352
366
|
"""
|
|
353
|
-
Get codec by extension (highest priority match).
|
|
367
|
+
Get codec by extension with O(1) caching (highest priority match).
|
|
368
|
+
|
|
369
|
+
OPTIMIZED: Reduced lock contention by doing normalization outside lock.
|
|
354
370
|
|
|
355
371
|
Args:
|
|
356
372
|
ext: File extension (with or without dot)
|
|
@@ -358,18 +374,22 @@ class UniversalCodecRegistry:
|
|
|
358
374
|
Returns:
|
|
359
375
|
Highest priority codec instance or None
|
|
360
376
|
"""
|
|
377
|
+
# Normalize outside lock (no shared state access)
|
|
378
|
+
normalized_ext = ext.lower()
|
|
379
|
+
if not normalized_ext.startswith('.'):
|
|
380
|
+
normalized_ext = f'.{normalized_ext}'
|
|
381
|
+
|
|
382
|
+
# Quick lookup with lock
|
|
361
383
|
with self._lock:
|
|
362
|
-
normalized_ext = ext.lower()
|
|
363
|
-
if not normalized_ext.startswith('.'):
|
|
364
|
-
normalized_ext = f'.{normalized_ext}'
|
|
365
|
-
|
|
366
384
|
codec_list = self._by_extension.get(normalized_ext, [])
|
|
367
385
|
if not codec_list:
|
|
368
386
|
return None
|
|
369
387
|
|
|
370
388
|
# Return highest priority (first in sorted list)
|
|
371
389
|
codec_id = codec_list[0][0]
|
|
372
|
-
|
|
390
|
+
|
|
391
|
+
# Get instance outside lock (uses lockless cache)
|
|
392
|
+
return self.get_by_id(codec_id)
|
|
373
393
|
|
|
374
394
|
def get_by_mime_type(self, mime: str) -> Optional[ICodec]:
|
|
375
395
|
"""
|
|
@@ -407,7 +427,7 @@ class UniversalCodecRegistry:
|
|
|
407
427
|
return self.get_by_id(codec_id)
|
|
408
428
|
|
|
409
429
|
@lru_cache(maxsize=256)
|
|
410
|
-
def detect(self, path:
|
|
430
|
+
def detect(self, path: str | Path, codec_type: Optional[str] = None) -> Optional[ICodec]:
|
|
411
431
|
"""
|
|
412
432
|
Auto-detect codec from file path (best match with optional type filter).
|
|
413
433
|
|
|
@@ -429,7 +449,7 @@ class UniversalCodecRegistry:
|
|
|
429
449
|
with self._lock:
|
|
430
450
|
return self._detect_internal(path, codec_type)
|
|
431
451
|
|
|
432
|
-
def _detect_internal(self, path:
|
|
452
|
+
def _detect_internal(self, path: str | Path, codec_type: Optional[str] = None) -> Optional[ICodec]:
|
|
433
453
|
"""Internal detection implementation (not cached)."""
|
|
434
454
|
path_obj = Path(path)
|
|
435
455
|
|
|
@@ -574,7 +594,7 @@ class UniversalCodecRegistry:
|
|
|
574
594
|
results.append(codec)
|
|
575
595
|
return results
|
|
576
596
|
|
|
577
|
-
def detect_all(self, path:
|
|
597
|
+
def detect_all(self, path: str | Path, codec_type: Optional[str] = None) -> list[ICodec]:
|
|
578
598
|
"""
|
|
579
599
|
Detect all possible codecs for a file path.
|
|
580
600
|
|
|
@@ -732,10 +752,10 @@ class UniversalCodecRegistry:
|
|
|
732
752
|
|
|
733
753
|
def get_statistics(self) -> dict[str, int]:
|
|
734
754
|
"""
|
|
735
|
-
Get registry statistics.
|
|
755
|
+
Get registry statistics including cache performance.
|
|
736
756
|
|
|
737
757
|
Returns:
|
|
738
|
-
Dictionary with counts of registered items
|
|
758
|
+
Dictionary with counts of registered items and cache stats
|
|
739
759
|
"""
|
|
740
760
|
with self._lock:
|
|
741
761
|
return {
|
|
@@ -746,6 +766,32 @@ class UniversalCodecRegistry:
|
|
|
746
766
|
'types': len(self._by_type),
|
|
747
767
|
'magic_bytes': len(self._magic_bytes),
|
|
748
768
|
'cached_instances': len(self._instances),
|
|
769
|
+
'cache_hit_rate': len(self._instances) / max(1, len(self._by_id)), # % of codecs cached
|
|
770
|
+
'detect_cache_info': self.detect.cache_info()._asdict(), # LRU cache stats
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
def get_cache_stats(self) -> dict:
|
|
774
|
+
"""
|
|
775
|
+
Get detailed cache statistics for performance monitoring.
|
|
776
|
+
|
|
777
|
+
Returns:
|
|
778
|
+
Dictionary with cache performance metrics
|
|
779
|
+
"""
|
|
780
|
+
with self._lock:
|
|
781
|
+
detect_info = self.detect.cache_info()
|
|
782
|
+
return {
|
|
783
|
+
'instance_cache': {
|
|
784
|
+
'size': len(self._instances),
|
|
785
|
+
'max_codecs': len(self._by_id),
|
|
786
|
+
'hit_rate': len(self._instances) / max(1, len(self._by_id)),
|
|
787
|
+
},
|
|
788
|
+
'detect_cache': {
|
|
789
|
+
'hits': detect_info.hits,
|
|
790
|
+
'misses': detect_info.misses,
|
|
791
|
+
'size': detect_info.currsize,
|
|
792
|
+
'max_size': detect_info.maxsize,
|
|
793
|
+
'hit_rate': detect_info.hits / max(1, detect_info.hits + detect_info.misses),
|
|
794
|
+
}
|
|
749
795
|
}
|
|
750
796
|
|
|
751
797
|
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
+
#exonware/xwsystem/src/exonware/xwsystem/io/common/atomic.py
|
|
1
2
|
"""
|
|
2
3
|
Atomic file operations to prevent data corruption during writes.
|
|
3
4
|
"""
|
|
4
5
|
|
|
5
6
|
import logging
|
|
6
7
|
import os
|
|
8
|
+
import platform
|
|
7
9
|
import shutil
|
|
8
10
|
import tempfile
|
|
9
11
|
import time
|
|
10
12
|
from contextlib import contextmanager
|
|
11
13
|
from pathlib import Path
|
|
12
|
-
from typing import Any, BinaryIO, Optional, TextIO
|
|
14
|
+
from typing import Any, BinaryIO, Optional, TextIO
|
|
13
15
|
|
|
14
16
|
logger = logging.getLogger(__name__)
|
|
15
17
|
|
|
@@ -31,11 +33,11 @@ class AtomicFileWriter:
|
|
|
31
33
|
|
|
32
34
|
def __init__(
|
|
33
35
|
self,
|
|
34
|
-
target_path:
|
|
36
|
+
target_path: str | Path,
|
|
35
37
|
mode: str = "w",
|
|
36
38
|
encoding: Optional[str] = "utf-8",
|
|
37
39
|
backup: bool = False,
|
|
38
|
-
temp_dir: Optional[
|
|
40
|
+
temp_dir: Optional[str | Path] = None,
|
|
39
41
|
):
|
|
40
42
|
"""
|
|
41
43
|
Initialize atomic file writer.
|
|
@@ -55,11 +57,11 @@ class AtomicFileWriter:
|
|
|
55
57
|
|
|
56
58
|
self.temp_path: Optional[Path] = None
|
|
57
59
|
self.backup_path: Optional[Path] = None
|
|
58
|
-
self.file_handle: Optional[
|
|
60
|
+
self.file_handle: Optional[BinaryIO | TextIO] = None
|
|
59
61
|
self._committed = False
|
|
60
62
|
self._started = False
|
|
61
63
|
|
|
62
|
-
def __enter__(self) ->
|
|
64
|
+
def __enter__(self) -> BinaryIO | TextIO:
|
|
63
65
|
"""Context manager entry - create temporary file."""
|
|
64
66
|
return self.start()
|
|
65
67
|
|
|
@@ -73,7 +75,7 @@ class AtomicFileWriter:
|
|
|
73
75
|
self.rollback()
|
|
74
76
|
return False # Don't suppress exceptions
|
|
75
77
|
|
|
76
|
-
def start(self) ->
|
|
78
|
+
def start(self) -> BinaryIO | TextIO:
|
|
77
79
|
"""
|
|
78
80
|
Start the atomic write operation.
|
|
79
81
|
|
|
@@ -150,8 +152,8 @@ class AtomicFileWriter:
|
|
|
150
152
|
logger.warning(f"Temporary file is empty: {self.temp_path}")
|
|
151
153
|
|
|
152
154
|
# Atomic move to target location
|
|
153
|
-
# On Windows, need to remove target first if it exists
|
|
154
|
-
if
|
|
155
|
+
# On Windows, need to remove target first if it exists (Windows filesystem limitation)
|
|
156
|
+
if platform.system() == 'Windows' and self.target_path.exists():
|
|
155
157
|
self.target_path.unlink()
|
|
156
158
|
|
|
157
159
|
# Perform the atomic move
|
|
@@ -250,11 +252,11 @@ class AtomicFileWriter:
|
|
|
250
252
|
|
|
251
253
|
@contextmanager
|
|
252
254
|
def atomic_write(
|
|
253
|
-
target_path:
|
|
255
|
+
target_path: str | Path,
|
|
254
256
|
mode: str = "w",
|
|
255
257
|
encoding: Optional[str] = "utf-8",
|
|
256
258
|
backup: bool = True,
|
|
257
|
-
temp_dir: Optional[
|
|
259
|
+
temp_dir: Optional[str | Path] = None,
|
|
258
260
|
):
|
|
259
261
|
"""
|
|
260
262
|
Context manager for atomic file writing.
|
|
@@ -286,10 +288,11 @@ def atomic_write(
|
|
|
286
288
|
|
|
287
289
|
|
|
288
290
|
def safe_write_text(
|
|
289
|
-
target_path:
|
|
291
|
+
target_path: str | Path,
|
|
290
292
|
content: str,
|
|
291
293
|
encoding: str = "utf-8",
|
|
292
294
|
backup: bool = True,
|
|
295
|
+
append: bool = False,
|
|
293
296
|
) -> None:
|
|
294
297
|
"""
|
|
295
298
|
Safely write text content to a file atomically.
|
|
@@ -300,12 +303,22 @@ def safe_write_text(
|
|
|
300
303
|
encoding: Text encoding
|
|
301
304
|
backup: Whether to create backup
|
|
302
305
|
"""
|
|
306
|
+
target_path = Path(target_path)
|
|
307
|
+
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
308
|
+
|
|
309
|
+
# For append operations, atomic write semantics don't apply cleanly (we are not replacing the file).
|
|
310
|
+
# Tests and typical usage protect append with a lock (see FileLock usage in core tests).
|
|
311
|
+
if append:
|
|
312
|
+
with open(target_path, "a", encoding=encoding) as f:
|
|
313
|
+
f.write(content)
|
|
314
|
+
return
|
|
315
|
+
|
|
303
316
|
with atomic_write(target_path, "w", encoding=encoding, backup=backup) as f:
|
|
304
317
|
f.write(content)
|
|
305
318
|
|
|
306
319
|
|
|
307
320
|
def safe_write_bytes(
|
|
308
|
-
target_path:
|
|
321
|
+
target_path: str | Path, content: bytes, backup: bool = True
|
|
309
322
|
) -> None:
|
|
310
323
|
"""
|
|
311
324
|
Safely write binary content to a file atomically.
|
|
@@ -320,7 +333,7 @@ def safe_write_bytes(
|
|
|
320
333
|
|
|
321
334
|
|
|
322
335
|
def safe_read_text(
|
|
323
|
-
file_path:
|
|
336
|
+
file_path: str | Path, encoding: str = "utf-8", max_size_mb: float = 100.0
|
|
324
337
|
) -> str:
|
|
325
338
|
"""
|
|
326
339
|
Safely read text content from a file with size validation.
|
|
@@ -372,7 +385,7 @@ def safe_read_text(
|
|
|
372
385
|
raise FileOperationError(f"IOError reading file '{file_path}': {e}") from e
|
|
373
386
|
|
|
374
387
|
|
|
375
|
-
def safe_read_bytes(file_path:
|
|
388
|
+
def safe_read_bytes(file_path: str | Path, max_size_mb: float = 100.0) -> bytes:
|
|
376
389
|
"""
|
|
377
390
|
Safely read binary content from a file with size validation.
|
|
378
391
|
|
|
@@ -419,7 +432,7 @@ def safe_read_bytes(file_path: Union[str, Path], max_size_mb: float = 100.0) ->
|
|
|
419
432
|
|
|
420
433
|
|
|
421
434
|
def safe_read_with_fallback(
|
|
422
|
-
file_path:
|
|
435
|
+
file_path: str | Path,
|
|
423
436
|
preferred_encoding: str = "utf-8",
|
|
424
437
|
fallback_encodings: Optional[list[str]] = None,
|
|
425
438
|
max_size_mb: float = 100.0,
|
|
@@ -442,7 +455,7 @@ def safe_read_with_fallback(
|
|
|
442
455
|
if fallback_encodings is None:
|
|
443
456
|
fallback_encodings = ["latin1", "cp1252", "iso-8859-1"]
|
|
444
457
|
|
|
445
|
-
# Try
|
|
458
|
+
# Try encoding first
|
|
446
459
|
try:
|
|
447
460
|
return safe_read_text(file_path, preferred_encoding, max_size_mb)
|
|
448
461
|
except FileOperationError as e:
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
Company: eXonware.com
|
|
5
5
|
Author: Eng. Muhammad AlShehri
|
|
6
6
|
Email: connect@exonware.com
|
|
7
|
-
Version: 0.1.0.
|
|
7
|
+
Version: 0.1.0.4
|
|
8
8
|
Generation Date: 30-Oct-2025
|
|
9
9
|
|
|
10
10
|
Base classes and utilities for common IO operations.
|
|
@@ -21,8 +21,10 @@ Priority 4 (Performance): Efficient operations
|
|
|
21
21
|
Priority 5 (Extensibility): Ready for new utilities
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
24
26
|
from abc import ABC, abstractmethod
|
|
25
|
-
from typing import Optional,
|
|
27
|
+
from typing import Optional, Callable
|
|
26
28
|
from pathlib import Path
|
|
27
29
|
|
|
28
30
|
from ..contracts import IAtomicWriter, IPathValidator, IFileWatcher, IFileLock
|
|
@@ -43,7 +45,7 @@ class AAtomicWriter(IAtomicWriter, ABC):
|
|
|
43
45
|
Provides skeletal implementation for atomic write operations.
|
|
44
46
|
"""
|
|
45
47
|
|
|
46
|
-
def __init__(self, path:
|
|
48
|
+
def __init__(self, path: str | Path, mode: AtomicMode = AtomicMode.WRITE_BACKUP):
|
|
47
49
|
"""Initialize atomic writer."""
|
|
48
50
|
self.path = Path(path)
|
|
49
51
|
self.mode = mode
|
|
@@ -55,7 +57,7 @@ class AAtomicWriter(IAtomicWriter, ABC):
|
|
|
55
57
|
"""Write data atomically."""
|
|
56
58
|
pass
|
|
57
59
|
|
|
58
|
-
def __enter__(self) ->
|
|
60
|
+
def __enter__(self) -> AAtomicWriter:
|
|
59
61
|
"""Enter context manager."""
|
|
60
62
|
return self
|
|
61
63
|
|
|
@@ -77,18 +79,18 @@ class APathValidator(IPathValidator, ABC):
|
|
|
77
79
|
self.security_level = security_level
|
|
78
80
|
|
|
79
81
|
@abstractmethod
|
|
80
|
-
def validate_path(self, path:
|
|
82
|
+
def validate_path(self, path: str | Path) -> bool:
|
|
81
83
|
"""Validate path safety."""
|
|
82
84
|
pass
|
|
83
85
|
|
|
84
|
-
def is_safe_path(self, path:
|
|
86
|
+
def is_safe_path(self, path: str | Path) -> bool:
|
|
85
87
|
"""Check if path is safe to use."""
|
|
86
88
|
try:
|
|
87
89
|
return self.validate_path(path)
|
|
88
90
|
except Exception:
|
|
89
91
|
return False
|
|
90
92
|
|
|
91
|
-
def normalize_path(self, path:
|
|
93
|
+
def normalize_path(self, path: str | Path) -> Path:
|
|
92
94
|
"""Normalize and resolve path."""
|
|
93
95
|
return Path(path).resolve()
|
|
94
96
|
|
|
@@ -134,7 +136,7 @@ class AFileLock(IFileLock, ABC):
|
|
|
134
136
|
Provides common locking logic.
|
|
135
137
|
"""
|
|
136
138
|
|
|
137
|
-
def __init__(self, path:
|
|
139
|
+
def __init__(self, path: str | Path, mode: LockMode = LockMode.EXCLUSIVE):
|
|
138
140
|
"""Initialize lock."""
|
|
139
141
|
self.path = Path(path)
|
|
140
142
|
self.mode = mode
|
|
@@ -154,7 +156,7 @@ class AFileLock(IFileLock, ABC):
|
|
|
154
156
|
"""Check if locked."""
|
|
155
157
|
return self._locked
|
|
156
158
|
|
|
157
|
-
def __enter__(self) ->
|
|
159
|
+
def __enter__(self) -> AFileLock:
|
|
158
160
|
"""Context manager entry."""
|
|
159
161
|
self.acquire()
|
|
160
162
|
return self
|
|
@@ -162,4 +164,3 @@ class AFileLock(IFileLock, ABC):
|
|
|
162
164
|
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
163
165
|
"""Context manager exit."""
|
|
164
166
|
self.release()
|
|
165
|
-
|