oscura 0.0.1__py3-none-any.whl → 0.1.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.
- oscura/__init__.py +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.1.dist-info/METADATA +300 -0
- oscura-0.1.1.dist-info/RECORD +463 -0
- oscura-0.1.1.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,1125 @@
|
|
|
1
|
+
"""Extension point registry and management system.
|
|
2
|
+
|
|
3
|
+
This module implements a central registry for extension points that allows
|
|
4
|
+
plugins and custom code to extend Oscura functionality at well-defined
|
|
5
|
+
integration points.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from enum import Enum, auto
|
|
13
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from collections.abc import Callable
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
T = TypeVar("T")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class HookErrorPolicy(Enum):
|
|
24
|
+
"""Policy for handling hook errors.
|
|
25
|
+
|
|
26
|
+
Attributes:
|
|
27
|
+
CONTINUE: Continue executing remaining hooks after error
|
|
28
|
+
ABORT: Stop execution immediately on error
|
|
29
|
+
IGNORE: Ignore error silently
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
CONTINUE = auto()
|
|
33
|
+
ABORT = auto()
|
|
34
|
+
IGNORE = auto()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class ExtensionPointSpec:
|
|
39
|
+
"""Specification for an extension point.
|
|
40
|
+
|
|
41
|
+
Defines the contract that implementations must follow including
|
|
42
|
+
required and optional methods, version info, and documentation.
|
|
43
|
+
|
|
44
|
+
Attributes:
|
|
45
|
+
name: Unique name for the extension point
|
|
46
|
+
version: API version (semver format)
|
|
47
|
+
description: Human-readable description
|
|
48
|
+
required_methods: List of method names that must be implemented
|
|
49
|
+
optional_methods: List of optional method names
|
|
50
|
+
interface: Optional interface class that implementations should inherit from
|
|
51
|
+
|
|
52
|
+
Example:
|
|
53
|
+
>>> spec = ExtensionPointSpec(
|
|
54
|
+
... name="protocol_decoder",
|
|
55
|
+
... version="1.0.0",
|
|
56
|
+
... description="Decode protocol from waveform",
|
|
57
|
+
... required_methods=["decode", "get_metadata"],
|
|
58
|
+
... optional_methods=["configure", "reset"]
|
|
59
|
+
... )
|
|
60
|
+
|
|
61
|
+
References:
|
|
62
|
+
EXT-001: Extension Point Registry
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
name: str
|
|
66
|
+
version: str = "1.0.0"
|
|
67
|
+
description: str = ""
|
|
68
|
+
required_methods: list[str] = field(default_factory=list)
|
|
69
|
+
optional_methods: list[str] = field(default_factory=list)
|
|
70
|
+
interface: type | None = None
|
|
71
|
+
|
|
72
|
+
def validate_implementation(self, impl: Any) -> tuple[bool, list[str]]:
|
|
73
|
+
"""Validate that implementation matches interface.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
impl: Implementation to validate
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Tuple of (is_valid, list of missing methods)
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
>>> is_valid, missing = spec.validate_implementation(MyDecoder())
|
|
83
|
+
>>> if not is_valid:
|
|
84
|
+
... print(f"Missing methods: {missing}")
|
|
85
|
+
"""
|
|
86
|
+
missing = []
|
|
87
|
+
for method in self.required_methods:
|
|
88
|
+
if not hasattr(impl, method) or not callable(getattr(impl, method)):
|
|
89
|
+
missing.append(method)
|
|
90
|
+
return len(missing) == 0, missing
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass
|
|
94
|
+
class RegisteredAlgorithm:
|
|
95
|
+
"""Metadata for a registered algorithm.
|
|
96
|
+
|
|
97
|
+
Attributes:
|
|
98
|
+
name: Algorithm name
|
|
99
|
+
category: Algorithm category
|
|
100
|
+
func: Algorithm implementation
|
|
101
|
+
priority: Execution priority (higher = first)
|
|
102
|
+
performance: Performance characteristics
|
|
103
|
+
supports: Supported data types
|
|
104
|
+
description: Human-readable description
|
|
105
|
+
complexity: Time complexity string
|
|
106
|
+
capabilities: Algorithm capabilities
|
|
107
|
+
memory_usage: Memory usage characteristics
|
|
108
|
+
registration_order: Order in which algorithm was registered
|
|
109
|
+
|
|
110
|
+
References:
|
|
111
|
+
EXT-002: Algorithm Registration (capability queries, performance metadata)
|
|
112
|
+
EXT-004: Priority System (registration order for tie-breaking)
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
name: str
|
|
116
|
+
category: str
|
|
117
|
+
func: Callable[..., Any]
|
|
118
|
+
priority: int = 50
|
|
119
|
+
performance: dict[str, str] = field(default_factory=dict)
|
|
120
|
+
supports: list[str] = field(default_factory=list)
|
|
121
|
+
description: str = ""
|
|
122
|
+
complexity: str = "O(n)"
|
|
123
|
+
capabilities: dict[str, Any] = field(default_factory=dict)
|
|
124
|
+
memory_usage: str = "unknown"
|
|
125
|
+
registration_order: int = 0
|
|
126
|
+
|
|
127
|
+
def can(self, capability: str) -> bool:
|
|
128
|
+
"""Check if algorithm has a specific capability.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
capability: Capability name to check
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
True if algorithm supports the capability
|
|
135
|
+
|
|
136
|
+
Example:
|
|
137
|
+
>>> algo.can("multi_channel")
|
|
138
|
+
True
|
|
139
|
+
|
|
140
|
+
References:
|
|
141
|
+
EXT-002: Algorithm Registration (capability queries)
|
|
142
|
+
"""
|
|
143
|
+
return self.capabilities.get(capability, False) # type: ignore[no-any-return]
|
|
144
|
+
|
|
145
|
+
def get_capabilities(self) -> dict[str, Any]:
|
|
146
|
+
"""Get all capabilities of this algorithm.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Dictionary of capability names to values
|
|
150
|
+
|
|
151
|
+
Example:
|
|
152
|
+
>>> caps = algo.get_capabilities()
|
|
153
|
+
>>> print(caps)
|
|
154
|
+
{'multi_channel': True, 'real_time': False, 'max_sample_rate': 1000000}
|
|
155
|
+
|
|
156
|
+
References:
|
|
157
|
+
EXT-002: Algorithm Registration (capability queries)
|
|
158
|
+
"""
|
|
159
|
+
return self.capabilities.copy()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@dataclass
|
|
163
|
+
class HookContext:
|
|
164
|
+
"""Context passed to hook functions.
|
|
165
|
+
|
|
166
|
+
Attributes:
|
|
167
|
+
data: Primary data being processed
|
|
168
|
+
metadata: Additional context metadata
|
|
169
|
+
abort: Set to True to abort operation
|
|
170
|
+
abort_reason: Reason for abort
|
|
171
|
+
|
|
172
|
+
Example:
|
|
173
|
+
>>> @osc.hooks.register("pre_decode")
|
|
174
|
+
>>> def validate_waveform(context):
|
|
175
|
+
... if context.data.sample_rate < 1000:
|
|
176
|
+
... context.abort = True
|
|
177
|
+
... context.abort_reason = "Sample rate too low"
|
|
178
|
+
... return context
|
|
179
|
+
|
|
180
|
+
References:
|
|
181
|
+
EXT-005: Hook System
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
data: Any = None
|
|
185
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
186
|
+
abort: bool = False
|
|
187
|
+
abort_reason: str = ""
|
|
188
|
+
|
|
189
|
+
def __post_init__(self): # type: ignore[no-untyped-def]
|
|
190
|
+
"""Initialize metadata if None."""
|
|
191
|
+
if self.metadata is None:
|
|
192
|
+
self.metadata = {} # type: ignore[unreachable]
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@dataclass
|
|
196
|
+
class RegisteredHook:
|
|
197
|
+
"""Registered hook function.
|
|
198
|
+
|
|
199
|
+
Attributes:
|
|
200
|
+
hook_point: Name of hook point
|
|
201
|
+
func: Hook function
|
|
202
|
+
priority: Execution priority (higher = first)
|
|
203
|
+
name: Optional hook name
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
hook_point: str
|
|
207
|
+
func: Callable[[HookContext], HookContext]
|
|
208
|
+
priority: int = 50
|
|
209
|
+
name: str = ""
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class ExtensionPointRegistry:
|
|
213
|
+
"""Central registry of all extension points in Oscura.
|
|
214
|
+
|
|
215
|
+
Manages registration and lookup of extension points, algorithms,
|
|
216
|
+
and hooks throughout the system.
|
|
217
|
+
|
|
218
|
+
Example:
|
|
219
|
+
>>> # List all extension points
|
|
220
|
+
>>> extension_points = osc.extensions.list()
|
|
221
|
+
>>> for ep in extension_points:
|
|
222
|
+
... print(f"{ep.name} v{ep.version}")
|
|
223
|
+
|
|
224
|
+
>>> # Get specific extension point
|
|
225
|
+
>>> decoder_ep = osc.extensions.get("protocol_decoder")
|
|
226
|
+
>>> print(f"Required methods: {decoder_ep.required_methods}")
|
|
227
|
+
|
|
228
|
+
References:
|
|
229
|
+
EXT-001: Extension Point Registry
|
|
230
|
+
EXT-002: Algorithm Registration
|
|
231
|
+
EXT-003: Algorithm Selection
|
|
232
|
+
EXT-004: Priority System
|
|
233
|
+
EXT-005: Hook System
|
|
234
|
+
EXT-006: Custom Decoder Registration
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
_instance: ExtensionPointRegistry | None = None
|
|
238
|
+
|
|
239
|
+
def __new__(cls) -> ExtensionPointRegistry:
|
|
240
|
+
"""Ensure singleton instance.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
Singleton ExtensionPointRegistry instance.
|
|
244
|
+
"""
|
|
245
|
+
if cls._instance is None:
|
|
246
|
+
cls._instance = super().__new__(cls)
|
|
247
|
+
cls._instance._extension_points: dict[str, ExtensionPointSpec] = {} # type: ignore[misc, attr-defined]
|
|
248
|
+
cls._instance._algorithms: dict[str, dict[str, RegisteredAlgorithm]] = {} # type: ignore[misc, attr-defined]
|
|
249
|
+
cls._instance._hooks: dict[str, list[RegisteredHook]] = {} # type: ignore[misc, attr-defined]
|
|
250
|
+
cls._instance._hook_error_policy = HookErrorPolicy.CONTINUE
|
|
251
|
+
cls._instance._log_hook_errors = True
|
|
252
|
+
cls._instance._initialized = False # type: ignore[has-type]
|
|
253
|
+
cls._instance._registration_counter = 0 # type: ignore[misc, attr-defined]
|
|
254
|
+
return cls._instance
|
|
255
|
+
|
|
256
|
+
def initialize(self) -> None:
|
|
257
|
+
"""Initialize built-in extension points.
|
|
258
|
+
|
|
259
|
+
Registers the standard extension points that come with Oscura.
|
|
260
|
+
"""
|
|
261
|
+
if self._initialized: # type: ignore[has-type]
|
|
262
|
+
return
|
|
263
|
+
|
|
264
|
+
# Register standard extension points
|
|
265
|
+
self.register_point(
|
|
266
|
+
ExtensionPointSpec(
|
|
267
|
+
name="protocol_decoder",
|
|
268
|
+
version="1.0.0",
|
|
269
|
+
description="Decode protocol from waveform or digital trace",
|
|
270
|
+
required_methods=["decode", "get_metadata"],
|
|
271
|
+
optional_methods=["configure", "reset", "validate_config"],
|
|
272
|
+
)
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
self.register_point(
|
|
276
|
+
ExtensionPointSpec(
|
|
277
|
+
name="file_loader",
|
|
278
|
+
version="1.0.0",
|
|
279
|
+
description="Load trace data from file format",
|
|
280
|
+
required_methods=["load", "can_load"],
|
|
281
|
+
optional_methods=["get_metadata", "get_channels"],
|
|
282
|
+
)
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
self.register_point(
|
|
286
|
+
ExtensionPointSpec(
|
|
287
|
+
name="measurement",
|
|
288
|
+
version="1.0.0",
|
|
289
|
+
description="Compute measurement from trace",
|
|
290
|
+
required_methods=["measure"],
|
|
291
|
+
optional_methods=["validate_input", "get_units"],
|
|
292
|
+
)
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
self.register_point(
|
|
296
|
+
ExtensionPointSpec(
|
|
297
|
+
name="exporter",
|
|
298
|
+
version="1.0.0",
|
|
299
|
+
description="Export trace data to file format",
|
|
300
|
+
required_methods=["export"],
|
|
301
|
+
optional_methods=["get_supported_formats"],
|
|
302
|
+
)
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
self.register_point(
|
|
306
|
+
ExtensionPointSpec(
|
|
307
|
+
name="algorithm",
|
|
308
|
+
version="1.0.0",
|
|
309
|
+
description="Signal processing algorithm",
|
|
310
|
+
required_methods=["process"],
|
|
311
|
+
optional_methods=["configure", "get_parameters"],
|
|
312
|
+
)
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
self._initialized = True
|
|
316
|
+
logger.debug("Extension point registry initialized with built-in points")
|
|
317
|
+
|
|
318
|
+
# =========================================================================
|
|
319
|
+
# Extension Point Management (EXT-001)
|
|
320
|
+
# =========================================================================
|
|
321
|
+
|
|
322
|
+
def register_point(self, spec: ExtensionPointSpec) -> None:
|
|
323
|
+
"""Register an extension point.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
spec: Extension point specification
|
|
327
|
+
|
|
328
|
+
Raises:
|
|
329
|
+
ValueError: If extension point already exists
|
|
330
|
+
|
|
331
|
+
Example:
|
|
332
|
+
>>> spec = ExtensionPointSpec(
|
|
333
|
+
... name="my_extension",
|
|
334
|
+
... version="1.0.0",
|
|
335
|
+
... required_methods=["process"]
|
|
336
|
+
... )
|
|
337
|
+
>>> registry.register_point(spec)
|
|
338
|
+
"""
|
|
339
|
+
if spec.name in self._extension_points: # type: ignore[attr-defined]
|
|
340
|
+
raise ValueError(f"Extension point '{spec.name}' already registered")
|
|
341
|
+
self._extension_points[spec.name] = spec # type: ignore[attr-defined]
|
|
342
|
+
logger.debug(f"Registered extension point: {spec.name} v{spec.version}")
|
|
343
|
+
|
|
344
|
+
def get_point(self, name: str) -> ExtensionPointSpec:
|
|
345
|
+
"""Get extension point specification.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
name: Extension point name
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
Extension point specification
|
|
352
|
+
|
|
353
|
+
Raises:
|
|
354
|
+
KeyError: If extension point not found
|
|
355
|
+
"""
|
|
356
|
+
if name not in self._extension_points: # type: ignore[attr-defined]
|
|
357
|
+
raise KeyError(
|
|
358
|
+
f"Extension point '{name}' not found. "
|
|
359
|
+
f"Available: {list(self._extension_points.keys())}" # type: ignore[attr-defined]
|
|
360
|
+
)
|
|
361
|
+
return self._extension_points[name] # type: ignore[no-any-return, attr-defined]
|
|
362
|
+
|
|
363
|
+
def list_points(self) -> list[ExtensionPointSpec]:
|
|
364
|
+
"""List all registered extension points.
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
List of extension point specifications
|
|
368
|
+
"""
|
|
369
|
+
return list(self._extension_points.values()) # type: ignore[attr-defined]
|
|
370
|
+
|
|
371
|
+
def exists(self, name: str) -> bool:
|
|
372
|
+
"""Check if extension point exists.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
name: Extension point name
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
True if exists
|
|
379
|
+
"""
|
|
380
|
+
return name in self._extension_points # type: ignore[attr-defined]
|
|
381
|
+
|
|
382
|
+
# =========================================================================
|
|
383
|
+
# Algorithm Management (EXT-002, EXT-003, EXT-004)
|
|
384
|
+
# =========================================================================
|
|
385
|
+
|
|
386
|
+
def register_algorithm(
|
|
387
|
+
self,
|
|
388
|
+
name: str,
|
|
389
|
+
func: Callable[..., Any],
|
|
390
|
+
category: str,
|
|
391
|
+
priority: int = 50,
|
|
392
|
+
performance: dict[str, str] | None = None,
|
|
393
|
+
supports: list[str] | None = None,
|
|
394
|
+
description: str = "",
|
|
395
|
+
complexity: str = "O(n)",
|
|
396
|
+
capabilities: dict[str, Any] | None = None,
|
|
397
|
+
memory_usage: str = "unknown",
|
|
398
|
+
) -> None:
|
|
399
|
+
"""Register a custom algorithm implementation.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
name: Algorithm name
|
|
403
|
+
func: Algorithm function
|
|
404
|
+
category: Algorithm category
|
|
405
|
+
priority: Execution priority (0-100, higher = first)
|
|
406
|
+
performance: Performance characteristics dict (speed/accuracy/memory)
|
|
407
|
+
supports: List of supported data types
|
|
408
|
+
description: Human-readable description
|
|
409
|
+
complexity: Time complexity string (e.g., "O(n)", "O(n log n)")
|
|
410
|
+
capabilities: Algorithm capabilities dict (e.g., {'multi_channel': True})
|
|
411
|
+
memory_usage: Memory usage characteristics (low/medium/high/unknown)
|
|
412
|
+
|
|
413
|
+
Raises:
|
|
414
|
+
ValueError: If algorithm already registered
|
|
415
|
+
TypeError: If func is not callable
|
|
416
|
+
|
|
417
|
+
Example:
|
|
418
|
+
>>> def my_edge_detector(data, threshold=0.5):
|
|
419
|
+
... return find_edges(data, threshold)
|
|
420
|
+
>>> registry.register_algorithm(
|
|
421
|
+
... name="my_detector",
|
|
422
|
+
... func=my_edge_detector,
|
|
423
|
+
... category="edge_detection",
|
|
424
|
+
... priority=75,
|
|
425
|
+
... performance={"speed": "fast", "accuracy": "medium", "memory": "low"},
|
|
426
|
+
... capabilities={"multi_channel": True, "max_sample_rate": 1000000},
|
|
427
|
+
... memory_usage="low"
|
|
428
|
+
... )
|
|
429
|
+
|
|
430
|
+
References:
|
|
431
|
+
EXT-002: Algorithm Registration (capability queries, performance metadata)
|
|
432
|
+
"""
|
|
433
|
+
if not callable(func):
|
|
434
|
+
raise TypeError(f"Algorithm must be callable, got {type(func).__name__}")
|
|
435
|
+
|
|
436
|
+
if category not in self._algorithms: # type: ignore[attr-defined]
|
|
437
|
+
self._algorithms[category] = {} # type: ignore[attr-defined]
|
|
438
|
+
|
|
439
|
+
if name in self._algorithms[category]: # type: ignore[attr-defined]
|
|
440
|
+
raise ValueError(f"Algorithm '{name}' already registered in category '{category}'")
|
|
441
|
+
|
|
442
|
+
# Increment registration counter
|
|
443
|
+
self._registration_counter += 1 # type: ignore[attr-defined]
|
|
444
|
+
|
|
445
|
+
algo = RegisteredAlgorithm(
|
|
446
|
+
name=name,
|
|
447
|
+
category=category,
|
|
448
|
+
func=func,
|
|
449
|
+
priority=priority,
|
|
450
|
+
performance=performance or {},
|
|
451
|
+
supports=supports or [],
|
|
452
|
+
description=description,
|
|
453
|
+
complexity=complexity,
|
|
454
|
+
capabilities=capabilities or {},
|
|
455
|
+
memory_usage=memory_usage,
|
|
456
|
+
registration_order=self._registration_counter, # type: ignore[attr-defined]
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
self._algorithms[category][name] = algo # type: ignore[attr-defined]
|
|
460
|
+
logger.debug(f"Registered algorithm: {name} in category {category}")
|
|
461
|
+
|
|
462
|
+
def get_algorithm(self, category: str, name: str) -> RegisteredAlgorithm:
|
|
463
|
+
"""Get algorithm by category and name.
|
|
464
|
+
|
|
465
|
+
Args:
|
|
466
|
+
category: Algorithm category
|
|
467
|
+
name: Algorithm name
|
|
468
|
+
|
|
469
|
+
Returns:
|
|
470
|
+
Registered algorithm metadata
|
|
471
|
+
|
|
472
|
+
Raises:
|
|
473
|
+
KeyError: If not found
|
|
474
|
+
"""
|
|
475
|
+
if category not in self._algorithms: # type: ignore[attr-defined]
|
|
476
|
+
raise KeyError(f"Category '{category}' not found")
|
|
477
|
+
if name not in self._algorithms[category]: # type: ignore[attr-defined]
|
|
478
|
+
raise KeyError(f"Algorithm '{name}' not found in category '{category}'")
|
|
479
|
+
return self._algorithms[category][name] # type: ignore[no-any-return, attr-defined]
|
|
480
|
+
|
|
481
|
+
def select_algorithm(
|
|
482
|
+
self,
|
|
483
|
+
category: str,
|
|
484
|
+
name: str | None = None,
|
|
485
|
+
*,
|
|
486
|
+
optimize_for: str = "speed",
|
|
487
|
+
constraints: dict[str, Any] | None = None,
|
|
488
|
+
required_capabilities: list[str] | None = None,
|
|
489
|
+
) -> RegisteredAlgorithm:
|
|
490
|
+
"""Select algorithm implementation at runtime.
|
|
491
|
+
|
|
492
|
+
Selects by name if provided, otherwise auto-selects based on
|
|
493
|
+
optimization criteria and capability matching.
|
|
494
|
+
|
|
495
|
+
Args:
|
|
496
|
+
category: Algorithm category
|
|
497
|
+
name: Specific algorithm name (optional)
|
|
498
|
+
optimize_for: Optimization target: "speed", "accuracy", "memory"
|
|
499
|
+
constraints: Filter constraints on performance/supports
|
|
500
|
+
required_capabilities: List of required capabilities for auto-selection
|
|
501
|
+
|
|
502
|
+
Returns:
|
|
503
|
+
Selected algorithm
|
|
504
|
+
|
|
505
|
+
Raises:
|
|
506
|
+
KeyError: If category not found or no matching algorithm
|
|
507
|
+
|
|
508
|
+
Example:
|
|
509
|
+
>>> # Select by name
|
|
510
|
+
>>> algo = registry.select_algorithm("edge_detection", "fast_detector")
|
|
511
|
+
|
|
512
|
+
>>> # Auto-select for speed
|
|
513
|
+
>>> algo = registry.select_algorithm(
|
|
514
|
+
... "edge_detection",
|
|
515
|
+
... optimize_for="speed"
|
|
516
|
+
... )
|
|
517
|
+
|
|
518
|
+
>>> # Auto-select by capability matching
|
|
519
|
+
>>> algo = registry.select_algorithm(
|
|
520
|
+
... "edge_detection",
|
|
521
|
+
... required_capabilities=["multi_channel", "real_time"]
|
|
522
|
+
... )
|
|
523
|
+
|
|
524
|
+
References:
|
|
525
|
+
EXT-003: Algorithm Selection (auto-selection by capability matching)
|
|
526
|
+
"""
|
|
527
|
+
if category not in self._algorithms: # type: ignore[attr-defined]
|
|
528
|
+
raise KeyError(f"Category '{category}' not found")
|
|
529
|
+
|
|
530
|
+
if name:
|
|
531
|
+
return self.get_algorithm(category, name)
|
|
532
|
+
|
|
533
|
+
# Auto-select based on criteria
|
|
534
|
+
candidates = list(self._algorithms[category].values()) # type: ignore[attr-defined]
|
|
535
|
+
|
|
536
|
+
if not candidates:
|
|
537
|
+
raise KeyError(f"No algorithms registered in category '{category}'")
|
|
538
|
+
|
|
539
|
+
# Filter by required capabilities (EXT-003)
|
|
540
|
+
if required_capabilities:
|
|
541
|
+
filtered = []
|
|
542
|
+
for algo in candidates:
|
|
543
|
+
if all(algo.can(cap) for cap in required_capabilities):
|
|
544
|
+
filtered.append(algo)
|
|
545
|
+
candidates = filtered
|
|
546
|
+
|
|
547
|
+
if not candidates:
|
|
548
|
+
raise KeyError(f"No algorithms match required capabilities in category '{category}'")
|
|
549
|
+
|
|
550
|
+
# Apply constraints
|
|
551
|
+
if constraints:
|
|
552
|
+
filtered = []
|
|
553
|
+
for algo in candidates:
|
|
554
|
+
match = True
|
|
555
|
+
for key, value in constraints.items():
|
|
556
|
+
if key.startswith("performance."):
|
|
557
|
+
perf_key = key.split(".", 1)[1]
|
|
558
|
+
if algo.performance.get(perf_key) != value:
|
|
559
|
+
match = False
|
|
560
|
+
break
|
|
561
|
+
elif key.startswith("capabilities."):
|
|
562
|
+
cap_key = key.split(".", 1)[1]
|
|
563
|
+
if algo.capabilities.get(cap_key) != value:
|
|
564
|
+
match = False
|
|
565
|
+
break
|
|
566
|
+
elif key == "supports":
|
|
567
|
+
if isinstance(value, list):
|
|
568
|
+
if not any(s in algo.supports for s in value):
|
|
569
|
+
match = False
|
|
570
|
+
break
|
|
571
|
+
elif value not in algo.supports:
|
|
572
|
+
match = False
|
|
573
|
+
break
|
|
574
|
+
elif key == "memory_usage":
|
|
575
|
+
if algo.memory_usage != value:
|
|
576
|
+
match = False
|
|
577
|
+
break
|
|
578
|
+
if match:
|
|
579
|
+
filtered.append(algo)
|
|
580
|
+
candidates = filtered
|
|
581
|
+
|
|
582
|
+
if not candidates:
|
|
583
|
+
raise KeyError(f"No algorithms match constraints in category '{category}'")
|
|
584
|
+
|
|
585
|
+
# Sort by optimization criteria
|
|
586
|
+
if optimize_for == "speed":
|
|
587
|
+
|
|
588
|
+
def sort_key(a): # type: ignore[no-untyped-def]
|
|
589
|
+
return (
|
|
590
|
+
0
|
|
591
|
+
if a.performance.get("speed") == "fast"
|
|
592
|
+
else 1
|
|
593
|
+
if a.performance.get("speed") == "medium"
|
|
594
|
+
else 2,
|
|
595
|
+
-a.priority,
|
|
596
|
+
)
|
|
597
|
+
elif optimize_for == "accuracy":
|
|
598
|
+
|
|
599
|
+
def sort_key(a): # type: ignore[no-untyped-def]
|
|
600
|
+
return (
|
|
601
|
+
0
|
|
602
|
+
if a.performance.get("accuracy") == "high"
|
|
603
|
+
else 1
|
|
604
|
+
if a.performance.get("accuracy") == "medium"
|
|
605
|
+
else 2,
|
|
606
|
+
-a.priority,
|
|
607
|
+
)
|
|
608
|
+
elif optimize_for == "memory":
|
|
609
|
+
|
|
610
|
+
def sort_key(a): # type: ignore[no-untyped-def]
|
|
611
|
+
return (
|
|
612
|
+
0
|
|
613
|
+
if a.performance.get("memory") == "low"
|
|
614
|
+
else 1
|
|
615
|
+
if a.performance.get("memory") == "medium"
|
|
616
|
+
else 2,
|
|
617
|
+
-a.priority,
|
|
618
|
+
)
|
|
619
|
+
else:
|
|
620
|
+
# Default to priority only
|
|
621
|
+
def sort_key(a): # type: ignore[no-untyped-def]
|
|
622
|
+
return -a.priority
|
|
623
|
+
|
|
624
|
+
candidates.sort(key=sort_key)
|
|
625
|
+
return candidates[0] # type: ignore[no-any-return]
|
|
626
|
+
|
|
627
|
+
def list_algorithms(
|
|
628
|
+
self,
|
|
629
|
+
category: str,
|
|
630
|
+
ordered: bool = False,
|
|
631
|
+
tie_break: str = "name",
|
|
632
|
+
) -> list[RegisteredAlgorithm]:
|
|
633
|
+
"""List all algorithms in a category.
|
|
634
|
+
|
|
635
|
+
Args:
|
|
636
|
+
category: Algorithm category
|
|
637
|
+
ordered: If True, sort by priority (highest first)
|
|
638
|
+
tie_break: Tie-breaking rule: "name" (alphabetical) or "registration" (order registered)
|
|
639
|
+
|
|
640
|
+
Returns:
|
|
641
|
+
List of registered algorithms
|
|
642
|
+
|
|
643
|
+
Raises:
|
|
644
|
+
KeyError: If category not found
|
|
645
|
+
|
|
646
|
+
Example:
|
|
647
|
+
>>> # Get algorithms sorted by priority, ties broken by name
|
|
648
|
+
>>> algos = registry.list_algorithms("edge_detection", ordered=True, tie_break="name")
|
|
649
|
+
|
|
650
|
+
>>> # Get algorithms sorted by priority, ties broken by registration order
|
|
651
|
+
>>> algos = registry.list_algorithms("edge_detection", ordered=True, tie_break="registration")
|
|
652
|
+
|
|
653
|
+
References:
|
|
654
|
+
EXT-004: Priority System (tie-breaking rules by name or registration order)
|
|
655
|
+
"""
|
|
656
|
+
if category not in self._algorithms: # type: ignore[attr-defined]
|
|
657
|
+
raise KeyError(f"Category '{category}' not found")
|
|
658
|
+
|
|
659
|
+
algos = list(self._algorithms[category].values()) # type: ignore[attr-defined]
|
|
660
|
+
|
|
661
|
+
if ordered:
|
|
662
|
+
if tie_break == "registration":
|
|
663
|
+
# Sort by priority (highest first), then by registration order for ties
|
|
664
|
+
algos.sort(key=lambda a: (-a.priority, a.registration_order))
|
|
665
|
+
else:
|
|
666
|
+
# Sort by priority (highest first), then by name for ties (default)
|
|
667
|
+
algos.sort(key=lambda a: (-a.priority, a.name))
|
|
668
|
+
|
|
669
|
+
return algos
|
|
670
|
+
|
|
671
|
+
def list_categories(self) -> list[str]:
|
|
672
|
+
"""List all algorithm categories.
|
|
673
|
+
|
|
674
|
+
Returns:
|
|
675
|
+
List of category names
|
|
676
|
+
"""
|
|
677
|
+
return list(self._algorithms.keys()) # type: ignore[attr-defined]
|
|
678
|
+
|
|
679
|
+
def benchmark_algorithms(
|
|
680
|
+
self,
|
|
681
|
+
category: str,
|
|
682
|
+
test_data: Any,
|
|
683
|
+
*,
|
|
684
|
+
metrics: list[str] | None = None,
|
|
685
|
+
iterations: int = 10,
|
|
686
|
+
) -> dict[str, dict[str, float]]:
|
|
687
|
+
"""Benchmark all algorithms in a category.
|
|
688
|
+
|
|
689
|
+
Runs performance tests on all registered algorithms and measures
|
|
690
|
+
execution time, memory usage, and optionally custom metrics.
|
|
691
|
+
|
|
692
|
+
Args:
|
|
693
|
+
category: Algorithm category to benchmark
|
|
694
|
+
test_data: Test data to pass to algorithms
|
|
695
|
+
metrics: List of metrics to measure (defaults to ["execution_time"])
|
|
696
|
+
iterations: Number of iterations to average over
|
|
697
|
+
|
|
698
|
+
Returns:
|
|
699
|
+
Dict mapping algorithm names to metric results
|
|
700
|
+
|
|
701
|
+
Raises:
|
|
702
|
+
KeyError: If category is not found.
|
|
703
|
+
|
|
704
|
+
Example:
|
|
705
|
+
>>> import numpy as np
|
|
706
|
+
>>> test_signal = np.random.randn(1000)
|
|
707
|
+
>>> results = registry.benchmark_algorithms(
|
|
708
|
+
... "edge_detection",
|
|
709
|
+
... test_signal,
|
|
710
|
+
... metrics=["execution_time", "memory_usage"],
|
|
711
|
+
... iterations=100
|
|
712
|
+
... )
|
|
713
|
+
>>> for name, metrics in results.items():
|
|
714
|
+
... print(f"{name}: {metrics['execution_time']:.3f}s")
|
|
715
|
+
|
|
716
|
+
References:
|
|
717
|
+
EXT-003: Algorithm Selection (benchmarking support)
|
|
718
|
+
"""
|
|
719
|
+
import time
|
|
720
|
+
import tracemalloc
|
|
721
|
+
|
|
722
|
+
if category not in self._algorithms: # type: ignore[attr-defined]
|
|
723
|
+
raise KeyError(f"Category '{category}' not found")
|
|
724
|
+
|
|
725
|
+
if metrics is None:
|
|
726
|
+
metrics = ["execution_time"]
|
|
727
|
+
|
|
728
|
+
results = {}
|
|
729
|
+
|
|
730
|
+
for name, algo in self._algorithms[category].items(): # type: ignore[attr-defined]
|
|
731
|
+
algo_results = {}
|
|
732
|
+
|
|
733
|
+
if "execution_time" in metrics:
|
|
734
|
+
times = []
|
|
735
|
+
for _ in range(iterations):
|
|
736
|
+
start = time.perf_counter()
|
|
737
|
+
try:
|
|
738
|
+
algo.func(test_data)
|
|
739
|
+
except Exception as e:
|
|
740
|
+
logger.warning(f"Algorithm {name} failed during benchmark: {e}")
|
|
741
|
+
times.append(float("inf"))
|
|
742
|
+
continue
|
|
743
|
+
end = time.perf_counter()
|
|
744
|
+
times.append(end - start)
|
|
745
|
+
|
|
746
|
+
algo_results["execution_time"] = sum(times) / len(times)
|
|
747
|
+
algo_results["min_time"] = min(times)
|
|
748
|
+
algo_results["max_time"] = max(times)
|
|
749
|
+
|
|
750
|
+
if "memory_usage" in metrics:
|
|
751
|
+
tracemalloc.start()
|
|
752
|
+
try:
|
|
753
|
+
algo.func(test_data)
|
|
754
|
+
current, peak = tracemalloc.get_traced_memory()
|
|
755
|
+
algo_results["memory_current"] = current / 1024 / 1024 # MB
|
|
756
|
+
algo_results["memory_peak"] = peak / 1024 / 1024 # MB
|
|
757
|
+
except Exception as e:
|
|
758
|
+
logger.warning(f"Algorithm {name} failed during benchmark: {e}")
|
|
759
|
+
algo_results["memory_current"] = float("inf")
|
|
760
|
+
algo_results["memory_peak"] = float("inf")
|
|
761
|
+
finally:
|
|
762
|
+
tracemalloc.stop()
|
|
763
|
+
|
|
764
|
+
results[name] = algo_results
|
|
765
|
+
|
|
766
|
+
return results
|
|
767
|
+
|
|
768
|
+
def configure_priorities(self, config: dict[str, dict[str, int]]) -> None:
|
|
769
|
+
"""Override algorithm priorities via configuration.
|
|
770
|
+
|
|
771
|
+
Args:
|
|
772
|
+
config: Dict mapping category -> {algorithm_name: new_priority}
|
|
773
|
+
|
|
774
|
+
Example:
|
|
775
|
+
>>> registry.configure_priorities({
|
|
776
|
+
... "edge_detection": {
|
|
777
|
+
... "fast_detector": 100,
|
|
778
|
+
... "accurate_detector": 50
|
|
779
|
+
... }
|
|
780
|
+
... })
|
|
781
|
+
|
|
782
|
+
References:
|
|
783
|
+
EXT-004: Priority System
|
|
784
|
+
"""
|
|
785
|
+
for category, priorities in config.items():
|
|
786
|
+
if category not in self._algorithms: # type: ignore[attr-defined]
|
|
787
|
+
continue
|
|
788
|
+
for name, priority in priorities.items():
|
|
789
|
+
if name in self._algorithms[category]: # type: ignore[attr-defined]
|
|
790
|
+
self._algorithms[category][name].priority = priority # type: ignore[attr-defined]
|
|
791
|
+
logger.debug(f"Set priority for {category}/{name} to {priority}")
|
|
792
|
+
|
|
793
|
+
# =========================================================================
|
|
794
|
+
# Hook System (EXT-005)
|
|
795
|
+
# =========================================================================
|
|
796
|
+
|
|
797
|
+
def register_hook(
|
|
798
|
+
self,
|
|
799
|
+
hook_point: str,
|
|
800
|
+
func: Callable[[HookContext], HookContext],
|
|
801
|
+
priority: int = 50,
|
|
802
|
+
name: str = "",
|
|
803
|
+
) -> None:
|
|
804
|
+
"""Register a hook function.
|
|
805
|
+
|
|
806
|
+
Args:
|
|
807
|
+
hook_point: Name of hook point (e.g., "pre_decode", "post_decode")
|
|
808
|
+
func: Hook function accepting and returning HookContext
|
|
809
|
+
priority: Execution priority (higher = first)
|
|
810
|
+
name: Optional hook name for identification
|
|
811
|
+
|
|
812
|
+
Example:
|
|
813
|
+
>>> @osc.hooks.register("pre_decode")
|
|
814
|
+
>>> def validate_waveform(context):
|
|
815
|
+
... if context.data.sample_rate < 1000:
|
|
816
|
+
... raise ValueError("Sample rate too low")
|
|
817
|
+
... return context
|
|
818
|
+
|
|
819
|
+
References:
|
|
820
|
+
EXT-005: Hook System
|
|
821
|
+
"""
|
|
822
|
+
if hook_point not in self._hooks: # type: ignore[attr-defined]
|
|
823
|
+
self._hooks[hook_point] = [] # type: ignore[attr-defined]
|
|
824
|
+
|
|
825
|
+
hook = RegisteredHook(
|
|
826
|
+
hook_point=hook_point,
|
|
827
|
+
func=func,
|
|
828
|
+
priority=priority,
|
|
829
|
+
name=name or func.__name__,
|
|
830
|
+
)
|
|
831
|
+
|
|
832
|
+
self._hooks[hook_point].append(hook) # type: ignore[attr-defined]
|
|
833
|
+
# Sort by priority (highest first)
|
|
834
|
+
self._hooks[hook_point].sort(key=lambda h: -h.priority) # type: ignore[attr-defined]
|
|
835
|
+
|
|
836
|
+
logger.debug(f"Registered hook '{hook.name}' at point '{hook_point}'")
|
|
837
|
+
|
|
838
|
+
def execute_hooks(self, hook_point: str, context: HookContext) -> HookContext:
|
|
839
|
+
"""Execute all hooks at a hook point with chaining and error isolation.
|
|
840
|
+
|
|
841
|
+
Hooks are executed in priority order (highest first). Each hook receives
|
|
842
|
+
the context from the previous hook (chaining). If a hook fails, the error
|
|
843
|
+
is isolated based on the configured error policy, preventing one hook's
|
|
844
|
+
failure from stopping other hooks.
|
|
845
|
+
|
|
846
|
+
Args:
|
|
847
|
+
hook_point: Hook point name
|
|
848
|
+
context: Hook context to pass through
|
|
849
|
+
|
|
850
|
+
Returns:
|
|
851
|
+
Modified context after all hooks
|
|
852
|
+
|
|
853
|
+
Raises:
|
|
854
|
+
Exception: If error policy is ABORT and a hook fails.
|
|
855
|
+
|
|
856
|
+
Example:
|
|
857
|
+
>>> context = HookContext(data=trace)
|
|
858
|
+
>>> context = registry.execute_hooks("pre_decode", context)
|
|
859
|
+
>>> if context.abort:
|
|
860
|
+
... raise ValueError(context.abort_reason)
|
|
861
|
+
|
|
862
|
+
References:
|
|
863
|
+
EXT-005: Hook System (hook chaining, error isolation)
|
|
864
|
+
"""
|
|
865
|
+
if hook_point not in self._hooks: # type: ignore[attr-defined]
|
|
866
|
+
return context
|
|
867
|
+
|
|
868
|
+
# Execute hooks in priority order (hook chaining - EXT-005)
|
|
869
|
+
for hook in self._hooks[hook_point]: # type: ignore[attr-defined]
|
|
870
|
+
try:
|
|
871
|
+
context = hook.func(context)
|
|
872
|
+
if context.abort:
|
|
873
|
+
logger.info(f"Hook '{hook.name}' requested abort: {context.abort_reason}")
|
|
874
|
+
break
|
|
875
|
+
except Exception as e:
|
|
876
|
+
# Error isolation - EXT-005: one hook failure doesn't stop others
|
|
877
|
+
if self._log_hook_errors:
|
|
878
|
+
logger.error(f"Hook '{hook.name}' at '{hook_point}' failed: {e}")
|
|
879
|
+
|
|
880
|
+
if self._hook_error_policy == HookErrorPolicy.ABORT:
|
|
881
|
+
raise
|
|
882
|
+
elif self._hook_error_policy == HookErrorPolicy.CONTINUE:
|
|
883
|
+
continue # Continue to next hook despite error
|
|
884
|
+
# IGNORE falls through
|
|
885
|
+
|
|
886
|
+
return context
|
|
887
|
+
|
|
888
|
+
def configure_hooks(self, on_error: str = "continue", log_errors: bool = True) -> None:
|
|
889
|
+
"""Configure hook error handling behavior.
|
|
890
|
+
|
|
891
|
+
Args:
|
|
892
|
+
on_error: Error policy: "continue", "abort", "ignore"
|
|
893
|
+
log_errors: Whether to log hook errors
|
|
894
|
+
|
|
895
|
+
References:
|
|
896
|
+
EXT-005: Hook System
|
|
897
|
+
"""
|
|
898
|
+
policy_map = {
|
|
899
|
+
"continue": HookErrorPolicy.CONTINUE,
|
|
900
|
+
"abort": HookErrorPolicy.ABORT,
|
|
901
|
+
"ignore": HookErrorPolicy.IGNORE,
|
|
902
|
+
}
|
|
903
|
+
self._hook_error_policy = policy_map.get(on_error, HookErrorPolicy.CONTINUE)
|
|
904
|
+
self._log_hook_errors = log_errors
|
|
905
|
+
|
|
906
|
+
def list_hooks(self, hook_point: str | None = None) -> dict[str, list[str]]:
|
|
907
|
+
"""List registered hooks.
|
|
908
|
+
|
|
909
|
+
Args:
|
|
910
|
+
hook_point: Specific hook point, or None for all
|
|
911
|
+
|
|
912
|
+
Returns:
|
|
913
|
+
Dict mapping hook points to list of hook names
|
|
914
|
+
"""
|
|
915
|
+
if hook_point:
|
|
916
|
+
if hook_point not in self._hooks: # type: ignore[attr-defined]
|
|
917
|
+
return {hook_point: []}
|
|
918
|
+
return {hook_point: [h.name for h in self._hooks[hook_point]]} # type: ignore[attr-defined]
|
|
919
|
+
|
|
920
|
+
return {point: [h.name for h in hooks] for point, hooks in self._hooks.items()} # type: ignore[attr-defined]
|
|
921
|
+
|
|
922
|
+
def clear_hooks(self, hook_point: str | None = None) -> None:
|
|
923
|
+
"""Clear registered hooks.
|
|
924
|
+
|
|
925
|
+
Args:
|
|
926
|
+
hook_point: Specific hook point to clear, or None for all
|
|
927
|
+
"""
|
|
928
|
+
if hook_point:
|
|
929
|
+
self._hooks.pop(hook_point, None) # type: ignore[attr-defined]
|
|
930
|
+
else:
|
|
931
|
+
self._hooks.clear() # type: ignore[attr-defined]
|
|
932
|
+
|
|
933
|
+
# =========================================================================
|
|
934
|
+
# Custom Decoder Registration (EXT-006)
|
|
935
|
+
# =========================================================================
|
|
936
|
+
|
|
937
|
+
def register_decoder(self, protocol: str, decoder_class: type, priority: int = 50) -> None:
|
|
938
|
+
"""Register a custom protocol decoder.
|
|
939
|
+
|
|
940
|
+
Args:
|
|
941
|
+
protocol: Protocol name (e.g., "uart", "spi", "my_custom")
|
|
942
|
+
decoder_class: Decoder class implementing ProtocolDecoder interface
|
|
943
|
+
priority: Registration priority
|
|
944
|
+
|
|
945
|
+
Raises:
|
|
946
|
+
ValueError: If decoder doesn't implement required interface or lacks documentation
|
|
947
|
+
|
|
948
|
+
Example:
|
|
949
|
+
>>> class MyDecoder:
|
|
950
|
+
... '''Custom decoder for my protocol.'''
|
|
951
|
+
... def decode(self, trace):
|
|
952
|
+
... return []
|
|
953
|
+
... def get_metadata(self):
|
|
954
|
+
... return {"name": "my_decoder"}
|
|
955
|
+
>>> registry.register_decoder("my_protocol", MyDecoder)
|
|
956
|
+
|
|
957
|
+
References:
|
|
958
|
+
EXT-006: Custom Decoder Registration (validation of decoder interface, documentation requirements)
|
|
959
|
+
"""
|
|
960
|
+
# Validate decoder implements required interface
|
|
961
|
+
spec = self.get_point("protocol_decoder")
|
|
962
|
+
instance = decoder_class()
|
|
963
|
+
is_valid, missing = spec.validate_implementation(instance)
|
|
964
|
+
|
|
965
|
+
if not is_valid:
|
|
966
|
+
raise ValueError(f"Decoder '{protocol}' missing required methods: {missing}")
|
|
967
|
+
|
|
968
|
+
# Check documentation requirements (EXT-006)
|
|
969
|
+
if not decoder_class.__doc__ or not decoder_class.__doc__.strip():
|
|
970
|
+
raise ValueError(
|
|
971
|
+
f"Decoder '{protocol}' must have a docstring documenting its purpose and usage"
|
|
972
|
+
)
|
|
973
|
+
|
|
974
|
+
# Register as algorithm in protocol_decoder category
|
|
975
|
+
self.register_algorithm(
|
|
976
|
+
name=protocol,
|
|
977
|
+
func=decoder_class,
|
|
978
|
+
category="protocol_decoder",
|
|
979
|
+
priority=priority,
|
|
980
|
+
description=decoder_class.__doc__.strip().split("\n")[0]
|
|
981
|
+
if decoder_class.__doc__
|
|
982
|
+
else f"Protocol decoder for {protocol}",
|
|
983
|
+
)
|
|
984
|
+
|
|
985
|
+
logger.info(f"Registered custom decoder for protocol: {protocol}")
|
|
986
|
+
|
|
987
|
+
def get_decoder(self, protocol: str) -> type:
|
|
988
|
+
"""Get decoder class for a protocol.
|
|
989
|
+
|
|
990
|
+
Args:
|
|
991
|
+
protocol: Protocol name
|
|
992
|
+
|
|
993
|
+
Returns:
|
|
994
|
+
Decoder class
|
|
995
|
+
"""
|
|
996
|
+
algo = self.get_algorithm("protocol_decoder", protocol)
|
|
997
|
+
return algo.func # type: ignore[return-value]
|
|
998
|
+
|
|
999
|
+
def list_decoders(self) -> list[str]:
|
|
1000
|
+
"""List all registered protocol decoders.
|
|
1001
|
+
|
|
1002
|
+
Returns:
|
|
1003
|
+
List of protocol names
|
|
1004
|
+
"""
|
|
1005
|
+
if "protocol_decoder" not in self._algorithms: # type: ignore[attr-defined]
|
|
1006
|
+
return []
|
|
1007
|
+
return list(self._algorithms["protocol_decoder"].keys()) # type: ignore[attr-defined]
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
# Global registry instance
|
|
1011
|
+
_registry = ExtensionPointRegistry()
|
|
1012
|
+
|
|
1013
|
+
|
|
1014
|
+
# =========================================================================
|
|
1015
|
+
# Module-Level Convenience Functions
|
|
1016
|
+
# =========================================================================
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
def get_registry() -> ExtensionPointRegistry:
|
|
1020
|
+
"""Get the global extension point registry.
|
|
1021
|
+
|
|
1022
|
+
Returns:
|
|
1023
|
+
Global ExtensionPointRegistry instance
|
|
1024
|
+
"""
|
|
1025
|
+
_registry.initialize()
|
|
1026
|
+
return _registry
|
|
1027
|
+
|
|
1028
|
+
|
|
1029
|
+
def list_extension_points() -> list[ExtensionPointSpec]:
|
|
1030
|
+
"""List all registered extension points.
|
|
1031
|
+
|
|
1032
|
+
Returns:
|
|
1033
|
+
List of extension point specifications
|
|
1034
|
+
|
|
1035
|
+
References:
|
|
1036
|
+
EXT-001: Extension Point Registry
|
|
1037
|
+
"""
|
|
1038
|
+
return get_registry().list_points()
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
def get_extension_point(name: str) -> ExtensionPointSpec:
|
|
1042
|
+
"""Get extension point by name.
|
|
1043
|
+
|
|
1044
|
+
Args:
|
|
1045
|
+
name: Extension point name
|
|
1046
|
+
|
|
1047
|
+
Returns:
|
|
1048
|
+
Extension point specification
|
|
1049
|
+
|
|
1050
|
+
References:
|
|
1051
|
+
EXT-001: Extension Point Registry
|
|
1052
|
+
"""
|
|
1053
|
+
return get_registry().get_point(name)
|
|
1054
|
+
|
|
1055
|
+
|
|
1056
|
+
def extension_point_exists(name: str) -> bool:
|
|
1057
|
+
"""Check if extension point exists.
|
|
1058
|
+
|
|
1059
|
+
Args:
|
|
1060
|
+
name: Extension point name
|
|
1061
|
+
|
|
1062
|
+
Returns:
|
|
1063
|
+
True if exists
|
|
1064
|
+
|
|
1065
|
+
References:
|
|
1066
|
+
EXT-001: Extension Point Registry
|
|
1067
|
+
"""
|
|
1068
|
+
return get_registry().exists(name)
|
|
1069
|
+
|
|
1070
|
+
|
|
1071
|
+
def register_extension_point(spec: ExtensionPointSpec) -> None:
|
|
1072
|
+
"""Register a new extension point.
|
|
1073
|
+
|
|
1074
|
+
Args:
|
|
1075
|
+
spec: Extension point specification
|
|
1076
|
+
|
|
1077
|
+
References:
|
|
1078
|
+
EXT-001: Extension Point Registry
|
|
1079
|
+
"""
|
|
1080
|
+
get_registry().register_point(spec)
|
|
1081
|
+
|
|
1082
|
+
|
|
1083
|
+
# Hook decorator
|
|
1084
|
+
def hook(hook_point: str, priority: int = 50, name: str = ""): # type: ignore[no-untyped-def]
|
|
1085
|
+
"""Decorator for registering hook functions.
|
|
1086
|
+
|
|
1087
|
+
Args:
|
|
1088
|
+
hook_point: Hook point name
|
|
1089
|
+
priority: Execution priority
|
|
1090
|
+
name: Optional hook name
|
|
1091
|
+
|
|
1092
|
+
Returns:
|
|
1093
|
+
Decorator function that registers the hook.
|
|
1094
|
+
|
|
1095
|
+
Example:
|
|
1096
|
+
>>> @hook("pre_decode", priority=100)
|
|
1097
|
+
>>> def validate_input(context):
|
|
1098
|
+
... # validation logic
|
|
1099
|
+
... return context
|
|
1100
|
+
|
|
1101
|
+
References:
|
|
1102
|
+
EXT-005: Hook System
|
|
1103
|
+
"""
|
|
1104
|
+
|
|
1105
|
+
def decorator(func: Callable[[HookContext], HookContext]): # type: ignore[no-untyped-def]
|
|
1106
|
+
get_registry().register_hook(hook_point, func, priority, name or func.__name__)
|
|
1107
|
+
return func
|
|
1108
|
+
|
|
1109
|
+
return decorator
|
|
1110
|
+
|
|
1111
|
+
|
|
1112
|
+
__all__ = [
|
|
1113
|
+
"ExtensionPointRegistry",
|
|
1114
|
+
"ExtensionPointSpec",
|
|
1115
|
+
"HookContext",
|
|
1116
|
+
"HookErrorPolicy",
|
|
1117
|
+
"RegisteredAlgorithm",
|
|
1118
|
+
"RegisteredHook",
|
|
1119
|
+
"extension_point_exists",
|
|
1120
|
+
"get_extension_point",
|
|
1121
|
+
"get_registry",
|
|
1122
|
+
"hook",
|
|
1123
|
+
"list_extension_points",
|
|
1124
|
+
"register_extension_point",
|
|
1125
|
+
]
|