classifyre-cli 0.4.74__tar.gz → 0.4.75__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 (262) hide show
  1. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/.turbo/turbo-build.log +1 -1
  2. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/PKG-INFO +1 -1
  3. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/package.json +1 -1
  4. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/pyproject.toml +1 -1
  5. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/atlassian_common.py +3 -2
  6. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/email/source.py +2 -2
  7. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/google_workspace/source.py +2 -2
  8. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/microsoft_365/source.py +2 -2
  9. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/notion/client.py +3 -1
  10. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/object_storage/base.py +8 -4
  11. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/utils/file_parser.py +52 -10
  12. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/utils/test_file_parser.py +73 -0
  13. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/uv.lock +2 -2
  14. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/.gitignore +0 -0
  15. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/.python-version +0 -0
  16. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/README.md +0 -0
  17. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/main.py +0 -0
  18. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/scripts/generate_models.py +0 -0
  19. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/__init__.py +0 -0
  20. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/config.py +0 -0
  21. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/__init__.py +0 -0
  22. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/base.py +0 -0
  23. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/broken_links/__init__.py +0 -0
  24. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/broken_links/detector.py +0 -0
  25. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/config.py +0 -0
  26. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/content/__init__.py +0 -0
  27. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/custom/__init__.py +0 -0
  28. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/custom/detector.py +0 -0
  29. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/custom/extractor.py +0 -0
  30. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/custom/runners/__init__.py +0 -0
  31. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/custom/runners/_base.py +0 -0
  32. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/custom/runners/_factory.py +0 -0
  33. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/custom/runners/_gliner2.py +0 -0
  34. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/custom/runners/_image_classification.py +0 -0
  35. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/custom/runners/_llm.py +0 -0
  36. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/custom/runners/_object_detection.py +0 -0
  37. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/custom/runners/_regex.py +0 -0
  38. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/custom/runners/_text_classification.py +0 -0
  39. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/custom/trainer.py +0 -0
  40. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/dependencies.py +0 -0
  41. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/pii/__init__.py +0 -0
  42. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/pii/detector.py +0 -0
  43. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/secrets/__init__.py +0 -0
  44. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/secrets/detector.py +0 -0
  45. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/threat/__init__.py +0 -0
  46. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/threat/code_security_detector.py +0 -0
  47. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/detectors/threat/yara_detector.py +0 -0
  48. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/file_evaluation/__init__.py +0 -0
  49. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/file_evaluation/runner.py +0 -0
  50. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/main.py +0 -0
  51. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/models/generated_detectors.py +0 -0
  52. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/models/generated_input.py +0 -0
  53. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/models/generated_single_asset_scan_results.py +0 -0
  54. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/outputs/__init__.py +0 -0
  55. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/outputs/base.py +0 -0
  56. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/outputs/console.py +0 -0
  57. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/outputs/factory.py +0 -0
  58. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/outputs/file.py +0 -0
  59. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/outputs/rest.py +0 -0
  60. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/pipeline/__init__.py +0 -0
  61. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/pipeline/content_provider.py +0 -0
  62. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/pipeline/detector_pipeline.py +0 -0
  63. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/pipeline/parsed_content_provider.py +0 -0
  64. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/pipeline/text_artifact.py +0 -0
  65. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/pipeline/worker_pool.py +0 -0
  66. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/__init__.py +0 -0
  67. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/asset_metadata.py +0 -0
  68. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/azure_blob_storage/__init__.py +0 -0
  69. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/azure_blob_storage/source.py +0 -0
  70. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/base.py +0 -0
  71. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/confluence/__init__.py +0 -0
  72. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/confluence/source.py +0 -0
  73. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/databricks/__init__.py +0 -0
  74. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/databricks/source.py +0 -0
  75. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/delta_lake/__init__.py +0 -0
  76. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/delta_lake/source.py +0 -0
  77. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/dependencies.py +0 -0
  78. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/dropbox/__init__.py +0 -0
  79. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/dropbox/auth.py +0 -0
  80. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/dropbox/source.py +0 -0
  81. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/elasticsearch/__init__.py +0 -0
  82. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/elasticsearch/source.py +0 -0
  83. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/email/__init__.py +0 -0
  84. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/google_cloud_storage/__init__.py +0 -0
  85. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/google_cloud_storage/source.py +0 -0
  86. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/google_workspace/__init__.py +0 -0
  87. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/hive/__init__.py +0 -0
  88. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/hive/source.py +0 -0
  89. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/iceberg/__init__.py +0 -0
  90. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/iceberg/source.py +0 -0
  91. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/jira/__init__.py +0 -0
  92. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/jira/source.py +0 -0
  93. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/kafka/__init__.py +0 -0
  94. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/kafka/rest.py +0 -0
  95. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/kafka/source.py +0 -0
  96. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/lakehouse_base.py +0 -0
  97. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/local_folder/__init__.py +0 -0
  98. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/local_folder/source.py +0 -0
  99. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/meilisearch/__init__.py +0 -0
  100. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/meilisearch/source.py +0 -0
  101. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/microsoft_365/__init__.py +0 -0
  102. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/mongodb/__init__.py +0 -0
  103. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/mongodb/source.py +0 -0
  104. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/mssql/__init__.py +0 -0
  105. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/mssql/source.py +0 -0
  106. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/mysql/__init__.py +0 -0
  107. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/mysql/source.py +0 -0
  108. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/neo4j/__init__.py +0 -0
  109. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/neo4j/source.py +0 -0
  110. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/notion/__init__.py +0 -0
  111. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/notion/source.py +0 -0
  112. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/opensearch/__init__.py +0 -0
  113. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/opensearch/source.py +0 -0
  114. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/oracle/__init__.py +0 -0
  115. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/oracle/source.py +0 -0
  116. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/postgresql/__init__.py +0 -0
  117. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/postgresql/source.py +0 -0
  118. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/powerbi/__init__.py +0 -0
  119. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/powerbi/source.py +0 -0
  120. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/recipe_normalizer.py +0 -0
  121. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/s3_client.py +0 -0
  122. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/s3_compatible_storage/README.md +0 -0
  123. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/s3_compatible_storage/__init__.py +0 -0
  124. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/s3_compatible_storage/source.py +0 -0
  125. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/sandbox/__init__.py +0 -0
  126. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/sandbox/source.py +0 -0
  127. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/search_engine_base.py +0 -0
  128. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/servicedesk/__init__.py +0 -0
  129. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/servicedesk/source.py +0 -0
  130. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/slack/__init__.py +0 -0
  131. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/slack/source.py +0 -0
  132. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/snowflake/__init__.py +0 -0
  133. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/snowflake/source.py +0 -0
  134. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/sqlite/__init__.py +0 -0
  135. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/sqlite/source.py +0 -0
  136. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/tableau/__init__.py +0 -0
  137. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/tableau/source.py +0 -0
  138. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/tabular_base.py +0 -0
  139. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/tabular_utils.py +0 -0
  140. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/wordpress/__init__.py +0 -0
  141. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/wordpress/source.py +0 -0
  142. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/youtube/__init__.py +0 -0
  143. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/sources/youtube/source.py +0 -0
  144. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/telemetry.py +0 -0
  145. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/utils/__init__.py +0 -0
  146. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/utils/archive_extraction.py +0 -0
  147. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/utils/content_extraction.py +0 -0
  148. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/utils/dependency_groups.py +0 -0
  149. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/utils/embedded_images.py +0 -0
  150. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/utils/file_metadata.py +0 -0
  151. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/utils/file_to_images.py +0 -0
  152. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/utils/hashing.py +0 -0
  153. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/utils/legacy_office.py +0 -0
  154. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/utils/resources.py +0 -0
  155. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/utils/transcription.py +0 -0
  156. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/utils/uv_sync.py +0 -0
  157. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/utils/validation.py +0 -0
  158. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/src/utils/video_processing.py +0 -0
  159. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/__init__.py +0 -0
  160. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/_lakehouse_fakes.py +0 -0
  161. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/conftest.py +0 -0
  162. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/__init__.py +0 -0
  163. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/broken_links/test_broken_links_detector.py +0 -0
  164. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/conftest.py +0 -0
  165. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/content/__init__.py +0 -0
  166. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/custom/__init__.py +0 -0
  167. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/custom/conftest.py +0 -0
  168. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/custom/test_gliner2_classification.py +0 -0
  169. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/custom/test_invoice_extraction.py +0 -0
  170. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/custom/test_llm_runner.py +0 -0
  171. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/custom/test_pipeline_integration.py +0 -0
  172. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/custom/test_regex_runner.py +0 -0
  173. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/custom/test_runner_hardening.py +0 -0
  174. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/custom/test_transformer_runners.py +0 -0
  175. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/pii/__init__.py +0 -0
  176. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/pii/conftest.py +0 -0
  177. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/pii/sample_invoice.pdf +0 -0
  178. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/pii/test_pii_chunking.py +0 -0
  179. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/pii/test_pii_detector.py +0 -0
  180. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/pii/test_pii_detector_extended.py +0 -0
  181. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/pii/test_pii_regional_entities.py +0 -0
  182. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/pii/test_pii_severity.py +0 -0
  183. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/secrets/__init__.py +0 -0
  184. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/secrets/test_secrets_detector.py +0 -0
  185. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/secrets/test_secrets_detector_extended.py +0 -0
  186. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/test_base_detector.py +0 -0
  187. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/test_custom_detector_examples_runtime.py +0 -0
  188. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/test_detector_catalog_commercial.py +0 -0
  189. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/test_detector_pipeline_types.py +0 -0
  190. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/test_detector_schema_examples.py +0 -0
  191. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/test_detector_types.py +0 -0
  192. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/test_phase2_detectors.py +0 -0
  193. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/test_registry.py +0 -0
  194. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/threat/__init__.py +0 -0
  195. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/threat/test_code_security_detector.py +0 -0
  196. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/detectors/threat/test_yara_detector.py +0 -0
  197. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/integration/test_wordpress_broken_links_detector.py +0 -0
  198. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/integration/test_wordpress_links_assets.py +0 -0
  199. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/pipeline/test_detector_outcomes.py +0 -0
  200. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/pipeline/test_detector_pipeline.py +0 -0
  201. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/pipeline/test_worker_pool.py +0 -0
  202. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_assets_metadata_catalog.py +0 -0
  203. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_azure_blob_storage_source.py +0 -0
  204. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_base_source_attachment.py +0 -0
  205. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_base_source_sampling.py +0 -0
  206. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_config.py +0 -0
  207. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_confluence_source.py +0 -0
  208. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_custom_extractor.py +0 -0
  209. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_databricks_source.py +0 -0
  210. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_delta_lake_source.py +0 -0
  211. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_dependency_groups.py +0 -0
  212. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_dropbox_auth_command.py +0 -0
  213. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_dropbox_source.py +0 -0
  214. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_elasticsearch_source.py +0 -0
  215. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_email_source.py +0 -0
  216. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_file_evaluation_runner.py +0 -0
  217. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_google_cloud_storage_source.py +0 -0
  218. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_google_workspace_source.py +0 -0
  219. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_hashing.py +0 -0
  220. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_hive_source.py +0 -0
  221. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_iceberg_source.py +0 -0
  222. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_jira_source.py +0 -0
  223. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_kafka_source.py +0 -0
  224. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_local_folder_source.py +0 -0
  225. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_main.py +0 -0
  226. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_meilisearch_source.py +0 -0
  227. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_microsoft_365_source.py +0 -0
  228. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_mongodb_source.py +0 -0
  229. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_mssql_source.py +0 -0
  230. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_mysql_source.py +0 -0
  231. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_neo4j_source.py +0 -0
  232. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_notion_source.py +0 -0
  233. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_opensearch_source.py +0 -0
  234. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_oracle_source.py +0 -0
  235. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_outputs.py +0 -0
  236. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_postgresql_source.py +0 -0
  237. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_powerbi_source.py +0 -0
  238. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_recipe_normalizer.py +0 -0
  239. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_s3_compatible_storage_source.py +0 -0
  240. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_sampling_automatic.py +0 -0
  241. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_sandbox_source.py +0 -0
  242. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_servicedesk_source.py +0 -0
  243. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_slack_source.py +0 -0
  244. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_snowflake_source.py +0 -0
  245. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_source_dependency_groups.py +0 -0
  246. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_source_strategy_conformance.py +0 -0
  247. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_sqlite_source.py +0 -0
  248. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_tableau_source.py +0 -0
  249. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_tabular_automatic_sampling.py +0 -0
  250. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_tabular_utils.py +0 -0
  251. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_uv_sync.py +0 -0
  252. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_wordpress_source.py +0 -0
  253. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_youtube_source.py +0 -0
  254. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/test_youtube_source_integration.py +0 -0
  255. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/utils/test_archive_extraction.py +0 -0
  256. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/utils/test_content_extraction.py +0 -0
  257. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/utils/test_embedded_images.py +0 -0
  258. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/utils/test_file_metadata.py +0 -0
  259. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/utils/test_file_to_images.py +0 -0
  260. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/utils/test_legacy_office.py +0 -0
  261. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/utils/test_transcription.py +0 -0
  262. {classifyre_cli-0.4.74 → classifyre_cli-0.4.75}/tests/utils/test_video_processing.py +0 -0
