py-wlcli 0.8.0__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 (320) hide show
  1. py_wlcli-0.8.0.dist-info/METADATA +139 -0
  2. py_wlcli-0.8.0.dist-info/RECORD +320 -0
  3. py_wlcli-0.8.0.dist-info/WHEEL +4 -0
  4. py_wlcli-0.8.0.dist-info/entry_points.txt +2 -0
  5. py_wlcli-0.8.0.dist-info/licenses/LICENSE +21 -0
  6. wl_cli/__init__.py +27 -0
  7. wl_cli/__main__.py +130 -0
  8. wl_cli/capabilities.py +79 -0
  9. wl_cli/cc/__init__.py +5 -0
  10. wl_cli/cc/checks.py +1019 -0
  11. wl_cli/cc/cli.py +370 -0
  12. wl_cli/cc/mutate.py +1350 -0
  13. wl_cli/cli_click.py +156 -0
  14. wl_cli/commands/__init__.py +84 -0
  15. wl_cli/commands/base.py +80 -0
  16. wl_cli/commands/buildcommands/__init__.py +1 -0
  17. wl_cli/commands/buildcommands/build.py +71 -0
  18. wl_cli/commands/buildcommands/build_utils.py +47 -0
  19. wl_cli/commands/buildcommands/builders/__init__.py +18 -0
  20. wl_cli/commands/buildcommands/builders/cleanup.py +35 -0
  21. wl_cli/commands/buildcommands/builders/coordinator.py +122 -0
  22. wl_cli/commands/buildcommands/builders/environment.py +81 -0
  23. wl_cli/commands/buildcommands/builders/rust_builder.py +39 -0
  24. wl_cli/commands/buildcommands/builders/stubs.py +58 -0
  25. wl_cli/commands/buildcommands/dist.py +326 -0
  26. wl_cli/commands/buildcommands/test.py +89 -0
  27. wl_cli/commands/clean/__init__.py +1 -0
  28. wl_cli/commands/clean/build.py +151 -0
  29. wl_cli/commands/clean/clean.py +294 -0
  30. wl_cli/commands/clean/clean_utils.py +77 -0
  31. wl_cli/commands/clean/core.py +236 -0
  32. wl_cli/commands/clean/dead_code/__init__.py +0 -0
  33. wl_cli/commands/clean/dead_code/dead_code_command.py +70 -0
  34. wl_cli/commands/clean/dead_code/detector.py +161 -0
  35. wl_cli/commands/clean/dead_code/fixer.py +176 -0
  36. wl_cli/commands/clean/dead_code/reporter.py +295 -0
  37. wl_cli/commands/clean/dead_code/utils.py +0 -0
  38. wl_cli/commands/clean/lfs.py +215 -0
  39. wl_cli/commands/clean/python.py +81 -0
  40. wl_cli/commands/clean/rust.py +91 -0
  41. wl_cli/commands/clean/utils/__init__.py +34 -0
  42. wl_cli/commands/clean/venv.py +91 -0
  43. wl_cli/commands/cleanup.py +73 -0
  44. wl_cli/commands/click_base.py +79 -0
  45. wl_cli/commands/config/__init__.py +1 -0
  46. wl_cli/commands/config/config.py +291 -0
  47. wl_cli/commands/debug/__init__.py +6 -0
  48. wl_cli/commands/debug/debug.py +309 -0
  49. wl_cli/commands/debug/debug_cache.py +86 -0
  50. wl_cli/commands/debug/debug_subagent.py +156 -0
  51. wl_cli/commands/debug/output_formatter.py +165 -0
  52. wl_cli/commands/debug/source_hasher.py +92 -0
  53. wl_cli/commands/format/__init__.py +13 -0
  54. wl_cli/commands/format/format_cache_adapter.py +125 -0
  55. wl_cli/commands/format/format_command.py +197 -0
  56. wl_cli/commands/format/format_coordinator.py +191 -0
  57. wl_cli/commands/format/format_executor.py +470 -0
  58. wl_cli/commands/format/format_outcome.py +44 -0
  59. wl_cli/commands/format/format_path_handler.py +1016 -0
  60. wl_cli/commands/format/format_progress.py +62 -0
  61. wl_cli/commands/format/format_reporter.py +31 -0
  62. wl_cli/commands/format/python_formatter.py +506 -0
  63. wl_cli/commands/format/rust_formatter.py +106 -0
  64. wl_cli/commands/gate/__init__.py +1 -0
  65. wl_cli/commands/gate/gate_command.py +862 -0
  66. wl_cli/commands/gate/preflight.py +445 -0
  67. wl_cli/commands/git/__init__.py +1 -0
  68. wl_cli/commands/git/config.py +169 -0
  69. wl_cli/commands/git/git_facts.py +60 -0
  70. wl_cli/commands/git/git_flow.py +86 -0
  71. wl_cli/commands/git/hook.py +112 -0
  72. wl_cli/commands/git/policy.py +216 -0
  73. wl_cli/commands/git/push_update.py +82 -0
  74. wl_cli/commands/initenv/__init__.py +1 -0
  75. wl_cli/commands/initenv/common/__init__.py +23 -0
  76. wl_cli/commands/initenv/common/check_git_bash.py +78 -0
  77. wl_cli/commands/initenv/common/error_handler.py +68 -0
  78. wl_cli/commands/initenv/common/exceptions.py +52 -0
  79. wl_cli/commands/initenv/components/__init__.py +4 -0
  80. wl_cli/commands/initenv/components/cargo_sync.py +240 -0
  81. wl_cli/commands/initenv/components/config_manager.py +106 -0
  82. wl_cli/commands/initenv/components/directory_utils.py +71 -0
  83. wl_cli/commands/initenv/components/git/__init__.py +9 -0
  84. wl_cli/commands/initenv/components/git/branch_creator.py +143 -0
  85. wl_cli/commands/initenv/components/git/git_flow_interface.py +24 -0
  86. wl_cli/commands/initenv/components/git/git_flow_manager.py +159 -0
  87. wl_cli/commands/initenv/components/git/git_lfs_manager.py +272 -0
  88. wl_cli/commands/initenv/components/git/git_utility.py +254 -0
  89. wl_cli/commands/initenv/components/git/gitignore_manager.py +53 -0
  90. wl_cli/commands/initenv/components/git_initializer.py +212 -0
  91. wl_cli/commands/initenv/components/i18n_manager.py +104 -0
  92. wl_cli/commands/initenv/components/log_manager.py +241 -0
  93. wl_cli/commands/initenv/components/project_structure/__init__.py +23 -0
  94. wl_cli/commands/initenv/components/project_structure/config_generator.py +360 -0
  95. wl_cli/commands/initenv/components/project_structure/directory_creator.py +184 -0
  96. wl_cli/commands/initenv/components/project_structure/hook_source.py +110 -0
  97. wl_cli/commands/initenv/components/project_structure/hooks_manager.py +995 -0
  98. wl_cli/commands/initenv/components/project_structure/readme_handler.py +56 -0
  99. wl_cli/commands/initenv/components/project_structure/rust_files_handler.py +30 -0
  100. wl_cli/commands/initenv/components/project_structure/setup_handler.py +103 -0
  101. wl_cli/commands/initenv/components/project_structure/sop_generator.py +73 -0
  102. wl_cli/commands/initenv/components/project_structure/template_copier.py +104 -0
  103. wl_cli/commands/initenv/components/project_structure/template_manager.py +195 -0
  104. wl_cli/commands/initenv/components/pyproject_generator.py +194 -0
  105. wl_cli/commands/initenv/components/rust_initializer.py +145 -0
  106. wl_cli/commands/initenv/components/symlink_cleaner.py +99 -0
  107. wl_cli/commands/initenv/components/symlink_manager.py +52 -0
  108. wl_cli/commands/initenv/components/venv_manager.py +118 -0
  109. wl_cli/commands/initenv/initenv.py +169 -0
  110. wl_cli/commands/initenv/services/__init__.py +11 -0
  111. wl_cli/commands/initenv/services/environment_initializer.py +288 -0
  112. wl_cli/commands/initenv/services/git_lfs_initializer.py +26 -0
  113. wl_cli/commands/initenv/services/project_structure_initializer.py +52 -0
  114. wl_cli/commands/lfs/README.md +221 -0
  115. wl_cli/commands/lfs/__init__.py +5 -0
  116. wl_cli/commands/lfs/cli.py +106 -0
  117. wl_cli/commands/lfs/config.py +164 -0
  118. wl_cli/commands/lfs/hook.py +61 -0
  119. wl_cli/commands/lfs/initializer.py +531 -0
  120. wl_cli/commands/lfs/manager.py +95 -0
  121. wl_cli/commands/lfs/policy.py +165 -0
  122. wl_cli/commands/lfs/watcher.py +294 -0
  123. wl_cli/commands/lint/__init__.py +1 -0
  124. wl_cli/commands/lint/lint_command.py +297 -0
  125. wl_cli/commands/lint/lint_executor.py +409 -0
  126. wl_cli/commands/lint/lint_reporter.py +579 -0
  127. wl_cli/commands/lint/lint_run.py +258 -0
  128. wl_cli/commands/lint/mypy_config.py +90 -0
  129. wl_cli/commands/registry.py +103 -0
  130. wl_cli/commands/self/__init__.py +7 -0
  131. wl_cli/commands/self/common/__init__.py +8 -0
  132. wl_cli/commands/self/common/capabilities.py +30 -0
  133. wl_cli/commands/self/common/config.py +27 -0
  134. wl_cli/commands/self/common/version_cache.py +85 -0
  135. wl_cli/commands/self/handlers/__init__.py +19 -0
  136. wl_cli/commands/self/handlers/base_handler.py +28 -0
  137. wl_cli/commands/self/handlers/install_handler.py +85 -0
  138. wl_cli/commands/self/handlers/status_handler.py +66 -0
  139. wl_cli/commands/self/handlers/uninstall_handler.py +58 -0
  140. wl_cli/commands/self/handlers/update_handler.py +102 -0
  141. wl_cli/commands/self/handlers/version_handler.py +16 -0
  142. wl_cli/commands/self/self_command.py +237 -0
  143. wl_cli/commands/self/services/__init__.py +15 -0
  144. wl_cli/commands/self/services/installation_manager.py +187 -0
  145. wl_cli/commands/self/services/local_installer.py +165 -0
  146. wl_cli/commands/self/services/pypi_updater.py +278 -0
  147. wl_cli/commands/self/services/windows_update.py +174 -0
  148. wl_cli/commands/testcommands/__init__.py +8 -0
  149. wl_cli/commands/testcommands/base_dependency_analyzer.py +198 -0
  150. wl_cli/commands/testcommands/batch_processor.py +278 -0
  151. wl_cli/commands/testcommands/cache_manager.py +1156 -0
  152. wl_cli/commands/testcommands/coverage_analyzer.py +955 -0
  153. wl_cli/commands/testcommands/coverage_cache_manager.py +183 -0
  154. wl_cli/commands/testcommands/progress_manager.py +79 -0
  155. wl_cli/commands/testcommands/python_env_detector.py +207 -0
  156. wl_cli/commands/testcommands/report_generator.py +101 -0
  157. wl_cli/commands/testcommands/single_test_executor.py +250 -0
  158. wl_cli/commands/testcommands/speed_checker.py +118 -0
  159. wl_cli/commands/testcommands/test_command_builder.py +165 -0
  160. wl_cli/commands/testcommands/test_commands.py +786 -0
  161. wl_cli/commands/testcommands/test_reporter.py +181 -0
  162. wl_cli/commands/testcommands/test_result_handler.py +96 -0
  163. wl_cli/commands/testcommands/test_runner.py +1343 -0
  164. wl_cli/commands/testcommands/test_utils.py +197 -0
  165. wl_cli/commands/testcommands/utils/__init__.py +17 -0
  166. wl_cli/commands/testcommands/utils/coverage_parser.py +98 -0
  167. wl_cli/commands/testcommands/utils/env_info.py +36 -0
  168. wl_cli/commands/testcommands/utils/junit_parser.py +75 -0
  169. wl_cli/commands/testcommands/utils/markdown_utils.py +20 -0
  170. wl_cli/commands/testcommands/utils/project_root.py +61 -0
  171. wl_cli/commands/update/__init__.py +9 -0
  172. wl_cli/commands/update/services/__init__.py +21 -0
  173. wl_cli/commands/update/services/backup_manager.py +207 -0
  174. wl_cli/commands/update/services/component_manager.py +83 -0
  175. wl_cli/commands/update/services/conflict_resolver.py +189 -0
  176. wl_cli/commands/update/services/vendors_updater.py +85 -0
  177. wl_cli/commands/update/services/version_tracker.py +127 -0
  178. wl_cli/commands/update/update_command.py +270 -0
  179. wl_cli/commands/update/utils/__init__.py +3 -0
  180. wl_cli/commands/version/__init__.py +9 -0
  181. wl_cli/commands/version/version_command.py +155 -0
  182. wl_cli/commands/workflow.py +477 -0
  183. wl_cli/env/__init__.py +5 -0
  184. wl_cli/env/cli.py +167 -0
  185. wl_cli/env_status.py +157 -0
  186. wl_cli/envelope.py +91 -0
  187. wl_cli/exceptions.py +85 -0
  188. wl_cli/lib/__init__.py +3 -0
  189. wl_cli/lib/rust_utils.py +35 -0
  190. wl_cli/mcp/__init__.py +6 -0
  191. wl_cli/mcp/capabilities.py +19 -0
  192. wl_cli/mcp/cli.py +22 -0
  193. wl_cli/mcp/gitlab.py +553 -0
  194. wl_cli/mcp/k8s.py +344 -0
  195. wl_cli/mcp/protocol.py +159 -0
  196. wl_cli/platform.py +139 -0
  197. wl_cli/quality/__init__.py +18 -0
  198. wl_cli/utils/__init__.py +7 -0
  199. wl_cli/utils/cache.py +491 -0
  200. wl_cli/utils/cache_ledger.py +355 -0
  201. wl_cli/utils/cache_store.py +546 -0
  202. wl_cli/utils/config/__init__.py +27 -0
  203. wl_cli/utils/config/config_loader.py +163 -0
  204. wl_cli/utils/config/core.py +862 -0
  205. wl_cli/utils/config/defaults.py +142 -0
  206. wl_cli/utils/config/env_exporter.py +86 -0
  207. wl_cli/utils/config/env_parser.py +108 -0
  208. wl_cli/utils/config/validation.py +280 -0
  209. wl_cli/utils/error_handler.py +165 -0
  210. wl_cli/utils/file_copier.py +56 -0
  211. wl_cli/utils/file_operations.py +506 -0
  212. wl_cli/utils/language_utils.py +122 -0
  213. wl_cli/utils/lazy_import.py +240 -0
  214. wl_cli/utils/log_levels.py +35 -0
  215. wl_cli/utils/log_rotators.py +74 -0
  216. wl_cli/utils/logging.py +458 -0
  217. wl_cli/utils/output_context.py +142 -0
  218. wl_cli/utils/performance/__init__.py +1 -0
  219. wl_cli/utils/performance/decorators.py +111 -0
  220. wl_cli/utils/performance/exporter.py +38 -0
  221. wl_cli/utils/performance/models.py +16 -0
  222. wl_cli/utils/performance/monitor.py +161 -0
  223. wl_cli/utils/performance_monitor.py +32 -0
  224. wl_cli/utils/platform_adapter.py +131 -0
  225. wl_cli/utils/pre_commit.py +266 -0
  226. wl_cli/utils/pre_commit_config.py +72 -0
  227. wl_cli/utils/root.py +521 -0
  228. wl_cli/utils/root_fallback.py +256 -0
  229. wl_cli/utils/run_evidence.py +236 -0
  230. wl_cli/utils/structured_logger.py +18 -0
  231. wl_cli/utils/structured_logging/__init__.py +19 -0
  232. wl_cli/utils/structured_logging/config.py +337 -0
  233. wl_cli/utils/structured_logging/core.py +177 -0
  234. wl_cli/utils/structured_logging/env.py +56 -0
  235. wl_cli/utils/structured_logging/filtering.py +75 -0
  236. wl_cli/utils/structured_logging/filters.py +55 -0
  237. wl_cli/utils/structured_logging/formatters.py +251 -0
  238. wl_cli/utils/structured_logging/handlers.py +308 -0
  239. wl_cli/utils/structured_logging/logger.py +354 -0
  240. wl_cli/utils/subprocess.py +71 -0
  241. wl_cli/utils/subprocess_utils.py +260 -0
  242. wl_cli/utils/test_utils/__init__.py +5 -0
  243. wl_cli/utils/test_utils/test_filter.py +36 -0
  244. wl_cli/utils/uv_run.py +79 -0
  245. wl_cli/utils/uv_tool.py +117 -0
  246. wl_cli/utils/uv_tree.py +71 -0
  247. wl_cli/utils/uv_workspace.py +174 -0
  248. wl_cli/utils/version/__init__.py +15 -0
  249. wl_cli/utils/version/pypi_version_checker.py +95 -0
  250. wl_cli/utils/version/version_comparator.py +133 -0
  251. wl_cli/utils/version/version_detectors.py +312 -0
  252. wl_cli/utils/version/version_service.py +131 -0
  253. wl_cli/utils/version/version_updater.py +198 -0
  254. wl_cli/utils/wl_dir_updater.py +1440 -0
  255. wl_cli/utils/workspace_detector/__init__.py +28 -0
  256. wl_cli/utils/workspace_detector/core.py +161 -0
  257. wl_cli/utils/workspace_detector/detection_strategies.py +107 -0
  258. wl_cli/utils/workspace_detector/exceptions.py +5 -0
  259. wl_cli/utils/workspace_detector/types.py +17 -0
  260. wl_cli/utils/workspace_detector/venv_resolver.py +84 -0
  261. wl_cli/vendors/config/.codespellrc +2 -0
  262. wl_cli/vendors/config/dependencies.toml +10 -0
  263. wl_cli/vendors/config/mypy.ini +62 -0
  264. wl_cli/vendors/config/pyproject_defaults.toml +28 -0
  265. wl_cli/vendors/config/template_config.toml +24 -0
  266. wl_cli/vendors/config/tool_configs.toml +76 -0
  267. wl_cli/vendors/git/.gitignore +122 -0
  268. wl_cli/vendors/git/.pre-commit-config.yaml +37 -0
  269. wl_cli/vendors/hooks/applypatch-msg.sample +15 -0
  270. wl_cli/vendors/hooks/commit-msg.sample +24 -0
  271. wl_cli/vendors/hooks/fsmonitor-watchman.sample +174 -0
  272. wl_cli/vendors/hooks/post-update.sample +8 -0
  273. wl_cli/vendors/hooks/pre-applypatch.sample +14 -0
  274. wl_cli/vendors/hooks/pre-commit +45 -0
  275. wl_cli/vendors/hooks/pre-commit.legacy +31 -0
  276. wl_cli/vendors/hooks/pre-commit.sample +49 -0
  277. wl_cli/vendors/hooks/pre-merge-commit.sample +13 -0
  278. wl_cli/vendors/hooks/pre-push.sample +60 -0
  279. wl_cli/vendors/hooks/pre-rebase.sample +169 -0
  280. wl_cli/vendors/hooks/pre-receive.sample +24 -0
  281. wl_cli/vendors/hooks/prepare-commit-msg.sample +42 -0
  282. wl_cli/vendors/hooks/push-to-checkout.sample +78 -0
  283. wl_cli/vendors/hooks/sendemail-validate.sample +77 -0
  284. wl_cli/vendors/hooks/unix/applypatch-msg.sample +15 -0
  285. wl_cli/vendors/hooks/unix/check_lockfile.py +58 -0
  286. wl_cli/vendors/hooks/unix/check_lockfile.sh +36 -0
  287. wl_cli/vendors/hooks/unix/commit-msg.sample +24 -0
  288. wl_cli/vendors/hooks/unix/fsmonitor-watchman.sample +174 -0
  289. wl_cli/vendors/hooks/unix/post-update.sample +8 -0
  290. wl_cli/vendors/hooks/unix/pre-applypatch.sample +14 -0
  291. wl_cli/vendors/hooks/unix/pre-commit +190 -0
  292. wl_cli/vendors/hooks/unix/pre-commit.legacy +31 -0
  293. wl_cli/vendors/hooks/unix/pre-commit.py +81 -0
  294. wl_cli/vendors/hooks/unix/pre-commit.sample +49 -0
  295. wl_cli/vendors/hooks/unix/pre-merge-commit.sample +13 -0
  296. wl_cli/vendors/hooks/unix/pre-push +417 -0
  297. wl_cli/vendors/hooks/unix/pre-push.sample +53 -0
  298. wl_cli/vendors/hooks/unix/pre-rebase.sample +169 -0
  299. wl_cli/vendors/hooks/unix/pre-receive.sample +24 -0
  300. wl_cli/vendors/hooks/unix/prepare-commit-msg.sample +42 -0
  301. wl_cli/vendors/hooks/update.sample +128 -0
  302. wl_cli/vendors/hooks/win/work.bat +63 -0
  303. wl_cli/vendors/project/.gitignore +90 -0
  304. wl_cli/vendors/project/.python-version +1 -0
  305. wl_cli/vendors/project/LICENSE +21 -0
  306. wl_cli/vendors/project/README.md +0 -0
  307. wl_cli/vendors/project/pyproject.toml +27 -0
  308. wl_cli/vendors/project/pyproject_no_workspace.toml +16 -0
  309. wl_cli/vendors/project/pyproject_workspace.toml +14 -0
  310. wl_cli/vendors/readme/README.md +120 -0
  311. wl_cli/vendors/rust/Cargo.toml +14 -0
  312. wl_cli/vendors/rust/lib/__init__.py +3 -0
  313. wl_cli/vendors/rust/lib/rust_utils.py +31 -0
  314. wl_cli/vendors/rust/src/lib.rs +13 -0
  315. wl_cli/vendors/rust/src/main.rs +3 -0
  316. wl_cli/vendors/rust/tests/test_rust_fallback.py +40 -0
  317. wl_cli/vendors/sop_doc/git-dev-sop.md +220 -0
  318. wl_cli/vendors/sop_doc/industrial-standard-sop.md +145 -0
  319. wl_cli/vendors/sop_doc/python-sop.md +342 -0
  320. wl_cli/vendors/sop_doc/rust-readme.md +219 -0
