langfun 0.1.2.dev202509120804__py3-none-any.whl → 0.1.2.dev202512150805__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.
- langfun/__init__.py +1 -1
- langfun/core/__init__.py +7 -1
- langfun/core/agentic/__init__.py +8 -1
- langfun/core/agentic/action.py +740 -112
- langfun/core/agentic/action_eval.py +9 -2
- langfun/core/agentic/action_test.py +189 -24
- langfun/core/async_support.py +104 -5
- langfun/core/async_support_test.py +23 -0
- langfun/core/coding/python/correction.py +19 -9
- langfun/core/coding/python/execution.py +14 -12
- langfun/core/coding/python/generation.py +21 -16
- langfun/core/coding/python/sandboxing.py +23 -3
- langfun/core/component.py +42 -3
- langfun/core/concurrent.py +70 -6
- langfun/core/concurrent_test.py +9 -2
- langfun/core/console.py +1 -1
- langfun/core/data/conversion/anthropic.py +12 -3
- langfun/core/data/conversion/anthropic_test.py +8 -6
- langfun/core/data/conversion/gemini.py +11 -2
- langfun/core/data/conversion/gemini_test.py +48 -9
- langfun/core/data/conversion/openai.py +145 -31
- langfun/core/data/conversion/openai_test.py +161 -17
- langfun/core/eval/base.py +48 -44
- langfun/core/eval/base_test.py +5 -5
- langfun/core/eval/matching.py +5 -2
- langfun/core/eval/patching.py +3 -3
- langfun/core/eval/scoring.py +4 -3
- langfun/core/eval/v2/__init__.py +3 -0
- langfun/core/eval/v2/checkpointing.py +148 -46
- langfun/core/eval/v2/checkpointing_test.py +9 -2
- langfun/core/eval/v2/config_saver.py +37 -0
- langfun/core/eval/v2/config_saver_test.py +36 -0
- langfun/core/eval/v2/eval_test_helper.py +104 -3
- langfun/core/eval/v2/evaluation.py +102 -19
- langfun/core/eval/v2/evaluation_test.py +9 -3
- langfun/core/eval/v2/example.py +50 -40
- langfun/core/eval/v2/example_test.py +16 -8
- langfun/core/eval/v2/experiment.py +95 -20
- langfun/core/eval/v2/experiment_test.py +19 -0
- langfun/core/eval/v2/metric_values.py +31 -3
- langfun/core/eval/v2/metric_values_test.py +32 -0
- langfun/core/eval/v2/metrics.py +157 -44
- langfun/core/eval/v2/metrics_test.py +39 -18
- langfun/core/eval/v2/progress.py +31 -1
- langfun/core/eval/v2/progress_test.py +27 -0
- langfun/core/eval/v2/progress_tracking.py +13 -5
- langfun/core/eval/v2/progress_tracking_test.py +9 -1
- langfun/core/eval/v2/reporting.py +88 -71
- langfun/core/eval/v2/reporting_test.py +24 -6
- langfun/core/eval/v2/runners/__init__.py +30 -0
- langfun/core/eval/v2/{runners.py → runners/base.py} +73 -180
- langfun/core/eval/v2/runners/beam.py +354 -0
- langfun/core/eval/v2/runners/beam_test.py +153 -0
- langfun/core/eval/v2/runners/ckpt_monitor.py +350 -0
- langfun/core/eval/v2/runners/ckpt_monitor_test.py +213 -0
- langfun/core/eval/v2/runners/debug.py +40 -0
- langfun/core/eval/v2/runners/debug_test.py +76 -0
- langfun/core/eval/v2/runners/parallel.py +243 -0
- langfun/core/eval/v2/runners/parallel_test.py +182 -0
- langfun/core/eval/v2/runners/sequential.py +47 -0
- langfun/core/eval/v2/runners/sequential_test.py +169 -0
- langfun/core/langfunc.py +45 -130
- langfun/core/langfunc_test.py +7 -5
- langfun/core/language_model.py +189 -36
- langfun/core/language_model_test.py +54 -3
- langfun/core/llms/__init__.py +14 -1
- langfun/core/llms/anthropic.py +157 -2
- langfun/core/llms/azure_openai.py +29 -17
- langfun/core/llms/cache/base.py +25 -3
- langfun/core/llms/cache/in_memory.py +48 -7
- langfun/core/llms/cache/in_memory_test.py +14 -4
- langfun/core/llms/compositional.py +25 -1
- langfun/core/llms/deepseek.py +30 -2
- langfun/core/llms/fake.py +32 -1
- langfun/core/llms/gemini.py +90 -12
- langfun/core/llms/gemini_test.py +110 -0
- langfun/core/llms/google_genai.py +52 -1
- langfun/core/llms/groq.py +28 -3
- langfun/core/llms/llama_cpp.py +23 -4
- langfun/core/llms/openai.py +120 -3
- langfun/core/llms/openai_compatible.py +148 -27
- langfun/core/llms/openai_compatible_test.py +207 -20
- langfun/core/llms/openai_test.py +0 -2
- langfun/core/llms/rest.py +16 -1
- langfun/core/llms/vertexai.py +78 -8
- langfun/core/logging.py +1 -1
- langfun/core/mcp/__init__.py +10 -0
- langfun/core/mcp/client.py +177 -0
- langfun/core/mcp/client_test.py +71 -0
- langfun/core/mcp/session.py +241 -0
- langfun/core/mcp/session_test.py +54 -0
- langfun/core/mcp/testing/simple_mcp_client.py +33 -0
- langfun/core/mcp/testing/simple_mcp_server.py +33 -0
- langfun/core/mcp/tool.py +254 -0
- langfun/core/mcp/tool_test.py +197 -0
- langfun/core/memory.py +1 -0
- langfun/core/message.py +160 -55
- langfun/core/message_test.py +65 -81
- langfun/core/modalities/__init__.py +8 -0
- langfun/core/modalities/audio.py +21 -1
- langfun/core/modalities/image.py +73 -3
- langfun/core/modalities/image_test.py +116 -0
- langfun/core/modalities/mime.py +78 -4
- langfun/core/modalities/mime_test.py +59 -0
- langfun/core/modalities/pdf.py +19 -1
- langfun/core/modalities/video.py +21 -1
- langfun/core/modality.py +167 -29
- langfun/core/modality_test.py +42 -12
- langfun/core/natural_language.py +1 -1
- langfun/core/sampling.py +4 -4
- langfun/core/sampling_test.py +20 -4
- langfun/core/structured/__init__.py +2 -24
- langfun/core/structured/completion.py +34 -44
- langfun/core/structured/completion_test.py +23 -43
- langfun/core/structured/description.py +54 -50
- langfun/core/structured/function_generation.py +29 -12
- langfun/core/structured/mapping.py +81 -37
- langfun/core/structured/parsing.py +95 -79
- langfun/core/structured/parsing_test.py +0 -3
- langfun/core/structured/querying.py +230 -154
- langfun/core/structured/querying_test.py +69 -33
- langfun/core/structured/schema/__init__.py +49 -0
- langfun/core/structured/schema/base.py +664 -0
- langfun/core/structured/schema/base_test.py +531 -0
- langfun/core/structured/schema/json.py +174 -0
- langfun/core/structured/schema/json_test.py +121 -0
- langfun/core/structured/schema/python.py +316 -0
- langfun/core/structured/schema/python_test.py +410 -0
- langfun/core/structured/schema_generation.py +33 -14
- langfun/core/structured/scoring.py +47 -36
- langfun/core/structured/tokenization.py +26 -11
- langfun/core/subscription.py +2 -2
- langfun/core/template.py +175 -50
- langfun/core/template_test.py +123 -17
- langfun/env/__init__.py +43 -0
- langfun/env/base_environment.py +827 -0
- langfun/env/base_environment_test.py +473 -0
- langfun/env/base_feature.py +304 -0
- langfun/env/base_feature_test.py +228 -0
- langfun/env/base_sandbox.py +842 -0
- langfun/env/base_sandbox_test.py +1235 -0
- langfun/env/event_handlers/__init__.py +14 -0
- langfun/env/event_handlers/chain.py +233 -0
- langfun/env/event_handlers/chain_test.py +253 -0
- langfun/env/event_handlers/event_logger.py +472 -0
- langfun/env/event_handlers/event_logger_test.py +304 -0
- langfun/env/event_handlers/metric_writer.py +726 -0
- langfun/env/event_handlers/metric_writer_test.py +214 -0
- langfun/env/interface.py +1640 -0
- langfun/env/interface_test.py +153 -0
- langfun/env/load_balancers.py +59 -0
- langfun/env/load_balancers_test.py +141 -0
- langfun/env/test_utils.py +507 -0
- {langfun-0.1.2.dev202509120804.dist-info → langfun-0.1.2.dev202512150805.dist-info}/METADATA +7 -3
- langfun-0.1.2.dev202512150805.dist-info/RECORD +217 -0
- langfun/core/eval/v2/runners_test.py +0 -343
- langfun/core/structured/schema.py +0 -987
- langfun/core/structured/schema_test.py +0 -982
- langfun-0.1.2.dev202509120804.dist-info/RECORD +0 -172
- {langfun-0.1.2.dev202509120804.dist-info → langfun-0.1.2.dev202512150805.dist-info}/WHEEL +0 -0
- {langfun-0.1.2.dev202509120804.dist-info → langfun-0.1.2.dev202512150805.dist-info}/licenses/LICENSE +0 -0
- {langfun-0.1.2.dev202509120804.dist-info → langfun-0.1.2.dev202512150805.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
# Copyright 2025 The Langfun Authors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""Environment event logger."""
|
|
15
|
+
|
|
16
|
+
import re
|
|
17
|
+
import time
|
|
18
|
+
from typing import Annotated
|
|
19
|
+
from langfun.env import interface
|
|
20
|
+
import pyglove as pg
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class EventLogger(pg.Object, interface.EventHandler):
|
|
24
|
+
"""Event handler for logging debugger."""
|
|
25
|
+
|
|
26
|
+
colored: Annotated[
|
|
27
|
+
bool,
|
|
28
|
+
(
|
|
29
|
+
'If True, log events with colors.'
|
|
30
|
+
)
|
|
31
|
+
] = False
|
|
32
|
+
|
|
33
|
+
regex: Annotated[
|
|
34
|
+
str | list[str] | None,
|
|
35
|
+
(
|
|
36
|
+
'One or a list of regular expressions to filter event messages. '
|
|
37
|
+
'If None, no filtering will be applied.'
|
|
38
|
+
)
|
|
39
|
+
] = None
|
|
40
|
+
|
|
41
|
+
error_only: Annotated[
|
|
42
|
+
bool,
|
|
43
|
+
(
|
|
44
|
+
'If True, log events with errors only.'
|
|
45
|
+
)
|
|
46
|
+
] = False
|
|
47
|
+
|
|
48
|
+
sandbox_status: Annotated[
|
|
49
|
+
bool,
|
|
50
|
+
(
|
|
51
|
+
'If True, log events for sandbox status changes.'
|
|
52
|
+
)
|
|
53
|
+
] = True
|
|
54
|
+
|
|
55
|
+
feature_status: Annotated[
|
|
56
|
+
bool,
|
|
57
|
+
(
|
|
58
|
+
'If True, log events for feature setup/teardown updates.'
|
|
59
|
+
)
|
|
60
|
+
] = True
|
|
61
|
+
|
|
62
|
+
session_status: Annotated[
|
|
63
|
+
bool,
|
|
64
|
+
(
|
|
65
|
+
'If True, log events for session start/end status update.'
|
|
66
|
+
)
|
|
67
|
+
] = True
|
|
68
|
+
|
|
69
|
+
housekeep_status: Annotated[
|
|
70
|
+
bool,
|
|
71
|
+
(
|
|
72
|
+
'If True, log housekeeping events.'
|
|
73
|
+
)
|
|
74
|
+
] = True
|
|
75
|
+
|
|
76
|
+
stats_report_interval: Annotated[
|
|
77
|
+
float | None,
|
|
78
|
+
(
|
|
79
|
+
'The minimum interval in seconds for reporting the environment '
|
|
80
|
+
'stats. If None, stats will not be reported.'
|
|
81
|
+
)
|
|
82
|
+
] = 300.0
|
|
83
|
+
|
|
84
|
+
def _on_bound(self) -> None:
|
|
85
|
+
super()._on_bound()
|
|
86
|
+
|
|
87
|
+
regex_exps = self.regex
|
|
88
|
+
if isinstance(regex_exps, str):
|
|
89
|
+
regex_exps = [regex_exps]
|
|
90
|
+
elif regex_exps is None:
|
|
91
|
+
regex_exps = []
|
|
92
|
+
self._regex_exps = [re.compile(x) for x in regex_exps]
|
|
93
|
+
self._last_stats_report_time = None
|
|
94
|
+
|
|
95
|
+
def _maybe_colored(
|
|
96
|
+
self, message: str, color: str, styles: list[str] | None = None
|
|
97
|
+
) -> str:
|
|
98
|
+
if self.colored:
|
|
99
|
+
return pg.colored(message, color, styles=styles)
|
|
100
|
+
return message
|
|
101
|
+
|
|
102
|
+
def _format_message(
|
|
103
|
+
self,
|
|
104
|
+
message: str,
|
|
105
|
+
error: BaseException | None,
|
|
106
|
+
) -> str:
|
|
107
|
+
if error is not None:
|
|
108
|
+
message = (
|
|
109
|
+
f'{message} with error: {pg.utils.ErrorInfo.from_exception(error)}'
|
|
110
|
+
)
|
|
111
|
+
return message
|
|
112
|
+
|
|
113
|
+
def _keep(
|
|
114
|
+
self,
|
|
115
|
+
message: str,
|
|
116
|
+
error: BaseException | None,
|
|
117
|
+
) -> bool:
|
|
118
|
+
if error is None and self.error_only:
|
|
119
|
+
return False
|
|
120
|
+
if self._regex_exps and all(
|
|
121
|
+
not exp.match(message) for exp in self._regex_exps
|
|
122
|
+
):
|
|
123
|
+
return False
|
|
124
|
+
return True
|
|
125
|
+
|
|
126
|
+
def on_environment_starting(
|
|
127
|
+
self,
|
|
128
|
+
environment: interface.Environment,
|
|
129
|
+
) -> None:
|
|
130
|
+
"""Called when the environment is starting."""
|
|
131
|
+
self._print(
|
|
132
|
+
f'[{environment.id}] environment starting',
|
|
133
|
+
error=None,
|
|
134
|
+
color='green',
|
|
135
|
+
styles=['bold'],
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def on_environment_shutting_down(
|
|
139
|
+
self,
|
|
140
|
+
environment: interface.Environment,
|
|
141
|
+
offline_duration: float,
|
|
142
|
+
) -> None:
|
|
143
|
+
"""Called when the environment is shutting down."""
|
|
144
|
+
self._print(
|
|
145
|
+
f'[{environment.id}] environment shutting down '
|
|
146
|
+
f'(offline_duration={offline_duration:.2f} seconds)',
|
|
147
|
+
error=None,
|
|
148
|
+
color='green',
|
|
149
|
+
styles=['bold'],
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
def on_environment_start(
|
|
153
|
+
self,
|
|
154
|
+
environment: interface.Environment,
|
|
155
|
+
duration: float,
|
|
156
|
+
error: BaseException | None
|
|
157
|
+
) -> None:
|
|
158
|
+
"""Called when the environment is started."""
|
|
159
|
+
self._print(
|
|
160
|
+
f'[{environment.id}] environment started '
|
|
161
|
+
f'(duration={duration:.2f} seconds)',
|
|
162
|
+
error=error,
|
|
163
|
+
color='green',
|
|
164
|
+
styles=['bold'],
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
def on_environment_housekeep(
|
|
168
|
+
self,
|
|
169
|
+
environment: interface.Environment,
|
|
170
|
+
counter: int,
|
|
171
|
+
duration: float,
|
|
172
|
+
error: BaseException | None,
|
|
173
|
+
**kwargs
|
|
174
|
+
) -> None:
|
|
175
|
+
"""Called when the environment is housekeeping."""
|
|
176
|
+
if self.housekeep_status:
|
|
177
|
+
self._print(
|
|
178
|
+
f'[{environment.id}] environment housekeeping complete '
|
|
179
|
+
f'(counter={counter}, duration={duration:.2f} seconds, '
|
|
180
|
+
f'housekeep_info={kwargs})',
|
|
181
|
+
error=error,
|
|
182
|
+
color='green',
|
|
183
|
+
)
|
|
184
|
+
if (self.stats_report_interval is not None and
|
|
185
|
+
(self._last_stats_report_time is None
|
|
186
|
+
or time.time() - self._last_stats_report_time
|
|
187
|
+
> self.stats_report_interval)):
|
|
188
|
+
self._write_log(
|
|
189
|
+
f'[{environment.id}] environment stats: {environment.stats()}',
|
|
190
|
+
color='magenta',
|
|
191
|
+
error=None,
|
|
192
|
+
)
|
|
193
|
+
self._last_stats_report_time = time.time()
|
|
194
|
+
|
|
195
|
+
def on_environment_shutdown(
|
|
196
|
+
self,
|
|
197
|
+
environment: interface.Environment,
|
|
198
|
+
duration: float,
|
|
199
|
+
lifetime: float,
|
|
200
|
+
error: BaseException | None
|
|
201
|
+
) -> None:
|
|
202
|
+
"""Called when the environment is shutdown."""
|
|
203
|
+
self._print(
|
|
204
|
+
f'[{environment.id}] environment shutdown '
|
|
205
|
+
f'(duration={duration:.2f} seconds), lifetime={lifetime:.2f} seconds)',
|
|
206
|
+
error=error,
|
|
207
|
+
color='green',
|
|
208
|
+
styles=['bold'],
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
def on_sandbox_start(
|
|
212
|
+
self,
|
|
213
|
+
sandbox: interface.Sandbox,
|
|
214
|
+
duration: float,
|
|
215
|
+
error: BaseException | None
|
|
216
|
+
) -> None:
|
|
217
|
+
if self.sandbox_status:
|
|
218
|
+
self._print(
|
|
219
|
+
f'[{sandbox.id}] sandbox started '
|
|
220
|
+
f'(duration={duration:.2f} seconds)',
|
|
221
|
+
error=error,
|
|
222
|
+
color='white',
|
|
223
|
+
styles=['bold'],
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def on_sandbox_status_change(
|
|
227
|
+
self,
|
|
228
|
+
sandbox: interface.Sandbox,
|
|
229
|
+
old_status: interface.Sandbox.Status,
|
|
230
|
+
new_status: interface.Sandbox.Status,
|
|
231
|
+
span: float
|
|
232
|
+
) -> None:
|
|
233
|
+
if self.sandbox_status:
|
|
234
|
+
self._print(
|
|
235
|
+
f'[{sandbox.id}] {old_status.value} '
|
|
236
|
+
f'({span:.2f} seconds) -> {new_status.value}',
|
|
237
|
+
error=None,
|
|
238
|
+
color='white',
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
def on_sandbox_shutdown(
|
|
242
|
+
self,
|
|
243
|
+
sandbox: interface.Sandbox,
|
|
244
|
+
duration: float,
|
|
245
|
+
lifetime: float,
|
|
246
|
+
error: BaseException | None
|
|
247
|
+
) -> None:
|
|
248
|
+
if self.sandbox_status:
|
|
249
|
+
self._print(
|
|
250
|
+
f'[{sandbox.id}] sandbox shutdown '
|
|
251
|
+
f'(duration={duration:.2f} seconds), '
|
|
252
|
+
f'lifetime={lifetime:.2f} seconds)',
|
|
253
|
+
error=error,
|
|
254
|
+
color='white',
|
|
255
|
+
styles=['bold'],
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
def on_sandbox_session_start(
|
|
259
|
+
self,
|
|
260
|
+
sandbox: interface.Sandbox,
|
|
261
|
+
session_id: str,
|
|
262
|
+
duration: float,
|
|
263
|
+
error: BaseException | None
|
|
264
|
+
) -> None:
|
|
265
|
+
"""Called when a sandbox session starts."""
|
|
266
|
+
if self.session_status:
|
|
267
|
+
self._print(
|
|
268
|
+
f'[{sandbox.id}@{session_id}] sandbox session started '
|
|
269
|
+
f'(duration={duration:.2f} seconds)',
|
|
270
|
+
error=error,
|
|
271
|
+
color='blue',
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
def on_sandbox_session_end(
|
|
275
|
+
self,
|
|
276
|
+
sandbox: interface.Sandbox,
|
|
277
|
+
session_id: str,
|
|
278
|
+
duration: float,
|
|
279
|
+
lifetime: float,
|
|
280
|
+
error: BaseException | None
|
|
281
|
+
) -> None:
|
|
282
|
+
"""Called when a sandbox session ends."""
|
|
283
|
+
if self.session_status:
|
|
284
|
+
self._print(
|
|
285
|
+
f'[{sandbox.id}@{session_id}] sandbox session ended '
|
|
286
|
+
f'(duration={duration:.2f} seconds), '
|
|
287
|
+
f'lifetime={lifetime:.2f} seconds)',
|
|
288
|
+
error=error,
|
|
289
|
+
color='blue',
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
def on_sandbox_activity(
|
|
293
|
+
self,
|
|
294
|
+
name: str,
|
|
295
|
+
sandbox: interface.Sandbox,
|
|
296
|
+
session_id: str | None,
|
|
297
|
+
duration: float,
|
|
298
|
+
error: BaseException | None,
|
|
299
|
+
**kwargs
|
|
300
|
+
) -> None:
|
|
301
|
+
"""Called when a sandbox activity is performed."""
|
|
302
|
+
log_id = f'{sandbox.id}@{session_id or "<idle>"}'
|
|
303
|
+
color = 'yellow' if session_id is None else 'cyan'
|
|
304
|
+
self._print(
|
|
305
|
+
f'[{log_id}] sandbox call {name!r} '
|
|
306
|
+
f'(duration={duration:.2f} seconds, kwargs={kwargs}) ',
|
|
307
|
+
error,
|
|
308
|
+
color=color
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
def on_sandbox_housekeep(
|
|
312
|
+
self,
|
|
313
|
+
sandbox: interface.Sandbox,
|
|
314
|
+
counter: int,
|
|
315
|
+
duration: float,
|
|
316
|
+
error: BaseException | None,
|
|
317
|
+
**kwargs
|
|
318
|
+
) -> None:
|
|
319
|
+
"""Called when a sandbox feature is housekeeping."""
|
|
320
|
+
if self.sandbox_status and self.housekeep_status:
|
|
321
|
+
self._print(
|
|
322
|
+
f'[{sandbox.id}] sandbox housekeeping complete '
|
|
323
|
+
f'(counter={counter}, duration={duration:.2f} seconds, '
|
|
324
|
+
f'housekeep_info={kwargs})',
|
|
325
|
+
error=error,
|
|
326
|
+
color='white',
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
def on_feature_setup(
|
|
330
|
+
self,
|
|
331
|
+
feature: interface.Feature,
|
|
332
|
+
duration: float,
|
|
333
|
+
error: BaseException | None
|
|
334
|
+
) -> None:
|
|
335
|
+
"""Called when a sandbox feature is setup."""
|
|
336
|
+
if self.feature_status:
|
|
337
|
+
self._print(
|
|
338
|
+
f'[{feature.id}] feature setup complete '
|
|
339
|
+
f'(duration={duration:.2f} seconds)',
|
|
340
|
+
error=error,
|
|
341
|
+
color='white',
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
def on_feature_teardown(
|
|
345
|
+
self,
|
|
346
|
+
feature: interface.Feature,
|
|
347
|
+
duration: float,
|
|
348
|
+
error: BaseException | None
|
|
349
|
+
) -> None:
|
|
350
|
+
"""Called when a sandbox feature is teardown."""
|
|
351
|
+
if self.feature_status:
|
|
352
|
+
self._print(
|
|
353
|
+
f'[{feature.id}] feature teardown complete '
|
|
354
|
+
f'(duration={duration:.2f} seconds)',
|
|
355
|
+
error=error,
|
|
356
|
+
color='white',
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
def on_feature_setup_session(
|
|
360
|
+
self,
|
|
361
|
+
feature: interface.Feature,
|
|
362
|
+
session_id: str | None,
|
|
363
|
+
duration: float,
|
|
364
|
+
error: BaseException | None
|
|
365
|
+
) -> None:
|
|
366
|
+
"""Called when a sandbox feature is setup."""
|
|
367
|
+
if self.feature_status:
|
|
368
|
+
self._print(
|
|
369
|
+
f'[{feature.id}@{session_id or "<idle>"}] '
|
|
370
|
+
f'feature setup complete (duration={duration:.2f} seconds)',
|
|
371
|
+
error=error,
|
|
372
|
+
color='yellow',
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
def on_feature_teardown_session(
|
|
376
|
+
self,
|
|
377
|
+
feature: interface.Feature,
|
|
378
|
+
session_id: str,
|
|
379
|
+
duration: float,
|
|
380
|
+
error: BaseException | None
|
|
381
|
+
) -> None:
|
|
382
|
+
"""Called when a sandbox feature is teardown."""
|
|
383
|
+
if self.feature_status:
|
|
384
|
+
self._print(
|
|
385
|
+
f'[{feature.id}@{session_id}] '
|
|
386
|
+
f'feature teardown complete (duration={duration:.2f} seconds)',
|
|
387
|
+
error=error,
|
|
388
|
+
color='yellow',
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
def on_feature_activity(
|
|
392
|
+
self,
|
|
393
|
+
name: str,
|
|
394
|
+
feature: interface.Feature,
|
|
395
|
+
session_id: str | None,
|
|
396
|
+
duration: float,
|
|
397
|
+
error: BaseException | None,
|
|
398
|
+
**kwargs
|
|
399
|
+
) -> None:
|
|
400
|
+
"""Called when a feature activity is performed."""
|
|
401
|
+
log_id = f'{feature.id}@{session_id or "<idle>"}'
|
|
402
|
+
color = 'yellow' if session_id is None else 'cyan'
|
|
403
|
+
self._print(
|
|
404
|
+
f'[{log_id}] feature call {name!r} '
|
|
405
|
+
f'(duration={duration:.2f} seconds, kwargs={kwargs}) ',
|
|
406
|
+
error,
|
|
407
|
+
color=color
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
def on_feature_housekeep(
|
|
411
|
+
self,
|
|
412
|
+
feature: interface.Feature,
|
|
413
|
+
counter: int,
|
|
414
|
+
duration: float,
|
|
415
|
+
error: BaseException | None,
|
|
416
|
+
**kwargs
|
|
417
|
+
) -> None:
|
|
418
|
+
"""Called when a sandbox feature is housekeeping."""
|
|
419
|
+
if self.feature_status and self.housekeep_status:
|
|
420
|
+
self._print(
|
|
421
|
+
f'[{feature.id}] feature housekeeping complete '
|
|
422
|
+
f'(counter={counter}, (duration={duration:.2f} seconds, '
|
|
423
|
+
f'housekeep_info={kwargs})',
|
|
424
|
+
error=error,
|
|
425
|
+
color='white',
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
def _print(
|
|
429
|
+
self,
|
|
430
|
+
message: str,
|
|
431
|
+
error: BaseException | None,
|
|
432
|
+
color: str | None = None,
|
|
433
|
+
styles: list[str] | None = None,
|
|
434
|
+
):
|
|
435
|
+
message = self._format_message(message, error)
|
|
436
|
+
if not self._keep(message, error):
|
|
437
|
+
return
|
|
438
|
+
self._write_log(message, error, color, styles)
|
|
439
|
+
|
|
440
|
+
def _write_log(
|
|
441
|
+
self,
|
|
442
|
+
message: str,
|
|
443
|
+
error: BaseException | None,
|
|
444
|
+
color: str | None = None,
|
|
445
|
+
styles: list[str] | None = None,
|
|
446
|
+
):
|
|
447
|
+
message = self._maybe_colored(
|
|
448
|
+
message, color if error is None else 'red', styles=styles
|
|
449
|
+
)
|
|
450
|
+
if error is not None:
|
|
451
|
+
pg.logging.error(message)
|
|
452
|
+
else:
|
|
453
|
+
pg.logging.info(message)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
class ConsoleEventLogger(EventLogger):
|
|
457
|
+
"""Event handler for console debugger."""
|
|
458
|
+
|
|
459
|
+
colored = True
|
|
460
|
+
|
|
461
|
+
def _write_log(
|
|
462
|
+
self,
|
|
463
|
+
message: str,
|
|
464
|
+
error: BaseException | None,
|
|
465
|
+
color: str | None = None,
|
|
466
|
+
styles: list[str] | None = None
|
|
467
|
+
):
|
|
468
|
+
print(
|
|
469
|
+
self._maybe_colored(
|
|
470
|
+
message, color if error is None else 'red', styles=styles
|
|
471
|
+
)
|
|
472
|
+
)
|