oscura 0.0.1__py3-none-any.whl → 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscura/__init__.py +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.1.dist-info/METADATA +300 -0
- oscura-0.1.1.dist-info/RECORD +463 -0
- oscura-0.1.1.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/WHEEL +0 -0
oscura/core/progress.py
ADDED
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
"""Progress tracking and cancellation support for Oscura operations.
|
|
2
|
+
|
|
3
|
+
This module provides progress callbacks, cancellation tokens, and memory warnings
|
|
4
|
+
for long-running operations.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.core.progress import ProgressCallback, CancellationToken
|
|
9
|
+
>>> token = CancellationToken()
|
|
10
|
+
>>> def progress_fn(current, total, message):
|
|
11
|
+
... print(f"{current}/{total}: {message}")
|
|
12
|
+
>>> # Use in analysis functions
|
|
13
|
+
>>> result = analyze(data, progress_callback=progress_fn, cancel_token=token)
|
|
14
|
+
|
|
15
|
+
References:
|
|
16
|
+
- WCAG 2.1 progress indication guidelines
|
|
17
|
+
- Python threading and multiprocessing best practices
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import time
|
|
23
|
+
import warnings
|
|
24
|
+
from typing import TYPE_CHECKING, Protocol
|
|
25
|
+
|
|
26
|
+
import psutil
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from collections.abc import Callable
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ProgressCallback(Protocol):
|
|
33
|
+
"""Protocol for progress callback functions.
|
|
34
|
+
|
|
35
|
+
: Progress callback parameter on all analysis functions.
|
|
36
|
+
Callback receives (current, total, message) for progress reporting.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
current: Current progress value (e.g., samples processed)
|
|
40
|
+
total: Total expected value (e.g., total samples)
|
|
41
|
+
message: Descriptive message about current operation
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
>>> def my_progress(current: int, total: int, message: str) -> None:
|
|
45
|
+
... percent = 100 * current / total
|
|
46
|
+
... print(f"{percent:.1f}%: {message}")
|
|
47
|
+
|
|
48
|
+
References:
|
|
49
|
+
PROG-001: Progress Indication for Long Operations
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __call__(self, current: int, total: int, message: str) -> None:
|
|
53
|
+
"""Progress callback signature.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
current: Current progress (completed items)
|
|
57
|
+
total: Total items to process
|
|
58
|
+
message: Status message
|
|
59
|
+
"""
|
|
60
|
+
...
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class CancellationToken:
|
|
64
|
+
"""Token for cancelling long-running operations.
|
|
65
|
+
|
|
66
|
+
: Cancellation Support - cancel() method on operation handles.
|
|
67
|
+
Allows graceful cancellation of operations with Ctrl+C support.
|
|
68
|
+
|
|
69
|
+
Attributes:
|
|
70
|
+
cancelled: Whether cancellation has been requested
|
|
71
|
+
message: Optional cancellation message
|
|
72
|
+
|
|
73
|
+
Example:
|
|
74
|
+
>>> from oscura.core.progress import CancellationToken, CancelledError
|
|
75
|
+
>>> token = CancellationToken()
|
|
76
|
+
>>> # In analysis function:
|
|
77
|
+
>>> for i in range(n_samples):
|
|
78
|
+
... if token.is_cancelled():
|
|
79
|
+
... raise CancelledError("Analysis cancelled by user")
|
|
80
|
+
... # ... process sample ...
|
|
81
|
+
|
|
82
|
+
References:
|
|
83
|
+
PROG-002: Cancellation Support
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def __init__(self) -> None:
|
|
87
|
+
"""Initialize cancellation token."""
|
|
88
|
+
self._cancelled: bool = False
|
|
89
|
+
self._message: str = ""
|
|
90
|
+
self._cancelled_at: float | None = None
|
|
91
|
+
|
|
92
|
+
def cancel(self, message: str = "Operation cancelled") -> None:
|
|
93
|
+
"""Request cancellation of the operation.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
message: Reason for cancellation (default: "Operation cancelled")
|
|
97
|
+
|
|
98
|
+
Example:
|
|
99
|
+
>>> token = CancellationToken()
|
|
100
|
+
>>> token.cancel("User requested stop")
|
|
101
|
+
>>> assert token.is_cancelled()
|
|
102
|
+
|
|
103
|
+
References:
|
|
104
|
+
PROG-002: Cancellation Support
|
|
105
|
+
"""
|
|
106
|
+
self._cancelled = True
|
|
107
|
+
self._message = message
|
|
108
|
+
self._cancelled_at = time.time()
|
|
109
|
+
|
|
110
|
+
def is_cancelled(self) -> bool:
|
|
111
|
+
"""Check if cancellation has been requested.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
True if operation should be cancelled
|
|
115
|
+
|
|
116
|
+
Example:
|
|
117
|
+
>>> token = CancellationToken()
|
|
118
|
+
>>> if token.is_cancelled():
|
|
119
|
+
... return # Exit early
|
|
120
|
+
|
|
121
|
+
References:
|
|
122
|
+
PROG-002: Cancellation Support
|
|
123
|
+
"""
|
|
124
|
+
return self._cancelled
|
|
125
|
+
|
|
126
|
+
def check(self) -> None:
|
|
127
|
+
"""Check cancellation status and raise if cancelled.
|
|
128
|
+
|
|
129
|
+
Raises:
|
|
130
|
+
CancelledError: If cancellation has been requested
|
|
131
|
+
|
|
132
|
+
Example:
|
|
133
|
+
>>> token = CancellationToken()
|
|
134
|
+
>>> token.cancel()
|
|
135
|
+
>>> token.check() # Raises CancelledError
|
|
136
|
+
|
|
137
|
+
References:
|
|
138
|
+
PROG-002: Cancellation Support
|
|
139
|
+
"""
|
|
140
|
+
if self._cancelled:
|
|
141
|
+
raise CancelledError(self._message)
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def message(self) -> str:
|
|
145
|
+
"""Get cancellation message.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Cancellation message
|
|
149
|
+
|
|
150
|
+
References:
|
|
151
|
+
PROG-002: Cancellation Support
|
|
152
|
+
"""
|
|
153
|
+
return self._message
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def cancelled_at(self) -> float | None:
|
|
157
|
+
"""Get timestamp when cancellation was requested.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Timestamp in seconds since epoch, or None if not cancelled
|
|
161
|
+
|
|
162
|
+
References:
|
|
163
|
+
PROG-002: Cancellation Support
|
|
164
|
+
"""
|
|
165
|
+
return self._cancelled_at
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class CancelledError(Exception):
|
|
169
|
+
"""Exception raised when operation is cancelled.
|
|
170
|
+
|
|
171
|
+
: Partial results available after cancellation.
|
|
172
|
+
Operations can catch this to save partial results before exiting.
|
|
173
|
+
|
|
174
|
+
Attributes:
|
|
175
|
+
message: Reason for cancellation
|
|
176
|
+
progress: Progress percentage at cancellation (0-100)
|
|
177
|
+
|
|
178
|
+
Example:
|
|
179
|
+
>>> from oscura.core.progress import CancelledError
|
|
180
|
+
>>> try:
|
|
181
|
+
... # ... long operation ...
|
|
182
|
+
... raise CancelledError("User cancelled", progress=45.5)
|
|
183
|
+
... except CancelledError as e:
|
|
184
|
+
... print(f"Cancelled at {e.progress}%: {e.message}")
|
|
185
|
+
|
|
186
|
+
References:
|
|
187
|
+
PROG-002: Cancellation Support
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
def __init__(self, message: str, *, progress: float = 0.0) -> None:
|
|
191
|
+
"""Initialize CancelledError.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
message: Reason for cancellation
|
|
195
|
+
progress: Progress percentage at cancellation (default: 0.0)
|
|
196
|
+
"""
|
|
197
|
+
self.message = message
|
|
198
|
+
self.progress = progress
|
|
199
|
+
super().__init__(f"{message} ({progress:.1f}% complete)")
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def create_progress_tracker(
|
|
203
|
+
total: int,
|
|
204
|
+
*,
|
|
205
|
+
callback: Callable[[int, int, str], None] | None = None,
|
|
206
|
+
update_interval: float = 0.1,
|
|
207
|
+
) -> ProgressTracker:
|
|
208
|
+
"""Create a progress tracker for an operation.
|
|
209
|
+
|
|
210
|
+
: Progress callback receives (current, total, eta_seconds).
|
|
211
|
+
Automatically calculates ETA and throttles updates.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
total: Total number of items to process
|
|
215
|
+
callback: Optional progress callback function
|
|
216
|
+
update_interval: Minimum time between updates in seconds (default: 0.1)
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
ProgressTracker instance
|
|
220
|
+
|
|
221
|
+
Example:
|
|
222
|
+
>>> from oscura.core.progress import create_progress_tracker
|
|
223
|
+
>>> tracker = create_progress_tracker(1000, callback=my_progress)
|
|
224
|
+
>>> for i in range(1000):
|
|
225
|
+
... tracker.update(i + 1, "Processing item")
|
|
226
|
+
|
|
227
|
+
References:
|
|
228
|
+
PROG-001: Progress Indication for Long Operations
|
|
229
|
+
"""
|
|
230
|
+
return ProgressTracker(total, callback=callback, update_interval=update_interval)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class ProgressTracker:
|
|
234
|
+
"""Progress tracker with ETA calculation and throttling.
|
|
235
|
+
|
|
236
|
+
: Callback receives (current, total, eta_seconds).
|
|
237
|
+
Tracks progress and calculates estimated time to completion.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
total: Total number of items
|
|
241
|
+
callback: Optional progress callback
|
|
242
|
+
update_interval: Minimum seconds between updates
|
|
243
|
+
|
|
244
|
+
Example:
|
|
245
|
+
>>> from oscura.core.progress import ProgressTracker
|
|
246
|
+
>>> tracker = ProgressTracker(1000)
|
|
247
|
+
>>> for i in range(1000):
|
|
248
|
+
... tracker.update(i + 1, "Processing")
|
|
249
|
+
>>> tracker.finish("Complete")
|
|
250
|
+
|
|
251
|
+
References:
|
|
252
|
+
PROG-001: Progress Indication for Long Operations
|
|
253
|
+
"""
|
|
254
|
+
|
|
255
|
+
def __init__(
|
|
256
|
+
self,
|
|
257
|
+
total: int,
|
|
258
|
+
*,
|
|
259
|
+
callback: Callable[[int, int, str], None] | None = None,
|
|
260
|
+
update_interval: float = 0.1,
|
|
261
|
+
) -> None:
|
|
262
|
+
"""Initialize progress tracker.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
total: Total items to process
|
|
266
|
+
callback: Progress callback function
|
|
267
|
+
update_interval: Minimum seconds between updates
|
|
268
|
+
"""
|
|
269
|
+
self.total = total
|
|
270
|
+
self.current = 0
|
|
271
|
+
self.callback = callback
|
|
272
|
+
self.update_interval = update_interval
|
|
273
|
+
|
|
274
|
+
self._start_time = time.time()
|
|
275
|
+
self._last_update_time = 0.0
|
|
276
|
+
self._finished = False
|
|
277
|
+
|
|
278
|
+
def update(self, current: int, message: str = "") -> None:
|
|
279
|
+
"""Update progress.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
current: Current progress value
|
|
283
|
+
message: Status message
|
|
284
|
+
|
|
285
|
+
Example:
|
|
286
|
+
>>> tracker.update(500, "Halfway done")
|
|
287
|
+
|
|
288
|
+
References:
|
|
289
|
+
PROG-001: Progress Indication for Long Operations
|
|
290
|
+
"""
|
|
291
|
+
self.current = current
|
|
292
|
+
|
|
293
|
+
# Throttle updates
|
|
294
|
+
now = time.time()
|
|
295
|
+
if now - self._last_update_time < self.update_interval:
|
|
296
|
+
return
|
|
297
|
+
|
|
298
|
+
self._last_update_time = now
|
|
299
|
+
|
|
300
|
+
if self.callback:
|
|
301
|
+
self.callback(current, self.total, message)
|
|
302
|
+
|
|
303
|
+
def get_eta(self) -> float:
|
|
304
|
+
"""Calculate estimated time to completion.
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
Estimated seconds remaining
|
|
308
|
+
|
|
309
|
+
Example:
|
|
310
|
+
>>> tracker.update(500, "Processing")
|
|
311
|
+
>>> eta = tracker.get_eta()
|
|
312
|
+
>>> print(f"ETA: {eta:.1f} seconds")
|
|
313
|
+
|
|
314
|
+
References:
|
|
315
|
+
PROG-001: Progress Indication for Long Operations
|
|
316
|
+
"""
|
|
317
|
+
if self.current == 0:
|
|
318
|
+
return 0.0
|
|
319
|
+
|
|
320
|
+
elapsed = time.time() - self._start_time
|
|
321
|
+
rate = self.current / elapsed
|
|
322
|
+
remaining = self.total - self.current
|
|
323
|
+
|
|
324
|
+
if rate > 0:
|
|
325
|
+
return remaining / rate
|
|
326
|
+
else:
|
|
327
|
+
return 0.0
|
|
328
|
+
|
|
329
|
+
def get_progress_percent(self) -> float:
|
|
330
|
+
"""Get progress as percentage.
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
Progress percentage (0-100)
|
|
334
|
+
|
|
335
|
+
Example:
|
|
336
|
+
>>> tracker.update(250, "Processing")
|
|
337
|
+
>>> print(f"Progress: {tracker.get_progress_percent():.1f}%")
|
|
338
|
+
|
|
339
|
+
References:
|
|
340
|
+
PROG-001: Progress Indication for Long Operations
|
|
341
|
+
"""
|
|
342
|
+
if self.total == 0:
|
|
343
|
+
return 100.0
|
|
344
|
+
return 100.0 * self.current / self.total
|
|
345
|
+
|
|
346
|
+
def finish(self, message: str = "Complete") -> None:
|
|
347
|
+
"""Mark operation as finished.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
message: Completion message (default: "Complete")
|
|
351
|
+
|
|
352
|
+
Example:
|
|
353
|
+
>>> tracker.finish("Analysis complete")
|
|
354
|
+
|
|
355
|
+
References:
|
|
356
|
+
PROG-001: Progress Indication for Long Operations
|
|
357
|
+
"""
|
|
358
|
+
self._finished = True
|
|
359
|
+
self.current = self.total
|
|
360
|
+
|
|
361
|
+
if self.callback:
|
|
362
|
+
self.callback(self.total, self.total, message)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def estimate_memory_usage(
|
|
366
|
+
n_samples: int,
|
|
367
|
+
dtype_bytes: int = 8,
|
|
368
|
+
*,
|
|
369
|
+
n_channels: int = 1,
|
|
370
|
+
scratch_multiplier: float = 2.0,
|
|
371
|
+
) -> int:
|
|
372
|
+
"""Estimate memory usage for an operation.
|
|
373
|
+
|
|
374
|
+
: Estimate memory before large FFT/spectrograms.
|
|
375
|
+
Calculates expected memory consumption including scratch space.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
n_samples: Number of samples
|
|
379
|
+
dtype_bytes: Bytes per sample (default: 8 for float64)
|
|
380
|
+
n_channels: Number of channels (default: 1)
|
|
381
|
+
scratch_multiplier: Multiplier for temporary arrays (default: 2.0)
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
Estimated memory usage in bytes
|
|
385
|
+
|
|
386
|
+
Example:
|
|
387
|
+
>>> from oscura.core.progress import estimate_memory_usage
|
|
388
|
+
>>> memory_bytes = estimate_memory_usage(1_000_000, dtype_bytes=8)
|
|
389
|
+
>>> memory_mb = memory_bytes / (1024 ** 2)
|
|
390
|
+
>>> print(f"Estimated: {memory_mb:.1f} MB")
|
|
391
|
+
|
|
392
|
+
References:
|
|
393
|
+
PROG-003: Memory Usage Warnings
|
|
394
|
+
"""
|
|
395
|
+
# Base array size
|
|
396
|
+
base_size = n_samples * dtype_bytes * n_channels
|
|
397
|
+
|
|
398
|
+
# Include scratch space for operations (e.g., FFT)
|
|
399
|
+
total_size = int(base_size * scratch_multiplier)
|
|
400
|
+
|
|
401
|
+
return total_size
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def check_memory_available(required_bytes: int, *, threshold: float = 0.8) -> bool:
|
|
405
|
+
"""Check if sufficient memory is available.
|
|
406
|
+
|
|
407
|
+
: Warn if estimated > 80% of available RAM.
|
|
408
|
+
Checks system memory availability before large operations.
|
|
409
|
+
|
|
410
|
+
Args:
|
|
411
|
+
required_bytes: Required memory in bytes
|
|
412
|
+
threshold: Maximum fraction of available RAM to use (default: 0.8)
|
|
413
|
+
|
|
414
|
+
Returns:
|
|
415
|
+
True if sufficient memory is available
|
|
416
|
+
|
|
417
|
+
Example:
|
|
418
|
+
>>> from oscura.core.progress import check_memory_available
|
|
419
|
+
>>> required = 1024 * 1024 * 1024 # 1 GB
|
|
420
|
+
>>> if not check_memory_available(required):
|
|
421
|
+
... print("Warning: Insufficient memory")
|
|
422
|
+
|
|
423
|
+
References:
|
|
424
|
+
PROG-003: Memory Usage Warnings
|
|
425
|
+
"""
|
|
426
|
+
memory = psutil.virtual_memory()
|
|
427
|
+
available_bytes = memory.available
|
|
428
|
+
threshold_bytes = available_bytes * threshold
|
|
429
|
+
|
|
430
|
+
return required_bytes <= threshold_bytes # type: ignore[no-any-return]
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def warn_memory_usage(
|
|
434
|
+
required_bytes: int,
|
|
435
|
+
*,
|
|
436
|
+
threshold: float = 0.8,
|
|
437
|
+
suggest_chunked: bool = True,
|
|
438
|
+
) -> None:
|
|
439
|
+
"""Warn if operation may exceed available memory.
|
|
440
|
+
|
|
441
|
+
: Warn before operations that may exceed available memory.
|
|
442
|
+
Issues warning and suggests chunked processing if needed.
|
|
443
|
+
|
|
444
|
+
Args:
|
|
445
|
+
required_bytes: Required memory in bytes
|
|
446
|
+
threshold: Maximum fraction of available RAM (default: 0.8)
|
|
447
|
+
suggest_chunked: Suggest chunked processing (default: True)
|
|
448
|
+
|
|
449
|
+
Example:
|
|
450
|
+
>>> from oscura.core.progress import warn_memory_usage
|
|
451
|
+
>>> required = estimate_memory_usage(10_000_000)
|
|
452
|
+
>>> warn_memory_usage(required)
|
|
453
|
+
|
|
454
|
+
References:
|
|
455
|
+
PROG-003: Memory Usage Warnings
|
|
456
|
+
"""
|
|
457
|
+
memory = psutil.virtual_memory()
|
|
458
|
+
available_bytes = memory.available
|
|
459
|
+
threshold_bytes = available_bytes * threshold
|
|
460
|
+
|
|
461
|
+
required_mb = required_bytes / (1024**2)
|
|
462
|
+
available_mb = available_bytes / (1024**2)
|
|
463
|
+
threshold_mb = threshold_bytes / (1024**2)
|
|
464
|
+
|
|
465
|
+
if required_bytes > threshold_bytes:
|
|
466
|
+
message = (
|
|
467
|
+
f"Warning: Operation may require {required_mb:.1f} MB of memory, "
|
|
468
|
+
f"but only {available_mb:.1f} MB is available "
|
|
469
|
+
f"(threshold: {threshold_mb:.1f} MB)."
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
if suggest_chunked:
|
|
473
|
+
message += " Consider using chunked processing or reducing the data size."
|
|
474
|
+
|
|
475
|
+
warnings.warn(message, ResourceWarning, stacklevel=2)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def create_simple_progress(
|
|
479
|
+
message_prefix: str = "Progress",
|
|
480
|
+
) -> Callable[[int, int, str], None]:
|
|
481
|
+
"""Create a simple text-based progress callback.
|
|
482
|
+
|
|
483
|
+
: CLI shows progress bar for long operations.
|
|
484
|
+
Returns a callback that prints progress to stdout.
|
|
485
|
+
|
|
486
|
+
Args:
|
|
487
|
+
message_prefix: Prefix for progress messages (default: "Progress")
|
|
488
|
+
|
|
489
|
+
Returns:
|
|
490
|
+
Progress callback function
|
|
491
|
+
|
|
492
|
+
Example:
|
|
493
|
+
>>> from oscura.core.progress import create_simple_progress
|
|
494
|
+
>>> callback = create_simple_progress("Loading")
|
|
495
|
+
>>> for i in range(100):
|
|
496
|
+
... callback(i + 1, 100, "Processing")
|
|
497
|
+
|
|
498
|
+
References:
|
|
499
|
+
PROG-001: Progress Indication for Long Operations
|
|
500
|
+
"""
|
|
501
|
+
|
|
502
|
+
def callback(current: int, total: int, message: str) -> None:
|
|
503
|
+
percent = 100 * current / total if total > 0 else 0
|
|
504
|
+
status = f"{message_prefix}: {percent:.1f}% ({current}/{total})"
|
|
505
|
+
if message:
|
|
506
|
+
status += f" - {message}"
|
|
507
|
+
print(f"\r{status}", end="", flush=True)
|
|
508
|
+
if current >= total:
|
|
509
|
+
print() # New line when complete
|
|
510
|
+
|
|
511
|
+
return callback
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
__all__ = [
|
|
515
|
+
"CancellationToken",
|
|
516
|
+
"CancelledError",
|
|
517
|
+
"ProgressCallback",
|
|
518
|
+
"ProgressTracker",
|
|
519
|
+
"check_memory_available",
|
|
520
|
+
"create_progress_tracker",
|
|
521
|
+
"create_simple_progress",
|
|
522
|
+
"estimate_memory_usage",
|
|
523
|
+
"warn_memory_usage",
|
|
524
|
+
]
|