oscura 0.5.1__py3-none-any.whl → 0.7.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/edges.py +325 -65
- 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/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 +17 -102
- 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/{schemas → core/schemas}/device_mapping.json +2 -8
- oscura/{schemas → core/schemas}/packet_format.json +4 -24
- oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
- 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 -8
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- 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/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 +183 -67
- 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/tss.py +456 -0
- oscura/loaders/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -0
- 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/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/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 +1 -1
- 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.7.0.dist-info/METADATA +661 -0
- oscura-0.7.0.dist-info/RECORD +591 -0
- 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/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 -291
- 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/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/workflow/__init__.py +0 -13
- oscura-0.5.1.dist-info/METADATA +0 -583
- oscura-0.5.1.dist-info/RECORD +0 -481
- /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/{schemas → core/schemas}/bus_configuration.json +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.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
oscura/core/cache.py
CHANGED
|
@@ -21,7 +21,10 @@ from __future__ import annotations
|
|
|
21
21
|
|
|
22
22
|
import contextlib
|
|
23
23
|
import hashlib
|
|
24
|
+
import hmac
|
|
25
|
+
import logging
|
|
24
26
|
import pickle
|
|
27
|
+
import secrets
|
|
25
28
|
import tempfile
|
|
26
29
|
import threading
|
|
27
30
|
import time
|
|
@@ -32,10 +35,14 @@ from typing import TYPE_CHECKING, Any, TypeVar
|
|
|
32
35
|
|
|
33
36
|
import numpy as np
|
|
34
37
|
|
|
38
|
+
from oscura.core.exceptions import SecurityError
|
|
39
|
+
|
|
35
40
|
if TYPE_CHECKING:
|
|
36
41
|
from collections.abc import Callable
|
|
37
42
|
|
|
38
43
|
|
|
44
|
+
logger = logging.getLogger(__name__)
|
|
45
|
+
|
|
39
46
|
T = TypeVar("T")
|
|
40
47
|
|
|
41
48
|
|
|
@@ -180,6 +187,9 @@ class OscuraCache:
|
|
|
180
187
|
self._disk_spills = 0
|
|
181
188
|
self._current_memory = 0
|
|
182
189
|
|
|
190
|
+
# Security: HMAC signing key for cache integrity (SEC-003 fix)
|
|
191
|
+
self._cache_key = self._load_or_create_cache_key()
|
|
192
|
+
|
|
183
193
|
def __enter__(self) -> OscuraCache:
|
|
184
194
|
"""Enter context."""
|
|
185
195
|
return self
|
|
@@ -227,12 +237,21 @@ class OscuraCache:
|
|
|
227
237
|
|
|
228
238
|
# Load from disk if needed
|
|
229
239
|
if not entry.in_memory:
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
240
|
+
try:
|
|
241
|
+
entry.value = self._load_from_disk(entry.disk_path) # type: ignore[arg-type]
|
|
242
|
+
entry.in_memory = True
|
|
243
|
+
self._current_memory += entry.size_bytes
|
|
244
|
+
|
|
245
|
+
# Check if we need to spill to make room
|
|
246
|
+
self._ensure_memory_limit()
|
|
247
|
+
except SecurityError:
|
|
248
|
+
# Re-raise security errors (tampered data)
|
|
249
|
+
raise
|
|
250
|
+
except (OSError, pickle.UnpicklingError):
|
|
251
|
+
# Remove corrupted entry from cache for non-security errors
|
|
252
|
+
del self._cache[key]
|
|
253
|
+
self._misses += 1
|
|
254
|
+
return None
|
|
236
255
|
|
|
237
256
|
return entry.value
|
|
238
257
|
|
|
@@ -441,8 +460,7 @@ class OscuraCache:
|
|
|
441
460
|
self._evictions += 1
|
|
442
461
|
|
|
443
462
|
def _spill_to_disk(self, key: str, value: Any) -> Path:
|
|
444
|
-
"""Write value to disk.
|
|
445
|
-
|
|
463
|
+
"""Write value to disk with HMAC signature.
|
|
446
464
|
|
|
447
465
|
Args:
|
|
448
466
|
key: Cache key.
|
|
@@ -450,29 +468,88 @@ class OscuraCache:
|
|
|
450
468
|
|
|
451
469
|
Returns:
|
|
452
470
|
Path to disk file.
|
|
471
|
+
|
|
472
|
+
Security:
|
|
473
|
+
SEC-003 fix: Writes HMAC-SHA256 signature + pickled data.
|
|
474
|
+
Format: [32 bytes signature][pickled data]
|
|
475
|
+
Signature computed over pickled data using self._cache_key.
|
|
476
|
+
|
|
477
|
+
References:
|
|
478
|
+
MEM-031: Persistent Cache (Disk-Based)
|
|
453
479
|
"""
|
|
454
480
|
disk_path = self.cache_dir / f"{key}.pkl"
|
|
481
|
+
|
|
482
|
+
# Serialize data
|
|
483
|
+
data = pickle.dumps(value, protocol=pickle.HIGHEST_PROTOCOL)
|
|
484
|
+
|
|
485
|
+
# Compute HMAC-SHA256 signature
|
|
486
|
+
signature = hmac.new(self._cache_key, data, hashlib.sha256).digest()
|
|
487
|
+
|
|
488
|
+
# Write signature + data
|
|
455
489
|
with open(disk_path, "wb") as f:
|
|
456
|
-
|
|
490
|
+
f.write(signature) # First 32 bytes
|
|
491
|
+
f.write(data) # Rest is pickled data
|
|
492
|
+
|
|
457
493
|
return disk_path
|
|
458
494
|
|
|
459
495
|
def _load_from_disk(self, disk_path: Path) -> Any:
|
|
460
|
-
"""Load value from disk.
|
|
461
|
-
|
|
496
|
+
"""Load value from disk with HMAC verification.
|
|
462
497
|
|
|
463
498
|
Args:
|
|
464
499
|
disk_path: Path to disk file.
|
|
465
500
|
|
|
466
501
|
Returns:
|
|
467
502
|
Loaded value.
|
|
503
|
+
|
|
504
|
+
Raises:
|
|
505
|
+
SecurityError: If HMAC verification fails (tampered cache file).
|
|
506
|
+
|
|
507
|
+
Security:
|
|
508
|
+
SEC-003 fix: Verifies HMAC-SHA256 signature before unpickling.
|
|
509
|
+
Prevents code execution from tampered cache files.
|
|
510
|
+
Uses constant-time comparison (hmac.compare_digest).
|
|
511
|
+
|
|
512
|
+
References:
|
|
513
|
+
MEM-031: Persistent Cache (Disk-Based)
|
|
468
514
|
"""
|
|
469
|
-
|
|
470
|
-
|
|
515
|
+
try:
|
|
516
|
+
with open(disk_path, "rb") as f:
|
|
517
|
+
signature = f.read(32) # SHA256 = 32 bytes
|
|
518
|
+
data = f.read()
|
|
519
|
+
|
|
520
|
+
# Check if file is too short (corrupted, not tampered)
|
|
521
|
+
if len(signature) < 32:
|
|
522
|
+
logger.warning(f"Cache file too short (corrupted): {disk_path.name}")
|
|
523
|
+
disk_path.unlink(missing_ok=True)
|
|
524
|
+
raise OSError(f"Cache file corrupted (too short): {disk_path.name}")
|
|
525
|
+
|
|
526
|
+
# Verify HMAC signature
|
|
527
|
+
expected_signature = hmac.new(self._cache_key, data, hashlib.sha256).digest()
|
|
528
|
+
|
|
529
|
+
if not hmac.compare_digest(signature, expected_signature):
|
|
530
|
+
logger.error(f"Cache integrity check failed for {disk_path.name}")
|
|
531
|
+
# Delete corrupted cache file
|
|
532
|
+
disk_path.unlink(missing_ok=True)
|
|
533
|
+
raise SecurityError(
|
|
534
|
+
f"Cache file integrity verification failed: {disk_path.name}. "
|
|
535
|
+
"File may have been tampered with and has been removed."
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
# Deserialize only after HMAC verification
|
|
539
|
+
return pickle.loads(data)
|
|
540
|
+
|
|
541
|
+
except SecurityError:
|
|
542
|
+
raise # Re-raise security errors
|
|
543
|
+
except Exception as e:
|
|
544
|
+
logger.warning(f"Failed to load cache file {disk_path.name}: {e}")
|
|
545
|
+
# Clean up corrupted file
|
|
546
|
+
disk_path.unlink(missing_ok=True)
|
|
547
|
+
raise
|
|
471
548
|
|
|
472
549
|
def _estimate_size(self, value: Any) -> int:
|
|
473
550
|
"""Estimate size of value in bytes."""
|
|
474
551
|
if isinstance(value, np.ndarray):
|
|
475
|
-
return value.nbytes
|
|
552
|
+
return value.nbytes
|
|
476
553
|
elif isinstance(value, list | tuple):
|
|
477
554
|
return sum(self._estimate_size(item) for item in value)
|
|
478
555
|
elif isinstance(value, dict):
|
|
@@ -488,7 +565,7 @@ class OscuraCache:
|
|
|
488
565
|
"""Convert object to hashable bytes."""
|
|
489
566
|
if isinstance(obj, np.ndarray):
|
|
490
567
|
# Use array bytes for hashing
|
|
491
|
-
return obj.tobytes()
|
|
568
|
+
return obj.tobytes()
|
|
492
569
|
elif isinstance(obj, str | bytes):
|
|
493
570
|
return obj.encode() if isinstance(obj, str) else obj
|
|
494
571
|
elif isinstance(obj, int | float | bool):
|
|
@@ -512,6 +589,40 @@ class OscuraCache:
|
|
|
512
589
|
else:
|
|
513
590
|
return int(memory_str)
|
|
514
591
|
|
|
592
|
+
def _load_or_create_cache_key(self) -> bytes:
|
|
593
|
+
"""Load or create HMAC signing key for cache integrity.
|
|
594
|
+
|
|
595
|
+
Returns:
|
|
596
|
+
256-bit signing key.
|
|
597
|
+
|
|
598
|
+
Security:
|
|
599
|
+
SEC-003 fix: Protects cached pickle files from tampering.
|
|
600
|
+
Key is persistent per cache directory and stored with 0o600 permissions.
|
|
601
|
+
Each cache directory has its own unique key.
|
|
602
|
+
|
|
603
|
+
References:
|
|
604
|
+
https://owasp.org/www-project-top-ten/
|
|
605
|
+
"""
|
|
606
|
+
key_file = self.cache_dir / ".cache_key"
|
|
607
|
+
|
|
608
|
+
# Load existing key
|
|
609
|
+
if key_file.exists():
|
|
610
|
+
with open(key_file, "rb") as f:
|
|
611
|
+
return f.read()
|
|
612
|
+
|
|
613
|
+
# Create new 256-bit key
|
|
614
|
+
key = secrets.token_bytes(32)
|
|
615
|
+
|
|
616
|
+
# Save with restrictive permissions
|
|
617
|
+
with open(key_file, "wb") as f:
|
|
618
|
+
f.write(key)
|
|
619
|
+
|
|
620
|
+
# Set owner read/write only (0o600)
|
|
621
|
+
key_file.chmod(0o600)
|
|
622
|
+
|
|
623
|
+
logger.info(f"Created new cache signing key: {key_file}")
|
|
624
|
+
return key
|
|
625
|
+
|
|
515
626
|
|
|
516
627
|
# Global cache instance
|
|
517
628
|
_global_cache: OscuraCache | None = None
|
oscura/core/cancellation.py
CHANGED
|
@@ -260,7 +260,7 @@ class CancellationManager:
|
|
|
260
260
|
except KeyboardInterrupt:
|
|
261
261
|
self.cancel("Interrupted by user (Ctrl+C)")
|
|
262
262
|
self._cleanup()
|
|
263
|
-
raise CancelledException(
|
|
263
|
+
raise CancelledException(
|
|
264
264
|
f"{name} interrupted by user",
|
|
265
265
|
partial_results=self._partial_results,
|
|
266
266
|
elapsed_time=time.time() - self._start_time,
|
|
@@ -10,23 +10,23 @@ This package provides:
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
Example:
|
|
13
|
-
>>> from oscura.config import load_config, validate_config
|
|
13
|
+
>>> from oscura.core.config import load_config, validate_config
|
|
14
14
|
>>> config = load_config("pipeline.yaml")
|
|
15
15
|
>>> validate_config(config, schema="pipeline")
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
from oscura.config.defaults import (
|
|
18
|
+
from oscura.core.config.defaults import (
|
|
19
19
|
DEFAULT_CONFIG,
|
|
20
20
|
get_effective_config,
|
|
21
21
|
inject_defaults,
|
|
22
22
|
)
|
|
23
|
-
from oscura.config.loader import (
|
|
23
|
+
from oscura.core.config.loader import (
|
|
24
24
|
get_config_value,
|
|
25
25
|
load_config,
|
|
26
26
|
load_config_file,
|
|
27
27
|
save_config,
|
|
28
28
|
)
|
|
29
|
-
from oscura.config.memory import (
|
|
29
|
+
from oscura.core.config.memory import (
|
|
30
30
|
MemoryConfiguration,
|
|
31
31
|
configure_from_environment,
|
|
32
32
|
enable_auto_degrade,
|
|
@@ -36,7 +36,7 @@ from oscura.config.memory import (
|
|
|
36
36
|
set_memory_reserve,
|
|
37
37
|
set_memory_thresholds,
|
|
38
38
|
)
|
|
39
|
-
from oscura.config.migration import (
|
|
39
|
+
from oscura.core.config.migration import (
|
|
40
40
|
Migration,
|
|
41
41
|
MigrationFunction,
|
|
42
42
|
SchemaMigration,
|
|
@@ -46,7 +46,7 @@ from oscura.config.migration import (
|
|
|
46
46
|
migrate_config,
|
|
47
47
|
register_migration,
|
|
48
48
|
)
|
|
49
|
-
from oscura.config.pipeline import (
|
|
49
|
+
from oscura.core.config.pipeline import (
|
|
50
50
|
Pipeline,
|
|
51
51
|
PipelineDefinition,
|
|
52
52
|
PipelineExecutionError,
|
|
@@ -57,7 +57,7 @@ from oscura.config.pipeline import (
|
|
|
57
57
|
load_pipeline,
|
|
58
58
|
resolve_includes,
|
|
59
59
|
)
|
|
60
|
-
from oscura.config.preferences import (
|
|
60
|
+
from oscura.core.config.preferences import (
|
|
61
61
|
DefaultsPreferences,
|
|
62
62
|
EditorPreferences,
|
|
63
63
|
ExportPreferences,
|
|
@@ -69,7 +69,7 @@ from oscura.config.preferences import (
|
|
|
69
69
|
get_preferences_manager,
|
|
70
70
|
save_preferences,
|
|
71
71
|
)
|
|
72
|
-
from oscura.config.protocol import (
|
|
72
|
+
from oscura.core.config.protocol import (
|
|
73
73
|
ProtocolCapabilities,
|
|
74
74
|
ProtocolDefinition,
|
|
75
75
|
ProtocolRegistry,
|
|
@@ -78,7 +78,7 @@ from oscura.config.protocol import (
|
|
|
78
78
|
load_protocol,
|
|
79
79
|
resolve_inheritance,
|
|
80
80
|
)
|
|
81
|
-
from oscura.config.schema import (
|
|
81
|
+
from oscura.core.config.schema import (
|
|
82
82
|
ConfigSchema,
|
|
83
83
|
SchemaRegistry,
|
|
84
84
|
ValidationError,
|
|
@@ -86,7 +86,13 @@ from oscura.config.schema import (
|
|
|
86
86
|
register_schema,
|
|
87
87
|
validate_against_schema,
|
|
88
88
|
)
|
|
89
|
-
|
|
89
|
+
|
|
90
|
+
# Alias for backward compatibility with oscura.core.config module
|
|
91
|
+
validate_config = validate_against_schema
|
|
92
|
+
|
|
93
|
+
# Legacy imports for backward compatibility
|
|
94
|
+
from oscura.core.config.legacy import SmartDefaults, _deep_merge
|
|
95
|
+
from oscura.core.config.settings import (
|
|
90
96
|
AnalysisSettings,
|
|
91
97
|
CLIDefaults,
|
|
92
98
|
OutputSettings,
|
|
@@ -97,7 +103,7 @@ from oscura.config.settings import (
|
|
|
97
103
|
save_settings,
|
|
98
104
|
set_settings,
|
|
99
105
|
)
|
|
100
|
-
from oscura.config.thresholds import (
|
|
106
|
+
from oscura.core.config.thresholds import (
|
|
101
107
|
LogicFamily,
|
|
102
108
|
ThresholdProfile,
|
|
103
109
|
ThresholdRegistry,
|
|
@@ -144,11 +150,13 @@ __all__ = [
|
|
|
144
150
|
"SchemaMigration",
|
|
145
151
|
"SchemaRegistry",
|
|
146
152
|
"Settings",
|
|
153
|
+
"SmartDefaults",
|
|
147
154
|
"ThresholdProfile",
|
|
148
155
|
"ThresholdRegistry",
|
|
149
156
|
"UserPreferences",
|
|
150
157
|
"ValidationError",
|
|
151
158
|
"VisualizationPreferences",
|
|
159
|
+
"_deep_merge",
|
|
152
160
|
"configure_from_environment",
|
|
153
161
|
"enable_auto_degrade",
|
|
154
162
|
"get_config_value",
|
|
@@ -188,4 +196,5 @@ __all__ = [
|
|
|
188
196
|
"set_memory_thresholds",
|
|
189
197
|
"set_settings",
|
|
190
198
|
"validate_against_schema",
|
|
199
|
+
"validate_config",
|
|
191
200
|
]
|
|
@@ -5,7 +5,7 @@ for injecting defaults into user configurations.
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Example:
|
|
8
|
-
>>> from oscura.config.defaults import inject_defaults
|
|
8
|
+
>>> from oscura.core.config.defaults import inject_defaults
|
|
9
9
|
>>> config = {"name": "test"}
|
|
10
10
|
>>> full_config = inject_defaults(config, "protocol")
|
|
11
11
|
"""
|
|
@@ -5,7 +5,7 @@ with support for schema validation, default injection, and path resolution.
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Example:
|
|
8
|
-
>>> from oscura.config.loader import load_config_file
|
|
8
|
+
>>> from oscura.core.config.loader import load_config_file
|
|
9
9
|
>>> config = load_config_file("pipeline.yaml", schema="pipeline")
|
|
10
10
|
"""
|
|
11
11
|
|
|
@@ -76,7 +76,7 @@ def load_config_file(
|
|
|
76
76
|
try:
|
|
77
77
|
config = _load_json(path)
|
|
78
78
|
except ConfigurationError:
|
|
79
|
-
raise ConfigurationError(
|
|
79
|
+
raise ConfigurationError(
|
|
80
80
|
f"Unsupported configuration format: {ext}",
|
|
81
81
|
config_key=str(path),
|
|
82
82
|
fix_hint="Use .yaml, .yml, or .json extension",
|
|
@@ -84,13 +84,13 @@ def load_config_file(
|
|
|
84
84
|
|
|
85
85
|
# Validate against schema if requested
|
|
86
86
|
if validate and schema is not None:
|
|
87
|
-
from oscura.config.schema import validate_against_schema
|
|
87
|
+
from oscura.core.config.schema import validate_against_schema
|
|
88
88
|
|
|
89
89
|
validate_against_schema(config, schema)
|
|
90
90
|
|
|
91
91
|
# Inject defaults if requested
|
|
92
92
|
if inject_defaults and schema is not None:
|
|
93
|
-
from oscura.config.defaults import inject_defaults as do_inject
|
|
93
|
+
from oscura.core.config.defaults import inject_defaults as do_inject
|
|
94
94
|
|
|
95
95
|
config = do_inject(config, schema)
|
|
96
96
|
|
|
@@ -215,7 +215,7 @@ def load_config(
|
|
|
215
215
|
>>> config = load_config() # Auto-find config
|
|
216
216
|
>>> config = load_config("~/.oscura/config.yaml")
|
|
217
217
|
"""
|
|
218
|
-
from oscura.config.defaults import DEFAULT_CONFIG, deep_merge
|
|
218
|
+
from oscura.core.config.defaults import DEFAULT_CONFIG, deep_merge
|
|
219
219
|
|
|
220
220
|
config: dict[str, Any] = {}
|
|
221
221
|
|
|
@@ -224,6 +224,8 @@ def load_config(
|
|
|
224
224
|
|
|
225
225
|
config = copy.deepcopy(DEFAULT_CONFIG)
|
|
226
226
|
|
|
227
|
+
# Search for config files if no explicit path provided
|
|
228
|
+
# use_defaults flag only controls whether to merge with DEFAULT_CONFIG
|
|
227
229
|
if config_path is None:
|
|
228
230
|
# Search standard locations
|
|
229
231
|
search_paths = [
|
|
@@ -4,7 +4,7 @@ This module provides global memory limit configuration and settings.
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
Example:
|
|
7
|
-
>>> from oscura.config.memory import set_memory_limit, get_memory_config
|
|
7
|
+
>>> from oscura.core.config.memory import set_memory_limit, get_memory_config
|
|
8
8
|
>>> set_memory_limit("4GB")
|
|
9
9
|
>>> config = get_memory_config()
|
|
10
10
|
>>> print(f"Max memory: {config.max_memory / 1e9:.1f} GB")
|
|
@@ -90,7 +90,7 @@ def set_memory_limit(limit: int | str | None) -> None:
|
|
|
90
90
|
Environment:
|
|
91
91
|
Can also be set via TK_MAX_MEMORY environment variable.
|
|
92
92
|
"""
|
|
93
|
-
global _global_config
|
|
93
|
+
global _global_config
|
|
94
94
|
|
|
95
95
|
if limit is None:
|
|
96
96
|
_global_config.max_memory = None
|
|
@@ -117,7 +117,7 @@ def set_memory_thresholds(
|
|
|
117
117
|
>>> set_memory_thresholds(warn_threshold=0.7, critical_threshold=0.9)
|
|
118
118
|
>>> set_memory_thresholds(critical_threshold=0.95) # Keep warn unchanged
|
|
119
119
|
"""
|
|
120
|
-
global _global_config
|
|
120
|
+
global _global_config
|
|
121
121
|
|
|
122
122
|
if warn_threshold is not None:
|
|
123
123
|
_global_config.warn_threshold = warn_threshold
|
|
@@ -139,7 +139,7 @@ def enable_auto_degrade(enabled: bool = True) -> None:
|
|
|
139
139
|
>>> enable_auto_degrade(True)
|
|
140
140
|
>>> # Operations will now auto-downsample if memory insufficient
|
|
141
141
|
"""
|
|
142
|
-
global _global_config
|
|
142
|
+
global _global_config
|
|
143
143
|
_global_config.auto_degrade = enabled
|
|
144
144
|
|
|
145
145
|
|
|
@@ -154,7 +154,7 @@ def set_memory_reserve(reserve: int | str) -> None:
|
|
|
154
154
|
>>> set_memory_reserve("1GB") # Reserve 1 GB for system
|
|
155
155
|
>>> set_memory_reserve(512 * 1024**2) # 512 MB
|
|
156
156
|
"""
|
|
157
|
-
global _global_config
|
|
157
|
+
global _global_config
|
|
158
158
|
|
|
159
159
|
if isinstance(reserve, str):
|
|
160
160
|
_global_config.memory_reserve = _parse_memory_string(reserve)
|
|
@@ -5,7 +5,7 @@ configuration files between schema versions while preserving user data.
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Example:
|
|
8
|
-
>>> from oscura.config.migration import migrate_config, register_migration
|
|
8
|
+
>>> from oscura.core.config.migration import migrate_config, register_migration
|
|
9
9
|
>>> # Register a migration function
|
|
10
10
|
>>> def migrate_1_0_to_1_1(config: dict) -> dict:
|
|
11
11
|
... config['new_field'] = 'default_value'
|
|
@@ -17,7 +17,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
17
17
|
|
|
18
18
|
import yaml
|
|
19
19
|
|
|
20
|
-
from oscura.config.schema import validate_against_schema
|
|
20
|
+
from oscura.core.config.schema import validate_against_schema
|
|
21
21
|
from oscura.core.exceptions import ConfigurationError
|
|
22
22
|
|
|
23
23
|
if TYPE_CHECKING:
|
|
@@ -690,7 +690,7 @@ def _substitute_variables(content: str, variables: dict[str, Any], max_depth: in
|
|
|
690
690
|
pattern = re.compile(r"\$\{(\w+)\}")
|
|
691
691
|
depth = 0
|
|
692
692
|
|
|
693
|
-
for depth in range(max_depth):
|
|
693
|
+
for depth in range(max_depth):
|
|
694
694
|
prev_content = content
|
|
695
695
|
substitutions_made = False
|
|
696
696
|
|
|
@@ -768,33 +768,99 @@ def resolve_includes(
|
|
|
768
768
|
if not pipeline.includes:
|
|
769
769
|
return pipeline
|
|
770
770
|
|
|
771
|
-
|
|
772
|
-
source_key
|
|
771
|
+
source_key = _get_source_key(pipeline)
|
|
772
|
+
_validate_circular_dependency(source_key, _visited)
|
|
773
|
+
_validate_depth_limit(_depth, max_depth, _visited, source_key)
|
|
773
774
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
775
|
+
if source_key:
|
|
776
|
+
_visited.add(source_key)
|
|
777
|
+
|
|
778
|
+
merged_steps = _merge_included_pipelines(
|
|
779
|
+
pipeline, base_path, max_depth, namespace_isolation, _visited, _depth
|
|
780
|
+
)
|
|
781
|
+
merged_steps.extend(pipeline.steps)
|
|
782
|
+
|
|
783
|
+
return _build_resolved_pipeline(pipeline, merged_steps)
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
def _get_source_key(pipeline: PipelineDefinition) -> str | None:
|
|
787
|
+
"""Get normalized source file path for pipeline.
|
|
788
|
+
|
|
789
|
+
Args:
|
|
790
|
+
pipeline: Pipeline definition.
|
|
791
|
+
|
|
792
|
+
Returns:
|
|
793
|
+
Normalized source path or None.
|
|
794
|
+
"""
|
|
795
|
+
return str(Path(pipeline.source_file).resolve()) if pipeline.source_file else None
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
def _validate_circular_dependency(source_key: str | None, visited: set[str]) -> None:
|
|
799
|
+
"""Validate no circular dependency.
|
|
800
|
+
|
|
801
|
+
Args:
|
|
802
|
+
source_key: Source file path.
|
|
803
|
+
visited: Set of visited paths.
|
|
804
|
+
|
|
805
|
+
Raises:
|
|
806
|
+
PipelineValidationError: If circular dependency detected.
|
|
807
|
+
"""
|
|
808
|
+
if source_key and source_key in visited:
|
|
809
|
+
cycle_list = [*list(visited), source_key]
|
|
777
810
|
cycle = " → ".join([Path(p).name for p in cycle_list])
|
|
778
811
|
raise PipelineValidationError(
|
|
779
812
|
f"Circular pipeline include detected: {cycle}",
|
|
780
813
|
suggestion=f"Remove circular dependency from {Path(source_key).name}",
|
|
781
814
|
)
|
|
782
815
|
|
|
783
|
-
|
|
784
|
-
|
|
816
|
+
|
|
817
|
+
def _validate_depth_limit(
|
|
818
|
+
depth: int, max_depth: int, visited: set[str], source_key: str | None
|
|
819
|
+
) -> None:
|
|
820
|
+
"""Validate include depth within limit.
|
|
821
|
+
|
|
822
|
+
Args:
|
|
823
|
+
depth: Current depth.
|
|
824
|
+
max_depth: Maximum allowed depth.
|
|
825
|
+
visited: Set of visited paths.
|
|
826
|
+
source_key: Current source key.
|
|
827
|
+
|
|
828
|
+
Raises:
|
|
829
|
+
PipelineValidationError: If depth exceeded.
|
|
830
|
+
"""
|
|
831
|
+
if depth >= max_depth:
|
|
785
832
|
chain = " → ".join(
|
|
786
|
-
[Path(p).name for p in
|
|
833
|
+
[Path(p).name for p in visited] + [Path(source_key).name if source_key else "?"]
|
|
787
834
|
)
|
|
788
835
|
raise PipelineValidationError(
|
|
789
836
|
f"Pipeline include depth exceeded maximum of {max_depth}",
|
|
790
837
|
suggestion=f"Reduce nesting. Current chain: {chain}",
|
|
791
838
|
)
|
|
792
839
|
|
|
793
|
-
if source_key:
|
|
794
|
-
_visited.add(source_key)
|
|
795
840
|
|
|
796
|
-
|
|
841
|
+
def _merge_included_pipelines(
|
|
842
|
+
pipeline: PipelineDefinition,
|
|
843
|
+
base_path: Path,
|
|
844
|
+
max_depth: int,
|
|
845
|
+
namespace_isolation: bool,
|
|
846
|
+
visited: set[str],
|
|
847
|
+
depth: int,
|
|
848
|
+
) -> list[PipelineStep]:
|
|
849
|
+
"""Merge all included pipelines.
|
|
850
|
+
|
|
851
|
+
Args:
|
|
852
|
+
pipeline: Main pipeline.
|
|
853
|
+
base_path: Base path for includes.
|
|
854
|
+
max_depth: Maximum depth.
|
|
855
|
+
namespace_isolation: Enable namespacing.
|
|
856
|
+
visited: Visited paths.
|
|
857
|
+
depth: Current depth.
|
|
858
|
+
|
|
859
|
+
Returns:
|
|
860
|
+
List of merged steps.
|
|
861
|
+
"""
|
|
797
862
|
merged_steps = []
|
|
863
|
+
|
|
798
864
|
for include_path in pipeline.includes:
|
|
799
865
|
include_full = base_path / include_path
|
|
800
866
|
|
|
@@ -803,22 +869,18 @@ def resolve_includes(
|
|
|
803
869
|
continue
|
|
804
870
|
|
|
805
871
|
try:
|
|
806
|
-
# Load included pipeline
|
|
807
872
|
included = load_pipeline(include_full, pipeline.variables)
|
|
808
|
-
|
|
809
|
-
# Recursively resolve nested includes
|
|
810
873
|
resolved = resolve_includes(
|
|
811
874
|
included,
|
|
812
875
|
include_full.parent,
|
|
813
876
|
max_depth=max_depth,
|
|
814
877
|
namespace_isolation=namespace_isolation,
|
|
815
|
-
_visited=
|
|
816
|
-
_depth=
|
|
878
|
+
_visited=visited.copy(),
|
|
879
|
+
_depth=depth + 1,
|
|
817
880
|
)
|
|
818
881
|
|
|
819
|
-
# Apply namespace isolation
|
|
820
882
|
if namespace_isolation:
|
|
821
|
-
namespace = Path(include_path).stem
|
|
883
|
+
namespace = Path(include_path).stem
|
|
822
884
|
namespaced_steps = _apply_namespace(resolved.steps, namespace)
|
|
823
885
|
merged_steps.extend(namespaced_steps)
|
|
824
886
|
logger.debug(f"Included pipeline '{namespace}' with {len(namespaced_steps)} steps")
|
|
@@ -832,9 +894,21 @@ def resolve_includes(
|
|
|
832
894
|
suggestion=f"Check file exists and is valid YAML: {e}",
|
|
833
895
|
) from e
|
|
834
896
|
|
|
835
|
-
|
|
836
|
-
|
|
897
|
+
return merged_steps
|
|
898
|
+
|
|
837
899
|
|
|
900
|
+
def _build_resolved_pipeline(
|
|
901
|
+
pipeline: PipelineDefinition, merged_steps: list[PipelineStep]
|
|
902
|
+
) -> PipelineDefinition:
|
|
903
|
+
"""Build resolved pipeline with merged steps.
|
|
904
|
+
|
|
905
|
+
Args:
|
|
906
|
+
pipeline: Original pipeline.
|
|
907
|
+
merged_steps: Merged steps list.
|
|
908
|
+
|
|
909
|
+
Returns:
|
|
910
|
+
New PipelineDefinition with resolved includes.
|
|
911
|
+
"""
|
|
838
912
|
return PipelineDefinition(
|
|
839
913
|
name=pipeline.name,
|
|
840
914
|
version=pipeline.version,
|
|
@@ -842,7 +916,7 @@ def resolve_includes(
|
|
|
842
916
|
steps=merged_steps,
|
|
843
917
|
parallel_groups=pipeline.parallel_groups,
|
|
844
918
|
variables=pipeline.variables,
|
|
845
|
-
includes=[],
|
|
919
|
+
includes=[],
|
|
846
920
|
source_file=pipeline.source_file,
|
|
847
921
|
)
|
|
848
922
|
|
|
@@ -865,6 +939,8 @@ def _apply_namespace(steps: list[PipelineStep], namespace: str) -> list[Pipeline
|
|
|
865
939
|
namespaced = []
|
|
866
940
|
for step in steps:
|
|
867
941
|
# Create a copy with namespaced name
|
|
942
|
+
# NECESSARY COPIES: All .copy() calls create isolated params/conditions.
|
|
943
|
+
# This prevents parameter mutations from affecting original step definitions.
|
|
868
944
|
namespaced_step = PipelineStep(
|
|
869
945
|
name=f"{namespace}.{step.name}",
|
|
870
946
|
type=step.type,
|
|
@@ -15,7 +15,7 @@ from typing import Any
|
|
|
15
15
|
|
|
16
16
|
import yaml
|
|
17
17
|
|
|
18
|
-
from oscura.config.schema import validate_against_schema
|
|
18
|
+
from oscura.core.config.schema import validate_against_schema
|
|
19
19
|
from oscura.core.exceptions import ConfigurationError
|
|
20
20
|
|
|
21
21
|
logger = logging.getLogger(__name__)
|
|
@@ -18,7 +18,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
18
18
|
|
|
19
19
|
import yaml
|
|
20
20
|
|
|
21
|
-
from oscura.config.schema import validate_against_schema
|
|
21
|
+
from oscura.core.config.schema import validate_against_schema
|
|
22
22
|
from oscura.core.exceptions import ConfigurationError
|
|
23
23
|
|
|
24
24
|
if TYPE_CHECKING:
|
|
@@ -644,7 +644,7 @@ class ProtocolWatcher:
|
|
|
644
644
|
continue
|
|
645
645
|
|
|
646
646
|
try:
|
|
647
|
-
mtime = os.path.getmtime(file_path)
|
|
647
|
+
mtime = os.path.getmtime(file_path)
|
|
648
648
|
except OSError:
|
|
649
649
|
continue
|
|
650
650
|
|
|
@@ -674,7 +674,7 @@ class ProtocolWatcher:
|
|
|
674
674
|
for file_path in self.directory.glob("**/*.yaml"):
|
|
675
675
|
if file_path.is_file():
|
|
676
676
|
with contextlib.suppress(OSError):
|
|
677
|
-
self._file_mtimes[str(file_path)] = os.path.getmtime(file_path)
|
|
677
|
+
self._file_mtimes[str(file_path)] = os.path.getmtime(file_path)
|
|
678
678
|
|
|
679
679
|
def _notify(self, protocol: ProtocolDefinition) -> None:
|
|
680
680
|
"""Notify callbacks of protocol change."""
|