oscura 0.7.0__py3-none-any.whl → 0.10.0__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.
Files changed (175) hide show
  1. oscura/__init__.py +19 -19
  2. oscura/analyzers/__init__.py +2 -0
  3. oscura/analyzers/digital/extraction.py +2 -3
  4. oscura/analyzers/digital/quality.py +1 -1
  5. oscura/analyzers/digital/timing.py +1 -1
  6. oscura/analyzers/eye/__init__.py +5 -1
  7. oscura/analyzers/eye/generation.py +501 -0
  8. oscura/analyzers/jitter/__init__.py +6 -6
  9. oscura/analyzers/jitter/timing.py +419 -0
  10. oscura/analyzers/patterns/__init__.py +94 -0
  11. oscura/analyzers/patterns/reverse_engineering.py +991 -0
  12. oscura/analyzers/power/__init__.py +35 -12
  13. oscura/analyzers/power/basic.py +3 -3
  14. oscura/analyzers/power/soa.py +1 -1
  15. oscura/analyzers/power/switching.py +3 -3
  16. oscura/analyzers/signal_classification.py +529 -0
  17. oscura/analyzers/signal_integrity/sparams.py +3 -3
  18. oscura/analyzers/statistics/__init__.py +4 -0
  19. oscura/analyzers/statistics/basic.py +152 -0
  20. oscura/analyzers/statistics/correlation.py +47 -6
  21. oscura/analyzers/validation.py +1 -1
  22. oscura/analyzers/waveform/__init__.py +2 -0
  23. oscura/analyzers/waveform/measurements.py +329 -163
  24. oscura/analyzers/waveform/measurements_with_uncertainty.py +91 -35
  25. oscura/analyzers/waveform/spectral.py +498 -54
  26. oscura/api/dsl/commands.py +15 -6
  27. oscura/api/server/templates/base.html +137 -146
  28. oscura/api/server/templates/export.html +84 -110
  29. oscura/api/server/templates/home.html +248 -267
  30. oscura/api/server/templates/protocols.html +44 -48
  31. oscura/api/server/templates/reports.html +27 -35
  32. oscura/api/server/templates/session_detail.html +68 -78
  33. oscura/api/server/templates/sessions.html +62 -72
  34. oscura/api/server/templates/waveforms.html +54 -64
  35. oscura/automotive/__init__.py +1 -1
  36. oscura/automotive/can/session.py +1 -1
  37. oscura/automotive/dbc/generator.py +638 -23
  38. oscura/automotive/dtc/data.json +102 -17
  39. oscura/automotive/uds/decoder.py +99 -6
  40. oscura/cli/analyze.py +8 -2
  41. oscura/cli/batch.py +36 -5
  42. oscura/cli/characterize.py +18 -4
  43. oscura/cli/export.py +47 -5
  44. oscura/cli/main.py +2 -0
  45. oscura/cli/onboarding/wizard.py +10 -6
  46. oscura/cli/pipeline.py +585 -0
  47. oscura/cli/visualize.py +6 -4
  48. oscura/convenience.py +400 -32
  49. oscura/core/config/loader.py +0 -1
  50. oscura/core/measurement_result.py +286 -0
  51. oscura/core/progress.py +1 -1
  52. oscura/core/schemas/device_mapping.json +8 -2
  53. oscura/core/schemas/packet_format.json +24 -4
  54. oscura/core/schemas/protocol_definition.json +12 -2
  55. oscura/core/types.py +300 -199
  56. oscura/correlation/multi_protocol.py +1 -1
  57. oscura/export/legacy/__init__.py +11 -0
  58. oscura/export/legacy/wav.py +75 -0
  59. oscura/exporters/__init__.py +19 -0
  60. oscura/exporters/wireshark.py +809 -0
  61. oscura/hardware/acquisition/file.py +5 -19
  62. oscura/hardware/acquisition/saleae.py +10 -10
  63. oscura/hardware/acquisition/socketcan.py +4 -6
  64. oscura/hardware/acquisition/synthetic.py +1 -5
  65. oscura/hardware/acquisition/visa.py +6 -6
  66. oscura/hardware/security/side_channel_detector.py +5 -508
  67. oscura/inference/message_format.py +686 -1
  68. oscura/jupyter/display.py +2 -2
  69. oscura/jupyter/magic.py +3 -3
  70. oscura/loaders/__init__.py +17 -12
  71. oscura/loaders/binary.py +1 -1
  72. oscura/loaders/chipwhisperer.py +1 -2
  73. oscura/loaders/configurable.py +1 -1
  74. oscura/loaders/csv_loader.py +2 -2
  75. oscura/loaders/hdf5_loader.py +1 -1
  76. oscura/loaders/lazy.py +6 -1
  77. oscura/loaders/mmap_loader.py +0 -1
  78. oscura/loaders/numpy_loader.py +8 -7
  79. oscura/loaders/preprocessing.py +3 -5
  80. oscura/loaders/rigol.py +21 -7
  81. oscura/loaders/sigrok.py +2 -5
  82. oscura/loaders/tdms.py +3 -2
  83. oscura/loaders/tektronix.py +38 -32
  84. oscura/loaders/tss.py +20 -27
  85. oscura/loaders/vcd.py +13 -8
  86. oscura/loaders/wav.py +1 -6
  87. oscura/pipeline/__init__.py +76 -0
  88. oscura/pipeline/handlers/__init__.py +165 -0
  89. oscura/pipeline/handlers/analyzers.py +1045 -0
  90. oscura/pipeline/handlers/decoders.py +899 -0
  91. oscura/pipeline/handlers/exporters.py +1103 -0
  92. oscura/pipeline/handlers/filters.py +891 -0
  93. oscura/pipeline/handlers/loaders.py +640 -0
  94. oscura/pipeline/handlers/transforms.py +768 -0
  95. oscura/reporting/__init__.py +88 -1
  96. oscura/reporting/automation.py +348 -0
  97. oscura/reporting/citations.py +374 -0
  98. oscura/reporting/core.py +54 -0
  99. oscura/reporting/formatting/__init__.py +11 -0
  100. oscura/reporting/formatting/measurements.py +320 -0
  101. oscura/reporting/html.py +57 -0
  102. oscura/reporting/interpretation.py +431 -0
  103. oscura/reporting/summary.py +329 -0
  104. oscura/reporting/templates/enhanced/protocol_re.html +504 -503
  105. oscura/reporting/visualization.py +542 -0
  106. oscura/side_channel/__init__.py +38 -57
  107. oscura/utils/builders/signal_builder.py +5 -5
  108. oscura/utils/comparison/compare.py +7 -9
  109. oscura/utils/comparison/golden.py +1 -1
  110. oscura/utils/filtering/convenience.py +2 -2
  111. oscura/utils/math/arithmetic.py +38 -62
  112. oscura/utils/math/interpolation.py +20 -20
  113. oscura/utils/pipeline/__init__.py +4 -17
  114. oscura/utils/progressive.py +1 -4
  115. oscura/utils/triggering/edge.py +1 -1
  116. oscura/utils/triggering/pattern.py +2 -2
  117. oscura/utils/triggering/pulse.py +2 -2
  118. oscura/utils/triggering/window.py +3 -3
  119. oscura/validation/hil_testing.py +11 -11
  120. oscura/visualization/__init__.py +47 -284
  121. oscura/visualization/batch.py +160 -0
  122. oscura/visualization/plot.py +542 -53
  123. oscura/visualization/styles.py +184 -318
  124. oscura/workflows/__init__.py +2 -0
  125. oscura/workflows/batch/advanced.py +1 -1
  126. oscura/workflows/batch/aggregate.py +7 -8
  127. oscura/workflows/complete_re.py +251 -23
  128. oscura/workflows/digital.py +27 -4
  129. oscura/workflows/multi_trace.py +136 -17
  130. oscura/workflows/waveform.py +788 -0
  131. {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/METADATA +59 -79
  132. {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/RECORD +135 -149
  133. oscura/side_channel/dpa.py +0 -1025
  134. oscura/utils/optimization/__init__.py +0 -19
  135. oscura/utils/optimization/parallel.py +0 -443
  136. oscura/utils/optimization/search.py +0 -532
  137. oscura/utils/pipeline/base.py +0 -338
  138. oscura/utils/pipeline/composition.py +0 -248
  139. oscura/utils/pipeline/parallel.py +0 -449
  140. oscura/utils/pipeline/pipeline.py +0 -375
  141. oscura/utils/search/__init__.py +0 -16
  142. oscura/utils/search/anomaly.py +0 -424
  143. oscura/utils/search/context.py +0 -294
  144. oscura/utils/search/pattern.py +0 -288
  145. oscura/utils/storage/__init__.py +0 -61
  146. oscura/utils/storage/database.py +0 -1166
  147. oscura/visualization/accessibility.py +0 -526
  148. oscura/visualization/annotations.py +0 -371
  149. oscura/visualization/axis_scaling.py +0 -305
  150. oscura/visualization/colors.py +0 -451
  151. oscura/visualization/digital.py +0 -436
  152. oscura/visualization/eye.py +0 -571
  153. oscura/visualization/histogram.py +0 -281
  154. oscura/visualization/interactive.py +0 -1035
  155. oscura/visualization/jitter.py +0 -1042
  156. oscura/visualization/keyboard.py +0 -394
  157. oscura/visualization/layout.py +0 -400
  158. oscura/visualization/optimization.py +0 -1079
  159. oscura/visualization/palettes.py +0 -446
  160. oscura/visualization/power.py +0 -508
  161. oscura/visualization/power_extended.py +0 -955
  162. oscura/visualization/presets.py +0 -469
  163. oscura/visualization/protocols.py +0 -1246
  164. oscura/visualization/render.py +0 -223
  165. oscura/visualization/rendering.py +0 -444
  166. oscura/visualization/reverse_engineering.py +0 -838
  167. oscura/visualization/signal_integrity.py +0 -989
  168. oscura/visualization/specialized.py +0 -643
  169. oscura/visualization/spectral.py +0 -1226
  170. oscura/visualization/thumbnails.py +0 -340
  171. oscura/visualization/time_axis.py +0 -351
  172. oscura/visualization/waveform.py +0 -454
  173. {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/WHEEL +0 -0
  174. {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/entry_points.txt +0 -0
  175. {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/licenses/LICENSE +0 -0
oscura/loaders/tss.py CHANGED
@@ -13,7 +13,7 @@ A .tss session file typically contains:
13
13
  Example:
14
14
  >>> import oscura as osc
15
15
  >>> trace = osc.load("oscilloscope_session.tss")
16
- >>> print(f"Channel: {trace.metadata.channel_name}")
16
+ >>> print(f"Channel: {trace.metadata.channel}")
17
17
  >>> print(f"Sample rate: {trace.metadata.sample_rate} Hz")
18
18
 
19
19
  >>> # Load specific channel
@@ -93,7 +93,7 @@ def load_tss(
93
93
  )
94
94
 
95
95
  # Select channel
96
- trace, channel_name = _select_channel(waveforms, channel, path)
96
+ trace, channel = _select_channel(waveforms, channel, path)
97
97
 
98
98
  # Enrich metadata with session information
99
99
  try:
@@ -317,18 +317,18 @@ def _load_all_waveforms(path: Path) -> dict[str, WaveformTrace | DigitalTrace |
317
317
 
318
318
  for wfm_name in sorted(wfm_files): # Sort for consistent ordering
319
319
  # Derive channel name from filename
320
- channel_name = _derive_channel_name(wfm_name)
320
+ channel = _derive_channel(wfm_name)
321
321
 
322
322
  # Load waveform
323
323
  trace = _load_wfm_from_archive(zf, wfm_name, path)
324
324
 
325
325
  # Store with normalized channel name
326
- waveforms[channel_name] = trace
326
+ waveforms[channel] = trace
327
327
 
328
328
  return waveforms
329
329
 
330
330
 
331
- def _derive_channel_name(wfm_filename: str) -> str:
331
+ def _derive_channel(wfm_filename: str) -> str:
332
332
  """Derive channel name from .wfm filename.
333
333
 
334
334
  Args:
@@ -338,13 +338,13 @@ def _derive_channel_name(wfm_filename: str) -> str:
338
338
  Normalized channel name (lowercase, e.g., "ch1", "ch2", "d0").
339
339
 
340
340
  Examples:
341
- >>> _derive_channel_name("CH1.wfm")
341
+ >>> _derive_channel("CH1.wfm")
342
342
  'ch1'
343
- >>> _derive_channel_name("subdir/CH2_Voltage.wfm")
343
+ >>> _derive_channel("subdir/CH2_Voltage.wfm")
344
344
  'ch2'
345
- >>> _derive_channel_name("D0.wfm")
345
+ >>> _derive_channel("D0.wfm")
346
346
  'd0'
347
- >>> _derive_channel_name("MATH1.wfm")
347
+ >>> _derive_channel("MATH1.wfm")
348
348
  'math1'
349
349
  """
350
350
  # Get base filename without path
@@ -373,27 +373,27 @@ def _select_channel(
373
373
  path: Path to session file (for error messages).
374
374
 
375
375
  Returns:
376
- Tuple of (selected_trace, channel_name).
376
+ Tuple of (selected_trace, channel).
377
377
 
378
378
  Raises:
379
379
  LoaderError: If channel not found or index out of range.
380
380
  """
381
381
  if channel is None:
382
382
  # Default: first channel (alphabetically sorted)
383
- channel_name = sorted(waveforms.keys())[0]
384
- return waveforms[channel_name], channel_name
383
+ channel = sorted(waveforms.keys())[0]
384
+ return waveforms[channel], channel
385
385
 
386
386
  if isinstance(channel, int):
387
387
  # Select by index
388
- channel_names = sorted(waveforms.keys())
389
- if channel < 0 or channel >= len(channel_names):
388
+ channels = sorted(waveforms.keys())
389
+ if channel < 0 or channel >= len(channels):
390
390
  raise LoaderError(
391
391
  f"Channel index {channel} out of range",
392
392
  file_path=str(path),
393
- fix_hint=f"Available channels: {', '.join(channel_names)} (indices 0-{len(channel_names) - 1})",
393
+ fix_hint=f"Available channels: {', '.join(channels)} (indices 0-{len(channels) - 1})",
394
394
  )
395
- channel_name = channel_names[channel]
396
- return waveforms[channel_name], channel_name
395
+ channel = channels[channel]
396
+ return waveforms[channel], channel
397
397
 
398
398
  # Select by name (case-insensitive)
399
399
  channel_lower = channel.lower()
@@ -426,26 +426,19 @@ def _enrich_metadata_from_session(
426
426
  Trace with enriched metadata.
427
427
  """
428
428
  # Create new metadata with session information
429
- from dataclasses import replace
430
429
 
431
430
  metadata = trace.metadata
432
431
 
433
- # Update source file to point to .tss instead of temp .wfm
434
- metadata = replace(metadata, source_file=source_file)
435
-
436
- # Add trigger info from session if available
437
- if "trigger" in session_metadata and metadata.trigger_info is None:
438
- metadata = replace(metadata, trigger_info=session_metadata["trigger"])
432
+ # Note: source_file and trigger_info attributes removed from TraceMetadata
439
433
 
440
434
  # Return trace with updated metadata
441
435
  if isinstance(trace, WaveformTrace):
442
436
  return WaveformTrace(data=trace.data, metadata=metadata)
443
437
  if isinstance(trace, DigitalTrace):
444
- return DigitalTrace(data=trace.data, metadata=metadata, edges=trace.edges)
438
+ return DigitalTrace(data=trace.data, metadata=metadata)
445
439
  # IQTrace
446
440
  return IQTrace(
447
- i_data=trace.i_data,
448
- q_data=trace.q_data,
441
+ data=trace.data,
449
442
  metadata=metadata,
450
443
  )
451
444
 
oscura/loaders/vcd.py CHANGED
@@ -16,7 +16,7 @@ import mmap
16
16
  import re
17
17
  from dataclasses import dataclass, field
18
18
  from pathlib import Path
19
- from typing import TYPE_CHECKING
19
+ from typing import TYPE_CHECKING, Any
20
20
 
21
21
  import numpy as np
22
22
  from numpy.typing import NDArray
@@ -130,7 +130,7 @@ def load_vcd(
130
130
  data, edges = _changes_to_samples(changes, header.timescale, sample_rate)
131
131
  metadata = _build_trace_metadata(path, target_var, header, sample_rate)
132
132
 
133
- return DigitalTrace(data=data.astype(np.bool_), metadata=metadata, edges=edges)
133
+ return DigitalTrace(data=data.astype(np.bool_), metadata=metadata)
134
134
 
135
135
  except UnicodeDecodeError as e:
136
136
  raise FormatError(
@@ -239,15 +239,20 @@ def _build_trace_metadata(
239
239
  path: Path, target_var: VCDVariable, header: VCDHeader, sample_rate: float
240
240
  ) -> TraceMetadata:
241
241
  """Build trace metadata from VCD information."""
242
+ # Build trigger_info from VCD header
243
+ trigger_info: dict[str, Any] = {}
244
+ if header.timescale is not None:
245
+ trigger_info["timescale"] = header.timescale
246
+ if header.date:
247
+ trigger_info["date"] = header.date
248
+ if header.version:
249
+ trigger_info["version"] = header.version
250
+
242
251
  return TraceMetadata(
243
252
  sample_rate=sample_rate,
253
+ channel=target_var.name,
244
254
  source_file=str(path),
245
- channel_name=target_var.name,
246
- trigger_info={
247
- "timescale": header.timescale,
248
- "var_type": target_var.var_type,
249
- "bit_width": target_var.size,
250
- },
255
+ trigger_info=trigger_info if trigger_info else None,
251
256
  )
252
257
 
253
258
 
oscura/loaders/wav.py CHANGED
@@ -250,13 +250,8 @@ def load_wav(
250
250
  # Build metadata
251
251
  metadata = TraceMetadata(
252
252
  sample_rate=float(sample_rate),
253
+ channel=channel_name,
253
254
  source_file=str(path),
254
- channel_name=channel_name,
255
- trigger_info={
256
- "original_dtype": str(data.dtype),
257
- "n_channels": data.shape[1] if data.ndim == 2 else 1,
258
- "normalized": normalize,
259
- },
260
255
  )
261
256
 
262
257
  return WaveformTrace(data=audio_data, metadata=metadata)
@@ -0,0 +1,76 @@
1
+ """YAML-based pipeline system for Oscura.
2
+
3
+ This package provides a complete pipeline execution framework with:
4
+ - YAML configuration for declarative workflows
5
+ - 60+ built-in handlers for common operations
6
+ - Transaction semantics with automatic rollback
7
+ - Conditional logic and parallel execution
8
+ - Template variables and composition
9
+
10
+ Quick Start:
11
+ >>> from oscura.pipeline import Pipeline, register_all_handlers
12
+ >>>
13
+ >>> # Load and execute a pipeline
14
+ >>> pipeline = Pipeline.load("analysis.yaml")
15
+ >>> register_all_handlers(pipeline)
16
+ >>> results = pipeline.execute()
17
+ >>>
18
+ >>> # Access results
19
+ >>> trace = results.outputs["load_trace"]["trace"]
20
+ >>> frames = results.outputs["decode_uart"]["frames"]
21
+
22
+ Example YAML:
23
+ ```yaml
24
+ pipeline:
25
+ name: uart_analysis
26
+ version: 1.0.0
27
+ steps:
28
+ - name: load_trace
29
+ type: input.file
30
+ params:
31
+ path: ${input_file}
32
+ outputs:
33
+ trace: waveform
34
+
35
+ - name: decode_uart
36
+ type: decoder.uart
37
+ inputs:
38
+ trace: load_trace.waveform
39
+ params:
40
+ baud_rate: 115200
41
+ outputs:
42
+ frames: decoded_frames
43
+ ```
44
+ """
45
+
46
+ from oscura.core.config.pipeline import (
47
+ Pipeline,
48
+ PipelineDefinition,
49
+ PipelineExecutionError,
50
+ PipelineResult,
51
+ PipelineStep,
52
+ PipelineValidationError,
53
+ )
54
+ from oscura.pipeline.handlers import (
55
+ get_all_handlers,
56
+ get_handler,
57
+ list_handler_types,
58
+ register_all_handlers,
59
+ register_handler,
60
+ )
61
+
62
+ __all__ = [
63
+ # Core classes
64
+ "Pipeline",
65
+ "PipelineDefinition",
66
+ "PipelineExecutionError",
67
+ "PipelineResult",
68
+ "PipelineStep",
69
+ "PipelineValidationError",
70
+ "get_all_handlers",
71
+ "get_handler",
72
+ "list_handler_types",
73
+ "register_all_handlers",
74
+ # Registry functions
75
+ "register_handler",
76
+ ]
@@ -0,0 +1,165 @@
1
+ """Pipeline handler registry system.
2
+
3
+ This module provides a centralized registry for pipeline step handlers.
4
+ Handlers are functions that process specific step types (e.g., "input.file",
5
+ "decoder.uart") and return outputs that can be used by subsequent steps.
6
+
7
+ Handler Signature:
8
+ All handlers must follow this signature:
9
+
10
+ def handler(inputs: dict[str, Any], params: dict[str, Any], step_name: str) -> dict[str, Any]:
11
+ '''Handler for step type.
12
+
13
+ Args:
14
+ inputs: Input data from previous steps
15
+ params: Step parameters from YAML
16
+ step_name: Name of step being executed (for error reporting)
17
+
18
+ Returns:
19
+ Dictionary of outputs to pass to next steps
20
+
21
+ Raises:
22
+ PipelineExecutionError: If step execution fails
23
+ '''
24
+
25
+ Example:
26
+ >>> from oscura.pipeline.handlers import register_handler
27
+ >>>
28
+ >>> @register_handler("custom.step")
29
+ >>> def handle_custom(inputs: dict[str, Any], params: dict[str, Any], step_name: str) -> dict[str, Any]:
30
+ ... result = do_custom_processing(inputs, params)
31
+ ... return {"result": result}
32
+ >>>
33
+ >>> # Auto-register all handlers
34
+ >>> from oscura.core.config.pipeline import Pipeline
35
+ >>> from oscura.pipeline.handlers import register_all_handlers
36
+ >>>
37
+ >>> pipeline = Pipeline.load("analysis.yaml")
38
+ >>> register_all_handlers(pipeline)
39
+ >>> results = pipeline.execute()
40
+ """
41
+
42
+ from __future__ import annotations
43
+
44
+ from typing import TYPE_CHECKING, Any
45
+
46
+ if TYPE_CHECKING:
47
+ from collections.abc import Callable
48
+
49
+ from oscura.core.config.pipeline import Pipeline
50
+
51
+
52
+ # Global handler registry
53
+ _HANDLER_REGISTRY: dict[str, Callable[[dict[str, Any], dict[str, Any], str], dict[str, Any]]] = {}
54
+
55
+
56
+ def register_handler(
57
+ step_type: str,
58
+ ) -> Callable[
59
+ [Callable[[dict[str, Any], dict[str, Any], str], dict[str, Any]]],
60
+ Callable[[dict[str, Any], dict[str, Any], str], dict[str, Any]],
61
+ ]:
62
+ """Decorator to register a handler for a step type.
63
+
64
+ Args:
65
+ step_type: Step type identifier (e.g., "input.file", "decoder.uart")
66
+
67
+ Returns:
68
+ Decorator function
69
+
70
+ Example:
71
+ >>> @register_handler("input.file")
72
+ >>> def handle_input_file(inputs: dict[str, Any], params: dict[str, Any], step_name: str) -> dict[str, Any]:
73
+ ... # Load file and return trace
74
+ ... return {"trace": loaded_trace}
75
+ """
76
+
77
+ def decorator(
78
+ func: Callable[[dict[str, Any], dict[str, Any], str], dict[str, Any]],
79
+ ) -> Callable[[dict[str, Any], dict[str, Any], str], dict[str, Any]]:
80
+ """Register handler function."""
81
+ _HANDLER_REGISTRY[step_type] = func
82
+ return func
83
+
84
+ return decorator
85
+
86
+
87
+ def get_handler(
88
+ step_type: str,
89
+ ) -> Callable[[dict[str, Any], dict[str, Any], str], dict[str, Any]] | None:
90
+ """Get handler for a step type.
91
+
92
+ Args:
93
+ step_type: Step type identifier
94
+
95
+ Returns:
96
+ Handler function or None if not found
97
+ """
98
+ return _HANDLER_REGISTRY.get(step_type)
99
+
100
+
101
+ def get_all_handlers() -> dict[
102
+ str, Callable[[dict[str, Any], dict[str, Any], str], dict[str, Any]]
103
+ ]:
104
+ """Get all registered handlers.
105
+
106
+ Returns:
107
+ Dictionary mapping step types to handler functions
108
+ """
109
+ return _HANDLER_REGISTRY.copy()
110
+
111
+
112
+ def list_handler_types() -> list[str]:
113
+ """List all registered handler types.
114
+
115
+ Returns:
116
+ Sorted list of step type identifiers
117
+ """
118
+ return sorted(_HANDLER_REGISTRY.keys())
119
+
120
+
121
+ def register_all_handlers(pipeline: Pipeline) -> None:
122
+ """Register all handlers with a pipeline.
123
+
124
+ This is the main entry point for setting up a pipeline with all
125
+ available handlers. Import this after importing handler modules.
126
+
127
+ Args:
128
+ pipeline: Pipeline instance to register handlers with
129
+
130
+ Example:
131
+ >>> from oscura.core.config.pipeline import Pipeline
132
+ >>> from oscura.pipeline.handlers import register_all_handlers
133
+ >>>
134
+ >>> pipeline = Pipeline.load("analysis.yaml")
135
+ >>> register_all_handlers(pipeline)
136
+ >>> results = pipeline.execute()
137
+ """
138
+ for step_type, handler in _HANDLER_REGISTRY.items():
139
+ pipeline.register_handler(step_type, handler)
140
+
141
+
142
+ # Import all handler modules to trigger registration
143
+ # These imports must come after the registry is defined
144
+ from oscura.pipeline.handlers import (
145
+ analyzers,
146
+ decoders,
147
+ exporters,
148
+ filters,
149
+ loaders,
150
+ transforms,
151
+ )
152
+
153
+ __all__ = [
154
+ "analyzers",
155
+ "decoders",
156
+ "exporters",
157
+ "filters",
158
+ "get_all_handlers",
159
+ "get_handler",
160
+ "list_handler_types",
161
+ "loaders",
162
+ "register_all_handlers",
163
+ "register_handler",
164
+ "transforms",
165
+ ]