exonware-xwsystem 0.1.0.1__py3-none-any.whl → 0.1.0.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. exonware/__init__.py +2 -1
  2. exonware/conf.py +2 -2
  3. exonware/xwsystem/__init__.py +115 -43
  4. exonware/xwsystem/base.py +30 -0
  5. exonware/xwsystem/caching/__init__.py +39 -13
  6. exonware/xwsystem/caching/base.py +24 -6
  7. exonware/xwsystem/caching/bloom_cache.py +2 -2
  8. exonware/xwsystem/caching/cache_manager.py +2 -1
  9. exonware/xwsystem/caching/conditional.py +2 -2
  10. exonware/xwsystem/caching/contracts.py +85 -139
  11. exonware/xwsystem/caching/decorators.py +6 -19
  12. exonware/xwsystem/caching/defs.py +2 -1
  13. exonware/xwsystem/caching/disk_cache.py +2 -1
  14. exonware/xwsystem/caching/distributed.py +2 -1
  15. exonware/xwsystem/caching/errors.py +2 -1
  16. exonware/xwsystem/caching/events.py +110 -27
  17. exonware/xwsystem/caching/eviction_strategies.py +2 -2
  18. exonware/xwsystem/caching/external_caching_python.py +701 -0
  19. exonware/xwsystem/caching/facade.py +253 -0
  20. exonware/xwsystem/caching/factory.py +300 -0
  21. exonware/xwsystem/caching/fluent.py +14 -12
  22. exonware/xwsystem/caching/integrity.py +21 -6
  23. exonware/xwsystem/caching/lfu_cache.py +2 -1
  24. exonware/xwsystem/caching/lfu_optimized.py +18 -6
  25. exonware/xwsystem/caching/lru_cache.py +7 -4
  26. exonware/xwsystem/caching/memory_bounded.py +2 -2
  27. exonware/xwsystem/caching/metrics_exporter.py +2 -2
  28. exonware/xwsystem/caching/observable_cache.py +2 -2
  29. exonware/xwsystem/caching/pluggable_cache.py +2 -2
  30. exonware/xwsystem/caching/rate_limiter.py +2 -2
  31. exonware/xwsystem/caching/read_through.py +2 -2
  32. exonware/xwsystem/caching/secure_cache.py +81 -28
  33. exonware/xwsystem/caching/serializable.py +9 -7
  34. exonware/xwsystem/caching/stats.py +2 -2
  35. exonware/xwsystem/caching/tagging.py +2 -2
  36. exonware/xwsystem/caching/ttl_cache.py +4 -3
  37. exonware/xwsystem/caching/two_tier_cache.py +6 -3
  38. exonware/xwsystem/caching/utils.py +30 -12
  39. exonware/xwsystem/caching/validation.py +2 -2
  40. exonware/xwsystem/caching/warming.py +6 -3
  41. exonware/xwsystem/caching/write_behind.py +15 -6
  42. exonware/xwsystem/config/__init__.py +11 -17
  43. exonware/xwsystem/config/base.py +5 -5
  44. exonware/xwsystem/config/contracts.py +93 -153
  45. exonware/xwsystem/config/defaults.py +3 -2
  46. exonware/xwsystem/config/defs.py +3 -2
  47. exonware/xwsystem/config/errors.py +2 -5
  48. exonware/xwsystem/config/logging.py +12 -8
  49. exonware/xwsystem/config/logging_setup.py +3 -2
  50. exonware/xwsystem/config/performance.py +1 -46
  51. exonware/xwsystem/config/performance_modes.py +9 -8
  52. exonware/xwsystem/config/version_manager.py +1 -0
  53. exonware/xwsystem/config.py +27 -0
  54. exonware/xwsystem/console/__init__.py +53 -0
  55. exonware/xwsystem/console/base.py +133 -0
  56. exonware/xwsystem/console/cli/__init__.py +61 -0
  57. exonware/xwsystem/{cli → console/cli}/args.py +27 -24
  58. exonware/xwsystem/{cli → console/cli}/base.py +18 -87
  59. exonware/xwsystem/{cli → console/cli}/colors.py +15 -13
  60. exonware/xwsystem/console/cli/console.py +98 -0
  61. exonware/xwsystem/{cli → console/cli}/contracts.py +51 -69
  62. exonware/xwsystem/console/cli/defs.py +87 -0
  63. exonware/xwsystem/console/cli/encoding.py +69 -0
  64. exonware/xwsystem/{cli → console/cli}/errors.py +8 -3
  65. exonware/xwsystem/console/cli/event_logger.py +166 -0
  66. exonware/xwsystem/{cli → console/cli}/progress.py +25 -21
  67. exonware/xwsystem/{cli → console/cli}/prompts.py +3 -2
  68. exonware/xwsystem/{cli → console/cli}/tables.py +27 -24
  69. exonware/xwsystem/console/contracts.py +113 -0
  70. exonware/xwsystem/console/defs.py +154 -0
  71. exonware/xwsystem/console/errors.py +34 -0
  72. exonware/xwsystem/console/event_logger.py +385 -0
  73. exonware/xwsystem/console/writer.py +132 -0
  74. exonware/xwsystem/contracts.py +28 -0
  75. exonware/xwsystem/data_structures/__init__.py +23 -0
  76. exonware/xwsystem/data_structures/trie.py +34 -0
  77. exonware/xwsystem/data_structures/union_find.py +144 -0
  78. exonware/xwsystem/defs.py +17 -0
  79. exonware/xwsystem/errors.py +23 -0
  80. exonware/xwsystem/facade.py +62 -0
  81. exonware/xwsystem/http_client/__init__.py +22 -1
  82. exonware/xwsystem/http_client/advanced_client.py +8 -5
  83. exonware/xwsystem/http_client/base.py +3 -2
  84. exonware/xwsystem/http_client/client.py +7 -4
  85. exonware/xwsystem/http_client/contracts.py +42 -56
  86. exonware/xwsystem/http_client/defs.py +2 -1
  87. exonware/xwsystem/http_client/errors.py +2 -1
  88. exonware/xwsystem/http_client/facade.py +156 -0
  89. exonware/xwsystem/io/__init__.py +22 -3
  90. exonware/xwsystem/io/archive/__init__.py +8 -2
  91. exonware/xwsystem/io/archive/archive.py +1 -1
  92. exonware/xwsystem/io/archive/archive_files.py +4 -7
  93. exonware/xwsystem/io/archive/archivers.py +120 -10
  94. exonware/xwsystem/io/archive/base.py +4 -5
  95. exonware/xwsystem/io/archive/codec_integration.py +1 -2
  96. exonware/xwsystem/io/archive/compression.py +1 -2
  97. exonware/xwsystem/io/archive/facade.py +263 -0
  98. exonware/xwsystem/io/archive/formats/__init__.py +2 -3
  99. exonware/xwsystem/io/archive/formats/brotli_format.py +20 -7
  100. exonware/xwsystem/io/archive/formats/lz4_format.py +20 -7
  101. exonware/xwsystem/io/archive/formats/rar.py +11 -5
  102. exonware/xwsystem/io/archive/formats/sevenzip.py +12 -6
  103. exonware/xwsystem/io/archive/formats/squashfs_format.py +1 -2
  104. exonware/xwsystem/io/archive/formats/tar.py +52 -7
  105. exonware/xwsystem/io/archive/formats/wim_format.py +11 -5
  106. exonware/xwsystem/io/archive/formats/zip.py +1 -2
  107. exonware/xwsystem/io/archive/formats/zpaq_format.py +1 -2
  108. exonware/xwsystem/io/archive/formats/zstandard.py +20 -7
  109. exonware/xwsystem/io/base.py +119 -115
  110. exonware/xwsystem/io/codec/__init__.py +4 -2
  111. exonware/xwsystem/io/codec/base.py +19 -13
  112. exonware/xwsystem/io/codec/contracts.py +59 -2
  113. exonware/xwsystem/io/codec/registry.py +67 -21
  114. exonware/xwsystem/io/common/__init__.py +1 -1
  115. exonware/xwsystem/io/common/atomic.py +29 -16
  116. exonware/xwsystem/io/common/base.py +11 -10
  117. exonware/xwsystem/io/common/lock.py +6 -5
  118. exonware/xwsystem/io/common/path_manager.py +2 -1
  119. exonware/xwsystem/io/common/watcher.py +1 -2
  120. exonware/xwsystem/io/contracts.py +301 -433
  121. exonware/xwsystem/io/contracts_1.py +1180 -0
  122. exonware/xwsystem/io/data_operations.py +19 -20
  123. exonware/xwsystem/io/defs.py +4 -3
  124. exonware/xwsystem/io/errors.py +3 -2
  125. exonware/xwsystem/io/facade.py +87 -61
  126. exonware/xwsystem/io/file/__init__.py +1 -1
  127. exonware/xwsystem/io/file/base.py +8 -9
  128. exonware/xwsystem/io/file/conversion.py +2 -3
  129. exonware/xwsystem/io/file/file.py +61 -18
  130. exonware/xwsystem/io/file/paged_source.py +8 -8
  131. exonware/xwsystem/io/file/paging/__init__.py +1 -2
  132. exonware/xwsystem/io/file/paging/byte_paging.py +4 -5
  133. exonware/xwsystem/io/file/paging/line_paging.py +2 -3
  134. exonware/xwsystem/io/file/paging/record_paging.py +2 -3
  135. exonware/xwsystem/io/file/paging/registry.py +1 -2
  136. exonware/xwsystem/io/file/source.py +13 -17
  137. exonware/xwsystem/io/filesystem/__init__.py +1 -1
  138. exonware/xwsystem/io/filesystem/base.py +1 -2
  139. exonware/xwsystem/io/filesystem/local.py +3 -4
  140. exonware/xwsystem/io/folder/__init__.py +1 -1
  141. exonware/xwsystem/io/folder/base.py +1 -2
  142. exonware/xwsystem/io/folder/folder.py +16 -7
  143. exonware/xwsystem/io/indexing/__init__.py +14 -0
  144. exonware/xwsystem/io/indexing/facade.py +443 -0
  145. exonware/xwsystem/io/path_parser.py +98 -0
  146. exonware/xwsystem/io/serialization/__init__.py +21 -3
  147. exonware/xwsystem/io/serialization/auto_serializer.py +146 -20
  148. exonware/xwsystem/io/serialization/base.py +84 -34
  149. exonware/xwsystem/io/serialization/contracts.py +50 -73
  150. exonware/xwsystem/io/serialization/defs.py +2 -1
  151. exonware/xwsystem/io/serialization/errors.py +2 -1
  152. exonware/xwsystem/io/serialization/flyweight.py +154 -7
  153. exonware/xwsystem/io/serialization/format_detector.py +15 -14
  154. exonware/xwsystem/io/serialization/formats/__init__.py +8 -5
  155. exonware/xwsystem/io/serialization/formats/binary/bson.py +15 -6
  156. exonware/xwsystem/io/serialization/formats/binary/cbor.py +5 -5
  157. exonware/xwsystem/io/serialization/formats/binary/marshal.py +5 -5
  158. exonware/xwsystem/io/serialization/formats/binary/msgpack.py +5 -5
  159. exonware/xwsystem/io/serialization/formats/binary/pickle.py +5 -5
  160. exonware/xwsystem/io/serialization/formats/binary/plistlib.py +5 -5
  161. exonware/xwsystem/io/serialization/formats/database/dbm.py +7 -7
  162. exonware/xwsystem/io/serialization/formats/database/shelve.py +7 -7
  163. exonware/xwsystem/io/serialization/formats/database/sqlite3.py +7 -7
  164. exonware/xwsystem/io/serialization/formats/tabular/__init__.py +27 -0
  165. exonware/xwsystem/io/serialization/formats/tabular/base.py +89 -0
  166. exonware/xwsystem/io/serialization/formats/tabular/csv.py +319 -0
  167. exonware/xwsystem/io/serialization/formats/tabular/df.py +249 -0
  168. exonware/xwsystem/io/serialization/formats/tabular/excel.py +291 -0
  169. exonware/xwsystem/io/serialization/formats/tabular/googlesheets.py +374 -0
  170. exonware/xwsystem/io/serialization/formats/text/__init__.py +1 -1
  171. exonware/xwsystem/io/serialization/formats/text/append_only_log.py +5 -7
  172. exonware/xwsystem/io/serialization/formats/text/configparser.py +5 -5
  173. exonware/xwsystem/io/serialization/formats/text/csv.py +7 -5
  174. exonware/xwsystem/io/serialization/formats/text/formdata.py +5 -5
  175. exonware/xwsystem/io/serialization/formats/text/json.py +27 -18
  176. exonware/xwsystem/io/serialization/formats/text/json5.py +8 -4
  177. exonware/xwsystem/io/serialization/formats/text/jsonlines.py +18 -14
  178. exonware/xwsystem/io/serialization/formats/text/multipart.py +5 -5
  179. exonware/xwsystem/io/serialization/formats/text/toml.py +8 -6
  180. exonware/xwsystem/io/serialization/formats/text/xml.py +25 -20
  181. exonware/xwsystem/io/serialization/formats/text/yaml.py +8 -6
  182. exonware/xwsystem/io/serialization/parsers/__init__.py +3 -2
  183. exonware/xwsystem/io/serialization/parsers/base.py +6 -5
  184. exonware/xwsystem/io/serialization/parsers/hybrid_parser.py +7 -6
  185. exonware/xwsystem/io/serialization/parsers/msgspec_parser.py +10 -7
  186. exonware/xwsystem/io/serialization/parsers/orjson_direct_parser.py +7 -6
  187. exonware/xwsystem/io/serialization/parsers/orjson_parser.py +11 -8
  188. exonware/xwsystem/io/serialization/parsers/pysimdjson_parser.py +13 -9
  189. exonware/xwsystem/io/serialization/parsers/rapidjson_parser.py +10 -7
  190. exonware/xwsystem/io/serialization/parsers/registry.py +11 -10
  191. exonware/xwsystem/io/serialization/parsers/standard.py +7 -6
  192. exonware/xwsystem/io/serialization/parsers/ujson_parser.py +10 -7
  193. exonware/xwsystem/io/serialization/registry.py +4 -4
  194. exonware/xwsystem/io/serialization/serializer.py +168 -79
  195. exonware/xwsystem/io/serialization/universal_options.py +367 -0
  196. exonware/xwsystem/io/serialization/utils/__init__.py +1 -2
  197. exonware/xwsystem/io/serialization/utils/path_ops.py +5 -6
  198. exonware/xwsystem/io/source_reader.py +223 -0
  199. exonware/xwsystem/io/stream/__init__.py +1 -1
  200. exonware/xwsystem/io/stream/async_operations.py +61 -14
  201. exonware/xwsystem/io/stream/base.py +1 -2
  202. exonware/xwsystem/io/stream/codec_io.py +6 -7
  203. exonware/xwsystem/ipc/__init__.py +1 -0
  204. exonware/xwsystem/ipc/async_fabric.py +4 -4
  205. exonware/xwsystem/ipc/base.py +6 -5
  206. exonware/xwsystem/ipc/contracts.py +41 -66
  207. exonware/xwsystem/ipc/defs.py +2 -1
  208. exonware/xwsystem/ipc/errors.py +2 -1
  209. exonware/xwsystem/ipc/message_queue.py +5 -2
  210. exonware/xwsystem/ipc/pipes.py +70 -34
  211. exonware/xwsystem/ipc/process_manager.py +7 -5
  212. exonware/xwsystem/ipc/process_pool.py +6 -5
  213. exonware/xwsystem/ipc/shared_memory.py +64 -11
  214. exonware/xwsystem/monitoring/__init__.py +7 -0
  215. exonware/xwsystem/monitoring/base.py +11 -8
  216. exonware/xwsystem/monitoring/contracts.py +86 -144
  217. exonware/xwsystem/monitoring/defs.py +2 -1
  218. exonware/xwsystem/monitoring/error_recovery.py +16 -3
  219. exonware/xwsystem/monitoring/errors.py +2 -1
  220. exonware/xwsystem/monitoring/facade.py +183 -0
  221. exonware/xwsystem/monitoring/memory_monitor.py +1 -0
  222. exonware/xwsystem/monitoring/metrics.py +1 -0
  223. exonware/xwsystem/monitoring/performance_manager_generic.py +7 -7
  224. exonware/xwsystem/monitoring/performance_monitor.py +1 -0
  225. exonware/xwsystem/monitoring/performance_validator.py +1 -0
  226. exonware/xwsystem/monitoring/system_monitor.py +6 -5
  227. exonware/xwsystem/monitoring/tracing.py +18 -16
  228. exonware/xwsystem/monitoring/tracker.py +2 -1
  229. exonware/xwsystem/operations/__init__.py +5 -50
  230. exonware/xwsystem/operations/base.py +3 -44
  231. exonware/xwsystem/operations/contracts.py +25 -15
  232. exonware/xwsystem/operations/defs.py +1 -1
  233. exonware/xwsystem/operations/diff.py +5 -4
  234. exonware/xwsystem/operations/errors.py +1 -1
  235. exonware/xwsystem/operations/merge.py +6 -4
  236. exonware/xwsystem/operations/patch.py +5 -4
  237. exonware/xwsystem/patterns/__init__.py +1 -0
  238. exonware/xwsystem/patterns/base.py +2 -1
  239. exonware/xwsystem/patterns/context_manager.py +2 -1
  240. exonware/xwsystem/patterns/contracts.py +215 -256
  241. exonware/xwsystem/patterns/defs.py +2 -1
  242. exonware/xwsystem/patterns/dynamic_facade.py +1 -0
  243. exonware/xwsystem/patterns/errors.py +2 -4
  244. exonware/xwsystem/patterns/handler_factory.py +2 -3
  245. exonware/xwsystem/patterns/import_registry.py +1 -0
  246. exonware/xwsystem/patterns/object_pool.py +1 -0
  247. exonware/xwsystem/patterns/registry.py +4 -43
  248. exonware/xwsystem/plugins/__init__.py +2 -1
  249. exonware/xwsystem/plugins/base.py +6 -5
  250. exonware/xwsystem/plugins/contracts.py +94 -158
  251. exonware/xwsystem/plugins/defs.py +2 -1
  252. exonware/xwsystem/plugins/errors.py +2 -1
  253. exonware/xwsystem/py.typed +3 -0
  254. exonware/xwsystem/query/__init__.py +36 -0
  255. exonware/xwsystem/query/contracts.py +56 -0
  256. exonware/xwsystem/query/errors.py +22 -0
  257. exonware/xwsystem/query/registry.py +128 -0
  258. exonware/xwsystem/runtime/__init__.py +2 -1
  259. exonware/xwsystem/runtime/base.py +4 -3
  260. exonware/xwsystem/runtime/contracts.py +39 -60
  261. exonware/xwsystem/runtime/defs.py +2 -1
  262. exonware/xwsystem/runtime/env.py +11 -9
  263. exonware/xwsystem/runtime/errors.py +2 -1
  264. exonware/xwsystem/runtime/reflection.py +3 -2
  265. exonware/xwsystem/security/__init__.py +68 -11
  266. exonware/xwsystem/security/audit.py +167 -0
  267. exonware/xwsystem/security/base.py +121 -24
  268. exonware/xwsystem/security/contracts.py +91 -146
  269. exonware/xwsystem/security/crypto.py +17 -16
  270. exonware/xwsystem/security/defs.py +2 -1
  271. exonware/xwsystem/security/errors.py +2 -1
  272. exonware/xwsystem/security/facade.py +321 -0
  273. exonware/xwsystem/security/file_security.py +330 -0
  274. exonware/xwsystem/security/hazmat.py +11 -8
  275. exonware/xwsystem/security/monitor.py +372 -0
  276. exonware/xwsystem/security/path_validator.py +140 -18
  277. exonware/xwsystem/security/policy.py +357 -0
  278. exonware/xwsystem/security/resource_limits.py +1 -0
  279. exonware/xwsystem/security/validator.py +455 -0
  280. exonware/xwsystem/shared/__init__.py +14 -1
  281. exonware/xwsystem/shared/base.py +285 -2
  282. exonware/xwsystem/shared/contracts.py +415 -126
  283. exonware/xwsystem/shared/defs.py +2 -1
  284. exonware/xwsystem/shared/errors.py +2 -2
  285. exonware/xwsystem/shared/xwobject.py +316 -0
  286. exonware/xwsystem/structures/__init__.py +1 -0
  287. exonware/xwsystem/structures/base.py +3 -2
  288. exonware/xwsystem/structures/circular_detector.py +15 -14
  289. exonware/xwsystem/structures/contracts.py +53 -76
  290. exonware/xwsystem/structures/defs.py +2 -1
  291. exonware/xwsystem/structures/errors.py +2 -1
  292. exonware/xwsystem/structures/tree_walker.py +2 -1
  293. exonware/xwsystem/threading/__init__.py +21 -4
  294. exonware/xwsystem/threading/async_primitives.py +6 -5
  295. exonware/xwsystem/threading/base.py +3 -2
  296. exonware/xwsystem/threading/contracts.py +87 -143
  297. exonware/xwsystem/threading/defs.py +2 -1
  298. exonware/xwsystem/threading/errors.py +2 -1
  299. exonware/xwsystem/threading/facade.py +175 -0
  300. exonware/xwsystem/threading/locks.py +1 -0
  301. exonware/xwsystem/threading/safe_factory.py +1 -0
  302. exonware/xwsystem/utils/__init__.py +40 -0
  303. exonware/xwsystem/utils/base.py +22 -21
  304. exonware/xwsystem/utils/contracts.py +50 -73
  305. exonware/xwsystem/utils/dt/__init__.py +19 -3
  306. exonware/xwsystem/utils/dt/base.py +5 -4
  307. exonware/xwsystem/utils/dt/contracts.py +22 -29
  308. exonware/xwsystem/utils/dt/defs.py +2 -1
  309. exonware/xwsystem/utils/dt/errors.py +2 -5
  310. exonware/xwsystem/utils/dt/formatting.py +88 -2
  311. exonware/xwsystem/utils/dt/humanize.py +10 -9
  312. exonware/xwsystem/utils/dt/parsing.py +56 -5
  313. exonware/xwsystem/utils/dt/timezone_utils.py +2 -24
  314. exonware/xwsystem/utils/errors.py +2 -4
  315. exonware/xwsystem/utils/paths.py +1 -0
  316. exonware/xwsystem/utils/string.py +49 -0
  317. exonware/xwsystem/utils/test_runner.py +185 -0
  318. exonware/xwsystem/utils/utils_contracts.py +2 -1
  319. exonware/xwsystem/utils/web.py +110 -0
  320. exonware/xwsystem/validation/__init__.py +25 -1
  321. exonware/xwsystem/validation/base.py +6 -5
  322. exonware/xwsystem/validation/contracts.py +29 -41
  323. exonware/xwsystem/validation/data_validator.py +1 -0
  324. exonware/xwsystem/validation/declarative.py +11 -8
  325. exonware/xwsystem/validation/defs.py +2 -1
  326. exonware/xwsystem/validation/errors.py +2 -1
  327. exonware/xwsystem/validation/facade.py +198 -0
  328. exonware/xwsystem/validation/fluent_validator.py +22 -19
  329. exonware/xwsystem/validation/schema_discovery.py +210 -0
  330. exonware/xwsystem/validation/type_safety.py +2 -1
  331. exonware/xwsystem/version.py +2 -2
  332. {exonware_xwsystem-0.1.0.1.dist-info → exonware_xwsystem-0.1.0.4.dist-info}/METADATA +71 -4
  333. exonware_xwsystem-0.1.0.4.dist-info/RECORD +337 -0
  334. exonware/xwsystem/cli/__init__.py +0 -43
  335. exonware/xwsystem/cli/console.py +0 -113
  336. exonware/xwsystem/cli/defs.py +0 -134
  337. exonware/xwsystem/conf.py +0 -44
  338. exonware/xwsystem/security/auth.py +0 -484
  339. exonware_xwsystem-0.1.0.1.dist-info/RECORD +0 -284
  340. {exonware_xwsystem-0.1.0.1.dist-info → exonware_xwsystem-0.1.0.4.dist-info}/WHEEL +0 -0
  341. {exonware_xwsystem-0.1.0.1.dist-info → exonware_xwsystem-0.1.0.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,291 @@
1
+ #exonware/xwsystem/src/exonware/xwsystem/io/serialization/formats/tabular/excel.py
2
+ """
3
+ Company: eXonware.com
4
+ Author: Eng. Muhammad AlShehri
5
+ Email: connect@exonware.com
6
+ Version: 0.1.0.4
7
+ Generation Date: January 2025
8
+
9
+ Excel serialization - .xlsx, .xls format.
10
+
11
+ Following I→A→XW pattern:
12
+ - I: ISerialization (interface)
13
+ - A: ASerialization (abstract base)
14
+ - ATabular: ATabularSerialization (tabular base)
15
+ - Concrete: ExcelSerializer
16
+ """
17
+
18
+ import io
19
+ from typing import Any, Optional
20
+ from pathlib import Path
21
+
22
+ import pandas as pd
23
+
24
+ from .base import ATabularSerialization
25
+ from ....contracts import EncodeOptions, DecodeOptions
26
+ from ....defs import CodecCapability
27
+ from ....errors import SerializationError
28
+
29
+
30
+ class ExcelSerializer(ATabularSerialization):
31
+ """
32
+ Excel serializer - follows the I→A→ATabular pattern.
33
+
34
+ I: ISerialization (interface)
35
+ A: ASerialization (abstract base)
36
+ ATabular: ATabularSerialization (tabular base)
37
+ Concrete: ExcelSerializer
38
+
39
+ Supports .xlsx and .xls formats via pandas/openpyxl/xlrd.
40
+
41
+ Examples:
42
+ >>> serializer = ExcelSerializer()
43
+ >>>
44
+ >>> # Convert Excel bytes to DataFrame
45
+ >>> df = serializer.to_df(excel_bytes)
46
+ >>>
47
+ >>> # Convert DataFrame to Excel bytes
48
+ >>> excel_bytes = serializer.from_df(df)
49
+ >>>
50
+ >>> # Save to file
51
+ >>> serializer.save_file(df, "data.xlsx")
52
+ >>>
53
+ >>> # Load from file
54
+ >>> df = serializer.load_file("data.xlsx")
55
+ """
56
+
57
+ # ========================================================================
58
+ # CODEC METADATA
59
+ # ========================================================================
60
+
61
+ @property
62
+ def codec_id(self) -> str:
63
+ return "excel"
64
+
65
+ @property
66
+ def media_types(self) -> list[str]:
67
+ return [
68
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", # .xlsx
69
+ "application/vnd.ms-excel", # .xls
70
+ ]
71
+
72
+ @property
73
+ def file_extensions(self) -> list[str]:
74
+ return [".xlsx", ".xls"]
75
+
76
+ @property
77
+ def format_name(self) -> str:
78
+ return "Excel"
79
+
80
+ @property
81
+ def mime_type(self) -> str:
82
+ return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
83
+
84
+ @property
85
+ def is_binary_format(self) -> bool:
86
+ return True # Excel is binary
87
+
88
+ @property
89
+ def supports_streaming(self) -> bool:
90
+ return False # Excel doesn't naturally support streaming
91
+
92
+ @property
93
+ def capabilities(self) -> CodecCapability:
94
+ return CodecCapability.BIDIRECTIONAL
95
+
96
+ @property
97
+ def aliases(self) -> list[str]:
98
+ return ["excel", "Excel", "xlsx", "XLSX", "xls", "XLS"]
99
+
100
+ @property
101
+ def codec_types(self) -> list[str]:
102
+ """Excel is a data exchange format."""
103
+ return ["data", "tabular"]
104
+
105
+ # ========================================================================
106
+ # CORE ENCODE/DECODE (Using pandas)
107
+ # ========================================================================
108
+
109
+ def encode(self, value: Any, *, options: Optional[EncodeOptions] = None) -> bytes:
110
+ """
111
+ Encode data to Excel bytes.
112
+
113
+ Args:
114
+ value: DataFrame or dict of {sheet_name: DataFrame}
115
+ options: Excel options (engine, sheet_name, etc.)
116
+
117
+ Returns:
118
+ Excel file as bytes
119
+
120
+ Raises:
121
+ SerializationError: If encoding fails
122
+ """
123
+ try:
124
+ # If value is already a DataFrame or dict, use from_df
125
+ if isinstance(value, (pd.DataFrame, dict)):
126
+ return self.from_df(value, **(options or {}))
127
+
128
+ # Otherwise, try to serialize as-is (fallback)
129
+ raise SerializationError(
130
+ f"Excel encoding requires DataFrame or dict of DataFrames, got {type(value)}",
131
+ format_name=self.format_name
132
+ )
133
+
134
+ except Exception as e:
135
+ raise SerializationError(
136
+ f"Failed to encode Excel: {e}",
137
+ format_name=self.format_name,
138
+ original_error=e
139
+ )
140
+
141
+ def decode(self, repr: bytes | str, *, options: Optional[DecodeOptions] = None) -> Any:
142
+ """
143
+ Decode Excel bytes to DataFrame(s).
144
+
145
+ Args:
146
+ repr: Excel file as bytes
147
+ options: Excel options (sheet_name, engine, etc.)
148
+
149
+ Returns:
150
+ DataFrame or dict of {sheet_name: DataFrame}
151
+
152
+ Raises:
153
+ SerializationError: If decoding fails
154
+ """
155
+ try:
156
+ if isinstance(repr, str):
157
+ # If string, treat as file path
158
+ return self.load_file(repr, **(options or {}))
159
+
160
+ # Use to_df for bytes
161
+ return self.to_df(repr, **(options or {}))
162
+
163
+ except Exception as e:
164
+ raise SerializationError(
165
+ f"Failed to decode Excel: {e}",
166
+ format_name=self.format_name,
167
+ original_error=e
168
+ )
169
+
170
+ # ========================================================================
171
+ # TABULAR METHODS (to_df/from_df)
172
+ # ========================================================================
173
+
174
+ def to_df(
175
+ self,
176
+ excel_content: bytes | str | Path,
177
+ sheet_name: Optional[str | list[str]] = None,
178
+ **options
179
+ ) -> pd.DataFrame | dict[str, pd.DataFrame]:
180
+ """
181
+ Convert Excel content to DataFrame(s).
182
+
183
+ Args:
184
+ excel_content: Excel file as bytes, file path, or Path object
185
+ sheet_name: Specific sheet name(s) to load, or None for all sheets
186
+ **options: Additional pandas read_excel options (engine, header, etc.)
187
+
188
+ Returns:
189
+ Single DataFrame if one sheet, or dict of {sheet_name: DataFrame} if multiple
190
+
191
+ Examples:
192
+ >>> serializer = ExcelSerializer()
193
+ >>> df = serializer.to_df(excel_bytes) # Single sheet
194
+ >>> sheets = serializer.to_df(excel_bytes) # All sheets (dict)
195
+ >>> df = serializer.to_df(excel_bytes, sheet_name="Sheet1") # Specific sheet
196
+ """
197
+ try:
198
+ # Handle file path
199
+ if isinstance(excel_content, (str, Path)):
200
+ file_path = str(excel_content)
201
+ if sheet_name is None:
202
+ # Read all sheets
203
+ return pd.read_excel(file_path, sheet_name=None, **options)
204
+ else:
205
+ # Read specific sheet(s)
206
+ return pd.read_excel(file_path, sheet_name=sheet_name, **options)
207
+
208
+ # Handle bytes
209
+ if not excel_content:
210
+ raise ValueError("Excel content cannot be empty")
211
+
212
+ excel_io = io.BytesIO(excel_content)
213
+
214
+ # Determine engine based on options or file extension
215
+ engine = options.get('engine', 'openpyxl') # Default to openpyxl for .xlsx
216
+
217
+ if sheet_name is None:
218
+ # Read all sheets
219
+ return pd.read_excel(excel_io, sheet_name=None, engine=engine, **{k: v for k, v in options.items() if k != 'engine'})
220
+ else:
221
+ # Read specific sheet(s)
222
+ result = pd.read_excel(excel_io, sheet_name=sheet_name, engine=engine, **{k: v for k, v in options.items() if k != 'engine'})
223
+
224
+ # If single sheet name, return DataFrame directly
225
+ if isinstance(sheet_name, str):
226
+ return result
227
+ # If list of sheet names, result is already a dict
228
+ return result
229
+
230
+ except Exception as e:
231
+ raise SerializationError(
232
+ f"Failed to convert Excel to DataFrame: {e}",
233
+ format_name=self.format_name,
234
+ original_error=e
235
+ )
236
+
237
+ def from_df(
238
+ self,
239
+ df: pd.DataFrame | dict[str, pd.DataFrame],
240
+ **options
241
+ ) -> bytes:
242
+ """
243
+ Convert DataFrame(s) to Excel bytes.
244
+
245
+ Args:
246
+ df: Single DataFrame or dict of {sheet_name: DataFrame}
247
+ **options: Additional pandas to_excel options (engine, index, etc.)
248
+
249
+ Returns:
250
+ Excel file as bytes
251
+
252
+ Examples:
253
+ >>> serializer = ExcelSerializer()
254
+ >>> excel_bytes = serializer.from_df(df) # Single sheet
255
+ >>> excel_bytes = serializer.from_df({"Sheet1": df1, "Sheet2": df2}) # Multiple sheets
256
+ """
257
+ try:
258
+ excel_io = io.BytesIO()
259
+
260
+ # Determine engine
261
+ engine = options.get('engine', 'openpyxl') # Default to openpyxl
262
+
263
+ if isinstance(df, dict):
264
+ # Multiple sheets
265
+ with pd.ExcelWriter(excel_io, engine=engine) as writer:
266
+ for sheet_name, sheet_df in df.items():
267
+ sheet_df.to_excel(
268
+ writer,
269
+ sheet_name=sheet_name,
270
+ index=options.get('index', False),
271
+ **{k: v for k, v in options.items() if k not in ('engine', 'index')}
272
+ )
273
+ else:
274
+ # Single sheet
275
+ df.to_excel(
276
+ excel_io,
277
+ engine=engine,
278
+ index=options.get('index', False),
279
+ sheet_name=options.get('sheet_name', 'Sheet1'),
280
+ **{k: v for k, v in options.items() if k not in ('engine', 'index', 'sheet_name')}
281
+ )
282
+
283
+ excel_io.seek(0)
284
+ return excel_io.read()
285
+
286
+ except Exception as e:
287
+ raise SerializationError(
288
+ f"Failed to convert DataFrame to Excel: {e}",
289
+ format_name=self.format_name,
290
+ original_error=e
291
+ )
@@ -0,0 +1,374 @@
1
+ #exonware/xwsystem/src/exonware/xwsystem/io/serialization/formats/tabular/googlesheets.py
2
+ """
3
+ Company: eXonware.com
4
+ Author: Eng. Muhammad AlShehri
5
+ Email: connect@exonware.com
6
+ Version: 0.1.0.4
7
+ Generation Date: January 2025
8
+
9
+ Google Sheets serialization.
10
+
11
+ Following I→A→ATabular pattern:
12
+ - I: ISerialization (interface)
13
+ - A: ASerialization (abstract base)
14
+ - ATabular: ATabularSerialization (tabular base)
15
+ - Concrete: GoogleSheetsSerializer
16
+
17
+ Note: For local saving, convert to Excel or CSV first.
18
+ For cloud saving, use xwstorage (avoids circular dependencies).
19
+ """
20
+
21
+ from typing import Any, Optional
22
+ from pathlib import Path
23
+
24
+ import pandas as pd
25
+
26
+ from .base import ATabularSerialization
27
+ from ....contracts import EncodeOptions, DecodeOptions
28
+ from ....defs import CodecCapability
29
+ from ....errors import SerializationError
30
+
31
+
32
+ class GoogleSheetsSerializer(ATabularSerialization):
33
+ """
34
+ Google Sheets serializer - follows the I→A→ATabular pattern.
35
+
36
+ I: ISerialization (interface)
37
+ A: ASerialization (abstract base)
38
+ ATabular: ATabularSerialization (tabular base)
39
+ Concrete: GoogleSheetsSerializer
40
+
41
+ Uses gspread library for Google Sheets API access.
42
+
43
+ Local saving: Convert to Excel or CSV first, then save.
44
+ Cloud saving: Use xwstorage (avoids circular dependencies).
45
+
46
+ Examples:
47
+ >>> serializer = GoogleSheetsSerializer(credentials_path="path/to/credentials.json")
48
+ >>>
49
+ >>> # Read from Google Sheets
50
+ >>> df = serializer.to_df(spreadsheet_url, sheet_name="Sheet1")
51
+ >>> sheets = serializer.to_df(spreadsheet_url) # All sheets
52
+ >>>
53
+ >>> # Write to Google Sheets
54
+ >>> serializer.from_df(df, spreadsheet_url, sheet_name="Sheet1")
55
+ >>> serializer.from_df({"Sheet1": df1, "Sheet2": df2}, spreadsheet_url) # Multiple sheets
56
+ """
57
+
58
+ def __init__(
59
+ self,
60
+ credentials_path: Optional[str | Path] = None,
61
+ max_depth: Optional[int] = None,
62
+ max_size_mb: Optional[float] = None
63
+ ):
64
+ """
65
+ Initialize Google Sheets serializer.
66
+
67
+ Args:
68
+ credentials_path: Path to Google service account credentials JSON file
69
+ max_depth: Maximum nesting depth (inherited from ASerialization)
70
+ max_size_mb: Maximum size in MB (inherited from ASerialization)
71
+ """
72
+ super().__init__(max_depth=max_depth, max_size_mb=max_size_mb)
73
+ self._credentials_path = str(credentials_path) if credentials_path else None
74
+ self._client = None
75
+
76
+ def _get_client(self):
77
+ """Get or create gspread client (lazy initialization)."""
78
+ if self._client is None:
79
+ import importlib.util
80
+ _gspread_spec = importlib.util.find_spec('gspread')
81
+ _oauth2client_spec = importlib.util.find_spec('oauth2client')
82
+
83
+ if _gspread_spec is None or _oauth2client_spec is None:
84
+ raise ImportError(
85
+ "gspread and oauth2client are required for Google Sheets support. "
86
+ "Install with: pip install gspread oauth2client"
87
+ )
88
+
89
+ import gspread
90
+ from oauth2client.service_account import ServiceAccountCredentials
91
+
92
+ if not self._credentials_path:
93
+ raise ValueError("credentials_path is required for Google Sheets access")
94
+
95
+ scope = 'https://www.googleapis.com/auth/spreadsheets'
96
+ creds = ServiceAccountCredentials.from_json_keyfile_name(
97
+ self._credentials_path, scope
98
+ )
99
+ self._client = gspread.authorize(creds)
100
+
101
+ return self._client
102
+
103
+ # ========================================================================
104
+ # CODEC METADATA
105
+ # ========================================================================
106
+
107
+ @property
108
+ def codec_id(self) -> str:
109
+ return "googlesheets"
110
+
111
+ @property
112
+ def media_types(self) -> list[str]:
113
+ return ["application/vnd.google-apps.spreadsheet"]
114
+
115
+ @property
116
+ def file_extensions(self) -> list[str]:
117
+ return [] # Google Sheets don't have local file extensions
118
+
119
+ @property
120
+ def format_name(self) -> str:
121
+ return "Google Sheets"
122
+
123
+ @property
124
+ def mime_type(self) -> str:
125
+ return "application/vnd.google-apps.spreadsheet"
126
+
127
+ @property
128
+ def is_binary_format(self) -> bool:
129
+ return False # Google Sheets API uses JSON
130
+
131
+ @property
132
+ def supports_streaming(self) -> bool:
133
+ return False # Google Sheets API doesn't support streaming
134
+
135
+ @property
136
+ def capabilities(self) -> CodecCapability:
137
+ return CodecCapability.BIDIRECTIONAL
138
+
139
+ @property
140
+ def aliases(self) -> list[str]:
141
+ return ["googlesheets", "GoogleSheets", "gsheet", "gsheets"]
142
+
143
+ @property
144
+ def codec_types(self) -> list[str]:
145
+ """Google Sheets is a cloud data format."""
146
+ return ["data", "tabular", "cloud"]
147
+
148
+ # ========================================================================
149
+ # CORE ENCODE/DECODE
150
+ # ========================================================================
151
+
152
+ def encode(self, value: Any, *, options: Optional[EncodeOptions] = None) -> str:
153
+ """
154
+ Encode data to Google Sheets format.
155
+
156
+ Note: This is a placeholder. Use from_df() with spreadsheet_url instead.
157
+
158
+ Args:
159
+ value: DataFrame or dict of DataFrames
160
+ options: Options including spreadsheet_url and sheet_name
161
+
162
+ Returns:
163
+ Spreadsheet URL (as string representation)
164
+
165
+ Raises:
166
+ SerializationError: If encoding fails
167
+ """
168
+ try:
169
+ opts = options or {}
170
+ spreadsheet_url = opts.get('spreadsheet_url')
171
+ if not spreadsheet_url:
172
+ raise ValueError("spreadsheet_url is required in options")
173
+
174
+ # Use from_df for actual encoding
175
+ if isinstance(value, (pd.DataFrame, dict)):
176
+ self.from_df(value, spreadsheet_url=spreadsheet_url, **(opts or {}))
177
+ return spreadsheet_url
178
+
179
+ raise ValueError(f"Google Sheets encoding requires DataFrame or dict, got {type(value)}")
180
+
181
+ except Exception as e:
182
+ raise SerializationError(
183
+ f"Failed to encode Google Sheets: {e}",
184
+ format_name=self.format_name,
185
+ original_error=e
186
+ )
187
+
188
+ def decode(self, repr: bytes | str, *, options: Optional[DecodeOptions] = None) -> Any:
189
+ """
190
+ Decode Google Sheets to DataFrame(s).
191
+
192
+ Args:
193
+ repr: Spreadsheet URL (as string)
194
+ options: Options including sheet_name
195
+
196
+ Returns:
197
+ DataFrame or dict of {sheet_name: DataFrame}
198
+
199
+ Raises:
200
+ SerializationError: If decoding fails
201
+ """
202
+ try:
203
+ if isinstance(repr, bytes):
204
+ repr = repr.decode('utf-8')
205
+
206
+ opts = options or {}
207
+ sheet_name = opts.get('sheet_name')
208
+
209
+ return self.to_df(repr, sheet_name=sheet_name, **(opts or {}))
210
+
211
+ except Exception as e:
212
+ raise SerializationError(
213
+ f"Failed to decode Google Sheets: {e}",
214
+ format_name=self.format_name,
215
+ original_error=e
216
+ )
217
+
218
+ # ========================================================================
219
+ # TABULAR METHODS (to_df/from_df)
220
+ # ========================================================================
221
+
222
+ def to_df(
223
+ self,
224
+ spreadsheet_url: str,
225
+ sheet_name: Optional[str | list[str]] = None,
226
+ header: int = 0,
227
+ **options
228
+ ) -> pd.DataFrame | dict[str, pd.DataFrame]:
229
+ """
230
+ Convert Google Sheet to DataFrame(s).
231
+
232
+ Args:
233
+ spreadsheet_url: Google Sheets URL
234
+ sheet_name: Specific sheet name(s) to load, or None for all sheets
235
+ header: Row index to use as header (default: 0)
236
+ **options: Additional options
237
+
238
+ Returns:
239
+ Single DataFrame if one sheet, or dict of {sheet_name: DataFrame} if multiple
240
+
241
+ Examples:
242
+ >>> serializer = GoogleSheetsSerializer(credentials_path="creds.json")
243
+ >>> df = serializer.to_df(spreadsheet_url, sheet_name="Sheet1")
244
+ >>> sheets = serializer.to_df(spreadsheet_url) # All sheets
245
+ """
246
+ try:
247
+ from gspread_dataframe import get_as_dataframe
248
+
249
+ client = self._get_client()
250
+ spreadsheet = client.open_by_url(spreadsheet_url)
251
+
252
+ if sheet_name is None:
253
+ # Read all sheets
254
+ result = {}
255
+ for worksheet in spreadsheet.worksheets():
256
+ try:
257
+ df = get_as_dataframe(worksheet, header=header, **options)
258
+ result[worksheet.title] = df
259
+ except Exception as e:
260
+ # Skip sheets that can't be read
261
+ continue
262
+ return result
263
+
264
+ elif isinstance(sheet_name, str):
265
+ # Read single sheet
266
+ try:
267
+ worksheet = spreadsheet.worksheet(sheet_name)
268
+ return get_as_dataframe(worksheet, header=header, **options)
269
+ except Exception as e:
270
+ raise SerializationError(
271
+ f"Failed to read sheet '{sheet_name}': {e}",
272
+ format_name=self.format_name,
273
+ original_error=e
274
+ )
275
+
276
+ else:
277
+ # Read multiple specific sheets
278
+ result = {}
279
+ for name in sheet_name:
280
+ try:
281
+ worksheet = spreadsheet.worksheet(name)
282
+ df = get_as_dataframe(worksheet, header=header, **options)
283
+ result[name] = df
284
+ except Exception as e:
285
+ # Skip sheets that can't be read
286
+ continue
287
+ return result
288
+
289
+ except Exception as e:
290
+ raise SerializationError(
291
+ f"Failed to convert Google Sheet to DataFrame: {e}",
292
+ format_name=self.format_name,
293
+ original_error=e
294
+ )
295
+
296
+ def from_df(
297
+ self,
298
+ df: pd.DataFrame | dict[str, pd.DataFrame],
299
+ spreadsheet_url: str,
300
+ sheet_name: Optional[str] = None,
301
+ **options
302
+ ) -> None:
303
+ """
304
+ Convert DataFrame(s) to Google Sheet.
305
+
306
+ Note: This writes directly to Google Sheets (cloud).
307
+ For local saving, convert to Excel or CSV first.
308
+
309
+ Args:
310
+ df: Single DataFrame or dict of {sheet_name: DataFrame}
311
+ spreadsheet_url: Google Sheets URL
312
+ sheet_name: Sheet name (required if df is single DataFrame)
313
+ **options: Additional options
314
+
315
+ Examples:
316
+ >>> serializer = GoogleSheetsSerializer(credentials_path="creds.json")
317
+ >>> serializer.from_df(df, spreadsheet_url, sheet_name="Sheet1")
318
+ >>> serializer.from_df({"Sheet1": df1, "Sheet2": df2}, spreadsheet_url)
319
+ """
320
+ try:
321
+ from gspread_dataframe import set_with_dataframe
322
+
323
+ client = self._get_client()
324
+ spreadsheet = client.open_by_url(spreadsheet_url)
325
+
326
+ if isinstance(df, dict):
327
+ # Multiple sheets
328
+ for name, sheet_df in df.items():
329
+ try:
330
+ try:
331
+ worksheet = spreadsheet.worksheet(name)
332
+ worksheet.clear()
333
+ except Exception:
334
+ # Create worksheet if it doesn't exist
335
+ worksheet = spreadsheet.add_worksheet(
336
+ title=name, rows="2000", cols="30"
337
+ )
338
+
339
+ set_with_dataframe(worksheet, sheet_df, **options)
340
+ except Exception as e:
341
+ raise SerializationError(
342
+ f"Failed to write sheet '{name}': {e}",
343
+ format_name=self.format_name,
344
+ original_error=e
345
+ )
346
+ else:
347
+ # Single sheet
348
+ if not sheet_name:
349
+ raise ValueError("sheet_name is required when writing a single DataFrame")
350
+
351
+ try:
352
+ try:
353
+ worksheet = spreadsheet.worksheet(sheet_name)
354
+ worksheet.clear()
355
+ except Exception:
356
+ # Create worksheet if it doesn't exist
357
+ worksheet = spreadsheet.add_worksheet(
358
+ title=sheet_name, rows="2000", cols="30"
359
+ )
360
+
361
+ set_with_dataframe(worksheet, df, **options)
362
+ except Exception as e:
363
+ raise SerializationError(
364
+ f"Failed to write sheet '{sheet_name}': {e}",
365
+ format_name=self.format_name,
366
+ original_error=e
367
+ )
368
+
369
+ except Exception as e:
370
+ raise SerializationError(
371
+ f"Failed to convert DataFrame to Google Sheet: {e}",
372
+ format_name=self.format_name,
373
+ original_error=e
374
+ )
@@ -1,3 +1,4 @@
1
+ #exonware/xwsystem/src/exonware/xwsystem/io/serialization/formats/text/__init__.py
1
2
  """Text-based serialization formats."""
2
3
 
3
4
  from .json import JsonSerializer
@@ -24,4 +25,3 @@ __all__ = [
24
25
  "FormDataSerializer",
25
26
  "MultipartSerializer",
26
27
  ]
27
-