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,319 @@
|
|
|
1
|
+
#exonware/xwsystem/src/exonware/xwsystem/io/serialization/formats/tabular/csv.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 2025
|
|
8
|
+
|
|
9
|
+
CSV serialization - Comma-separated values format.
|
|
10
|
+
|
|
11
|
+
Following I→A→ATabular pattern:
|
|
12
|
+
- I: ISerialization (interface)
|
|
13
|
+
- A: ASerialization (abstract base)
|
|
14
|
+
- ATabular: ATabularSerialization (tabular base)
|
|
15
|
+
- Concrete: CsvSerializer
|
|
16
|
+
|
|
17
|
+
Moved from text/csv.py and updated to extend ATabularSerialization.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import csv
|
|
21
|
+
import io
|
|
22
|
+
from typing import Any, Optional
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
import pandas as pd
|
|
26
|
+
|
|
27
|
+
from .base import ATabularSerialization
|
|
28
|
+
from ....contracts import EncodeOptions, DecodeOptions
|
|
29
|
+
from ....defs import CodecCapability
|
|
30
|
+
from ....errors import SerializationError
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class CsvSerializer(ATabularSerialization):
|
|
34
|
+
"""
|
|
35
|
+
CSV serializer - follows the I→A→ATabular pattern.
|
|
36
|
+
|
|
37
|
+
I: ISerialization (interface)
|
|
38
|
+
A: ASerialization (abstract base)
|
|
39
|
+
ATabular: ATabularSerialization (tabular base)
|
|
40
|
+
Concrete: CsvSerializer
|
|
41
|
+
|
|
42
|
+
Uses Python's built-in csv module and pandas for DataFrame operations.
|
|
43
|
+
|
|
44
|
+
Examples:
|
|
45
|
+
>>> serializer = CsvSerializer()
|
|
46
|
+
>>>
|
|
47
|
+
>>> # Encode list of dicts
|
|
48
|
+
>>> csv_str = serializer.encode([
|
|
49
|
+
... {"name": "John", "age": 30},
|
|
50
|
+
... {"name": "Jane", "age": 25}
|
|
51
|
+
... ])
|
|
52
|
+
>>>
|
|
53
|
+
>>> # Decode to list of dicts
|
|
54
|
+
>>> data = serializer.decode(csv_str)
|
|
55
|
+
>>>
|
|
56
|
+
>>> # Convert DataFrame to CSV
|
|
57
|
+
>>> csv_str = serializer.from_df(df)
|
|
58
|
+
>>>
|
|
59
|
+
>>> # Convert CSV to DataFrame
|
|
60
|
+
>>> df = serializer.to_df(csv_str)
|
|
61
|
+
>>>
|
|
62
|
+
>>> # Save to file
|
|
63
|
+
>>> serializer.save_file(rows, "data.csv")
|
|
64
|
+
>>>
|
|
65
|
+
>>> # Load from file
|
|
66
|
+
>>> rows = serializer.load_file("data.csv")
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
# ========================================================================
|
|
70
|
+
# CODEC METADATA
|
|
71
|
+
# ========================================================================
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def codec_id(self) -> str:
|
|
75
|
+
return "csv"
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def media_types(self) -> list[str]:
|
|
79
|
+
return ["text/csv", "application/csv"]
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def file_extensions(self) -> list[str]:
|
|
83
|
+
return [".csv", ".tsv", ".psv"]
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def format_name(self) -> str:
|
|
87
|
+
return "CSV"
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def mime_type(self) -> str:
|
|
91
|
+
return "text/csv"
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def is_binary_format(self) -> bool:
|
|
95
|
+
return False # CSV is text-based
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def supports_streaming(self) -> bool:
|
|
99
|
+
return True # CSV naturally supports streaming (row by row)
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def capabilities(self) -> CodecCapability:
|
|
103
|
+
return CodecCapability.BIDIRECTIONAL
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def aliases(self) -> list[str]:
|
|
107
|
+
return ["csv", "CSV", "tsv", "TSV"]
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def codec_types(self) -> list[str]:
|
|
111
|
+
"""CSV is primarily a data exchange format."""
|
|
112
|
+
return ["data", "tabular"]
|
|
113
|
+
|
|
114
|
+
# ========================================================================
|
|
115
|
+
# CORE ENCODE/DECODE (Using csv module)
|
|
116
|
+
# ========================================================================
|
|
117
|
+
|
|
118
|
+
def encode(self, value: Any, *, options: Optional[EncodeOptions] = None) -> str:
|
|
119
|
+
"""
|
|
120
|
+
Encode data to CSV string.
|
|
121
|
+
|
|
122
|
+
Uses csv.DictWriter or csv.writer.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
value: Data to serialize (list of dicts, list of lists, or DataFrame)
|
|
126
|
+
options: CSV options (delimiter, quoting, etc.)
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
CSV string
|
|
130
|
+
|
|
131
|
+
Raises:
|
|
132
|
+
SerializationError: If encoding fails
|
|
133
|
+
"""
|
|
134
|
+
try:
|
|
135
|
+
# If DataFrame, use from_df
|
|
136
|
+
if isinstance(value, pd.DataFrame):
|
|
137
|
+
return self.from_df(value, **(options or {}))
|
|
138
|
+
|
|
139
|
+
opts = options or {}
|
|
140
|
+
|
|
141
|
+
# Get CSV options
|
|
142
|
+
delimiter = opts.get('delimiter', ',')
|
|
143
|
+
quoting = opts.get('quoting', csv.QUOTE_MINIMAL)
|
|
144
|
+
|
|
145
|
+
# Create string buffer
|
|
146
|
+
output = io.StringIO()
|
|
147
|
+
|
|
148
|
+
if isinstance(value, list) and value:
|
|
149
|
+
if isinstance(value[0], dict):
|
|
150
|
+
# List of dicts - use DictWriter
|
|
151
|
+
fieldnames = opts.get('fieldnames', list(value[0].keys()))
|
|
152
|
+
writer = csv.DictWriter(
|
|
153
|
+
output,
|
|
154
|
+
fieldnames=fieldnames,
|
|
155
|
+
delimiter=delimiter,
|
|
156
|
+
quoting=quoting
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Write header
|
|
160
|
+
if opts.get('header', True):
|
|
161
|
+
writer.writeheader()
|
|
162
|
+
|
|
163
|
+
# Write rows
|
|
164
|
+
writer.writerows(value)
|
|
165
|
+
else:
|
|
166
|
+
# List of lists - use regular writer
|
|
167
|
+
writer = csv.writer(
|
|
168
|
+
output,
|
|
169
|
+
delimiter=delimiter,
|
|
170
|
+
quoting=quoting
|
|
171
|
+
)
|
|
172
|
+
writer.writerows(value)
|
|
173
|
+
|
|
174
|
+
return output.getvalue()
|
|
175
|
+
|
|
176
|
+
except Exception as e:
|
|
177
|
+
raise SerializationError(
|
|
178
|
+
f"Failed to encode CSV: {e}",
|
|
179
|
+
format_name=self.format_name,
|
|
180
|
+
original_error=e
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def decode(self, repr: bytes | str, *, options: Optional[DecodeOptions] = None) -> Any:
|
|
184
|
+
"""
|
|
185
|
+
Decode CSV string to data.
|
|
186
|
+
|
|
187
|
+
Uses csv.DictReader or csv.reader.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
repr: CSV string (bytes or str)
|
|
191
|
+
options: CSV options (delimiter, has_header, etc.)
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
List of dicts (if header) or list of lists
|
|
195
|
+
|
|
196
|
+
Raises:
|
|
197
|
+
SerializationError: If decoding fails
|
|
198
|
+
"""
|
|
199
|
+
try:
|
|
200
|
+
# Convert bytes to str if needed
|
|
201
|
+
if isinstance(repr, bytes):
|
|
202
|
+
repr = repr.decode('utf-8')
|
|
203
|
+
|
|
204
|
+
opts = options or {}
|
|
205
|
+
|
|
206
|
+
# Get CSV options
|
|
207
|
+
delimiter = opts.get('delimiter', ',')
|
|
208
|
+
has_header = opts.get('header', True)
|
|
209
|
+
|
|
210
|
+
# Create string buffer
|
|
211
|
+
input_stream = io.StringIO(repr)
|
|
212
|
+
|
|
213
|
+
if has_header:
|
|
214
|
+
# Use DictReader for header-based CSV
|
|
215
|
+
reader = csv.DictReader(input_stream, delimiter=delimiter)
|
|
216
|
+
data = list(reader)
|
|
217
|
+
else:
|
|
218
|
+
# Use regular reader for headerless CSV
|
|
219
|
+
reader = csv.reader(input_stream, delimiter=delimiter)
|
|
220
|
+
data = list(reader)
|
|
221
|
+
|
|
222
|
+
return data
|
|
223
|
+
|
|
224
|
+
except (Exception, UnicodeDecodeError) as e:
|
|
225
|
+
raise SerializationError(
|
|
226
|
+
f"Failed to decode CSV: {e}",
|
|
227
|
+
format_name=self.format_name,
|
|
228
|
+
original_error=e
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# ========================================================================
|
|
232
|
+
# TABULAR METHODS (to_df/from_df)
|
|
233
|
+
# ========================================================================
|
|
234
|
+
|
|
235
|
+
def to_df(
|
|
236
|
+
self,
|
|
237
|
+
csv_content: bytes | str | Path,
|
|
238
|
+
sheet_name: Optional[str | list[str]] = None,
|
|
239
|
+
**options
|
|
240
|
+
) -> pd.DataFrame:
|
|
241
|
+
"""
|
|
242
|
+
Convert CSV content to DataFrame.
|
|
243
|
+
|
|
244
|
+
Note: CSV doesn't support multiple sheets, so sheet_name is ignored.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
csv_content: CSV content as bytes, string, or file path
|
|
248
|
+
sheet_name: Ignored (CSV doesn't support multiple sheets)
|
|
249
|
+
**options: Additional pandas read_csv options (delimiter, header, etc.)
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
DataFrame
|
|
253
|
+
|
|
254
|
+
Examples:
|
|
255
|
+
>>> serializer = CsvSerializer()
|
|
256
|
+
>>> df = serializer.to_df(csv_str)
|
|
257
|
+
>>> df = serializer.to_df("data.csv")
|
|
258
|
+
"""
|
|
259
|
+
try:
|
|
260
|
+
# Handle file path
|
|
261
|
+
if isinstance(csv_content, (str, Path)):
|
|
262
|
+
file_path = str(csv_content)
|
|
263
|
+
return pd.read_csv(file_path, **options)
|
|
264
|
+
|
|
265
|
+
# Handle bytes or string
|
|
266
|
+
if isinstance(csv_content, bytes):
|
|
267
|
+
csv_content = csv_content.decode('utf-8')
|
|
268
|
+
|
|
269
|
+
# Use StringIO for in-memory CSV
|
|
270
|
+
csv_io = io.StringIO(csv_content)
|
|
271
|
+
return pd.read_csv(csv_io, **options)
|
|
272
|
+
|
|
273
|
+
except Exception as e:
|
|
274
|
+
raise SerializationError(
|
|
275
|
+
f"Failed to convert CSV to DataFrame: {e}",
|
|
276
|
+
format_name=self.format_name,
|
|
277
|
+
original_error=e
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
def from_df(
|
|
281
|
+
self,
|
|
282
|
+
df: pd.DataFrame,
|
|
283
|
+
**options
|
|
284
|
+
) -> str:
|
|
285
|
+
"""
|
|
286
|
+
Convert DataFrame to CSV string.
|
|
287
|
+
|
|
288
|
+
Note: CSV doesn't support multiple sheets, so dict input is not supported.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
df: Single DataFrame
|
|
292
|
+
**options: Additional pandas to_csv options (index, delimiter, etc.)
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
CSV string
|
|
296
|
+
|
|
297
|
+
Examples:
|
|
298
|
+
>>> serializer = CsvSerializer()
|
|
299
|
+
>>> csv_str = serializer.from_df(df)
|
|
300
|
+
"""
|
|
301
|
+
try:
|
|
302
|
+
if isinstance(df, dict):
|
|
303
|
+
raise ValueError("CSV format does not support multiple sheets. Use a single DataFrame.")
|
|
304
|
+
|
|
305
|
+
# Use StringIO to capture CSV output
|
|
306
|
+
output = io.StringIO()
|
|
307
|
+
df.to_csv(
|
|
308
|
+
output,
|
|
309
|
+
index=options.get('index', False),
|
|
310
|
+
**{k: v for k, v in options.items() if k != 'index'}
|
|
311
|
+
)
|
|
312
|
+
return output.getvalue()
|
|
313
|
+
|
|
314
|
+
except Exception as e:
|
|
315
|
+
raise SerializationError(
|
|
316
|
+
f"Failed to convert DataFrame to CSV: {e}",
|
|
317
|
+
format_name=self.format_name,
|
|
318
|
+
original_error=e
|
|
319
|
+
)
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#exonware/xwsystem/src/exonware/xwsystem/io/serialization/formats/tabular/df.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 2025
|
|
8
|
+
|
|
9
|
+
DataFrame serialization - Direct pandas DataFrame operations.
|
|
10
|
+
|
|
11
|
+
Following I→A→ATabular pattern:
|
|
12
|
+
- I: ISerialization (interface)
|
|
13
|
+
- A: ASerialization (abstract base)
|
|
14
|
+
- ATabular: ATabularSerialization (tabular base)
|
|
15
|
+
- Concrete: DataFrameSerializer
|
|
16
|
+
|
|
17
|
+
Useful for intermediate DataFrame processing and format conversion.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from typing import Any, Optional
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
import pandas as pd
|
|
24
|
+
|
|
25
|
+
from .base import ATabularSerialization
|
|
26
|
+
from ....contracts import EncodeOptions, DecodeOptions
|
|
27
|
+
from ....defs import CodecCapability
|
|
28
|
+
from ....errors import SerializationError
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class DataFrameSerializer(ATabularSerialization):
|
|
32
|
+
"""
|
|
33
|
+
DataFrame serializer - follows the I→A→ATabular pattern.
|
|
34
|
+
|
|
35
|
+
I: ISerialization (interface)
|
|
36
|
+
A: ASerialization (abstract base)
|
|
37
|
+
ATabular: ATabularSerialization (tabular base)
|
|
38
|
+
Concrete: DataFrameSerializer
|
|
39
|
+
|
|
40
|
+
Direct DataFrame operations - useful for intermediate processing.
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
>>> serializer = DataFrameSerializer()
|
|
44
|
+
>>>
|
|
45
|
+
>>> # Convert DataFrame to bytes (pickle)
|
|
46
|
+
>>> df_bytes = serializer.from_df(df)
|
|
47
|
+
>>>
|
|
48
|
+
>>> # Convert bytes to DataFrame
|
|
49
|
+
>>> df = serializer.to_df(df_bytes)
|
|
50
|
+
>>>
|
|
51
|
+
>>> # Save DataFrame
|
|
52
|
+
>>> serializer.save_file(df, "data.pkl")
|
|
53
|
+
>>>
|
|
54
|
+
>>> # Load DataFrame
|
|
55
|
+
>>> df = serializer.load_file("data.pkl")
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
# ========================================================================
|
|
59
|
+
# CODEC METADATA
|
|
60
|
+
# ========================================================================
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def codec_id(self) -> str:
|
|
64
|
+
return "dataframe"
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def media_types(self) -> list[str]:
|
|
68
|
+
return [
|
|
69
|
+
"application/x-pandas-dataframe",
|
|
70
|
+
"application/x-pickle",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def file_extensions(self) -> list[str]:
|
|
75
|
+
return [".pkl", ".pickle", ".df"]
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def format_name(self) -> str:
|
|
79
|
+
return "DataFrame"
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def mime_type(self) -> str:
|
|
83
|
+
return "application/x-pandas-dataframe"
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def is_binary_format(self) -> bool:
|
|
87
|
+
return True # DataFrame pickle is binary
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def supports_streaming(self) -> bool:
|
|
91
|
+
return False # DataFrames don't naturally support streaming
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def capabilities(self) -> CodecCapability:
|
|
95
|
+
return CodecCapability.BIDIRECTIONAL
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def aliases(self) -> list[str]:
|
|
99
|
+
return ["dataframe", "DataFrame", "df", "pandas", "pkl"]
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def codec_types(self) -> list[str]:
|
|
103
|
+
"""DataFrame is an intermediate data format."""
|
|
104
|
+
return ["data", "tabular", "intermediate"]
|
|
105
|
+
|
|
106
|
+
# ========================================================================
|
|
107
|
+
# CORE ENCODE/DECODE (Using pickle)
|
|
108
|
+
# ========================================================================
|
|
109
|
+
|
|
110
|
+
def encode(self, value: Any, *, options: Optional[EncodeOptions] = None) -> bytes:
|
|
111
|
+
"""
|
|
112
|
+
Encode DataFrame to pickle bytes.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
value: DataFrame or dict of {sheet_name: DataFrame}
|
|
116
|
+
options: Pickle options (protocol, etc.)
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Pickled DataFrame as bytes
|
|
120
|
+
|
|
121
|
+
Raises:
|
|
122
|
+
SerializationError: If encoding fails
|
|
123
|
+
"""
|
|
124
|
+
try:
|
|
125
|
+
import pickle
|
|
126
|
+
|
|
127
|
+
opts = options or {}
|
|
128
|
+
protocol = opts.get('protocol', pickle.HIGHEST_PROTOCOL)
|
|
129
|
+
|
|
130
|
+
return pickle.dumps(value, protocol=protocol)
|
|
131
|
+
|
|
132
|
+
except Exception as e:
|
|
133
|
+
raise SerializationError(
|
|
134
|
+
f"Failed to encode DataFrame: {e}",
|
|
135
|
+
format_name=self.format_name,
|
|
136
|
+
original_error=e
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def decode(self, repr: bytes | str, *, options: Optional[DecodeOptions] = None) -> Any:
|
|
140
|
+
"""
|
|
141
|
+
Decode pickle bytes to DataFrame(s).
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
repr: Pickled DataFrame as bytes, or file path
|
|
145
|
+
options: Decode options
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
DataFrame or dict of {sheet_name: DataFrame}
|
|
149
|
+
|
|
150
|
+
Raises:
|
|
151
|
+
SerializationError: If decoding fails
|
|
152
|
+
"""
|
|
153
|
+
try:
|
|
154
|
+
import pickle
|
|
155
|
+
|
|
156
|
+
# Handle file path
|
|
157
|
+
if isinstance(repr, (str, Path)):
|
|
158
|
+
with open(repr, 'rb') as f:
|
|
159
|
+
return pickle.load(f)
|
|
160
|
+
|
|
161
|
+
# Handle bytes
|
|
162
|
+
if isinstance(repr, str):
|
|
163
|
+
repr = repr.encode('utf-8')
|
|
164
|
+
|
|
165
|
+
return pickle.loads(repr)
|
|
166
|
+
|
|
167
|
+
except Exception as e:
|
|
168
|
+
raise SerializationError(
|
|
169
|
+
f"Failed to decode DataFrame: {e}",
|
|
170
|
+
format_name=self.format_name,
|
|
171
|
+
original_error=e
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# ========================================================================
|
|
175
|
+
# TABULAR METHODS (to_df/from_df)
|
|
176
|
+
# ========================================================================
|
|
177
|
+
|
|
178
|
+
def to_df(
|
|
179
|
+
self,
|
|
180
|
+
data: bytes | str | Path,
|
|
181
|
+
sheet_name: Optional[str | list[str]] = None,
|
|
182
|
+
**options
|
|
183
|
+
) -> pd.DataFrame | dict[str, pd.DataFrame]:
|
|
184
|
+
"""
|
|
185
|
+
Convert pickled data to DataFrame(s).
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
data: Pickled DataFrame as bytes, or file path
|
|
189
|
+
sheet_name: Ignored (pickle preserves structure)
|
|
190
|
+
**options: Decode options
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
DataFrame or dict of {sheet_name: DataFrame} (as stored)
|
|
194
|
+
|
|
195
|
+
Examples:
|
|
196
|
+
>>> serializer = DataFrameSerializer()
|
|
197
|
+
>>> df = serializer.to_df(pickled_bytes)
|
|
198
|
+
>>> df = serializer.to_df("data.pkl")
|
|
199
|
+
"""
|
|
200
|
+
try:
|
|
201
|
+
# Use decode method
|
|
202
|
+
result = self.decode(data, **(options or {}))
|
|
203
|
+
|
|
204
|
+
# If sheet_name specified and result is dict, filter
|
|
205
|
+
if sheet_name is not None and isinstance(result, dict):
|
|
206
|
+
if isinstance(sheet_name, str):
|
|
207
|
+
return result.get(sheet_name)
|
|
208
|
+
else:
|
|
209
|
+
return {name: result.get(name) for name in sheet_name if name in result}
|
|
210
|
+
|
|
211
|
+
return result
|
|
212
|
+
|
|
213
|
+
except Exception as e:
|
|
214
|
+
raise SerializationError(
|
|
215
|
+
f"Failed to convert to DataFrame: {e}",
|
|
216
|
+
format_name=self.format_name,
|
|
217
|
+
original_error=e
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
def from_df(
|
|
221
|
+
self,
|
|
222
|
+
df: pd.DataFrame | dict[str, pd.DataFrame],
|
|
223
|
+
**options
|
|
224
|
+
) -> bytes:
|
|
225
|
+
"""
|
|
226
|
+
Convert DataFrame(s) to pickle bytes.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
df: Single DataFrame or dict of {sheet_name: DataFrame}
|
|
230
|
+
**options: Pickle options (protocol, etc.)
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Pickled DataFrame as bytes
|
|
234
|
+
|
|
235
|
+
Examples:
|
|
236
|
+
>>> serializer = DataFrameSerializer()
|
|
237
|
+
>>> bytes = serializer.from_df(df)
|
|
238
|
+
>>> bytes = serializer.from_df({"Sheet1": df1, "Sheet2": df2})
|
|
239
|
+
"""
|
|
240
|
+
try:
|
|
241
|
+
# Use encode method
|
|
242
|
+
return self.encode(df, **(options or {}))
|
|
243
|
+
|
|
244
|
+
except Exception as e:
|
|
245
|
+
raise SerializationError(
|
|
246
|
+
f"Failed to convert DataFrame: {e}",
|
|
247
|
+
format_name=self.format_name,
|
|
248
|
+
original_error=e
|
|
249
|
+
)
|