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.
Files changed (513) hide show
  1. oscura/__init__.py +169 -167
  2. oscura/analyzers/__init__.py +3 -0
  3. oscura/analyzers/classification.py +659 -0
  4. oscura/analyzers/digital/__init__.py +0 -48
  5. oscura/analyzers/digital/edges.py +325 -65
  6. oscura/analyzers/digital/extraction.py +0 -195
  7. oscura/analyzers/digital/quality.py +293 -166
  8. oscura/analyzers/digital/timing.py +260 -115
  9. oscura/analyzers/digital/timing_numba.py +334 -0
  10. oscura/analyzers/entropy.py +605 -0
  11. oscura/analyzers/eye/diagram.py +176 -109
  12. oscura/analyzers/eye/metrics.py +5 -5
  13. oscura/analyzers/jitter/__init__.py +6 -4
  14. oscura/analyzers/jitter/ber.py +52 -52
  15. oscura/analyzers/jitter/classification.py +156 -0
  16. oscura/analyzers/jitter/decomposition.py +163 -113
  17. oscura/analyzers/jitter/spectrum.py +80 -64
  18. oscura/analyzers/ml/__init__.py +39 -0
  19. oscura/analyzers/ml/features.py +600 -0
  20. oscura/analyzers/ml/signal_classifier.py +604 -0
  21. oscura/analyzers/packet/daq.py +246 -158
  22. oscura/analyzers/packet/parser.py +12 -1
  23. oscura/analyzers/packet/payload.py +50 -2110
  24. oscura/analyzers/packet/payload_analysis.py +361 -181
  25. oscura/analyzers/packet/payload_patterns.py +133 -70
  26. oscura/analyzers/packet/stream.py +84 -23
  27. oscura/analyzers/patterns/__init__.py +26 -5
  28. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  29. oscura/analyzers/patterns/clustering.py +169 -108
  30. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  31. oscura/analyzers/patterns/discovery.py +1 -1
  32. oscura/analyzers/patterns/matching.py +581 -197
  33. oscura/analyzers/patterns/pattern_mining.py +778 -0
  34. oscura/analyzers/patterns/periodic.py +121 -38
  35. oscura/analyzers/patterns/sequences.py +175 -78
  36. oscura/analyzers/power/conduction.py +1 -1
  37. oscura/analyzers/power/soa.py +6 -6
  38. oscura/analyzers/power/switching.py +250 -110
  39. oscura/analyzers/protocol/__init__.py +17 -1
  40. oscura/analyzers/protocols/__init__.py +1 -22
  41. oscura/analyzers/protocols/base.py +6 -6
  42. oscura/analyzers/protocols/ble/__init__.py +38 -0
  43. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  44. oscura/analyzers/protocols/ble/uuids.py +288 -0
  45. oscura/analyzers/protocols/can.py +257 -127
  46. oscura/analyzers/protocols/can_fd.py +107 -80
  47. oscura/analyzers/protocols/flexray.py +139 -80
  48. oscura/analyzers/protocols/hdlc.py +93 -58
  49. oscura/analyzers/protocols/i2c.py +247 -106
  50. oscura/analyzers/protocols/i2s.py +138 -86
  51. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  52. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  53. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  54. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  55. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  56. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  57. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  58. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  59. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  60. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  61. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  62. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  63. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  64. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  65. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  66. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  67. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  68. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  69. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  70. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  71. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  72. oscura/analyzers/protocols/jtag.py +180 -98
  73. oscura/analyzers/protocols/lin.py +219 -114
  74. oscura/analyzers/protocols/manchester.py +4 -4
  75. oscura/analyzers/protocols/onewire.py +253 -149
  76. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  77. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  78. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  79. oscura/analyzers/protocols/spi.py +192 -95
  80. oscura/analyzers/protocols/swd.py +321 -167
  81. oscura/analyzers/protocols/uart.py +267 -125
  82. oscura/analyzers/protocols/usb.py +235 -131
  83. oscura/analyzers/side_channel/power.py +17 -12
  84. oscura/analyzers/signal/__init__.py +15 -0
  85. oscura/analyzers/signal/timing_analysis.py +1086 -0
  86. oscura/analyzers/signal_integrity/__init__.py +4 -1
  87. oscura/analyzers/signal_integrity/sparams.py +2 -19
  88. oscura/analyzers/spectral/chunked.py +129 -60
  89. oscura/analyzers/spectral/chunked_fft.py +300 -94
  90. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  91. oscura/analyzers/statistical/checksum.py +376 -217
  92. oscura/analyzers/statistical/classification.py +229 -107
  93. oscura/analyzers/statistical/entropy.py +78 -53
  94. oscura/analyzers/statistics/correlation.py +407 -211
  95. oscura/analyzers/statistics/outliers.py +2 -2
  96. oscura/analyzers/statistics/streaming.py +30 -5
  97. oscura/analyzers/validation.py +216 -101
  98. oscura/analyzers/waveform/measurements.py +9 -0
  99. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  100. oscura/analyzers/waveform/spectral.py +500 -228
  101. oscura/api/__init__.py +31 -5
  102. oscura/api/dsl/__init__.py +582 -0
  103. oscura/{dsl → api/dsl}/commands.py +43 -76
  104. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  105. oscura/{dsl → api/dsl}/parser.py +107 -77
  106. oscura/{dsl → api/dsl}/repl.py +2 -2
  107. oscura/api/dsl.py +1 -1
  108. oscura/{integrations → api/integrations}/__init__.py +1 -1
  109. oscura/{integrations → api/integrations}/llm.py +201 -102
  110. oscura/api/operators.py +3 -3
  111. oscura/api/optimization.py +144 -30
  112. oscura/api/rest_server.py +921 -0
  113. oscura/api/server/__init__.py +17 -0
  114. oscura/api/server/dashboard.py +850 -0
  115. oscura/api/server/static/README.md +34 -0
  116. oscura/api/server/templates/base.html +181 -0
  117. oscura/api/server/templates/export.html +120 -0
  118. oscura/api/server/templates/home.html +284 -0
  119. oscura/api/server/templates/protocols.html +58 -0
  120. oscura/api/server/templates/reports.html +43 -0
  121. oscura/api/server/templates/session_detail.html +89 -0
  122. oscura/api/server/templates/sessions.html +83 -0
  123. oscura/api/server/templates/waveforms.html +73 -0
  124. oscura/automotive/__init__.py +8 -1
  125. oscura/automotive/can/__init__.py +10 -0
  126. oscura/automotive/can/checksum.py +3 -1
  127. oscura/automotive/can/dbc_generator.py +590 -0
  128. oscura/automotive/can/message_wrapper.py +121 -74
  129. oscura/automotive/can/patterns.py +98 -21
  130. oscura/automotive/can/session.py +292 -56
  131. oscura/automotive/can/state_machine.py +6 -3
  132. oscura/automotive/can/stimulus_response.py +97 -75
  133. oscura/automotive/dbc/__init__.py +10 -2
  134. oscura/automotive/dbc/generator.py +84 -56
  135. oscura/automotive/dbc/parser.py +6 -6
  136. oscura/automotive/dtc/data.json +2763 -0
  137. oscura/automotive/dtc/database.py +2 -2
  138. oscura/automotive/flexray/__init__.py +31 -0
  139. oscura/automotive/flexray/analyzer.py +504 -0
  140. oscura/automotive/flexray/crc.py +185 -0
  141. oscura/automotive/flexray/fibex.py +449 -0
  142. oscura/automotive/j1939/__init__.py +45 -8
  143. oscura/automotive/j1939/analyzer.py +605 -0
  144. oscura/automotive/j1939/spns.py +326 -0
  145. oscura/automotive/j1939/transport.py +306 -0
  146. oscura/automotive/lin/__init__.py +47 -0
  147. oscura/automotive/lin/analyzer.py +612 -0
  148. oscura/automotive/loaders/blf.py +13 -2
  149. oscura/automotive/loaders/csv_can.py +143 -72
  150. oscura/automotive/loaders/dispatcher.py +50 -2
  151. oscura/automotive/loaders/mdf.py +86 -45
  152. oscura/automotive/loaders/pcap.py +111 -61
  153. oscura/automotive/uds/__init__.py +4 -0
  154. oscura/automotive/uds/analyzer.py +725 -0
  155. oscura/automotive/uds/decoder.py +140 -58
  156. oscura/automotive/uds/models.py +7 -1
  157. oscura/automotive/visualization.py +1 -1
  158. oscura/cli/analyze.py +348 -0
  159. oscura/cli/batch.py +142 -122
  160. oscura/cli/benchmark.py +275 -0
  161. oscura/cli/characterize.py +137 -82
  162. oscura/cli/compare.py +224 -131
  163. oscura/cli/completion.py +250 -0
  164. oscura/cli/config_cmd.py +361 -0
  165. oscura/cli/decode.py +164 -87
  166. oscura/cli/export.py +286 -0
  167. oscura/cli/main.py +115 -31
  168. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  169. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  170. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  171. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  172. oscura/cli/progress.py +147 -0
  173. oscura/cli/shell.py +157 -135
  174. oscura/cli/validate_cmd.py +204 -0
  175. oscura/cli/visualize.py +158 -0
  176. oscura/convenience.py +125 -79
  177. oscura/core/__init__.py +4 -2
  178. oscura/core/backend_selector.py +3 -3
  179. oscura/core/cache.py +126 -15
  180. oscura/core/cancellation.py +1 -1
  181. oscura/{config → core/config}/__init__.py +20 -11
  182. oscura/{config → core/config}/defaults.py +1 -1
  183. oscura/{config → core/config}/loader.py +7 -5
  184. oscura/{config → core/config}/memory.py +5 -5
  185. oscura/{config → core/config}/migration.py +1 -1
  186. oscura/{config → core/config}/pipeline.py +99 -23
  187. oscura/{config → core/config}/preferences.py +1 -1
  188. oscura/{config → core/config}/protocol.py +3 -3
  189. oscura/{config → core/config}/schema.py +426 -272
  190. oscura/{config → core/config}/settings.py +1 -1
  191. oscura/{config → core/config}/thresholds.py +195 -153
  192. oscura/core/correlation.py +5 -6
  193. oscura/core/cross_domain.py +0 -2
  194. oscura/core/debug.py +9 -5
  195. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  196. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  197. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  198. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  199. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  200. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  201. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  202. oscura/core/gpu_backend.py +11 -7
  203. oscura/core/log_query.py +101 -11
  204. oscura/core/logging.py +126 -54
  205. oscura/core/logging_advanced.py +5 -5
  206. oscura/core/memory_limits.py +108 -70
  207. oscura/core/memory_monitor.py +2 -2
  208. oscura/core/memory_progress.py +7 -7
  209. oscura/core/memory_warnings.py +1 -1
  210. oscura/core/numba_backend.py +13 -13
  211. oscura/{plugins → core/plugins}/__init__.py +9 -9
  212. oscura/{plugins → core/plugins}/base.py +7 -7
  213. oscura/{plugins → core/plugins}/cli.py +3 -3
  214. oscura/{plugins → core/plugins}/discovery.py +186 -106
  215. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  216. oscura/{plugins → core/plugins}/manager.py +7 -7
  217. oscura/{plugins → core/plugins}/registry.py +3 -3
  218. oscura/{plugins → core/plugins}/versioning.py +1 -1
  219. oscura/core/progress.py +16 -1
  220. oscura/core/provenance.py +8 -2
  221. oscura/{schemas → core/schemas}/__init__.py +2 -2
  222. oscura/core/schemas/bus_configuration.json +322 -0
  223. oscura/core/schemas/device_mapping.json +182 -0
  224. oscura/core/schemas/packet_format.json +418 -0
  225. oscura/core/schemas/protocol_definition.json +363 -0
  226. oscura/core/types.py +4 -0
  227. oscura/core/uncertainty.py +3 -3
  228. oscura/correlation/__init__.py +52 -0
  229. oscura/correlation/multi_protocol.py +811 -0
  230. oscura/discovery/auto_decoder.py +117 -35
  231. oscura/discovery/comparison.py +191 -86
  232. oscura/discovery/quality_validator.py +155 -68
  233. oscura/discovery/signal_detector.py +196 -79
  234. oscura/export/__init__.py +18 -20
  235. oscura/export/kaitai_struct.py +513 -0
  236. oscura/export/scapy_layer.py +801 -0
  237. oscura/export/wireshark/README.md +15 -15
  238. oscura/export/wireshark/generator.py +1 -1
  239. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  240. oscura/export/wireshark_dissector.py +746 -0
  241. oscura/guidance/wizard.py +207 -111
  242. oscura/hardware/__init__.py +19 -0
  243. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  244. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  245. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  246. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  247. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  248. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  249. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  250. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  251. oscura/hardware/firmware/__init__.py +29 -0
  252. oscura/hardware/firmware/pattern_recognition.py +874 -0
  253. oscura/hardware/hal_detector.py +736 -0
  254. oscura/hardware/security/__init__.py +37 -0
  255. oscura/hardware/security/side_channel_detector.py +1126 -0
  256. oscura/inference/__init__.py +4 -0
  257. oscura/inference/active_learning/README.md +7 -7
  258. oscura/inference/active_learning/observation_table.py +4 -1
  259. oscura/inference/alignment.py +216 -123
  260. oscura/inference/bayesian.py +113 -33
  261. oscura/inference/crc_reverse.py +101 -55
  262. oscura/inference/logic.py +6 -2
  263. oscura/inference/message_format.py +342 -183
  264. oscura/inference/protocol.py +95 -44
  265. oscura/inference/protocol_dsl.py +180 -82
  266. oscura/inference/signal_intelligence.py +1439 -706
  267. oscura/inference/spectral.py +99 -57
  268. oscura/inference/state_machine.py +810 -158
  269. oscura/inference/stream.py +270 -110
  270. oscura/iot/__init__.py +34 -0
  271. oscura/iot/coap/__init__.py +32 -0
  272. oscura/iot/coap/analyzer.py +668 -0
  273. oscura/iot/coap/options.py +212 -0
  274. oscura/iot/lorawan/__init__.py +21 -0
  275. oscura/iot/lorawan/crypto.py +206 -0
  276. oscura/iot/lorawan/decoder.py +801 -0
  277. oscura/iot/lorawan/mac_commands.py +341 -0
  278. oscura/iot/mqtt/__init__.py +27 -0
  279. oscura/iot/mqtt/analyzer.py +999 -0
  280. oscura/iot/mqtt/properties.py +315 -0
  281. oscura/iot/zigbee/__init__.py +31 -0
  282. oscura/iot/zigbee/analyzer.py +615 -0
  283. oscura/iot/zigbee/security.py +153 -0
  284. oscura/iot/zigbee/zcl.py +349 -0
  285. oscura/jupyter/display.py +125 -45
  286. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  287. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  288. oscura/jupyter/exploratory/fuzzy.py +746 -0
  289. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  290. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  291. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  292. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  293. oscura/jupyter/exploratory/sync.py +612 -0
  294. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  295. oscura/jupyter/magic.py +4 -4
  296. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  297. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  298. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  299. oscura/loaders/__init__.py +171 -63
  300. oscura/loaders/binary.py +88 -1
  301. oscura/loaders/chipwhisperer.py +153 -137
  302. oscura/loaders/configurable.py +208 -86
  303. oscura/loaders/csv_loader.py +458 -215
  304. oscura/loaders/hdf5_loader.py +278 -119
  305. oscura/loaders/lazy.py +87 -54
  306. oscura/loaders/mmap_loader.py +1 -1
  307. oscura/loaders/numpy_loader.py +253 -116
  308. oscura/loaders/pcap.py +226 -151
  309. oscura/loaders/rigol.py +110 -49
  310. oscura/loaders/sigrok.py +201 -78
  311. oscura/loaders/tdms.py +81 -58
  312. oscura/loaders/tektronix.py +291 -174
  313. oscura/loaders/touchstone.py +182 -87
  314. oscura/loaders/vcd.py +215 -117
  315. oscura/loaders/wav.py +155 -68
  316. oscura/reporting/__init__.py +9 -7
  317. oscura/reporting/analyze.py +352 -146
  318. oscura/reporting/argument_preparer.py +69 -14
  319. oscura/reporting/auto_report.py +97 -61
  320. oscura/reporting/batch.py +131 -58
  321. oscura/reporting/chart_selection.py +57 -45
  322. oscura/reporting/comparison.py +63 -17
  323. oscura/reporting/content/executive.py +76 -24
  324. oscura/reporting/core_formats/multi_format.py +11 -8
  325. oscura/reporting/engine.py +312 -158
  326. oscura/reporting/enhanced_reports.py +949 -0
  327. oscura/reporting/export.py +86 -43
  328. oscura/reporting/formatting/numbers.py +69 -42
  329. oscura/reporting/html.py +139 -58
  330. oscura/reporting/index.py +137 -65
  331. oscura/reporting/output.py +158 -67
  332. oscura/reporting/pdf.py +67 -102
  333. oscura/reporting/plots.py +191 -112
  334. oscura/reporting/sections.py +88 -47
  335. oscura/reporting/standards.py +104 -61
  336. oscura/reporting/summary_generator.py +75 -55
  337. oscura/reporting/tables.py +138 -54
  338. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  339. oscura/reporting/templates/index.md +13 -13
  340. oscura/sessions/__init__.py +14 -23
  341. oscura/sessions/base.py +3 -3
  342. oscura/sessions/blackbox.py +106 -10
  343. oscura/sessions/generic.py +2 -2
  344. oscura/sessions/legacy.py +783 -0
  345. oscura/side_channel/__init__.py +63 -0
  346. oscura/side_channel/dpa.py +1025 -0
  347. oscura/utils/__init__.py +15 -1
  348. oscura/utils/autodetect.py +1 -5
  349. oscura/utils/bitwise.py +118 -0
  350. oscura/{builders → utils/builders}/__init__.py +1 -1
  351. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  352. oscura/{comparison → utils/comparison}/compare.py +202 -101
  353. oscura/{comparison → utils/comparison}/golden.py +83 -63
  354. oscura/{comparison → utils/comparison}/limits.py +313 -89
  355. oscura/{comparison → utils/comparison}/mask.py +151 -45
  356. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  357. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  358. oscura/{component → utils/component}/__init__.py +3 -3
  359. oscura/{component → utils/component}/impedance.py +122 -58
  360. oscura/{component → utils/component}/reactive.py +165 -168
  361. oscura/{component → utils/component}/transmission_line.py +3 -3
  362. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  363. oscura/{filtering → utils/filtering}/base.py +1 -1
  364. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  365. oscura/{filtering → utils/filtering}/design.py +169 -93
  366. oscura/{filtering → utils/filtering}/filters.py +2 -2
  367. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  368. oscura/utils/geometry.py +31 -0
  369. oscura/utils/imports.py +184 -0
  370. oscura/utils/lazy.py +1 -1
  371. oscura/{math → utils/math}/__init__.py +2 -2
  372. oscura/{math → utils/math}/arithmetic.py +114 -48
  373. oscura/{math → utils/math}/interpolation.py +139 -106
  374. oscura/utils/memory.py +129 -66
  375. oscura/utils/memory_advanced.py +92 -9
  376. oscura/utils/memory_extensions.py +10 -8
  377. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  378. oscura/{optimization → utils/optimization}/search.py +2 -2
  379. oscura/utils/performance/__init__.py +58 -0
  380. oscura/utils/performance/caching.py +889 -0
  381. oscura/utils/performance/lsh_clustering.py +333 -0
  382. oscura/utils/performance/memory_optimizer.py +699 -0
  383. oscura/utils/performance/optimizations.py +675 -0
  384. oscura/utils/performance/parallel.py +654 -0
  385. oscura/utils/performance/profiling.py +661 -0
  386. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  387. oscura/{pipeline → utils/pipeline}/composition.py +11 -3
  388. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  389. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  390. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  391. oscura/{search → utils/search}/__init__.py +3 -3
  392. oscura/{search → utils/search}/anomaly.py +188 -58
  393. oscura/utils/search/context.py +294 -0
  394. oscura/{search → utils/search}/pattern.py +138 -10
  395. oscura/utils/serial.py +51 -0
  396. oscura/utils/storage/__init__.py +61 -0
  397. oscura/utils/storage/database.py +1166 -0
  398. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  399. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  400. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  401. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  402. oscura/{triggering → utils/triggering}/base.py +6 -6
  403. oscura/{triggering → utils/triggering}/edge.py +2 -2
  404. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  405. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  406. oscura/{triggering → utils/triggering}/window.py +2 -2
  407. oscura/utils/validation.py +32 -0
  408. oscura/validation/__init__.py +121 -0
  409. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  410. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  411. oscura/{compliance → validation/compliance}/masks.py +1 -1
  412. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  413. oscura/{compliance → validation/compliance}/testing.py +114 -52
  414. oscura/validation/compliance_tests.py +915 -0
  415. oscura/validation/fuzzer.py +990 -0
  416. oscura/validation/grammar_tests.py +596 -0
  417. oscura/validation/grammar_validator.py +904 -0
  418. oscura/validation/hil_testing.py +977 -0
  419. oscura/{quality → validation/quality}/__init__.py +4 -4
  420. oscura/{quality → validation/quality}/ensemble.py +251 -171
  421. oscura/{quality → validation/quality}/explainer.py +3 -3
  422. oscura/{quality → validation/quality}/scoring.py +1 -1
  423. oscura/{quality → validation/quality}/warnings.py +4 -4
  424. oscura/validation/regression_suite.py +808 -0
  425. oscura/validation/replay.py +788 -0
  426. oscura/{testing → validation/testing}/__init__.py +2 -2
  427. oscura/{testing → validation/testing}/synthetic.py +5 -5
  428. oscura/visualization/__init__.py +9 -0
  429. oscura/visualization/accessibility.py +1 -1
  430. oscura/visualization/annotations.py +64 -67
  431. oscura/visualization/colors.py +7 -7
  432. oscura/visualization/digital.py +180 -81
  433. oscura/visualization/eye.py +236 -85
  434. oscura/visualization/interactive.py +320 -143
  435. oscura/visualization/jitter.py +587 -247
  436. oscura/visualization/layout.py +169 -134
  437. oscura/visualization/optimization.py +103 -52
  438. oscura/visualization/palettes.py +1 -1
  439. oscura/visualization/power.py +427 -211
  440. oscura/visualization/power_extended.py +626 -297
  441. oscura/visualization/presets.py +2 -0
  442. oscura/visualization/protocols.py +495 -181
  443. oscura/visualization/render.py +79 -63
  444. oscura/visualization/reverse_engineering.py +171 -124
  445. oscura/visualization/signal_integrity.py +460 -279
  446. oscura/visualization/specialized.py +190 -100
  447. oscura/visualization/spectral.py +670 -255
  448. oscura/visualization/thumbnails.py +166 -137
  449. oscura/visualization/waveform.py +150 -63
  450. oscura/workflows/__init__.py +3 -0
  451. oscura/{batch → workflows/batch}/__init__.py +5 -5
  452. oscura/{batch → workflows/batch}/advanced.py +150 -75
  453. oscura/workflows/batch/aggregate.py +531 -0
  454. oscura/workflows/batch/analyze.py +236 -0
  455. oscura/{batch → workflows/batch}/logging.py +2 -2
  456. oscura/{batch → workflows/batch}/metrics.py +1 -1
  457. oscura/workflows/complete_re.py +1144 -0
  458. oscura/workflows/compliance.py +44 -54
  459. oscura/workflows/digital.py +197 -51
  460. oscura/workflows/legacy/__init__.py +12 -0
  461. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  462. oscura/workflows/multi_trace.py +9 -9
  463. oscura/workflows/power.py +42 -62
  464. oscura/workflows/protocol.py +82 -49
  465. oscura/workflows/reverse_engineering.py +351 -150
  466. oscura/workflows/signal_integrity.py +157 -82
  467. oscura-0.6.0.dist-info/METADATA +643 -0
  468. oscura-0.6.0.dist-info/RECORD +590 -0
  469. oscura/analyzers/digital/ic_database.py +0 -498
  470. oscura/analyzers/digital/timing_paths.py +0 -339
  471. oscura/analyzers/digital/vintage.py +0 -377
  472. oscura/analyzers/digital/vintage_result.py +0 -148
  473. oscura/analyzers/protocols/parallel_bus.py +0 -449
  474. oscura/batch/aggregate.py +0 -300
  475. oscura/batch/analyze.py +0 -139
  476. oscura/dsl/__init__.py +0 -73
  477. oscura/exceptions.py +0 -59
  478. oscura/exploratory/fuzzy.py +0 -513
  479. oscura/exploratory/sync.py +0 -384
  480. oscura/export/wavedrom.py +0 -430
  481. oscura/exporters/__init__.py +0 -94
  482. oscura/exporters/csv.py +0 -303
  483. oscura/exporters/exporters.py +0 -44
  484. oscura/exporters/hdf5.py +0 -217
  485. oscura/exporters/html_export.py +0 -701
  486. oscura/exporters/json_export.py +0 -338
  487. oscura/exporters/markdown_export.py +0 -367
  488. oscura/exporters/matlab_export.py +0 -354
  489. oscura/exporters/npz_export.py +0 -219
  490. oscura/exporters/spice_export.py +0 -210
  491. oscura/exporters/vintage_logic_csv.py +0 -247
  492. oscura/reporting/vintage_logic_report.py +0 -523
  493. oscura/search/context.py +0 -149
  494. oscura/session/__init__.py +0 -34
  495. oscura/session/annotations.py +0 -289
  496. oscura/session/history.py +0 -313
  497. oscura/session/session.py +0 -520
  498. oscura/visualization/digital_advanced.py +0 -718
  499. oscura/visualization/figure_manager.py +0 -156
  500. oscura/workflow/__init__.py +0 -13
  501. oscura-0.5.0.dist-info/METADATA +0 -407
  502. oscura-0.5.0.dist-info/RECORD +0 -486
  503. /oscura/core/{config.py → config/legacy.py} +0 -0
  504. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  505. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  506. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  507. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  508. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  509. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  510. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  511. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
  512. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
  513. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -6,7 +6,7 @@ security checks.
