classifyre-cli 0.4.8__tar.gz → 0.4.9__tar.gz

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 (177) hide show
  1. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/.turbo/turbo-build.log +1 -1
  2. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/PKG-INFO +1 -1
  3. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/package.json +1 -1
  4. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/pyproject.toml +1 -1
  5. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/outputs/rest.py +71 -0
  6. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_outputs.py +3 -0
  7. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/uv.lock +182 -150
  8. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/.gitignore +0 -0
  9. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/.python-version +0 -0
  10. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/README.md +0 -0
  11. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/main.py +0 -0
  12. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/scripts/generate_models.py +0 -0
  13. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/__init__.py +0 -0
  14. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/__init__.py +0 -0
  15. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/base.py +0 -0
  16. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/broken_links/__init__.py +0 -0
  17. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/broken_links/detector.py +0 -0
  18. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/config.py +0 -0
  19. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/content/__init__.py +0 -0
  20. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/custom/__init__.py +0 -0
  21. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/custom/detector.py +0 -0
  22. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/custom/extractor.py +0 -0
  23. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/custom/runners/__init__.py +0 -0
  24. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/custom/runners/_base.py +0 -0
  25. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/custom/runners/_factory.py +0 -0
  26. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/custom/runners/_feature_extraction.py +0 -0
  27. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/custom/runners/_gliner2.py +0 -0
  28. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/custom/runners/_image_classification.py +0 -0
  29. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/custom/runners/_llm.py +0 -0
  30. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/custom/runners/_object_detection.py +0 -0
  31. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/custom/runners/_regex.py +0 -0
  32. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/custom/runners/_text_classification.py +0 -0
  33. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/custom/trainer.py +0 -0
  34. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/dependencies.py +0 -0
  35. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/pii/__init__.py +0 -0
  36. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/pii/detector.py +0 -0
  37. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/secrets/__init__.py +0 -0
  38. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/secrets/detector.py +0 -0
  39. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/threat/__init__.py +0 -0
  40. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/threat/code_security_detector.py +0 -0
  41. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/detectors/threat/yara_detector.py +0 -0
  42. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/main.py +0 -0
  43. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/models/generated_detectors.py +0 -0
  44. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/models/generated_input.py +0 -0
  45. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/models/generated_single_asset_scan_results.py +0 -0
  46. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/outputs/__init__.py +0 -0
  47. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/outputs/base.py +0 -0
  48. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/outputs/console.py +0 -0
  49. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/outputs/factory.py +0 -0
  50. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/outputs/file.py +0 -0
  51. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/pipeline/__init__.py +0 -0
  52. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/pipeline/content_provider.py +0 -0
  53. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/pipeline/detector_pipeline.py +0 -0
  54. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/pipeline/parsed_content_provider.py +0 -0
  55. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/pipeline/worker_pool.py +0 -0
  56. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sandbox/__init__.py +0 -0
  57. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sandbox/runner.py +0 -0
  58. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/__init__.py +0 -0
  59. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/atlassian_common.py +0 -0
  60. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/azure_blob_storage/__init__.py +0 -0
  61. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/azure_blob_storage/source.py +0 -0
  62. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/base.py +0 -0
  63. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/confluence/__init__.py +0 -0
  64. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/confluence/source.py +0 -0
  65. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/databricks/__init__.py +0 -0
  66. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/databricks/source.py +0 -0
  67. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/dependencies.py +0 -0
  68. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/google_cloud_storage/__init__.py +0 -0
  69. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/google_cloud_storage/source.py +0 -0
  70. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/hive/__init__.py +0 -0
  71. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/hive/source.py +0 -0
  72. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/jira/__init__.py +0 -0
  73. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/jira/source.py +0 -0
  74. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/mongodb/__init__.py +0 -0
  75. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/mongodb/source.py +0 -0
  76. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/mssql/__init__.py +0 -0
  77. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/mssql/source.py +0 -0
  78. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/mysql/__init__.py +0 -0
  79. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/mysql/source.py +0 -0
  80. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/neo4j/__init__.py +0 -0
  81. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/neo4j/source.py +0 -0
  82. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/object_storage/base.py +0 -0
  83. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/oracle/__init__.py +0 -0
  84. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/oracle/source.py +0 -0
  85. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/postgresql/__init__.py +0 -0
  86. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/postgresql/source.py +0 -0
  87. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/powerbi/__init__.py +0 -0
  88. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/powerbi/source.py +0 -0
  89. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/recipe_normalizer.py +0 -0
  90. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/s3_compatible_storage/README.md +0 -0
  91. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/s3_compatible_storage/__init__.py +0 -0
  92. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/s3_compatible_storage/source.py +0 -0
  93. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/servicedesk/__init__.py +0 -0
  94. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/servicedesk/source.py +0 -0
  95. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/slack/__init__.py +0 -0
  96. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/slack/source.py +0 -0
  97. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/snowflake/__init__.py +0 -0
  98. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/snowflake/source.py +0 -0
  99. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/sqlite/__init__.py +0 -0
  100. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/sqlite/source.py +0 -0
  101. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/tableau/__init__.py +0 -0
  102. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/tableau/source.py +0 -0
  103. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/tabular_base.py +0 -0
  104. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/tabular_utils.py +0 -0
  105. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/wordpress/__init__.py +0 -0
  106. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/sources/wordpress/source.py +0 -0
  107. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/telemetry.py +0 -0
  108. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/utils/__init__.py +0 -0
  109. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/utils/content_extraction.py +0 -0
  110. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/utils/file_parser.py +0 -0
  111. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/utils/hashing.py +0 -0
  112. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/utils/uv_sync.py +0 -0
  113. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/src/utils/validation.py +0 -0
  114. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/__init__.py +0 -0
  115. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/conftest.py +0 -0
  116. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/__init__.py +0 -0
  117. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/broken_links/test_broken_links_detector.py +0 -0
  118. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/conftest.py +0 -0
  119. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/content/__init__.py +0 -0
  120. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/custom/__init__.py +0 -0
  121. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/custom/conftest.py +0 -0
  122. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/custom/test_invoice_extraction.py +0 -0
  123. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/custom/test_pipeline_integration.py +0 -0
  124. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/custom/test_regex_runner.py +0 -0
  125. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/custom/test_transformer_runners.py +0 -0
  126. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/pii/__init__.py +0 -0
  127. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/pii/conftest.py +0 -0
  128. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/pii/sample_invoice.pdf +0 -0
  129. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/pii/test_pii_detector.py +0 -0
  130. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/pii/test_pii_detector_extended.py +0 -0
  131. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/secrets/__init__.py +0 -0
  132. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/secrets/test_secrets_detector.py +0 -0
  133. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/secrets/test_secrets_detector_extended.py +0 -0
  134. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/test_base_detector.py +0 -0
  135. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/test_custom_detector_examples_runtime.py +0 -0
  136. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/test_detector_catalog_commercial.py +0 -0
  137. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/test_detector_pipeline_types.py +0 -0
  138. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/test_detector_schema_examples.py +0 -0
  139. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/test_detector_types.py +0 -0
  140. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/test_phase2_detectors.py +0 -0
  141. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/test_registry.py +0 -0
  142. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/threat/__init__.py +0 -0
  143. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/threat/test_code_security_detector.py +0 -0
  144. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/detectors/threat/test_yara_detector.py +0 -0
  145. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/integration/test_wordpress_broken_links_detector.py +0 -0
  146. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/integration/test_wordpress_links_assets.py +0 -0
  147. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/pipeline/test_detector_pipeline.py +0 -0
  148. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/pipeline/test_worker_pool.py +0 -0
  149. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_azure_blob_storage_source.py +0 -0
  150. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_base_source_attachment.py +0 -0
  151. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_base_source_sampling.py +0 -0
  152. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_confluence_source.py +0 -0
  153. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_custom_extractor.py +0 -0
  154. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_databricks_source.py +0 -0
  155. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_google_cloud_storage_source.py +0 -0
  156. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_hashing.py +0 -0
  157. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_hive_source.py +0 -0
  158. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_jira_source.py +0 -0
  159. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_mongodb_source.py +0 -0
  160. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_mssql_source.py +0 -0
  161. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_mysql_source.py +0 -0
  162. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_neo4j_source.py +0 -0
  163. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_oracle_source.py +0 -0
  164. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_postgresql_source.py +0 -0
  165. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_powerbi_source.py +0 -0
  166. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_recipe_normalizer.py +0 -0
  167. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_s3_compatible_storage_source.py +0 -0
  168. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_servicedesk_source.py +0 -0
  169. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_slack_source.py +0 -0
  170. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_snowflake_source.py +0 -0
  171. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_source_dependency_groups.py +0 -0
  172. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_sqlite_source.py +0 -0
  173. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_tableau_source.py +0 -0
  174. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_tabular_utils.py +0 -0
  175. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/test_wordpress_source.py +0 -0
  176. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/utils/test_content_extraction.py +0 -0
  177. {classifyre_cli-0.4.8 → classifyre_cli-0.4.9}/tests/utils/test_file_parser.py +0 -0
