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
@@ -0,0 +1,337 @@
1
+ """Configuration management for structured logging."""
2
+
3
+ import os
4
+ from typing import Any, cast
5
+
6
+ from ..log_levels import LogLevel
7
+ from .env import is_test_environment
8
+
9
+
10
+ class LoggingConfig:
11
+ """Manages configuration for structured logging."""
12
+
13
+ # Singleton instance for consistent configuration across the application
14
+ _instance = None
15
+
16
+ def __new__(cls, config_dict: dict[str, Any] | None = None) -> "LoggingConfig":
17
+ """Create or return the singleton instance."""
18
+ if cls._instance is None:
19
+ cls._instance = super().__new__(cls)
20
+ # Initialize only once
21
+ cls._instance._initialize(config_dict)
22
+ elif config_dict:
23
+ # Update existing instance with new config if provided
24
+ cls._instance.update(config_dict)
25
+ return cls._instance
26
+
27
+ def _initialize(self, config_dict: dict[str, Any] | None = None) -> None:
28
+ """Initialize logging configuration.
29
+
30
+ Args:
31
+ config_dict: Optional dictionary with initial configuration.
32
+ """
33
+ self._config_functions = self._get_config_functions()
34
+ self._explicit_config = config_dict or {}
35
+ # Add configuration caching for better performance
36
+ self._cache: dict[str, Any] = {}
37
+ # Disable caching in test environment to prevent stale cache from mocks.
38
+ # is_test_environment covers plain pytest, xdist workers, and wl test's
39
+ # NON_INTERACTIVE escape hatch.
40
+ self._cache_enabled = not is_test_environment()
41
+
42
+ def _get_config_functions(self):
43
+ """Get configuration functions with fallbacks."""
44
+ try:
45
+ from ..config import get_config
46
+
47
+ return get_config
48
+ except ImportError:
49
+ # Fallback if config is not available
50
+ def config(key: str, default: Any = None) -> Any:
51
+ return default
52
+
53
+ return config
54
+
55
+ def _get_environment(self) -> str:
56
+ """Get current environment."""
57
+ try:
58
+ from ..config import get_config
59
+
60
+ return cast("str", get_config("environment", "development"))
61
+ except ImportError:
62
+ # Fallback to environment variable
63
+ return os.environ.get("WL_ENVIRONMENT", "development")
64
+
65
+ def get_environment_specific_config(self, key: str, default: Any = None) -> Any:
66
+ """Get environment-specific configuration."""
67
+ environment = self._get_environment()
68
+
69
+ # Try environment-specific key first
70
+ env_specific_key = f"{key}_{environment}"
71
+ value = self._config_functions(env_specific_key)
72
+ if value is not None:
73
+ return value
74
+
75
+ # Fallback to regular key
76
+ return self._config_functions(key, default)
77
+
78
+ def get_log_level(self, min_level: int | None = None) -> int:
79
+ """
80
+ Get log level from config or parameter.
81
+
82
+ Args:
83
+ min_level: Explicit log level. If None, get from config.
84
+
85
+ Returns:
86
+ Log level as integer.
87
+ """
88
+ if min_level is not None:
89
+ return min_level
90
+
91
+ # Never cache in test environment to prevent stale values from mocks.
92
+ # is_test_environment covers plain pytest, xdist workers, and wl test's
93
+ # NON_INTERACTIVE escape hatch.
94
+ if is_test_environment():
95
+ cache_enabled = False
96
+ # Clear any existing cached values from previous tests
97
+ self._cache.clear()
98
+ else:
99
+ cache_enabled = self._cache_enabled
100
+
101
+ # Check cache first
102
+ cache_key = f"log_level_{self._get_environment()}"
103
+ if cache_enabled and cache_key in self._cache:
104
+ return cast("int", self._cache[cache_key])
105
+
106
+ # Check explicit config first
107
+ if "log_level" in self._explicit_config:
108
+ level_value = self._explicit_config["log_level"]
109
+ else:
110
+ # Get environment-specific log level
111
+ level_value = self.get_environment_specific_config("log_level", "INFO")
112
+
113
+ # Handle both string and numeric log levels
114
+ if isinstance(level_value, str):
115
+ level_name = level_value.upper()
116
+ log_level = getattr(LogLevel, level_name, LogLevel.INFO)
117
+ else:
118
+ log_level = level_value
119
+
120
+ # Cache the result
121
+ if cache_enabled:
122
+ self._cache[cache_key] = log_level
123
+
124
+ return log_level
125
+
126
+ def get_console_settings(self) -> tuple[bool, str]:
127
+ """
128
+ Get console output settings.
129
+
130
+ Returns:
131
+ Tuple of (enable_console, console_format).
132
+ """
133
+ # Check explicit config first
134
+ if "log_console" in self._explicit_config:
135
+ enable_console = self._explicit_config["log_console"]
136
+ else:
137
+ # Get environment-specific console setting
138
+ enable_console = self.get_environment_specific_config("log_console", False)
139
+
140
+ if "log_console_format" in self._explicit_config:
141
+ console_format = self._explicit_config["log_console_format"]
142
+ else:
143
+ # 首先尝试从环境变量获取
144
+ env_value = os.environ.get("WL_LOG_CONSOLE_FORMAT")
145
+ if env_value:
146
+ console_format = env_value
147
+ else:
148
+ # Get environment-specific console format
149
+ console_format = self.get_environment_specific_config(
150
+ "log_console_format", "colored"
151
+ )
152
+
153
+ return enable_console, console_format
154
+
155
+ def get_file_format_settings(self) -> str:
156
+ """
157
+ Get log file format setting.
158
+
159
+ Returns:
160
+ Log file format ('human' or 'json').
161
+ """
162
+ # Check explicit config first
163
+ if "log_file_format" in self._explicit_config:
164
+ return cast("str", self._explicit_config["log_file_format"])
165
+ else:
166
+ # Get environment-specific file format
167
+ return cast(
168
+ "str", self.get_environment_specific_config("log_file_format", "human")
169
+ )
170
+
171
+ def get_log_file(self, log_file: str | None = None) -> str | None:
172
+ """
173
+ Get log file path.
174
+
175
+ Args:
176
+ log_file: Explicit log file path. If None, get from config.
177
+
178
+ Returns:
179
+ Log file path or None.
180
+ """
181
+ if log_file is not None:
182
+ return log_file
183
+
184
+ # Check explicit config first
185
+ if "log_file" in self._explicit_config:
186
+ return cast("str | None", self._explicit_config["log_file"])
187
+ else:
188
+ return None
189
+
190
+ def get_rotation_settings(self) -> dict[str, Any]:
191
+ """
192
+ Get log rotation settings.
193
+
194
+ Returns:
195
+ Dictionary with rotation settings.
196
+ """
197
+ # Check explicit config first
198
+ if "rotation" in self._explicit_config:
199
+ return cast("dict[str, Any]", self._explicit_config["rotation"])
200
+ else:
201
+ return {
202
+ "max_size": self._config_functions("log_max_size", 10 * 1024 * 1024),
203
+ "max_backups": self._config_functions("log_max_backups", 5),
204
+ "rotate_days": self._config_functions("log_rotate_days", 7),
205
+ }
206
+
207
+ @classmethod
208
+ def from_dict(cls, config_dict: dict[str, Any]) -> "LoggingConfig":
209
+ """
210
+ Create LoggingConfig from dictionary.
211
+
212
+ Args:
213
+ config_dict: Dictionary with configuration.
214
+
215
+ Returns:
216
+ LoggingConfig instance.
217
+ """
218
+ return cls(config_dict)
219
+
220
+ @classmethod
221
+ def from_env(cls, prefix: str = "WL_") -> "LoggingConfig":
222
+ """
223
+ Create LoggingConfig from environment variables.
224
+
225
+ Args:
226
+ prefix: Prefix for environment variables.
227
+
228
+ Returns:
229
+ LoggingConfig instance.
230
+ """
231
+ config_dict: dict[str, Any] = {}
232
+
233
+ # Map environment variables to config keys
234
+ env_mapping = {
235
+ f"{prefix}LOG_LEVEL": "log_level",
236
+ f"{prefix}LOG_CONSOLE": "log_console",
237
+ f"{prefix}LOG_CONSOLE_FORMAT": "log_console_format",
238
+ f"{prefix}LOG_FILE": "log_file",
239
+ f"{prefix}LOG_FILE_FORMAT": "log_file_format",
240
+ f"{prefix}LOG_MAX_SIZE": "log_max_size",
241
+ f"{prefix}LOG_MAX_BACKUPS": "log_max_backups",
242
+ f"{prefix}LOG_ROTATE_DAYS": "log_rotate_days",
243
+ }
244
+
245
+ for env_key, config_key in env_mapping.items():
246
+ if env_key in os.environ:
247
+ raw_value = os.environ[env_key]
248
+ # Convert boolean values
249
+ if raw_value.lower() in ("true", "false"):
250
+ config_dict[config_key] = raw_value.lower() == "true"
251
+ # Convert numeric values
252
+ elif raw_value.isdigit():
253
+ config_dict[config_key] = int(raw_value)
254
+ else:
255
+ config_dict[config_key] = raw_value
256
+
257
+ # Handle rotation settings
258
+ rotation_keys = ["log_max_size", "log_max_backups", "log_rotate_days"]
259
+ rotation_settings: dict[str, Any] = {}
260
+ for key in rotation_keys:
261
+ if key in config_dict:
262
+ rotation_settings[key.replace("log_", "")] = config_dict.pop(key)
263
+
264
+ if rotation_settings:
265
+ config_dict["rotation"] = rotation_settings
266
+
267
+ return cls(config_dict)
268
+
269
+ def to_dict(self) -> dict[str, Any]:
270
+ """
271
+ Convert configuration to dictionary.
272
+
273
+ Returns:
274
+ Dictionary with configuration.
275
+ """
276
+ config_dict: dict[str, Any] = {
277
+ "log_level": LogLevel.get_name(self.get_log_level()),
278
+ "log_file": self.get_log_file(),
279
+ }
280
+
281
+ console_enabled, console_format = self.get_console_settings()
282
+ config_dict["log_console"] = console_enabled
283
+ config_dict["log_console_format"] = console_format
284
+
285
+ config_dict["log_file_format"] = self.get_file_format_settings()
286
+ config_dict["rotation"] = self.get_rotation_settings()
287
+
288
+ return config_dict
289
+
290
+ def update(self, config_dict: dict[str, Any]) -> None:
291
+ """
292
+ Update configuration with dictionary.
293
+
294
+ Args:
295
+ config_dict: Dictionary with configuration updates.
296
+ """
297
+ self._explicit_config.update(config_dict)
298
+ # Clear cache when config is updated
299
+ self._clear_cache()
300
+
301
+ def _clear_cache(self) -> None:
302
+ """
303
+ Clear configuration cache.
304
+ """
305
+ self._cache.clear()
306
+
307
+ def set_cache_enabled(self, enabled: bool) -> None:
308
+ """
309
+ Enable or disable configuration caching.
310
+
311
+ Args:
312
+ enabled: Whether to enable caching.
313
+ """
314
+ self._cache_enabled = enabled
315
+ if not enabled:
316
+ self._clear_cache()
317
+
318
+ def get(self, key: str, default: Any = None) -> Any:
319
+ """
320
+ Get configuration value.
321
+
322
+ Args:
323
+ key: Configuration key.
324
+ default: Default value if key not found.
325
+
326
+ Returns:
327
+ Configuration value or default.
328
+ """
329
+ if key in self._explicit_config:
330
+ return self._explicit_config[key]
331
+ else:
332
+ return self._config_functions(key, default)
333
+
334
+ @classmethod
335
+ def reset(cls) -> None:
336
+ """Reset the singleton instance."""
337
+ cls._instance = None
@@ -0,0 +1,177 @@
1
+ """Core logging functionality for WL Commands.
2
+
3
+ This module serves as the entry point for the modularized structured logging functionality.
4
+ 主要功能委托给专门的模块以获得更好的组织结构。
5
+ """
6
+
7
+ from typing import Any
8
+
9
+ from ..log_rotators import LogRotator
10
+ from .config import LoggingConfig
11
+ from .filtering import LogLevelChecker
12
+ from .formatters import LogFormatter
13
+ from .handlers import LogHandler
14
+ from .logger import StructuredLoggerCore
15
+
16
+
17
+ # Backward compatibility: re-export all the main components
18
+ class StructuredLogger(StructuredLoggerCore):
19
+ """A structured logger that outputs JSON formatted logs."""
20
+
21
+ def __init__(
22
+ self, name: str, min_level: int | None = None, log_file: str | None = None
23
+ ) -> None:
24
+ """
25
+ Initialize structured logger.
26
+
27
+ Args:
28
+ name (str): Logger name.
29
+ min_level (int, optional): Minimum log level. If None, read from config.
30
+ log_file (Optional[str]): Log file path. If None, read from config.
31
+ """
32
+ super().__init__(name, min_level, log_file)
33
+
34
+ # Add backward compatibility properties
35
+ self._min_level = self.filter.min_level
36
+ self._log_file = self.handler.log_file
37
+ self.log_rotator = self.handler.log_rotator
38
+ self._enable_console = self.handler.enable_console
39
+ self.console_format = self.handler.console_format
40
+ self.log_file_format = self.handler.log_file_format
41
+ self.filters = self.filter.filters
42
+
43
+ @property
44
+ def min_level(self):
45
+ """Get minimum log level."""
46
+ return self._min_level
47
+
48
+ @min_level.setter
49
+ def min_level(self, value):
50
+ """Set minimum log level."""
51
+ self._min_level = value
52
+ self.filter.min_level = value
53
+
54
+ @property
55
+ def log_file(self):
56
+ """Get log file path."""
57
+ return self._log_file
58
+
59
+ @log_file.setter
60
+ def log_file(self, value):
61
+ """Set log file path."""
62
+ self._log_file = value
63
+ self.handler.log_file = value
64
+
65
+ @property
66
+ def enable_console(self):
67
+ """Get console output enabled status."""
68
+ return self._enable_console
69
+
70
+ @enable_console.setter
71
+ def enable_console(self, value):
72
+ """Set console output enabled status."""
73
+ self._enable_console = value
74
+ self.handler.enable_console = value
75
+
76
+ # The five verb overrides used to live here, each re-popping `message` and
77
+ # `lang` before calling _log. That duplication is gone (#136): _log now
78
+ # normalizes those kwargs once for every verb, so the inherited
79
+ # StructuredLoggerCore.debug/info/warning/error/critical are used directly.
80
+
81
+ # Compatibility methods for backward compatibility with tests
82
+ def _write_log(self, message: str) -> None:
83
+ """Write log message to appropriate output (compatibility method)."""
84
+ import json
85
+
86
+ # Handle file writing directly for backward compatibility with tests
87
+ if self.log_file:
88
+ # Handle log rotation directly
89
+ if self.log_rotator and self.log_rotator.should_rotate():
90
+ self.log_rotator.do_rotate()
91
+
92
+ try:
93
+ with open(self.log_file, "a", encoding="utf-8") as f:
94
+ f.write(message + "\n")
95
+ except OSError:
96
+ # Ignore file write errors
97
+ pass
98
+
99
+ # Handle console output directly for backward compatibility with tests
100
+ if self.enable_console:
101
+ import sys
102
+
103
+ try:
104
+ # Format message for console based on console_format
105
+ console_message = message
106
+ if self.console_format != "json":
107
+ # Try to parse JSON message and reformat as human-readable
108
+ try:
109
+ log_record = json.loads(message)
110
+ console_message = self.formatter.format_console_log(log_record)
111
+ except (json.JSONDecodeError, TypeError):
112
+ # Fallback to raw message if parsing fails
113
+ pass
114
+
115
+ # Determine output stream based on log level in message
116
+ if "CRITICAL" in message or "ERROR" in message:
117
+ print(console_message, file=sys.stderr)
118
+ else:
119
+ print(console_message, file=sys.stdout)
120
+ except OSError:
121
+ # Ignore console write errors
122
+ pass
123
+
124
+ def _write_log_with_rotation(self, message: str) -> None:
125
+ """Write log message with rotation (compatibility method)."""
126
+ self._write_log(message)
127
+
128
+ def _format_human_log(self, record: dict[str, Any]) -> str:
129
+ """Format log record for human-readable file output (compatibility method)."""
130
+ return self.formatter.format_human_log(record)
131
+
132
+ def _format_console_log(self, record: dict[str, Any]) -> str:
133
+ """Format log record for console output (compatibility method)."""
134
+ return self.formatter.format_console_log(record)
135
+
136
+ # Property accessors for backward compatibility
137
+ @property
138
+ def _COLORS(
139
+ self,
140
+ ) -> dict[str, str]:
141
+ """Get color codes (compatibility property)."""
142
+ return self.formatter._COLORS
143
+
144
+ @_COLORS.setter
145
+ def _COLORS(self, value: dict[str, str]) -> None:
146
+ """Set color codes (compatibility property)."""
147
+ self.formatter._COLORS = value
148
+
149
+ def _should_log(self, level: int, **kwargs) -> bool:
150
+ """Check if log should be processed based on level and filters (compatibility method)."""
151
+ return self.filter.should_log(level, **kwargs)
152
+
153
+ def _get_level_name(self, level: int) -> str:
154
+ """Convert level number to name (compatibility method)."""
155
+ return self.filter.get_level_name(level)
156
+
157
+ @property
158
+ def filters(self):
159
+ """Get filters for backward compatibility."""
160
+ return self.filter.filters
161
+
162
+ @filters.setter
163
+ def filters(self, value):
164
+ """Set filters for backward compatibility."""
165
+ self.filter.filters = value
166
+
167
+
168
+ # Re-export classes for advanced usage
169
+ __all__ = [
170
+ "StructuredLogger",
171
+ "StructuredLoggerCore",
172
+ "LoggingConfig",
173
+ "LogLevelChecker",
174
+ "LogFormatter",
175
+ "LogHandler",
176
+ "LogRotator",
177
+ ]
@@ -0,0 +1,56 @@
1
+ """Test-environment predicates shared across the logging stack (#136, #142).
2
+
3
+ "Are we under pytest? Is this a performance test?" used to be answered
4
+ independently at every site that cared: the emit funnel
5
+ (:mod:`wl_cli.utils.logging`), several places downstream in
6
+ :class:`~wl_cli.utils.structured_logging.logger.StructuredLoggerCore`, and the
7
+ logging config / handler internals. They all now consult this module, so the
8
+ judgment is written exactly once and a change to it cannot drift between the
9
+ callers.
10
+ """
11
+
12
+ import os
13
+
14
+ __all__ = [
15
+ "is_performance_test",
16
+ "is_pytest",
17
+ "is_test_environment",
18
+ "pytest_test_name",
19
+ ]
20
+
21
+
22
+ def pytest_test_name() -> str:
23
+ """Return pytest's per-test node id, or ``""`` outside a running test."""
24
+ return os.environ.get("PYTEST_CURRENT_TEST", "")
25
+
26
+
27
+ def is_pytest() -> bool:
28
+ """True while pytest's per-test hook is active (plain or xdist worker)."""
29
+ return os.environ.get("PYTEST_CURRENT_TEST") is not None
30
+
31
+
32
+ def is_performance_test() -> bool:
33
+ """True for the throughput suites that deliberately skip log I/O.
34
+
35
+ Matches any node id whose file / class / function mentions ``performance``;
36
+ that covers both ``test_log_performance.py`` and
37
+ ``test_logging_performance.py``. This is a superset of the narrower
38
+ predicate the funnel used to carry, and is the one the logger already used
39
+ to suppress output, so routing the funnel through it changes nothing
40
+ observable.
41
+ """
42
+ return "performance" in pytest_test_name().lower()
43
+
44
+
45
+ def is_test_environment() -> bool:
46
+ """Broad "an automated/test run" gate used for logger defaults.
47
+
48
+ Covers pytest (plain and xdist) plus the ``NON_INTERACTIVE`` escape hatch
49
+ that ``wl test`` sets, and drives default level / file / console choices.
50
+ """
51
+ return (
52
+ is_pytest()
53
+ or os.environ.get("PYTEST_XDIST_WORKER") is not None
54
+ or os.environ.get("PYTEST_XDIST_NUM_WORKERS") is not None
55
+ or os.environ.get("NON_INTERACTIVE") is not None
56
+ )
@@ -0,0 +1,75 @@
1
+ """Filtering and level checking for structured logging."""
2
+
3
+ import datetime
4
+ from collections.abc import Callable
5
+ from typing import Any
6
+
7
+ from ..log_levels import LogLevel
8
+
9
+
10
+ class LogLevelChecker:
11
+ """Manages filtering and level checking for structured logging."""
12
+
13
+ def __init__(self, min_level: int) -> None:
14
+ """
15
+ Initialize log level checker.
16
+
17
+ Args:
18
+ min_level: Minimum log level.
19
+ """
20
+ self.min_level = min_level
21
+ self.filters: list[Callable[[dict[str, Any]], bool]] = []
22
+
23
+ def add_filter(self, filter_func: Callable[[dict[str, Any]], bool]) -> None:
24
+ """
25
+ Add a filter function.
26
+
27
+ Args:
28
+ filter_func: Function that takes a log record and returns bool.
29
+ """
30
+ self.filters.append(filter_func)
31
+
32
+ def should_log(self, level: int, **kwargs) -> bool:
33
+ """
34
+ Check if log should be processed based on level and filters.
35
+
36
+ Args:
37
+ level: Log level.
38
+ **kwargs: Log record fields.
39
+
40
+ Returns:
41
+ True if log should be processed.
42
+ """
43
+ if level < self.min_level:
44
+ return False
45
+
46
+ # Only create record if level is sufficient
47
+ if not self.filters:
48
+ return True
49
+
50
+ # Create record with timestamp only if there are filters to apply
51
+ record = {
52
+ "level": level,
53
+ "timestamp": datetime.datetime.now().isoformat(),
54
+ **kwargs,
55
+ }
56
+ return all(f(record) for f in self.filters)
57
+
58
+ def get_level_name(self, level: int) -> str:
59
+ """
60
+ Convert level number to name.
61
+
62
+ Args:
63
+ level: Log level number.
64
+
65
+ Returns:
66
+ Log level name as string.
67
+ """
68
+ level_names = {
69
+ LogLevel.DEBUG: "DEBUG",
70
+ LogLevel.INFO: "INFO",
71
+ LogLevel.WARNING: "WARNING",
72
+ LogLevel.ERROR: "ERROR",
73
+ LogLevel.CRITICAL: "CRITICAL",
74
+ }
75
+ return level_names.get(level, "UNKNOWN")
@@ -0,0 +1,55 @@
1
+ """Log filters for WL Commands."""
2
+
3
+ from typing import Any
4
+
5
+
6
+ class LogFilter:
7
+ """Base class for log filters."""
8
+
9
+ def __call__(self, record: dict[str, Any]) -> bool:
10
+ """Determine if the log record should be processed."""
11
+ raise NotImplementedError("Subclasses must implement __call__ method.")
12
+
13
+
14
+ class LevelFilter(LogFilter):
15
+ """Filter logs based on log level."""
16
+
17
+ def __init__(self, min_level: int, max_level: int | None = None) -> None:
18
+ """
19
+ Initialize level filter.
20
+
21
+ Args:
22
+ min_level (int): Minimum log level to include.
23
+ max_level (Optional[int]): Maximum log level to include.
24
+ """
25
+ self.min_level = min_level
26
+ self.max_level = max_level
27
+
28
+ def __call__(self, record: dict[str, Any]) -> bool:
29
+ """Check if log record level is within specified range."""
30
+ level = record.get("level", 0)
31
+ if level < self.min_level:
32
+ return False
33
+ if self.max_level is not None and level > self.max_level:
34
+ return False
35
+ return True
36
+
37
+
38
+ class KeyValueFilter(LogFilter):
39
+ """Filter logs based on key-value pairs."""
40
+
41
+ def __init__(self, **kwargs) -> None:
42
+ """
43
+ Initialize key-value filter.
44
+
45
+ Args:
46
+ **kwargs: Key-value pairs to match in log records.
47
+ """
48
+ self.filters = kwargs
49
+
50
+ def __call__(self, record: dict[str, Any]) -> bool:
51
+ """Check if log record contains all specified key-value pairs."""
52
+ for key, value in self.filters.items():
53
+ if record.get(key) != value:
54
+ return False
55
+ return True