waldiez 0.4.6__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.
- waldiez/__init__.py +5 -5
- waldiez/_version.py +1 -1
- waldiez/cli.py +112 -73
- waldiez/exporter.py +61 -19
- waldiez/exporting/__init__.py +25 -6
- waldiez/exporting/agent/__init__.py +7 -3
- waldiez/exporting/agent/code_execution.py +114 -0
- waldiez/exporting/agent/exporter.py +354 -0
- waldiez/exporting/agent/extras/__init__.py +15 -0
- waldiez/exporting/agent/extras/captain_agent_extras.py +315 -0
- waldiez/exporting/agent/extras/group/target.py +178 -0
- waldiez/exporting/agent/extras/group_manager_agent_extas.py +500 -0
- waldiez/exporting/agent/extras/group_member_extras.py +181 -0
- waldiez/exporting/agent/extras/handoffs/__init__.py +19 -0
- waldiez/exporting/agent/extras/handoffs/after_work.py +78 -0
- waldiez/exporting/agent/extras/handoffs/available.py +74 -0
- waldiez/exporting/agent/extras/handoffs/condition.py +158 -0
- waldiez/exporting/agent/extras/handoffs/handoff.py +171 -0
- waldiez/exporting/agent/extras/handoffs/target.py +189 -0
- waldiez/exporting/agent/extras/rag/__init__.py +10 -0
- waldiez/exporting/agent/{utils/rag_user/chroma_utils.py → extras/rag/chroma_extras.py} +16 -15
- waldiez/exporting/agent/{utils/rag_user/mongo_utils.py → extras/rag/mongo_extras.py} +10 -10
- waldiez/exporting/agent/{utils/rag_user/pgvector_utils.py → extras/rag/pgvector_extras.py} +13 -13
- waldiez/exporting/agent/{utils/rag_user/qdrant_utils.py → extras/rag/qdrant_extras.py} +13 -13
- waldiez/exporting/agent/{utils/rag_user/vector_db.py → extras/rag/vector_db_extras.py} +59 -46
- waldiez/exporting/agent/extras/rag_user_proxy_agent_extras.py +245 -0
- waldiez/exporting/agent/extras/reasoning_agent_extras.py +88 -0
- waldiez/exporting/agent/factory.py +95 -0
- waldiez/exporting/agent/processor.py +150 -0
- waldiez/exporting/agent/system_message.py +36 -0
- waldiez/exporting/agent/termination.py +50 -0
- waldiez/exporting/chats/__init__.py +7 -3
- waldiez/exporting/chats/exporter.py +97 -0
- waldiez/exporting/chats/factory.py +65 -0
- waldiez/exporting/chats/processor.py +226 -0
- waldiez/exporting/chats/utils/__init__.py +6 -5
- waldiez/exporting/chats/utils/common.py +11 -45
- waldiez/exporting/chats/utils/group.py +55 -0
- waldiez/exporting/chats/utils/nested.py +37 -52
- waldiez/exporting/chats/utils/sequential.py +72 -61
- waldiez/exporting/chats/utils/{single_chat.py → single.py} +48 -50
- waldiez/exporting/core/__init__.py +196 -0
- waldiez/exporting/core/constants.py +17 -0
- waldiez/exporting/core/content.py +69 -0
- waldiez/exporting/core/context.py +244 -0
- waldiez/exporting/core/enums.py +89 -0
- waldiez/exporting/core/errors.py +19 -0
- waldiez/exporting/core/exporter.py +390 -0
- waldiez/exporting/core/exporters.py +67 -0
- waldiez/exporting/core/extras/__init__.py +39 -0
- waldiez/exporting/core/extras/agent_extras/__init__.py +27 -0
- waldiez/exporting/core/extras/agent_extras/captain_extras.py +57 -0
- waldiez/exporting/core/extras/agent_extras/group_manager_extras.py +102 -0
- waldiez/exporting/core/extras/agent_extras/rag_user_extras.py +53 -0
- waldiez/exporting/core/extras/agent_extras/reasoning_extras.py +68 -0
- waldiez/exporting/core/extras/agent_extras/standard_extras.py +263 -0
- waldiez/exporting/core/extras/base.py +241 -0
- waldiez/exporting/core/extras/chat_extras.py +118 -0
- waldiez/exporting/core/extras/flow_extras.py +70 -0
- waldiez/exporting/core/extras/model_extras.py +73 -0
- waldiez/exporting/core/extras/path_resolver.py +93 -0
- waldiez/exporting/core/extras/serializer.py +138 -0
- waldiez/exporting/core/extras/tool_extras.py +82 -0
- waldiez/exporting/core/protocols.py +259 -0
- waldiez/exporting/core/result.py +705 -0
- waldiez/exporting/core/types.py +329 -0
- waldiez/exporting/core/utils/__init__.py +11 -0
- waldiez/exporting/core/utils/comment.py +33 -0
- waldiez/exporting/core/utils/llm_config.py +117 -0
- waldiez/exporting/core/validation.py +96 -0
- waldiez/exporting/flow/__init__.py +6 -2
- waldiez/exporting/flow/execution_generator.py +193 -0
- waldiez/exporting/flow/exporter.py +107 -0
- waldiez/exporting/flow/factory.py +94 -0
- waldiez/exporting/flow/file_generator.py +214 -0
- waldiez/exporting/flow/merger.py +387 -0
- waldiez/exporting/flow/orchestrator.py +411 -0
- waldiez/exporting/flow/utils/__init__.py +9 -36
- waldiez/exporting/flow/utils/common.py +206 -0
- waldiez/exporting/flow/utils/importing.py +373 -0
- waldiez/exporting/flow/utils/linting.py +200 -0
- waldiez/exporting/flow/utils/{logging_utils.py → logging.py} +23 -9
- waldiez/exporting/models/__init__.py +3 -1
- waldiez/exporting/models/exporter.py +233 -0
- waldiez/exporting/models/factory.py +66 -0
- waldiez/exporting/models/processor.py +139 -0
- waldiez/exporting/tools/__init__.py +11 -0
- waldiez/exporting/tools/exporter.py +207 -0
- waldiez/exporting/tools/factory.py +57 -0
- waldiez/exporting/tools/processor.py +248 -0
- waldiez/exporting/tools/registration.py +133 -0
- waldiez/io/__init__.py +128 -0
- waldiez/io/_ws.py +199 -0
- waldiez/io/models/__init__.py +60 -0
- waldiez/io/models/base.py +66 -0
- waldiez/io/models/constants.py +78 -0
- waldiez/io/models/content/__init__.py +23 -0
- waldiez/io/models/content/audio.py +43 -0
- waldiez/io/models/content/base.py +45 -0
- waldiez/io/models/content/file.py +43 -0
- waldiez/io/models/content/image.py +96 -0
- waldiez/io/models/content/text.py +37 -0
- waldiez/io/models/content/video.py +43 -0
- waldiez/io/models/user_input.py +269 -0
- waldiez/io/models/user_response.py +215 -0
- waldiez/io/mqtt.py +681 -0
- waldiez/io/redis.py +782 -0
- waldiez/io/structured.py +419 -0
- waldiez/io/utils.py +184 -0
- waldiez/io/ws.py +298 -0
- waldiez/logger.py +481 -0
- waldiez/models/__init__.py +108 -51
- waldiez/models/agents/__init__.py +34 -70
- waldiez/models/agents/agent/__init__.py +10 -4
- waldiez/models/agents/agent/agent.py +466 -65
- waldiez/models/agents/agent/agent_data.py +119 -47
- waldiez/models/agents/agent/agent_type.py +13 -2
- waldiez/models/agents/agent/code_execution.py +12 -12
- waldiez/models/agents/agent/human_input_mode.py +8 -0
- waldiez/models/agents/agent/{linked_skill.py → linked_tool.py} +7 -7
- waldiez/models/agents/agent/nested_chat.py +35 -7
- waldiez/models/agents/agent/termination_message.py +30 -22
- waldiez/models/agents/{swarm_agent → agent}/update_system_message.py +22 -22
- waldiez/models/agents/agents.py +58 -63
- waldiez/models/agents/assistant/assistant.py +4 -4
- waldiez/models/agents/assistant/assistant_data.py +13 -1
- waldiez/models/agents/{captain_agent → captain}/captain_agent.py +5 -5
- waldiez/models/agents/{captain_agent → captain}/captain_agent_data.py +5 -5
- waldiez/models/agents/extra_requirements.py +11 -16
- waldiez/models/agents/group_manager/group_manager.py +103 -13
- waldiez/models/agents/group_manager/group_manager_data.py +36 -14
- waldiez/models/agents/group_manager/speakers.py +77 -24
- waldiez/models/agents/{rag_user → rag_user_proxy}/__init__.py +16 -16
- waldiez/models/agents/rag_user_proxy/rag_user_proxy.py +64 -0
- waldiez/models/agents/{rag_user/rag_user_data.py → rag_user_proxy/rag_user_proxy_data.py} +6 -5
- waldiez/models/agents/{rag_user → rag_user_proxy}/retrieve_config.py +182 -114
- waldiez/models/agents/{rag_user → rag_user_proxy}/vector_db_config.py +13 -13
- waldiez/models/agents/reasoning/reasoning_agent.py +6 -6
- waldiez/models/agents/reasoning/reasoning_agent_data.py +110 -63
- waldiez/models/agents/reasoning/reasoning_agent_reason_config.py +38 -10
- waldiez/models/agents/user_proxy/user_proxy.py +11 -7
- waldiez/models/agents/user_proxy/user_proxy_data.py +2 -2
- waldiez/models/chat/__init__.py +2 -1
- waldiez/models/chat/chat.py +166 -87
- waldiez/models/chat/chat_data.py +99 -136
- waldiez/models/chat/chat_message.py +33 -23
- waldiez/models/chat/chat_nested.py +31 -30
- waldiez/models/chat/chat_summary.py +10 -8
- waldiez/models/common/__init__.py +52 -2
- waldiez/models/common/ag2_version.py +1 -1
- waldiez/models/common/base.py +38 -7
- waldiez/models/common/dict_utils.py +42 -17
- waldiez/models/common/handoff.py +459 -0
- waldiez/models/common/id_generator.py +19 -0
- waldiez/models/common/method_utils.py +130 -68
- waldiez/{exporting/base/utils → models/common}/naming.py +38 -61
- waldiez/models/common/waldiez_version.py +37 -0
- waldiez/models/flow/__init__.py +9 -2
- waldiez/models/flow/connection.py +18 -0
- waldiez/models/flow/flow.py +311 -215
- waldiez/models/flow/flow_data.py +207 -40
- waldiez/models/flow/info.py +85 -0
- waldiez/models/flow/naming.py +131 -0
- waldiez/models/model/__init__.py +7 -1
- waldiez/models/model/extra_requirements.py +3 -12
- waldiez/models/model/model.py +76 -21
- waldiez/models/model/model_data.py +108 -20
- waldiez/models/tool/__init__.py +16 -0
- waldiez/models/tool/extra_requirements.py +36 -0
- waldiez/models/{skill/skill.py → tool/tool.py} +88 -88
- waldiez/models/tool/tool_data.py +51 -0
- waldiez/models/tool/tool_type.py +8 -0
- waldiez/models/waldiez.py +97 -80
- waldiez/runner.py +114 -49
- waldiez/running/__init__.py +1 -1
- waldiez/running/environment.py +49 -68
- waldiez/running/gen_seq_diagram.py +16 -14
- waldiez/running/running.py +53 -34
- waldiez/utils/__init__.py +0 -4
- waldiez/utils/cli_extras/jupyter.py +5 -3
- waldiez/utils/cli_extras/runner.py +6 -4
- waldiez/utils/cli_extras/studio.py +6 -4
- waldiez/utils/conflict_checker.py +15 -9
- waldiez/utils/flaml_warnings.py +5 -5
- {waldiez-0.4.6.dist-info → waldiez-0.4.8.dist-info}/METADATA +235 -91
- waldiez-0.4.8.dist-info/RECORD +200 -0
- waldiez/exporting/agent/agent_exporter.py +0 -297
- waldiez/exporting/agent/utils/__init__.py +0 -23
- waldiez/exporting/agent/utils/captain_agent.py +0 -263
- waldiez/exporting/agent/utils/code_execution.py +0 -65
- waldiez/exporting/agent/utils/group_manager.py +0 -220
- waldiez/exporting/agent/utils/rag_user/__init__.py +0 -7
- waldiez/exporting/agent/utils/rag_user/rag_user.py +0 -209
- waldiez/exporting/agent/utils/reasoning.py +0 -36
- waldiez/exporting/agent/utils/swarm_agent.py +0 -469
- waldiez/exporting/agent/utils/teachability.py +0 -41
- waldiez/exporting/agent/utils/termination_message.py +0 -44
- waldiez/exporting/base/__init__.py +0 -25
- waldiez/exporting/base/agent_position.py +0 -75
- waldiez/exporting/base/base_exporter.py +0 -118
- waldiez/exporting/base/export_position.py +0 -48
- waldiez/exporting/base/import_position.py +0 -23
- waldiez/exporting/base/mixin.py +0 -137
- waldiez/exporting/base/utils/__init__.py +0 -18
- waldiez/exporting/base/utils/comments.py +0 -96
- waldiez/exporting/base/utils/path_check.py +0 -68
- waldiez/exporting/base/utils/to_string.py +0 -84
- waldiez/exporting/chats/chats_exporter.py +0 -240
- waldiez/exporting/chats/utils/swarm.py +0 -210
- waldiez/exporting/flow/flow_exporter.py +0 -528
- waldiez/exporting/flow/utils/agent_utils.py +0 -204
- waldiez/exporting/flow/utils/chat_utils.py +0 -71
- waldiez/exporting/flow/utils/def_main.py +0 -77
- waldiez/exporting/flow/utils/flow_content.py +0 -202
- waldiez/exporting/flow/utils/flow_names.py +0 -116
- waldiez/exporting/flow/utils/importing_utils.py +0 -227
- waldiez/exporting/models/models_exporter.py +0 -199
- waldiez/exporting/models/utils.py +0 -174
- waldiez/exporting/skills/__init__.py +0 -9
- waldiez/exporting/skills/skills_exporter.py +0 -176
- waldiez/exporting/skills/utils.py +0 -369
- waldiez/models/agents/agent/teachability.py +0 -70
- waldiez/models/agents/rag_user/rag_user.py +0 -60
- waldiez/models/agents/swarm_agent/__init__.py +0 -50
- waldiez/models/agents/swarm_agent/after_work.py +0 -179
- waldiez/models/agents/swarm_agent/on_condition.py +0 -105
- waldiez/models/agents/swarm_agent/on_condition_available.py +0 -142
- waldiez/models/agents/swarm_agent/on_condition_target.py +0 -40
- waldiez/models/agents/swarm_agent/swarm_agent.py +0 -107
- waldiez/models/agents/swarm_agent/swarm_agent_data.py +0 -124
- waldiez/models/flow/utils.py +0 -232
- waldiez/models/skill/__init__.py +0 -16
- waldiez/models/skill/extra_requirements.py +0 -36
- waldiez/models/skill/skill_data.py +0 -53
- waldiez/models/skill/skill_type.py +0 -8
- waldiez/utils/pysqlite3_checker.py +0 -308
- waldiez/utils/rdps_checker.py +0 -122
- waldiez-0.4.6.dist-info/RECORD +0 -149
- /waldiez/models/agents/{captain_agent → captain}/__init__.py +0 -0
- /waldiez/models/agents/{captain_agent → captain}/captain_agent_lib_entry.py +0 -0
- {waldiez-0.4.6.dist-info → waldiez-0.4.8.dist-info}/WHEEL +0 -0
- {waldiez-0.4.6.dist-info → waldiez-0.4.8.dist-info}/entry_points.txt +0 -0
- {waldiez-0.4.6.dist-info → waldiez-0.4.8.dist-info}/licenses/LICENSE +0 -0
- {waldiez-0.4.6.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
|
+
)
|