@@ -1,3 +1,3 @@
1
1
  $ uv sync
2
- Resolved 256 packages in 185ms
2
+ Resolved 256 packages in 201ms
3
3
  Checked 50 packages in 1ms
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: classifyre-cli
3
- Version: 0.4.8
3
+ Version: 0.4.9
4
4
  Summary: Classifyre CLI — scan and classify unstructured data sources
5
5
  License: MIT
6
6
  Keywords: data,ingestion,metadata,pii,secrets,unstructured
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classifyre/cli",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "build": "uv sync",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "classifyre-cli"
3
- version = "0.4.8"
3
+ version = "0.4.9"
4
4
  description = "Classifyre CLI — scan and classify unstructured data sources"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -1,17 +1,85 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
+ import random
4
5
  from typing import Any, Literal, cast
5
6
  from urllib.parse import urljoin
6
7
 
7
8
  import requests # type: ignore[import-untyped]
8
9
  from pydantic import BaseModel, ConfigDict, Field
10
+ from requests.adapters import HTTPAdapter
11
+ from urllib3.util.retry import Retry # type: ignore[import-untyped]
9
12
 
10
13
  from .base import OutputRuntimeContext, OutputType
11
14
 
12
15
  logger = logging.getLogger(__name__)
13
16
 
14
17
 
