mermaid-trace 0.4.0__py3-none-any.whl → 0.4.1__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.
@@ -1,3 +1,15 @@
1
+ """
2
+ Function Tracing Decorator Module
3
+
4
+ This module provides the core tracing functionality for MermaidTrace. It includes:
5
+ - A decorator (`trace`/`trace_interaction`) to instrument functions for tracing
6
+ - Helper functions for formatting and logging trace events
7
+ - Context management for tracking call chains
8
+
9
+ The decorator can be applied to both synchronous and asynchronous functions,
10
+ and automatically handles context propagation for nested calls.
11
+ """
12
+
1
13
  import functools
2
14
  import logging
3
15
  import inspect
@@ -7,6 +19,7 @@ from typing import Optional, Any, Callable, Tuple, Dict, Union, TypeVar, cast, o
7
19
  from .events import FlowEvent
8
20
  from .context import LogContext
9
21
 
22
+ # Logger name for flow events - used to isolate tracing logs from other application logs
10
23
  FLOW_LOGGER_NAME = "mermaid_trace.flow"
11
24
 
12
25
  # Define generic type variable for the decorated function
@@ -14,7 +27,11 @@ F = TypeVar("F", bound=Callable[..., Any])
14
27
 
15
28
 
16
29
  def get_flow_logger() -> logging.Logger:
17
- """Returns the dedicated logger for flow events."""
30
+ """Returns the dedicated logger for flow events.
31
+
32
+ Returns:
33
+ logging.Logger: Logger instance configured for tracing events
34
+ """
18
35
  return logging.getLogger(FLOW_LOGGER_NAME)
19
36
 
20
37
 
@@ -24,6 +41,14 @@ def _safe_repr(obj: Any, max_len: int = 50, max_depth: int = 1) -> str:
24
41
 
25
42
  Prevents massive log files by truncating long strings/objects
26
43
  and handling exceptions during __repr__ calls (e.g. strict objects).
44
+
45
+ Args:
46
+ obj: The object to represent as a string
47
+ max_len: Maximum length of the resulting string
48
+ max_depth: Maximum recursion depth for nested objects
49
+
50
+ Returns:
51
+ str: Safe, truncated representation of the object
27
52
  """
28
53
  try:
29
54
  # Create a custom repr object to control depth and length
@@ -37,6 +62,7 @@ def _safe_repr(obj: Any, max_len: int = 50, max_depth: int = 1) -> str:
37
62
  return r[:max_len] + "..."
38
63
  return r
39
64
  except Exception:
65
+ # Fallback if repr() fails for any reason
40
66
  return "<unrepresentable>"
41
67
 
42
68
 
@@ -50,14 +76,27 @@ def _format_args(
50
76
  """
51
77
  Formats function arguments into a single string "arg1, arg2, k=v".
52
78
  Used for the arrow label in the diagram.
79
+
80
+ Args:
81
+ args: Positional arguments to format
82
+ kwargs: Keyword arguments to format
83
+ capture_args: Whether to capture and format arguments at all
84
+ max_arg_length: Maximum length of each argument representation
85
+ max_arg_depth: Maximum recursion depth for nested arguments
86
+
87
+ Returns:
88
+ str: Formatted string of arguments, or empty string if capture_args is False
53
89
  """
54
90
  if not capture_args:
55
91
  return ""
56
92
 
57
- parts = []
93
+ parts: list[str] = []
94
+
95
+ # Format positional arguments
58
96
  for arg in args:
59
97
  parts.append(_safe_repr(arg, max_len=max_arg_length, max_depth=max_arg_depth))
60
98
 
99
+ # Format keyword arguments
61
100
  for k, v in kwargs.items():
62
101
  val_str = _safe_repr(v, max_len=max_arg_length, max_depth=max_arg_depth)
63
102
  parts.append(f"{k}={val_str}")
@@ -78,16 +117,22 @@ def _resolve_target(
78
117
  3. **Class Method**: If the first arg is a type (cls), use the class name.
79
118
  4. **Module Function**: Fallback to the name of the module containing the function.
80
119
  5. **Fallback**: "Unknown".
120
+
121
+ Args:
122
+ func: The function being called
123
+ args: Positional arguments passed to the function
124
+ target_override: Explicit target name provided by the user, if any
125
+
126
+ Returns:
127
+ str: Resolved target name for the diagram
81
128
  """
82
129
  if target_override:
83
130
  return target_override
84
131
 
