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
@@ -5,7 +5,7 @@ including API reference, usage examples, and metadata extraction from docstrings
5
5
 
6
6
 
7
7
  Example:
8
- >>> from oscura.extensibility.docs import generate_extension_docs
8
+ >>> from oscura.core.extensibility.docs import generate_extension_docs
9
9
  >>> from pathlib import Path
10
10
  >>>
11
11
  >>> # Generate documentation for an extension
@@ -561,39 +561,65 @@ def _process_section(
561
561
  include_examples: Extract examples
562
562
  """
563
563
  if section in ["args", "arguments", "parameters"]:
564
- # Parse parameters
565
- for line in content:
566
- line = line.strip()
567
- if ":" in line:
568
- parts = line.split(":", 1)
569
- param_name = parts[0].strip()
570
- param_desc = parts[1].strip()
571
- func_doc.parameters.append((param_name, param_desc))
572
-
564
+ _process_parameters_section(func_doc, content)
573
565
  elif section in ["returns", "return"]:
574
- func_doc.returns = "\n".join(content).strip()
575
-
566
+ _process_returns_section(func_doc, content)
576
567
  elif section in ["example", "examples"] and include_examples:
577
- # Extract code blocks
578
- in_code = False
579
- code_lines = []
580
-
581
- for line in content:
582
- if ">>>" in line or "..." in line:
583
- in_code = True
584
- code_lines.append(line.strip())
585
- elif in_code:
586
- if line.strip() and not line.strip().startswith("#"):
587
- if not (">>>" in line or "..." in line):
588
- in_code = False
589
- if code_lines:
590
- func_doc.examples.append("\n".join(code_lines))
591
- code_lines = []
592
- else:
593
- code_lines.append(line.strip())
594
-
595
- if code_lines:
596
- func_doc.examples.append("\n".join(code_lines))
568
+ _process_examples_section(func_doc, content)
569
+
570
+
571
+ def _process_parameters_section(func_doc: FunctionDoc, content: list[str]) -> None:
572
+ """Process parameters section.
573
+
574
+ Args:
575
+ func_doc: FunctionDoc to populate.
576
+ content: Section content lines.
577
+ """
578
+ for line in content:
579
+ line = line.strip()
580
+ if ":" in line:
581
+ parts = line.split(":", 1)
582
+ param_name = parts[0].strip()
583
+ param_desc = parts[1].strip()
584
+ func_doc.parameters.append((param_name, param_desc))
585
+
586
+
587
+ def _process_returns_section(func_doc: FunctionDoc, content: list[str]) -> None:
588
+ """Process returns section.
589
+
590
+ Args:
591
+ func_doc: FunctionDoc to populate.
592
+ content: Section content lines.
593
+ """
594
+ func_doc.returns = "\n".join(content).strip()
595
+
596
+
597
+ def _process_examples_section(func_doc: FunctionDoc, content: list[str]) -> None:
598
+ """Process examples section.
599
+
600
+ Args:
601
+ func_doc: FunctionDoc to populate.
602
+ content: Section content lines.
603
+ """
604
+ in_code = False
605
+ code_lines = []
606
+
607
+ for line in content:
608
+ if ">>>" in line or "..." in line:
609
+ in_code = True
610
+ code_lines.append(line.strip())
611
+ elif in_code:
612
+ if line.strip() and not line.strip().startswith("#"):
613
+ if not (">>>" in line or "..." in line):
614
+ in_code = False
615
+ if code_lines:
616
+ func_doc.examples.append("\n".join(code_lines))
617
+ code_lines = []
618
+ else:
619
+ code_lines.append(line.strip())
620
+
621
+ if code_lines:
622
+ func_doc.examples.append("\n".join(code_lines))
597
623
 
598
624
 
599
625
  def _get_name_from_ast(node: ast.expr) -> str:
@@ -622,13 +648,30 @@ def _generate_markdown(docs: ExtensionDocs) -> str:
622
648
  Returns:
623
649
  Markdown string
624
650
  """
625
- lines = []
651
+ lines: list[str] = []
652
+
653
+ # Title and metadata
654
+ _add_markdown_header(lines, docs)
626
655
 
627
- # Title
656
+ # Dependencies
657
+ _add_markdown_dependencies(lines, docs)
658
+
659
+ # Modules
660
+ _add_markdown_modules(lines, docs)
661
+
662
+ return "\n".join(lines)
663
+
664
+
665
+ def _add_markdown_header(lines: list[str], docs: ExtensionDocs) -> None:
666
+ """Add header section to markdown.
667
+
668
+ Args:
669
+ lines: List to append lines to.
670
+ docs: ExtensionDocs to render.
671
+ """
628
672
  lines.append(f"# {docs.name}")
629
673
  lines.append("")
630
674
 
631
- # Metadata
632
675
  if docs.version:
633
676
  lines.append(f"**Version:** {docs.version}")
634
677
  lines.append("")
@@ -639,7 +682,14 @@ def _generate_markdown(docs: ExtensionDocs) -> str:
639
682
  lines.append(docs.description)
640
683
  lines.append("")
641
684
 
642
- # Dependencies
685
+
686
+ def _add_markdown_dependencies(lines: list[str], docs: ExtensionDocs) -> None:
687
+ """Add dependencies section to markdown.
688
+
689
+ Args:
690
+ lines: List to append lines to.
691
+ docs: ExtensionDocs to render.
692
+ """
643
693
  if "dependencies" in docs.metadata:
644
694
  lines.append("## Dependencies")
645
695
  lines.append("")
@@ -647,7 +697,14 @@ def _generate_markdown(docs: ExtensionDocs) -> str:
647
697
  lines.append(f"- {dep}")
648
698
  lines.append("")
649
699
 
650
- # Modules
700
+
701
+ def _add_markdown_modules(lines: list[str], docs: ExtensionDocs) -> None:
702
+ """Add modules section to markdown.
703
+
704
+ Args:
705
+ lines: List to append lines to.
706
+ docs: ExtensionDocs to render.
707
+ """
651
708
  for module in docs.modules:
652
709
  lines.append(f"## Module: {module.name}")
653
710
  lines.append("")
@@ -656,43 +713,74 @@ def _generate_markdown(docs: ExtensionDocs) -> str:
656
713
  lines.append("")
657
714
 
658
715
  # Classes
659
- for cls in module.classes:
660
- lines.append(f"### Class: {cls.name}")
661
- lines.append("")
662
- if cls.docstring:
663
- lines.append(cls.docstring)
664
- lines.append("")
665
-
666
- # Methods
667
- if cls.methods:
668
- lines.append("#### Methods")
669
- lines.append("")
670
- for method in cls.methods:
671
- lines.append(f"##### {method.name}")
672
- lines.append("")
673
- if method.signature:
674
- lines.append("```python")
675
- lines.append(method.signature)
676
- lines.append("```")
677
- lines.append("")
678
- if method.docstring:
679
- lines.append(method.docstring)
680
- lines.append("")
716
+ _add_markdown_classes(lines, module)
681
717
 
682
718
  # Functions
683
- for func in module.functions:
684
- lines.append(f"### Function: {func.name}")
719
+ _add_markdown_functions(lines, module)
720
+
721
+
722
+ def _add_markdown_classes(lines: list[str], module: ModuleDoc) -> None:
723
+ """Add classes section to markdown.
724
+
725
+ Args:
726
+ lines: List to append lines to.
727
+ module: ModuleDoc to render.
728
+ """
729
+ for cls in module.classes:
730
+ lines.append(f"### Class: {cls.name}")
731
+ lines.append("")
732
+ if cls.docstring:
733
+ lines.append(cls.docstring)
685
734
  lines.append("")
686
- if func.signature:
687
- lines.append("```python")
688
- lines.append(func.signature)
689
- lines.append("```")
690
- lines.append("")
691
- if func.docstring:
692
- lines.append(func.docstring)
693
- lines.append("")
694
735
 
695
- return "\n".join(lines)
736
+ # Methods
737
+ if cls.methods:
738
+ lines.append("#### Methods")
739
+ lines.append("")
740
+ _add_markdown_methods(lines, cls.methods)
741
+
742
+
743
+ def _add_markdown_methods(lines: list[str], methods: list[FunctionDoc]) -> None:
744
+ """Add methods section to markdown.
745
+
746
+ Args:
747
+ lines: List to append lines to.
748
+ methods: List of method documentation.
749
+ """
750
+ for method in methods:
751
+ lines.append(f"##### {method.name}")
752
+ lines.append("")
753
+ _add_markdown_function_details(lines, method)
754
+
755
+
756
+ def _add_markdown_functions(lines: list[str], module: ModuleDoc) -> None:
757
+ """Add functions section to markdown.
758
+
759
+ Args:
760
+ lines: List to append lines to.
761
+ module: ModuleDoc to render.
762
+ """
763
+ for func in module.functions:
764
+ lines.append(f"### Function: {func.name}")
765
+ lines.append("")
766
+ _add_markdown_function_details(lines, func)
767
+
768
+
769
+ def _add_markdown_function_details(lines: list[str], func: FunctionDoc) -> None:
770
+ """Add function/method details to markdown.
771
+
772
+ Args:
773
+ lines: List to append lines to.
774
+ func: FunctionDoc to render.
775
+ """
776
+ if func.signature:
777
+ lines.append("```python")
778
+ lines.append(func.signature)
779
+ lines.append("```")
780
+ lines.append("")
781
+ if func.docstring:
782
+ lines.append(func.docstring)
783
+ lines.append("")
696
784
 
697
785
 
698
786
  def _generate_html(docs: ExtensionDocs) -> str:
@@ -250,7 +250,7 @@ class ExtensionPointRegistry:
250
250
  cls._instance._hook_error_policy = HookErrorPolicy.CONTINUE
251
251
  cls._instance._log_hook_errors = True
252
252
  cls._instance._initialized = False # type: ignore[has-type]
253
- cls._instance._registration_counter = 0 # type: ignore[misc, attr-defined]
253
+ cls._instance._registration_counter: int = 0 # type: ignore[misc, attr-defined]
254
254
  return cls._instance
255
255
 
256
256
  def initialize(self) -> None:
@@ -530,99 +530,183 @@ class ExtensionPointRegistry:
530
530
  if name:
531
531
  return self.get_algorithm(category, name)
532
532
 
533
- # Auto-select based on criteria
533
+ # Get and filter candidates
534
534
  candidates = list(self._algorithms[category].values()) # type: ignore[attr-defined]
535
-
536
535
  if not candidates:
537
536
  raise KeyError(f"No algorithms registered in category '{category}'")
538
537
 
539
- # Filter by required capabilities (EXT-003)
540
- if required_capabilities:
541
- filtered = []
542
- for algo in candidates:
543
- if all(algo.can(cap) for cap in required_capabilities):
544
- filtered.append(algo)
545
- candidates = filtered
538
+ candidates = self._filter_by_capabilities(candidates, required_capabilities, category)
539
+ candidates = self._filter_by_constraints(candidates, constraints, category)
546
540
 
547
- if not candidates:
541
+ # Select best match
542
+ sort_key = self._get_sort_key(optimize_for)
543
+ candidates.sort(key=sort_key)
544
+ return candidates[0]
545
+
546
+ def _filter_by_capabilities(
547
+ self,
548
+ candidates: list[RegisteredAlgorithm],
549
+ required_capabilities: list[str] | None,
550
+ category: str,
551
+ ) -> list[RegisteredAlgorithm]:
552
+ """Filter algorithms by required capabilities.
553
+
554
+ Args:
555
+ candidates: List of candidate algorithms.
556
+ required_capabilities: Required capabilities.
557
+ category: Category name for error messages.
558
+
559
+ Returns:
560
+ Filtered list of algorithms.
561
+
562
+ Raises:
563
+ KeyError: If no algorithms match capabilities.
564
+ """
565
+ if not required_capabilities:
566
+ return candidates
567
+
568
+ filtered = [
569
+ algo for algo in candidates if all(algo.can(cap) for cap in required_capabilities)
570
+ ]
571
+
572
+ if not filtered:
548
573
  raise KeyError(f"No algorithms match required capabilities in category '{category}'")
549
574
 
550
- # Apply constraints
551
- if constraints:
552
- filtered = []
553
- for algo in candidates:
554
- match = True
555
- for key, value in constraints.items():
556
- if key.startswith("performance."):
557
- perf_key = key.split(".", 1)[1]
558
- if algo.performance.get(perf_key) != value:
559
- match = False
560
- break
561
- elif key.startswith("capabilities."):
562
- cap_key = key.split(".", 1)[1]
563
- if algo.capabilities.get(cap_key) != value:
564
- match = False
565
- break
566
- elif key == "supports":
567
- if isinstance(value, list):
568
- if not any(s in algo.supports for s in value):
569
- match = False
570
- break
571
- elif value not in algo.supports:
572
- match = False
573
- break
574
- elif key == "memory_usage":
575
- if algo.memory_usage != value:
576
- match = False
577
- break
578
- if match:
579
- filtered.append(algo)
580
- candidates = filtered
575
+ return filtered
581
576
 
582
- if not candidates:
577
+ def _filter_by_constraints(
578
+ self,
579
+ candidates: list[RegisteredAlgorithm],
580
+ constraints: dict[str, Any] | None,
581
+ category: str,
582
+ ) -> list[RegisteredAlgorithm]:
583
+ """Filter algorithms by constraints.
584
+
585
+ Args:
586
+ candidates: List of candidate algorithms.
587
+ constraints: Constraint dictionary.
588
+ category: Category name for error messages.
589
+
590
+ Returns:
591
+ Filtered list of algorithms.
592
+
593
+ Raises:
594
+ KeyError: If no algorithms match constraints.
595
+ """
596
+ if not constraints:
597
+ return candidates
598
+
599
+ filtered = [algo for algo in candidates if self._matches_constraints(algo, constraints)]
600
+
601
+ if not filtered:
583
602
  raise KeyError(f"No algorithms match constraints in category '{category}'")
584
603
 
585
- # Sort by optimization criteria
604
+ return filtered
605
+
606
+ def _matches_constraints(
607
+ self,
608
+ algo: RegisteredAlgorithm,
609
+ constraints: dict[str, Any],
610
+ ) -> bool:
611
+ """Check if algorithm matches all constraints.
612
+
613
+ Args:
614
+ algo: Algorithm to check.
615
+ constraints: Constraint dictionary.
616
+
617
+ Returns:
618
+ True if all constraints match.
619
+ """
620
+ for key, value in constraints.items():
621
+ if not self._check_single_constraint(algo, key, value):
622
+ return False
623
+ return True
624
+
625
+ def _check_single_constraint(
626
+ self,
627
+ algo: RegisteredAlgorithm,
628
+ key: str,
629
+ value: Any,
630
+ ) -> bool:
631
+ """Check if algorithm matches a single constraint.
632
+
633
+ Args:
634
+ algo: Algorithm to check.
635
+ key: Constraint key.
636
+ value: Expected value.
637
+
638
+ Returns:
639
+ True if constraint matches.
640
+ """
641
+ if key.startswith("performance."):
642
+ return self._check_performance_constraint(algo, key, value)
643
+ elif key.startswith("capabilities."):
644
+ return self._check_capabilities_constraint(algo, key, value)
645
+ elif key == "supports":
646
+ return self._check_supports_constraint(algo, value)
647
+ elif key == "memory_usage":
648
+ memory_check: bool = bool(algo.memory_usage == value)
649
+ return memory_check
650
+ return True
651
+
652
+ def _check_performance_constraint(
653
+ self, algo: RegisteredAlgorithm, key: str, value: Any
654
+ ) -> bool:
655
+ """Check performance constraint."""
656
+ perf_key = key.split(".", 1)[1]
657
+ result: bool = bool(algo.performance.get(perf_key) == value)
658
+ return result
659
+
660
+ def _check_capabilities_constraint(
661
+ self, algo: RegisteredAlgorithm, key: str, value: Any
662
+ ) -> bool:
663
+ """Check capabilities constraint."""
664
+ cap_key = key.split(".", 1)[1]
665
+ result: bool = bool(algo.capabilities.get(cap_key) == value)
666
+ return result
667
+
668
+ def _check_supports_constraint(self, algo: RegisteredAlgorithm, value: Any) -> bool:
669
+ """Check supports constraint."""
670
+ if isinstance(value, list):
671
+ result: bool = bool(any(s in algo.supports for s in value))
672
+ return result
673
+ else:
674
+ in_supports: bool = bool(value in algo.supports)
675
+ return in_supports
676
+
677
+ def _get_sort_key(self, optimize_for: str) -> Any:
678
+ """Get sort key function for optimization criterion.
679
+
680
+ Args:
681
+ optimize_for: Optimization target.
682
+
683
+ Returns:
684
+ Sort key function.
685
+ """
586
686
  if optimize_for == "speed":
587
687
 
588
- def sort_key(a): # type: ignore[no-untyped-def]
589
- return (
590
- 0
591
- if a.performance.get("speed") == "fast"
592
- else 1
593
- if a.performance.get("speed") == "medium"
594
- else 2,
595
- -a.priority,
596
- )
688
+ def sort_key(a: RegisteredAlgorithm) -> tuple[int, int]:
689
+ speed = a.performance.get("speed")
690
+ rank = 0 if speed == "fast" else 1 if speed == "medium" else 2
691
+ return (rank, -a.priority)
597
692
  elif optimize_for == "accuracy":
598
693
 
599
- def sort_key(a): # type: ignore[no-untyped-def]
600
- return (
601
- 0
602
- if a.performance.get("accuracy") == "high"
603
- else 1
604
- if a.performance.get("accuracy") == "medium"
605
- else 2,
606
- -a.priority,
607
- )
694
+ def sort_key(a: RegisteredAlgorithm) -> tuple[int, int]:
695
+ acc = a.performance.get("accuracy")
696
+ rank = 0 if acc == "high" else 1 if acc == "medium" else 2
697
+ return (rank, -a.priority)
608
698
  elif optimize_for == "memory":
609
699
 
610
- def sort_key(a): # type: ignore[no-untyped-def]
611
- return (
612
- 0
613
- if a.performance.get("memory") == "low"
614
- else 1
615
- if a.performance.get("memory") == "medium"
616
- else 2,
617
- -a.priority,
618
- )
700
+ def sort_key(a: RegisteredAlgorithm) -> tuple[int, int]:
701
+ mem = a.performance.get("memory")
702
+ rank = 0 if mem == "low" else 1 if mem == "medium" else 2
703
+ return (rank, -a.priority)
619
704
  else:
620
- # Default to priority only
621
- def sort_key(a): # type: ignore[no-untyped-def]
622
- return -a.priority
623
705
 
624
- candidates.sort(key=sort_key)
625
- return candidates[0] # type: ignore[no-any-return]
706
+ def sort_key(a: RegisteredAlgorithm) -> tuple[int, int]:
707
+ return (-a.priority, 0)
708
+
709
+ return sort_key
626
710
 
627
711
  def list_algorithms(
628
712
  self,
@@ -5,7 +5,7 @@ and per-plugin configuration for proper log management.
5
5
 
6
6
 
7
7
  Example:
8
- >>> from oscura.extensibility.logging import get_plugin_logger
8
+ >>> from oscura.core.extensibility.logging import get_plugin_logger
9
9
  >>> logger = get_plugin_logger("my_plugin")
10
10
  >>> logger.info("Plugin initialized")
11
11
 
@@ -15,7 +15,7 @@ from .registry import AlgorithmRegistry
15
15
  if TYPE_CHECKING:
16
16
  from collections.abc import Callable
17
17
 
18
- from ..core.types import WaveformTrace
18
+ from oscura.core.types import WaveformTrace
19
19
 
20
20
 
21
21
  @dataclass
@@ -117,7 +117,7 @@ class PluginManager:
117
117
  """
118
118
 
119
119
  # Standard entry point groups
120
- ENTRY_POINT_GROUPS = [ # noqa: RUF012
120
+ ENTRY_POINT_GROUPS = [
121
121
  "oscura.decoders",
122
122
  "oscura.measurements",
123
123
  "oscura.loaders",
@@ -312,7 +312,41 @@ def _write_readme(template: PluginTemplate) -> None:
312
312
  class_name = _get_class_name(template)
313
313
  entry_point_group = _get_entry_point_group(template.plugin_type)
314
314
 
315
- content = textwrap.dedent(f"""\
315
+ content = _generate_readme_content(template, class_name, entry_point_group)
316
+ (template.output_dir / "README.md").write_text(content)
317
+
318
+
319
+ def _generate_readme_content(
320
+ template: PluginTemplate, class_name: str, entry_point_group: str
321
+ ) -> str:
322
+ """Generate README.md content.
323
+
324
+ Args:
325
+ template: Plugin template configuration.
326
+ class_name: PascalCase class name.
327
+ entry_point_group: Entry point group name.
328
+
329
+ Returns:
330
+ README content as string.
331
+ """
332
+ header = _generate_readme_header(template, class_name)
333
+ usage = _generate_readme_usage(template, class_name)
334
+ dev = _generate_readme_development(template)
335
+ metadata = _generate_readme_metadata(template, entry_point_group)
336
+ return f"{header}\n{usage}\n{dev}\n{metadata}"
337
+
338
+
339
+ def _generate_readme_header(template: PluginTemplate, class_name: str) -> str:
340
+ """Generate README header and installation section.
341
+
342
+ Args:
343
+ template: Plugin template configuration.
344
+ class_name: Class name.
345
+
346
+ Returns:
347
+ Header section content.
348
+ """
349
+ return textwrap.dedent(f"""\
316
350
  # {class_name}
317
351
 
318
352
  {template.description}
@@ -325,7 +359,20 @@ def _write_readme(template: PluginTemplate) -> None:
325
359
  cd {template.output_dir.name}
326
360
  pip install -e .
327
361
  ```
362
+ """)
363
+
364
+
365
+ def _generate_readme_usage(template: PluginTemplate, class_name: str) -> str:
366
+ """Generate usage section.
328
367
 
368
+ Args:
369
+ template: Plugin template configuration.
370
+ class_name: Class name.
371
+
372
+ Returns:
373
+ Usage section content.
374
+ """
375
+ return textwrap.dedent(f"""\
329
376
  ## Usage
330
377
 
331
378
  The plugin integrates automatically with Oscura via entry points:
@@ -359,7 +406,19 @@ def _write_readme(template: PluginTemplate) -> None:
359
406
  # Show plugin info
360
407
  oscura plugin info {template.name}
361
408
  ```
409
+ """)
410
+
411
+
412
+ def _generate_readme_development(template: PluginTemplate) -> str:
413
+ """Generate development section.
362
414
 
415
+ Args:
416
+ template: Plugin template configuration.
417
+
418
+ Returns:
419
+ Development section content.
420
+ """
421
+ return textwrap.dedent(f"""\
363
422
  ## Development
364
423
 
365
424
  ### Running Tests
@@ -380,7 +439,20 @@ def _write_readme(template: PluginTemplate) -> None:
380
439
  # Type checking
381
440
  mypy {template.name}.py
382
441
  ```
442
+ """)
443
+
444
+
445
+ def _generate_readme_metadata(template: PluginTemplate, entry_point_group: str) -> str:
446
+ """Generate metadata section.
447
+
448
+ Args:
449
+ template: Plugin template configuration.
450
+ entry_point_group: Entry point group name.
383
451
 
452
+ Returns:
453
+ Metadata section content.
454
+ """
455
+ return textwrap.dedent(f"""\
384
456
  ## Plugin Type: {template.plugin_type}
385
457
 
386
458
  This is a **{template.plugin_type}** plugin for Oscura.
@@ -407,8 +479,6 @@ def _write_readme(template: PluginTemplate) -> None:
407
479
  {template.version}
408
480
  """)
409
481
 
410
- (template.output_dir / "README.md").write_text(content)
411
-
412
482
 
413
483
  def _write_pyproject_toml(template: PluginTemplate) -> None:
414
484
  """Write pyproject.toml for plugin packaging.