18
+ class _JitteredRetry(Retry):
19
+ """urllib3 Retry subclass that adds ±25 % multiplicative jitter to the
20
+ computed backoff so that multiple concurrent CLI jobs do not all retry
21
+ at exactly the same moment (thundering-herd mitigation).
22
+
23
+ The jitter is applied *after* the standard exponential backoff formula
24
+ and the backoff_max cap, so it never pushes the delay above
25
+ backoff_max * 1.25.
26
+ """
27
+
28
+ _JITTER_FACTOR: float = 0.25
29
+
30
+ def get_backoff_time(self) -> float: # type: ignore[override]
31
+ base = super().get_backoff_time()
32
+ if base == 0:
33
+ return 0.0
34
+ lo = base * (1 - self._JITTER_FACTOR)
35
+ hi = base * (1 + self._JITTER_FACTOR)
36
+ return random.uniform(lo, hi)
37
+
38
+
39
+ # Retry policy for CLI → API REST calls.
40
+ #
41
+ # What we retry and why:
42
+ # connect=8 — pod restarted / not yet ready (RemoteDisconnected, ConnectionReset,
43
+ # ConnectTimeout). Request never reached the application.
44
+ # read=8 — API is under load and slow to respond (ReadTimeout). Safe to retry
45
+ # because all endpoints are idempotent (bulk ingest is upsert-based,
46
+ # status/findings updates are set-operations).
47
+ # status=8 — transient HTTP errors from an overloaded or restarting API:
48
+ # 408 Request Timeout - API-level timeout
49
+ # 429 Too Many Requests - rate-limited / backpressure
50
+ # 502 Bad Gateway - proxy has no upstream yet
51
+ # 503 Service Unavail. - under-pressure / pod not ready
52
+ # 504 Gateway Timeout - upstream took too long
53
+ #
54
+ # backoff_factor=2, backoff_max=60: exponential cap at 60 s, with ±25 % jitter
55
+ # (see _JitteredRetry). Approximate wait schedule between attempts:
56
+ # attempt 1 → immediate (0 s)
57
+ # attempt 2 → ~2 s
58
+ # attempt 3 → ~4 s
59
+ # attempt 4 → ~8 s
60
+ # attempt 5 → ~16 s
61
+ # attempt 6 → ~32 s
62
+ # attempt 7 → ~60 s (capped)
63
+ # attempt 8 → ~60 s (capped)
64
+ # Total extra wait: ~182 s (~3 min) — covers extended load spikes on a
65
+ # single-node VPS before event-loop pressure drops. Worst-case a single
66
+ # call costs 8 * 120 s + 182 s = ~18 min, acceptable for long-running scans.
67
+ #
68
+ # POST and PATCH are explicitly allowed: without this urllib3 only retries
69
+ # idempotent methods (GET/HEAD) by default.
70
+ _RETRY_POLICY = _JitteredRetry(
71
+ total=8,
72
+ connect=8,
73
+ read=8,
74
+ status=8,
75
+ backoff_factor=2,
76
+ backoff_max=60,
77
+ status_forcelist={408, 429, 502, 503, 504},
78
+ allowed_methods={"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"},
79
+ raise_on_status=False,
80
+ )
81
+
82
+
15
83
  def _drop_none_recursive(value: Any) -> Any:
16
84
  if isinstance(value, dict):
17
85
  return {key: _drop_none_recursive(item) for key, item in value.items() if item is not None}
@@ -63,6 +131,9 @@ class RestOutputSink:
63
131
  self.base_url = base_url.rstrip("/")
64
132
  self.timeout_sec = timeout_sec
65
133
  self.session = requests.Session()
134
+ adapter = HTTPAdapter(max_retries=_RETRY_POLICY)
135
+ self.session.mount("http://", adapter)
136
+ self.session.mount("https://", adapter)
66
137
  self._runner_id = context.runner_id
67
138
  self._seen_hashes: set[str] = set()
68
139
 
@@ -158,6 +158,9 @@ class _FakeSession:
158
158
  self.responses = responses
159
159
  self.calls: list[dict[str, Any]] = []
160
160
 
161
+ def mount(self, prefix: str, adapter: Any) -> None: # noqa: ARG002
162
+ """Absorb HTTPAdapter.mount() calls from RestOutputSink.__init__."""
163
+
161
164
  def request(
162
165
  self,
163
166
  method: str,