6
6
 
7
7
 
8
8
  Example:
9
- >>> from oscura.extensibility.validation import validate_extension
9
+ >>> from oscura.core.extensibility.validation import validate_extension
10
10
  >>> from pathlib import Path
11
11
  >>>
12
12
  >>> # Validate a plugin directory
@@ -445,7 +445,7 @@ class GPUBackend:
445
445
  return self._to_cpu(counts), self._to_cpu(edges)
446
446
  else:
447
447
  # CPU fallback
448
- return np.histogram(data, bins=bins, range=range, density=density) # type: ignore[no-any-return]
448
+ return np.histogram(data, bins=bins, range=range, density=density)
449
449
 
450
450
  def dot(
451
451
  self,
@@ -477,10 +477,12 @@ class GPUBackend:
477
477
  gpu_a = self._to_gpu(a)
478
478
  gpu_b = self._to_gpu(b)
479
479
  result = self._cp.dot(gpu_a, gpu_b)
480
- return self._to_cpu(result) # type: ignore[return-value]
480
+ return self._to_cpu(result)
481
481
  else:
482
- # CPU fallback
483
- return np.dot(a, b) # type: ignore[return-value, no-any-return]
482
+ # CPU fallback - cast result to expected type
483
+ from typing import cast
484
+
485
+ return cast("NDArray[np.float64] | np.float64", np.dot(a, b))
484
486
 
485
487
  def matmul(
486
488
  self,
@@ -511,10 +513,12 @@ class GPUBackend:
511
513
  gpu_a = self._to_gpu(a)
512
514
  gpu_b = self._to_gpu(b)
513
515
  result = self._cp.matmul(gpu_a, gpu_b)
514
- return self._to_cpu(result) # type: ignore[return-value]
516
+ cpu_result = self._to_cpu(result)
517
+ return cpu_result
515
518
  else:
516
- # CPU fallback
517
- return np.matmul(a, b) # type: ignore[return-value, no-any-return]
519
+ # CPU fallback - cast from Any to expected type
520
+ result_cpu: NDArray[np.float64] = np.matmul(a, b)
521
+ return result_cpu
518
522
 
519
523
 
520
524
  # Module-level singleton for convenient access
oscura/core/log_query.py CHANGED
@@ -218,7 +218,34 @@ class LogQuery:
218
218
  """
219
219
  results = self._records.copy()
220
220
 
221
- # Filter by timestamp range
221
+ # Apply all filters
222
+ results = self._filter_by_time(results, start_time, end_time)
223
+ results = self._filter_by_level(results, level)
224
+ results = self._filter_by_module(results, module, module_pattern)
225
+ results = self._filter_by_correlation(results, correlation_id)
226
+ results = self._filter_by_message(results, message_pattern)
227
+
228
+ # Apply pagination
229
+ results = self._apply_pagination(results, offset, limit)
230
+
231
+ return results
232
+
233
+ def _filter_by_time(
234
+ self,
235
+ results: list[LogRecord],
236
+ start_time: datetime | None,
237
+ end_time: datetime | None,
238
+ ) -> list[LogRecord]:
239
+ """Filter records by timestamp range.
240
+
241
+ Args:
242
+ results: Input records.
243
+ start_time: Start time filter.
244
+ end_time: End time filter.
245
+
246
+ Returns:
247
+ Filtered records.
248
+ """
222
249
  if start_time is not None:
223
250
  start_str = format_timestamp(start_time, format="iso8601")
224
251
  results = [r for r in results if r.timestamp >= start_str]
@@ -227,36 +254,99 @@ class LogQuery:
227
254
  end_str = format_timestamp(end_time, format="iso8601")
228
255
  results = [r for r in results if r.timestamp <= end_str]
229
256
 
230
- # Filter by log level
257
+ return results
258
+
259
+ def _filter_by_level(self, results: list[LogRecord], level: str | None) -> list[LogRecord]:
260
+ """Filter records by log level.
261
+
262
+ Args:
263
+ results: Input records.
264
+ level: Level filter.
265
+
266
+ Returns:
267
+ Filtered records.
268
+ """
231
269
  if level is not None:
232
- results = [r for r in results if r.level == level.upper()]
270
+ return [r for r in results if r.level == level.upper()]
271
+ return results
233
272
 
234
- # Filter by module
273
+ def _filter_by_module(
274
+ self,
275
+ results: list[LogRecord],
276
+ module: str | None,
277
+ module_pattern: str | None,
278
+ ) -> list[LogRecord]:
279
+ """Filter records by module name.
280
+
281
+ Args:
282
+ results: Input records.
283
+ module: Exact module filter.
284
+ module_pattern: Module pattern filter.
285
+
286
+ Returns:
287
+ Filtered records.
288
+ """
235
289
  if module is not None:
236
290
  results = [r for r in results if r.module == module]
237
291
 
238
- # Filter by module pattern
239
292
  if module_pattern is not None:
240
293
  # Convert glob pattern to regex
241
294
  pattern = module_pattern.replace(".", r"\.").replace("*", ".*")
242
295
  regex = re.compile(f"^{pattern}$")
243
296
  results = [r for r in results if regex.match(r.module)]
244
297
 
245
- # Filter by correlation ID
298
+ return results
299
+
300
+ def _filter_by_correlation(
301
+ self, results: list[LogRecord], correlation_id: str | None
302
+ ) -> list[LogRecord]:
303
+ """Filter records by correlation ID.
304
+
305
+ Args:
306
+ results: Input records.
307
+ correlation_id: Correlation ID filter.
308
+
309
+ Returns:
310
+ Filtered records.
311
+ """
246
312
  if correlation_id is not None:
247
- results = [r for r in results if r.correlation_id == correlation_id]
313
+ return [r for r in results if r.correlation_id == correlation_id]
314
+ return results
248
315
 
249
- # Filter by message pattern
316
+ def _filter_by_message(
317
+ self, results: list[LogRecord], message_pattern: str | None
318
+ ) -> list[LogRecord]:
319
+ """Filter records by message pattern.
320
+
321
+ Args:
322
+ results: Input records.
323
+ message_pattern: Message pattern filter.
324
+
325
+ Returns:
326
+ Filtered records.
327
+ """
250
328
  if message_pattern is not None:
251
329
  regex = re.compile(message_pattern)
252
- results = [r for r in results if regex.search(r.message)]
330
+ return [r for r in results if regex.search(r.message)]
331
+ return results
253
332
 
254
- # Apply pagination
333
+ def _apply_pagination(
334
+ self, results: list[LogRecord], offset: int, limit: int | None
335
+ ) -> list[LogRecord]:
336
+ """Apply pagination to results.
337
+
338
+ Args:
339
+ results: Input records.
340
+ offset: Number to skip.
341
+ limit: Maximum to return.
342
+
343
+ Returns:
344
+ Paginated records.
345
+ """
255
346
  if offset > 0:
256
347
  results = results[offset:]
257
348
  if limit is not None:
258
349
  results = results[:limit]
259
-
260
350
  return results
261
351
 
262
352
  def export_logs(
oscura/core/logging.py CHANGED
@@ -226,9 +226,7 @@ class CompressingTimedRotatingFileHandler(logging.handlers.TimedRotatingFileHand
226
226
  # Determine the file that just got rotated
227
227
  current_time = int(self.rolloverAt - self.interval)
228
228
  time_tuple = time.gmtime(current_time) if self.utc else time.localtime(current_time)
229
- dfn = self.rotation_filename(
230
- self.baseFilename + "." + self.suffix % time_tuple[:6] # type: ignore[arg-type]
231
- )
229
+ dfn = self.rotation_filename(self.baseFilename + "." + self.suffix % time_tuple[:6])
232
230
 
233
231
  # Handle the existing rotated file
234
232
  if Path(dfn).exists():
@@ -511,7 +509,7 @@ def configure_logging(
511
509
  LOG-002: Hierarchical Log Levels
512
510
  LOG-003: Automatic Log Rotation and Retention Policies
513
511
  """
514
- global _logging_configured, _config # noqa: PLW0602
512
+ global _logging_configured, _config
515
513
 
516
514
  # Update config
517
515
  _config.level = level
@@ -523,64 +521,138 @@ def configure_logging(
523
521
  root_logger.setLevel(getattr(logging, level.upper()))
524
522
 
525
523
  # Remove existing handlers and close them to prevent resource leaks
526
- for handler in root_logger.handlers[:]:
527
- handler.close()
528
- root_logger.removeHandler(handler)
524
+ _cleanup_existing_handlers(root_logger)
529
525
 
530
526
  # Create formatter
531
527
  formatter = StructuredFormatter(format, timestamp_format)
532
528
 
533
529
  # Add handlers
534
530
  if handlers:
535
- for name, config in handlers.items():
536
- if name == "console":
537
- handler = logging.StreamHandler(sys.stderr)
538
- handler.setLevel(getattr(logging, config.get("level", level).upper()))
539
- handler.setFormatter(formatter)
540
- root_logger.addHandler(handler)
541
- elif name == "file":
542
- filename = config.get("filename", "oscura.log")
543
- handler_level = config.get("level", "DEBUG")
544
- backup_count = int(config.get("backup_count", 5))
545
- compress = config.get("compress", False)
546
-
547
- # Check if time-based rotation is requested
548
- when = config.get("when")
549
- if when:
550
- # Time-based rotation (LOG-003)
551
- interval = int(config.get("interval", 1))
552
- max_age = config.get("max_age")
553
- handler = CompressingTimedRotatingFileHandler(
554
- filename,
555
- when=when,
556
- interval=interval,
557
- backupCount=backup_count,
558
- compress=compress,
559
- max_age=max_age,
560
- )
561
- else:
562
- # Size-based rotation
563
- max_bytes = int(config.get("max_bytes", 10_000_000))
564
- handler = CompressingRotatingFileHandler(
565
- filename,
566
- maxBytes=max_bytes,
567
- backupCount=backup_count,
568
- compress=compress,
569
- )
570
-
571
- handler.setLevel(getattr(logging, handler_level.upper()))
572
- handler.setFormatter(formatter)
573
- root_logger.addHandler(handler)
531
+ _add_configured_handlers(root_logger, handlers, formatter, level)
574
532
  else:
575
- # Default: console only
576
- handler = logging.StreamHandler(sys.stderr)
577
- handler.setLevel(getattr(logging, level.upper()))
578
- handler.setFormatter(formatter)
579
- root_logger.addHandler(handler)
533
+ _add_default_console_handler(root_logger, formatter, level)
580
534
 
581
535
  _logging_configured = True
582
536
 
583
537
 
538
+ def _cleanup_existing_handlers(logger: logging.Logger) -> None:
539
+ """Remove and close existing handlers.
540
+
541
+ Args:
542
+ logger: Logger to cleanup.
543
+ """
544
+ for handler in logger.handlers[:]:
545
+ handler.close()
546
+ logger.removeHandler(handler)
547
+
548
+
549
+ def _add_configured_handlers(
550
+ logger: logging.Logger,
551
+ handlers: dict[str, dict[str, Any]],
552
+ formatter: StructuredFormatter,
553
+ default_level: str,
554
+ ) -> None:
555
+ """Add configured handlers to logger.
556
+
557
+ Args:
558
+ logger: Logger to add handlers to.
559
+ handlers: Handler configuration dict.
560
+ formatter: Formatter to use.
561
+ default_level: Default log level.
562
+ """
563
+ for name, config in handlers.items():
564
+ if name == "console":
565
+ _add_console_handler(logger, config, formatter, default_level)
566
+ elif name == "file":
567
+ _add_file_handler(logger, config, formatter)
568
+
569
+
570
+ def _add_console_handler(
571
+ logger: logging.Logger,
572
+ config: dict[str, Any],
573
+ formatter: StructuredFormatter,
574
+ default_level: str,
575
+ ) -> None:
576
+ """Add console handler to logger.
577
+
578
+ Args:
579
+ logger: Logger to add handler to.
580
+ config: Handler configuration.
581
+ formatter: Formatter to use.
582
+ default_level: Default log level.
583
+ """
584
+ handler = logging.StreamHandler(sys.stderr)
585
+ handler.setLevel(getattr(logging, config.get("level", default_level).upper()))
586
+ handler.setFormatter(formatter)
587
+ logger.addHandler(handler)
588
+
589
+
590
+ def _add_file_handler(
591
+ logger: logging.Logger,
592
+ config: dict[str, Any],
593
+ formatter: StructuredFormatter,
594
+ ) -> None:
595
+ """Add file handler to logger with rotation support.
596
+
597
+ Args:
598
+ logger: Logger to add handler to.
599
+ config: Handler configuration.
600
+ formatter: Formatter to use.
601
+ """
602
+ filename = config.get("filename", "oscura.log")
603
+ handler_level = config.get("level", "DEBUG")
604
+ backup_count = int(config.get("backup_count", 5))
605
+ compress = config.get("compress", False)
606
+
607
+ # Check if time-based rotation is requested
608
+ when = config.get("when")
609
+ if when:
610
+ # Time-based rotation
611
+ interval = int(config.get("interval", 1))
612
+ max_age = config.get("max_age")
613
+ time_handler = CompressingTimedRotatingFileHandler(
614
+ filename,
615
+ when=when,
616
+ interval=interval,
617
+ backupCount=backup_count,
618
+ compress=compress,
619
+ max_age=max_age,
620
+ )
621
+ time_handler.setLevel(getattr(logging, handler_level.upper()))
622
+ time_handler.setFormatter(formatter)
623
+ logger.addHandler(time_handler)
624
+ else:
625
+ # Size-based rotation
626
+ max_bytes = int(config.get("max_bytes", 10_000_000))
627
+ size_handler = CompressingRotatingFileHandler(
628
+ filename,
629
+ maxBytes=max_bytes,
630
+ backupCount=backup_count,
631
+ compress=compress,
632
+ )
633
+ size_handler.setLevel(getattr(logging, handler_level.upper()))
634
+ size_handler.setFormatter(formatter)
635
+ logger.addHandler(size_handler)
636
+
637
+
638
+ def _add_default_console_handler(
639
+ logger: logging.Logger,
640
+ formatter: StructuredFormatter,
641
+ level: str,
642
+ ) -> None:
643
+ """Add default console handler when no handlers configured.
644
+
645
+ Args:
646
+ logger: Logger to add handler to.
647
+ formatter: Formatter to use.
648
+ level: Log level.
649
+ """
650
+ handler = logging.StreamHandler(sys.stderr)
651
+ handler.setLevel(getattr(logging, level.upper()))
652
+ handler.setFormatter(formatter)
653
+ logger.addHandler(handler)
654
+
655
+
584
656
  def get_logger(name: str) -> logging.Logger:
585
657
  """Get a logger with the specified name.
586
658
 
@@ -675,7 +747,7 @@ class ErrorContextCapture:
675
747
  def from_exception(
676
748
  cls,
677
749
  exc: BaseException,
678
- include_locals: bool = True, # noqa: ARG003
750
+ include_locals: bool = True,
679
751
  additional_context: dict[str, Any] | None = None,
680
752
  ) -> ErrorContextCapture:
681
753
  """Create error context from an exception.
@@ -885,14 +957,14 @@ _init_logging()
885
957
 
886
958
  # Re-export correlation and performance functions for convenience
887
959
  # These provide LOG-004 and LOG-006 functionality through this module
888
- from oscura.core.correlation import ( # noqa: E402
960
+ from oscura.core.correlation import (
889
961
  CorrelationContext,
890
962
  generate_correlation_id,
891
963
  get_correlation_id,
892
964
  set_correlation_id,
893
965
  with_correlation_id,
894
966
  )
895
- from oscura.core.performance import ( # noqa: E402
967
+ from oscura.core.performance import (
896
968
  PerformanceContext,
897
969
  PerformanceRecord,
898
970
  clear_performance_data,
@@ -603,11 +603,11 @@ class CompressedLogHandler(logging.Handler):
603
603
 
604
604
  def _open_file(self) -> None:
605
605
  """Open current log file."""
606
- self._current_file = gzip.open( # noqa: SIM115
606
+ self._current_file = gzip.open(
607
607
  f"{self.filename}.gz", "ab", compresslevel=self.compression_level
608
608
  )
609
609
  try:
610
- self._current_size = os.path.getsize(f"{self.filename}.gz") # noqa: PTH202
610
+ self._current_size = os.path.getsize(f"{self.filename}.gz")
611
611
  except OSError:
612
612
  self._current_size = 0
613
613
 
@@ -621,11 +621,11 @@ class CompressedLogHandler(logging.Handler):
621
621
  src = f"{self.filename}.{i}.gz"
622
622
  dst = f"{self.filename}.{i + 1}.gz"
623
623
  if os.path.exists(src):
624
- os.rename(src, dst) # noqa: PTH104
624
+ os.rename(src, dst)
625
625
 
626
626
  # Move current to .1
627
627
  if os.path.exists(f"{self.filename}.gz"):
628
- os.rename(f"{self.filename}.gz", f"{self.filename}.1.gz") # noqa: PTH104
628
+ os.rename(f"{self.filename}.gz", f"{self.filename}.1.gz")
629
629
 
630
630
  self._open_file()
631
631
 
@@ -667,7 +667,7 @@ class EncryptedLogHandler(logging.Handler):
667
667
 
668
668
  with self._lock:
669
669
  if self._file is None:
670
- self._file = open(self.filename, "ab") # noqa: SIM115
670
+ self._file = open(self.filename, "ab")
671
671
 
672
672
  # Write length-prefixed encrypted message
673
673
  length = len(encrypted).to_bytes(4, "big")
@@ -16,7 +16,7 @@ References:
16
16
  import warnings
17
17
  from typing import Any
18
18
 
19
- from oscura.config.memory import get_memory_config
19
+ from oscura.core.config.memory import get_memory_config
20
20
  from oscura.utils.memory import estimate_memory
21
21
 
22
22
 
@@ -93,83 +93,123 @@ def apply_memory_limit(
93
93
  If parameters cannot be adjusted to fit memory, a warning is issued
94
94
  and the original parameters are returned.
95
95
  """
96
- # Parse memory limit
97
- limit_bytes = parse_memory_limit(max_memory)
96
+ limit_bytes = _get_effective_memory_limit(max_memory)
98
97
  if limit_bytes is None:
99
- # Use global config
100
- config = get_memory_config()
101
- limit_bytes = config.max_memory
102
- if limit_bytes is None:
103
- # No limit, return params unchanged
104
- return params
98
+ return params
105
99
 
106
100
  samples = int(samples)
107
-
108
- # Estimate with current parameters
109
101
  current_estimate = estimate_memory(operation, samples, **params)
110
102
 
111
103
  if current_estimate.total <= limit_bytes:
112
- # Already within limit
113
104
  return params
114
105
 
115
- # Need to adjust parameters
116
- adjusted_params = params.copy()
106
+ adjusted_params = _adjust_parameters_for_operation(
107
+ operation, samples, limit_bytes, params.copy()
108
+ )
109
+ _validate_adjusted_params(operation, samples, adjusted_params, limit_bytes)
110
+
111
+ return adjusted_params
112
+
113
+
114
+ def _get_effective_memory_limit(max_memory: int | str | None) -> int | None:
115
+ """Get effective memory limit from parameter or config."""
116
+ limit_bytes = parse_memory_limit(max_memory)
117
+ if limit_bytes is not None:
118
+ return limit_bytes
117
119
 
120
+ config = get_memory_config()
121
+ return config.max_memory
122
+
123
+
124
+ def _adjust_parameters_for_operation(
125
+ operation: str, samples: int, limit_bytes: int, params: dict[str, Any]
126
+ ) -> dict[str, Any]:
127
+ """Adjust parameters based on operation type."""
118
128
  if operation in ("fft", "psd"):
119
- # Reduce nfft if specified
120
- if "nfft" in adjusted_params:
121
- # Try reducing nfft
122
- original_nfft = adjusted_params["nfft"]
123
- # Binary search for suitable nfft
124
- nfft = _find_max_nfft(operation, samples, limit_bytes, **adjusted_params)
125
- if nfft < original_nfft:
126
- adjusted_params["nfft"] = nfft
127
- warnings.warn(
128
- f"Reduced nfft from {original_nfft} to {nfft} to fit {limit_bytes / 1e6:.1f} MB limit",
129
- UserWarning,
130
- stacklevel=2,
131
- )
132
-
133
- elif operation == "spectrogram":
134
- # Adjust nperseg and/or nfft
135
- original_nperseg = adjusted_params.get("nperseg", 256)
136
- original_nfft = adjusted_params.get("nfft", original_nperseg)
137
-
138
- # Try reducing nperseg first
139
- nperseg = _find_max_nperseg(samples, limit_bytes, noverlap=adjusted_params.get("noverlap"))
140
- if nperseg < original_nperseg:
141
- adjusted_params["nperseg"] = nperseg
142
- # Adjust noverlap proportionally
143
- if "noverlap" in adjusted_params:
144
- overlap_ratio = adjusted_params["noverlap"] / original_nperseg
145
- adjusted_params["noverlap"] = int(nperseg * overlap_ratio)
146
- warnings.warn(
147
- f"Reduced nperseg from {original_nperseg} to {nperseg} to fit {limit_bytes / 1e6:.1f} MB limit",
148
- UserWarning,
149
- stacklevel=2,
150
- )
151
-
152
- # Also reduce nfft if needed
153
- if "nfft" in adjusted_params and adjusted_params["nfft"] > nperseg:
154
- adjusted_params["nfft"] = nperseg
155
-
156
- elif operation == "eye_diagram":
157
- # Reduce samples_per_ui or num_uis
158
- if "num_uis" in adjusted_params:
159
- original_num_uis = adjusted_params["num_uis"]
160
- # Calculate max num_uis that fits
161
- samples_per_ui = adjusted_params.get("samples_per_ui", 100)
162
- max_num_uis = _find_max_num_uis(limit_bytes, samples_per_ui)
163
- if max_num_uis < original_num_uis:
164
- adjusted_params["num_uis"] = max_num_uis
165
- warnings.warn(
166
- f"Reduced num_uis from {original_num_uis} to {max_num_uis} to fit {limit_bytes / 1e6:.1f} MB limit",
167
- UserWarning,
168
- stacklevel=2,
169
- )
170
-
171
- # Verify final estimate
172
- final_estimate = estimate_memory(operation, samples, **adjusted_params)
129
+ return _adjust_fft_params(operation, samples, limit_bytes, params)
130
+
131
+ if operation == "spectrogram":
132
+ return _adjust_spectrogram_params(samples, limit_bytes, params)
133
+
134
+ if operation == "eye_diagram":
135
+ return _adjust_eye_diagram_params(limit_bytes, params)
136
+
137
+ return params
138
+
139
+
140
+ def _adjust_fft_params(
141
+ operation: str, samples: int, limit_bytes: int, params: dict[str, Any]
142
+ ) -> dict[str, Any]:
143
+ """Adjust FFT/PSD parameters to fit memory limit."""
144
+ if "nfft" not in params:
145
+ return params
146
+
147
+ original_nfft = params["nfft"]
148
+ nfft = _find_max_nfft(operation, samples, limit_bytes, **params)
149
+
150
+ if nfft < original_nfft:
151
+ params["nfft"] = nfft
152
+ warnings.warn(
153
+ f"Reduced nfft from {original_nfft} to {nfft} to fit {limit_bytes / 1e6:.1f} MB limit",
154
+ UserWarning,
155
+ stacklevel=2,
156
+ )
157
+
158
+ return params
159
+
160
+
161
+ def _adjust_spectrogram_params(
162
+ samples: int, limit_bytes: int, params: dict[str, Any]
163
+ ) -> dict[str, Any]:
164
+ """Adjust spectrogram parameters to fit memory limit."""
165
+ original_nperseg = params.get("nperseg", 256)
166
+ nperseg = _find_max_nperseg(samples, limit_bytes, noverlap=params.get("noverlap"))
167
+
168
+ if nperseg < original_nperseg:
169
+ params["nperseg"] = nperseg
170
+
171
+ if "noverlap" in params:
172
+ overlap_ratio = params["noverlap"] / original_nperseg
173
+ params["noverlap"] = int(nperseg * overlap_ratio)
174
+
175
+ warnings.warn(
176
+ f"Reduced nperseg from {original_nperseg} to {nperseg} to fit {limit_bytes / 1e6:.1f} MB limit",
177
+ UserWarning,
178
+ stacklevel=2,
179
+ )
180
+
181
+ if "nfft" in params and params["nfft"] > nperseg:
182
+ params["nfft"] = nperseg
183
+
184
+ return params
185
+
186
+
187
+ def _adjust_eye_diagram_params(limit_bytes: int, params: dict[str, Any]) -> dict[str, Any]:
188
+ """Adjust eye diagram parameters to fit memory limit."""
189
+ if "num_uis" not in params:
190
+ return params
191
+
192
+ original_num_uis = params["num_uis"]
193
+ samples_per_ui = params.get("samples_per_ui", 100)
194
+ max_num_uis = _find_max_num_uis(limit_bytes, samples_per_ui)
195
+
196
+ if max_num_uis < original_num_uis:
197
+ params["num_uis"] = max_num_uis
198
+ warnings.warn(
199
+ f"Reduced num_uis from {original_num_uis} to {max_num_uis} to fit {limit_bytes / 1e6:.1f} MB limit",
200
+ UserWarning,
201
+ stacklevel=2,
202
+ )
203
+
204
+ return params
205
+
206
+
207
+ def _validate_adjusted_params(
208
+ operation: str, samples: int, params: dict[str, Any], limit_bytes: int
209
+ ) -> None:
210
+ """Verify that adjusted parameters fit within memory limit."""
211
+ final_estimate = estimate_memory(operation, samples, **params)
212
+
173
213
  if final_estimate.total > limit_bytes:
174
214
  warnings.warn(
175
215
  f"Could not adjust parameters to fit {limit_bytes / 1e6:.1f} MB limit. "
@@ -179,8 +219,6 @@ def apply_memory_limit(
179
219
  stacklevel=2,
180
220
  )
181
221
 
182
- return adjusted_params
183
-
184
222
 
185
223
  def _find_max_nfft(operation: str, samples: int, limit_bytes: int, **params: Any) -> int:
186
224
  """Binary search for maximum nfft that fits memory limit.