waldiez 0.4.7__py3-none-any.whl → 0.4.8__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.

Potentially problematic release.


This version of waldiez might be problematic. Click here for more details.

Files changed (244) hide show
  1. waldiez/__init__.py +5 -5
  2. waldiez/_version.py +1 -1
  3. waldiez/cli.py +112 -73
  4. waldiez/exporter.py +61 -19
  5. waldiez/exporting/__init__.py +25 -6
  6. waldiez/exporting/agent/__init__.py +7 -3
  7. waldiez/exporting/agent/code_execution.py +114 -0
  8. waldiez/exporting/agent/exporter.py +354 -0
  9. waldiez/exporting/agent/extras/__init__.py +15 -0
  10. waldiez/exporting/agent/extras/captain_agent_extras.py +315 -0
  11. waldiez/exporting/agent/extras/group/target.py +178 -0
  12. waldiez/exporting/agent/extras/group_manager_agent_extas.py +500 -0
  13. waldiez/exporting/agent/extras/group_member_extras.py +181 -0
  14. waldiez/exporting/agent/extras/handoffs/__init__.py +19 -0
  15. waldiez/exporting/agent/extras/handoffs/after_work.py +78 -0
  16. waldiez/exporting/agent/extras/handoffs/available.py +74 -0
  17. waldiez/exporting/agent/extras/handoffs/condition.py +158 -0
  18. waldiez/exporting/agent/extras/handoffs/handoff.py +171 -0
  19. waldiez/exporting/agent/extras/handoffs/target.py +189 -0
  20. waldiez/exporting/agent/extras/rag/__init__.py +10 -0
  21. waldiez/exporting/agent/{utils/rag_user/chroma_utils.py → extras/rag/chroma_extras.py} +16 -15
  22. waldiez/exporting/agent/{utils/rag_user/mongo_utils.py → extras/rag/mongo_extras.py} +10 -10
  23. waldiez/exporting/agent/{utils/rag_user/pgvector_utils.py → extras/rag/pgvector_extras.py} +13 -13
  24. waldiez/exporting/agent/{utils/rag_user/qdrant_utils.py → extras/rag/qdrant_extras.py} +13 -13
  25. waldiez/exporting/agent/{utils/rag_user/vector_db.py → extras/rag/vector_db_extras.py} +59 -46
  26. waldiez/exporting/agent/extras/rag_user_proxy_agent_extras.py +245 -0
  27. waldiez/exporting/agent/extras/reasoning_agent_extras.py +88 -0
  28. waldiez/exporting/agent/factory.py +95 -0
  29. waldiez/exporting/agent/processor.py +150 -0
  30. waldiez/exporting/agent/system_message.py +36 -0
  31. waldiez/exporting/agent/termination.py +50 -0
  32. waldiez/exporting/chats/__init__.py +7 -3
  33. waldiez/exporting/chats/exporter.py +97 -0
  34. waldiez/exporting/chats/factory.py +65 -0
  35. waldiez/exporting/chats/processor.py +226 -0
  36. waldiez/exporting/chats/utils/__init__.py +6 -5
  37. waldiez/exporting/chats/utils/common.py +11 -45
  38. waldiez/exporting/chats/utils/group.py +55 -0
  39. waldiez/exporting/chats/utils/nested.py +37 -52
  40. waldiez/exporting/chats/utils/sequential.py +72 -61
  41. waldiez/exporting/chats/utils/{single_chat.py → single.py} +48 -50
  42. waldiez/exporting/core/__init__.py +196 -0
  43. waldiez/exporting/core/constants.py +17 -0
  44. waldiez/exporting/core/content.py +69 -0
  45. waldiez/exporting/core/context.py +244 -0
  46. waldiez/exporting/core/enums.py +89 -0
  47. waldiez/exporting/core/errors.py +19 -0
  48. waldiez/exporting/core/exporter.py +390 -0
  49. waldiez/exporting/core/exporters.py +67 -0
  50. waldiez/exporting/core/extras/__init__.py +39 -0
  51. waldiez/exporting/core/extras/agent_extras/__init__.py +27 -0
  52. waldiez/exporting/core/extras/agent_extras/captain_extras.py +57 -0
  53. waldiez/exporting/core/extras/agent_extras/group_manager_extras.py +102 -0
  54. waldiez/exporting/core/extras/agent_extras/rag_user_extras.py +53 -0
  55. waldiez/exporting/core/extras/agent_extras/reasoning_extras.py +68 -0
  56. waldiez/exporting/core/extras/agent_extras/standard_extras.py +263 -0
  57. waldiez/exporting/core/extras/base.py +241 -0
  58. waldiez/exporting/core/extras/chat_extras.py +118 -0
  59. waldiez/exporting/core/extras/flow_extras.py +70 -0
  60. waldiez/exporting/core/extras/model_extras.py +73 -0
  61. waldiez/exporting/core/extras/path_resolver.py +93 -0
  62. waldiez/exporting/core/extras/serializer.py +138 -0
  63. waldiez/exporting/core/extras/tool_extras.py +82 -0
  64. waldiez/exporting/core/protocols.py +259 -0
  65. waldiez/exporting/core/result.py +705 -0
  66. waldiez/exporting/core/types.py +329 -0
  67. waldiez/exporting/core/utils/__init__.py +11 -0
  68. waldiez/exporting/core/utils/comment.py +33 -0
  69. waldiez/exporting/core/utils/llm_config.py +117 -0
  70. waldiez/exporting/core/validation.py +96 -0
  71. waldiez/exporting/flow/__init__.py +6 -2
  72. waldiez/exporting/flow/execution_generator.py +193 -0
  73. waldiez/exporting/flow/exporter.py +107 -0
  74. waldiez/exporting/flow/factory.py +94 -0
  75. waldiez/exporting/flow/file_generator.py +214 -0
  76. waldiez/exporting/flow/merger.py +387 -0
  77. waldiez/exporting/flow/orchestrator.py +411 -0
  78. waldiez/exporting/flow/utils/__init__.py +9 -36
  79. waldiez/exporting/flow/utils/common.py +206 -0
  80. waldiez/exporting/flow/utils/importing.py +373 -0
  81. waldiez/exporting/flow/utils/linting.py +200 -0
  82. waldiez/exporting/flow/utils/{logging_utils.py → logging.py} +23 -9
  83. waldiez/exporting/models/__init__.py +3 -1
  84. waldiez/exporting/models/exporter.py +233 -0
  85. waldiez/exporting/models/factory.py +66 -0
  86. waldiez/exporting/models/processor.py +139 -0
  87. waldiez/exporting/tools/__init__.py +11 -0
  88. waldiez/exporting/tools/exporter.py +207 -0
  89. waldiez/exporting/tools/factory.py +57 -0
  90. waldiez/exporting/tools/processor.py +248 -0
  91. waldiez/exporting/tools/registration.py +133 -0
  92. waldiez/io/__init__.py +128 -0
  93. waldiez/io/_ws.py +199 -0
  94. waldiez/io/models/__init__.py +60 -0
  95. waldiez/io/models/base.py +66 -0
  96. waldiez/io/models/constants.py +78 -0
  97. waldiez/io/models/content/__init__.py +23 -0
  98. waldiez/io/models/content/audio.py +43 -0
  99. waldiez/io/models/content/base.py +45 -0
  100. waldiez/io/models/content/file.py +43 -0
  101. waldiez/io/models/content/image.py +96 -0
  102. waldiez/io/models/content/text.py +37 -0
  103. waldiez/io/models/content/video.py +43 -0
  104. waldiez/io/models/user_input.py +269 -0
  105. waldiez/io/models/user_response.py +215 -0
  106. waldiez/io/mqtt.py +681 -0
  107. waldiez/io/redis.py +782 -0
  108. waldiez/io/structured.py +419 -0
  109. waldiez/io/utils.py +184 -0
  110. waldiez/io/ws.py +298 -0
  111. waldiez/logger.py +481 -0
  112. waldiez/models/__init__.py +108 -51
  113. waldiez/models/agents/__init__.py +34 -70
  114. waldiez/models/agents/agent/__init__.py +10 -4
  115. waldiez/models/agents/agent/agent.py +466 -65
  116. waldiez/models/agents/agent/agent_data.py +119 -47
  117. waldiez/models/agents/agent/agent_type.py +13 -2
  118. waldiez/models/agents/agent/code_execution.py +12 -12
  119. waldiez/models/agents/agent/human_input_mode.py +8 -0
  120. waldiez/models/agents/agent/{linked_skill.py → linked_tool.py} +7 -7
  121. waldiez/models/agents/agent/nested_chat.py +35 -7
  122. waldiez/models/agents/agent/termination_message.py +30 -22
  123. waldiez/models/agents/{swarm_agent → agent}/update_system_message.py +22 -22
  124. waldiez/models/agents/agents.py +58 -63
  125. waldiez/models/agents/assistant/assistant.py +4 -4
  126. waldiez/models/agents/assistant/assistant_data.py +13 -1
  127. waldiez/models/agents/{captain_agent → captain}/captain_agent.py +5 -5
  128. waldiez/models/agents/{captain_agent → captain}/captain_agent_data.py +5 -5
  129. waldiez/models/agents/extra_requirements.py +11 -16
  130. waldiez/models/agents/group_manager/group_manager.py +103 -13
  131. waldiez/models/agents/group_manager/group_manager_data.py +36 -14
  132. waldiez/models/agents/group_manager/speakers.py +77 -24
  133. waldiez/models/agents/{rag_user → rag_user_proxy}/__init__.py +16 -16
  134. waldiez/models/agents/rag_user_proxy/rag_user_proxy.py +64 -0
  135. waldiez/models/agents/{rag_user/rag_user_data.py → rag_user_proxy/rag_user_proxy_data.py} +6 -5
  136. waldiez/models/agents/{rag_user → rag_user_proxy}/retrieve_config.py +182 -114
  137. waldiez/models/agents/{rag_user → rag_user_proxy}/vector_db_config.py +13 -13
  138. waldiez/models/agents/reasoning/reasoning_agent.py +6 -6
  139. waldiez/models/agents/reasoning/reasoning_agent_data.py +110 -63
  140. waldiez/models/agents/reasoning/reasoning_agent_reason_config.py +38 -10
  141. waldiez/models/agents/user_proxy/user_proxy.py +11 -7
  142. waldiez/models/agents/user_proxy/user_proxy_data.py +2 -2
  143. waldiez/models/chat/__init__.py +2 -1
  144. waldiez/models/chat/chat.py +166 -87
  145. waldiez/models/chat/chat_data.py +99 -136
  146. waldiez/models/chat/chat_message.py +33 -23
  147. waldiez/models/chat/chat_nested.py +31 -30
  148. waldiez/models/chat/chat_summary.py +10 -8
  149. waldiez/models/common/__init__.py +52 -2
  150. waldiez/models/common/ag2_version.py +1 -1
  151. waldiez/models/common/base.py +38 -7
  152. waldiez/models/common/dict_utils.py +42 -17
  153. waldiez/models/common/handoff.py +459 -0
  154. waldiez/models/common/id_generator.py +19 -0
  155. waldiez/models/common/method_utils.py +130 -68
  156. waldiez/{exporting/base/utils → models/common}/naming.py +38 -61
  157. waldiez/models/common/waldiez_version.py +37 -0
  158. waldiez/models/flow/__init__.py +9 -2
  159. waldiez/models/flow/connection.py +18 -0
  160. waldiez/models/flow/flow.py +311 -215
  161. waldiez/models/flow/flow_data.py +207 -40
  162. waldiez/models/flow/info.py +85 -0
  163. waldiez/models/flow/naming.py +131 -0
  164. waldiez/models/model/__init__.py +7 -1
  165. waldiez/models/model/extra_requirements.py +3 -12
  166. waldiez/models/model/model.py +76 -21
  167. waldiez/models/model/model_data.py +108 -20
  168. waldiez/models/tool/__init__.py +16 -0
  169. waldiez/models/tool/extra_requirements.py +36 -0
  170. waldiez/models/{skill/skill.py → tool/tool.py} +88 -88
  171. waldiez/models/tool/tool_data.py +51 -0
  172. waldiez/models/tool/tool_type.py +8 -0
  173. waldiez/models/waldiez.py +97 -80
  174. waldiez/runner.py +114 -49
  175. waldiez/running/__init__.py +1 -1
  176. waldiez/running/environment.py +49 -68
  177. waldiez/running/gen_seq_diagram.py +16 -14
  178. waldiez/running/running.py +53 -34
  179. waldiez/utils/__init__.py +0 -4
  180. waldiez/utils/cli_extras/jupyter.py +5 -3
  181. waldiez/utils/cli_extras/runner.py +6 -4
  182. waldiez/utils/cli_extras/studio.py +6 -4
  183. waldiez/utils/conflict_checker.py +15 -9
  184. waldiez/utils/flaml_warnings.py +5 -5
  185. {waldiez-0.4.7.dist-info → waldiez-0.4.8.dist-info}/METADATA +235 -91
  186. waldiez-0.4.8.dist-info/RECORD +200 -0
  187. waldiez/exporting/agent/agent_exporter.py +0 -297
  188. waldiez/exporting/agent/utils/__init__.py +0 -23
  189. waldiez/exporting/agent/utils/captain_agent.py +0 -263
  190. waldiez/exporting/agent/utils/code_execution.py +0 -65
  191. waldiez/exporting/agent/utils/group_manager.py +0 -220
  192. waldiez/exporting/agent/utils/rag_user/__init__.py +0 -7
  193. waldiez/exporting/agent/utils/rag_user/rag_user.py +0 -209
  194. waldiez/exporting/agent/utils/reasoning.py +0 -36
  195. waldiez/exporting/agent/utils/swarm_agent.py +0 -469
  196. waldiez/exporting/agent/utils/teachability.py +0 -41
  197. waldiez/exporting/agent/utils/termination_message.py +0 -44
  198. waldiez/exporting/base/__init__.py +0 -25
  199. waldiez/exporting/base/agent_position.py +0 -75
  200. waldiez/exporting/base/base_exporter.py +0 -118
  201. waldiez/exporting/base/export_position.py +0 -48
  202. waldiez/exporting/base/import_position.py +0 -23
  203. waldiez/exporting/base/mixin.py +0 -137
  204. waldiez/exporting/base/utils/__init__.py +0 -18
  205. waldiez/exporting/base/utils/comments.py +0 -96
  206. waldiez/exporting/base/utils/path_check.py +0 -68
  207. waldiez/exporting/base/utils/to_string.py +0 -84
  208. waldiez/exporting/chats/chats_exporter.py +0 -240
  209. waldiez/exporting/chats/utils/swarm.py +0 -210
  210. waldiez/exporting/flow/flow_exporter.py +0 -528
  211. waldiez/exporting/flow/utils/agent_utils.py +0 -204
  212. waldiez/exporting/flow/utils/chat_utils.py +0 -71
  213. waldiez/exporting/flow/utils/def_main.py +0 -77
  214. waldiez/exporting/flow/utils/flow_content.py +0 -202
  215. waldiez/exporting/flow/utils/flow_names.py +0 -116
  216. waldiez/exporting/flow/utils/importing_utils.py +0 -227
  217. waldiez/exporting/models/models_exporter.py +0 -199
  218. waldiez/exporting/models/utils.py +0 -174
  219. waldiez/exporting/skills/__init__.py +0 -9
  220. waldiez/exporting/skills/skills_exporter.py +0 -176
  221. waldiez/exporting/skills/utils.py +0 -369
  222. waldiez/models/agents/agent/teachability.py +0 -70
  223. waldiez/models/agents/rag_user/rag_user.py +0 -60
  224. waldiez/models/agents/swarm_agent/__init__.py +0 -50
  225. waldiez/models/agents/swarm_agent/after_work.py +0 -179
  226. waldiez/models/agents/swarm_agent/on_condition.py +0 -105
  227. waldiez/models/agents/swarm_agent/on_condition_available.py +0 -142
  228. waldiez/models/agents/swarm_agent/on_condition_target.py +0 -40
  229. waldiez/models/agents/swarm_agent/swarm_agent.py +0 -107
  230. waldiez/models/agents/swarm_agent/swarm_agent_data.py +0 -124
  231. waldiez/models/flow/utils.py +0 -232
  232. waldiez/models/skill/__init__.py +0 -16
  233. waldiez/models/skill/extra_requirements.py +0 -36
  234. waldiez/models/skill/skill_data.py +0 -53
  235. waldiez/models/skill/skill_type.py +0 -8
  236. waldiez/utils/pysqlite3_checker.py +0 -308
  237. waldiez/utils/rdps_checker.py +0 -122
  238. waldiez-0.4.7.dist-info/RECORD +0 -149
  239. /waldiez/models/agents/{captain_agent → captain}/__init__.py +0 -0
  240. /waldiez/models/agents/{captain_agent → captain}/captain_agent_lib_entry.py +0 -0
  241. {waldiez-0.4.7.dist-info → waldiez-0.4.8.dist-info}/WHEEL +0 -0
  242. {waldiez-0.4.7.dist-info → waldiez-0.4.8.dist-info}/entry_points.txt +0 -0
  243. {waldiez-0.4.7.dist-info → waldiez-0.4.8.dist-info}/licenses/LICENSE +0 -0
  244. {waldiez-0.4.7.dist-info → waldiez-0.4.8.dist-info}/licenses/NOTICE.md +0 -0