85
- # Heuristic: If it's a method call, args[0] is usually 'self'.
132
+ # Heuristic: If it's a method call, args[0] is usually 'self' or 'cls'
86
133
  if args:
87
134
  first_arg = args[0]
88
- # Check if it looks like a class instance
89
- # We check hasattr(__class__) to distinguish objects from primitives/containers broadly,
90
- # ensuring we don't mislabel a plain list passed as first arg to a function as a "List" participant.
135
+ # Check if it looks like a class instance (not a primitive or container)
91
136
  if hasattr(first_arg, "__class__") and not isinstance(
92
137
  first_arg, (str, int, float, bool, list, dict, type)
93
138
  ):
@@ -113,7 +158,15 @@ def _log_interaction(
113
158
  ) -> None:
114
159
  """
115
160
  Logs the 'Call' event (Start of function).
116
- Arrow: source -> target
161
+ Generates a FlowEvent and logs it with the appropriate context.
162
+
163
+ Args:
164
+ logger: Logger instance to use for logging
165
+ source: Name of the source participant (caller)
166
+ target: Name of the target participant (callee)
167
+ action: Name of the action being performed
168
+ params: Formatted string of function arguments
169
+ trace_id: Unique trace identifier for correlation
117
170
  """
118
171
  req_event = FlowEvent(
119
172
  source=source,
@@ -144,6 +197,17 @@ def _log_return(
144
197
 
145
198
  Note: 'source' here is the original caller, 'target' is the callee.
146
199
  So the return arrow goes from target back to source.
200
+
201
+ Args:
202
+ logger: Logger instance to use for logging
203
+ source: Name of the original caller
204
+ target: Name of the callee that's returning
205
+ action: Name of the action being returned from
206
+ result: Return value of the function
207
+ trace_id: Unique trace identifier for correlation
208
+ capture_args: Whether to include the return value in the log
209
+ max_arg_length: Maximum length of the return value representation
210
+ max_arg_depth: Maximum recursion depth for nested return values
147
211
  """
148
212
  result_str = ""
149
213
  if capture_args:
@@ -172,6 +236,14 @@ def _log_error(
172
236
  """
173
237
  Logs an 'Error' event if the function raises an exception.
174
238
  Arrow: target -x source (Error return)
239
+
240
+ Args:
241
+ logger: Logger instance to use for logging
242
+ source: Name of the original caller
243
+ target: Name of the callee that encountered an error
244
+ action: Name of the action that failed
245
+ error: Exception that was raised
246
+ trace_id: Unique trace identifier for correlation
175
247
  """
176
248
  err_event = FlowEvent(
177
249
  source=target,
@@ -217,25 +289,32 @@ def trace_interaction(
217
289
  """
218
290
  Main Decorator for tracing function execution in Mermaid diagrams.
219
291
 
292
+ This decorator instruments functions to log their execution flow as Mermaid
293
+ sequence diagram events. It supports both synchronous and asynchronous functions,
294
+ and automatically handles context propagation for nested calls.
295
+
220
296
  It supports two modes of operation:
221
- 1. **Simple**: `@trace` (No arguments)
222
- 2. **Configured**: `@trace(action="Login", target="AuthService")`
297
+ 1. **Simple**: `@trace` (No arguments) - uses default settings
298
+ 2. **Configured**: `@trace(action="Login", target="AuthService")` - customizes behavior
223
299
 
224
300
  Args:
225
301
  func: The function being decorated (automatically passed in simple mode).
226
302
  source: Explicit name of the caller participant (rarely used, usually inferred from Context).
227
303
  target: Explicit name of the callee participant (overrides automatic resolution).
228
304
  name: Alias for 'target' (for clearer API usage).
229
- action: Label for the arrow (defaults to function name).
305
+ action: Label for the arrow (defaults to function name in Title Case).
230
306
  capture_args: Whether to include arguments and return values in the log. Default True.
231
307
  max_arg_length: Maximum string length for argument/result representation. Default 50.
232
308
  max_arg_depth: Maximum recursion depth for argument/result representation. Default 1.
309
+
310
+ Returns:
311
+ Callable: Either the decorated function (simple mode) or a decorator factory (configured mode)
233
312
  """
234
313
 
235
- # Handle alias
314
+ # Handle alias - 'name' is an alternative name for 'target'
236
315
  final_target = target or name
237
316
 
238
- # Mode 1: @trace used without parentheses
317
+ # Mode 1: @trace used without parentheses - directly decorate the function
239
318
  if func is not None and callable(func):
240
319
  return _create_decorator(
241
320
  func,
@@ -247,7 +326,7 @@ def trace_interaction(
247
326
  max_arg_depth,
248
327
  )
249
328
 
250
- # Mode 2: @trace(...) used with arguments -> returns a factory
329
+ # Mode 2: @trace(...) used with arguments -> returns a factory that will decorate the function
251
330
  def factory(f: F) -> F:
252
331
  return _create_decorator(
253
332
  f, source, final_target, action, capture_args, max_arg_length, max_arg_depth
@@ -266,11 +345,23 @@ def _create_decorator(
266
345
  max_arg_depth: int,
267
346
  ) -> F:
268
347
  """
269
- Constructs the actual wrapper function.
270
- Handles both synchronous and asynchronous functions.
348
+ Constructs the actual wrapper function for the decorated function.
349
+ Handles both synchronous and asynchronous functions by creating the appropriate wrapper.
271
350
 
272
351
  This function separates the wrapper creation logic from the argument parsing logic
273
352
  in `trace_interaction`, making the code cleaner and easier to test.
353
+
354
+ Args:
355
+ func: The function to decorate
356
+ source: Explicit source name, if any
357
+ target: Explicit target name, if any
358
+ action: Explicit action name, if any
359
+ capture_args: Whether to capture arguments and return values
360
+ max_arg_length: Maximum length for argument/return value representations
361
+ max_arg_depth: Maximum recursion depth for nested objects
362
+
363
+ Returns:
364
+ Callable: Decorated function with tracing logic
274
365
  """
275
366
 
276
367
  # Pre-calculate static metadata to save time at runtime
@@ -280,10 +371,9 @@ def _create_decorator(
280
371
 
281
372
  @functools.wraps(func)
282
373
  def wrapper(*args: Any, **kwargs: Any) -> Any:
283
- """Sync function wrapper."""
374
+ """Synchronous function wrapper that adds tracing logic."""
284
375
  # 1. Resolve Context
285
376
  # 'source' is who called us (from Context). 'target' is who we are (resolved from self/cls).
286
- # If 'source' is not explicitly provided, we look up the 'participant' set by the caller.
287
377
  current_source = source or LogContext.current_participant()
288
378
  trace_id = LogContext.current_trace_id()
289
379
  current_target = _resolve_target(func, args, target)
@@ -294,7 +384,7 @@ def _create_decorator(
294
384
  args, kwargs, capture_args, max_arg_length, max_arg_depth
295
385
  )
296
386
 
297
- # 2. Log Request (Start of block)
387
+ # 2. Log Request (Start of function)
298
388
  # Logs the initial "Call" arrow (Source -> Target)
299
389
  _log_interaction(
300
390
  logger, current_source, current_target, action, params_str, trace_id
@@ -328,7 +418,7 @@ def _create_decorator(
328
418
 
329
419
  @functools.wraps(func)
330
420
  async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
331
- """Async function wrapper (coroutine)."""
421
+ """Asynchronous function wrapper that adds tracing logic."""
332
422
  # 1. Resolve Context (Same as sync)
333
423
  current_source = source or LogContext.current_participant()
334
424
  trace_id = LogContext.current_trace_id()
@@ -372,9 +462,9 @@ def _create_decorator(
372
462
 
373
463
  # Detect if the wrapped function is a coroutine to choose the right wrapper
374
464
  if inspect.iscoroutinefunction(func):
375
- return cast(F, async_wrapper)
376
- return cast(F, wrapper)
465
+ return cast(F, async_wrapper) # Return async wrapper for coroutines
466
+ return cast(F, wrapper) # Return sync wrapper for regular functions
377
467
 
378
468
 
379
- # Alias for easy import
469
+ # Alias for easy import - 'trace' is the primary name users should use
380
470
  trace = trace_interaction
@@ -1,28 +1,107 @@
1
+ """
2
+ Event Definition Module
3
+
4
+ This module defines the event structure for the tracing system. It provides an
5
+ abstract Event base class and a concrete FlowEvent implementation that represents
6
+ individual interactions in the execution flow.
7
+ """
8
+
9
+ from abc import ABC, abstractmethod
1
10
  from dataclasses import dataclass, field
2
11
  import time
3
12
  from typing import Optional
4
13
 
5
14
 
15
+ class Event(ABC):
16
+ """
17
+ Abstract base class for all event types.
18
+
19
+ This provides a common interface for different types of events, allowing
20
+ for extensibility and supporting multiple output formats. Concrete event
21
+ classes must implement all abstract methods.
22
+ """
23
+
24
+ @abstractmethod
25
+ def get_source(self) -> str:
26
+ """
27
+ Get the source of the event.
28
+
29
+ Returns:
30
+ str: Name of the participant that generated the event
31
+ """
32
+ pass
33
+
34
+ @abstractmethod
35
+ def get_target(self) -> str:
36
+ """
37
+ Get the target of the event.
38
+
39
+ Returns:
40
+ str: Name of the participant that received the event
41
+ """
42
+ pass
43
+
44
+ @abstractmethod
45
+ def get_action(self) -> str:
46
+ """
47
+ Get the action name of the event.
48
+
49
+ Returns:
50
+ str: Short name describing the action performed
51
+ """
52
+ pass
53
+
54
+ @abstractmethod
55
+ def get_message(self) -> str:
56
+ """
57
+ Get the message text of the event.
58
+
59
+ Returns:
60
+ str: Detailed message describing the event
61
+ """
62
+ pass
63
+
64
+ @abstractmethod
65
+ def get_timestamp(self) -> float:
66
+ """
67
+ Get the timestamp of the event.
68
+
69
+ Returns:
70
+ float: Unix timestamp (seconds) when the event occurred
71
+ """
72
+ pass
73
+
74
+ @abstractmethod
75
+ def get_trace_id(self) -> str:
76
+ """
77
+ Get the trace ID of the event.
78
+
79
+ Returns:
80
+ str: Unique identifier for the trace session
81
+ """
82
+ pass
83
+
84
+
6
85
  @dataclass
7
- class FlowEvent:
86
+ class FlowEvent(Event):
8
87
  """
9
88
  Represents a single interaction or step in the execution flow.
10
89
 
11
90
  This data structure acts as the intermediate representation (IR) between
12
- runtime code execution and the final Mermaid diagram output. Each instance
91
+ runtime code execution and the final diagram output. Each instance
13
92
  corresponds directly to one arrow or note in the sequence diagram.
14
93
 
15
- The fields map to Mermaid syntax components as follows:
94
+ The fields map to diagram syntax components as follows:
16
95
  `source` -> `target`: `message`
17
96
 
18
97
  Attributes:
19
98
  source (str):
20
99
  The name of the participant initiating the action (the "Caller").
21
- In Mermaid: The participant on the LEFT side of the arrow.
100
+ In sequence diagrams: The participant on the LEFT side of the arrow.
22
101
 
23
102
  target (str):
24
103
  The name of the participant receiving the action (the "Callee").
25
- In Mermaid: The participant on the RIGHT side of the arrow.
104
+ In sequence diagrams: The participant on the RIGHT side of the arrow.
26
105
 
27
106
  action (str):
28
107
  A short, human-readable name for the operation (e.g., function name).
@@ -34,8 +113,8 @@ class FlowEvent:
34
113
 
35
114
  timestamp (float):
36
115
  Unix timestamp (seconds) of when the event occurred.
37
- Used for ordering events if logs are processed asynchronously,
38
- though Mermaid sequence diagrams primarily rely on line order.
116
+ Used for ordering events if logs are processed asynchronously.
117
+ Defaults to current time when event is created.
39
118
 
40
119
  trace_id (str):
41
120
  Unique identifier for the trace session.
@@ -44,34 +123,68 @@ class FlowEvent:
44
123
 
45
124
  is_return (bool):
46
125
  Flag indicating if this is a response arrow.
47
- If True, the arrow is drawn as a dotted line (`-->`) in Mermaid.
48
- If False, it is a solid line (`->`) representing a call.
126
+ If True, the arrow is drawn as a dotted line in sequence diagrams.
127
+ If False, it is a solid line representing a call.
128
+ Defaults to False.
49
129
 
50
130
  is_error (bool):
51
131
  Flag indicating if an exception occurred.
52
- If True, the arrow might be styled differently (e.g., `-x`) to show failure.
132
+ If True, the arrow might be styled differently to show failure.
133
+ Defaults to False.
53
134
 
54
135
  error_message (Optional[str]):
55
136
  Detailed error text if `is_error` is True.
56
137
  Can be added as a note or included in the arrow label.
138
+ Defaults to None.
57
139
 
58
140
  params (Optional[str]):
59
141
  Stringified representation of function arguments.
60
142
  Captured only for request events (call start).
143
+ Defaults to None.
61
144
 
62
145
  result (Optional[str]):
63
146
  Stringified representation of the return value.
64
147
  Captured only for return events (call end).
148
+ Defaults to None.
65
149
  """
66
150
 
67
- source: str
68
- target: str
69
- action: str
70
- message: str
71
- trace_id: str
72
- timestamp: float = field(default_factory=time.time)
73
- is_return: bool = False
74
- is_error: bool = False
75
- error_message: Optional[str] = None
76
- params: Optional[str] = None
77
- result: Optional[str] = None
151
+ # Required fields for every event
152
+ source: str # Participant who initiated the action
153
+ target: str # Participant who received the action
154
+ action: str # Short name for the operation
155
+ message: str # Detailed message for the diagram arrow
156
+ trace_id: str # Unique identifier for the trace session
157
+
158
+ # Optional fields with defaults
159
+ timestamp: float = field(
160
+ default_factory=time.time
161
+ ) # Unix timestamp of event creation
162
+ is_return: bool = False # Whether this is a response arrow
163
+ is_error: bool = False # Whether an error occurred
164
+ error_message: Optional[str] = None # Detailed error message if is_error is True
165
+ params: Optional[str] = None # Stringified function arguments
166
+ result: Optional[str] = None # Stringified return value
167
+
168
+ def get_source(self) -> str:
169
+ """Get the source of the event."""
170
+ return self.source
171
+
172
+ def get_target(self) -> str:
173
+ """Get the target of the event."""
174
+ return self.target
175
+
176
+ def get_action(self) -> str:
177
+ """Get the action name of the event."""
178
+ return self.action
179
+
180
+ def get_message(self) -> str:
181
+ """Get the message text of the event."""
182
+ return self.message
183
+
184
+ def get_timestamp(self) -> float:
185
+ """Get the timestamp of the event."""
186
+ return self.timestamp
187
+
188
+ def get_trace_id(self) -> str:
189
+ """Get the trace ID of the event."""
190
+ return self.trace_id
@@ -1,81 +1,151 @@
1
+ """
2
+ Event Formatting Module
3
+
4
+ This module provides formatters to convert Event objects into various output formats.
5
+ Currently, it supports Mermaid sequence diagram syntax formatting, but can be extended
6
+ with additional formatters for other diagram types or logging formats.
7
+ """
8
+
1
9
  import logging
2
10
  import re
11
+ from abc import ABC, abstractmethod
3
12
  from typing import Optional
4
- from .events import FlowEvent
13
+ from .events import Event, FlowEvent
5
14
 
6
15
 
7
- class MermaidFormatter(logging.Formatter):
16
+ class BaseFormatter(ABC, logging.Formatter):
8
17
  """
9
- Custom formatter to convert FlowEvents into Mermaid sequence diagram syntax.
18
+ Abstract base class for all event formatters.
19
+
20
+ This provides a common interface for different formatters, allowing
21
+ for extensibility and supporting multiple output formats.
22
+
23
+ Subclasses must implement the format_event method to convert Event objects
24
+ into the desired output string format.
10
25
  """
11
26
 
27
+ @abstractmethod
28
+ def format_event(self, event: Event) -> str:
29
+ """
30
+ Format an Event into the desired output string.
31
+
32
+ Args:
33
+ event: The Event object to format
34
+
35
+ Returns:
36
+ str: Formatted string representation of the event
37
+ """
38
+ pass
39
+
12
40
  def format(self, record: logging.LogRecord) -> str:
13
- # 1. Retrieve the FlowEvent
14
- event: Optional[FlowEvent] = getattr(record, "flow_event", None)
41
+ """
42
+ Format a logging record containing an event.
43
+
44
+ This method overrides the standard logging.Formatter.format method to
45
+ extract and format Event objects from log records.
46
+
47
+ Args:
48
+ record: The logging record to format. Must contain a 'flow_event' attribute
49
+ if it represents a tracing event.
50
+
51
+ Returns:
52
+ str: Formatted string representation of the record
53
+ """
54
+ # Retrieve the Event object from the log record
55
+ event: Optional[Event] = getattr(record, "flow_event", None)
15
56
 
16
57
  if not event:
17
58
  # Fallback for standard logs if they accidentally reach this handler
18
59
  return super().format(record)
19
60
 
20
- # 2. Convert event to Mermaid line
21
- return self._to_mermaid_line(event)
61
+ # Convert event to the desired format using the subclass's format_event method
62
+ return self.format_event(event)
22
63
 
23
- def _to_mermaid_line(self, event: FlowEvent) -> str:
64
+
65
+ class MermaidFormatter(BaseFormatter):
66
+ """
67
+ Custom formatter to convert Events into Mermaid sequence diagram syntax.
68
+
69
+ This formatter transforms FlowEvent objects into lines of Mermaid syntax that
70
+ can be directly written to a .mmd file. Each event becomes a single line in the
71
+ sequence diagram.
72
+ """
73
+
74
+ def format_event(self, event: Event) -> str:
24
75
  """
25
- Converts a FlowEvent into a Mermaid syntax string.
76
+ Converts an Event into a Mermaid syntax string.
77
+
78
+ Args:
79
+ event: The Event object to format
80
+
81
+ Returns:
82
+ str: Mermaid syntax string representation of the event
26
83
  """
84
+ if not isinstance(event, FlowEvent):
85
+ # Fallback format for non-FlowEvent types
86
+ return f"{event.get_source()}->>{event.get_target()}: {event.get_message()}"
87
+
27
88
  # Sanitize participant names to avoid syntax errors in Mermaid
28
89
  src = self._sanitize(event.source)
29
90
  tgt = self._sanitize(event.target)
30
91
 
31
- # Determine arrow type
92
+ # Determine arrow type based on event properties
32
93
  # ->> : Solid line with arrowhead (synchronous call)
33
94
  # -->> : Dotted line with arrowhead (return)
34
95
  # --x : Dotted line with cross (error)
35
96
  arrow = "-->>" if event.is_return else "->>"
36
97
 
98
+ # Construct message text based on event type
37
99
  msg = ""
38
100
  if event.is_error:
39
101
  arrow = "--x"
40
102
  msg = f"Error: {event.error_message}"
41
103
  elif event.is_return:
42
- # For returns, we usually show the return value or just "Return"
104
+ # For return events, show return value or just "Return"
43
105
  msg = f"Return: {event.result}" if event.result else "Return"
44
106
  else:
45
- # For calls, we show Action(Params) or just Action
107
+ # For call events, show Action(Params) or just Action
46
108
  msg = f"{event.message}({event.params})" if event.params else event.message
47
109
 
48
- # Optional: Add note or group if trace_id changes (not implemented in single line format)
49
- # For now, we just output the interaction.
50
-
51
- # Escape message for Mermaid safety (e.g. replacing newlines)
110
+ # Escape message for Mermaid safety (e.g., replacing newlines)
52
111
  msg = self._escape_message(msg)
53
112
 
113
+ # Return the complete Mermaid syntax line
54
114
  # Format: Source->>Target: Message
55
115
  return f"{src}{arrow}{tgt}: {msg}"
56
116
 
57
117
  def _sanitize(self, name: str) -> str:
58
118
  """
59
119
  Sanitizes participant names to be valid Mermaid identifiers.
60
- Allows alphanumeric and underscores. Replaces others.
61
120
 
62
121
  Mermaid doesn't like spaces or special characters in participant aliases
63
122
  unless they are quoted (which we are not doing here for simplicity),
64
123
  so we replace them with underscores.
124
+
125
+ Args:
126
+ name: Original participant name
127
+
128
+ Returns:
129
+ str: Sanitized participant name
65
130
  """
66
131
  # Replace any non-alphanumeric character (except underscore) with underscore
67
132
  clean_name = re.sub(r"[^a-zA-Z0-9_]", "_", name)
68
- # Ensure it doesn't start with a digit (Mermaid doesn't like that sometimes, though often okay)
133
+ # Ensure it doesn't start with a digit (Mermaid doesn't like that sometimes)
69
134
  if clean_name and clean_name[0].isdigit():
70
135
  clean_name = "_" + clean_name
71
136
  return clean_name
72
137
 
73
138
  def _escape_message(self, msg: str) -> str:
74
139
  """
75
- Escapes special characters in the message text.
76
- Mermaid messages can contain most chars, but : and newlines can be tricky.
140
+ Escapes special characters in the message text for safe Mermaid rendering.
141
+
142
+ Args:
143
+ msg: Original message text
144
+
145
+ Returns:
146
+ str: Escaped message text
77
147
  """
78
- # Replace newlines with <br/> for Mermaid display
148
+ # Replace newlines with <br/> for proper display in Mermaid diagrams
79
149
  msg = msg.replace("\n", "<br/>")
80
- # We might want to escape other chars if needed, but usually text after : is forgiving.
150
+ # Additional escaping could be added here if needed for other characters
81
151
  return msg