@@ -1,3 +1,3 @@
1
1
  $ uv sync
2
- Resolved 303 packages in 170ms
2
+ Resolved 303 packages in 208ms
3
3
  Checked 51 packages in 1ms
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: classifyre-cli
3
- Version: 0.4.74
3
+ Version: 0.4.75
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.74",
3
+ "version": "0.4.75",
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.74"
3
+ version = "0.4.75"
4
4
  description = "Classifyre CLI — scan and classify unstructured data sources"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -11,6 +11,7 @@ from urllib.parse import urljoin, urlsplit, urlunsplit
11
11
 
12
12
  import requests
13
13
 
14
+ from ..utils.file_parser import normalize_mime_type
14
15
  from ..utils.hashing import normalize_http_url
15
16
 
16
17
  logger = logging.getLogger(__name__)
@@ -86,7 +87,7 @@ def extract_urls_from_text(text: str) -> list[str]:
86
87
 
87
88
 
88
89
  def is_tabular_mime_type(mime_type: str) -> bool:
89
- normalized = mime_type.split(";", 1)[0].strip().lower()
90
+ normalized = normalize_mime_type(mime_type)
90
91
  return normalized in TABULAR_MIME_TYPES
91
92
 
92
93
 
@@ -206,7 +207,7 @@ class AtlassianCloudClient:
206
207
  for chunk in response.iter_content(chunk_size=8192):
