exonware-xwsystem 0.1.0.1__py3-none-any.whl → 0.1.0.3__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.3.dist-info}/METADATA +71 -4
- exonware_xwsystem-0.1.0.3.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.3.dist-info}/WHEEL +0 -0
- {exonware_xwsystem-0.1.0.1.dist-info → exonware_xwsystem-0.1.0.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
+
#exonware/xwsystem/src/exonware/xwsystem/validation/defs.py
|
|
2
3
|
#exonware/xwsystem/validation/types.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.3
|
|
8
9
|
Generation Date: 07-Sep-2025
|
|
9
10
|
|
|
10
11
|
Validation types and enums for XWSystem.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
#exonware/xwsystem/src/exonware/xwsystem/validation/errors.py
|
|
1
2
|
#exonware/xwsystem/validation/errors.py
|
|
2
3
|
"""
|
|
3
4
|
Company: eXonware.com
|
|
4
5
|
Author: Eng. Muhammad AlShehri
|
|
5
6
|
Email: connect@exonware.com
|
|
6
|
-
Version: 0.1.0.
|
|
7
|
+
Version: 0.1.0.3
|
|
7
8
|
Generation Date: September 04, 2025
|
|
8
9
|
|
|
9
10
|
Validation module errors - exception classes for validation functionality.
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#exonware/xwsystem/src/exonware/xwsystem/validation/facade.py
|
|
2
|
+
"""
|
|
3
|
+
Company: eXonware.com
|
|
4
|
+
Author: Eng. Muhammad AlShehri
|
|
5
|
+
Email: connect@exonware.com
|
|
6
|
+
Version: 0.1.0.3
|
|
7
|
+
Generation Date: January 2026
|
|
8
|
+
|
|
9
|
+
XWValidator - Unified Validation Facade
|
|
10
|
+
|
|
11
|
+
Simplified API for validation operations:
|
|
12
|
+
- Quick validation
|
|
13
|
+
- Declarative models
|
|
14
|
+
- Path validation
|
|
15
|
+
- Data validation (depth, size)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from typing import Any, Optional, Dict, Type
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from .declarative import XModel, Field, ValidationError
|
|
22
|
+
from .data_validator import DataValidator, validate_path_input, check_data_depth, estimate_memory_usage
|
|
23
|
+
from .type_safety import validate_untrusted_data, SafeTypeValidator
|
|
24
|
+
from ..security.path_validator import PathValidator
|
|
25
|
+
from ..config.logging_setup import get_logger
|
|
26
|
+
|
|
27
|
+
logger = get_logger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class XWValidator:
|
|
31
|
+
"""
|
|
32
|
+
Unified validation facade - simple API for validation operations.
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
>>> # Quick validation
|
|
36
|
+
>>> XWValidator.validate(data, rules={"age": "int", "name": "required"})
|
|
37
|
+
|
|
38
|
+
>>> # Declarative models
|
|
39
|
+
>>> class User(XWValidator.Model):
|
|
40
|
+
... name: str = XWValidator.Field(required=True, min_length=3)
|
|
41
|
+
... age: int = XWValidator.Field(min=0, max=150)
|
|
42
|
+
|
|
43
|
+
>>> user = User(name="John", age=30) # Validates automatically
|
|
44
|
+
|
|
45
|
+
>>> # Path validation
|
|
46
|
+
>>> XWValidator.validate_path("/safe/dir/file.txt", base_path="/safe")
|
|
47
|
+
|
|
48
|
+
>>> # Data validation
|
|
49
|
+
>>> XWValidator.validate_depth(data, max_depth=100)
|
|
50
|
+
>>> XWValidator.validate_size(data, max_size_mb=10)
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
# Re-export XModel and Field for convenience
|
|
54
|
+
Model = XModel
|
|
55
|
+
Field = Field
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def validate(data: Any, rules: Dict[str, Any]) -> bool:
|
|
59
|
+
"""
|
|
60
|
+
Quick validation with rules dictionary.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
data: Data to validate
|
|
64
|
+
rules: Validation rules (e.g., {"age": "int", "name": "required"})
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
True if valid
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
ValidationError: If validation fails
|
|
71
|
+
"""
|
|
72
|
+
# Simple rule-based validation
|
|
73
|
+
if not isinstance(data, dict):
|
|
74
|
+
raise ValidationError("Data must be a dictionary")
|
|
75
|
+
|
|
76
|
+
for key, rule in rules.items():
|
|
77
|
+
if rule == "required" and key not in data:
|
|
78
|
+
raise ValidationError(f"Required field missing: {key}")
|
|
79
|
+
elif key in data:
|
|
80
|
+
value = data[key]
|
|
81
|
+
if rule == "int" and not isinstance(value, int):
|
|
82
|
+
raise ValidationError(f"Field {key} must be int, got {type(value).__name__}")
|
|
83
|
+
elif rule == "str" and not isinstance(value, str):
|
|
84
|
+
raise ValidationError(f"Field {key} must be str, got {type(value).__name__}")
|
|
85
|
+
|
|
86
|
+
return True
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def validate_path(path: str, base_path: Optional[str] = None, must_exist: bool = False) -> str:
|
|
90
|
+
"""
|
|
91
|
+
Validate and sanitize file path.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
path: Path to validate
|
|
95
|
+
base_path: Base directory (prevents directory traversal)
|
|
96
|
+
must_exist: Whether path must exist (default: False)
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Validated path
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
ValidationError: If path is invalid
|
|
103
|
+
"""
|
|
104
|
+
try:
|
|
105
|
+
validator = PathValidator(base_path) if base_path else PathValidator()
|
|
106
|
+
validated = validator.validate_path(path)
|
|
107
|
+
|
|
108
|
+
# Check existence if required
|
|
109
|
+
if must_exist and not Path(validated).exists():
|
|
110
|
+
raise ValidationError(f"Path does not exist: {validated}")
|
|
111
|
+
|
|
112
|
+
return validated
|
|
113
|
+
except ValidationError:
|
|
114
|
+
raise
|
|
115
|
+
except Exception as e:
|
|
116
|
+
raise ValidationError(f"Path validation failed: {e}") from e
|
|
117
|
+
|
|
118
|
+
@staticmethod
|
|
119
|
+
def validate_depth(data: Any, max_depth: int = 100) -> bool:
|
|
120
|
+
"""
|
|
121
|
+
Validate data depth.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
data: Data to validate
|
|
125
|
+
max_depth: Maximum allowed depth
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
True if depth is within limits
|
|
129
|
+
|
|
130
|
+
Raises:
|
|
131
|
+
ValidationError: If depth exceeds limit
|
|
132
|
+
"""
|
|
133
|
+
# check_data_depth raises exception if depth exceeds, returns None otherwise
|
|
134
|
+
try:
|
|
135
|
+
check_data_depth(data, max_depth=max_depth)
|
|
136
|
+
return True
|
|
137
|
+
except Exception as e:
|
|
138
|
+
raise ValidationError(f"Data depth validation failed: {e}") from e
|
|
139
|
+
|
|
140
|
+
@staticmethod
|
|
141
|
+
def validate_size(data: Any, max_size_mb: int = 10) -> bool:
|
|
142
|
+
"""
|
|
143
|
+
Validate data size.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
data: Data to validate
|
|
147
|
+
max_size_mb: Maximum size in MB
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
True if size is within limits
|
|
151
|
+
|
|
152
|
+
Raises:
|
|
153
|
+
ValidationError: If size exceeds limit
|
|
154
|
+
"""
|
|
155
|
+
size_mb = estimate_memory_usage(data) / (1024 * 1024)
|
|
156
|
+
if size_mb > max_size_mb:
|
|
157
|
+
raise ValidationError(f"Data size {size_mb:.2f}MB exceeds maximum {max_size_mb}MB")
|
|
158
|
+
return True
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def validate_untrusted(data: Any, max_depth: int = 100) -> bool:
|
|
162
|
+
"""
|
|
163
|
+
Validate untrusted data with security checks.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
data: Untrusted data to validate
|
|
167
|
+
max_depth: Maximum allowed depth
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
True if valid
|
|
171
|
+
|
|
172
|
+
Raises:
|
|
173
|
+
ValidationError: If validation fails
|
|
174
|
+
"""
|
|
175
|
+
return validate_untrusted_data(data, max_depth=max_depth)
|
|
176
|
+
|
|
177
|
+
@classmethod
|
|
178
|
+
def create_model(cls, **fields) -> Type[XModel]:
|
|
179
|
+
"""
|
|
180
|
+
Create a model class dynamically.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
**fields: Field definitions
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Model class
|
|
187
|
+
"""
|
|
188
|
+
# Create a dynamic model class
|
|
189
|
+
class DynamicModel(XModel):
|
|
190
|
+
pass
|
|
191
|
+
|
|
192
|
+
for name, field_def in fields.items():
|
|
193
|
+
if isinstance(field_def, Field):
|
|
194
|
+
setattr(DynamicModel, name, field_def)
|
|
195
|
+
else:
|
|
196
|
+
setattr(DynamicModel, name, Field(default=field_def))
|
|
197
|
+
|
|
198
|
+
return DynamicModel
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
+
#exonware/xwsystem/src/exonware/xwsystem/validation/fluent_validator.py
|
|
2
3
|
"""
|
|
3
4
|
Company: eXonware.com
|
|
4
5
|
Author: Eng. Muhammad AlShehri
|
|
5
6
|
Email: connect@exonware.com
|
|
6
|
-
Version: 0.1.0.
|
|
7
|
+
Version: 0.1.0.3
|
|
7
8
|
Generation Date: October 26, 2025
|
|
8
9
|
|
|
9
10
|
Fluent validator with chainable API for data validation.
|
|
10
11
|
"""
|
|
11
12
|
|
|
12
|
-
from
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Any, Callable, Optional
|
|
13
16
|
from .errors import ValidationError
|
|
14
17
|
from ..config.logging_setup import get_logger
|
|
15
18
|
|
|
@@ -40,7 +43,7 @@ class FluentValidator:
|
|
|
40
43
|
self.errors: list[str] = []
|
|
41
44
|
self.rules: list[dict[str, Any]] = []
|
|
42
45
|
|
|
43
|
-
def require(self, field_name: str, message: Optional[str] = None) ->
|
|
46
|
+
def require(self, field_name: str, message: Optional[str] = None) -> FluentValidator:
|
|
44
47
|
"""
|
|
45
48
|
Require a field to be present and not None.
|
|
46
49
|
|
|
@@ -65,7 +68,7 @@ class FluentValidator:
|
|
|
65
68
|
|
|
66
69
|
return self
|
|
67
70
|
|
|
68
|
-
def type_check(self, expected_type: type, message: Optional[str] = None) ->
|
|
71
|
+
def type_check(self, expected_type: type, message: Optional[str] = None) -> FluentValidator:
|
|
69
72
|
"""
|
|
70
73
|
Check if data is of expected type.
|
|
71
74
|
|
|
@@ -88,10 +91,10 @@ class FluentValidator:
|
|
|
88
91
|
|
|
89
92
|
def range_check(
|
|
90
93
|
self,
|
|
91
|
-
min_value: Optional[
|
|
92
|
-
max_value: Optional[
|
|
94
|
+
min_value: Optional[int | float] = None,
|
|
95
|
+
max_value: Optional[int | float] = None,
|
|
93
96
|
message: Optional[str] = None
|
|
94
|
-
) ->
|
|
97
|
+
) -> FluentValidator:
|
|
95
98
|
"""
|
|
96
99
|
Check if numeric data is within range.
|
|
97
100
|
|
|
@@ -128,7 +131,7 @@ class FluentValidator:
|
|
|
128
131
|
min_length: Optional[int] = None,
|
|
129
132
|
max_length: Optional[int] = None,
|
|
130
133
|
message: Optional[str] = None
|
|
131
|
-
) ->
|
|
134
|
+
) -> FluentValidator:
|
|
132
135
|
"""
|
|
133
136
|
Check if data length is within range.
|
|
134
137
|
|
|
@@ -162,7 +165,7 @@ class FluentValidator:
|
|
|
162
165
|
|
|
163
166
|
return self
|
|
164
167
|
|
|
165
|
-
def pattern_check(self, pattern: str, message: Optional[str] = None) ->
|
|
168
|
+
def pattern_check(self, pattern: str, message: Optional[str] = None) -> FluentValidator:
|
|
166
169
|
"""
|
|
167
170
|
Check if string data matches pattern (regex).
|
|
168
171
|
|
|
@@ -191,7 +194,7 @@ class FluentValidator:
|
|
|
191
194
|
|
|
192
195
|
return self
|
|
193
196
|
|
|
194
|
-
def add_rule(self, validator_func: Callable[[Any], bool], message: Optional[str] = None) ->
|
|
197
|
+
def add_rule(self, validator_func: Callable[[Any], bool], message: Optional[str] = None) -> FluentValidator:
|
|
195
198
|
"""
|
|
196
199
|
Add custom validation rule.
|
|
197
200
|
|
|
@@ -216,7 +219,7 @@ class FluentValidator:
|
|
|
216
219
|
"""Check if data is valid (no errors)."""
|
|
217
220
|
return len(self.errors) == 0
|
|
218
221
|
|
|
219
|
-
def validate(self) ->
|
|
222
|
+
def validate(self) -> FluentValidator:
|
|
220
223
|
"""
|
|
221
224
|
Validate data and raise ValidationError if invalid.
|
|
222
225
|
|
|
@@ -234,12 +237,12 @@ class FluentValidator:
|
|
|
234
237
|
"""Get list of validation errors."""
|
|
235
238
|
return self.errors.copy()
|
|
236
239
|
|
|
237
|
-
def clear_errors(self) ->
|
|
240
|
+
def clear_errors(self) -> FluentValidator:
|
|
238
241
|
"""Clear all validation errors."""
|
|
239
242
|
self.errors.clear()
|
|
240
243
|
return self
|
|
241
244
|
|
|
242
|
-
def set_data(self, data: Any) ->
|
|
245
|
+
def set_data(self, data: Any) -> FluentValidator:
|
|
243
246
|
"""Set data to validate."""
|
|
244
247
|
self.data = data
|
|
245
248
|
return self
|
|
@@ -248,8 +251,8 @@ class FluentValidator:
|
|
|
248
251
|
self,
|
|
249
252
|
field_name: str,
|
|
250
253
|
field_data: Any,
|
|
251
|
-
rules: list[Callable[[
|
|
252
|
-
) ->
|
|
254
|
+
rules: list[Callable[[FluentValidator], FluentValidator]]
|
|
255
|
+
) -> FluentValidator:
|
|
253
256
|
"""
|
|
254
257
|
Validate a specific field with rules.
|
|
255
258
|
|
|
@@ -280,8 +283,8 @@ class FluentValidator:
|
|
|
280
283
|
|
|
281
284
|
def validate_dict_fields(
|
|
282
285
|
self,
|
|
283
|
-
field_rules: dict[str, list[Callable[[
|
|
284
|
-
) ->
|
|
286
|
+
field_rules: dict[str, list[Callable[[FluentValidator], FluentValidator]]]
|
|
287
|
+
) -> FluentValidator:
|
|
285
288
|
"""
|
|
286
289
|
Validate multiple fields in a dictionary.
|
|
287
290
|
|
|
@@ -342,7 +345,7 @@ def validate_string(data: str) -> FluentValidator:
|
|
|
342
345
|
return FluentValidator(data)
|
|
343
346
|
|
|
344
347
|
|
|
345
|
-
def validate_numeric(data:
|
|
348
|
+
def validate_numeric(data: int | float) -> FluentValidator:
|
|
346
349
|
"""
|
|
347
350
|
Create a fluent validator for numeric data.
|
|
348
351
|
|
|
@@ -366,7 +369,7 @@ def is_type(expected_type: type) -> Callable[[FluentValidator], FluentValidator]
|
|
|
366
369
|
return lambda v: v.type_check(expected_type)
|
|
367
370
|
|
|
368
371
|
|
|
369
|
-
def is_in_range(min_val: Optional[
|
|
372
|
+
def is_in_range(min_val: Optional[int | float] = None, max_val: Optional[int | float] = None) -> Callable[[FluentValidator], FluentValidator]:
|
|
370
373
|
"""Create a range check rule."""
|
|
371
374
|
return lambda v: v.range_check(min_val, max_val)
|
|
372
375
|
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#exonware/xwsystem/src/exonware/xwsystem/validation/schema_discovery.py
|
|
3
|
+
"""
|
|
4
|
+
Schema validator discovery (plugin entry points).
|
|
5
|
+
|
|
6
|
+
This module provides a lightweight "plugin" mechanism (via Python packaging
|
|
7
|
+
entry points) to discover an implementation of
|
|
8
|
+
`xwsystem.validation.contracts.ISchemaProvider`.
|
|
9
|
+
|
|
10
|
+
Why this exists:
|
|
11
|
+
- Avoids hard dependency cycles (e.g., xwdata <-> xwschema)
|
|
12
|
+
- Keeps xwsystem as the stable contract owner
|
|
13
|
+
- Enables optional providers to be installed and discovered at runtime
|
|
14
|
+
|
|
15
|
+
Entry point group:
|
|
16
|
+
- xwsystem.schema_validators
|
|
17
|
+
|
|
18
|
+
Providers should register an entry point that resolves to either:
|
|
19
|
+
- an ISchemaProvider instance
|
|
20
|
+
- a class (constructible with no args) returning an ISchemaProvider instance
|
|
21
|
+
- a callable (factory) returning an ISchemaProvider instance
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from dataclasses import dataclass
|
|
27
|
+
import threading
|
|
28
|
+
from typing import Any, Optional
|
|
29
|
+
|
|
30
|
+
from ..config.logging_setup import get_logger
|
|
31
|
+
from .contracts import ISchemaProvider
|
|
32
|
+
|
|
33
|
+
logger = get_logger("xwsystem.validation.schema_discovery")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
DEFAULT_SCHEMA_VALIDATOR_ENTRYPOINT_GROUP = "xwsystem.schema_validators"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class SchemaValidatorDiscoveryResult:
|
|
41
|
+
"""Structured discovery information for observability/debugging."""
|
|
42
|
+
|
|
43
|
+
group: str
|
|
44
|
+
loaded: dict[str, str] # name -> provider repr
|
|
45
|
+
errors: dict[str, str] # name -> error string
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
_lock = threading.RLock()
|
|
49
|
+
_attempted_groups: set[str] = set()
|
|
50
|
+
_providers_by_group: dict[str, dict[str, ISchemaProvider]] = {}
|
|
51
|
+
_default_provider_by_group: dict[str, str] = {}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _as_schema_provider(obj: Any) -> Optional[ISchemaProvider]:
|
|
55
|
+
"""Coerce an entry-point loaded object into an ISchemaProvider instance."""
|
|
56
|
+
if obj is None:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
# 1) Direct instance
|
|
60
|
+
try:
|
|
61
|
+
if isinstance(obj, ISchemaProvider):
|
|
62
|
+
return obj
|
|
63
|
+
except Exception:
|
|
64
|
+
# Protocol runtime checks can raise if obj is weird; treat as not valid.
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
# 2) Class -> instantiate
|
|
68
|
+
if isinstance(obj, type):
|
|
69
|
+
try:
|
|
70
|
+
inst = obj()
|
|
71
|
+
except Exception:
|
|
72
|
+
return None
|
|
73
|
+
try:
|
|
74
|
+
return inst if isinstance(inst, ISchemaProvider) else None
|
|
75
|
+
except Exception:
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
# 3) Factory callable -> call
|
|
79
|
+
if callable(obj):
|
|
80
|
+
try:
|
|
81
|
+
inst = obj()
|
|
82
|
+
except Exception:
|
|
83
|
+
return None
|
|
84
|
+
try:
|
|
85
|
+
return inst if isinstance(inst, ISchemaProvider) else None
|
|
86
|
+
except Exception:
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def discover_schema_validators(
|
|
93
|
+
*,
|
|
94
|
+
group: str = DEFAULT_SCHEMA_VALIDATOR_ENTRYPOINT_GROUP,
|
|
95
|
+
force: bool = False,
|
|
96
|
+
) -> tuple[dict[str, ISchemaProvider], SchemaValidatorDiscoveryResult]:
|
|
97
|
+
"""
|
|
98
|
+
Discover schema validator providers from entry points.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
group: Entry-point group to search.
|
|
102
|
+
force: If True, re-run discovery even if already attempted.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
(providers, discovery_result)
|
|
106
|
+
"""
|
|
107
|
+
with _lock:
|
|
108
|
+
already_attempted = group in _attempted_groups
|
|
109
|
+
if already_attempted and not force:
|
|
110
|
+
providers = dict(_providers_by_group.get(group, {}))
|
|
111
|
+
loaded_repr = {k: repr(v) for k, v in providers.items()}
|
|
112
|
+
return providers, SchemaValidatorDiscoveryResult(group=group, loaded=loaded_repr, errors={})
|
|
113
|
+
|
|
114
|
+
loaded: dict[str, ISchemaProvider] = {}
|
|
115
|
+
loaded_repr: dict[str, str] = {}
|
|
116
|
+
errors: dict[str, str] = {}
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
from importlib.metadata import entry_points
|
|
120
|
+
eps = entry_points(group=group)
|
|
121
|
+
except Exception as e:
|
|
122
|
+
# Cache attempted to avoid repeated expensive failures.
|
|
123
|
+
with _lock:
|
|
124
|
+
_attempted_groups.add(group)
|
|
125
|
+
_providers_by_group.setdefault(group, {})
|
|
126
|
+
logger.debug(f"Schema validator discovery failed for group '{group}': {e}")
|
|
127
|
+
return {}, SchemaValidatorDiscoveryResult(group=group, loaded={}, errors={"__discovery__": str(e)})
|
|
128
|
+
|
|
129
|
+
for ep in eps:
|
|
130
|
+
name = getattr(ep, "name", "<unknown>")
|
|
131
|
+
try:
|
|
132
|
+
obj = ep.load()
|
|
133
|
+
inst = _as_schema_provider(obj)
|
|
134
|
+
if inst is None:
|
|
135
|
+
errors[name] = "Entry point did not resolve to an ISchemaProvider (instance/class/factory)."
|
|
136
|
+
continue
|
|
137
|
+
loaded[name] = inst
|
|
138
|
+
loaded_repr[name] = repr(inst)
|
|
139
|
+
except Exception as e:
|
|
140
|
+
errors[name] = str(e)
|
|
141
|
+
|
|
142
|
+
with _lock:
|
|
143
|
+
_attempted_groups.add(group)
|
|
144
|
+
_providers_by_group[group] = dict(loaded)
|
|
145
|
+
# Preserve existing default selection if still valid; else choose first.
|
|
146
|
+
if group not in _default_provider_by_group or _default_provider_by_group[group] not in loaded:
|
|
147
|
+
if loaded:
|
|
148
|
+
_default_provider_by_group[group] = sorted(loaded.keys())[0]
|
|
149
|
+
|
|
150
|
+
if errors:
|
|
151
|
+
logger.debug(f"Schema validator discovery had errors for group '{group}': {errors}")
|
|
152
|
+
|
|
153
|
+
return dict(loaded), SchemaValidatorDiscoveryResult(group=group, loaded=loaded_repr, errors=errors)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def set_schema_validator(
|
|
157
|
+
validator: ISchemaProvider,
|
|
158
|
+
*,
|
|
159
|
+
name: str = "manual",
|
|
160
|
+
group: str = DEFAULT_SCHEMA_VALIDATOR_ENTRYPOINT_GROUP,
|
|
161
|
+
make_default: bool = True,
|
|
162
|
+
) -> None:
|
|
163
|
+
"""Manually register a schema provider (useful for tests / custom wiring)."""
|
|
164
|
+
with _lock:
|
|
165
|
+
_attempted_groups.add(group)
|
|
166
|
+
_providers_by_group.setdefault(group, {})
|
|
167
|
+
_providers_by_group[group][name] = validator
|
|
168
|
+
if make_default:
|
|
169
|
+
_default_provider_by_group[group] = name
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def get_schema_validator(
|
|
173
|
+
*,
|
|
174
|
+
name: Optional[str] = None,
|
|
175
|
+
group: str = DEFAULT_SCHEMA_VALIDATOR_ENTRYPOINT_GROUP,
|
|
176
|
+
) -> Optional[ISchemaProvider]:
|
|
177
|
+
"""
|
|
178
|
+
Get a schema provider (validator).
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
name: Optional provider name; if omitted, returns the group's default provider.
|
|
182
|
+
group: Entry-point group.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
ISchemaProvider instance or None if no providers available.
|
|
186
|
+
"""
|
|
187
|
+
providers, _ = discover_schema_validators(group=group, force=False)
|
|
188
|
+
if not providers:
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
if name:
|
|
192
|
+
return providers.get(name)
|
|
193
|
+
|
|
194
|
+
with _lock:
|
|
195
|
+
default_name = _default_provider_by_group.get(group)
|
|
196
|
+
|
|
197
|
+
if default_name and default_name in providers:
|
|
198
|
+
return providers[default_name]
|
|
199
|
+
|
|
200
|
+
# Fall back to deterministic choice
|
|
201
|
+
return providers[sorted(providers.keys())[0]]
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def available_schema_validators(
|
|
205
|
+
*,
|
|
206
|
+
group: str = DEFAULT_SCHEMA_VALIDATOR_ENTRYPOINT_GROUP,
|
|
207
|
+
) -> list[str]:
|
|
208
|
+
"""List discovered provider names."""
|
|
209
|
+
providers, _ = discover_schema_validators(group=group, force=False)
|
|
210
|
+
return sorted(providers.keys())
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#exonware/xwsystem/src/exonware/xwsystem/validation/type_safety.py
|
|
1
2
|
"""
|
|
2
3
|
Generic type safety validation.
|
|
3
4
|
|
|
@@ -20,7 +21,7 @@ class SafeTypeValidator:
|
|
|
20
21
|
# Allowed types for untrusted data
|
|
21
22
|
SAFE_TYPES = (str, int, float, bool, list, dict, type(None))
|
|
22
23
|
|
|
23
|
-
# Types that are
|
|
24
|
+
# Types that are safe to cache
|
|
24
25
|
IMMUTABLE_TYPES = (str, int, float, bool, tuple, frozenset, type(None))
|
|
25
26
|
|
|
26
27
|
@classmethod
|
exonware/xwsystem/version.py
CHANGED
|
@@ -14,13 +14,13 @@ All version references should import from this module to ensure consistency.
|
|
|
14
14
|
# =============================================================================
|
|
15
15
|
|
|
16
16
|
# Main version - update this to change version across entire project
|
|
17
|
-
__version__ = "0.1.0.
|
|
17
|
+
__version__ = "0.1.0.3"
|
|
18
18
|
|
|
19
19
|
# Version components for programmatic access
|
|
20
20
|
VERSION_MAJOR = 0
|
|
21
21
|
VERSION_MINOR = 1
|
|
22
22
|
VERSION_PATCH = 0
|
|
23
|
-
VERSION_BUILD =
|
|
23
|
+
VERSION_BUILD = 3 # Set to None for releases, or build number for dev builds
|
|
24
24
|
|
|
25
25
|
# Version metadata
|
|
26
26
|
VERSION_SUFFIX = "" # e.g., "dev", "alpha", "beta", "rc1"
|