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
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
#exonware/xwsystem/src/exonware/xwsystem/utils/test_runner.py
|
|
4
|
+
|
|
5
|
+
Reusable pytest runner utilities for all eXonware libraries.
|
|
6
|
+
|
|
7
|
+
Implements the hierarchical runner utilities described in:
|
|
8
|
+
- docs/guides/GUIDE_DEV.md
|
|
9
|
+
- docs/guides/GUIDE_TEST.md
|
|
10
|
+
|
|
11
|
+
Company: eXonware.com
|
|
12
|
+
Author: Eng. Muhammad AlShehri
|
|
13
|
+
Email: connect@exonware.com
|
|
14
|
+
Version: 0.1.0.3
|
|
15
|
+
Generation Date: 28-Dec-2025
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import sys
|
|
21
|
+
import subprocess
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from datetime import datetime
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any, Optional
|
|
26
|
+
|
|
27
|
+
from exonware.xwsystem.console.cli import ensure_utf8_console
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def format_path(path: Path) -> str:
|
|
31
|
+
"""Format a path as a full absolute path string."""
|
|
32
|
+
return str(path.resolve())
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class DualOutput:
|
|
36
|
+
"""
|
|
37
|
+
Capture output for terminal and Markdown file simultaneously.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, output_file: Path):
|
|
41
|
+
self.output_file = output_file
|
|
42
|
+
self._markdown_lines: list[str] = []
|
|
43
|
+
|
|
44
|
+
def print(self, text: str, markdown_format: Optional[str] = None, *, emoji: Optional[str] = None) -> None:
|
|
45
|
+
prefix = f"{emoji} " if emoji else ""
|
|
46
|
+
line = f"{prefix}{text}"
|
|
47
|
+
print(line)
|
|
48
|
+
self._markdown_lines.append(markdown_format if markdown_format is not None else line)
|
|
49
|
+
|
|
50
|
+
def save(self, header_info: Optional[dict[str, Any]] = None) -> None:
|
|
51
|
+
header_info = header_info or {}
|
|
52
|
+
header = (
|
|
53
|
+
"# Test Runner Output\n\n"
|
|
54
|
+
f"**Library:** {header_info.get('library', '')} \n"
|
|
55
|
+
f"**Layer:** {header_info.get('layer', '')} \n"
|
|
56
|
+
f"**Generated:** {datetime.now().strftime('%d-%b-%Y %H:%M:%S')} \n"
|
|
57
|
+
f"**Runner:** {header_info.get('runner', 'TestRunner')} \n\n"
|
|
58
|
+
"---\n\n"
|
|
59
|
+
)
|
|
60
|
+
self.output_file.write_text(header + "\n".join(self._markdown_lines) + "\n", encoding="utf-8")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def print_header(title: str, output: Optional[DualOutput] = None) -> None:
|
|
64
|
+
sep = "=" * 80
|
|
65
|
+
if output is None:
|
|
66
|
+
print(sep)
|
|
67
|
+
print(title)
|
|
68
|
+
print(sep)
|
|
69
|
+
return
|
|
70
|
+
output.print(sep)
|
|
71
|
+
output.print(title)
|
|
72
|
+
output.print(sep)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def print_section(title: str, output: Optional[DualOutput] = None) -> None:
|
|
76
|
+
if output is None:
|
|
77
|
+
print(f"\n{'=' * 80}\n{title}\n{'=' * 80}\n")
|
|
78
|
+
return
|
|
79
|
+
output.print(f"\n{title}", f"\n## {title}\n")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def print_status(success: bool, message: str, output: Optional[DualOutput] = None) -> None:
|
|
83
|
+
emoji = "✅" if success else "❌"
|
|
84
|
+
if output is None:
|
|
85
|
+
print(f"{emoji} {message}")
|
|
86
|
+
return
|
|
87
|
+
output.print(message, f"**Result:** {emoji} {message}", emoji=emoji)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass(frozen=True)
|
|
91
|
+
class PytestRunResult:
|
|
92
|
+
exit_code: int
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def run_pytest(
|
|
96
|
+
*,
|
|
97
|
+
test_dir: Path,
|
|
98
|
+
markers: Optional[list[str]] = None,
|
|
99
|
+
extra_args: Optional[list[str]] = None,
|
|
100
|
+
cwd: Optional[Path] = None,
|
|
101
|
+
) -> PytestRunResult:
|
|
102
|
+
"""
|
|
103
|
+
Run pytest as a subprocess with standard eXonware flags.
|
|
104
|
+
"""
|
|
105
|
+
markers = markers or []
|
|
106
|
+
extra_args = extra_args or []
|
|
107
|
+
cwd = cwd or test_dir
|
|
108
|
+
|
|
109
|
+
args: list[str] = [
|
|
110
|
+
sys.executable,
|
|
111
|
+
"-m",
|
|
112
|
+
"pytest",
|
|
113
|
+
str(test_dir),
|
|
114
|
+
"-v",
|
|
115
|
+
"--tb=short",
|
|
116
|
+
"-x",
|
|
117
|
+
"--strict-markers",
|
|
118
|
+
]
|
|
119
|
+
if markers:
|
|
120
|
+
args.extend(["-m", " and ".join(markers)])
|
|
121
|
+
args.extend(extra_args)
|
|
122
|
+
|
|
123
|
+
completed = subprocess.run(args, cwd=str(cwd))
|
|
124
|
+
return PytestRunResult(exit_code=int(completed.returncode))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class TestRunner:
|
|
128
|
+
"""
|
|
129
|
+
Simple reusable test runner for a single directory/layer.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
def __init__(
|
|
133
|
+
self,
|
|
134
|
+
*,
|
|
135
|
+
library_name: str,
|
|
136
|
+
layer_name: str,
|
|
137
|
+
description: str,
|
|
138
|
+
test_dir: Path,
|
|
139
|
+
markers: Optional[list[str]] = None,
|
|
140
|
+
output_file: Optional[Path] = None,
|
|
141
|
+
):
|
|
142
|
+
self.library_name = library_name
|
|
143
|
+
self.layer_name = layer_name
|
|
144
|
+
self.description = description
|
|
145
|
+
self.test_dir = test_dir
|
|
146
|
+
self.markers = markers or []
|
|
147
|
+
|
|
148
|
+
# Layer runners should pass output_file=None to not write files
|
|
149
|
+
# Only main runner writes to docs/logs/tests/
|
|
150
|
+
self.output_file = output_file
|
|
151
|
+
if self.output_file is None:
|
|
152
|
+
# Layer runner - create dummy output that won't save
|
|
153
|
+
# Use a temporary path that we'll ignore
|
|
154
|
+
import tempfile
|
|
155
|
+
dummy_file = Path(tempfile.gettempdir()) / "xwsystem_test_runner_dummy.md"
|
|
156
|
+
self.output = DualOutput(dummy_file)
|
|
157
|
+
self._is_layer_runner = True
|
|
158
|
+
else:
|
|
159
|
+
self.output = DualOutput(self.output_file)
|
|
160
|
+
self._is_layer_runner = False
|
|
161
|
+
|
|
162
|
+
def run(self) -> int:
|
|
163
|
+
ensure_utf8_console()
|
|
164
|
+
|
|
165
|
+
print_header(self.description, self.output)
|
|
166
|
+
self.output.print(
|
|
167
|
+
f"Directory: {format_path(self.test_dir)}",
|
|
168
|
+
f"**Directory:** `{format_path(self.test_dir)}`",
|
|
169
|
+
emoji="📂",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
result = run_pytest(test_dir=self.test_dir, markers=self.markers)
|
|
173
|
+
ok = result.exit_code == 0
|
|
174
|
+
print_status(ok, "PASSED" if ok else "FAILED", self.output)
|
|
175
|
+
|
|
176
|
+
# Layer runners don't write files - only main runner writes to docs/logs/tests/
|
|
177
|
+
if not self._is_layer_runner:
|
|
178
|
+
self.output.save(
|
|
179
|
+
{
|
|
180
|
+
"library": self.library_name,
|
|
181
|
+
"layer": self.layer_name,
|
|
182
|
+
"runner": "TestRunner",
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
return result.exit_code
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
+
#exonware/xwsystem/src/exonware/xwsystem/utils/utils_contracts.py
|
|
2
3
|
#exonware/xwsystem/utils/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
|
Utils types and enums for XWSystem.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#exonware/xwsystem/src/exonware/xwsystem/utils/web.py
|
|
3
|
+
"""
|
|
4
|
+
Company: eXonware.com
|
|
5
|
+
Author: Eng. Muhammad AlShehri
|
|
6
|
+
Email: connect@exonware.com
|
|
7
|
+
Version: 0.1.0.3
|
|
8
|
+
Generation Date: January 2025
|
|
9
|
+
|
|
10
|
+
Web utility functions for XSystem.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import Optional
|
|
14
|
+
from urllib.parse import urlparse
|
|
15
|
+
|
|
16
|
+
# Prevent BeautifulSoup from trying to import lxml (which has Python 2 syntax issues)
|
|
17
|
+
# Block lxml import before BeautifulSoup tries to import it
|
|
18
|
+
import sys
|
|
19
|
+
if 'lxml' not in sys.modules:
|
|
20
|
+
# Create a dummy module to prevent lxml from being imported
|
|
21
|
+
class DummyModule:
|
|
22
|
+
pass
|
|
23
|
+
sys.modules['lxml'] = DummyModule()
|
|
24
|
+
sys.modules['lxml.etree'] = DummyModule()
|
|
25
|
+
sys.modules['lxml.html'] = DummyModule()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def validate_url_accessible(url: str, timeout: int = 10) -> bool:
|
|
29
|
+
"""
|
|
30
|
+
Validate that a URL is accessible.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
url: URL to validate
|
|
34
|
+
timeout: Request timeout in seconds
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
True if URL is accessible (status 200), False otherwise
|
|
38
|
+
|
|
39
|
+
Examples:
|
|
40
|
+
>>> validate_url_accessible("https://www.example.com")
|
|
41
|
+
True
|
|
42
|
+
>>> validate_url_accessible("https://invalid-url-that-does-not-exist.com")
|
|
43
|
+
False
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
parsed_url = urlparse(url)
|
|
47
|
+
if not all([parsed_url.scheme in ["http", "https"], parsed_url.netloc]):
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
# Optional dependency: requests
|
|
51
|
+
import importlib.util
|
|
52
|
+
_requests_spec = importlib.util.find_spec('requests')
|
|
53
|
+
if _requests_spec is None:
|
|
54
|
+
# If requests is not available, we can't validate
|
|
55
|
+
return False
|
|
56
|
+
import requests
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
# First try a HEAD request (more efficient)
|
|
60
|
+
response = requests.head(url, allow_redirects=True, timeout=timeout)
|
|
61
|
+
except requests.RequestException:
|
|
62
|
+
# If HEAD request fails, try a GET request
|
|
63
|
+
response = requests.get(url, allow_redirects=True, timeout=timeout)
|
|
64
|
+
|
|
65
|
+
# Check if the status code indicates success
|
|
66
|
+
return response.status_code == 200
|
|
67
|
+
|
|
68
|
+
except (requests.RequestException, Exception):
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def extract_webpage_text(url: str) -> str:
|
|
73
|
+
"""
|
|
74
|
+
Extract text content from a webpage.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
url: URL to extract text from
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Extracted text content
|
|
81
|
+
|
|
82
|
+
Examples:
|
|
83
|
+
>>> text = extract_webpage_text("https://www.example.com")
|
|
84
|
+
>>> len(text) > 0
|
|
85
|
+
True
|
|
86
|
+
"""
|
|
87
|
+
from urllib.request import urlopen
|
|
88
|
+
from bs4 import BeautifulSoup
|
|
89
|
+
|
|
90
|
+
html = urlopen(url).read()
|
|
91
|
+
soup = BeautifulSoup(html, features="html.parser")
|
|
92
|
+
|
|
93
|
+
# Remove script and style elements
|
|
94
|
+
for script in soup(["script", "style"]):
|
|
95
|
+
script.extract()
|
|
96
|
+
|
|
97
|
+
# Get text
|
|
98
|
+
text = soup.get_text()
|
|
99
|
+
|
|
100
|
+
# Break into lines and remove leading/trailing space
|
|
101
|
+
lines = (line.strip() for line in text.splitlines())
|
|
102
|
+
chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
|
|
103
|
+
# Drop blank lines
|
|
104
|
+
return '\n'.join(chunk for chunk in chunks if chunk)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
__all__ = [
|
|
108
|
+
'validate_url_accessible',
|
|
109
|
+
'extract_webpage_text',
|
|
110
|
+
]
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
#exonware/xwsystem/src/exonware/xwsystem/validation/__init__.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.3
|
|
6
7
|
Generation Date: September 04, 2025
|
|
7
8
|
|
|
8
9
|
XSystem Validation Package
|
|
@@ -12,10 +13,33 @@ Declarative validation with type hints, automatic coercion, and Pydantic-style m
|
|
|
12
13
|
|
|
13
14
|
from .declarative import XModel, Field, ValidationError
|
|
14
15
|
from .type_safety import validate_untrusted_data
|
|
16
|
+
from .contracts import ISchemaProvider
|
|
17
|
+
from .schema_discovery import (
|
|
18
|
+
DEFAULT_SCHEMA_VALIDATOR_ENTRYPOINT_GROUP,
|
|
19
|
+
SchemaValidatorDiscoveryResult,
|
|
20
|
+
discover_schema_validators,
|
|
21
|
+
get_schema_validator,
|
|
22
|
+
set_schema_validator,
|
|
23
|
+
available_schema_validators,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Unified Facade
|
|
27
|
+
from .facade import XWValidator
|
|
15
28
|
|
|
16
29
|
__all__ = [
|
|
30
|
+
# Unified Facade
|
|
31
|
+
"XWValidator",
|
|
32
|
+
# Core Classes
|
|
17
33
|
"XModel",
|
|
18
34
|
"Field",
|
|
19
35
|
"ValidationError",
|
|
20
36
|
"validate_untrusted_data",
|
|
37
|
+
# Schema validation contracts + discovery (optional providers)
|
|
38
|
+
"ISchemaProvider",
|
|
39
|
+
"DEFAULT_SCHEMA_VALIDATOR_ENTRYPOINT_GROUP",
|
|
40
|
+
"SchemaValidatorDiscoveryResult",
|
|
41
|
+
"discover_schema_validators",
|
|
42
|
+
"get_schema_validator",
|
|
43
|
+
"set_schema_validator",
|
|
44
|
+
"available_schema_validators",
|
|
21
45
|
]
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
+
#exonware/xwsystem/src/exonware/xwsystem/validation/base.py
|
|
1
2
|
#exonware/xwsystem/validation/base.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 base classes - abstract classes for validation functionality.
|
|
10
11
|
"""
|
|
11
12
|
|
|
12
13
|
from abc import ABC, abstractmethod
|
|
13
|
-
from typing import Any, Optional,
|
|
14
|
+
from typing import Any, Optional, Callable
|
|
14
15
|
# Root cause: Migrating to Python 3.12 built-in generic syntax for consistency
|
|
15
16
|
# Priority #3: Maintainability - Modern type annotations improve code clarity
|
|
16
17
|
from .contracts import ValidationType, ValidationLevel, ConstraintType, SchemaType
|
|
@@ -102,13 +103,13 @@ class ADataValidatorBase(ABC):
|
|
|
102
103
|
pass
|
|
103
104
|
|
|
104
105
|
@abstractmethod
|
|
105
|
-
def validate_range(self, data:
|
|
106
|
-
max_value: Optional[
|
|
106
|
+
def validate_range(self, data: int | float, min_value: Optional[int | float] = None,
|
|
107
|
+
max_value: Optional[int | float] = None) -> bool:
|
|
107
108
|
"""Validate data range."""
|
|
108
109
|
pass
|
|
109
110
|
|
|
110
111
|
@abstractmethod
|
|
111
|
-
def validate_length(self, data:
|
|
112
|
+
def validate_length(self, data: str | list | dict, min_length: Optional[int] = None,
|
|
112
113
|
max_length: Optional[int] = None) -> bool:
|
|
113
114
|
"""Validate data length."""
|
|
114
115
|
pass
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
+
#exonware/xwsystem/src/exonware/xwsystem/validation/contracts.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 protocol interfaces for XWSystem.
|
|
10
11
|
"""
|
|
11
12
|
|
|
12
|
-
from
|
|
13
|
-
from typing import Any, Optional, Union, Iterator, Callable, Protocol
|
|
14
|
-
from typing_extensions import runtime_checkable
|
|
13
|
+
from typing import Any, Optional, Iterator, Callable, Protocol, runtime_checkable
|
|
15
14
|
|
|
16
15
|
# Import enums from types module
|
|
17
16
|
from .defs import (
|
|
@@ -25,14 +24,14 @@ from .defs import (
|
|
|
25
24
|
# VALIDATION INTERFACES
|
|
26
25
|
# ============================================================================
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
@runtime_checkable
|
|
28
|
+
class IValidatable(Protocol):
|
|
29
29
|
"""
|
|
30
30
|
Interface for objects that can be validated.
|
|
31
31
|
|
|
32
32
|
Enforces consistent validation behavior across XWSystem.
|
|
33
33
|
"""
|
|
34
34
|
|
|
35
|
-
@abstractmethod
|
|
36
35
|
def validate(self) -> bool:
|
|
37
36
|
"""
|
|
38
37
|
Validate this object.
|
|
@@ -40,9 +39,8 @@ class IValidatable(ABC):
|
|
|
40
39
|
Returns:
|
|
41
40
|
True if valid
|
|
42
41
|
"""
|
|
43
|
-
|
|
42
|
+
...
|
|
44
43
|
|
|
45
|
-
@abstractmethod
|
|
46
44
|
def is_valid(self) -> bool:
|
|
47
45
|
"""
|
|
48
46
|
Check if object is valid.
|
|
@@ -50,9 +48,8 @@ class IValidatable(ABC):
|
|
|
50
48
|
Returns:
|
|
51
49
|
True if valid
|
|
52
50
|
"""
|
|
53
|
-
|
|
51
|
+
...
|
|
54
52
|
|
|
55
|
-
@abstractmethod
|
|
56
53
|
def get_errors(self) -> list[str]:
|
|
57
54
|
"""
|
|
58
55
|
Get validation errors.
|
|
@@ -60,16 +57,14 @@ class IValidatable(ABC):
|
|
|
60
57
|
Returns:
|
|
61
58
|
List of error messages
|
|
62
59
|
"""
|
|
63
|
-
|
|
60
|
+
...
|
|
64
61
|
|
|
65
|
-
@abstractmethod
|
|
66
62
|
def clear_errors(self) -> None:
|
|
67
63
|
"""
|
|
68
64
|
Clear validation errors.
|
|
69
65
|
"""
|
|
70
|
-
|
|
66
|
+
...
|
|
71
67
|
|
|
72
|
-
@abstractmethod
|
|
73
68
|
def has_errors(self) -> bool:
|
|
74
69
|
"""
|
|
75
70
|
Check if object has validation errors.
|
|
@@ -77,9 +72,8 @@ class IValidatable(ABC):
|
|
|
77
72
|
Returns:
|
|
78
73
|
True if has errors
|
|
79
74
|
"""
|
|
80
|
-
|
|
75
|
+
...
|
|
81
76
|
|
|
82
|
-
@abstractmethod
|
|
83
77
|
def add_error(self, error: str) -> None:
|
|
84
78
|
"""
|
|
85
79
|
Add validation error.
|
|
@@ -87,21 +81,21 @@ class IValidatable(ABC):
|
|
|
87
81
|
Args:
|
|
88
82
|
error: Error message
|
|
89
83
|
"""
|
|
90
|
-
|
|
84
|
+
...
|
|
91
85
|
|
|
92
86
|
|
|
93
87
|
# ============================================================================
|
|
94
88
|
# VALIDATION MANAGER INTERFACES
|
|
95
89
|
# ============================================================================
|
|
96
90
|
|
|
97
|
-
|
|
91
|
+
@runtime_checkable
|
|
92
|
+
class IValidationManager(Protocol):
|
|
98
93
|
"""
|
|
99
94
|
Interface for validation management.
|
|
100
95
|
|
|
101
96
|
Enforces consistent validation management across XWSystem.
|
|
102
97
|
"""
|
|
103
98
|
|
|
104
|
-
@abstractmethod
|
|
105
99
|
def add_validator(self, name: str, validator: Callable[[Any], bool]) -> None:
|
|
106
100
|
"""
|
|
107
101
|
Add validator function.
|
|
@@ -110,9 +104,8 @@ class IValidationManager(ABC):
|
|
|
110
104
|
name: Validator name
|
|
111
105
|
validator: Validator function
|
|
112
106
|
"""
|
|
113
|
-
|
|
107
|
+
...
|
|
114
108
|
|
|
115
|
-
@abstractmethod
|
|
116
109
|
def remove_validator(self, name: str) -> bool:
|
|
117
110
|
"""
|
|
118
111
|
Remove validator.
|
|
@@ -123,9 +116,8 @@ class IValidationManager(ABC):
|
|
|
123
116
|
Returns:
|
|
124
117
|
True if removed
|
|
125
118
|
"""
|
|
126
|
-
|
|
119
|
+
...
|
|
127
120
|
|
|
128
|
-
@abstractmethod
|
|
129
121
|
def validate_object(self, obj: Any, validators: list[str]) -> tuple[bool, list[str]]:
|
|
130
122
|
"""
|
|
131
123
|
Validate object with specified validators.
|
|
@@ -137,9 +129,8 @@ class IValidationManager(ABC):
|
|
|
137
129
|
Returns:
|
|
138
130
|
Tuple of (is_valid, error_messages)
|
|
139
131
|
"""
|
|
140
|
-
|
|
132
|
+
...
|
|
141
133
|
|
|
142
|
-
@abstractmethod
|
|
143
134
|
def get_validators(self) -> list[str]:
|
|
144
135
|
"""
|
|
145
136
|
Get list of available validators.
|
|
@@ -147,21 +138,22 @@ class IValidationManager(ABC):
|
|
|
147
138
|
Returns:
|
|
148
139
|
List of validator names
|
|
149
140
|
"""
|
|
150
|
-
|
|
141
|
+
...
|
|
151
142
|
|
|
152
143
|
|
|
153
144
|
# ============================================================================
|
|
154
|
-
# SCHEMA VALIDATION INTERFACES
|
|
145
|
+
# SCHEMA VALIDATION INTERFACES (Schema Provider)
|
|
155
146
|
# ============================================================================
|
|
156
147
|
|
|
157
|
-
|
|
148
|
+
@runtime_checkable
|
|
149
|
+
class ISchemaProvider(Protocol):
|
|
158
150
|
"""
|
|
159
|
-
Interface for schema validation.
|
|
151
|
+
Interface for schema validation (schema provider).
|
|
160
152
|
|
|
161
153
|
Enforces consistent schema validation across XWSystem.
|
|
154
|
+
Implementations are discovered via entry point xwsystem.schema_validators.
|
|
162
155
|
"""
|
|
163
156
|
|
|
164
|
-
@abstractmethod
|
|
165
157
|
def validate_schema(self, data: Any, schema: dict[str, Any]) -> tuple[bool, list[str]]:
|
|
166
158
|
"""
|
|
167
159
|
Validate data against schema.
|
|
@@ -173,9 +165,8 @@ class ISchemaValidator(ABC):
|
|
|
173
165
|
Returns:
|
|
174
166
|
Tuple of (is_valid, error_messages)
|
|
175
167
|
"""
|
|
176
|
-
|
|
168
|
+
...
|
|
177
169
|
|
|
178
|
-
@abstractmethod
|
|
179
170
|
def create_schema(self, data: Any) -> dict[str, Any]:
|
|
180
171
|
"""
|
|
181
172
|
Create schema from data.
|
|
@@ -186,9 +177,8 @@ class ISchemaValidator(ABC):
|
|
|
186
177
|
Returns:
|
|
187
178
|
Schema definition
|
|
188
179
|
"""
|
|
189
|
-
|
|
180
|
+
...
|
|
190
181
|
|
|
191
|
-
@abstractmethod
|
|
192
182
|
def validate_type(self, data: Any, expected_type: str) -> bool:
|
|
193
183
|
"""
|
|
194
184
|
Validate data type.
|
|
@@ -200,9 +190,8 @@ class ISchemaValidator(ABC):
|
|
|
200
190
|
Returns:
|
|
201
191
|
True if type matches
|
|
202
192
|
"""
|
|
203
|
-
|
|
193
|
+
...
|
|
204
194
|
|
|
205
|
-
@abstractmethod
|
|
206
195
|
def validate_range(self, data: Any, min_value: Any, max_value: Any) -> bool:
|
|
207
196
|
"""
|
|
208
197
|
Validate data range.
|
|
@@ -215,9 +204,8 @@ class ISchemaValidator(ABC):
|
|
|
215
204
|
Returns:
|
|
216
205
|
True if in range
|
|
217
206
|
"""
|
|
218
|
-
|
|
207
|
+
...
|
|
219
208
|
|
|
220
|
-
@abstractmethod
|
|
221
209
|
def validate_pattern(self, data: str, pattern: str) -> bool:
|
|
222
210
|
"""
|
|
223
211
|
Validate string pattern.
|
|
@@ -229,7 +217,7 @@ class ISchemaValidator(ABC):
|
|
|
229
217
|
Returns:
|
|
230
218
|
True if pattern matches
|
|
231
219
|
"""
|
|
232
|
-
|
|
220
|
+
...
|
|
233
221
|
|
|
234
222
|
|
|
235
223
|
# ============================================================================
|
|
@@ -237,8 +225,8 @@ class ISchemaValidator(ABC):
|
|
|
237
225
|
# ============================================================================
|
|
238
226
|
|
|
239
227
|
@runtime_checkable
|
|
240
|
-
class
|
|
241
|
-
"""Protocol for objects that support data validation."""
|
|
228
|
+
class IValidatableSimple(Protocol):
|
|
229
|
+
"""Protocol for objects that support data validation (simpler interface than IValidatable)."""
|
|
242
230
|
|
|
243
231
|
def validate(self, data: Any, **kwargs: Any) -> bool:
|
|
244
232
|
"""Validate data against rules."""
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
#exonware/xwsystem/src/exonware/xwsystem/validation/declarative.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.3
|
|
6
7
|
Generation Date: September 04, 2025
|
|
7
8
|
|
|
8
9
|
Pydantic-style declarative validation with type hints and automatic coercion.
|
|
9
10
|
"""
|
|
10
11
|
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
11
14
|
import inspect
|
|
12
15
|
import json
|
|
13
16
|
from dataclasses import dataclass, field
|
|
@@ -58,10 +61,10 @@ class Field:
|
|
|
58
61
|
description: Optional[str] = None
|
|
59
62
|
|
|
60
63
|
# Validation constraints
|
|
61
|
-
gt: Optional[
|
|
62
|
-
ge: Optional[
|
|
63
|
-
lt: Optional[
|
|
64
|
-
le: Optional[
|
|
64
|
+
gt: Optional[int | float] = None # Greater than
|
|
65
|
+
ge: Optional[int | float] = None # Greater than or equal
|
|
66
|
+
lt: Optional[int | float] = None # Less than
|
|
67
|
+
le: Optional[int | float] = None # Less than or equal
|
|
65
68
|
min_length: Optional[int] = None
|
|
66
69
|
max_length: Optional[int] = None
|
|
67
70
|
pattern: Optional[str] = None
|
|
@@ -73,7 +76,7 @@ class Field:
|
|
|
73
76
|
to_upper: bool = False
|
|
74
77
|
|
|
75
78
|
# Advanced constraints
|
|
76
|
-
multiple_of: Optional[
|
|
79
|
+
multiple_of: Optional[int | float] = None
|
|
77
80
|
allow_inf_nan: bool = True
|
|
78
81
|
|
|
79
82
|
# Metadata
|
|
@@ -442,12 +445,12 @@ class XModel(metaclass=ModelMeta):
|
|
|
442
445
|
raise ValidationError(f"Value must be one of: {field_config.enum}")
|
|
443
446
|
|
|
444
447
|
@classmethod
|
|
445
|
-
def model_validate(cls, data: dict[str, Any]) ->
|
|
448
|
+
def model_validate(cls, data: dict[str, Any]) -> XModel:
|
|
446
449
|
"""Create and validate model from dictionary data."""
|
|
447
450
|
return cls(**data)
|
|
448
451
|
|
|
449
452
|
@classmethod
|
|
450
|
-
def model_validate_json(cls, json_data: str) ->
|
|
453
|
+
def model_validate_json(cls, json_data: str) -> XModel:
|
|
451
454
|
"""Create and validate model from JSON string."""
|
|
452
455
|
data = json.loads(json_data)
|
|
453
456
|
return cls.model_validate(data)
|