oscura 0.0.1__py3-none-any.whl → 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscura/__init__.py +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.0.dist-info/METADATA +300 -0
- oscura-0.1.0.dist-info/RECORD +463 -0
- oscura-0.1.0.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,651 @@
|
|
|
1
|
+
"""Extension validation system for TraceKit plugins and custom decoders.
|
|
2
|
+
|
|
3
|
+
This module provides comprehensive validation of extensions including metadata
|
|
4
|
+
validation, interface compliance checking, dependency verification, and
|
|
5
|
+
security checks.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Example:
|
|
9
|
+
>>> from oscura.extensibility.validation import validate_extension
|
|
10
|
+
>>> from pathlib import Path
|
|
11
|
+
>>>
|
|
12
|
+
>>> # Validate a plugin directory
|
|
13
|
+
>>> result = validate_extension(Path("my_plugin/"))
|
|
14
|
+
>>> if result.is_valid:
|
|
15
|
+
... print("Plugin is valid!")
|
|
16
|
+
>>> else:
|
|
17
|
+
... for error in result.errors:
|
|
18
|
+
... print(f"Error: {error}")
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import ast
|
|
24
|
+
import inspect
|
|
25
|
+
import logging
|
|
26
|
+
from dataclasses import dataclass, field
|
|
27
|
+
from typing import TYPE_CHECKING, Any
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from collections.abc import Callable
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class ValidationIssue:
|
|
38
|
+
"""A validation issue found during extension validation.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
severity: Severity level ("error", "warning", "info")
|
|
42
|
+
message: Human-readable error message
|
|
43
|
+
location: Optional location information (file, line number)
|
|
44
|
+
fix_hint: Optional suggestion for fixing the issue
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
severity: str
|
|
48
|
+
message: str
|
|
49
|
+
location: str = ""
|
|
50
|
+
fix_hint: str = ""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class ValidationResult:
|
|
55
|
+
"""Result of extension validation.
|
|
56
|
+
|
|
57
|
+
Attributes:
|
|
58
|
+
is_valid: Whether extension passed all validation checks
|
|
59
|
+
errors: List of error-level issues
|
|
60
|
+
warnings: List of warning-level issues
|
|
61
|
+
info: List of informational messages
|
|
62
|
+
metadata: Extracted extension metadata
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
is_valid: bool = True
|
|
66
|
+
errors: list[ValidationIssue] = field(default_factory=list)
|
|
67
|
+
warnings: list[ValidationIssue] = field(default_factory=list)
|
|
68
|
+
info: list[ValidationIssue] = field(default_factory=list)
|
|
69
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
70
|
+
|
|
71
|
+
def add_error(self, message: str, location: str = "", fix_hint: str = "") -> None:
|
|
72
|
+
"""Add an error issue.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
message: Error message
|
|
76
|
+
location: Optional location
|
|
77
|
+
fix_hint: Optional fix suggestion
|
|
78
|
+
"""
|
|
79
|
+
self.errors.append(
|
|
80
|
+
ValidationIssue(
|
|
81
|
+
severity="error",
|
|
82
|
+
message=message,
|
|
83
|
+
location=location,
|
|
84
|
+
fix_hint=fix_hint,
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
self.is_valid = False
|
|
88
|
+
|
|
89
|
+
def add_warning(self, message: str, location: str = "", fix_hint: str = "") -> None:
|
|
90
|
+
"""Add a warning issue.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
message: Warning message
|
|
94
|
+
location: Optional location
|
|
95
|
+
fix_hint: Optional fix suggestion
|
|
96
|
+
"""
|
|
97
|
+
self.warnings.append(
|
|
98
|
+
ValidationIssue(
|
|
99
|
+
severity="warning",
|
|
100
|
+
message=message,
|
|
101
|
+
location=location,
|
|
102
|
+
fix_hint=fix_hint,
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def add_info(self, message: str, location: str = "") -> None:
|
|
107
|
+
"""Add an informational message.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
message: Info message
|
|
111
|
+
location: Optional location
|
|
112
|
+
"""
|
|
113
|
+
self.info.append(
|
|
114
|
+
ValidationIssue(
|
|
115
|
+
severity="info",
|
|
116
|
+
message=message,
|
|
117
|
+
location=location,
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def all_issues(self) -> list[ValidationIssue]:
|
|
123
|
+
"""Get all issues sorted by severity.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
List of all issues (errors, warnings, info)
|
|
127
|
+
"""
|
|
128
|
+
return self.errors + self.warnings + self.info
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def validate_extension(
|
|
132
|
+
extension_path: Path,
|
|
133
|
+
*,
|
|
134
|
+
check_dependencies: bool = True,
|
|
135
|
+
check_security: bool = True,
|
|
136
|
+
strict: bool = False,
|
|
137
|
+
) -> ValidationResult:
|
|
138
|
+
"""Validate an extension (plugin, decoder, etc.) at the given path.
|
|
139
|
+
|
|
140
|
+
Performs comprehensive validation including:
|
|
141
|
+
- Metadata validation (pyproject.toml or plugin.yaml)
|
|
142
|
+
- Interface compliance checking
|
|
143
|
+
- Entry point validation
|
|
144
|
+
- Dependency verification
|
|
145
|
+
- Security checks (if enabled)
|
|
146
|
+
- Code quality checks (if strict)
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
extension_path: Path to extension directory
|
|
150
|
+
check_dependencies: Verify dependencies are satisfied
|
|
151
|
+
check_security: Perform security checks
|
|
152
|
+
strict: Enable strict validation (warnings become errors)
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
ValidationResult with validation outcome
|
|
156
|
+
|
|
157
|
+
Example:
|
|
158
|
+
>>> from pathlib import Path
|
|
159
|
+
>>> result = validate_extension(Path("plugins/my_decoder/"))
|
|
160
|
+
>>> if not result.is_valid:
|
|
161
|
+
... for error in result.errors:
|
|
162
|
+
... print(f"Error: {error.message}")
|
|
163
|
+
... if error.fix_hint:
|
|
164
|
+
... print(f" Fix: {error.fix_hint}")
|
|
165
|
+
|
|
166
|
+
References:
|
|
167
|
+
EXT-005: Extension Validation
|
|
168
|
+
"""
|
|
169
|
+
result = ValidationResult()
|
|
170
|
+
|
|
171
|
+
if not extension_path.exists():
|
|
172
|
+
result.add_error(
|
|
173
|
+
f"Extension path does not exist: {extension_path}",
|
|
174
|
+
fix_hint="Check the path is correct",
|
|
175
|
+
)
|
|
176
|
+
return result
|
|
177
|
+
|
|
178
|
+
if not extension_path.is_dir():
|
|
179
|
+
result.add_error(
|
|
180
|
+
f"Extension path is not a directory: {extension_path}",
|
|
181
|
+
fix_hint="Provide path to extension directory",
|
|
182
|
+
)
|
|
183
|
+
return result
|
|
184
|
+
|
|
185
|
+
result.add_info(f"Validating extension at: {extension_path}")
|
|
186
|
+
|
|
187
|
+
# Validate metadata
|
|
188
|
+
_validate_metadata(extension_path, result)
|
|
189
|
+
|
|
190
|
+
# Validate structure
|
|
191
|
+
_validate_structure(extension_path, result)
|
|
192
|
+
|
|
193
|
+
# Validate entry points
|
|
194
|
+
_validate_entry_points(extension_path, result)
|
|
195
|
+
|
|
196
|
+
# Validate implementation
|
|
197
|
+
_validate_implementation(extension_path, result)
|
|
198
|
+
|
|
199
|
+
# Check dependencies if requested
|
|
200
|
+
if check_dependencies:
|
|
201
|
+
_check_dependencies(extension_path, result)
|
|
202
|
+
|
|
203
|
+
# Security checks if requested
|
|
204
|
+
if check_security:
|
|
205
|
+
_check_security(extension_path, result)
|
|
206
|
+
|
|
207
|
+
# Convert warnings to errors in strict mode
|
|
208
|
+
if strict and result.warnings:
|
|
209
|
+
for warning in result.warnings:
|
|
210
|
+
result.add_error(
|
|
211
|
+
f"Strict mode: {warning.message}",
|
|
212
|
+
location=warning.location,
|
|
213
|
+
fix_hint=warning.fix_hint,
|
|
214
|
+
)
|
|
215
|
+
result.warnings = []
|
|
216
|
+
|
|
217
|
+
return result
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def validate_decoder_interface(
|
|
221
|
+
decoder_class: type,
|
|
222
|
+
) -> ValidationResult:
|
|
223
|
+
"""Validate that a decoder class implements the required interface.
|
|
224
|
+
|
|
225
|
+
Checks for:
|
|
226
|
+
- Required methods (decode, get_metadata)
|
|
227
|
+
- Optional methods (configure, reset, validate_config)
|
|
228
|
+
- Method signatures
|
|
229
|
+
- Return types
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
decoder_class: Decoder class to validate
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
ValidationResult with validation outcome
|
|
236
|
+
|
|
237
|
+
Example:
|
|
238
|
+
>>> class MyDecoder:
|
|
239
|
+
... def decode(self, trace):
|
|
240
|
+
... return []
|
|
241
|
+
... def get_metadata(self):
|
|
242
|
+
... return {"name": "my_decoder"}
|
|
243
|
+
>>> result = validate_decoder_interface(MyDecoder)
|
|
244
|
+
>>> assert result.is_valid
|
|
245
|
+
|
|
246
|
+
References:
|
|
247
|
+
EXT-006: Custom Decoder Registration
|
|
248
|
+
"""
|
|
249
|
+
result = ValidationResult()
|
|
250
|
+
|
|
251
|
+
# Required methods
|
|
252
|
+
required_methods = {
|
|
253
|
+
"decode": {
|
|
254
|
+
"params": ["self", "trace"],
|
|
255
|
+
"returns": "list",
|
|
256
|
+
},
|
|
257
|
+
"get_metadata": {
|
|
258
|
+
"params": ["self"],
|
|
259
|
+
"returns": "dict",
|
|
260
|
+
},
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
# Optional methods
|
|
264
|
+
optional_methods = {
|
|
265
|
+
"configure": {"params": ["self"], "returns": None},
|
|
266
|
+
"reset": {"params": ["self"], "returns": None},
|
|
267
|
+
"validate_config": {"params": ["self", "config"], "returns": "bool"},
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
# Check required methods
|
|
271
|
+
for method_name in required_methods:
|
|
272
|
+
if not hasattr(decoder_class, method_name):
|
|
273
|
+
result.add_error(
|
|
274
|
+
f"Missing required method: {method_name}",
|
|
275
|
+
location=f"{decoder_class.__name__}",
|
|
276
|
+
fix_hint=f"Add method: def {method_name}(self, ...): ...",
|
|
277
|
+
)
|
|
278
|
+
continue
|
|
279
|
+
|
|
280
|
+
method = getattr(decoder_class, method_name)
|
|
281
|
+
if not callable(method):
|
|
282
|
+
result.add_error(
|
|
283
|
+
f"Method {method_name} is not callable",
|
|
284
|
+
location=f"{decoder_class.__name__}.{method_name}",
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
# Check optional methods if present
|
|
288
|
+
for method_name in optional_methods:
|
|
289
|
+
if hasattr(decoder_class, method_name):
|
|
290
|
+
method = getattr(decoder_class, method_name)
|
|
291
|
+
if not callable(method):
|
|
292
|
+
result.add_warning(
|
|
293
|
+
f"Optional method {method_name} exists but is not callable",
|
|
294
|
+
location=f"{decoder_class.__name__}.{method_name}",
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
# Check documentation requirements (EXT-006)
|
|
298
|
+
if not decoder_class.__doc__ or not decoder_class.__doc__.strip():
|
|
299
|
+
result.add_error(
|
|
300
|
+
"Decoder class must have a docstring documenting its purpose and usage",
|
|
301
|
+
location=f"{decoder_class.__name__}",
|
|
302
|
+
fix_hint='Add docstring: """Decoder for XYZ protocol."""',
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
# Extract metadata
|
|
306
|
+
result.metadata = {
|
|
307
|
+
"class_name": decoder_class.__name__,
|
|
308
|
+
"module": decoder_class.__module__,
|
|
309
|
+
"required_methods": list(required_methods.keys()),
|
|
310
|
+
"optional_methods": list(optional_methods.keys()),
|
|
311
|
+
"has_docstring": decoder_class.__doc__ is not None,
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if result.is_valid:
|
|
315
|
+
result.add_info(f"Decoder interface validation passed for {decoder_class.__name__}")
|
|
316
|
+
|
|
317
|
+
return result
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def validate_hook_function(
|
|
321
|
+
func: Callable[[Any], Any],
|
|
322
|
+
) -> ValidationResult:
|
|
323
|
+
"""Validate that a function is suitable for use as a hook.
|
|
324
|
+
|
|
325
|
+
Checks:
|
|
326
|
+
- Function signature accepts HookContext
|
|
327
|
+
- Function returns HookContext
|
|
328
|
+
- Function has docstring
|
|
329
|
+
- Function handles exceptions
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
func: Hook function to validate
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
ValidationResult with validation outcome
|
|
336
|
+
|
|
337
|
+
Example:
|
|
338
|
+
>>> def my_hook(context):
|
|
339
|
+
... '''Validate context.'''
|
|
340
|
+
... return context
|
|
341
|
+
>>> result = validate_hook_function(my_hook)
|
|
342
|
+
>>> assert result.is_valid
|
|
343
|
+
|
|
344
|
+
References:
|
|
345
|
+
EXT-005: Hook System
|
|
346
|
+
"""
|
|
347
|
+
result = ValidationResult()
|
|
348
|
+
|
|
349
|
+
if not callable(func):
|
|
350
|
+
result.add_error( # type: ignore[unreachable]
|
|
351
|
+
"Hook must be callable",
|
|
352
|
+
fix_hint="Provide a function or callable object",
|
|
353
|
+
)
|
|
354
|
+
return result
|
|
355
|
+
|
|
356
|
+
# Check signature
|
|
357
|
+
sig = inspect.signature(func)
|
|
358
|
+
params = list(sig.parameters.keys())
|
|
359
|
+
|
|
360
|
+
if len(params) < 1:
|
|
361
|
+
result.add_error(
|
|
362
|
+
"Hook function must accept at least one parameter (context)",
|
|
363
|
+
location=func.__name__,
|
|
364
|
+
fix_hint="Add parameter: def hook(context): ...",
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
# Check for docstring
|
|
368
|
+
if not func.__doc__:
|
|
369
|
+
result.add_warning(
|
|
370
|
+
"Hook function should have a docstring",
|
|
371
|
+
location=func.__name__,
|
|
372
|
+
fix_hint='Add docstring: """Hook description."""',
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
result.metadata = {
|
|
376
|
+
"name": func.__name__,
|
|
377
|
+
"params": params,
|
|
378
|
+
"has_docstring": func.__doc__ is not None,
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if result.is_valid:
|
|
382
|
+
result.add_info(f"Hook function validation passed for {func.__name__}")
|
|
383
|
+
|
|
384
|
+
return result
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _validate_metadata(extension_path: Path, result: ValidationResult) -> None:
|
|
388
|
+
"""Validate extension metadata (pyproject.toml or plugin.yaml).
|
|
389
|
+
|
|
390
|
+
Args:
|
|
391
|
+
extension_path: Path to extension directory
|
|
392
|
+
result: ValidationResult to append issues to
|
|
393
|
+
"""
|
|
394
|
+
pyproject = extension_path / "pyproject.toml"
|
|
395
|
+
plugin_yaml = extension_path / "plugin.yaml"
|
|
396
|
+
|
|
397
|
+
if not pyproject.exists() and not plugin_yaml.exists():
|
|
398
|
+
result.add_error(
|
|
399
|
+
"No metadata file found (pyproject.toml or plugin.yaml)",
|
|
400
|
+
location=str(extension_path),
|
|
401
|
+
fix_hint="Create pyproject.toml with [project] section",
|
|
402
|
+
)
|
|
403
|
+
return
|
|
404
|
+
|
|
405
|
+
if pyproject.exists():
|
|
406
|
+
try:
|
|
407
|
+
import tomllib
|
|
408
|
+
|
|
409
|
+
with open(pyproject, "rb") as f:
|
|
410
|
+
data = tomllib.load(f)
|
|
411
|
+
|
|
412
|
+
# Check required project fields
|
|
413
|
+
if "project" not in data:
|
|
414
|
+
result.add_error(
|
|
415
|
+
"pyproject.toml missing [project] section",
|
|
416
|
+
location=str(pyproject),
|
|
417
|
+
)
|
|
418
|
+
else:
|
|
419
|
+
project = data["project"]
|
|
420
|
+
required = ["name", "version", "description"]
|
|
421
|
+
for field in required:
|
|
422
|
+
if field not in project:
|
|
423
|
+
result.add_error(
|
|
424
|
+
f"pyproject.toml missing required field: {field}",
|
|
425
|
+
location="[project]",
|
|
426
|
+
fix_hint=f'Add: {field} = "..."',
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
result.metadata.update(
|
|
430
|
+
{
|
|
431
|
+
"name": project.get("name", ""),
|
|
432
|
+
"version": project.get("version", ""),
|
|
433
|
+
"description": project.get("description", ""),
|
|
434
|
+
}
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
except Exception as e:
|
|
438
|
+
result.add_error(
|
|
439
|
+
f"Failed to parse pyproject.toml: {e}",
|
|
440
|
+
location=str(pyproject),
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def _validate_structure(extension_path: Path, result: ValidationResult) -> None:
|
|
445
|
+
"""Validate extension directory structure.
|
|
446
|
+
|
|
447
|
+
Args:
|
|
448
|
+
extension_path: Path to extension directory
|
|
449
|
+
result: ValidationResult to append issues to
|
|
450
|
+
"""
|
|
451
|
+
# Check for __init__.py
|
|
452
|
+
init_py = extension_path / "__init__.py"
|
|
453
|
+
if not init_py.exists():
|
|
454
|
+
result.add_warning(
|
|
455
|
+
"No __init__.py found",
|
|
456
|
+
location=str(extension_path),
|
|
457
|
+
fix_hint="Add __init__.py to make it a Python package",
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
# Check for tests directory
|
|
461
|
+
tests_dir = extension_path / "tests"
|
|
462
|
+
if not tests_dir.exists():
|
|
463
|
+
result.add_warning(
|
|
464
|
+
"No tests/ directory found",
|
|
465
|
+
location=str(extension_path),
|
|
466
|
+
fix_hint="Add tests/ directory with unit tests",
|
|
467
|
+
)
|
|
468
|
+
else:
|
|
469
|
+
# Check for test files
|
|
470
|
+
test_files = list(tests_dir.glob("test_*.py"))
|
|
471
|
+
if not test_files:
|
|
472
|
+
result.add_warning(
|
|
473
|
+
"No test files found in tests/",
|
|
474
|
+
location=str(tests_dir),
|
|
475
|
+
fix_hint="Add test_*.py files",
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
# Check for README
|
|
479
|
+
readme_files = list(extension_path.glob("README.*"))
|
|
480
|
+
if not readme_files:
|
|
481
|
+
result.add_warning(
|
|
482
|
+
"No README file found",
|
|
483
|
+
location=str(extension_path),
|
|
484
|
+
fix_hint="Add README.md with usage documentation",
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def _validate_entry_points(extension_path: Path, result: ValidationResult) -> None:
|
|
489
|
+
"""Validate entry points configuration.
|
|
490
|
+
|
|
491
|
+
Args:
|
|
492
|
+
extension_path: Path to extension directory
|
|
493
|
+
result: ValidationResult to append issues to
|
|
494
|
+
"""
|
|
495
|
+
pyproject = extension_path / "pyproject.toml"
|
|
496
|
+
if not pyproject.exists():
|
|
497
|
+
return
|
|
498
|
+
|
|
499
|
+
try:
|
|
500
|
+
import tomllib
|
|
501
|
+
|
|
502
|
+
with open(pyproject, "rb") as f:
|
|
503
|
+
data = tomllib.load(f)
|
|
504
|
+
|
|
505
|
+
# Check for entry points
|
|
506
|
+
if "project" not in data or "entry-points" not in data["project"]:
|
|
507
|
+
result.add_info(
|
|
508
|
+
"No entry points defined (plugin may be used as library)",
|
|
509
|
+
location=str(pyproject),
|
|
510
|
+
)
|
|
511
|
+
return
|
|
512
|
+
|
|
513
|
+
entry_points = data["project"]["entry-points"]
|
|
514
|
+
oscura_groups = [k for k in entry_points if k.startswith("oscura.")]
|
|
515
|
+
|
|
516
|
+
if not oscura_groups:
|
|
517
|
+
result.add_warning(
|
|
518
|
+
"No TraceKit entry points found",
|
|
519
|
+
location="[project.entry-points]",
|
|
520
|
+
fix_hint="Add entry point like: oscura.decoders = ...",
|
|
521
|
+
)
|
|
522
|
+
else:
|
|
523
|
+
result.metadata["entry_points"] = oscura_groups
|
|
524
|
+
result.add_info(f"Found entry point groups: {', '.join(oscura_groups)}")
|
|
525
|
+
|
|
526
|
+
except Exception as e:
|
|
527
|
+
result.add_warning(f"Failed to validate entry points: {e}")
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def _validate_implementation(extension_path: Path, result: ValidationResult) -> None:
|
|
531
|
+
"""Validate extension implementation files.
|
|
532
|
+
|
|
533
|
+
Args:
|
|
534
|
+
extension_path: Path to extension directory
|
|
535
|
+
result: ValidationResult to append issues to
|
|
536
|
+
"""
|
|
537
|
+
# Find Python files
|
|
538
|
+
py_files = list(extension_path.glob("*.py"))
|
|
539
|
+
py_files = [f for f in py_files if f.name != "__init__.py"]
|
|
540
|
+
|
|
541
|
+
if not py_files:
|
|
542
|
+
result.add_warning(
|
|
543
|
+
"No implementation files found",
|
|
544
|
+
location=str(extension_path),
|
|
545
|
+
fix_hint="Add Python module with implementation",
|
|
546
|
+
)
|
|
547
|
+
return
|
|
548
|
+
|
|
549
|
+
# Basic syntax check
|
|
550
|
+
for py_file in py_files:
|
|
551
|
+
try:
|
|
552
|
+
with open(py_file, encoding="utf-8") as f:
|
|
553
|
+
source = f.read()
|
|
554
|
+
ast.parse(source)
|
|
555
|
+
result.add_info(f"Syntax check passed: {py_file.name}")
|
|
556
|
+
except SyntaxError as e:
|
|
557
|
+
result.add_error(
|
|
558
|
+
f"Syntax error in {py_file.name}: {e}",
|
|
559
|
+
location=f"{py_file.name}:{e.lineno}",
|
|
560
|
+
fix_hint="Fix syntax error",
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
def _check_dependencies(extension_path: Path, result: ValidationResult) -> None:
|
|
565
|
+
"""Check extension dependencies are satisfied.
|
|
566
|
+
|
|
567
|
+
Args:
|
|
568
|
+
extension_path: Path to extension directory
|
|
569
|
+
result: ValidationResult to append issues to
|
|
570
|
+
"""
|
|
571
|
+
pyproject = extension_path / "pyproject.toml"
|
|
572
|
+
if not pyproject.exists():
|
|
573
|
+
return
|
|
574
|
+
|
|
575
|
+
try:
|
|
576
|
+
import tomllib
|
|
577
|
+
|
|
578
|
+
with open(pyproject, "rb") as f:
|
|
579
|
+
data = tomllib.load(f)
|
|
580
|
+
|
|
581
|
+
if "project" not in data or "dependencies" not in data["project"]:
|
|
582
|
+
result.add_info("No dependencies declared")
|
|
583
|
+
return
|
|
584
|
+
|
|
585
|
+
dependencies = data["project"]["dependencies"]
|
|
586
|
+
result.metadata["dependencies"] = dependencies
|
|
587
|
+
|
|
588
|
+
# Check if oscura is in dependencies
|
|
589
|
+
oscura_deps = [d for d in dependencies if "oscura" in d.lower()]
|
|
590
|
+
if not oscura_deps:
|
|
591
|
+
result.add_warning(
|
|
592
|
+
"TraceKit not listed in dependencies",
|
|
593
|
+
location="[project.dependencies]",
|
|
594
|
+
fix_hint='Add: "oscura>=0.1.0"',
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
except Exception as e:
|
|
598
|
+
result.add_warning(f"Failed to check dependencies: {e}")
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def _check_security(extension_path: Path, result: ValidationResult) -> None:
|
|
602
|
+
"""Perform basic security checks on extension.
|
|
603
|
+
|
|
604
|
+
Args:
|
|
605
|
+
extension_path: Path to extension directory
|
|
606
|
+
result: ValidationResult to append issues to
|
|
607
|
+
"""
|
|
608
|
+
# Check for common security issues
|
|
609
|
+
py_files = list(extension_path.rglob("*.py"))
|
|
610
|
+
|
|
611
|
+
dangerous_imports = ["pickle", "eval", "exec", "compile", "__import__"]
|
|
612
|
+
dangerous_calls = ["eval(", "exec(", "compile(", "__import__("]
|
|
613
|
+
|
|
614
|
+
for py_file in py_files:
|
|
615
|
+
try:
|
|
616
|
+
with open(py_file, encoding="utf-8") as f:
|
|
617
|
+
source = f.read()
|
|
618
|
+
|
|
619
|
+
# Check for dangerous imports
|
|
620
|
+
tree = ast.parse(source)
|
|
621
|
+
for node in ast.walk(tree):
|
|
622
|
+
if isinstance(node, ast.Import):
|
|
623
|
+
for alias in node.names:
|
|
624
|
+
if alias.name in dangerous_imports:
|
|
625
|
+
result.add_warning(
|
|
626
|
+
f"Potentially unsafe import: {alias.name}",
|
|
627
|
+
location=f"{py_file.name}:{node.lineno}",
|
|
628
|
+
fix_hint="Consider safer alternatives",
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
# Check for dangerous function calls
|
|
632
|
+
for call in dangerous_calls:
|
|
633
|
+
if call in source:
|
|
634
|
+
result.add_warning(
|
|
635
|
+
f"Potentially unsafe call: {call}",
|
|
636
|
+
location=py_file.name,
|
|
637
|
+
fix_hint="Avoid eval/exec for security",
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
except Exception:
|
|
641
|
+
# Ignore parse errors, already caught in implementation validation
|
|
642
|
+
pass
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
__all__ = [
|
|
646
|
+
"ValidationIssue",
|
|
647
|
+
"ValidationResult",
|
|
648
|
+
"validate_decoder_interface",
|
|
649
|
+
"validate_extension",
|
|
650
|
+
"validate_hook_function",
|
|
651
|
+
]
|