waldiez/logger.py ADDED
@@ -0,0 +1,481 @@
1
+ # SPDX-License-Identifier: Apache-2.0.
2
+ # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
+ # pylint: disable=broad-exception-caught,too-many-try-statements
4
+ """Waldiez logger."""
5
+
6
+ import inspect
7
+ import os
8
+ import re
9
+ import threading
10
+ import traceback
11
+ from datetime import datetime
12
+ from enum import IntEnum
13
+ from typing import Any, Callable, Optional
14
+
15
+ import click
16
+
17
+
18
+ class LogLevel(IntEnum):
19
+ """Log level enumeration for comparison."""
20
+
21
+ DEBUG = 10
22
+ INFO = 20
23
+ SUCCESS = 30
24
+ WARNING = 40
25
+ ERROR = 50
26
+ CRITICAL = 60
27
+
28
+
29
+ class WaldiezLogger:
30
+ """A colorful logger implementation using Click.
31
+
32
+ Supports both .format() and %-style formatting:
33
+ - logger.info("Hello {name}!", name="world")
34
+ - logger.info("Hello %s!", "world")
35
+ - logger.info("User {user} has {count} items", user="john", count=5)
36
+ """
37
+
38
+ _instance: Optional["WaldiezLogger"] = None
39
+ _lock: threading.Lock = threading.Lock()
40
+
41
+ _INTERNAL_METHODS = {
42
+ "_get_caller_info",
43
+ "_format_message",
44
+ "log",
45
+ "debug",
46
+ "info",
47
+ "success",
48
+ "warning",
49
+ "error",
50
+ "critical",
51
+ "exception",
52
+ }
53
+
54
+ def __new__(cls, *args: Any, **kwargs: Any) -> "WaldiezLogger":
55
+ """Ensure only one instance of the logger is created."""
56
+ if cls._instance is None:
57
+ with cls._lock:
58
+ # Double-check locking pattern
59
+ if cls._instance is None: # pragma: no branch
60
+ cls._instance = super().__new__(cls)
61
+ return cls._instance
62
+
63
+ def __init__(
64
+ self,
65
+ level: str = "info",
66
+ timestamp_format: str = "%Y-%m-%d %H:%M:%S",
67
+ ) -> None:
68
+ """Initialize the logger with a default level.
69
+
70
+ Parameters
71
+ ----------
72
+ level : str, optional
73
+ Initial logging level, by default "info"
74
+ timestamp_format : str, optional
75
+ Timestamp format string, by default "%Y-%m-%d %H:%M:%S"
76
+ """
77
+ if getattr(self, "_initialized", False) is True:
78
+ if level != self.get_level():
79
+ self.set_level(level)
80
+ if (
81
+ self._instance
82
+ and self._instance.get_timestamp_format() != timestamp_format
83
+ ):
84
+ self._instance.set_timestamp_format(timestamp_format)
85
+ return
86
+ self._level = level.upper()
87
+ self._timestamp_format = timestamp_format
88
+
89
+ self._level_map: dict[str, LogLevel] = {
90
+ "DEBUG": LogLevel.DEBUG,
91
+ "INFO": LogLevel.INFO,
92
+ "SUCCESS": LogLevel.SUCCESS,
93
+ "WARNING": LogLevel.WARNING,
94
+ "ERROR": LogLevel.ERROR,
95
+ "CRITICAL": LogLevel.CRITICAL,
96
+ }
97
+
98
+ # Map levels to click styling functions
99
+ self._style_map: dict[str, Callable[[str], str]] = {
100
+ "DEBUG": lambda msg: click.style(msg, dim=True),
101
+ "INFO": lambda msg: click.style(msg, fg="blue"),
102
+ "SUCCESS": lambda msg: click.style(msg, fg="green"),
103
+ "WARNING": lambda msg: click.style(msg, fg="yellow"),
104
+ "ERROR": lambda msg: click.style(msg, fg="red"),
105
+ "CRITICAL": lambda msg: click.style(msg, fg="red", bold=True),
106
+ "EXCEPTION": lambda msg: click.style(msg, fg="red", bold=True),
107
+ }
108
+ self._initialized = True
109
+
110
+ @classmethod
111
+ def get_instance(
112
+ cls,
113
+ level: str = "info",
114
+ timestamp_format: str = "%Y-%m-%d %H:%M:%S",
115
+ ) -> "WaldiezLogger":
116
+ """Get the singleton instance.
117
+
118
+ Parameters
119
+ ----------
120
+ level : str, optional
121
+ Initial logging level, by default "info"
122
+ timestamp_format : str, optional
123
+ Timestamp format string, by default "%Y-%m-%d %H:%M:%S"
124
+
125
+ Returns
126
+ -------
127
+ WaldiezLogger
128
+ The singleton logger instance
129
+ """
130
+ return cls(level, timestamp_format)
131
+
132
+ def log(
133
+ self, message: Any, *args: Any, level: str = "info", **kwargs: Any
134
+ ) -> None:
135
+ """Log a message with the specified level.
136
+
137
+ Parameters
138
+ ----------
139
+ message : Any
140
+ The message to log or message template for formatting.
141
+ level : str, optional
142
+ The logging level to use (e.g., "debug", "info", "success",
143
+ "warning", "error", "critical"). Defaults to "info".
144
+ *args : Any
145
+ Arguments to format into the message using % formatting.
146
+ **kwargs : Any
147
+ Additional keyword arguments for formatting.
148
+ """
149
+ if self._should_log(level):
150
+ formatted_message_content = self._format_args(
151
+ message, *args, **kwargs
152
+ )
153
+ formatted_message = self._format_message(
154
+ formatted_message_content, level
155
+ )
156
+ click.echo(formatted_message)
157
+
158
+ def debug(self, message: Any, *args: Any, **kwargs: Any) -> None:
159
+ """Log a debug message.
160
+
161
+ Parameters
162
+ ----------
163
+ message : Any
164
+ The debug message to log or message template.
165
+ *args : Any
166
+ Arguments to format into the message.
167
+ **kwargs : Any
168
+ Additional keyword arguments for formatting.
169
+ """
170
+ self.log(message, *args, level="debug", **kwargs)
171
+
172
+ def info(self, message: Any, *args: Any, **kwargs: Any) -> None:
173
+ """Log an informational message.
174
+
175
+ Parameters
176
+ ----------
177
+ message : Any
178
+ The informational message to log or message template.
179
+ *args : Any
180
+ Arguments to format into the message.
181
+ **kwargs : Any
182
+ Additional keyword arguments for formatting.
183
+ """
184
+ self.log(message, *args, level="info", **kwargs)
185
+
186
+ def success(self, message: Any, *args: Any, **kwargs: Any) -> None:
187
+ """Log a success message.
188
+
189
+ Parameters
190
+ ----------
191
+ message : Any
192
+ The success message to log or message template.
193
+ *args : Any
194
+ Arguments to format into the message.
195
+ **kwargs : Any
196
+ Additional keyword arguments for formatting.
197
+ """
198
+ self.log(message, *args, level="success", **kwargs)
199
+
200
+ def warning(self, message: Any, *args: Any, **kwargs: Any) -> None:
201
+ """Log a warning message.
202
+
203
+ Parameters
204
+ ----------
205
+ message : Any
206
+ The warning message to log or message template.
207
+ *args : Any
208
+ Arguments to format into the message.
209
+ **kwargs : Any
210
+ Additional keyword arguments for formatting.
211
+ """
212
+ self.log(message, *args, level="warning", **kwargs)
213
+
214
+ def error(self, message: Any, *args: Any, **kwargs: Any) -> None:
215
+ """Log an error message.
216
+
217
+ Parameters
218
+ ----------
219
+ message : Any
220
+ The error message to log or message template.
221
+ *args : Any
222
+ Arguments to format into the message.
223
+ **kwargs : Any
224
+ Additional keyword arguments for formatting.
225
+ """
226
+ self.log(message, *args, level="error", **kwargs)
227
+
228
+ def critical(self, message: Any, *args: Any, **kwargs: Any) -> None:
229
+ """Log a critical error message.
230
+
231
+ Parameters
232
+ ----------
233
+ message : Any
234
+ The critical error message to log or message template.
235
+ *args : Any
236
+ Arguments to format into the message.
237
+ **kwargs : Any
238
+ Additional keyword arguments for formatting.
239
+ """
240
+ self.log(message, *args, level="critical", **kwargs)
241
+
242
+ def exception(self, message: Any, *args: Any, **kwargs: Any) -> None:
243
+ """Log an exception message.
244
+
245
+ Parameters
246
+ ----------
247
+ message : Any
248
+ The exception message to log or message template.
249
+ *args : Any
250
+ Arguments to format into the message.
251
+ **kwargs : Any
252
+ Additional keyword arguments for formatting.
253
+ """
254
+ formatted_message_content = self._format_args(message, *args, **kwargs)
255
+ formatted_message = self._format_message(
256
+ formatted_message_content, "exception"
257
+ )
258
+ click.echo(formatted_message)
259
+ tb = traceback.format_exc()
260
+ if tb and "NoneType: None" not in tb: # pragma: no branch
261
+ click.echo(click.style(tb, fg="red", dim=True))
262
+
263
+ def set_level(self, level: str) -> None:
264
+ """Set the logging level.
265
+
266
+ Parameters
267
+ ----------
268
+ level : str
269
+ The logging level to set
270
+ (e.g., "debug", "info", "warning", "error", "critical").
271
+
272
+ Raises
273
+ ------
274
+ ValueError
275
+ If the provided level is invalid.
276
+ """
277
+ level_upper = level.upper()
278
+ if level_upper in self._level_map:
279
+ self._level = level_upper
280
+ else:
281
+ raise ValueError(
282
+ f"Invalid log level: {level}. "
283
+ f"Valid levels are: {list(self._level_map.keys())}"
284
+ )
285
+
286
+ def get_level(self) -> str:
287
+ """Get the current logging level.
288
+
289
+ Returns
290
+ -------
291
+ str
292
+ The current logging level.
293
+ """
294
+ return self._level.lower()
295
+
296
+ def get_timestamp_format(self) -> str:
297
+ """Get the current timestamp format.
298
+
299
+ Returns
300
+ -------
301
+ str
302
+ The current timestamp format string.
303
+ """
304
+ return self._timestamp_format
305
+
306
+ def set_timestamp_format(self, timestamp_format: str) -> None:
307
+ """Set the timestamp format for log messages.
308
+
309
+ Parameters
310
+ ----------
311
+ timestamp_format : str
312
+ The new timestamp format string.
313
+ """
314
+ self._timestamp_format = timestamp_format
315
+
316
+ def _should_log(self, level: str) -> bool:
317
+ """Check if a message should be logged based on current level."""
318
+ current_level_value = self._level_map.get(self._level, LogLevel.INFO)
319
+ message_level_value = self._level_map.get(level.upper(), LogLevel.INFO)
320
+ return message_level_value >= current_level_value
321
+
322
+ @classmethod
323
+ def _get_caller_info(cls) -> str:
324
+ """Get caller information (filename and line number) from the stack."""
325
+ frame = inspect.currentframe()
326
+ try:
327
+ if frame is None: # pragma: no cover
328
+ return ""
329
+ # Walk up the call stack until we find a frame that's not internal
330
+ current_frame = frame.f_back # Skip _get_caller_info itself
331
+ while current_frame is not None:
332
+ code = current_frame.f_code
333
+ function_name = code.co_name
334
+ filename = code.co_filename
335
+
336
+ # Skip frames that are internal to the logger
337
+ if cls._is_internal_frame(function_name, filename):
338
+ current_frame = current_frame.f_back
339
+ continue
340
+ # Found the actual caller
341
+ line_number = current_frame.f_lineno
342
+ return cls._format_caller_display(filename, line_number)
343
+ return "" # pragma: no cover
344
+ except Exception: # pragma: no cover
345
+ # If anything goes wrong, silently return empty string
346
+ # Logging shouldn't crash the application
347
+ return ""
348
+ finally:
349
+ del frame # Prevent reference cycles
350
+
351
+ @classmethod
352
+ def _is_internal_frame(cls, function_name: str, filename: str) -> bool:
353
+ """Check if a frame is internal to the logger and should be skipped."""
354
+ if function_name in cls._INTERNAL_METHODS:
355
+ return True
356
+ # Skip if it's in the same file as the logger (this file)
357
+ logger_file = os.path.abspath(__file__)
358
+ frame_file = os.path.abspath(filename)
359
+ if frame_file == logger_file: # pragma: no cover
360
+ return True
361
+ if function_name.startswith("_log") or function_name.endswith("_log"):
362
+ return True
363
+ if function_name == "<lambda>": # pragma: no cover
364
+ return True
365
+ return False
366
+
367
+ @staticmethod
368
+ def _format_caller_display(filename: str, line_number: int) -> str:
369
+ """Format the caller information for display."""
370
+ basename = os.path.realpath(filename)
371
+ return f"{basename}:{line_number}"
372
+
373
+ def _get_timestamp(self) -> str:
374
+ """Get the current timestamp in a human-readable format."""
375
+ return datetime.now().strftime(self._timestamp_format)
376
+
377
+ @staticmethod
378
+ def _has_format_braces(msg_str: str) -> bool:
379
+ """Check if the message contains .format() style placeholders."""
380
+ return bool(re.search(r"\{[^}]*\}", msg_str))
381
+
382
+ @staticmethod
383
+ def _has_percent_placeholders(msg_str: str) -> bool:
384
+ """Check if the message contains %-style placeholders."""
385
+ return bool(re.search(r"%[sdifgGexXocr%]", msg_str))
386
+
387
+ @staticmethod
388
+ def _try_format_style(
389
+ msg_str: str, *args: Any, **kwargs: Any
390
+ ) -> tuple[bool, Any]:
391
+ """Attempt .format() formatting, returning (success, result)."""
392
+ try:
393
+ return True, msg_str.format(*args, **kwargs)
394
+ except Exception as e:
395
+ return False, e
396
+
397
+ @staticmethod
398
+ def _try_percent_style(msg_str: str, *args: Any) -> tuple[bool, Any]:
399
+ """Attempt %-style formatting, returning (success, result)."""
400
+ try:
401
+ return True, msg_str % args
402
+ except Exception as e:
403
+ return False, e
404
+
405
+ # pylint: disable=too-many-return-statements
406
+ def _format_args(self, message: Any, *args: Any, **kwargs: Any) -> str:
407
+ """Format message with arguments, similar to standard logging."""
408
+ if not args and not kwargs:
409
+ return str(message)
410
+ msg_str = str(message)
411
+ has_format = self._has_format_braces(msg_str)
412
+ has_percent = self._has_percent_placeholders(msg_str)
413
+ # Prefer .format() if curly braces detected
414
+ if has_format:
415
+ ok, res = self._try_format_style(msg_str, *args, **kwargs)
416
+ if ok:
417
+ return res
418
+ # If .format() fails and % placeholders exist, try % formatting
419
+ if has_percent and args and not kwargs: # pragma: no cover
420
+ ok2, res2 = self._try_percent_style(msg_str, *args)
421
+ if ok2:
422
+ return res2
423
+ return (
424
+ f"{msg_str} (formatting error: {res}, "
425
+ f"args: {args}, kwargs: {kwargs})"
426
+ )
427
+ # Use % formatting if % placeholders present and args (but no kwargs)
428
+ if has_percent and args and not kwargs:
429
+ ok, res = self._try_percent_style(msg_str, *args)
430
+ if ok:
431
+ return res
432
+ return ( # pragma: no cover
433
+ f"{msg_str} (formatting error: {res}, "
434
+ f"args: {args}, kwargs: {kwargs})"
435
+ )
436
+ # we would never reach here (handled before calling this method)
437
+ return msg_str # pragma: no cover
438
+
439
+ def _format_message(self, message: Any, level: str) -> str:
440
+ """Format a log message with level and styling."""
441
+ level_upper = level.upper()
442
+
443
+ def _fallback_style(msg: str) -> str:
444
+ return msg
445
+
446
+ parts: list[str] = []
447
+ styled_level = self._style_map.get(level_upper, _fallback_style)(
448
+ f"[{level_upper}]"
449
+ )
450
+ parts.append(styled_level)
451
+ timestamp = self._get_timestamp()
452
+ timestamp_styled = click.style(timestamp, dim=True)
453
+ parts.append(timestamp_styled)
454
+ caller_info = self._get_caller_info()
455
+ if caller_info: # pragma: no branch
456
+ caller_styled = click.style(f"[{caller_info}]", dim=True)
457
+ parts.append(caller_styled)
458
+ prefix = " ".join(parts)
459
+ return f"{prefix} {message}"
460
+
461
+
462
+ def get_logger(
463
+ level: str = "info", timestamp_format: str = "%Y-%m-%d %H:%M:%S"
464
+ ) -> WaldiezLogger:
465
+ """Get the singleton logger instance.
466
+
467
+ Parameters
468
+ ----------
469
+ level : str
470
+ The logging level to use (default: "info").
471
+ timestamp_format : str
472
+ The timestamps format in log messages (default: "%Y-%m-%d %H:%M:%S").
473
+
474
+ Returns
475
+ -------
476
+ WaldiezLogger
477
+ The singleton logger instance.
478
+ """
479
+ return WaldiezLogger.get_instance(
480
+ level=level, timestamp_format=timestamp_format
481
+ )