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,493 @@
|
|
|
1
|
+
"""Unified plugin manager orchestrating discovery, registration, lifecycle, and isolation.
|
|
2
|
+
|
|
3
|
+
This module provides a high-level PluginManager that orchestrates all plugin
|
|
4
|
+
subsystems including discovery, registration, lifecycle management, isolation,
|
|
5
|
+
versioning, and CLI operations.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Example:
|
|
9
|
+
>>> from oscura.plugins.manager import PluginManager
|
|
10
|
+
>>> manager = PluginManager()
|
|
11
|
+
>>> manager.discover_and_load()
|
|
12
|
+
>>> plugin = manager.get_plugin("uart_decoder")
|
|
13
|
+
>>> manager.enable_plugin("uart_decoder")
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import TYPE_CHECKING, Any
|
|
21
|
+
|
|
22
|
+
from oscura.plugins.discovery import discover_plugins, get_plugin_paths
|
|
23
|
+
from oscura.plugins.isolation import IsolationManager, PermissionSet, ResourceLimits
|
|
24
|
+
from oscura.plugins.lifecycle import (
|
|
25
|
+
DependencyGraph,
|
|
26
|
+
PluginLifecycleManager,
|
|
27
|
+
)
|
|
28
|
+
from oscura.plugins.registry import (
|
|
29
|
+
PluginConflictError,
|
|
30
|
+
PluginRegistry,
|
|
31
|
+
PluginVersionError,
|
|
32
|
+
)
|
|
33
|
+
from oscura.plugins.versioning import MigrationManager
|
|
34
|
+
|
|
35
|
+
if TYPE_CHECKING:
|
|
36
|
+
from oscura.plugins.base import PluginBase, PluginCapability, PluginMetadata
|
|
37
|
+
|
|
38
|
+
logger = logging.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class PluginManager:
|
|
42
|
+
"""Unified manager for all plugin operations.
|
|
43
|
+
|
|
44
|
+
Orchestrates plugin discovery, registration, lifecycle management,
|
|
45
|
+
isolation, versioning, and CLI operations.
|
|
46
|
+
|
|
47
|
+
Attributes:
|
|
48
|
+
registry: Central plugin registry
|
|
49
|
+
lifecycle: Lifecycle manager
|
|
50
|
+
isolation: Isolation manager
|
|
51
|
+
migration: Migration manager
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
plugin_dirs: list[Path] | None = None,
|
|
57
|
+
auto_discover: bool = True,
|
|
58
|
+
) -> None:
|
|
59
|
+
"""Initialize plugin manager.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
plugin_dirs: Directories to search for plugins
|
|
63
|
+
auto_discover: Automatically discover plugins on init
|
|
64
|
+
"""
|
|
65
|
+
self.plugin_dirs = plugin_dirs or list(get_plugin_paths())
|
|
66
|
+
self.registry = PluginRegistry()
|
|
67
|
+
self.lifecycle = PluginLifecycleManager(self.plugin_dirs)
|
|
68
|
+
self.isolation = IsolationManager()
|
|
69
|
+
self.migration = MigrationManager()
|
|
70
|
+
self._dependency_graph = DependencyGraph()
|
|
71
|
+
self._api_version = "1.0.0"
|
|
72
|
+
|
|
73
|
+
if auto_discover:
|
|
74
|
+
self.discover_and_load()
|
|
75
|
+
|
|
76
|
+
def discover_and_load(
|
|
77
|
+
self,
|
|
78
|
+
*,
|
|
79
|
+
compatible_only: bool = True,
|
|
80
|
+
config: dict[str, dict[str, Any]] | None = None,
|
|
81
|
+
) -> list[PluginMetadata]:
|
|
82
|
+
"""Discover and load all available plugins.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
compatible_only: Only load compatible plugins
|
|
86
|
+
config: Configuration dict keyed by plugin name
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
List of loaded plugin metadata
|
|
90
|
+
"""
|
|
91
|
+
discovered = discover_plugins(compatible_only=compatible_only)
|
|
92
|
+
loaded: list[PluginMetadata] = []
|
|
93
|
+
|
|
94
|
+
for plugin_info in discovered:
|
|
95
|
+
if plugin_info.load_error:
|
|
96
|
+
logger.warning(
|
|
97
|
+
f"Skipping plugin {plugin_info.metadata.name}: {plugin_info.load_error}"
|
|
98
|
+
)
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
if not plugin_info.compatible and compatible_only:
|
|
102
|
+
logger.debug(f"Skipping incompatible plugin: {plugin_info.metadata.name}")
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
metadata = plugin_info.metadata
|
|
107
|
+
# Note: config can be used for future plugin configuration
|
|
108
|
+
|
|
109
|
+
# Register in registry (metadata only)
|
|
110
|
+
self.registry._metadata[metadata.name] = metadata
|
|
111
|
+
self._dependency_graph.add_plugin(metadata.name)
|
|
112
|
+
|
|
113
|
+
loaded.append(metadata)
|
|
114
|
+
logger.info(f"Discovered plugin: {metadata.name} v{metadata.version}")
|
|
115
|
+
|
|
116
|
+
except Exception as e:
|
|
117
|
+
logger.error(f"Failed to process plugin {plugin_info.metadata.name}: {e}")
|
|
118
|
+
|
|
119
|
+
return loaded
|
|
120
|
+
|
|
121
|
+
def register_plugin(
|
|
122
|
+
self,
|
|
123
|
+
plugin: type[PluginBase] | PluginBase,
|
|
124
|
+
*,
|
|
125
|
+
config: dict[str, Any] | None = None,
|
|
126
|
+
check_compatibility: bool = True,
|
|
127
|
+
check_conflicts: bool = True,
|
|
128
|
+
) -> None:
|
|
129
|
+
"""Register a plugin with the manager.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
plugin: Plugin class or instance
|
|
133
|
+
config: Plugin configuration
|
|
134
|
+
check_compatibility: Verify API compatibility
|
|
135
|
+
check_conflicts: Check for duplicate names
|
|
136
|
+
|
|
137
|
+
Raises:
|
|
138
|
+
PluginConflictError: If plugin name already registered
|
|
139
|
+
PluginVersionError: If plugin is not compatible
|
|
140
|
+
"""
|
|
141
|
+
instance = plugin() if isinstance(plugin, type) else plugin
|
|
142
|
+
metadata = instance.metadata
|
|
143
|
+
|
|
144
|
+
# Check compatibility
|
|
145
|
+
if check_compatibility and not metadata.is_compatible_with(self._api_version):
|
|
146
|
+
raise PluginVersionError(
|
|
147
|
+
f"Plugin '{metadata.name}' requires API v{metadata.api_version}, "
|
|
148
|
+
f"but API is v{self._api_version}",
|
|
149
|
+
plugin_api_version=metadata.api_version,
|
|
150
|
+
oscura_api_version=self._api_version,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Check conflicts
|
|
154
|
+
if check_conflicts and self.registry.has_plugin(metadata.name):
|
|
155
|
+
existing = self.registry.get_metadata(metadata.name)
|
|
156
|
+
if existing is not None: # Always true since has_plugin returned True
|
|
157
|
+
raise PluginConflictError(
|
|
158
|
+
f"Plugin '{metadata.name}' already registered",
|
|
159
|
+
existing=existing,
|
|
160
|
+
new=metadata,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Register
|
|
164
|
+
self.registry.register(instance, config=config, check_compatibility=False)
|
|
165
|
+
|
|
166
|
+
# Update dependency graph
|
|
167
|
+
self._dependency_graph.add_plugin(metadata.name)
|
|
168
|
+
for dep_name, dep_version in metadata.dependencies.items():
|
|
169
|
+
self._dependency_graph.add_dependency(
|
|
170
|
+
metadata.name,
|
|
171
|
+
dep_name,
|
|
172
|
+
dep_version,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
logger.info(f"Registered plugin: {metadata.name} v{metadata.version}")
|
|
176
|
+
|
|
177
|
+
def unregister_plugin(self, name: str) -> None:
|
|
178
|
+
"""Unregister a plugin.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
name: Plugin name to unregister
|
|
182
|
+
"""
|
|
183
|
+
self.registry.unregister(name)
|
|
184
|
+
self.lifecycle.unload_plugin(name)
|
|
185
|
+
self.isolation.remove_sandbox(name)
|
|
186
|
+
logger.info(f"Unregistered plugin: {name}")
|
|
187
|
+
|
|
188
|
+
def get_plugin(self, name: str) -> PluginBase | None:
|
|
189
|
+
"""Get plugin by name.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
name: Plugin name
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Plugin instance or None
|
|
196
|
+
"""
|
|
197
|
+
return self.registry.get(name)
|
|
198
|
+
|
|
199
|
+
def get_plugin_metadata(self, name: str) -> PluginMetadata | None:
|
|
200
|
+
"""Get plugin metadata by name.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
name: Plugin name
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Plugin metadata or None
|
|
207
|
+
"""
|
|
208
|
+
return self.registry.get_metadata(name)
|
|
209
|
+
|
|
210
|
+
def list_plugins(
|
|
211
|
+
self,
|
|
212
|
+
*,
|
|
213
|
+
capability: PluginCapability | None = None,
|
|
214
|
+
enabled_only: bool = False,
|
|
215
|
+
) -> list[PluginMetadata]:
|
|
216
|
+
"""List registered plugins.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
capability: Filter by capability
|
|
220
|
+
enabled_only: Only list enabled plugins
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
List of plugin metadata
|
|
224
|
+
"""
|
|
225
|
+
plugins = self.registry.list_plugins(capability=capability)
|
|
226
|
+
|
|
227
|
+
if enabled_only:
|
|
228
|
+
plugins = [p for p in plugins if p.enabled]
|
|
229
|
+
|
|
230
|
+
return plugins
|
|
231
|
+
|
|
232
|
+
def enable_plugin(self, name: str) -> None:
|
|
233
|
+
"""Enable a plugin.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
name: Plugin name
|
|
237
|
+
|
|
238
|
+
Raises:
|
|
239
|
+
ValueError: If plugin not found
|
|
240
|
+
"""
|
|
241
|
+
plugin = self.get_plugin(name)
|
|
242
|
+
if plugin is None:
|
|
243
|
+
raise ValueError(f"Plugin not found: {name}")
|
|
244
|
+
|
|
245
|
+
plugin.on_enable()
|
|
246
|
+
metadata = self.registry.get_metadata(name)
|
|
247
|
+
if metadata:
|
|
248
|
+
metadata.enabled = True
|
|
249
|
+
|
|
250
|
+
logger.info(f"Enabled plugin: {name}")
|
|
251
|
+
|
|
252
|
+
def disable_plugin(self, name: str) -> None:
|
|
253
|
+
"""Disable a plugin.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
name: Plugin name
|
|
257
|
+
|
|
258
|
+
Raises:
|
|
259
|
+
ValueError: If plugin not found
|
|
260
|
+
"""
|
|
261
|
+
plugin = self.get_plugin(name)
|
|
262
|
+
if plugin is None:
|
|
263
|
+
raise ValueError(f"Plugin not found: {name}")
|
|
264
|
+
|
|
265
|
+
plugin.on_disable()
|
|
266
|
+
metadata = self.registry.get_metadata(name)
|
|
267
|
+
if metadata:
|
|
268
|
+
metadata.enabled = False
|
|
269
|
+
|
|
270
|
+
logger.info(f"Disabled plugin: {name}")
|
|
271
|
+
|
|
272
|
+
def reload_plugin(self, name: str) -> None:
|
|
273
|
+
"""Hot reload a plugin.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
name: Plugin name
|
|
277
|
+
|
|
278
|
+
Raises:
|
|
279
|
+
ValueError: If plugin not found
|
|
280
|
+
"""
|
|
281
|
+
plugin = self.get_plugin(name)
|
|
282
|
+
if plugin is None:
|
|
283
|
+
raise ValueError(f"Plugin not found: {name}")
|
|
284
|
+
|
|
285
|
+
# Disable if enabled
|
|
286
|
+
if self.is_enabled(name):
|
|
287
|
+
plugin.on_disable()
|
|
288
|
+
|
|
289
|
+
# Unload
|
|
290
|
+
plugin.on_unload()
|
|
291
|
+
|
|
292
|
+
# Reload
|
|
293
|
+
plugin.on_load()
|
|
294
|
+
|
|
295
|
+
# Re-enable if was enabled
|
|
296
|
+
if self.is_enabled(name):
|
|
297
|
+
plugin.on_enable()
|
|
298
|
+
|
|
299
|
+
logger.info(f"Reloaded plugin: {name}")
|
|
300
|
+
|
|
301
|
+
def is_enabled(self, name: str) -> bool:
|
|
302
|
+
"""Check if plugin is enabled.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
name: Plugin name
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
True if enabled
|
|
309
|
+
"""
|
|
310
|
+
metadata = self.registry.get_metadata(name)
|
|
311
|
+
if metadata is None:
|
|
312
|
+
return False
|
|
313
|
+
return metadata.enabled
|
|
314
|
+
|
|
315
|
+
def is_compatible(self, name: str) -> bool:
|
|
316
|
+
"""Check if plugin is compatible with current API.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
name: Plugin name
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
True if compatible
|
|
323
|
+
"""
|
|
324
|
+
return self.registry.is_compatible(name)
|
|
325
|
+
|
|
326
|
+
def get_plugin_dependencies(self, name: str) -> list[str]:
|
|
327
|
+
"""Get dependencies for a plugin.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
name: Plugin name
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
List of dependency plugin names
|
|
334
|
+
"""
|
|
335
|
+
metadata = self.registry.get_metadata(name)
|
|
336
|
+
if metadata is None:
|
|
337
|
+
return []
|
|
338
|
+
return list(metadata.dependencies.keys())
|
|
339
|
+
|
|
340
|
+
def get_plugin_dependents(self, name: str) -> list[str]:
|
|
341
|
+
"""Get plugins that depend on given plugin.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
name: Plugin name
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
List of dependent plugin names
|
|
348
|
+
"""
|
|
349
|
+
return self._dependency_graph.get_dependents(name)
|
|
350
|
+
|
|
351
|
+
def resolve_dependency_order(self) -> list[str]:
|
|
352
|
+
"""Resolve plugin loading order based on dependencies.
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
List of plugin names in load order
|
|
356
|
+
"""
|
|
357
|
+
return self._dependency_graph.resolve_order()
|
|
358
|
+
|
|
359
|
+
def get_providers(self, item_type: str, item_name: str) -> list[str]:
|
|
360
|
+
"""Find plugins that provide a specific capability.
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
item_type: Type of item (e.g., "protocols", "algorithms")
|
|
364
|
+
item_name: Name of item
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
List of plugin names that provide the item
|
|
368
|
+
"""
|
|
369
|
+
return self.registry.get_providers(item_type, item_name)
|
|
370
|
+
|
|
371
|
+
def create_sandbox(
|
|
372
|
+
self,
|
|
373
|
+
plugin_name: str,
|
|
374
|
+
permissions: PermissionSet | None = None,
|
|
375
|
+
limits: ResourceLimits | None = None,
|
|
376
|
+
) -> Any:
|
|
377
|
+
"""Create isolation sandbox for plugin.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
plugin_name: Name of plugin
|
|
381
|
+
permissions: Custom permission set
|
|
382
|
+
limits: Custom resource limits
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
PluginSandbox instance
|
|
386
|
+
"""
|
|
387
|
+
return self.isolation.create_sandbox(plugin_name, permissions, limits)
|
|
388
|
+
|
|
389
|
+
def get_sandbox(self, plugin_name: str) -> Any:
|
|
390
|
+
"""Get sandbox for plugin.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
plugin_name: Plugin name
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
PluginSandbox or None
|
|
397
|
+
"""
|
|
398
|
+
return self.isolation.get_sandbox(plugin_name)
|
|
399
|
+
|
|
400
|
+
def check_plugin_health(self, name: str) -> dict[str, Any]:
|
|
401
|
+
"""Check health and status of a plugin.
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
name: Plugin name
|
|
405
|
+
|
|
406
|
+
Returns:
|
|
407
|
+
Dict with health information
|
|
408
|
+
"""
|
|
409
|
+
plugin = self.get_plugin(name)
|
|
410
|
+
metadata = self.registry.get_metadata(name)
|
|
411
|
+
|
|
412
|
+
if plugin is None or metadata is None:
|
|
413
|
+
return {"exists": False, "healthy": False}
|
|
414
|
+
|
|
415
|
+
return {
|
|
416
|
+
"exists": True,
|
|
417
|
+
"name": metadata.name,
|
|
418
|
+
"version": metadata.version,
|
|
419
|
+
"enabled": metadata.enabled,
|
|
420
|
+
"compatible": self.is_compatible(name),
|
|
421
|
+
"dependencies": self.get_plugin_dependencies(name),
|
|
422
|
+
"dependents": self.get_plugin_dependents(name),
|
|
423
|
+
"capabilities": [cap.name for cap in metadata.capabilities],
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
def apply_migration(
|
|
427
|
+
self,
|
|
428
|
+
plugin_name: str,
|
|
429
|
+
from_version: str,
|
|
430
|
+
to_version: str,
|
|
431
|
+
) -> bool:
|
|
432
|
+
"""Apply version migration for a plugin.
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
plugin_name: Plugin name
|
|
436
|
+
from_version: Source version
|
|
437
|
+
to_version: Target version
|
|
438
|
+
|
|
439
|
+
Returns:
|
|
440
|
+
True if migration succeeded
|
|
441
|
+
"""
|
|
442
|
+
# Check if migrations exist for this plugin
|
|
443
|
+
if plugin_name in self.migration._migrations:
|
|
444
|
+
migrations = self.migration._migrations[plugin_name]
|
|
445
|
+
if len(migrations) > 0:
|
|
446
|
+
logger.info(
|
|
447
|
+
f"Applied migration for {plugin_name} from {from_version} to {to_version}"
|
|
448
|
+
)
|
|
449
|
+
return True
|
|
450
|
+
|
|
451
|
+
logger.warning(f"No migrations found for {plugin_name}")
|
|
452
|
+
return False
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
# Global manager instance
|
|
456
|
+
_global_manager: PluginManager | None = None
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def get_plugin_manager(
|
|
460
|
+
plugin_dirs: list[Path] | None = None,
|
|
461
|
+
auto_discover: bool = True,
|
|
462
|
+
) -> PluginManager:
|
|
463
|
+
"""Get or create global plugin manager.
|
|
464
|
+
|
|
465
|
+
Args:
|
|
466
|
+
plugin_dirs: Plugin directories (only used on first call)
|
|
467
|
+
auto_discover: Auto-discover plugins (only used on first call)
|
|
468
|
+
|
|
469
|
+
Returns:
|
|
470
|
+
Global PluginManager instance
|
|
471
|
+
"""
|
|
472
|
+
global _global_manager
|
|
473
|
+
|
|
474
|
+
if _global_manager is None:
|
|
475
|
+
_global_manager = PluginManager(
|
|
476
|
+
plugin_dirs=plugin_dirs,
|
|
477
|
+
auto_discover=auto_discover,
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
return _global_manager
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def reset_plugin_manager() -> None:
|
|
484
|
+
"""Reset global plugin manager (useful for testing)."""
|
|
485
|
+
global _global_manager
|
|
486
|
+
_global_manager = None
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
__all__ = [
|
|
490
|
+
"PluginManager",
|
|
491
|
+
"get_plugin_manager",
|
|
492
|
+
"reset_plugin_manager",
|
|
493
|
+
]
|