207
208
  if chunk:
208
209
  chunks.append(chunk)
209
- mime = response.headers.get("Content-Type", "").split(";")[0].strip().lower()
210
+ mime = normalize_mime_type(response.headers.get("Content-Type", ""))
210
211
  return b"".join(chunks), mime
211
212
 
212
213
  def iter_confluence_results(
@@ -25,7 +25,7 @@ from ...models.generated_single_asset_scan_results import (
25
25
  )
26
26
  from ...utils.content_extraction import html_to_text
27
27
  from ...utils.file_metadata import extract_file_metadata
28
- from ...utils.file_parser import resolve_mime_type
28
+ from ...utils.file_parser import normalize_mime_type, resolve_mime_type
29
29
  from ...utils.hashing import hash_id
30
30
  from ..base import BaseSource
31
31
  from ..dependencies import require_module
@@ -488,7 +488,7 @@ class EmailSource(BaseSource):
488
488
 
489
489
  @staticmethod
490
490
  def _asset_type_from_mime(mime: str) -> OutputAssetType:
491
- normalized = (mime or "").split(";", 1)[0].strip().lower()
491
+ normalized = normalize_mime_type(mime)
492
492
  if normalized.startswith("image/"):
493
493
  return OutputAssetType.IMAGE
494
494
  if normalized.startswith("audio/"):
@@ -28,7 +28,7 @@ from ...models.generated_single_asset_scan_results import (
28
28
  SingleAssetScanResults,
29
29
  )
30
30
  from ...utils.file_metadata import extract_file_metadata
31
- from ...utils.file_parser import resolve_mime_type
31
+ from ...utils.file_parser import normalize_mime_type, resolve_mime_type
32
32
  from ...utils.hashing import hash_id
33
33
  from ..base import BaseSource
34
34
  from ..dependencies import require_module
@@ -572,7 +572,7 @@ class GoogleWorkspaceSource(BaseSource):
572
572
  # -- Asset construction --
573
573
 
574
574
  def _asset_type_from_mime_or_key(self, mime_type: str | None, key: str) -> OutputAssetType:
575
- normalized_mime = (mime_type or "").split(";", maxsplit=1)[0].strip().lower()
575
+ normalized_mime = normalize_mime_type(mime_type)
576
576
  extension = PurePosixPath(key).suffix.lower()
577
577
 
578
578
  if normalized_mime in _TABULAR_MIME_TYPES:
@@ -32,7 +32,7 @@ from ...models.generated_single_asset_scan_results import (
32
32
  SingleAssetScanResults,
33
33
  )
34
34
  from ...utils.file_metadata import extract_file_metadata
35
- from ...utils.file_parser import resolve_mime_type
35
+ from ...utils.file_parser import normalize_mime_type, resolve_mime_type
36
36
  from ...utils.hashing import hash_id
37
37
  from ..base import BaseSource
38
38
  from ..dependencies import require_module
@@ -679,7 +679,7 @@ class Microsoft365Source(BaseSource):
679
679
  # -- Asset construction --
680
680
 
681
681
  def _asset_type_from_mime_or_key(self, mime_type: str | None, key: str) -> OutputAssetType:
682
- normalized_mime = (mime_type or "").split(";", maxsplit=1)[0].strip().lower()
682
+ normalized_mime = normalize_mime_type(mime_type)
683
683
  extension = PurePosixPath(key).suffix.lower()
684
684
 
685
685
  if normalized_mime in _TABULAR_MIME_TYPES:
@@ -6,6 +6,8 @@ from typing import Any
6
6
 
7
7
  import requests
8
8
 
9
+ from ...utils.file_parser import normalize_mime_type
10
+
9
11
  logger = logging.getLogger(__name__)
10
12
 
11
13
  NOTION_API_BASE = "https://api.notion.com/v1"
@@ -154,7 +156,7 @@ class NotionClient:
154
156
  for chunk in response.iter_content(chunk_size=8192):
155
157
  if chunk:
156
158
  chunks.append(chunk)
157
- mime = response.headers.get("Content-Type", "").split(";")[0].strip().lower()
159
+ mime = normalize_mime_type(response.headers.get("Content-Type", ""))
158
160
  return b"".join(chunks), mime
159
161
 
160
162
  def get_page(self, page_id: str) -> dict[str, Any]:
@@ -25,7 +25,11 @@ from ...models.generated_single_asset_scan_results import (
25
25
  from ...utils.archive_extraction import ArchiveMember, is_archive_mime, iter_archive_members
26
26
  from ...utils.embedded_images import EmbeddedImage, has_embedded_images, iter_embedded_images
27
27
  from ...utils.file_metadata import extract_file_metadata
28
- from ...utils.file_parser import infer_mime_type_from_file_name, resolve_mime_type
28
+ from ...utils.file_parser import (
29
+ infer_mime_type_from_file_name,
30
+ normalize_mime_type,
31
+ resolve_mime_type,
32
+ )
29
33
  from ...utils.hashing import hash_id, unhash_id
30
34
  from ..base import BaseSource
31
35
  from ..dependencies import require_module
@@ -325,7 +329,7 @@ class ObjectStorageSourceBase(BaseSource, ABC):
325
329
  return ref.key
326
330
 
327
331
  def _asset_type_from_mime_or_key(self, mime_type: str | None, key: str) -> OutputAssetType:
328
- normalized_mime = (mime_type or "").split(";", maxsplit=1)[0].strip().lower()
332
+ normalized_mime = normalize_mime_type(mime_type)
329
333
  extension = self._file_extension(key)
330
334
 
331
335
  if normalized_mime in _TABULAR_MIME_TYPES:
@@ -397,7 +401,7 @@ class ObjectStorageSourceBase(BaseSource, ABC):
397
401
 
398
402
  def _build_snapshot(self, ref: ObjectRef) -> ContentSnapshot:
399
403
  if self._discovery_only or not self._include_content_preview():
400
- mime = (ref.content_type_hint or "").split(";", maxsplit=1)[0].strip().lower()
404
+ mime = normalize_mime_type(ref.content_type_hint)
401
405
  if not mime:
402
406
  mime = infer_mime_type_from_file_name(self._object_file_name(ref))
403
407
  return ContentSnapshot(
@@ -426,7 +430,7 @@ class ObjectStorageSourceBase(BaseSource, ABC):
426
430
  declared_mime_type=content_type_hint or ref.content_type_hint or "",
427
431
  file_name=self._object_file_name(ref),
428
432
  )
429
- normalized_mime = mime_type.split(";", 1)[0].strip().lower()
433
+ normalized_mime = normalize_mime_type(mime_type)
430
434
 
431
435
  # Images and audio/video are extractable automatically. Opaque binaries
432
436
  # remain available to binary detectors but have no text page stream.
@@ -61,6 +61,37 @@ class ParsedBytes:
61
61
  parse_error: str | None = None
62
62
 
63
63
 
64
+ OCTET_STREAM = "application/octet-stream"
65
+
66
+ # Spellings of "I don't know what these bytes are". Only the first is the
67
+ # registered type, but object stores are inconsistent: S3-compatible services
68
+ # (Backblaze B2 among them) label every upload `binary/octet-stream`. Because
69
+ # that is not the IANA spelling, treating it as a *real* content type made it
70
+ # win over magic-byte detection and filename inference — so every object served
71
+ # that way skipped MIME resolution entirely, matched no branch in extract_text,
72
+ # and returned no text AND no error. Assets were then recorded as scanned and
73
+ # empty, which is indistinguishable from a genuinely empty file.
74
+ _GENERIC_BINARY_MIME_TYPES = frozenset(
75
+ {
76
+ OCTET_STREAM,
77
+ "binary/octet-stream",
78
+ "application/binary",
79
+ "application/unknown",
80
+ "unknown/unknown",
81
+ }
82
+ )
83
+
84
+
85
+ def _canonical_mime_type(mime_type: str) -> str:
86
+ """Collapse every "unknown bytes" spelling onto the registered one.
87
+
88
+ Downstream code compares against ``application/octet-stream`` in a dozen
89
+ places (is_binary, archive handling, detector content-type matching); mapping
90
+ aliases here means none of those have to know the alias list.
91
+ """
92
+ return OCTET_STREAM if mime_type in _GENERIC_BINARY_MIME_TYPES else mime_type
93
+
94
+
64
95
  _TEXT_RAW_MIME_TYPES = {
65
96
  "application/json",
66
97
  "application/xml",
@@ -327,10 +358,21 @@ def _require_file_processing(module_name: str) -> object:
327
358
  return require_module(module_name, "file parser", ["file-processing"])
328
359
 
329
360
 
330
- def _normalize_mime_type(mime_type: str | None) -> str:
361
+ def normalize_mime_type(mime_type: str | None) -> str:
362
+ """Strip parameters, lower-case, and collapse "unknown bytes" aliases.
363
+
364
+ The single place MIME strings are normalized. Sources must use this rather
365
+ than an inline ``split(";")[0].strip().lower()``: that idiom looks equivalent
366
+ but skips the alias collapsing, which is exactly how `binary/octet-stream`
367
+ from S3-compatible stores slipped through as a real content type.
368
+ """
331
369
  if not mime_type:
332
370
  return ""
333
- return str(mime_type).split(";", 1)[0].strip().lower()
371
+ return _canonical_mime_type(str(mime_type).split(";", 1)[0].strip().lower())
372
+
373
+
374
+ # Internal alias kept so the many call sites in this module stay unchanged.
375
+ _normalize_mime_type = normalize_mime_type
334
376
 
335
377
 
336
378
  def _file_extension(file_name: str) -> str:
@@ -357,7 +399,7 @@ def normalize_detected_mime_type(detected_mime_type: str, file_name: str) -> str
357
399
  mime = _normalize_mime_type(detected_mime_type)
358
400
  inferred_mime = infer_mime_type_from_file_name(file_name)
359
401
 
360
- if not mime or mime == "application/octet-stream":
402
+ if not mime or mime == OCTET_STREAM:
361
403
  return inferred_mime
362
404
 
363
405
  if mime == "text/plain" and inferred_mime in _TABULAR_MIME_TYPES:
@@ -1015,17 +1057,17 @@ def resolve_mime_type(
1015
1057
  detected_mime = _normalize_mime_type(detect_mime_type(file_bytes))
1016
1058
  inferred_mime = infer_mime_type_from_file_name(file_name)
1017
1059
 
1018
- if declared_mime and declared_mime != "application/octet-stream":
1060
+ if declared_mime and declared_mime != OCTET_STREAM:
1019
1061
  mime_type = declared_mime
1020
- elif detected_mime and detected_mime != "application/octet-stream":
1062
+ elif detected_mime and detected_mime != OCTET_STREAM:
1021
1063
  mime_type = detected_mime
1022
- elif inferred_mime and inferred_mime != "application/octet-stream":
1064
+ elif inferred_mime and inferred_mime != OCTET_STREAM:
1023
1065
  mime_type = inferred_mime
1024
1066
  else:
1025
- mime_type = declared_mime or detected_mime or inferred_mime or "application/octet-stream"
1067
+ mime_type = declared_mime or detected_mime or inferred_mime or OCTET_STREAM
1026
1068
 
1027
1069
  mime_type = normalize_detected_mime_type(mime_type, file_name)
1028
- if mime_type == "application/octet-stream" and inferred_mime != "application/octet-stream":
1070
+ if mime_type == OCTET_STREAM and inferred_mime != OCTET_STREAM:
1029
1071
  mime_type = inferred_mime
1030
1072
 
1031
1073
  # A text-like claim (declared upload header or file extension) must survive
@@ -1033,10 +1075,10 @@ def resolve_mime_type(
1033
1075
  # control-character garbage that poisons every text consumer downstream.
1034
1076
  if (
1035
1077
  file_bytes
1036
- and detected_mime == "application/octet-stream"
1078
+ and detected_mime == OCTET_STREAM
1037
1079
  and (mime_type.startswith("text/") or mime_type in ("application/json", "application/xml"))
1038
1080
  ):
1039
- return "application/octet-stream"
1081
+ return OCTET_STREAM
1040
1082
 
1041
1083
  return mime_type
1042
1084
 
@@ -665,6 +665,79 @@ class TestOleMimeResolution:
665
665
  assert resolve_mime_type(ole, file_name="blob") == "application/x-ole-storage"
666
666
 
667
667
 
668
+ class TestGenericBinaryMimeAliases:
669
+ """S3-compatible stores declare `binary/octet-stream`, not the IANA spelling.
670
+
671
+ Treating that as a real content type let it outrank magic bytes and the file
672
+ extension, so every object from Backblaze B2 resolved to a type no extractor
673
+ handles — producing no text AND no error, which recorded the asset as
674
+ scanned-and-empty. Covers all formats, not just legacy Office.
675
+ """
676
+
677
+ OLE = b"\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1" + b"\x00" * 512
678
+
679
+ @pytest.mark.parametrize(
680
+ "declared",
681
+ [
682
+ "binary/octet-stream",
683
+ "application/octet-stream",
684
+ "application/binary",
685
+ "application/unknown",
686
+ "unknown/unknown",
687
+ "BINARY/OCTET-STREAM",
688
+ "binary/octet-stream; charset=binary",
689
+ "",
690
+ ],
691
+ )
692
+ def test_generic_declaration_never_outranks_content(self, declared: str) -> None:
693
+ from src.utils.file_parser import resolve_mime_type
694
+
695
+ assert (
696
+ resolve_mime_type(self.OLE, declared_mime_type=declared, file_name="Cert.doc")
697
+ == "application/msword"
698
+ )
699
+
700
+ @pytest.mark.parametrize(
701
+ ("file_bytes", "file_name", "expected"),
702
+ [
703
+ (b"%PDF-1.4\n" + b"x" * 200, "a.pdf", "application/pdf"),
704
+ (b"\x89PNG\r\n\x1a\n" + b"\x00" * 64, "c.png", "image/png"),
705
+ (OLE, "b.xls", "application/vnd.ms-excel"),
706
+ (OLE, "d.ppt", "application/vnd.ms-powerpoint"),
707
+ ],
708
+ )
709
+ def test_every_format_survives_a_generic_declaration(
710
+ self, file_bytes: bytes, file_name: str, expected: str
711
+ ) -> None:
712
+ from src.utils.file_parser import resolve_mime_type
713
+
714
+ resolved = resolve_mime_type(
715
+ file_bytes, declared_mime_type="binary/octet-stream", file_name=file_name
716
+ )
717
+ assert resolved == expected
718
+
719
+ def test_normalize_mime_type_collapses_aliases(self) -> None:
720
+ from src.utils.file_parser import normalize_mime_type
721
+
722
+ for alias in ("binary/octet-stream", "application/binary", "unknown/unknown"):
723
+ assert normalize_mime_type(alias) == "application/octet-stream"
724
+ # A real type is left alone.
725
+ assert normalize_mime_type("application/pdf; charset=utf-8") == "application/pdf"
726
+
727
+ def test_real_declared_type_still_wins(self) -> None:
728
+ """The alias collapsing must not make us ignore genuine declarations."""
729
+ from src.utils.file_parser import resolve_mime_type
730
+
731
+ assert (
732
+ resolve_mime_type(
733
+ b"nothing recognisable here",
734
+ declared_mime_type="text/csv",
735
+ file_name="rows.bin",
736
+ )
737
+ == "text/csv"
738
+ )
739
+
740
+
668
741
  class TestRtfExtraction:
669
742
  def test_text_extracted(self) -> None:
670
743
  pytest.importorskip("striprtf")
@@ -830,7 +830,7 @@ wheels = [
830
830
 
831
831
  [[package]]
832
832
  name = "classifyre-cli"
833
- version = "0.4.74"
833
+ version = "0.4.75"
834
834
  source = { editable = "." }
835
835
  dependencies = [
836
836
  { name = "beautifulsoup4" },
@@ -1355,7 +1355,7 @@ youtube = [
1355
1355
 
1356
1356
  [[package]]
1357
1357
  name = "classifyre-schemas"
1358
- version = "0.4.74"
1358
+ version = "0.4.75"
1359
1359
  source = { editable = "../../packages/schemas" }
1360
1360
  dependencies = [
1361
1361
  { name = "fastjsonschema" },
File without changes