speedy-utils 1.1.44__py3-none-any.whl → 1.1.45__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.
llm_utils/lm/llm.py CHANGED
@@ -16,6 +16,7 @@ from openai.types.chat import ChatCompletionMessageParam
16
16
  from pydantic import BaseModel
17
17
 
18
18
  from speedy_utils.common.utils_io import jdumps
19
+ from speedy_utils import clean_traceback
19
20
 
20
21
  from .base_prompt_builder import BasePromptBuilder
21
22
  from .mixins import (
@@ -159,6 +160,7 @@ class LLM(
159
160
  messages.append({'role': 'user', 'content': user_content})
160
161
  return cast(Messages, messages)
161
162
 
163
+ @clean_traceback
162
164
  def text_completion(
163
165
  self, input_data: str | BaseModel | list[dict], **runtime_kwargs
164
166
  ) -> list[dict[str, Any]]:
@@ -214,6 +216,7 @@ class LLM(
214
216
  results.append(result_dict)
215
217
  return results
216
218
 
219
+ @clean_traceback
217
220
  def pydantic_parse(
218
221
  self,
219
222
  input_data: str | BaseModel | list[dict],
speedy_utils/__init__.py CHANGED
@@ -58,6 +58,9 @@ from .common.utils_print import (
58
58
  fprint,
59
59
  )
60
60
 
61
+ # Error handling utilities
62
+ from .common.utils_error import clean_traceback, handle_exceptions_with_clean_traceback
63
+
61
64
  # Multi-worker processing
62
65
  from .multi_worker.process import multi_process
63
66
  from .multi_worker.thread import kill_all_thread, multi_thread
@@ -156,6 +159,9 @@ __all__ = [
156
159
  'print_table',
157
160
  'setup_logger',
158
161
  'log',
162
+ # Error handling utilities
163
+ 'clean_traceback',
164
+ 'handle_exceptions_with_clean_traceback',
159
165
  # Multi-worker processing
160
166
  'multi_process',
161
167
  'multi_thread',
@@ -0,0 +1,261 @@
1
+ """
2
+ Error handling utilities for clean, user-focused tracebacks.
3
+ """
4
+
5
+ import inspect
6
+ import linecache
7
+ import sys
8
+ import traceback
9
+ from typing import Any, Callable, TypeVar
10
+
11
+ try:
12
+ from rich.console import Console
13
+ from rich.panel import Panel
14
+ from rich.text import Text
15
+ except ImportError:
16
+ Console = None
17
+ Panel = None
18
+ Text = None
19
+
20
+ F = TypeVar('F', bound=Callable[..., Any])
21
+
22
+
23
+ class CleanTracebackError(Exception):
24
+ """Exception wrapper that provides clean, user-focused tracebacks."""
25
+
26
+ def __init__(
27
+ self,
28
+ original_exception: Exception,
29
+ user_traceback: list[traceback.FrameSummary],
30
+ caller_frame: traceback.FrameSummary | None = None,
31
+ func_name: str | None = None,
32
+ ) -> None:
33
+ self.original_exception = original_exception
34
+ self.user_traceback = user_traceback
35
+ self.caller_frame = caller_frame
36
+ self.func_name = func_name
37
+
38
+ # Create a focused error message
39
+ tb_str = ''.join(traceback.format_list(user_traceback))
40
+ func_part = f' in {func_name}' if func_name else ''
41
+ msg = (
42
+ f'Error{func_part}:\n'
43
+ f'\nUser code traceback:\n{tb_str}'
44
+ f'{type(original_exception).__name__}: {original_exception}'
45
+ )
46
+ super().__init__(msg)
47
+
48
+ def __str__(self) -> str:
49
+ return super().__str__()
50
+
51
+ def format_rich(self) -> None:
52
+ """Format and print error with rich panels and code context."""
53
+ if Console is None or Panel is None or Text is None:
54
+ print(str(self), file=sys.stderr)
55
+ return
56
+
57
+ console = Console(stderr=True, force_terminal=True)
58
+
59
+ # Build traceback display with code context
60
+ tb_parts: list[str] = []
61
+
62
+ # Show caller frame first if available
63
+ if self.caller_frame and self.caller_frame.lineno is not None:
64
+ tb_parts.append(
65
+ f'[cyan]{self.caller_frame.filename}[/cyan]:[yellow]{self.caller_frame.lineno}[/yellow] '
66
+ f'in [green]{self.caller_frame.name}[/green]'
67
+ )
68
+ tb_parts.append('')
69
+ context = _get_code_context_rich(self.caller_frame.filename, self.caller_frame.lineno, 3)
70
+ tb_parts.extend(context)
71
+ tb_parts.append('')
72
+
73
+ # Show user code frames with context
74
+ for frame in self.user_traceback:
75
+ if frame.lineno is not None:
76
+ func_name = f' {self.func_name}' if self.func_name else ''
77
+ tb_parts.append(
78
+ f'[cyan]{frame.filename}[/cyan]:[yellow]{frame.lineno}[/yellow] '
79
+ f'in [green]{frame.name}{func_name}[/green]'
80
+ )
81
+ tb_parts.append('')
82
+ context = _get_code_context_rich(frame.filename, frame.lineno, 3)
83
+ tb_parts.extend(context)
84
+ tb_parts.append('')
85
+
86
+ # Print with rich Panel
87
+ console.print()
88
+ console.print(
89
+ Panel(
90
+ '\n'.join(tb_parts),
91
+ title='[bold red]Traceback (most recent call last)[/bold red]',
92
+ border_style='red',
93
+ expand=False,
94
+ )
95
+ )
96
+ console.print(
97
+ f'[bold red]{type(self.original_exception).__name__}[/bold red]: '
98
+ f'{self.original_exception}'
99
+ )
100
+ console.print()
101
+
102
+
103
+ def _get_code_context(filename: str, lineno: int, context_lines: int = 3) -> list[str]:
104
+ """Get code context around a line with line numbers and highlighting."""
105
+ lines: list[str] = []
106
+ start = max(1, lineno - context_lines)
107
+ end = lineno + context_lines
108
+
109
+ for i in range(start, end + 1):
110
+ line = linecache.getline(filename, i)
111
+ if not line:
112
+ continue
113
+ line = line.rstrip()
114
+ marker = '❱' if i == lineno else ' '
115
+ lines.append(f' {i:4d} {marker} {line}')
116
+
117
+ return lines
118
+
119
+
120
+ def _get_code_context_rich(filename: str, lineno: int, context_lines: int = 3) -> list[str]:
121
+ """Get code context with rich formatting (colors)."""
122
+ lines: list[str] = []
123
+ start = max(1, lineno - context_lines)
124
+ end = lineno + context_lines
125
+
126
+ for i in range(start, end + 1):
127
+ line = linecache.getline(filename, i)
128
+ if not line:
129
+ continue
130
+ line = line.rstrip()
131
+ num_str = f'{i:4d}'
132
+
133
+ if i == lineno:
134
+ # Highlight error line
135
+ lines.append(f'[dim]{num_str}[/dim] [red]❱[/red] {line}')
136
+ else:
137
+ # Normal context line
138
+ lines.append(f'[dim]{num_str} │[/dim] {line}')
139
+
140
+ return lines
141
+
142
+
143
+ def _filter_traceback_frames(tb_list: list[traceback.FrameSummary]) -> list[traceback.FrameSummary]:
144
+ """Filter traceback frames to show only user code."""
145
+ user_frames = []
146
+ skip_patterns = [
147
+ 'site-packages/',
148
+ 'dist-packages/',
149
+ 'python3.',
150
+ 'lib/python',
151
+ 'concurrent/futures/',
152
+ 'threading.py',
153
+ 'multiprocessing/',
154
+ 'urllib/',
155
+ 'httpx/',
156
+ 'httpcore/',
157
+ 'openai/',
158
+ 'requests/',
159
+ 'aiohttp/',
160
+ 'urllib3/',
161
+ 'speedy_utils/common/',
162
+ 'speedy_utils/multi_worker/',
163
+ 'llm_utils/lm/',
164
+ 'llm_utils/chat_format/',
165
+ 'llm_utils/vector_cache/',
166
+ ]
167
+
168
+ skip_functions = [
169
+ 'wrapper', # Our decorator wrapper
170
+ '__call__', # Internal calls
171
+ '__inner_call__', # Internal calls
172
+ '_worker', # Multi-thread worker
173
+ '_run_batch', # Batch runner
174
+ ]
175
+
176
+ for frame in tb_list:
177
+ # Skip frames matching skip patterns
178
+ if any(pattern in frame.filename for pattern in skip_patterns):
179
+ continue
180
+ # Skip specific function names
181
+ if frame.name in skip_functions:
182
+ continue
183
+ # Skip frames that are too deep in the call stack (internal implementation)
184
+ if 'speedy_utils' in frame.filename and any(
185
+ name in frame.name for name in ['__inner_call__', '_worker', '_run_batch']
186
+ ):
187
+ continue
188
+ user_frames.append(frame)
189
+
190
+ # If no user frames found, keep frames that are likely user code (not deep library internals)
191
+ if not user_frames:
192
+ for frame in reversed(tb_list):
193
+ # Keep if it's not in site-packages and not in standard library
194
+ if ('site-packages/' not in frame.filename and
195
+ 'dist-packages/' not in frame.filename and
196
+ not frame.filename.startswith('/usr/') and
197
+ not frame.filename.startswith('/opt/') and
198
+ 'python3.' not in frame.filename and
199
+ frame.name not in skip_functions):
200
+ user_frames.append(frame)
201
+ if len(user_frames) >= 5: # Keep up to 5 frames
202
+ break
203
+ user_frames.reverse()
204
+
205
+ return user_frames
206
+
207
+
208
+ def clean_traceback(func: F) -> F:
209
+ """Decorator to wrap function calls with clean traceback handling."""
210
+ def wrapper(*args, **kwargs):
211
+ try:
212
+ return func(*args, **kwargs)
213
+ except Exception as exc:
214
+ # Get the current traceback
215
+ exc_tb = sys.exc_info()[2]
216
+
217
+ if exc_tb is not None:
218
+ tb_list = traceback.extract_tb(exc_tb)
219
+
220
+ # Filter to keep only user code frames
221
+ user_frames = _filter_traceback_frames(tb_list)
222
+
223
+ # Get caller frame - walk up the stack to find the original caller
224
+ caller_context = None
225
+ frame = inspect.currentframe()
226
+ while frame:
227
+ frame = frame.f_back
228
+ if frame and frame.f_code.co_name not in ['wrapper', '__call__', '__inner_call__']:
229
+ caller_info = inspect.getframeinfo(frame)
230
+ if not any(skip in caller_info.filename for skip in [
231
+ 'speedy_utils/common/', 'speedy_utils/multi_worker/',
232
+ 'llm_utils/lm/', 'site-packages/', 'dist-packages/'
233
+ ]):
234
+ caller_context = traceback.FrameSummary(
235
+ caller_info.filename,
236
+ caller_info.lineno,
237
+ caller_info.function,
238
+ )
239
+ break
240
+
241
+ # If we have user frames, create and format our custom exception
242
+ if user_frames:
243
+ func_name = getattr(func, '__name__', repr(func))
244
+ clean_error = CleanTracebackError(
245
+ exc,
246
+ user_frames,
247
+ caller_context,
248
+ func_name,
249
+ )
250
+ clean_error.format_rich()
251
+ sys.exit(1) # Exit after formatting
252
+
253
+ # Fallback: re-raise original if we couldn't extract frames
254
+ raise
255
+
256
+ return wrapper # type: ignore
257
+
258
+
259
+ def handle_exceptions_with_clean_traceback(func: F) -> F:
260
+ """Alias for clean_traceback decorator."""
261
+ return clean_traceback(func)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: speedy-utils
3
- Version: 1.1.44
3
+ Version: 1.1.45
4
4
  Summary: Fast and easy-to-use package for data science
5
5
  Project-URL: Homepage, https://github.com/anhvth/speedy
6
6
  Project-URL: Repository, https://github.com/anhvth/speedy
@@ -61,6 +61,7 @@ Description-Content-Type: text/markdown
61
61
  ## 🚀 Recent Updates (January 27, 2026)
62
62
 
63
63
  **Enhanced Error Handling in Parallel Processing:**
64
+
64
65
  - Rich-formatted error tracebacks with code context and syntax highlighting
65
66
  - Three error handling modes: 'raise', 'ignore', and 'log'
66
67
  - Filtered tracebacks focusing on user code (hiding infrastructure)
@@ -261,6 +262,7 @@ print(results) # [0, 1, 4, 9, 16, None, 36, 49, 64, 81, None, 121]
261
262
  #### Rich Error Tracebacks
262
263
 
263
264
  When errors occur, you'll see beautifully formatted tracebacks with:
265
+
264
266
  - **Code context**: Lines of code around the error location
265
267
  - **Caller information**: Shows where the parallel function was invoked
266
268
  - **Filtered frames**: Focuses on user code, hiding infrastructure details
@@ -282,7 +284,7 @@ When using `error_handler='log'`, errors are automatically saved to timestamped
282
284
 
283
285
  Progress bars now show real-time error and success counts:
284
286
 
285
- ```
287
+ ```bash
286
288
  Multi-thread [8/10] [00:02<00:00, 3.45it/s, success=8, errors=2, pending=0]
287
289
  ```
288
290
 
@@ -7,7 +7,7 @@ llm_utils/chat_format/transform.py,sha256=PJ2g9KT1GSbWuAs7giEbTpTAffpU9QsIXyRlbf
7
7
  llm_utils/chat_format/utils.py,sha256=M2EctZ6NeHXqFYufh26Y3CpSphN0bdZm5xoNaEJj5vg,1251
8
8
  llm_utils/lm/__init__.py,sha256=4jYMy3wPH3tg-tHFyWEWOqrnmX4Tu32VZCdzRGMGQsI,778
9
9
  llm_utils/lm/base_prompt_builder.py,sha256=_TzYMsWr-SsbA_JNXptUVN56lV5RfgWWTrFi-E8LMy4,12337
10
- llm_utils/lm/llm.py,sha256=4nTBNvIZfsKUZo5SdllLQOp3w-HohR3B1hcfUnYDl7A,20014
10
+ llm_utils/lm/llm.py,sha256=i6L5aKF6NhzmaFPBA2pCm8TkQmS1nCgORMqP5QyfJ28,20097
11
11
  llm_utils/lm/llm_signature.py,sha256=vV8uZgLLd6ZKqWbq0OPywWvXAfl7hrJQnbtBF-VnZRU,1244
12
12
  llm_utils/lm/lm_base.py,sha256=Bk3q34KrcCK_bC4Ryxbc3KqkiPL39zuVZaBQ1i6wJqs,9437
13
13
  llm_utils/lm/mixins.py,sha256=Nz7CwJFBOvbZNbODUlJC04Pcbac3zWnT8vy7sZG_MVI,24906
@@ -30,7 +30,7 @@ llm_utils/vector_cache/core.py,sha256=VXuYJy1AX22NHKvIXRriETip5RrmQcNp73-g-ZT774
30
30
  llm_utils/vector_cache/types.py,sha256=CpMZanJSTeBVxQSqjBq6pBVWp7u2-JRcgY9t5jhykdQ,438
31
31
  llm_utils/vector_cache/utils.py,sha256=OsiRFydv8i8HiJtPL9hh40aUv8I5pYfg2zvmtDi4DME,1446
32
32
  speedy_utils/__imports.py,sha256=V0YzkDK4-QkK_IDXY1be6C6_STuNhXAKIp4_dM0coQs,7800
33
- speedy_utils/__init__.py,sha256=1ubAYR6P0cEZJLDt7KQLLxl6ylh-T7WE7HPP94-rVLI,3045
33
+ speedy_utils/__init__.py,sha256=_kSjS816Kv5UZPd4EM_juB68tXM_sHUYt6OFB-RhE6U,3261
34
34
  speedy_utils/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
35
  speedy_utils/common/clock.py,sha256=raLtMGIgzrRej5kUt7hOUm2ZZw2THVPo-q8dMvdZOxw,7354
36
36
  speedy_utils/common/function_decorator.py,sha256=GKXqRs_hHFFmhyhql0Br0o52WzekUnpNlm99NfaVwgY,2025
@@ -39,6 +39,7 @@ speedy_utils/common/notebook_utils.py,sha256=6mxXZcTHwYob3nobAzbSZnDyXRahFaaSko1
39
39
  speedy_utils/common/patcher.py,sha256=Rku-N4DJNue8BCLUx7y3ad_3t_WU2HleHKlbR0vhaRc,2319
40
40
  speedy_utils/common/report_manager.py,sha256=78KQ0gUzvbzT6EjHYZ5zgtV41cPRvdX8hnF2oSWA4qA,3849
41
41
  speedy_utils/common/utils_cache.py,sha256=1UAqOSb4nFVlhuQRfTEXCN-8Wf6yntXyMA6yp61-98I,26277
42
+ speedy_utils/common/utils_error.py,sha256=KQx2JTZsvsX2DsKRIoVR-4rc-6-l3OzEz9UtnHt8HJg,9108
42
43
  speedy_utils/common/utils_io.py,sha256=94m_EZ2eIs3w2m0rx-QQWsREPpVJctpweYHco3byczQ,15876
43
44
  speedy_utils/common/utils_misc.py,sha256=ZRJCS7OJxybpVm1sasoeCYRW2TaaGCXj4DySYlQeVR8,2227
44
45
  speedy_utils/common/utils_print.py,sha256=AGDB7mgJnO00QkJBH6kJb46738q3GzMUZPwtQ248vQw,4763
@@ -55,7 +56,7 @@ vision_utils/README.md,sha256=AIDZZj8jo_QNrEjFyHwd00iOO431s-js-M2dLtVTn3I,5740
55
56
  vision_utils/__init__.py,sha256=hF54sT6FAxby8kDVhOvruy4yot8O-Ateey5n96O1pQM,284
56
57
  vision_utils/io_utils.py,sha256=pI0Va6miesBysJcllK6NXCay8HpGZsaMWwlsKB2DMgA,26510
57
58
  vision_utils/plot.py,sha256=HkNj3osA3moPuupP1VguXfPPOW614dZO5tvC-EFKpKM,12028
58
- speedy_utils-1.1.44.dist-info/METADATA,sha256=Y5wQ_VbeiPTcqUkOCEbEvOKZ-wnwKCF2nvGhSnU3AJs,13067
59
- speedy_utils-1.1.44.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
60
- speedy_utils-1.1.44.dist-info/entry_points.txt,sha256=rwn89AYfBUh9SRJtFbpp-u2JIKiqmZ2sczvqyO6s9cI,289
61
- speedy_utils-1.1.44.dist-info/RECORD,,
59
+ speedy_utils-1.1.45.dist-info/METADATA,sha256=yXr9vEuXiRpvZ3VibGULqr3X1a832aQZJAb-lJLM6mM,13073
60
+ speedy_utils-1.1.45.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
61
+ speedy_utils-1.1.45.dist-info/entry_points.txt,sha256=rwn89AYfBUh9SRJtFbpp-u2JIKiqmZ2sczvqyO6s9cI,289
62
+ speedy_utils-1.1.45.dist-info/RECORD,,