oscura 0.0.1__py3-none-any.whl → 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscura/__init__.py +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.0.dist-info/METADATA +300 -0
- oscura-0.1.0.dist-info/RECORD +463 -0
- oscura-0.1.0.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
"""Extended memory management utilities for TraceKit.
|
|
2
|
+
|
|
3
|
+
Additional memory management features including context managers,
|
|
4
|
+
LRU caching, and HDF5 lazy loading support.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.utils.memory_extensions import ResourceManager, LRUCache
|
|
9
|
+
>>> with ResourceManager(large_array) as data:
|
|
10
|
+
... result = process(data)
|
|
11
|
+
>>> # Data automatically cleaned up
|
|
12
|
+
|
|
13
|
+
References:
|
|
14
|
+
Python resource management patterns
|
|
15
|
+
functools.lru_cache documentation
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import gc
|
|
21
|
+
import hashlib
|
|
22
|
+
import os
|
|
23
|
+
import time
|
|
24
|
+
from collections import OrderedDict
|
|
25
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
26
|
+
|
|
27
|
+
import numpy as np
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from collections.abc import Callable
|
|
31
|
+
|
|
32
|
+
from numpy.typing import NDArray
|
|
33
|
+
|
|
34
|
+
T = TypeVar("T")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# =============================================================================
|
|
38
|
+
# Context Managers for Resource Cleanup (MEM-019)
|
|
39
|
+
# =============================================================================
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ResourceManager:
|
|
43
|
+
"""Context manager for large data resources with automatic cleanup.
|
|
44
|
+
|
|
45
|
+
Ensures prompt memory release when done with large datasets.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
resource: Resource to manage (array, file handle, etc.).
|
|
49
|
+
cleanup_func: Optional cleanup function to call on exit.
|
|
50
|
+
|
|
51
|
+
Example:
|
|
52
|
+
>>> import numpy as np
|
|
53
|
+
>>> with ResourceManager(np.zeros(1000000)) as data:
|
|
54
|
+
... result = process(data)
|
|
55
|
+
>>> # Data is automatically released
|
|
56
|
+
|
|
57
|
+
References:
|
|
58
|
+
MEM-019: Explicit Resource Cleanup
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
resource: Any,
|
|
64
|
+
cleanup_func: Callable[[Any], None] | None = None,
|
|
65
|
+
) -> None:
|
|
66
|
+
self._resource = resource
|
|
67
|
+
self._cleanup_func = cleanup_func
|
|
68
|
+
|
|
69
|
+
def __enter__(self) -> Any:
|
|
70
|
+
"""Enter context, return resource."""
|
|
71
|
+
return self._resource
|
|
72
|
+
|
|
73
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
74
|
+
"""Exit context, cleanup resource."""
|
|
75
|
+
# Note: exc_val and exc_tb intentionally unused but required for Python 3.11+ compatibility
|
|
76
|
+
if self._cleanup_func is not None:
|
|
77
|
+
self._cleanup_func(self._resource)
|
|
78
|
+
|
|
79
|
+
# Delete reference
|
|
80
|
+
self._resource = None
|
|
81
|
+
|
|
82
|
+
# Force garbage collection
|
|
83
|
+
gc.collect()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class ArrayManager(ResourceManager):
|
|
87
|
+
"""Context manager specifically for numpy arrays.
|
|
88
|
+
|
|
89
|
+
Example:
|
|
90
|
+
>>> import numpy as np
|
|
91
|
+
>>> with ArrayManager(np.zeros((10000, 10000))) as arr:
|
|
92
|
+
... result = np.sum(arr)
|
|
93
|
+
>>> # Array memory is released
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(self, array: NDArray[Any]) -> None:
|
|
97
|
+
super().__init__(array, cleanup_func=lambda x: None) # noqa: ARG005
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# =============================================================================
|
|
101
|
+
# LRU Cache for Intermediate Results (MEM-021, MEM-029)
|
|
102
|
+
# =============================================================================
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class LRUCache[T]:
|
|
106
|
+
"""Least-Recently-Used cache with memory-based eviction.
|
|
107
|
+
|
|
108
|
+
Caches intermediate results with automatic eviction when
|
|
109
|
+
memory limit is exceeded.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
max_memory_bytes: Maximum cache size in bytes.
|
|
113
|
+
max_entries: Maximum number of cache entries (default unlimited).
|
|
114
|
+
|
|
115
|
+
Example:
|
|
116
|
+
>>> cache = LRUCache(max_memory_bytes=1_000_000_000) # 1 GB
|
|
117
|
+
>>> cache.put("key1", large_array, size_bytes=800_000_000)
|
|
118
|
+
>>> result = cache.get("key1")
|
|
119
|
+
>>> cache.clear()
|
|
120
|
+
|
|
121
|
+
References:
|
|
122
|
+
MEM-021: Intermediate Result Eviction
|
|
123
|
+
MEM-029: LRU Cache for Intermediate Results
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
def __init__(
|
|
127
|
+
self,
|
|
128
|
+
max_memory_bytes: int,
|
|
129
|
+
max_entries: int | None = None,
|
|
130
|
+
) -> None:
|
|
131
|
+
self._max_memory = max_memory_bytes
|
|
132
|
+
self._max_entries = max_entries
|
|
133
|
+
self._cache: OrderedDict[str, tuple[T, int, float]] = OrderedDict()
|
|
134
|
+
self._current_memory: int = 0
|
|
135
|
+
self._hits: int = 0
|
|
136
|
+
self._misses: int = 0
|
|
137
|
+
|
|
138
|
+
def get(self, key: str) -> T | None:
|
|
139
|
+
"""Get cached value by key.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
key: Cache key.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Cached value or None if not found.
|
|
146
|
+
"""
|
|
147
|
+
if key in self._cache:
|
|
148
|
+
# Move to end (most recently used)
|
|
149
|
+
value, size, _ = self._cache.pop(key)
|
|
150
|
+
self._cache[key] = (value, size, time.time())
|
|
151
|
+
self._hits += 1
|
|
152
|
+
return value
|
|
153
|
+
else:
|
|
154
|
+
self._misses += 1
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
def put(self, key: str, value: T, size_bytes: int | None = None) -> None:
|
|
158
|
+
"""Put value in cache.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
key: Cache key.
|
|
162
|
+
value: Value to cache.
|
|
163
|
+
size_bytes: Size in bytes. If None, estimated from value.
|
|
164
|
+
"""
|
|
165
|
+
# Estimate size if not provided
|
|
166
|
+
if size_bytes is None:
|
|
167
|
+
size_bytes = self._estimate_size(value)
|
|
168
|
+
|
|
169
|
+
# Check if single item exceeds max memory
|
|
170
|
+
if size_bytes > self._max_memory:
|
|
171
|
+
# Don't cache items larger than max memory
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
# Evict until we have space
|
|
175
|
+
while (
|
|
176
|
+
self._current_memory + size_bytes > self._max_memory
|
|
177
|
+
or (self._max_entries and len(self._cache) >= self._max_entries)
|
|
178
|
+
) and len(self._cache) > 0:
|
|
179
|
+
self._evict_oldest()
|
|
180
|
+
|
|
181
|
+
# Remove if key already exists
|
|
182
|
+
if key in self._cache:
|
|
183
|
+
_, old_size, _ = self._cache.pop(key)
|
|
184
|
+
self._current_memory -= old_size
|
|
185
|
+
|
|
186
|
+
# Add new entry
|
|
187
|
+
self._cache[key] = (value, size_bytes, time.time())
|
|
188
|
+
self._current_memory += size_bytes
|
|
189
|
+
|
|
190
|
+
def _evict_oldest(self) -> None:
|
|
191
|
+
"""Evict least recently used entry."""
|
|
192
|
+
if len(self._cache) > 0:
|
|
193
|
+
_, (_, size, _) = self._cache.popitem(last=False)
|
|
194
|
+
self._current_memory -= size
|
|
195
|
+
|
|
196
|
+
def clear(self) -> None:
|
|
197
|
+
"""Clear entire cache."""
|
|
198
|
+
self._cache.clear()
|
|
199
|
+
self._current_memory = 0
|
|
200
|
+
|
|
201
|
+
def _estimate_size(self, value: T) -> int:
|
|
202
|
+
"""Estimate size of cached value."""
|
|
203
|
+
if isinstance(value, np.ndarray):
|
|
204
|
+
return int(value.nbytes)
|
|
205
|
+
elif isinstance(value, list | tuple):
|
|
206
|
+
# Rough estimate for sequences
|
|
207
|
+
return sum(self._estimate_size(item) for item in value)
|
|
208
|
+
elif isinstance(value, dict):
|
|
209
|
+
return sum(self._estimate_size(k) + self._estimate_size(v) for k, v in value.items())
|
|
210
|
+
else:
|
|
211
|
+
# Fallback: assume 1KB for unknown types
|
|
212
|
+
return 1024
|
|
213
|
+
|
|
214
|
+
def stats(self) -> dict[str, int | float]:
|
|
215
|
+
"""Get cache statistics.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Dictionary with cache stats.
|
|
219
|
+
"""
|
|
220
|
+
total_requests = self._hits + self._misses
|
|
221
|
+
hit_rate = self._hits / total_requests if total_requests > 0 else 0.0
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
"entries": len(self._cache),
|
|
225
|
+
"memory_bytes": self._current_memory,
|
|
226
|
+
"memory_mb": self._current_memory / (1024 * 1024),
|
|
227
|
+
"hits": self._hits,
|
|
228
|
+
"misses": self._misses,
|
|
229
|
+
"hit_rate": hit_rate,
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
def __len__(self) -> int:
|
|
233
|
+
"""Number of cached entries."""
|
|
234
|
+
return len(self._cache)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
# Global result cache
|
|
238
|
+
_result_cache: LRUCache[Any] | None = None
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def get_result_cache() -> LRUCache[Any]:
|
|
242
|
+
"""Get global result cache.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
Global LRU cache instance.
|
|
246
|
+
|
|
247
|
+
Example:
|
|
248
|
+
>>> cache = get_result_cache()
|
|
249
|
+
>>> cache.put("fft_result", fft_data, size_bytes=8000000)
|
|
250
|
+
>>> result = cache.get("fft_result")
|
|
251
|
+
"""
|
|
252
|
+
global _result_cache
|
|
253
|
+
if _result_cache is None:
|
|
254
|
+
# Default: 2 GB cache
|
|
255
|
+
max_cache_size = int(os.environ.get("TK_CACHE_SIZE", 2 * 1024 * 1024 * 1024)) # noqa: PLW1508
|
|
256
|
+
_result_cache = LRUCache(max_memory_bytes=max_cache_size)
|
|
257
|
+
return _result_cache
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def clear_cache() -> None:
|
|
261
|
+
"""Clear the global result cache.
|
|
262
|
+
|
|
263
|
+
Example:
|
|
264
|
+
>>> clear_cache()
|
|
265
|
+
>>> # All cached results released
|
|
266
|
+
"""
|
|
267
|
+
cache = get_result_cache()
|
|
268
|
+
cache.clear()
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def show_cache_stats() -> dict[str, int | float]:
|
|
272
|
+
"""Show statistics for the global cache.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Dictionary with cache statistics.
|
|
276
|
+
|
|
277
|
+
Example:
|
|
278
|
+
>>> stats = show_cache_stats()
|
|
279
|
+
>>> print(f"Hit rate: {stats['hit_rate']*100:.1f}%")
|
|
280
|
+
>>> print(f"Memory used: {stats['memory_mb']:.1f} MB")
|
|
281
|
+
"""
|
|
282
|
+
cache = get_result_cache()
|
|
283
|
+
return cache.stats()
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def cache_key(*args: Any, **kwargs: Any) -> str:
|
|
287
|
+
"""Generate cache key from arguments.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
*args: Positional arguments.
|
|
291
|
+
**kwargs: Keyword arguments.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
Hash-based cache key.
|
|
295
|
+
|
|
296
|
+
Example:
|
|
297
|
+
>>> key = cache_key("fft", samples=1000, nfft=2048)
|
|
298
|
+
>>> # Use key for caching
|
|
299
|
+
"""
|
|
300
|
+
# Create stable string representation
|
|
301
|
+
parts = [str(arg) for arg in args]
|
|
302
|
+
parts.extend(f"{k}={v}" for k, v in sorted(kwargs.items()))
|
|
303
|
+
key_str = "|".join(parts)
|
|
304
|
+
|
|
305
|
+
# Hash for consistent key
|
|
306
|
+
return hashlib.md5(key_str.encode()).hexdigest()
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
# =============================================================================
|
|
310
|
+
# HDF5 Lazy Loading (MEM-017)
|
|
311
|
+
# =============================================================================
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def load_hdf5_lazy(
|
|
315
|
+
file_path: str,
|
|
316
|
+
dataset_path: str = "/data",
|
|
317
|
+
) -> Any:
|
|
318
|
+
"""Load HDF5 dataset as lazy h5py.Dataset (not fully in memory).
|
|
319
|
+
|
|
320
|
+
Enables partial loading via slicing without loading entire dataset.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
file_path: Path to HDF5 file.
|
|
324
|
+
dataset_path: Path to dataset within file (default "/data").
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
h5py.Dataset object (lazy, not loaded until accessed).
|
|
328
|
+
|
|
329
|
+
Raises:
|
|
330
|
+
ImportError: If h5py is not available.
|
|
331
|
+
FileNotFoundError: If file does not exist.
|
|
332
|
+
KeyError: If dataset not found in file.
|
|
333
|
+
|
|
334
|
+
Example:
|
|
335
|
+
>>> # Load dataset lazily
|
|
336
|
+
>>> dataset = load_hdf5_lazy("large_file.h5", "/signals/ch1")
|
|
337
|
+
>>> # Only load specific range (not entire file)
|
|
338
|
+
>>> chunk = dataset[1000:2000]
|
|
339
|
+
>>> print(f"Chunk shape: {chunk.shape}")
|
|
340
|
+
|
|
341
|
+
References:
|
|
342
|
+
MEM-017: HDF5 Chunked Dataset Access
|
|
343
|
+
"""
|
|
344
|
+
try:
|
|
345
|
+
import h5py
|
|
346
|
+
except ImportError:
|
|
347
|
+
raise ImportError( # noqa: B904
|
|
348
|
+
"h5py required for lazy HDF5 loading. Install with: pip install h5py"
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
from pathlib import Path
|
|
352
|
+
|
|
353
|
+
path = Path(file_path)
|
|
354
|
+
if not path.exists():
|
|
355
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
356
|
+
|
|
357
|
+
# Open file in read mode
|
|
358
|
+
# Note: File handle should be kept open for lazy access
|
|
359
|
+
# User is responsible for closing the file handle
|
|
360
|
+
f = h5py.File(file_path, "r")
|
|
361
|
+
|
|
362
|
+
if dataset_path not in f:
|
|
363
|
+
available = list(f.keys())
|
|
364
|
+
f.close()
|
|
365
|
+
raise KeyError(
|
|
366
|
+
f"Dataset '{dataset_path}' not found in HDF5 file. "
|
|
367
|
+
f"Available datasets: {', '.join(available)}"
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
dataset = f[dataset_path]
|
|
371
|
+
|
|
372
|
+
return dataset
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
class LazyHDF5Array:
|
|
376
|
+
"""Wrapper for lazy HDF5 dataset access with context management.
|
|
377
|
+
|
|
378
|
+
Provides automatic file handle cleanup and numpy-like slicing.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
file_path: Path to HDF5 file.
|
|
382
|
+
dataset_path: Path to dataset within file.
|
|
383
|
+
|
|
384
|
+
Example:
|
|
385
|
+
>>> with LazyHDF5Array("data.h5", "/signals/ch1") as arr:
|
|
386
|
+
... # Only loads specific slice
|
|
387
|
+
... chunk = arr[1000:2000]
|
|
388
|
+
... print(f"Shape: {arr.shape}, dtype: {arr.dtype}")
|
|
389
|
+
>>> # File automatically closed
|
|
390
|
+
|
|
391
|
+
References:
|
|
392
|
+
MEM-017: HDF5 Chunked Dataset Access
|
|
393
|
+
MEM-019: Explicit Resource Cleanup
|
|
394
|
+
"""
|
|
395
|
+
|
|
396
|
+
def __init__(self, file_path: str, dataset_path: str = "/data"):
|
|
397
|
+
self._file_path = file_path
|
|
398
|
+
self._dataset_path = dataset_path
|
|
399
|
+
self._file = None
|
|
400
|
+
self._dataset = None
|
|
401
|
+
|
|
402
|
+
def __enter__(self) -> LazyHDF5Array:
|
|
403
|
+
"""Open HDF5 file and dataset."""
|
|
404
|
+
try:
|
|
405
|
+
import h5py
|
|
406
|
+
except ImportError:
|
|
407
|
+
raise ImportError("h5py required. Install with: pip install h5py") # noqa: B904
|
|
408
|
+
|
|
409
|
+
self._file = h5py.File(self._file_path, "r")
|
|
410
|
+
self._dataset = self._file[self._dataset_path] # type: ignore[index]
|
|
411
|
+
return self
|
|
412
|
+
|
|
413
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
414
|
+
"""Close HDF5 file."""
|
|
415
|
+
# Note: exc_val and exc_tb intentionally unused but required for Python 3.11+ compatibility
|
|
416
|
+
if self._file is not None:
|
|
417
|
+
self._file.close() # type: ignore[unreachable]
|
|
418
|
+
self._file = None
|
|
419
|
+
self._dataset = None
|
|
420
|
+
|
|
421
|
+
def __getitem__(self, key: Any) -> NDArray[Any]:
|
|
422
|
+
"""Get item/slice from dataset (triggers partial load)."""
|
|
423
|
+
if self._dataset is None:
|
|
424
|
+
raise RuntimeError("LazyHDF5Array must be used as context manager")
|
|
425
|
+
return np.asarray(self._dataset[key]) # type: ignore[unreachable]
|
|
426
|
+
|
|
427
|
+
@property
|
|
428
|
+
def shape(self) -> tuple[int, ...]:
|
|
429
|
+
"""Dataset shape."""
|
|
430
|
+
if self._dataset is None:
|
|
431
|
+
raise RuntimeError("LazyHDF5Array must be used as context manager")
|
|
432
|
+
return self._dataset.shape # type: ignore[unreachable]
|
|
433
|
+
|
|
434
|
+
@property
|
|
435
|
+
def dtype(self) -> np.dtype[Any]:
|
|
436
|
+
"""Dataset dtype."""
|
|
437
|
+
if self._dataset is None:
|
|
438
|
+
raise RuntimeError("LazyHDF5Array must be used as context manager")
|
|
439
|
+
return self._dataset.dtype # type: ignore[unreachable]
|
|
440
|
+
|
|
441
|
+
@property
|
|
442
|
+
def size(self) -> int:
|
|
443
|
+
"""Total number of elements."""
|
|
444
|
+
if self._dataset is None:
|
|
445
|
+
raise RuntimeError("LazyHDF5Array must be used as context manager")
|
|
446
|
+
return self._dataset.size # type: ignore[unreachable]
|
|
447
|
+
|
|
448
|
+
def __len__(self) -> int:
|
|
449
|
+
"""Length of first dimension."""
|
|
450
|
+
if self._dataset is None:
|
|
451
|
+
raise RuntimeError("LazyHDF5Array must be used as context manager")
|
|
452
|
+
return len(self._dataset) # type: ignore[unreachable]
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
__all__ = [
|
|
456
|
+
"ArrayManager",
|
|
457
|
+
"LRUCache",
|
|
458
|
+
"LazyHDF5Array",
|
|
459
|
+
"ResourceManager",
|
|
460
|
+
"cache_key",
|
|
461
|
+
"clear_cache",
|
|
462
|
+
"get_result_cache",
|
|
463
|
+
"load_hdf5_lazy",
|
|
464
|
+
"show_cache_stats",
|
|
465
|
+
]
|