oscura 0.0.1__py3-none-any.whl → 0.1.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.
- 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.0.dist-info/METADATA +300 -0
- oscura-0.1.0.dist-info/RECORD +463 -0
- oscura-0.1.0.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.0.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.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
"""Plugin architecture for third-party extensions.
|
|
2
|
+
|
|
3
|
+
This module implements entry point discovery for third-party plugins,
|
|
4
|
+
allowing custom decoders, measurements, and file formats to be loaded
|
|
5
|
+
dynamically.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import importlib.metadata
|
|
11
|
+
import logging
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PluginError(Exception):
|
|
19
|
+
"""Exception raised when plugin loading fails.
|
|
20
|
+
|
|
21
|
+
This exception is used to isolate plugin failures so they don't
|
|
22
|
+
crash the main application.
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
>>> try:
|
|
26
|
+
... plugin = tk.load_plugin('oscura.decoders', 'flexray')
|
|
27
|
+
... except tk.PluginError as e:
|
|
28
|
+
... print(f"Plugin failed: {e}")
|
|
29
|
+
|
|
30
|
+
References:
|
|
31
|
+
API-007: Plugin Architecture
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class PluginMetadata:
|
|
37
|
+
"""Metadata about a loaded plugin.
|
|
38
|
+
|
|
39
|
+
Attributes:
|
|
40
|
+
name: Plugin name.
|
|
41
|
+
entry_point: Entry point group.
|
|
42
|
+
version: Plugin version (if available).
|
|
43
|
+
module: Module name.
|
|
44
|
+
callable: The loaded plugin object.
|
|
45
|
+
dependencies: Plugin dependencies (if available).
|
|
46
|
+
|
|
47
|
+
Example:
|
|
48
|
+
>>> plugin = load_plugin('oscura.decoders', 'can')
|
|
49
|
+
>>> print(f"Loaded {plugin.name} v{plugin.version}")
|
|
50
|
+
|
|
51
|
+
References:
|
|
52
|
+
API-007: Plugin Architecture
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
name: str
|
|
56
|
+
entry_point: str
|
|
57
|
+
version: str | None = None
|
|
58
|
+
module: str | None = None
|
|
59
|
+
callable: Any | None = None
|
|
60
|
+
dependencies: list[str] | None = None
|
|
61
|
+
|
|
62
|
+
def __repr__(self) -> str:
|
|
63
|
+
"""String representation of plugin metadata.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
String representation showing plugin name, version, and module.
|
|
67
|
+
"""
|
|
68
|
+
parts = [f"name='{self.name}'"]
|
|
69
|
+
if self.version:
|
|
70
|
+
parts.append(f"version='{self.version}'")
|
|
71
|
+
if self.module:
|
|
72
|
+
parts.append(f"module='{self.module}'")
|
|
73
|
+
return f"PluginMetadata({', '.join(parts)})"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class PluginManager:
|
|
77
|
+
"""Manager for discovering and loading third-party plugins.
|
|
78
|
+
|
|
79
|
+
Discovers plugins via setuptools entry points and provides lazy loading
|
|
80
|
+
with error isolation. Supports multiple entry point groups for different
|
|
81
|
+
plugin types.
|
|
82
|
+
|
|
83
|
+
Entry point groups:
|
|
84
|
+
- oscura.decoders: Protocol decoders
|
|
85
|
+
- oscura.measurements: Custom measurements
|
|
86
|
+
- oscura.loaders: File format loaders
|
|
87
|
+
- oscura.exporters: Export format handlers
|
|
88
|
+
|
|
89
|
+
Example:
|
|
90
|
+
>>> import oscura as tk
|
|
91
|
+
>>> # Plugins auto-discovered from installed packages
|
|
92
|
+
>>> # Use plugin decoder
|
|
93
|
+
>>> can_frames = tk.decode(trace, protocol='can', baudrate=500000)
|
|
94
|
+
>>> # List available plugins
|
|
95
|
+
>>> plugins = tk.list_plugins()
|
|
96
|
+
>>> print(f"Installed plugins: {plugins}")
|
|
97
|
+
|
|
98
|
+
Advanced Example:
|
|
99
|
+
>>> # Manually load plugin with error handling
|
|
100
|
+
>>> try:
|
|
101
|
+
... plugin = tk.load_plugin('oscura.decoders', 'flexray')
|
|
102
|
+
... print(f"Plugin loaded: {plugin.name} v{plugin.version}")
|
|
103
|
+
... except tk.PluginError as e:
|
|
104
|
+
... print(f"Plugin failed to load: {e}")
|
|
105
|
+
|
|
106
|
+
Plugin Package Example:
|
|
107
|
+
In your package's pyproject.toml:
|
|
108
|
+
```toml
|
|
109
|
+
[project.entry-points."oscura.decoders"]
|
|
110
|
+
flexray = "my_package.flexray:FlexRayDecoder"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
References:
|
|
114
|
+
API-007: Plugin Architecture
|
|
115
|
+
importlib.metadata entry points
|
|
116
|
+
https://packaging.python.org/en/latest/guides/creating-and-discovering-plugins/
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
# Standard entry point groups
|
|
120
|
+
ENTRY_POINT_GROUPS = [ # noqa: RUF012
|
|
121
|
+
"oscura.decoders",
|
|
122
|
+
"oscura.measurements",
|
|
123
|
+
"oscura.loaders",
|
|
124
|
+
"oscura.exporters",
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
def __init__(self) -> None:
|
|
128
|
+
"""Initialize plugin manager."""
|
|
129
|
+
self._loaded_plugins: dict[tuple[str, str], PluginMetadata] = {}
|
|
130
|
+
self._failed_plugins: dict[tuple[str, str], Exception] = {}
|
|
131
|
+
|
|
132
|
+
def discover_plugins(self, group: str | None = None) -> dict[str, list[str]]:
|
|
133
|
+
"""Discover available plugins via entry points.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
group: Specific entry point group to search. If None, searches
|
|
137
|
+
all standard groups.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Dictionary mapping group names to lists of plugin names.
|
|
141
|
+
|
|
142
|
+
Example:
|
|
143
|
+
>>> manager = PluginManager()
|
|
144
|
+
>>> plugins = manager.discover_plugins()
|
|
145
|
+
>>> print(plugins)
|
|
146
|
+
{'oscura.decoders': ['uart', 'spi', 'can'], ...}
|
|
147
|
+
|
|
148
|
+
References:
|
|
149
|
+
importlib.metadata.entry_points
|
|
150
|
+
"""
|
|
151
|
+
discovered: dict[str, list[str]] = {}
|
|
152
|
+
groups = [group] if group else self.ENTRY_POINT_GROUPS
|
|
153
|
+
|
|
154
|
+
for group_name in groups:
|
|
155
|
+
try:
|
|
156
|
+
# Get entry points for this group
|
|
157
|
+
# Python 3.10+ API
|
|
158
|
+
eps = importlib.metadata.entry_points(group=group_name)
|
|
159
|
+
discovered[group_name] = [ep.name for ep in eps]
|
|
160
|
+
except (AttributeError, TypeError):
|
|
161
|
+
# Fallback for Python 3.9 and earlier
|
|
162
|
+
try:
|
|
163
|
+
eps = importlib.metadata.entry_points().get(group_name, []) # type: ignore[attr-defined]
|
|
164
|
+
discovered[group_name] = [ep.name for ep in eps]
|
|
165
|
+
except Exception as e:
|
|
166
|
+
logger.warning(f"Failed to discover plugins for group '{group_name}': {e}")
|
|
167
|
+
discovered[group_name] = []
|
|
168
|
+
|
|
169
|
+
return discovered
|
|
170
|
+
|
|
171
|
+
def load_plugin(
|
|
172
|
+
self,
|
|
173
|
+
group: str,
|
|
174
|
+
name: str,
|
|
175
|
+
reload: bool = False,
|
|
176
|
+
) -> PluginMetadata:
|
|
177
|
+
"""Load a plugin by group and name.
|
|
178
|
+
|
|
179
|
+
Loads the plugin lazily on first use. Subsequent calls return cached
|
|
180
|
+
instance unless reload=True.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
group: Entry point group.
|
|
184
|
+
name: Plugin name.
|
|
185
|
+
reload: Force reload even if already loaded. Default False.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
PluginMetadata with loaded plugin information.
|
|
189
|
+
|
|
190
|
+
Raises:
|
|
191
|
+
PluginError: If plugin fails to load.
|
|
192
|
+
|
|
193
|
+
Example:
|
|
194
|
+
>>> manager = PluginManager()
|
|
195
|
+
>>> plugin = manager.load_plugin('oscura.decoders', 'can')
|
|
196
|
+
>>> decoder = plugin.callable
|
|
197
|
+
|
|
198
|
+
References:
|
|
199
|
+
API-007: Plugin Architecture
|
|
200
|
+
"""
|
|
201
|
+
plugin_key = (group, name)
|
|
202
|
+
|
|
203
|
+
# Check if already loaded
|
|
204
|
+
if not reload and plugin_key in self._loaded_plugins:
|
|
205
|
+
return self._loaded_plugins[plugin_key]
|
|
206
|
+
|
|
207
|
+
# Check if previously failed
|
|
208
|
+
if not reload and plugin_key in self._failed_plugins:
|
|
209
|
+
raise PluginError(
|
|
210
|
+
f"Plugin '{name}' in group '{group}' previously failed to load: "
|
|
211
|
+
f"{self._failed_plugins[plugin_key]}"
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
# Find entry point
|
|
216
|
+
entry_point = self._find_entry_point(group, name)
|
|
217
|
+
|
|
218
|
+
if entry_point is None:
|
|
219
|
+
raise PluginError(f"Plugin '{name}' not found in group '{group}'")
|
|
220
|
+
|
|
221
|
+
# Load the plugin
|
|
222
|
+
logger.info(f"Loading plugin '{name}' from group '{group}'")
|
|
223
|
+
plugin_obj = entry_point.load()
|
|
224
|
+
|
|
225
|
+
# Get version if available
|
|
226
|
+
version = None
|
|
227
|
+
if hasattr(entry_point, "dist") and entry_point.dist:
|
|
228
|
+
version = entry_point.dist.version
|
|
229
|
+
|
|
230
|
+
# Create metadata
|
|
231
|
+
metadata = PluginMetadata(
|
|
232
|
+
name=name,
|
|
233
|
+
entry_point=group,
|
|
234
|
+
version=version,
|
|
235
|
+
module=entry_point.value,
|
|
236
|
+
callable=plugin_obj,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
# Cache loaded plugin
|
|
240
|
+
self._loaded_plugins[plugin_key] = metadata
|
|
241
|
+
|
|
242
|
+
logger.info(f"Successfully loaded plugin '{name}' v{version}")
|
|
243
|
+
return metadata
|
|
244
|
+
|
|
245
|
+
except Exception as e:
|
|
246
|
+
# Cache failure
|
|
247
|
+
self._failed_plugins[plugin_key] = e
|
|
248
|
+
logger.error(f"Failed to load plugin '{name}': {e}")
|
|
249
|
+
raise PluginError(f"Failed to load plugin '{name}' from group '{group}': {e}") from e
|
|
250
|
+
|
|
251
|
+
def get_plugin(self, group: str, name: str) -> Any:
|
|
252
|
+
"""Get loaded plugin callable.
|
|
253
|
+
|
|
254
|
+
Convenience method that loads plugin if needed and returns the
|
|
255
|
+
callable object.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
group: Entry point group.
|
|
259
|
+
name: Plugin name.
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
The loaded plugin object.
|
|
263
|
+
|
|
264
|
+
Example:
|
|
265
|
+
>>> manager = PluginManager()
|
|
266
|
+
>>> decoder = manager.get_plugin('oscura.decoders', 'can')
|
|
267
|
+
>>> frames = decoder.decode(trace)
|
|
268
|
+
"""
|
|
269
|
+
metadata = self.load_plugin(group, name)
|
|
270
|
+
return metadata.callable
|
|
271
|
+
|
|
272
|
+
def is_loaded(self, group: str, name: str) -> bool:
|
|
273
|
+
"""Check if plugin is already loaded.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
group: Entry point group.
|
|
277
|
+
name: Plugin name.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
True if plugin is loaded.
|
|
281
|
+
|
|
282
|
+
Example:
|
|
283
|
+
>>> if manager.is_loaded('oscura.decoders', 'can'):
|
|
284
|
+
... print("CAN decoder already loaded")
|
|
285
|
+
"""
|
|
286
|
+
return (group, name) in self._loaded_plugins
|
|
287
|
+
|
|
288
|
+
def list_loaded_plugins(self) -> list[PluginMetadata]:
|
|
289
|
+
"""List all loaded plugins.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
List of PluginMetadata for loaded plugins.
|
|
293
|
+
|
|
294
|
+
Example:
|
|
295
|
+
>>> loaded = manager.list_loaded_plugins()
|
|
296
|
+
>>> for plugin in loaded:
|
|
297
|
+
... print(f"{plugin.name} v{plugin.version}")
|
|
298
|
+
"""
|
|
299
|
+
return list(self._loaded_plugins.values())
|
|
300
|
+
|
|
301
|
+
def unload_plugin(self, group: str, name: str) -> None:
|
|
302
|
+
"""Unload a plugin from cache.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
group: Entry point group.
|
|
306
|
+
name: Plugin name.
|
|
307
|
+
|
|
308
|
+
Example:
|
|
309
|
+
>>> manager.unload_plugin('oscura.decoders', 'can')
|
|
310
|
+
"""
|
|
311
|
+
plugin_key = (group, name)
|
|
312
|
+
if plugin_key in self._loaded_plugins:
|
|
313
|
+
del self._loaded_plugins[plugin_key]
|
|
314
|
+
if plugin_key in self._failed_plugins:
|
|
315
|
+
del self._failed_plugins[plugin_key]
|
|
316
|
+
|
|
317
|
+
def _find_entry_point(self, group: str, name: str) -> Any | None:
|
|
318
|
+
"""Find entry point by group and name.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
group: Entry point group.
|
|
322
|
+
name: Entry point name.
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
Entry point object or None if not found.
|
|
326
|
+
"""
|
|
327
|
+
try:
|
|
328
|
+
# Python 3.10+ API
|
|
329
|
+
eps = importlib.metadata.entry_points(group=group)
|
|
330
|
+
for ep in eps:
|
|
331
|
+
if ep.name == name:
|
|
332
|
+
return ep
|
|
333
|
+
except (AttributeError, TypeError):
|
|
334
|
+
# Fallback for Python 3.9 and earlier
|
|
335
|
+
try:
|
|
336
|
+
eps = importlib.metadata.entry_points().get(group, []) # type: ignore[attr-defined]
|
|
337
|
+
for ep in eps:
|
|
338
|
+
if ep.name == name:
|
|
339
|
+
return ep
|
|
340
|
+
except Exception:
|
|
341
|
+
pass
|
|
342
|
+
|
|
343
|
+
return None
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
# Global plugin manager instance
|
|
347
|
+
_manager = PluginManager()
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def load_plugin(group: str, name: str) -> PluginMetadata:
|
|
351
|
+
"""Load a plugin from the global plugin manager.
|
|
352
|
+
|
|
353
|
+
Convenience function for loading plugins without accessing the manager
|
|
354
|
+
directly.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
group: Entry point group.
|
|
358
|
+
name: Plugin name.
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
PluginMetadata with loaded plugin.
|
|
362
|
+
|
|
363
|
+
Example:
|
|
364
|
+
>>> import oscura as tk
|
|
365
|
+
>>> plugin = tk.load_plugin('oscura.decoders', 'flexray')
|
|
366
|
+
>>> print(f"Loaded {plugin.name} v{plugin.version}")
|
|
367
|
+
|
|
368
|
+
References:
|
|
369
|
+
API-007: Plugin Architecture
|
|
370
|
+
"""
|
|
371
|
+
return _manager.load_plugin(group, name)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def list_plugins(group: str | None = None) -> dict[str, list[str]]:
|
|
375
|
+
"""List available plugins.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
group: Specific group to list. If None, lists all groups.
|
|
379
|
+
|
|
380
|
+
Returns:
|
|
381
|
+
Dictionary mapping group names to plugin names.
|
|
382
|
+
|
|
383
|
+
Example:
|
|
384
|
+
>>> import oscura as tk
|
|
385
|
+
>>> plugins = tk.list_plugins()
|
|
386
|
+
>>> print(f"Available decoders: {plugins['oscura.decoders']}")
|
|
387
|
+
|
|
388
|
+
References:
|
|
389
|
+
API-007: Plugin Architecture
|
|
390
|
+
"""
|
|
391
|
+
return _manager.discover_plugins(group)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def get_plugin_manager() -> PluginManager:
|
|
395
|
+
"""Get the global plugin manager instance.
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
Global PluginManager instance.
|
|
399
|
+
|
|
400
|
+
Example:
|
|
401
|
+
>>> manager = tk.get_plugin_manager()
|
|
402
|
+
>>> loaded = manager.list_loaded_plugins()
|
|
403
|
+
"""
|
|
404
|
+
return _manager
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
__all__ = [
|
|
408
|
+
"PluginError",
|
|
409
|
+
"PluginManager",
|
|
410
|
+
"PluginMetadata",
|
|
411
|
+
"get_plugin_manager",
|
|
412
|
+
"list_plugins",
|
|
413
|
+
"load_plugin",
|
|
414
|
+
]
|