oscura 0.5.0__py3-none-any.whl → 0.6.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 +169 -167
- oscura/analyzers/__init__.py +3 -0
- oscura/analyzers/classification.py +659 -0
- oscura/analyzers/digital/__init__.py +0 -48
- oscura/analyzers/digital/edges.py +325 -65
- oscura/analyzers/digital/extraction.py +0 -195
- oscura/analyzers/digital/quality.py +293 -166
- oscura/analyzers/digital/timing.py +260 -115
- oscura/analyzers/digital/timing_numba.py +334 -0
- oscura/analyzers/entropy.py +605 -0
- oscura/analyzers/eye/diagram.py +176 -109
- oscura/analyzers/eye/metrics.py +5 -5
- oscura/analyzers/jitter/__init__.py +6 -4
- oscura/analyzers/jitter/ber.py +52 -52
- oscura/analyzers/jitter/classification.py +156 -0
- oscura/analyzers/jitter/decomposition.py +163 -113
- oscura/analyzers/jitter/spectrum.py +80 -64
- oscura/analyzers/ml/__init__.py +39 -0
- oscura/analyzers/ml/features.py +600 -0
- oscura/analyzers/ml/signal_classifier.py +604 -0
- oscura/analyzers/packet/daq.py +246 -158
- oscura/analyzers/packet/parser.py +12 -1
- oscura/analyzers/packet/payload.py +50 -2110
- oscura/analyzers/packet/payload_analysis.py +361 -181
- oscura/analyzers/packet/payload_patterns.py +133 -70
- oscura/analyzers/packet/stream.py +84 -23
- oscura/analyzers/patterns/__init__.py +26 -5
- oscura/analyzers/patterns/anomaly_detection.py +908 -0
- oscura/analyzers/patterns/clustering.py +169 -108
- oscura/analyzers/patterns/clustering_optimized.py +227 -0
- oscura/analyzers/patterns/discovery.py +1 -1
- oscura/analyzers/patterns/matching.py +581 -197
- oscura/analyzers/patterns/pattern_mining.py +778 -0
- oscura/analyzers/patterns/periodic.py +121 -38
- oscura/analyzers/patterns/sequences.py +175 -78
- oscura/analyzers/power/conduction.py +1 -1
- oscura/analyzers/power/soa.py +6 -6
- oscura/analyzers/power/switching.py +250 -110
- oscura/analyzers/protocol/__init__.py +17 -1
- oscura/analyzers/protocols/__init__.py +1 -22
- oscura/analyzers/protocols/base.py +6 -6
- oscura/analyzers/protocols/ble/__init__.py +38 -0
- oscura/analyzers/protocols/ble/analyzer.py +809 -0
- oscura/analyzers/protocols/ble/uuids.py +288 -0
- oscura/analyzers/protocols/can.py +257 -127
- oscura/analyzers/protocols/can_fd.py +107 -80
- oscura/analyzers/protocols/flexray.py +139 -80
- oscura/analyzers/protocols/hdlc.py +93 -58
- oscura/analyzers/protocols/i2c.py +247 -106
- oscura/analyzers/protocols/i2s.py +138 -86
- oscura/analyzers/protocols/industrial/__init__.py +40 -0
- oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
- oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
- oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
- oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
- oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
- oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
- oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
- oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
- oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
- oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
- oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
- oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
- oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
- oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
- oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
- oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
- oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
- oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
- oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
- oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
- oscura/analyzers/protocols/jtag.py +180 -98
- oscura/analyzers/protocols/lin.py +219 -114
- oscura/analyzers/protocols/manchester.py +4 -4
- oscura/analyzers/protocols/onewire.py +253 -149
- oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
- oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
- oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
- oscura/analyzers/protocols/spi.py +192 -95
- oscura/analyzers/protocols/swd.py +321 -167
- oscura/analyzers/protocols/uart.py +267 -125
- oscura/analyzers/protocols/usb.py +235 -131
- oscura/analyzers/side_channel/power.py +17 -12
- oscura/analyzers/signal/__init__.py +15 -0
- oscura/analyzers/signal/timing_analysis.py +1086 -0
- oscura/analyzers/signal_integrity/__init__.py +4 -1
- oscura/analyzers/signal_integrity/sparams.py +2 -19
- oscura/analyzers/spectral/chunked.py +129 -60
- oscura/analyzers/spectral/chunked_fft.py +300 -94
- oscura/analyzers/spectral/chunked_wavelet.py +100 -80
- oscura/analyzers/statistical/checksum.py +376 -217
- oscura/analyzers/statistical/classification.py +229 -107
- oscura/analyzers/statistical/entropy.py +78 -53
- oscura/analyzers/statistics/correlation.py +407 -211
- oscura/analyzers/statistics/outliers.py +2 -2
- oscura/analyzers/statistics/streaming.py +30 -5
- oscura/analyzers/validation.py +216 -101
- oscura/analyzers/waveform/measurements.py +9 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
- oscura/analyzers/waveform/spectral.py +500 -228
- oscura/api/__init__.py +31 -5
- oscura/api/dsl/__init__.py +582 -0
- oscura/{dsl → api/dsl}/commands.py +43 -76
- oscura/{dsl → api/dsl}/interpreter.py +26 -51
- oscura/{dsl → api/dsl}/parser.py +107 -77
- oscura/{dsl → api/dsl}/repl.py +2 -2
- oscura/api/dsl.py +1 -1
- oscura/{integrations → api/integrations}/__init__.py +1 -1
- oscura/{integrations → api/integrations}/llm.py +201 -102
- oscura/api/operators.py +3 -3
- oscura/api/optimization.py +144 -30
- oscura/api/rest_server.py +921 -0
- oscura/api/server/__init__.py +17 -0
- oscura/api/server/dashboard.py +850 -0
- oscura/api/server/static/README.md +34 -0
- oscura/api/server/templates/base.html +181 -0
- oscura/api/server/templates/export.html +120 -0
- oscura/api/server/templates/home.html +284 -0
- oscura/api/server/templates/protocols.html +58 -0
- oscura/api/server/templates/reports.html +43 -0
- oscura/api/server/templates/session_detail.html +89 -0
- oscura/api/server/templates/sessions.html +83 -0
- oscura/api/server/templates/waveforms.html +73 -0
- oscura/automotive/__init__.py +8 -1
- oscura/automotive/can/__init__.py +10 -0
- oscura/automotive/can/checksum.py +3 -1
- oscura/automotive/can/dbc_generator.py +590 -0
- oscura/automotive/can/message_wrapper.py +121 -74
- oscura/automotive/can/patterns.py +98 -21
- oscura/automotive/can/session.py +292 -56
- oscura/automotive/can/state_machine.py +6 -3
- oscura/automotive/can/stimulus_response.py +97 -75
- oscura/automotive/dbc/__init__.py +10 -2
- oscura/automotive/dbc/generator.py +84 -56
- oscura/automotive/dbc/parser.py +6 -6
- oscura/automotive/dtc/data.json +2763 -0
- oscura/automotive/dtc/database.py +2 -2
- oscura/automotive/flexray/__init__.py +31 -0
- oscura/automotive/flexray/analyzer.py +504 -0
- oscura/automotive/flexray/crc.py +185 -0
- oscura/automotive/flexray/fibex.py +449 -0
- oscura/automotive/j1939/__init__.py +45 -8
- oscura/automotive/j1939/analyzer.py +605 -0
- oscura/automotive/j1939/spns.py +326 -0
- oscura/automotive/j1939/transport.py +306 -0
- oscura/automotive/lin/__init__.py +47 -0
- oscura/automotive/lin/analyzer.py +612 -0
- oscura/automotive/loaders/blf.py +13 -2
- oscura/automotive/loaders/csv_can.py +143 -72
- oscura/automotive/loaders/dispatcher.py +50 -2
- oscura/automotive/loaders/mdf.py +86 -45
- oscura/automotive/loaders/pcap.py +111 -61
- oscura/automotive/uds/__init__.py +4 -0
- oscura/automotive/uds/analyzer.py +725 -0
- oscura/automotive/uds/decoder.py +140 -58
- oscura/automotive/uds/models.py +7 -1
- oscura/automotive/visualization.py +1 -1
- oscura/cli/analyze.py +348 -0
- oscura/cli/batch.py +142 -122
- oscura/cli/benchmark.py +275 -0
- oscura/cli/characterize.py +137 -82
- oscura/cli/compare.py +224 -131
- oscura/cli/completion.py +250 -0
- oscura/cli/config_cmd.py +361 -0
- oscura/cli/decode.py +164 -87
- oscura/cli/export.py +286 -0
- oscura/cli/main.py +115 -31
- oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
- oscura/{onboarding → cli/onboarding}/help.py +80 -58
- oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
- oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
- oscura/cli/progress.py +147 -0
- oscura/cli/shell.py +157 -135
- oscura/cli/validate_cmd.py +204 -0
- oscura/cli/visualize.py +158 -0
- oscura/convenience.py +125 -79
- oscura/core/__init__.py +4 -2
- oscura/core/backend_selector.py +3 -3
- oscura/core/cache.py +126 -15
- oscura/core/cancellation.py +1 -1
- oscura/{config → core/config}/__init__.py +20 -11
- oscura/{config → core/config}/defaults.py +1 -1
- oscura/{config → core/config}/loader.py +7 -5
- oscura/{config → core/config}/memory.py +5 -5
- oscura/{config → core/config}/migration.py +1 -1
- oscura/{config → core/config}/pipeline.py +99 -23
- oscura/{config → core/config}/preferences.py +1 -1
- oscura/{config → core/config}/protocol.py +3 -3
- oscura/{config → core/config}/schema.py +426 -272
- oscura/{config → core/config}/settings.py +1 -1
- oscura/{config → core/config}/thresholds.py +195 -153
- oscura/core/correlation.py +5 -6
- oscura/core/cross_domain.py +0 -2
- oscura/core/debug.py +9 -5
- oscura/{extensibility → core/extensibility}/docs.py +158 -70
- oscura/{extensibility → core/extensibility}/extensions.py +160 -76
- oscura/{extensibility → core/extensibility}/logging.py +1 -1
- oscura/{extensibility → core/extensibility}/measurements.py +1 -1
- oscura/{extensibility → core/extensibility}/plugins.py +1 -1
- oscura/{extensibility → core/extensibility}/templates.py +73 -3
- oscura/{extensibility → core/extensibility}/validation.py +1 -1
- oscura/core/gpu_backend.py +11 -7
- oscura/core/log_query.py +101 -11
- oscura/core/logging.py +126 -54
- oscura/core/logging_advanced.py +5 -5
- oscura/core/memory_limits.py +108 -70
- oscura/core/memory_monitor.py +2 -2
- oscura/core/memory_progress.py +7 -7
- oscura/core/memory_warnings.py +1 -1
- oscura/core/numba_backend.py +13 -13
- oscura/{plugins → core/plugins}/__init__.py +9 -9
- oscura/{plugins → core/plugins}/base.py +7 -7
- oscura/{plugins → core/plugins}/cli.py +3 -3
- oscura/{plugins → core/plugins}/discovery.py +186 -106
- oscura/{plugins → core/plugins}/lifecycle.py +1 -1
- oscura/{plugins → core/plugins}/manager.py +7 -7
- oscura/{plugins → core/plugins}/registry.py +3 -3
- oscura/{plugins → core/plugins}/versioning.py +1 -1
- oscura/core/progress.py +16 -1
- oscura/core/provenance.py +8 -2
- oscura/{schemas → core/schemas}/__init__.py +2 -2
- oscura/core/schemas/bus_configuration.json +322 -0
- oscura/core/schemas/device_mapping.json +182 -0
- oscura/core/schemas/packet_format.json +418 -0
- oscura/core/schemas/protocol_definition.json +363 -0
- oscura/core/types.py +4 -0
- oscura/core/uncertainty.py +3 -3
- oscura/correlation/__init__.py +52 -0
- oscura/correlation/multi_protocol.py +811 -0
- oscura/discovery/auto_decoder.py +117 -35
- oscura/discovery/comparison.py +191 -86
- oscura/discovery/quality_validator.py +155 -68
- oscura/discovery/signal_detector.py +196 -79
- oscura/export/__init__.py +18 -20
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- oscura/export/wireshark/README.md +15 -15
- oscura/export/wireshark/generator.py +1 -1
- oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
- oscura/export/wireshark_dissector.py +746 -0
- oscura/guidance/wizard.py +207 -111
- oscura/hardware/__init__.py +19 -0
- oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
- oscura/{acquisition → hardware/acquisition}/file.py +2 -2
- oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
- oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
- oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
- oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
- oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
- oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
- oscura/hardware/firmware/__init__.py +29 -0
- oscura/hardware/firmware/pattern_recognition.py +874 -0
- oscura/hardware/hal_detector.py +736 -0
- oscura/hardware/security/__init__.py +37 -0
- oscura/hardware/security/side_channel_detector.py +1126 -0
- oscura/inference/__init__.py +4 -0
- oscura/inference/active_learning/README.md +7 -7
- oscura/inference/active_learning/observation_table.py +4 -1
- oscura/inference/alignment.py +216 -123
- oscura/inference/bayesian.py +113 -33
- oscura/inference/crc_reverse.py +101 -55
- oscura/inference/logic.py +6 -2
- oscura/inference/message_format.py +342 -183
- oscura/inference/protocol.py +95 -44
- oscura/inference/protocol_dsl.py +180 -82
- oscura/inference/signal_intelligence.py +1439 -706
- oscura/inference/spectral.py +99 -57
- oscura/inference/state_machine.py +810 -158
- oscura/inference/stream.py +270 -110
- oscura/iot/__init__.py +34 -0
- oscura/iot/coap/__init__.py +32 -0
- oscura/iot/coap/analyzer.py +668 -0
- oscura/iot/coap/options.py +212 -0
- oscura/iot/lorawan/__init__.py +21 -0
- oscura/iot/lorawan/crypto.py +206 -0
- oscura/iot/lorawan/decoder.py +801 -0
- oscura/iot/lorawan/mac_commands.py +341 -0
- oscura/iot/mqtt/__init__.py +27 -0
- oscura/iot/mqtt/analyzer.py +999 -0
- oscura/iot/mqtt/properties.py +315 -0
- oscura/iot/zigbee/__init__.py +31 -0
- oscura/iot/zigbee/analyzer.py +615 -0
- oscura/iot/zigbee/security.py +153 -0
- oscura/iot/zigbee/zcl.py +349 -0
- oscura/jupyter/display.py +125 -45
- oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
- oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
- oscura/jupyter/exploratory/fuzzy.py +746 -0
- oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
- oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
- oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
- oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
- oscura/jupyter/exploratory/sync.py +612 -0
- oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
- oscura/jupyter/magic.py +4 -4
- oscura/{ui → jupyter/ui}/__init__.py +2 -2
- oscura/{ui → jupyter/ui}/formatters.py +3 -3
- oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
- oscura/loaders/__init__.py +171 -63
- oscura/loaders/binary.py +88 -1
- oscura/loaders/chipwhisperer.py +153 -137
- oscura/loaders/configurable.py +208 -86
- oscura/loaders/csv_loader.py +458 -215
- oscura/loaders/hdf5_loader.py +278 -119
- oscura/loaders/lazy.py +87 -54
- oscura/loaders/mmap_loader.py +1 -1
- oscura/loaders/numpy_loader.py +253 -116
- oscura/loaders/pcap.py +226 -151
- oscura/loaders/rigol.py +110 -49
- oscura/loaders/sigrok.py +201 -78
- oscura/loaders/tdms.py +81 -58
- oscura/loaders/tektronix.py +291 -174
- oscura/loaders/touchstone.py +182 -87
- oscura/loaders/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -7
- oscura/reporting/analyze.py +352 -146
- oscura/reporting/argument_preparer.py +69 -14
- oscura/reporting/auto_report.py +97 -61
- oscura/reporting/batch.py +131 -58
- oscura/reporting/chart_selection.py +57 -45
- oscura/reporting/comparison.py +63 -17
- oscura/reporting/content/executive.py +76 -24
- oscura/reporting/core_formats/multi_format.py +11 -8
- oscura/reporting/engine.py +312 -158
- oscura/reporting/enhanced_reports.py +949 -0
- oscura/reporting/export.py +86 -43
- oscura/reporting/formatting/numbers.py +69 -42
- oscura/reporting/html.py +139 -58
- oscura/reporting/index.py +137 -65
- oscura/reporting/output.py +158 -67
- oscura/reporting/pdf.py +67 -102
- oscura/reporting/plots.py +191 -112
- oscura/reporting/sections.py +88 -47
- oscura/reporting/standards.py +104 -61
- oscura/reporting/summary_generator.py +75 -55
- oscura/reporting/tables.py +138 -54
- oscura/reporting/templates/enhanced/protocol_re.html +525 -0
- oscura/reporting/templates/index.md +13 -13
- oscura/sessions/__init__.py +14 -23
- oscura/sessions/base.py +3 -3
- oscura/sessions/blackbox.py +106 -10
- oscura/sessions/generic.py +2 -2
- oscura/sessions/legacy.py +783 -0
- oscura/side_channel/__init__.py +63 -0
- oscura/side_channel/dpa.py +1025 -0
- oscura/utils/__init__.py +15 -1
- oscura/utils/autodetect.py +1 -5
- oscura/utils/bitwise.py +118 -0
- oscura/{builders → utils/builders}/__init__.py +1 -1
- oscura/{comparison → utils/comparison}/__init__.py +6 -6
- oscura/{comparison → utils/comparison}/compare.py +202 -101
- oscura/{comparison → utils/comparison}/golden.py +83 -63
- oscura/{comparison → utils/comparison}/limits.py +313 -89
- oscura/{comparison → utils/comparison}/mask.py +151 -45
- oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
- oscura/{comparison → utils/comparison}/visualization.py +147 -89
- oscura/{component → utils/component}/__init__.py +3 -3
- oscura/{component → utils/component}/impedance.py +122 -58
- oscura/{component → utils/component}/reactive.py +165 -168
- oscura/{component → utils/component}/transmission_line.py +3 -3
- oscura/{filtering → utils/filtering}/__init__.py +6 -6
- oscura/{filtering → utils/filtering}/base.py +1 -1
- oscura/{filtering → utils/filtering}/convenience.py +2 -2
- oscura/{filtering → utils/filtering}/design.py +169 -93
- oscura/{filtering → utils/filtering}/filters.py +2 -2
- oscura/{filtering → utils/filtering}/introspection.py +2 -2
- oscura/utils/geometry.py +31 -0
- oscura/utils/imports.py +184 -0
- oscura/utils/lazy.py +1 -1
- oscura/{math → utils/math}/__init__.py +2 -2
- oscura/{math → utils/math}/arithmetic.py +114 -48
- oscura/{math → utils/math}/interpolation.py +139 -106
- oscura/utils/memory.py +129 -66
- oscura/utils/memory_advanced.py +92 -9
- oscura/utils/memory_extensions.py +10 -8
- oscura/{optimization → utils/optimization}/__init__.py +1 -1
- oscura/{optimization → utils/optimization}/search.py +2 -2
- oscura/utils/performance/__init__.py +58 -0
- oscura/utils/performance/caching.py +889 -0
- oscura/utils/performance/lsh_clustering.py +333 -0
- oscura/utils/performance/memory_optimizer.py +699 -0
- oscura/utils/performance/optimizations.py +675 -0
- oscura/utils/performance/parallel.py +654 -0
- oscura/utils/performance/profiling.py +661 -0
- oscura/{pipeline → utils/pipeline}/base.py +1 -1
- oscura/{pipeline → utils/pipeline}/composition.py +11 -3
- oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
- oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
- oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
- oscura/{search → utils/search}/__init__.py +3 -3
- oscura/{search → utils/search}/anomaly.py +188 -58
- oscura/utils/search/context.py +294 -0
- oscura/{search → utils/search}/pattern.py +138 -10
- oscura/utils/serial.py +51 -0
- oscura/utils/storage/__init__.py +61 -0
- oscura/utils/storage/database.py +1166 -0
- oscura/{streaming → utils/streaming}/chunked.py +302 -143
- oscura/{streaming → utils/streaming}/progressive.py +1 -1
- oscura/{streaming → utils/streaming}/realtime.py +3 -2
- oscura/{triggering → utils/triggering}/__init__.py +6 -6
- oscura/{triggering → utils/triggering}/base.py +6 -6
- oscura/{triggering → utils/triggering}/edge.py +2 -2
- oscura/{triggering → utils/triggering}/pattern.py +2 -2
- oscura/{triggering → utils/triggering}/pulse.py +115 -74
- oscura/{triggering → utils/triggering}/window.py +2 -2
- oscura/utils/validation.py +32 -0
- oscura/validation/__init__.py +121 -0
- oscura/{compliance → validation/compliance}/__init__.py +5 -5
- oscura/{compliance → validation/compliance}/advanced.py +5 -5
- oscura/{compliance → validation/compliance}/masks.py +1 -1
- oscura/{compliance → validation/compliance}/reporting.py +127 -53
- oscura/{compliance → validation/compliance}/testing.py +114 -52
- oscura/validation/compliance_tests.py +915 -0
- oscura/validation/fuzzer.py +990 -0
- oscura/validation/grammar_tests.py +596 -0
- oscura/validation/grammar_validator.py +904 -0
- oscura/validation/hil_testing.py +977 -0
- oscura/{quality → validation/quality}/__init__.py +4 -4
- oscura/{quality → validation/quality}/ensemble.py +251 -171
- oscura/{quality → validation/quality}/explainer.py +3 -3
- oscura/{quality → validation/quality}/scoring.py +1 -1
- oscura/{quality → validation/quality}/warnings.py +4 -4
- oscura/validation/regression_suite.py +808 -0
- oscura/validation/replay.py +788 -0
- oscura/{testing → validation/testing}/__init__.py +2 -2
- oscura/{testing → validation/testing}/synthetic.py +5 -5
- oscura/visualization/__init__.py +9 -0
- oscura/visualization/accessibility.py +1 -1
- oscura/visualization/annotations.py +64 -67
- oscura/visualization/colors.py +7 -7
- oscura/visualization/digital.py +180 -81
- oscura/visualization/eye.py +236 -85
- oscura/visualization/interactive.py +320 -143
- oscura/visualization/jitter.py +587 -247
- oscura/visualization/layout.py +169 -134
- oscura/visualization/optimization.py +103 -52
- oscura/visualization/palettes.py +1 -1
- oscura/visualization/power.py +427 -211
- oscura/visualization/power_extended.py +626 -297
- oscura/visualization/presets.py +2 -0
- oscura/visualization/protocols.py +495 -181
- oscura/visualization/render.py +79 -63
- oscura/visualization/reverse_engineering.py +171 -124
- oscura/visualization/signal_integrity.py +460 -279
- oscura/visualization/specialized.py +190 -100
- oscura/visualization/spectral.py +670 -255
- oscura/visualization/thumbnails.py +166 -137
- oscura/visualization/waveform.py +150 -63
- oscura/workflows/__init__.py +3 -0
- oscura/{batch → workflows/batch}/__init__.py +5 -5
- oscura/{batch → workflows/batch}/advanced.py +150 -75
- oscura/workflows/batch/aggregate.py +531 -0
- oscura/workflows/batch/analyze.py +236 -0
- oscura/{batch → workflows/batch}/logging.py +2 -2
- oscura/{batch → workflows/batch}/metrics.py +1 -1
- oscura/workflows/complete_re.py +1144 -0
- oscura/workflows/compliance.py +44 -54
- oscura/workflows/digital.py +197 -51
- oscura/workflows/legacy/__init__.py +12 -0
- oscura/{workflow → workflows/legacy}/dag.py +4 -1
- oscura/workflows/multi_trace.py +9 -9
- oscura/workflows/power.py +42 -62
- oscura/workflows/protocol.py +82 -49
- oscura/workflows/reverse_engineering.py +351 -150
- oscura/workflows/signal_integrity.py +157 -82
- oscura-0.6.0.dist-info/METADATA +643 -0
- oscura-0.6.0.dist-info/RECORD +590 -0
- oscura/analyzers/digital/ic_database.py +0 -498
- oscura/analyzers/digital/timing_paths.py +0 -339
- oscura/analyzers/digital/vintage.py +0 -377
- oscura/analyzers/digital/vintage_result.py +0 -148
- oscura/analyzers/protocols/parallel_bus.py +0 -449
- oscura/batch/aggregate.py +0 -300
- oscura/batch/analyze.py +0 -139
- oscura/dsl/__init__.py +0 -73
- oscura/exceptions.py +0 -59
- oscura/exploratory/fuzzy.py +0 -513
- oscura/exploratory/sync.py +0 -384
- oscura/export/wavedrom.py +0 -430
- oscura/exporters/__init__.py +0 -94
- oscura/exporters/csv.py +0 -303
- oscura/exporters/exporters.py +0 -44
- oscura/exporters/hdf5.py +0 -217
- oscura/exporters/html_export.py +0 -701
- oscura/exporters/json_export.py +0 -338
- oscura/exporters/markdown_export.py +0 -367
- oscura/exporters/matlab_export.py +0 -354
- oscura/exporters/npz_export.py +0 -219
- oscura/exporters/spice_export.py +0 -210
- oscura/exporters/vintage_logic_csv.py +0 -247
- oscura/reporting/vintage_logic_report.py +0 -523
- oscura/search/context.py +0 -149
- oscura/session/__init__.py +0 -34
- oscura/session/annotations.py +0 -289
- oscura/session/history.py +0 -313
- oscura/session/session.py +0 -520
- oscura/visualization/digital_advanced.py +0 -718
- oscura/visualization/figure_manager.py +0 -156
- oscura/workflow/__init__.py +0 -13
- oscura-0.5.0.dist-info/METADATA +0 -407
- oscura-0.5.0.dist-info/RECORD +0 -486
- /oscura/core/{config.py → config/legacy.py} +0 -0
- /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
- /oscura/{extensibility → core/extensibility}/registry.py +0 -0
- /oscura/{plugins → core/plugins}/isolation.py +0 -0
- /oscura/{builders → utils/builders}/signal_builder.py +0 -0
- /oscura/{optimization → utils/optimization}/parallel.py +0 -0
- /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
- /oscura/{streaming → utils/streaming}/__init__.py +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
oscura/core/memory_monitor.py
CHANGED
|
@@ -24,7 +24,7 @@ from contextlib import contextmanager
|
|
|
24
24
|
from dataclasses import dataclass
|
|
25
25
|
from typing import TYPE_CHECKING, Any
|
|
26
26
|
|
|
27
|
-
from oscura.config.memory import get_memory_config
|
|
27
|
+
from oscura.core.config.memory import get_memory_config
|
|
28
28
|
from oscura.utils.memory import get_available_memory, get_max_memory
|
|
29
29
|
|
|
30
30
|
if TYPE_CHECKING:
|
|
@@ -97,7 +97,7 @@ class MemoryMonitor:
|
|
|
97
97
|
if max_memory is None:
|
|
98
98
|
self.max_memory = get_max_memory()
|
|
99
99
|
elif isinstance(max_memory, str):
|
|
100
|
-
from oscura.config.memory import _parse_memory_string
|
|
100
|
+
from oscura.core.config.memory import _parse_memory_string
|
|
101
101
|
|
|
102
102
|
self.max_memory = _parse_memory_string(max_memory)
|
|
103
103
|
else:
|
oscura/core/memory_progress.py
CHANGED
|
@@ -131,7 +131,7 @@ class MemoryLogger:
|
|
|
131
131
|
# Initialize CSV writer if needed
|
|
132
132
|
if self.format == "csv":
|
|
133
133
|
self._csv_writer = csv.DictWriter(
|
|
134
|
-
self._file_handle,
|
|
134
|
+
self._file_handle,
|
|
135
135
|
fieldnames=[
|
|
136
136
|
"timestamp",
|
|
137
137
|
"operation",
|
|
@@ -144,9 +144,9 @@ class MemoryLogger:
|
|
|
144
144
|
"message",
|
|
145
145
|
],
|
|
146
146
|
)
|
|
147
|
-
self._csv_writer.writeheader()
|
|
147
|
+
self._csv_writer.writeheader()
|
|
148
148
|
if self.auto_flush:
|
|
149
|
-
self._file_handle.flush()
|
|
149
|
+
self._file_handle.flush()
|
|
150
150
|
|
|
151
151
|
return self
|
|
152
152
|
|
|
@@ -155,7 +155,7 @@ class MemoryLogger:
|
|
|
155
155
|
# Note: exc_val and exc_tb intentionally unused but required for Python 3.11+ compatibility
|
|
156
156
|
# Write summary for JSON format
|
|
157
157
|
if self.format == "json" and self._file_handle:
|
|
158
|
-
summary = {
|
|
158
|
+
summary = {
|
|
159
159
|
"entries": [asdict(entry) for entry in self._entries],
|
|
160
160
|
"summary": self._get_summary_dict(),
|
|
161
161
|
}
|
|
@@ -163,7 +163,7 @@ class MemoryLogger:
|
|
|
163
163
|
|
|
164
164
|
# Close file
|
|
165
165
|
if self._file_handle:
|
|
166
|
-
self._file_handle.close()
|
|
166
|
+
self._file_handle.close()
|
|
167
167
|
self._file_handle = None
|
|
168
168
|
|
|
169
169
|
def log_operation(
|
|
@@ -213,8 +213,8 @@ class MemoryLogger:
|
|
|
213
213
|
self._entries.append(entry)
|
|
214
214
|
|
|
215
215
|
# Write to file
|
|
216
|
-
if self._file_handle and self.format == "csv":
|
|
217
|
-
self._csv_writer.writerow(asdict(entry))
|
|
216
|
+
if self._file_handle and self.format == "csv":
|
|
217
|
+
self._csv_writer.writerow(asdict(entry))
|
|
218
218
|
if self.auto_flush:
|
|
219
219
|
self._file_handle.flush()
|
|
220
220
|
|
oscura/core/memory_warnings.py
CHANGED
|
@@ -20,7 +20,7 @@ import warnings
|
|
|
20
20
|
from enum import Enum
|
|
21
21
|
from typing import TYPE_CHECKING
|
|
22
22
|
|
|
23
|
-
from oscura.config.memory import get_memory_config
|
|
23
|
+
from oscura.core.config.memory import get_memory_config
|
|
24
24
|
from oscura.utils.memory import get_available_memory, get_memory_pressure
|
|
25
25
|
|
|
26
26
|
if TYPE_CHECKING:
|
oscura/core/numba_backend.py
CHANGED
|
@@ -43,11 +43,11 @@ import numpy as np
|
|
|
43
43
|
|
|
44
44
|
# Try to import Numba
|
|
45
45
|
try:
|
|
46
|
-
from numba import guvectorize as _numba_guvectorize
|
|
47
|
-
from numba import jit as _numba_jit
|
|
48
|
-
from numba import njit as _numba_njit
|
|
49
|
-
from numba import prange as _numba_prange
|
|
50
|
-
from numba import vectorize as _numba_vectorize
|
|
46
|
+
from numba import guvectorize as _numba_guvectorize
|
|
47
|
+
from numba import jit as _numba_jit
|
|
48
|
+
from numba import njit as _numba_njit
|
|
49
|
+
from numba import prange as _numba_prange
|
|
50
|
+
from numba import vectorize as _numba_vectorize
|
|
51
51
|
|
|
52
52
|
HAS_NUMBA = True
|
|
53
53
|
except ImportError:
|
|
@@ -92,7 +92,7 @@ else:
|
|
|
92
92
|
# Handle both @njit and @njit() syntax
|
|
93
93
|
if len(args) == 1 and callable(args[0]) and not kwargs:
|
|
94
94
|
return decorator(args[0]) # type: ignore[no-any-return]
|
|
95
|
-
return decorator
|
|
95
|
+
return decorator
|
|
96
96
|
|
|
97
97
|
def prange(*args: Any, **kwargs: Any) -> range:
|
|
98
98
|
"""Fallback to regular range when Numba is not available.
|
|
@@ -122,7 +122,7 @@ else:
|
|
|
122
122
|
|
|
123
123
|
if len(args) == 1 and callable(args[0]):
|
|
124
124
|
return decorator(args[0]) # type: ignore[no-any-return]
|
|
125
|
-
return decorator
|
|
125
|
+
return decorator
|
|
126
126
|
|
|
127
127
|
def guvectorize(*args: Any, **kwargs: Any) -> Callable[[F], F]:
|
|
128
128
|
"""No-op decorator when Numba is not available.
|
|
@@ -140,7 +140,7 @@ else:
|
|
|
140
140
|
|
|
141
141
|
if len(args) == 1 and callable(args[0]):
|
|
142
142
|
return decorator(args[0]) # type: ignore[no-any-return]
|
|
143
|
-
return decorator
|
|
143
|
+
return decorator
|
|
144
144
|
|
|
145
145
|
def jit(*args: Any, **kwargs: Any) -> Callable[[F], F]:
|
|
146
146
|
"""No-op decorator when Numba is not available.
|
|
@@ -162,7 +162,7 @@ else:
|
|
|
162
162
|
|
|
163
163
|
if len(args) == 1 and callable(args[0]) and not kwargs:
|
|
164
164
|
return decorator(args[0]) # type: ignore[no-any-return]
|
|
165
|
-
return decorator
|
|
165
|
+
return decorator
|
|
166
166
|
|
|
167
167
|
|
|
168
168
|
def get_optimal_numba_config(
|
|
@@ -202,7 +202,7 @@ def get_optimal_numba_config(
|
|
|
202
202
|
# Example Numba-optimized functions for common operations
|
|
203
203
|
|
|
204
204
|
|
|
205
|
-
@njit(cache=True) # type: ignore[
|
|
205
|
+
@njit(cache=True) # type: ignore[untyped-decorator] # Numba JIT decorator
|
|
206
206
|
def find_crossings_numba(
|
|
207
207
|
data: np.ndarray, # type: ignore[type-arg]
|
|
208
208
|
threshold: float,
|
|
@@ -233,7 +233,7 @@ def find_crossings_numba(
|
|
|
233
233
|
return np.array(crossings, dtype=np.int64)
|
|
234
234
|
|
|
235
235
|
|
|
236
|
-
@njit(parallel=True, cache=True) # type: ignore[
|
|
236
|
+
@njit(parallel=True, cache=True) # type: ignore[untyped-decorator] # Numba JIT decorator
|
|
237
237
|
def moving_average_numba(
|
|
238
238
|
data: np.ndarray, # type: ignore[type-arg]
|
|
239
239
|
window_size: int,
|
|
@@ -259,7 +259,7 @@ def moving_average_numba(
|
|
|
259
259
|
return result
|
|
260
260
|
|
|
261
261
|
|
|
262
|
-
@njit(cache=True) # type: ignore[
|
|
262
|
+
@njit(cache=True) # type: ignore[untyped-decorator] # Numba JIT decorator
|
|
263
263
|
def argrelextrema_numba(
|
|
264
264
|
data: np.ndarray, # type: ignore[type-arg]
|
|
265
265
|
comparator: int,
|
|
@@ -297,7 +297,7 @@ def argrelextrema_numba(
|
|
|
297
297
|
return np.array(extrema, dtype=np.int64)
|
|
298
298
|
|
|
299
299
|
|
|
300
|
-
@njit(cache=True) # type: ignore[
|
|
300
|
+
@njit(cache=True) # type: ignore[untyped-decorator] # Numba JIT decorator
|
|
301
301
|
def interpolate_linear_numba(
|
|
302
302
|
x: np.ndarray, # type: ignore[type-arg]
|
|
303
303
|
y: np.ndarray, # type: ignore[type-arg]
|
|
@@ -5,18 +5,18 @@ for extending Oscura functionality.
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Example:
|
|
8
|
-
>>> from oscura.plugins import discover_plugins, get_plugin
|
|
8
|
+
>>> from oscura.core.plugins import discover_plugins, get_plugin
|
|
9
9
|
>>> plugins = discover_plugins()
|
|
10
10
|
>>> for plugin in plugins:
|
|
11
11
|
... print(f"{plugin.name} v{plugin.version}")
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
from oscura.plugins.base import (
|
|
14
|
+
from oscura.core.plugins.base import (
|
|
15
15
|
PluginBase,
|
|
16
16
|
PluginCapability,
|
|
17
17
|
PluginMetadata,
|
|
18
18
|
)
|
|
19
|
-
from oscura.plugins.cli import (
|
|
19
|
+
from oscura.core.plugins.cli import (
|
|
20
20
|
PluginInstaller,
|
|
21
21
|
cli_disable_plugin,
|
|
22
22
|
cli_enable_plugin,
|
|
@@ -25,12 +25,12 @@ from oscura.plugins.cli import (
|
|
|
25
25
|
cli_plugin_info,
|
|
26
26
|
cli_validate_plugin,
|
|
27
27
|
)
|
|
28
|
-
from oscura.plugins.discovery import (
|
|
28
|
+
from oscura.core.plugins.discovery import (
|
|
29
29
|
discover_plugins,
|
|
30
30
|
get_plugin_paths,
|
|
31
31
|
scan_directory,
|
|
32
32
|
)
|
|
33
|
-
from oscura.plugins.isolation import (
|
|
33
|
+
from oscura.core.plugins.isolation import (
|
|
34
34
|
IsolationManager,
|
|
35
35
|
Permission,
|
|
36
36
|
PermissionSet,
|
|
@@ -38,7 +38,7 @@ from oscura.plugins.isolation import (
|
|
|
38
38
|
ResourceLimits,
|
|
39
39
|
get_isolation_manager,
|
|
40
40
|
)
|
|
41
|
-
from oscura.plugins.lifecycle import (
|
|
41
|
+
from oscura.core.plugins.lifecycle import (
|
|
42
42
|
DependencyGraph,
|
|
43
43
|
DependencyInfo,
|
|
44
44
|
PluginHandle,
|
|
@@ -48,12 +48,12 @@ from oscura.plugins.lifecycle import (
|
|
|
48
48
|
get_lifecycle_manager,
|
|
49
49
|
set_plugin_directories,
|
|
50
50
|
)
|
|
51
|
-
from oscura.plugins.manager import (
|
|
51
|
+
from oscura.core.plugins.manager import (
|
|
52
52
|
PluginManager,
|
|
53
53
|
get_plugin_manager,
|
|
54
54
|
reset_plugin_manager,
|
|
55
55
|
)
|
|
56
|
-
from oscura.plugins.registry import (
|
|
56
|
+
from oscura.core.plugins.registry import (
|
|
57
57
|
PluginRegistry,
|
|
58
58
|
get_plugin,
|
|
59
59
|
get_plugin_registry,
|
|
@@ -61,7 +61,7 @@ from oscura.plugins.registry import (
|
|
|
61
61
|
list_plugins,
|
|
62
62
|
register_plugin,
|
|
63
63
|
)
|
|
64
|
-
from oscura.plugins.versioning import (
|
|
64
|
+
from oscura.core.plugins.versioning import (
|
|
65
65
|
Migration,
|
|
66
66
|
MigrationManager,
|
|
67
67
|
VersionCompatibilityLayer,
|
|
@@ -112,7 +112,7 @@ class PluginMetadata:
|
|
|
112
112
|
return False
|
|
113
113
|
|
|
114
114
|
|
|
115
|
-
class PluginBase(ABC):
|
|
115
|
+
class PluginBase(ABC):
|
|
116
116
|
"""Base class for all Oscura plugins.
|
|
117
117
|
|
|
118
118
|
Subclass this to create a plugin. Define class attributes for
|
|
@@ -143,8 +143,8 @@ class PluginBase(ABC): # noqa: B024
|
|
|
143
143
|
description: str = ""
|
|
144
144
|
homepage: str = ""
|
|
145
145
|
license: str = ""
|
|
146
|
-
capabilities: list[PluginCapability] = []
|
|
147
|
-
requires_plugins: list[tuple[str, str]] = [] # (name, version_spec)
|
|
146
|
+
capabilities: list[PluginCapability] = []
|
|
147
|
+
requires_plugins: list[tuple[str, str]] = [] # (name, version_spec)
|
|
148
148
|
|
|
149
149
|
def __init__(self) -> None:
|
|
150
150
|
"""Initialize plugin instance."""
|
|
@@ -174,7 +174,7 @@ class PluginBase(ABC): # noqa: B024
|
|
|
174
174
|
)
|
|
175
175
|
return self._metadata
|
|
176
176
|
|
|
177
|
-
def on_load(self) -> None:
|
|
177
|
+
def on_load(self) -> None:
|
|
178
178
|
"""Called when plugin is loaded.
|
|
179
179
|
|
|
180
180
|
Override to register capabilities, initialize resources, etc.
|
|
@@ -196,7 +196,7 @@ class PluginBase(ABC): # noqa: B024
|
|
|
196
196
|
"""
|
|
197
197
|
self._config = config
|
|
198
198
|
|
|
199
|
-
def on_enable(self) -> None:
|
|
199
|
+
def on_enable(self) -> None:
|
|
200
200
|
"""Called when plugin is enabled.
|
|
201
201
|
|
|
202
202
|
Override to activate plugin functionality, start services, etc.
|
|
@@ -205,7 +205,7 @@ class PluginBase(ABC): # noqa: B024
|
|
|
205
205
|
PLUG-002: Plugin Registration - lifecycle hooks
|
|
206
206
|
"""
|
|
207
207
|
|
|
208
|
-
def on_disable(self) -> None:
|
|
208
|
+
def on_disable(self) -> None:
|
|
209
209
|
"""Called when plugin is disabled.
|
|
210
210
|
|
|
211
211
|
Override to pause plugin functionality, stop services, etc.
|
|
@@ -214,7 +214,7 @@ class PluginBase(ABC): # noqa: B024
|
|
|
214
214
|
PLUG-002: Plugin Registration - lifecycle hooks
|
|
215
215
|
"""
|
|
216
216
|
|
|
217
|
-
def on_unload(self) -> None:
|
|
217
|
+
def on_unload(self) -> None:
|
|
218
218
|
"""Called when plugin is unloaded.
|
|
219
219
|
|
|
220
220
|
Override to clean up resources.
|
|
@@ -13,9 +13,9 @@ import tempfile
|
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
from urllib.parse import urlparse
|
|
15
15
|
|
|
16
|
-
from oscura.plugins.discovery import discover_plugins, get_plugin_paths
|
|
17
|
-
from oscura.plugins.lifecycle import get_lifecycle_manager
|
|
18
|
-
from oscura.plugins.registry import get_plugin_registry
|
|
16
|
+
from oscura.core.plugins.discovery import discover_plugins, get_plugin_paths
|
|
17
|
+
from oscura.core.plugins.lifecycle import get_lifecycle_manager
|
|
18
|
+
from oscura.core.plugins.registry import get_plugin_registry
|
|
19
19
|
|
|
20
20
|
logger = logging.getLogger(__name__)
|
|
21
21
|
|
|
@@ -5,7 +5,7 @@ and Python entry points.
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Example:
|
|
8
|
-
>>> from oscura.plugins.discovery import discover_plugins
|
|
8
|
+
>>> from oscura.core.plugins.discovery import discover_plugins
|
|
9
9
|
>>> plugins = discover_plugins()
|
|
10
10
|
>>> for plugin in plugins:
|
|
11
11
|
... print(f"Found: {plugin.name} v{plugin.version}")
|
|
@@ -20,9 +20,9 @@ import os
|
|
|
20
20
|
import sys
|
|
21
21
|
from dataclasses import dataclass
|
|
22
22
|
from pathlib import Path
|
|
23
|
-
from typing import TYPE_CHECKING
|
|
23
|
+
from typing import TYPE_CHECKING, Any
|
|
24
24
|
|
|
25
|
-
from oscura.plugins.base import PluginBase, PluginMetadata
|
|
25
|
+
from oscura.core.plugins.base import PluginBase, PluginMetadata
|
|
26
26
|
|
|
27
27
|
if TYPE_CHECKING:
|
|
28
28
|
from collections.abc import Iterator
|
|
@@ -256,47 +256,11 @@ def _load_plugin_from_yaml(yaml_path: Path) -> DiscoveredPlugin | None:
|
|
|
256
256
|
return None
|
|
257
257
|
|
|
258
258
|
try:
|
|
259
|
-
|
|
260
|
-
data = yaml.safe_load(f)
|
|
261
|
-
|
|
259
|
+
data = _read_yaml_file(yaml_path)
|
|
262
260
|
if not isinstance(data, dict):
|
|
263
261
|
return None
|
|
264
262
|
|
|
265
|
-
|
|
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
|
-
|
|
263
|
+
metadata = _build_plugin_metadata(yaml_path, data)
|
|
300
264
|
compatible = metadata.is_compatible_with(OSCURA_API_VERSION)
|
|
301
265
|
|
|
302
266
|
return DiscoveredPlugin(
|
|
@@ -306,16 +270,96 @@ def _load_plugin_from_yaml(yaml_path: Path) -> DiscoveredPlugin | None:
|
|
|
306
270
|
)
|
|
307
271
|
|
|
308
272
|
except Exception as e:
|
|
309
|
-
return
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
273
|
+
return _create_failed_plugin(yaml_path.parent, str(e))
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _read_yaml_file(yaml_path: Path) -> Any:
|
|
277
|
+
"""Read and parse YAML file.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
yaml_path: Path to YAML file.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
Parsed YAML data.
|
|
284
|
+
"""
|
|
285
|
+
with open(yaml_path, encoding="utf-8") as f:
|
|
286
|
+
return yaml.safe_load(f)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _build_plugin_metadata(yaml_path: Path, data: dict[str, Any]) -> PluginMetadata:
|
|
290
|
+
"""Build plugin metadata from YAML data.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
yaml_path: Path to plugin.yaml file.
|
|
294
|
+
data: Parsed YAML data.
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
PluginMetadata instance.
|
|
298
|
+
"""
|
|
299
|
+
metadata = PluginMetadata(
|
|
300
|
+
name=data.get("name", yaml_path.parent.name),
|
|
301
|
+
version=data.get("version", "0.0.0"),
|
|
302
|
+
api_version=data.get("api_version", "1.0.0"),
|
|
303
|
+
author=data.get("author", ""),
|
|
304
|
+
description=data.get("description", ""),
|
|
305
|
+
homepage=data.get("homepage", ""),
|
|
306
|
+
license=data.get("license", ""),
|
|
307
|
+
path=yaml_path.parent,
|
|
308
|
+
enabled=data.get("enabled", True),
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
_parse_plugin_dependencies(metadata, data)
|
|
312
|
+
_parse_plugin_provides(metadata, data)
|
|
313
|
+
|
|
314
|
+
return metadata
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _parse_plugin_dependencies(metadata: PluginMetadata, data: dict[str, Any]) -> None:
|
|
318
|
+
"""Parse plugin dependencies from YAML data.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
metadata: PluginMetadata to update.
|
|
322
|
+
data: Parsed YAML data.
|
|
323
|
+
"""
|
|
324
|
+
if "dependencies" not in data:
|
|
325
|
+
return
|
|
326
|
+
|
|
327
|
+
deps = data["dependencies"]
|
|
328
|
+
if not isinstance(deps, list):
|
|
329
|
+
return
|
|
330
|
+
|
|
331
|
+
for dep in deps:
|
|
332
|
+
if not isinstance(dep, dict):
|
|
333
|
+
continue
|
|
334
|
+
|
|
335
|
+
if "plugin" in dep:
|
|
336
|
+
metadata.dependencies[dep["plugin"]] = dep.get("version", "*")
|
|
337
|
+
elif "package" in dep:
|
|
338
|
+
metadata.dependencies[dep["package"]] = dep.get("version", "*")
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _parse_plugin_provides(metadata: PluginMetadata, data: dict[str, Any]) -> None:
|
|
342
|
+
"""Parse plugin provides from YAML data.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
metadata: PluginMetadata to update.
|
|
346
|
+
data: Parsed YAML data.
|
|
347
|
+
"""
|
|
348
|
+
if "provides" not in data:
|
|
349
|
+
return
|
|
350
|
+
|
|
351
|
+
provides = data["provides"]
|
|
352
|
+
if not isinstance(provides, list):
|
|
353
|
+
return
|
|
354
|
+
|
|
355
|
+
for item in provides:
|
|
356
|
+
if not isinstance(item, dict):
|
|
357
|
+
continue
|
|
358
|
+
|
|
359
|
+
for key, value in item.items():
|
|
360
|
+
if key not in metadata.provides:
|
|
361
|
+
metadata.provides[key] = []
|
|
362
|
+
metadata.provides[key].append(value)
|
|
319
363
|
|
|
320
364
|
|
|
321
365
|
def _load_plugin_from_module(module_path: Path) -> DiscoveredPlugin | None:
|
|
@@ -328,77 +372,113 @@ def _load_plugin_from_module(module_path: Path) -> DiscoveredPlugin | None:
|
|
|
328
372
|
DiscoveredPlugin or None if load fails.
|
|
329
373
|
"""
|
|
330
374
|
try:
|
|
331
|
-
# Add parent to path temporarily
|
|
332
375
|
parent = str(module_path.parent)
|
|
333
|
-
|
|
376
|
+
added_path = parent not in sys.path
|
|
377
|
+
|
|
378
|
+
if added_path:
|
|
334
379
|
sys.path.insert(0, parent)
|
|
335
|
-
added_path = True
|
|
336
|
-
else:
|
|
337
|
-
added_path = False
|
|
338
380
|
|
|
339
381
|
try:
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
382
|
+
module = _import_plugin_module(module_path)
|
|
383
|
+
if module is None:
|
|
384
|
+
return None
|
|
343
385
|
|
|
344
|
-
|
|
386
|
+
plugin_class = _find_plugin_class(module)
|
|
387
|
+
if plugin_class is None:
|
|
345
388
|
return None
|
|
346
389
|
|
|
347
|
-
|
|
348
|
-
spec.loader.exec_module(module)
|
|
390
|
+
return _create_discovered_plugin(plugin_class, module_path)
|
|
349
391
|
|
|
350
|
-
|
|
351
|
-
|
|
392
|
+
finally:
|
|
393
|
+
if added_path:
|
|
394
|
+
sys.path.remove(parent)
|
|
352
395
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
plugin_class = module.Plugin
|
|
356
|
-
elif hasattr(module, "plugin"):
|
|
357
|
-
plugin_class = module.plugin
|
|
396
|
+
except Exception as e:
|
|
397
|
+
return _create_failed_plugin(module_path, str(e))
|
|
358
398
|
|
|
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
399
|
|
|
371
|
-
|
|
372
|
-
|
|
400
|
+
def _import_plugin_module(module_path: Path) -> Any:
|
|
401
|
+
"""Import plugin module from path.
|
|
373
402
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
metadata = instance.metadata
|
|
377
|
-
metadata.path = module_path
|
|
403
|
+
Args:
|
|
404
|
+
module_path: Path to plugin package.
|
|
378
405
|
|
|
379
|
-
|
|
406
|
+
Returns:
|
|
407
|
+
Imported module or None.
|
|
408
|
+
"""
|
|
409
|
+
module_name = module_path.name
|
|
410
|
+
spec = importlib.util.spec_from_file_location(module_name, module_path / "__init__.py")
|
|
380
411
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
path=module_path,
|
|
384
|
-
compatible=compatible,
|
|
385
|
-
)
|
|
412
|
+
if spec is None or spec.loader is None:
|
|
413
|
+
return None
|
|
386
414
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
415
|
+
module = importlib.util.module_from_spec(spec)
|
|
416
|
+
spec.loader.exec_module(module)
|
|
417
|
+
return module
|
|
390
418
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
419
|
+
|
|
420
|
+
def _find_plugin_class(module: Any) -> type | None:
|
|
421
|
+
"""Find plugin class in module.
|
|
422
|
+
|
|
423
|
+
Args:
|
|
424
|
+
module: Imported module to search.
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
Plugin class or None.
|
|
428
|
+
"""
|
|
429
|
+
# Check explicit names first
|
|
430
|
+
if hasattr(module, "Plugin"):
|
|
431
|
+
plugin_attr = module.Plugin
|
|
432
|
+
if isinstance(plugin_attr, type):
|
|
433
|
+
return plugin_attr
|
|
434
|
+
if hasattr(module, "plugin"):
|
|
435
|
+
plugin_attr = module.plugin
|
|
436
|
+
if isinstance(plugin_attr, type):
|
|
437
|
+
return plugin_attr
|
|
438
|
+
|
|
439
|
+
# Search for PluginBase subclass
|
|
440
|
+
for attr_name in dir(module):
|
|
441
|
+
attr = getattr(module, attr_name)
|
|
442
|
+
if isinstance(attr, type) and issubclass(attr, PluginBase) and attr is not PluginBase:
|
|
443
|
+
return attr
|
|
444
|
+
|
|
445
|
+
return None
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def _create_discovered_plugin(plugin_class: type, module_path: Path) -> DiscoveredPlugin:
|
|
449
|
+
"""Create DiscoveredPlugin from plugin class.
|
|
450
|
+
|
|
451
|
+
Args:
|
|
452
|
+
plugin_class: Plugin class to instantiate.
|
|
453
|
+
module_path: Path to plugin module.
|
|
454
|
+
|
|
455
|
+
Returns:
|
|
456
|
+
DiscoveredPlugin instance.
|
|
457
|
+
"""
|
|
458
|
+
instance = plugin_class()
|
|
459
|
+
metadata = instance.metadata
|
|
460
|
+
metadata.path = module_path
|
|
461
|
+
compatible = metadata.is_compatible_with(OSCURA_API_VERSION)
|
|
462
|
+
|
|
463
|
+
return DiscoveredPlugin(metadata=metadata, path=module_path, compatible=compatible)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def _create_failed_plugin(module_path: Path, error: str) -> DiscoveredPlugin:
|
|
467
|
+
"""Create DiscoveredPlugin for failed load.
|
|
468
|
+
|
|
469
|
+
Args:
|
|
470
|
+
module_path: Path to plugin.
|
|
471
|
+
error: Error message.
|
|
472
|
+
|
|
473
|
+
Returns:
|
|
474
|
+
DiscoveredPlugin with error.
|
|
475
|
+
"""
|
|
476
|
+
return DiscoveredPlugin(
|
|
477
|
+
metadata=PluginMetadata(name=module_path.name, version="0.0.0", path=module_path),
|
|
478
|
+
path=module_path,
|
|
479
|
+
compatible=False,
|
|
480
|
+
load_error=error,
|
|
481
|
+
)
|
|
402
482
|
|
|
403
483
|
|
|
404
484
|
__all__ = [
|
|
@@ -20,7 +20,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
20
20
|
if TYPE_CHECKING:
|
|
21
21
|
from collections.abc import Callable
|
|
22
22
|
|
|
23
|
-
from oscura.plugins.base import PluginBase, PluginMetadata
|
|
23
|
+
from oscura.core.plugins.base import PluginBase, PluginMetadata
|
|
24
24
|
|
|
25
25
|
logger = logging.getLogger(__name__)
|
|
26
26
|
|