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/session/annotations.py
DELETED
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
"""Trace annotation support.
|
|
2
|
-
|
|
3
|
-
This module provides annotation capabilities for marking points of interest
|
|
4
|
-
in signal traces.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Example:
|
|
8
|
-
>>> layer = AnnotationLayer("Debug Markers")
|
|
9
|
-
>>> layer.add(Annotation(time=1.5e-6, text="Glitch detected"))
|
|
10
|
-
>>> layer.add(Annotation(time_range=(2e-6, 3e-6), text="Data packet"))
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
from __future__ import annotations
|
|
14
|
-
|
|
15
|
-
from dataclasses import dataclass, field
|
|
16
|
-
from datetime import datetime
|
|
17
|
-
from enum import Enum
|
|
18
|
-
from typing import Any
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class AnnotationType(Enum):
|
|
22
|
-
"""Types of annotations."""
|
|
23
|
-
|
|
24
|
-
POINT = "point" # Single time point
|
|
25
|
-
RANGE = "range" # Time range
|
|
26
|
-
VERTICAL = "vertical" # Vertical line
|
|
27
|
-
HORIZONTAL = "horizontal" # Horizontal line
|
|
28
|
-
REGION = "region" # 2D region (time + amplitude)
|
|
29
|
-
TEXT = "text" # Free-floating text
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@dataclass
|
|
33
|
-
class Annotation:
|
|
34
|
-
"""Single annotation on a trace.
|
|
35
|
-
|
|
36
|
-
Attributes:
|
|
37
|
-
text: Annotation text/label
|
|
38
|
-
time: Time point (for point annotations)
|
|
39
|
-
time_range: (start, end) time range
|
|
40
|
-
amplitude: Amplitude value (for horizontal lines)
|
|
41
|
-
amplitude_range: (min, max) amplitude range
|
|
42
|
-
annotation_type: Type of annotation
|
|
43
|
-
color: Display color (hex or name)
|
|
44
|
-
style: Line style ('solid', 'dashed', 'dotted')
|
|
45
|
-
visible: Whether annotation is visible
|
|
46
|
-
created_at: Creation timestamp
|
|
47
|
-
metadata: Additional metadata
|
|
48
|
-
"""
|
|
49
|
-
|
|
50
|
-
text: str
|
|
51
|
-
time: float | None = None
|
|
52
|
-
time_range: tuple[float, float] | None = None
|
|
53
|
-
amplitude: float | None = None
|
|
54
|
-
amplitude_range: tuple[float, float] | None = None
|
|
55
|
-
annotation_type: AnnotationType = AnnotationType.POINT
|
|
56
|
-
color: str = "#FF6B6B"
|
|
57
|
-
style: str = "solid"
|
|
58
|
-
visible: bool = True
|
|
59
|
-
created_at: datetime = field(default_factory=datetime.now)
|
|
60
|
-
metadata: dict[str, Any] = field(default_factory=dict)
|
|
61
|
-
|
|
62
|
-
def __post_init__(self) -> None:
|
|
63
|
-
"""Infer annotation type from provided parameters."""
|
|
64
|
-
if self.annotation_type == AnnotationType.POINT:
|
|
65
|
-
if self.time_range is not None:
|
|
66
|
-
self.annotation_type = AnnotationType.RANGE
|
|
67
|
-
elif self.amplitude is not None and self.time is None:
|
|
68
|
-
self.annotation_type = AnnotationType.HORIZONTAL
|
|
69
|
-
elif self.amplitude_range is not None and self.time_range is not None:
|
|
70
|
-
self.annotation_type = AnnotationType.REGION # type: ignore[unreachable]
|
|
71
|
-
|
|
72
|
-
@property
|
|
73
|
-
def start_time(self) -> float | None:
|
|
74
|
-
"""Get start time for range annotations."""
|
|
75
|
-
if self.time_range:
|
|
76
|
-
return self.time_range[0]
|
|
77
|
-
return self.time
|
|
78
|
-
|
|
79
|
-
@property
|
|
80
|
-
def end_time(self) -> float | None:
|
|
81
|
-
"""Get end time for range annotations."""
|
|
82
|
-
if self.time_range:
|
|
83
|
-
return self.time_range[1]
|
|
84
|
-
return self.time
|
|
85
|
-
|
|
86
|
-
def to_dict(self) -> dict[str, Any]:
|
|
87
|
-
"""Convert to dictionary for serialization."""
|
|
88
|
-
return {
|
|
89
|
-
"text": self.text,
|
|
90
|
-
"time": self.time,
|
|
91
|
-
"time_range": self.time_range,
|
|
92
|
-
"amplitude": self.amplitude,
|
|
93
|
-
"amplitude_range": self.amplitude_range,
|
|
94
|
-
"annotation_type": self.annotation_type.value,
|
|
95
|
-
"color": self.color,
|
|
96
|
-
"style": self.style,
|
|
97
|
-
"visible": self.visible,
|
|
98
|
-
"created_at": self.created_at.isoformat(),
|
|
99
|
-
"metadata": self.metadata,
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
@classmethod
|
|
103
|
-
def from_dict(cls, data: dict[str, Any]) -> Annotation:
|
|
104
|
-
"""Create from dictionary."""
|
|
105
|
-
data = data.copy()
|
|
106
|
-
data["annotation_type"] = AnnotationType(data.get("annotation_type", "point"))
|
|
107
|
-
if "created_at" in data and isinstance(data["created_at"], str):
|
|
108
|
-
data["created_at"] = datetime.fromisoformat(data["created_at"])
|
|
109
|
-
return cls(**data)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
@dataclass
|
|
113
|
-
class AnnotationLayer:
|
|
114
|
-
"""Collection of related annotations.
|
|
115
|
-
|
|
116
|
-
Attributes:
|
|
117
|
-
name: Layer name
|
|
118
|
-
annotations: List of annotations
|
|
119
|
-
visible: Whether layer is visible
|
|
120
|
-
locked: Whether layer is locked (read-only)
|
|
121
|
-
color: Default color for new annotations
|
|
122
|
-
description: Layer description
|
|
123
|
-
"""
|
|
124
|
-
|
|
125
|
-
name: str
|
|
126
|
-
annotations: list[Annotation] = field(default_factory=list)
|
|
127
|
-
visible: bool = True
|
|
128
|
-
locked: bool = False
|
|
129
|
-
color: str = "#FF6B6B"
|
|
130
|
-
description: str = ""
|
|
131
|
-
|
|
132
|
-
def add(
|
|
133
|
-
self,
|
|
134
|
-
annotation: Annotation | None = None,
|
|
135
|
-
*,
|
|
136
|
-
text: str = "",
|
|
137
|
-
time: float | None = None,
|
|
138
|
-
time_range: tuple[float, float] | None = None,
|
|
139
|
-
**kwargs: Any,
|
|
140
|
-
) -> Annotation:
|
|
141
|
-
"""Add annotation to layer.
|
|
142
|
-
|
|
143
|
-
Args:
|
|
144
|
-
annotation: Pre-built Annotation object.
|
|
145
|
-
text: Annotation text (if not using pre-built).
|
|
146
|
-
time: Time point.
|
|
147
|
-
time_range: Time range.
|
|
148
|
-
**kwargs: Additional Annotation parameters.
|
|
149
|
-
|
|
150
|
-
Returns:
|
|
151
|
-
Added annotation.
|
|
152
|
-
|
|
153
|
-
Raises:
|
|
154
|
-
ValueError: If layer is locked.
|
|
155
|
-
"""
|
|
156
|
-
if self.locked:
|
|
157
|
-
raise ValueError(f"Layer '{self.name}' is locked")
|
|
158
|
-
|
|
159
|
-
if annotation is None:
|
|
160
|
-
annotation = Annotation(
|
|
161
|
-
text=text,
|
|
162
|
-
time=time,
|
|
163
|
-
time_range=time_range,
|
|
164
|
-
color=kwargs.pop("color", self.color),
|
|
165
|
-
**kwargs,
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
self.annotations.append(annotation)
|
|
169
|
-
return annotation
|
|
170
|
-
|
|
171
|
-
def remove(self, annotation: Annotation) -> bool:
|
|
172
|
-
"""Remove annotation from layer.
|
|
173
|
-
|
|
174
|
-
Args:
|
|
175
|
-
annotation: Annotation to remove.
|
|
176
|
-
|
|
177
|
-
Returns:
|
|
178
|
-
True if removed, False if not found.
|
|
179
|
-
|
|
180
|
-
Raises:
|
|
181
|
-
ValueError: If layer is locked.
|
|
182
|
-
"""
|
|
183
|
-
if self.locked:
|
|
184
|
-
raise ValueError(f"Layer '{self.name}' is locked")
|
|
185
|
-
|
|
186
|
-
try:
|
|
187
|
-
self.annotations.remove(annotation)
|
|
188
|
-
return True
|
|
189
|
-
except ValueError:
|
|
190
|
-
return False
|
|
191
|
-
|
|
192
|
-
def find_at_time(
|
|
193
|
-
self,
|
|
194
|
-
time: float,
|
|
195
|
-
tolerance: float = 0.0,
|
|
196
|
-
) -> list[Annotation]:
|
|
197
|
-
"""Find annotations at or near a specific time.
|
|
198
|
-
|
|
199
|
-
Args:
|
|
200
|
-
time: Time to search.
|
|
201
|
-
tolerance: Time tolerance for matching.
|
|
202
|
-
|
|
203
|
-
Returns:
|
|
204
|
-
List of matching annotations.
|
|
205
|
-
"""
|
|
206
|
-
matches = []
|
|
207
|
-
for ann in self.annotations:
|
|
208
|
-
if ann.time is not None:
|
|
209
|
-
if abs(ann.time - time) <= tolerance:
|
|
210
|
-
matches.append(ann)
|
|
211
|
-
elif ann.time_range is not None and (
|
|
212
|
-
ann.time_range[0] - tolerance <= time <= ann.time_range[1] + tolerance
|
|
213
|
-
):
|
|
214
|
-
matches.append(ann)
|
|
215
|
-
return matches
|
|
216
|
-
|
|
217
|
-
def find_in_range(
|
|
218
|
-
self,
|
|
219
|
-
start_time: float,
|
|
220
|
-
end_time: float,
|
|
221
|
-
) -> list[Annotation]:
|
|
222
|
-
"""Find annotations within a time range.
|
|
223
|
-
|
|
224
|
-
Args:
|
|
225
|
-
start_time: Range start.
|
|
226
|
-
end_time: Range end.
|
|
227
|
-
|
|
228
|
-
Returns:
|
|
229
|
-
List of annotations within range.
|
|
230
|
-
"""
|
|
231
|
-
matches = []
|
|
232
|
-
for ann in self.annotations:
|
|
233
|
-
ann_start = ann.start_time
|
|
234
|
-
ann_end = ann.end_time
|
|
235
|
-
|
|
236
|
-
if ann_start is not None and (
|
|
237
|
-
start_time <= ann_start <= end_time
|
|
238
|
-
or (ann_end is not None and ann_start <= end_time and ann_end >= start_time)
|
|
239
|
-
):
|
|
240
|
-
matches.append(ann)
|
|
241
|
-
|
|
242
|
-
return matches
|
|
243
|
-
|
|
244
|
-
def clear(self) -> int:
|
|
245
|
-
"""Remove all annotations.
|
|
246
|
-
|
|
247
|
-
Returns:
|
|
248
|
-
Number of annotations removed.
|
|
249
|
-
|
|
250
|
-
Raises:
|
|
251
|
-
ValueError: If layer is locked.
|
|
252
|
-
"""
|
|
253
|
-
if self.locked:
|
|
254
|
-
raise ValueError(f"Layer '{self.name}' is locked")
|
|
255
|
-
|
|
256
|
-
count = len(self.annotations)
|
|
257
|
-
self.annotations.clear()
|
|
258
|
-
return count
|
|
259
|
-
|
|
260
|
-
def to_dict(self) -> dict[str, Any]:
|
|
261
|
-
"""Convert to dictionary for serialization."""
|
|
262
|
-
return {
|
|
263
|
-
"name": self.name,
|
|
264
|
-
"annotations": [a.to_dict() for a in self.annotations],
|
|
265
|
-
"visible": self.visible,
|
|
266
|
-
"locked": self.locked,
|
|
267
|
-
"color": self.color,
|
|
268
|
-
"description": self.description,
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
@classmethod
|
|
272
|
-
def from_dict(cls, data: dict[str, Any]) -> AnnotationLayer:
|
|
273
|
-
"""Create from dictionary."""
|
|
274
|
-
annotations = [Annotation.from_dict(a) for a in data.get("annotations", [])]
|
|
275
|
-
return cls(
|
|
276
|
-
name=data["name"],
|
|
277
|
-
annotations=annotations,
|
|
278
|
-
visible=data.get("visible", True),
|
|
279
|
-
locked=data.get("locked", False),
|
|
280
|
-
color=data.get("color", "#FF6B6B"),
|
|
281
|
-
description=data.get("description", ""),
|
|
282
|
-
)
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
__all__ = [
|
|
286
|
-
"Annotation",
|
|
287
|
-
"AnnotationLayer",
|
|
288
|
-
"AnnotationType",
|
|
289
|
-
]
|
oscura/session/history.py
DELETED
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
"""Operation history tracking.
|
|
2
|
-
|
|
3
|
-
This module provides operation history tracking for analysis sessions.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
Example:
|
|
7
|
-
>>> history = OperationHistory()
|
|
8
|
-
>>> history.record('load', {'file': 'capture.wfm'})
|
|
9
|
-
>>> history.record('measure_rise_time', {'result': 1.5e-9})
|
|
10
|
-
>>> print(history.to_script())
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
from __future__ import annotations
|
|
14
|
-
|
|
15
|
-
from dataclasses import dataclass, field
|
|
16
|
-
from datetime import datetime
|
|
17
|
-
from typing import Any
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
@dataclass
|
|
21
|
-
class HistoryEntry:
|
|
22
|
-
"""Single history entry recording an operation.
|
|
23
|
-
|
|
24
|
-
Attributes:
|
|
25
|
-
operation: Operation name (function/method called)
|
|
26
|
-
parameters: Input parameters
|
|
27
|
-
result: Operation result (summary)
|
|
28
|
-
timestamp: When operation was performed
|
|
29
|
-
duration_ms: Operation duration in milliseconds
|
|
30
|
-
success: Whether operation succeeded
|
|
31
|
-
error_message: Error message if failed
|
|
32
|
-
metadata: Additional metadata
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
|
-
operation: str
|
|
36
|
-
parameters: dict[str, Any] = field(default_factory=dict)
|
|
37
|
-
result: Any = None
|
|
38
|
-
timestamp: datetime = field(default_factory=datetime.now)
|
|
39
|
-
duration_ms: float = 0.0
|
|
40
|
-
success: bool = True
|
|
41
|
-
error_message: str | None = None
|
|
42
|
-
metadata: dict[str, Any] = field(default_factory=dict)
|
|
43
|
-
|
|
44
|
-
def to_dict(self) -> dict[str, Any]:
|
|
45
|
-
"""Convert to dictionary for serialization."""
|
|
46
|
-
return {
|
|
47
|
-
"operation": self.operation,
|
|
48
|
-
"parameters": self.parameters,
|
|
49
|
-
"result": self._serialize_result(self.result),
|
|
50
|
-
"timestamp": self.timestamp.isoformat(),
|
|
51
|
-
"duration_ms": self.duration_ms,
|
|
52
|
-
"success": self.success,
|
|
53
|
-
"error_message": self.error_message,
|
|
54
|
-
"metadata": self.metadata,
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
@staticmethod
|
|
58
|
-
def _serialize_result(result: Any) -> Any:
|
|
59
|
-
"""Serialize result for JSON storage."""
|
|
60
|
-
if result is None:
|
|
61
|
-
return None
|
|
62
|
-
if isinstance(result, str | int | float | bool):
|
|
63
|
-
return result
|
|
64
|
-
if isinstance(result, dict):
|
|
65
|
-
return {k: HistoryEntry._serialize_result(v) for k, v in result.items()}
|
|
66
|
-
if isinstance(result, list | tuple):
|
|
67
|
-
return [HistoryEntry._serialize_result(v) for v in result]
|
|
68
|
-
# For complex objects, store string representation
|
|
69
|
-
return str(result)
|
|
70
|
-
|
|
71
|
-
@classmethod
|
|
72
|
-
def from_dict(cls, data: dict[str, Any]) -> HistoryEntry:
|
|
73
|
-
"""Create from dictionary."""
|
|
74
|
-
data = data.copy()
|
|
75
|
-
if "timestamp" in data and isinstance(data["timestamp"], str):
|
|
76
|
-
data["timestamp"] = datetime.fromisoformat(data["timestamp"])
|
|
77
|
-
return cls(**data)
|
|
78
|
-
|
|
79
|
-
def to_code(self) -> str:
|
|
80
|
-
"""Generate Python code to replay this operation.
|
|
81
|
-
|
|
82
|
-
Returns:
|
|
83
|
-
Python code string.
|
|
84
|
-
"""
|
|
85
|
-
# Format parameters
|
|
86
|
-
params = []
|
|
87
|
-
for k, v in self.parameters.items():
|
|
88
|
-
if isinstance(v, str):
|
|
89
|
-
params.append(f'{k}="{v}"')
|
|
90
|
-
else:
|
|
91
|
-
params.append(f"{k}={v!r}")
|
|
92
|
-
|
|
93
|
-
param_str = ", ".join(params)
|
|
94
|
-
return f"osc.{self.operation}({param_str})"
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
@dataclass
|
|
98
|
-
class OperationHistory:
|
|
99
|
-
"""History of analysis operations.
|
|
100
|
-
|
|
101
|
-
Supports recording, replaying, and exporting operation history.
|
|
102
|
-
|
|
103
|
-
Attributes:
|
|
104
|
-
entries: List of history entries
|
|
105
|
-
max_entries: Maximum entries to keep (0 = unlimited)
|
|
106
|
-
auto_record: Whether to automatically record operations
|
|
107
|
-
"""
|
|
108
|
-
|
|
109
|
-
entries: list[HistoryEntry] = field(default_factory=list)
|
|
110
|
-
max_entries: int = 0
|
|
111
|
-
auto_record: bool = True
|
|
112
|
-
_current_session_start: datetime = field(default_factory=datetime.now)
|
|
113
|
-
|
|
114
|
-
def record(
|
|
115
|
-
self,
|
|
116
|
-
operation: str,
|
|
117
|
-
parameters: dict[str, Any] | None = None,
|
|
118
|
-
result: Any = None,
|
|
119
|
-
duration_ms: float = 0.0,
|
|
120
|
-
success: bool = True,
|
|
121
|
-
error_message: str | None = None,
|
|
122
|
-
**metadata: Any,
|
|
123
|
-
) -> HistoryEntry:
|
|
124
|
-
"""Record an operation.
|
|
125
|
-
|
|
126
|
-
Args:
|
|
127
|
-
operation: Operation name.
|
|
128
|
-
parameters: Input parameters.
|
|
129
|
-
result: Operation result.
|
|
130
|
-
duration_ms: Duration in milliseconds.
|
|
131
|
-
success: Whether operation succeeded.
|
|
132
|
-
error_message: Error message if failed.
|
|
133
|
-
**metadata: Additional metadata.
|
|
134
|
-
|
|
135
|
-
Returns:
|
|
136
|
-
Created history entry.
|
|
137
|
-
"""
|
|
138
|
-
entry = HistoryEntry(
|
|
139
|
-
operation=operation,
|
|
140
|
-
parameters=parameters or {},
|
|
141
|
-
result=result,
|
|
142
|
-
duration_ms=duration_ms,
|
|
143
|
-
success=success,
|
|
144
|
-
error_message=error_message,
|
|
145
|
-
metadata=metadata,
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
self.entries.append(entry)
|
|
149
|
-
|
|
150
|
-
# Trim if exceeded max entries
|
|
151
|
-
if self.max_entries > 0 and len(self.entries) > self.max_entries:
|
|
152
|
-
self.entries = self.entries[-self.max_entries :]
|
|
153
|
-
|
|
154
|
-
return entry
|
|
155
|
-
|
|
156
|
-
def undo(self) -> HistoryEntry | None:
|
|
157
|
-
"""Remove and return the last entry.
|
|
158
|
-
|
|
159
|
-
Returns:
|
|
160
|
-
Removed entry, or None if empty.
|
|
161
|
-
"""
|
|
162
|
-
if self.entries:
|
|
163
|
-
return self.entries.pop()
|
|
164
|
-
return None
|
|
165
|
-
|
|
166
|
-
def clear(self) -> int:
|
|
167
|
-
"""Clear all history.
|
|
168
|
-
|
|
169
|
-
Returns:
|
|
170
|
-
Number of entries cleared.
|
|
171
|
-
"""
|
|
172
|
-
count = len(self.entries)
|
|
173
|
-
self.entries.clear()
|
|
174
|
-
return count
|
|
175
|
-
|
|
176
|
-
def find(
|
|
177
|
-
self,
|
|
178
|
-
operation: str | None = None,
|
|
179
|
-
success_only: bool = False,
|
|
180
|
-
since: datetime | None = None,
|
|
181
|
-
) -> list[HistoryEntry]:
|
|
182
|
-
"""Find entries matching criteria.
|
|
183
|
-
|
|
184
|
-
Args:
|
|
185
|
-
operation: Filter by operation name.
|
|
186
|
-
success_only: Only return successful operations.
|
|
187
|
-
since: Only return entries after this time.
|
|
188
|
-
|
|
189
|
-
Returns:
|
|
190
|
-
Matching entries.
|
|
191
|
-
"""
|
|
192
|
-
results = []
|
|
193
|
-
for entry in self.entries:
|
|
194
|
-
if operation and entry.operation != operation:
|
|
195
|
-
continue
|
|
196
|
-
if success_only and not entry.success:
|
|
197
|
-
continue
|
|
198
|
-
if since and entry.timestamp < since:
|
|
199
|
-
continue
|
|
200
|
-
results.append(entry)
|
|
201
|
-
return results
|
|
202
|
-
|
|
203
|
-
def to_script(
|
|
204
|
-
self,
|
|
205
|
-
include_imports: bool = True,
|
|
206
|
-
include_comments: bool = True,
|
|
207
|
-
) -> str:
|
|
208
|
-
"""Export history as Python script.
|
|
209
|
-
|
|
210
|
-
Args:
|
|
211
|
-
include_imports: Include import statements.
|
|
212
|
-
include_comments: Include timestamp comments.
|
|
213
|
-
|
|
214
|
-
Returns:
|
|
215
|
-
Python script string.
|
|
216
|
-
|
|
217
|
-
Example:
|
|
218
|
-
>>> script = history.to_script()
|
|
219
|
-
>>> print(script)
|
|
220
|
-
# Generated by Oscura
|
|
221
|
-
import oscura as osc
|
|
222
|
-
osc.load("capture.wfm")
|
|
223
|
-
result = osc.measure_rise_time()
|
|
224
|
-
"""
|
|
225
|
-
lines = []
|
|
226
|
-
|
|
227
|
-
if include_imports:
|
|
228
|
-
lines.extend(
|
|
229
|
-
[
|
|
230
|
-
"#!/usr/bin/env python3",
|
|
231
|
-
'"""Oscura analysis script.',
|
|
232
|
-
"",
|
|
233
|
-
f"Generated: {datetime.now().isoformat()}",
|
|
234
|
-
'"""',
|
|
235
|
-
"",
|
|
236
|
-
"import oscura as osc",
|
|
237
|
-
"",
|
|
238
|
-
]
|
|
239
|
-
)
|
|
240
|
-
|
|
241
|
-
for entry in self.entries:
|
|
242
|
-
if not entry.success:
|
|
243
|
-
continue
|
|
244
|
-
|
|
245
|
-
if include_comments:
|
|
246
|
-
lines.append(f"# {entry.timestamp.strftime('%H:%M:%S')} - {entry.operation}")
|
|
247
|
-
|
|
248
|
-
lines.append(entry.to_code())
|
|
249
|
-
lines.append("")
|
|
250
|
-
|
|
251
|
-
return "\n".join(lines)
|
|
252
|
-
|
|
253
|
-
def summary(self) -> dict[str, Any]:
|
|
254
|
-
"""Get history summary statistics.
|
|
255
|
-
|
|
256
|
-
Returns:
|
|
257
|
-
Dictionary with summary statistics.
|
|
258
|
-
"""
|
|
259
|
-
if not self.entries:
|
|
260
|
-
return {
|
|
261
|
-
"total_operations": 0,
|
|
262
|
-
"successful": 0,
|
|
263
|
-
"failed": 0,
|
|
264
|
-
"total_duration_ms": 0,
|
|
265
|
-
"unique_operations": 0,
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
successful = sum(1 for e in self.entries if e.success)
|
|
269
|
-
failed = len(self.entries) - successful
|
|
270
|
-
total_duration = sum(e.duration_ms for e in self.entries)
|
|
271
|
-
unique_ops = len({e.operation for e in self.entries})
|
|
272
|
-
|
|
273
|
-
# Operation frequency
|
|
274
|
-
op_counts: dict[str, int] = {}
|
|
275
|
-
for entry in self.entries:
|
|
276
|
-
op_counts[entry.operation] = op_counts.get(entry.operation, 0) + 1
|
|
277
|
-
|
|
278
|
-
return {
|
|
279
|
-
"total_operations": len(self.entries),
|
|
280
|
-
"successful": successful,
|
|
281
|
-
"failed": failed,
|
|
282
|
-
"total_duration_ms": total_duration,
|
|
283
|
-
"unique_operations": unique_ops,
|
|
284
|
-
"operation_counts": op_counts,
|
|
285
|
-
"session_start": self._current_session_start.isoformat(),
|
|
286
|
-
"last_operation": self.entries[-1].timestamp.isoformat() if self.entries else None,
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
def to_dict(self) -> dict[str, Any]:
|
|
290
|
-
"""Convert to dictionary for serialization."""
|
|
291
|
-
return {
|
|
292
|
-
"entries": [e.to_dict() for e in self.entries],
|
|
293
|
-
"max_entries": self.max_entries,
|
|
294
|
-
"session_start": self._current_session_start.isoformat(),
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
@classmethod
|
|
298
|
-
def from_dict(cls, data: dict[str, Any]) -> OperationHistory:
|
|
299
|
-
"""Create from dictionary."""
|
|
300
|
-
entries = [HistoryEntry.from_dict(e) for e in data.get("entries", [])]
|
|
301
|
-
history = cls(
|
|
302
|
-
entries=entries,
|
|
303
|
-
max_entries=data.get("max_entries", 0),
|
|
304
|
-
)
|
|
305
|
-
if "session_start" in data:
|
|
306
|
-
history._current_session_start = datetime.fromisoformat(data["session_start"])
|
|
307
|
-
return history
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
__all__ = [
|
|
311
|
-
"HistoryEntry",
|
|
312
|
-
"OperationHistory",
|
|
313
|
-
]
|