wl_cli/utils/cache.py ADDED
@@ -0,0 +1,491 @@
1
+ """Generic in-memory/on-disk cache framework -- **debug-only** (#213, ADR-0011).
2
+
3
+ This module has exactly one production consumer,
4
+ :mod:`wl_cli.commands.debug.debug_cache`, in the subsystem scheduled for
5
+ retirement. The three live caches -- test results, coverage, format results --
6
+ go through :mod:`wl_cli.utils.cache_ledger`; this framework is *not* their seam
7
+ and must not grow into a second one.
8
+
9
+ It is isolated rather than migrated: a subsystem already judged dead does not
10
+ get ported. ``tests/test_utils_cache_is_debug_only.py`` fails if anything
11
+ outside ``commands/debug/`` imports it, and the module goes away with
12
+ ``wl debug``.
13
+ """
14
+
15
+ import hashlib
16
+ import pickle
17
+ import threading
18
+ import time
19
+ from collections.abc import Callable
20
+ from typing import Any
21
+
22
+
23
+ class CacheKeyGenerator:
24
+ """Cache key generator for creating unique cache keys."""
25
+
26
+ @staticmethod
27
+ def generate_key(*args, **kwargs) -> str:
28
+ """
29
+ Generate a unique cache key based on arguments.
30
+
31
+ Args:
32
+ *args: Positional arguments
33
+ **kwargs: Keyword arguments
34
+
35
+ Returns:
36
+ A unique cache key as a string
37
+ """
38
+ # Create a hashable representation of the arguments
39
+ key_components = []
40
+
41
+ # Process positional arguments
42
+ for arg in args:
43
+ if isinstance(arg, str | int | float | bool | type(None)):
44
+ key_components.append(str(arg))
45
+ elif isinstance(arg, list | tuple):
46
+ key_components.append(
47
+ str([CacheKeyGenerator._hash_object(item) for item in arg])
48
+ )
49
+ elif isinstance(arg, dict):
50
+ sorted_items = sorted(arg.items())
51
+ key_components.append(
52
+ str(
53
+ [
54
+ (k, CacheKeyGenerator._hash_object(v))
55
+ for k, v in sorted_items
56
+ ]
57
+ )
58
+ )
59
+ else:
60
+ key_components.append(CacheKeyGenerator._hash_object(arg))
61
+
62
+ # Process keyword arguments
63
+ sorted_kwargs = sorted(kwargs.items())
64
+ for key, value in sorted_kwargs:
65
+ key_components.append(f"{key}:{CacheKeyGenerator._hash_object(value)}")
66
+
67
+ # Combine and hash
68
+ combined = "|".join(key_components)
69
+ return hashlib.md5(combined.encode()).hexdigest()
70
+
71
+ @staticmethod
72
+ def _hash_object(obj: Any) -> str:
73
+ """
74
+ Hash an object to a string representation.
75
+
76
+ Args:
77
+ obj: The object to hash
78
+
79
+ Returns:
80
+ A hash string representation of the object
81
+ """
82
+ if hasattr(obj, "__dict__"):
83
+ # For objects with __dict__
84
+ sorted_attrs = sorted(obj.__dict__.items())
85
+ return hashlib.md5(str(sorted_attrs).encode()).hexdigest()
86
+ elif callable(obj):
87
+ # For callables
88
+ try:
89
+ return hashlib.md5(
90
+ (obj.__name__ + (obj.__module__ or "")).encode()
91
+ ).hexdigest()
92
+ except AttributeError:
93
+ return hashlib.md5(str(obj).encode()).hexdigest()
94
+ else:
95
+ # For other types
96
+ return hashlib.md5(str(obj).encode()).hexdigest()
97
+
98
+
99
+ class CacheItem:
100
+ """Cache item with metadata."""
101
+
102
+ def __init__(self, value: Any, expiry: float | None = None):
103
+ """
104
+ Initialize a cache item.
105
+
106
+ Args:
107
+ value: The value to cache
108
+ expiry: Optional expiry time in seconds since epoch
109
+ """
110
+ self.value = value
111
+ self.created_at = time.time()
112
+ self.expiry = expiry
113
+
114
+ def is_expired(self) -> bool:
115
+ """
116
+ Check if the cache item is expired.
117
+
118
+ Returns:
119
+ True if expired, False otherwise
120
+ """
121
+ if self.expiry is None:
122
+ return False
123
+ return time.time() > self.expiry
124
+
125
+
126
+ class BaseCache:
127
+ """Base cache implementation."""
128
+
129
+ def get(self, key: str) -> Any | None:
130
+ """
131
+ Get a value from the cache.
132
+
133
+ Args:
134
+ key: The cache key
135
+
136
+ Returns:
137
+ The cached value or None if not found or expired
138
+ """
139
+ raise NotImplementedError
140
+
141
+ def set(self, key: str, value: Any, ttl: int | None = None) -> None:
142
+ """
143
+ Set a value in the cache.
144
+
145
+ Args:
146
+ key: The cache key
147
+ value: The value to cache
148
+ ttl: Optional time-to-live in seconds
149
+ """
150
+ raise NotImplementedError
151
+
152
+ def delete(self, key: str) -> None:
153
+ """
154
+ Delete a value from the cache.
155
+
156
+ Args:
157
+ key: The cache key
158
+ """
159
+ raise NotImplementedError
160
+
161
+ def clear(self) -> None:
162
+ """
163
+ Clear the entire cache.
164
+ """
165
+ raise NotImplementedError
166
+
167
+ def has(self, key: str) -> bool:
168
+ """
169
+ Check if a key exists in the cache and is not expired.
170
+
171
+ Args:
172
+ key: The cache key
173
+
174
+ Returns:
175
+ True if the key exists and is not expired, False otherwise
176
+ """
177
+ raise NotImplementedError
178
+
179
+
180
+ class MemoryCache(BaseCache):
181
+ """In-memory cache implementation."""
182
+
183
+ def __init__(self):
184
+ """Initialize the memory cache."""
185
+ self._cache: dict[str, CacheItem] = {}
186
+ self._lock = threading.RLock()
187
+
188
+ def get(self, key: str) -> Any | None:
189
+ """
190
+ Get a value from the cache.
191
+
192
+ Args:
193
+ key: The cache key
194
+
195
+ Returns:
196
+ The cached value or None if not found or expired
197
+ """
198
+ with self._lock:
199
+ if key not in self._cache:
200
+ return None
201
+
202
+ item = self._cache[key]
203
+ if item.is_expired():
204
+ del self._cache[key]
205
+ return None
206
+
207
+ return item.value
208
+
209
+ def set(self, key: str, value: Any, ttl: int | None = None) -> None:
210
+ """
211
+ Set a value in the cache.
212
+
213
+ Args:
214
+ key: The cache key
215
+ value: The value to cache
216
+ ttl: Optional time-to-live in seconds
217
+ """
218
+ with self._lock:
219
+ expiry = time.time() + ttl if ttl is not None else None
220
+ self._cache[key] = CacheItem(value, expiry)
221
+
222
+ def delete(self, key: str) -> None:
223
+ """
224
+ Delete a value from the cache.
225
+
226
+ Args:
227
+ key: The cache key
228
+ """
229
+ with self._lock:
230
+ if key in self._cache:
231
+ del self._cache[key]
232
+
233
+ def clear(self) -> None:
234
+ """
235
+ Clear the entire cache.
236
+ """
237
+ with self._lock:
238
+ self._cache.clear()
239
+
240
+ def has(self, key: str) -> bool:
241
+ """
242
+ Check if a key exists in the cache and is not expired.
243
+
244
+ Args:
245
+ key: The cache key
246
+
247
+ Returns:
248
+ True if the key exists and is not expired, False otherwise
249
+ """
250
+ with self._lock:
251
+ if key not in self._cache:
252
+ return False
253
+
254
+ item = self._cache[key]
255
+ if item.is_expired():
256
+ del self._cache[key]
257
+ return False
258
+
259
+ return True
260
+
261
+ def get_size(self) -> int:
262
+ """
263
+ Get the size of the cache.
264
+
265
+ Returns:
266
+ The number of items in the cache
267
+ """
268
+ with self._lock:
269
+ # Clean up expired items first
270
+ expired_keys = [
271
+ key for key, item in self._cache.items() if item.is_expired()
272
+ ]
273
+ for key in expired_keys:
274
+ del self._cache[key]
275
+
276
+ return len(self._cache)
277
+
278
+
279
+ class DiskCache(BaseCache):
280
+ """Disk-based cache implementation."""
281
+
282
+ def __init__(self, cache_dir: str = ".wl_cache"):
283
+ """
284
+ Initialize the disk cache.
285
+
286
+ Args:
287
+ cache_dir: The directory to store cache files
288
+ """
289
+ import os
290
+
291
+ self.cache_dir = cache_dir
292
+ self._lock = threading.RLock()
293
+
294
+ # Create cache directory if it doesn't exist
295
+ os.makedirs(self.cache_dir, exist_ok=True)
296
+
297
+ def get(self, key: str) -> Any | None:
298
+ """
299
+ Get a value from the disk cache.
300
+
301
+ Args:
302
+ key: The cache key
303
+
304
+ Returns:
305
+ The cached value or None if not found or expired
306
+ """
307
+ import os
308
+
309
+ # Sanitize key to handle platform-specific path separators
310
+ sanitized_key = key.replace(os.sep, "_")
311
+ cache_file = os.path.join(self.cache_dir, sanitized_key)
312
+
313
+ with self._lock:
314
+ if not os.path.exists(cache_file):
315
+ return None
316
+
317
+ try:
318
+ with open(cache_file, "rb") as f:
319
+ item = pickle.load(f)
320
+
321
+ if item.is_expired():
322
+ os.remove(cache_file)
323
+ return None
324
+
325
+ return item.value
326
+ except (OSError, pickle.UnpicklingError):
327
+ # If there's an error reading the cache, remove the file
328
+ if os.path.exists(cache_file):
329
+ os.remove(cache_file)
330
+ return None
331
+
332
+ def set(self, key: str, value: Any, ttl: int | None = None) -> None:
333
+ """
334
+ Set a value in the disk cache.
335
+
336
+ Args:
337
+ key: The cache key
338
+ value: The value to cache
339
+ ttl: Optional time-to-live in seconds
340
+ """
341
+ import os
342
+
343
+ # Sanitize key to handle platform-specific path separators
344
+ sanitized_key = key.replace(os.sep, "_")
345
+ cache_file = os.path.join(self.cache_dir, sanitized_key)
346
+
347
+ with self._lock:
348
+ try:
349
+ expiry = time.time() + ttl if ttl is not None else None
350
+ item = CacheItem(value, expiry)
351
+
352
+ # Write to a temporary file first
353
+ import tempfile
354
+
355
+ with tempfile.NamedTemporaryFile(
356
+ dir=self.cache_dir, delete=False
357
+ ) as temp_f:
358
+ pickle.dump(item, temp_f)
359
+ temp_file_name = temp_f.name
360
+
361
+ # Atomically rename the temporary file to the final cache file
362
+ import os
363
+
364
+ os.replace(temp_file_name, cache_file)
365
+ except (OSError, pickle.PickleError, TypeError):
366
+ # Ignore IO, pickle, and type errors when writing to cache
367
+ # Clean up any temporary file that might have been created
368
+ if "temp_file_name" in locals() and os.path.exists(temp_file_name):
369
+ try:
370
+ os.remove(temp_file_name)
371
+ except OSError:
372
+ pass
373
+ pass
374
+
375
+ def delete(self, key: str) -> None:
376
+ """
377
+ Delete a value from the disk cache.
378
+
379
+ Args:
380
+ key: The cache key
381
+ """
382
+ import os
383
+
384
+ # Sanitize key to handle platform-specific path separators
385
+ sanitized_key = key.replace(os.sep, "_")
386
+ cache_file = os.path.join(self.cache_dir, sanitized_key)
387
+
388
+ with self._lock:
389
+ if os.path.exists(cache_file):
390
+ try:
391
+ os.remove(cache_file)
392
+ except OSError:
393
+ pass
394
+
395
+ def clear(self) -> None:
396
+ """
397
+ Clear the entire disk cache.
398
+ """
399
+ import os
400
+ import shutil
401
+
402
+ with self._lock:
403
+ if os.path.exists(self.cache_dir):
404
+ try:
405
+ shutil.rmtree(self.cache_dir)
406
+ os.makedirs(self.cache_dir, exist_ok=True)
407
+ except OSError:
408
+ pass
409
+
410
+ def has(self, key: str) -> bool:
411
+ """
412
+ Check if a key exists in the disk cache and is not expired.
413
+
414
+ Args:
415
+ key: The cache key
416
+
417
+ Returns:
418
+ True if the key exists and is not expired, False otherwise
419
+ """
420
+ return self.get(key) is not None
421
+
422
+
423
+ # Global cache instances
424
+ _memory_cache = MemoryCache()
425
+
426
+
427
+ def get_cache() -> MemoryCache:
428
+ """
429
+ Get the global memory cache instance.
430
+
431
+ Returns:
432
+ The global memory cache instance
433
+ """
434
+ return _memory_cache
435
+
436
+
437
+ def cached(ttl: int | None = 3600):
438
+ """
439
+ Decorator to cache function results.
440
+
441
+ Args:
442
+ ttl: Time-to-live in seconds
443
+
444
+ Returns:
445
+ A decorator that caches function results
446
+ """
447
+
448
+ def decorator(func: Callable) -> Callable:
449
+ # Precompute function identifier to avoid repeated attribute access
450
+ func_id = f"{func.__module__ or ''}:{func.__name__}"
451
+ cache = get_cache()
452
+
453
+ def wrapper(*args, **kwargs):
454
+ # Generate cache key
455
+ cache_key = f"{func_id}:{CacheKeyGenerator.generate_key(*args, **kwargs)}"
456
+
457
+ # Try to get from cache
458
+ cached_value = cache.get(cache_key)
459
+ if cached_value is not None:
460
+ return cached_value
461
+
462
+ # Call the function
463
+ result = func(*args, **kwargs)
464
+
465
+ # Cache the result
466
+ cache.set(cache_key, result, ttl)
467
+
468
+ return result
469
+
470
+ # Preserve function metadata
471
+ wrapper.__name__ = func.__name__
472
+ wrapper.__doc__ = func.__doc__
473
+ wrapper.__module__ = func.__module__
474
+
475
+ return wrapper
476
+
477
+ return decorator
478
+
479
+
480
+ # File system cache
481
+ _file_system_cache = MemoryCache()
482
+
483
+
484
+ def get_file_system_cache() -> MemoryCache:
485
+ """
486
+ Get the global file system cache instance.
487
+
488
+ Returns:
489
+ The global file system cache instance
490
+ """
491
+ return _file_system_cache