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,411 @@
|
|
|
1
|
+
"""Plugin discovery and scanning.
|
|
2
|
+
|
|
3
|
+
This module provides plugin discovery from filesystem directories
|
|
4
|
+
and Python entry points.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.plugins.discovery import discover_plugins
|
|
9
|
+
>>> plugins = discover_plugins()
|
|
10
|
+
>>> for plugin in plugins:
|
|
11
|
+
... print(f"Found: {plugin.name} v{plugin.version}")
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import importlib
|
|
17
|
+
import importlib.metadata
|
|
18
|
+
import importlib.util
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import TYPE_CHECKING
|
|
24
|
+
|
|
25
|
+
from oscura.plugins.base import PluginBase, PluginMetadata
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from collections.abc import Iterator
|
|
29
|
+
|
|
30
|
+
# Try to import yaml for plugin.yaml parsing
|
|
31
|
+
try:
|
|
32
|
+
import yaml
|
|
33
|
+
|
|
34
|
+
YAML_AVAILABLE = True
|
|
35
|
+
except ImportError:
|
|
36
|
+
YAML_AVAILABLE = False
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# TraceKit API version for compatibility checking
|
|
40
|
+
TRACEKIT_API_VERSION = "1.0.0"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class DiscoveredPlugin:
|
|
45
|
+
"""Information about a discovered plugin.
|
|
46
|
+
|
|
47
|
+
Attributes:
|
|
48
|
+
metadata: Plugin metadata.
|
|
49
|
+
path: Path to plugin directory or module.
|
|
50
|
+
entry_point: Entry point name (if from entry points).
|
|
51
|
+
compatible: Whether plugin is compatible with current API.
|
|
52
|
+
load_error: Error message if plugin failed to load.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
metadata: PluginMetadata
|
|
56
|
+
path: Path | None = None
|
|
57
|
+
entry_point: str | None = None
|
|
58
|
+
compatible: bool = True
|
|
59
|
+
load_error: str | None = None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_plugin_paths() -> list[Path]:
|
|
63
|
+
"""Get list of plugin search directories.
|
|
64
|
+
|
|
65
|
+
Returns paths in priority order:
|
|
66
|
+
1. Project plugins: ./plugins/
|
|
67
|
+
2. User plugins: ~/.oscura/plugins/
|
|
68
|
+
3. System plugins: /usr/lib/oscura/plugins/
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
List of plugin directory paths.
|
|
72
|
+
"""
|
|
73
|
+
paths: list[Path] = []
|
|
74
|
+
|
|
75
|
+
# Project plugins (current directory)
|
|
76
|
+
project_plugins = Path.cwd() / "plugins"
|
|
77
|
+
if project_plugins.exists():
|
|
78
|
+
paths.append(project_plugins)
|
|
79
|
+
|
|
80
|
+
# User plugins
|
|
81
|
+
user_plugins = Path.home() / ".oscura" / "plugins"
|
|
82
|
+
paths.append(user_plugins) # Include even if doesn't exist
|
|
83
|
+
|
|
84
|
+
# XDG config home
|
|
85
|
+
xdg_config = os.environ.get("XDG_CONFIG_HOME")
|
|
86
|
+
if xdg_config:
|
|
87
|
+
xdg_plugins = Path(xdg_config) / "oscura" / "plugins"
|
|
88
|
+
paths.append(xdg_plugins)
|
|
89
|
+
|
|
90
|
+
# System plugins (Linux)
|
|
91
|
+
system_plugins = Path("/usr/lib/oscura/plugins")
|
|
92
|
+
if system_plugins.exists():
|
|
93
|
+
paths.append(system_plugins)
|
|
94
|
+
|
|
95
|
+
# Local lib (Linux)
|
|
96
|
+
local_plugins = Path("/usr/local/lib/oscura/plugins")
|
|
97
|
+
if local_plugins.exists():
|
|
98
|
+
paths.append(local_plugins)
|
|
99
|
+
|
|
100
|
+
return paths
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def discover_plugins(
|
|
104
|
+
*,
|
|
105
|
+
compatible_only: bool = False,
|
|
106
|
+
include_disabled: bool = False,
|
|
107
|
+
) -> list[DiscoveredPlugin]:
|
|
108
|
+
"""Discover all available plugins.
|
|
109
|
+
|
|
110
|
+
Scans plugin directories and Python entry points for plugins.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
compatible_only: If True, only return compatible plugins.
|
|
114
|
+
include_disabled: If True, include disabled plugins.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
List of discovered plugins.
|
|
118
|
+
|
|
119
|
+
Example:
|
|
120
|
+
>>> plugins = discover_plugins()
|
|
121
|
+
>>> print(f"Found {len(plugins)} plugins")
|
|
122
|
+
"""
|
|
123
|
+
plugins: list[DiscoveredPlugin] = []
|
|
124
|
+
seen_names: set[str] = set()
|
|
125
|
+
|
|
126
|
+
# Scan plugin directories
|
|
127
|
+
for plugin_dir in get_plugin_paths():
|
|
128
|
+
if plugin_dir.exists() and plugin_dir.is_dir():
|
|
129
|
+
for plugin in scan_directory(plugin_dir):
|
|
130
|
+
if plugin.metadata.name not in seen_names:
|
|
131
|
+
plugins.append(plugin)
|
|
132
|
+
seen_names.add(plugin.metadata.name)
|
|
133
|
+
|
|
134
|
+
# Scan entry points
|
|
135
|
+
for plugin in scan_entry_points():
|
|
136
|
+
if plugin.metadata.name not in seen_names:
|
|
137
|
+
plugins.append(plugin)
|
|
138
|
+
seen_names.add(plugin.metadata.name)
|
|
139
|
+
|
|
140
|
+
# Filter by compatibility
|
|
141
|
+
if compatible_only:
|
|
142
|
+
plugins = [p for p in plugins if p.compatible]
|
|
143
|
+
|
|
144
|
+
# Filter disabled
|
|
145
|
+
if not include_disabled:
|
|
146
|
+
plugins = [p for p in plugins if p.metadata.enabled]
|
|
147
|
+
|
|
148
|
+
return plugins
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def scan_directory(directory: Path) -> Iterator[DiscoveredPlugin]:
|
|
152
|
+
"""Scan a directory for plugins.
|
|
153
|
+
|
|
154
|
+
Each subdirectory with a plugin.yaml or Python package
|
|
155
|
+
is considered a potential plugin.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
directory: Directory to scan.
|
|
159
|
+
|
|
160
|
+
Yields:
|
|
161
|
+
DiscoveredPlugin for each found plugin.
|
|
162
|
+
"""
|
|
163
|
+
if not directory.exists():
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
for item in directory.iterdir():
|
|
167
|
+
if item.is_dir():
|
|
168
|
+
# Check for plugin.yaml
|
|
169
|
+
plugin_yaml = item / "plugin.yaml"
|
|
170
|
+
plugin_yml = item / "plugin.yml"
|
|
171
|
+
|
|
172
|
+
if plugin_yaml.exists():
|
|
173
|
+
plugin = _load_plugin_from_yaml(plugin_yaml)
|
|
174
|
+
if plugin:
|
|
175
|
+
yield plugin
|
|
176
|
+
elif plugin_yml.exists():
|
|
177
|
+
plugin = _load_plugin_from_yaml(plugin_yml)
|
|
178
|
+
if plugin:
|
|
179
|
+
yield plugin
|
|
180
|
+
|
|
181
|
+
# Check for Python package with __init__.py
|
|
182
|
+
init_py = item / "__init__.py"
|
|
183
|
+
if init_py.exists():
|
|
184
|
+
plugin = _load_plugin_from_module(item)
|
|
185
|
+
if plugin:
|
|
186
|
+
yield plugin
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def scan_entry_points() -> Iterator[DiscoveredPlugin]:
|
|
190
|
+
"""Scan Python entry points for plugins.
|
|
191
|
+
|
|
192
|
+
Looks for entry points in the "oscura.plugins" group.
|
|
193
|
+
|
|
194
|
+
Yields:
|
|
195
|
+
DiscoveredPlugin for each found entry point.
|
|
196
|
+
"""
|
|
197
|
+
try:
|
|
198
|
+
# Python 3.10+ has entry_points with group filtering
|
|
199
|
+
if hasattr(importlib.metadata, "entry_points"):
|
|
200
|
+
eps = importlib.metadata.entry_points()
|
|
201
|
+
|
|
202
|
+
# Handle different API versions
|
|
203
|
+
if hasattr(eps, "select"):
|
|
204
|
+
# Python 3.10+
|
|
205
|
+
plugins_eps = eps.select(group="oscura.plugins")
|
|
206
|
+
elif hasattr(eps, "get"):
|
|
207
|
+
# Python 3.9
|
|
208
|
+
plugins_eps = eps.get("oscura.plugins", [])
|
|
209
|
+
else:
|
|
210
|
+
# Python 3.8 style (dict)
|
|
211
|
+
plugins_eps = eps.get("oscura.plugins", []) # type: ignore[attr-defined]
|
|
212
|
+
|
|
213
|
+
for ep in plugins_eps:
|
|
214
|
+
try:
|
|
215
|
+
plugin_class = ep.load()
|
|
216
|
+
|
|
217
|
+
if isinstance(plugin_class, type) and issubclass(plugin_class, PluginBase):
|
|
218
|
+
instance = plugin_class()
|
|
219
|
+
metadata = instance.metadata
|
|
220
|
+
|
|
221
|
+
compatible = metadata.is_compatible_with(TRACEKIT_API_VERSION)
|
|
222
|
+
|
|
223
|
+
yield DiscoveredPlugin(
|
|
224
|
+
metadata=metadata,
|
|
225
|
+
entry_point=ep.name,
|
|
226
|
+
compatible=compatible,
|
|
227
|
+
)
|
|
228
|
+
except Exception as e:
|
|
229
|
+
# Create placeholder for failed load
|
|
230
|
+
yield DiscoveredPlugin(
|
|
231
|
+
metadata=PluginMetadata(
|
|
232
|
+
name=ep.name,
|
|
233
|
+
version="0.0.0",
|
|
234
|
+
description="Failed to load",
|
|
235
|
+
),
|
|
236
|
+
entry_point=ep.name,
|
|
237
|
+
compatible=False,
|
|
238
|
+
load_error=str(e),
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
except Exception:
|
|
242
|
+
# Entry points not available or error
|
|
243
|
+
pass
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _load_plugin_from_yaml(yaml_path: Path) -> DiscoveredPlugin | None:
|
|
247
|
+
"""Load plugin metadata from YAML file.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
yaml_path: Path to plugin.yaml file.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
DiscoveredPlugin or None if load fails.
|
|
254
|
+
"""
|
|
255
|
+
if not YAML_AVAILABLE:
|
|
256
|
+
return None
|
|
257
|
+
|
|
258
|
+
try:
|
|
259
|
+
with open(yaml_path, encoding="utf-8") as f:
|
|
260
|
+
data = yaml.safe_load(f)
|
|
261
|
+
|
|
262
|
+
if not isinstance(data, dict):
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
# Extract metadata
|
|
266
|
+
metadata = PluginMetadata(
|
|
267
|
+
name=data.get("name", yaml_path.parent.name),
|
|
268
|
+
version=data.get("version", "0.0.0"),
|
|
269
|
+
api_version=data.get("api_version", "1.0.0"),
|
|
270
|
+
author=data.get("author", ""),
|
|
271
|
+
description=data.get("description", ""),
|
|
272
|
+
homepage=data.get("homepage", ""),
|
|
273
|
+
license=data.get("license", ""),
|
|
274
|
+
path=yaml_path.parent,
|
|
275
|
+
enabled=data.get("enabled", True),
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
# Parse dependencies
|
|
279
|
+
if "dependencies" in data:
|
|
280
|
+
deps = data["dependencies"]
|
|
281
|
+
if isinstance(deps, list):
|
|
282
|
+
for dep in deps:
|
|
283
|
+
if isinstance(dep, dict):
|
|
284
|
+
if "plugin" in dep:
|
|
285
|
+
metadata.dependencies[dep["plugin"]] = dep.get("version", "*")
|
|
286
|
+
elif "package" in dep:
|
|
287
|
+
metadata.dependencies[dep["package"]] = dep.get("version", "*")
|
|
288
|
+
|
|
289
|
+
# Parse provides
|
|
290
|
+
if "provides" in data:
|
|
291
|
+
provides = data["provides"]
|
|
292
|
+
if isinstance(provides, list):
|
|
293
|
+
for item in provides:
|
|
294
|
+
if isinstance(item, dict):
|
|
295
|
+
for key, value in item.items():
|
|
296
|
+
if key not in metadata.provides:
|
|
297
|
+
metadata.provides[key] = []
|
|
298
|
+
metadata.provides[key].append(value)
|
|
299
|
+
|
|
300
|
+
compatible = metadata.is_compatible_with(TRACEKIT_API_VERSION)
|
|
301
|
+
|
|
302
|
+
return DiscoveredPlugin(
|
|
303
|
+
metadata=metadata,
|
|
304
|
+
path=yaml_path.parent,
|
|
305
|
+
compatible=compatible,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
except Exception as e:
|
|
309
|
+
return DiscoveredPlugin(
|
|
310
|
+
metadata=PluginMetadata(
|
|
311
|
+
name=yaml_path.parent.name,
|
|
312
|
+
version="0.0.0",
|
|
313
|
+
path=yaml_path.parent,
|
|
314
|
+
),
|
|
315
|
+
path=yaml_path.parent,
|
|
316
|
+
compatible=False,
|
|
317
|
+
load_error=str(e),
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _load_plugin_from_module(module_path: Path) -> DiscoveredPlugin | None:
|
|
322
|
+
"""Load plugin from Python module.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
module_path: Path to Python package directory.
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
DiscoveredPlugin or None if load fails.
|
|
329
|
+
"""
|
|
330
|
+
try:
|
|
331
|
+
# Add parent to path temporarily
|
|
332
|
+
parent = str(module_path.parent)
|
|
333
|
+
if parent not in sys.path:
|
|
334
|
+
sys.path.insert(0, parent)
|
|
335
|
+
added_path = True
|
|
336
|
+
else:
|
|
337
|
+
added_path = False
|
|
338
|
+
|
|
339
|
+
try:
|
|
340
|
+
# Import the module
|
|
341
|
+
module_name = module_path.name
|
|
342
|
+
spec = importlib.util.spec_from_file_location(module_name, module_path / "__init__.py")
|
|
343
|
+
|
|
344
|
+
if spec is None or spec.loader is None:
|
|
345
|
+
return None
|
|
346
|
+
|
|
347
|
+
module = importlib.util.module_from_spec(spec)
|
|
348
|
+
spec.loader.exec_module(module)
|
|
349
|
+
|
|
350
|
+
# Look for Plugin class
|
|
351
|
+
plugin_class = None
|
|
352
|
+
|
|
353
|
+
# Check for explicit Plugin class
|
|
354
|
+
if hasattr(module, "Plugin"):
|
|
355
|
+
plugin_class = module.Plugin
|
|
356
|
+
elif hasattr(module, "plugin"):
|
|
357
|
+
plugin_class = module.plugin
|
|
358
|
+
|
|
359
|
+
# Check for any PluginBase subclass
|
|
360
|
+
if plugin_class is None:
|
|
361
|
+
for attr_name in dir(module):
|
|
362
|
+
attr = getattr(module, attr_name)
|
|
363
|
+
if (
|
|
364
|
+
isinstance(attr, type)
|
|
365
|
+
and issubclass(attr, PluginBase)
|
|
366
|
+
and attr is not PluginBase
|
|
367
|
+
):
|
|
368
|
+
plugin_class = attr
|
|
369
|
+
break
|
|
370
|
+
|
|
371
|
+
if plugin_class is None:
|
|
372
|
+
return None
|
|
373
|
+
|
|
374
|
+
# Create instance and get metadata
|
|
375
|
+
instance = plugin_class()
|
|
376
|
+
metadata = instance.metadata
|
|
377
|
+
metadata.path = module_path
|
|
378
|
+
|
|
379
|
+
compatible = metadata.is_compatible_with(TRACEKIT_API_VERSION)
|
|
380
|
+
|
|
381
|
+
return DiscoveredPlugin(
|
|
382
|
+
metadata=metadata,
|
|
383
|
+
path=module_path,
|
|
384
|
+
compatible=compatible,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
finally:
|
|
388
|
+
if added_path:
|
|
389
|
+
sys.path.remove(parent)
|
|
390
|
+
|
|
391
|
+
except Exception as e:
|
|
392
|
+
return DiscoveredPlugin(
|
|
393
|
+
metadata=PluginMetadata(
|
|
394
|
+
name=module_path.name,
|
|
395
|
+
version="0.0.0",
|
|
396
|
+
path=module_path,
|
|
397
|
+
),
|
|
398
|
+
path=module_path,
|
|
399
|
+
compatible=False,
|
|
400
|
+
load_error=str(e),
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
__all__ = [
|
|
405
|
+
"TRACEKIT_API_VERSION",
|
|
406
|
+
"DiscoveredPlugin",
|
|
407
|
+
"discover_plugins",
|
|
408
|
+
"get_plugin_paths",
|
|
409
|
+
"scan_directory",
|
|
410
|
+
"scan_entry_points",
|
|
411
|
+
]
|