classifyre-cli 0.7.0__tar.gz → 0.7.1__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 (394) hide show
  1. classifyre_cli-0.7.1/.turbo/turbo-build.log +3 -0
  2. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/PKG-INFO +1 -1
  3. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/package.json +1 -1
  4. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/pyproject.toml +1 -1
  5. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/main.py +5 -0
  6. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/outputs/rest.py +7 -0
  7. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/base.py +30 -0
  8. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/databricks/source.py +56 -12
  9. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/hive/source.py +4 -1
  10. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/mysql/source.py +1 -1
  11. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/neo4j/source.py +3 -0
  12. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/tabular_base.py +15 -0
  13. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/sql_lineage.py +11 -0
  14. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_databricks_source.py +90 -0
  15. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_hive_source.py +23 -0
  16. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_mysql_source.py +104 -0
  17. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_neo4j_source.py +26 -0
  18. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_sql_lineage.py +42 -1
  19. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/uv.lock +64 -64
  20. classifyre_cli-0.7.0/.turbo/turbo-build.log +0 -3
  21. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/.gitignore +0 -0
  22. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/.python-version +0 -0
  23. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/README.md +0 -0
  24. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/main.py +0 -0
  25. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/scripts/generate_models.py +0 -0
  26. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/scripts/generate_runtime_packages.py +0 -0
  27. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/scripts/generate_sdk_completions.py +0 -0
  28. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/__init__.py +0 -0
  29. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/augmentation/__init__.py +0 -0
  30. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/augmentation/contract.py +0 -0
  31. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/augmentation/preview.py +0 -0
  32. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/augmentation/runner.py +0 -0
  33. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/augmentation/sdk.py +0 -0
  34. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/augmentation/session.py +0 -0
  35. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/config.py +0 -0
  36. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/__init__.py +0 -0
  37. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/base.py +0 -0
  38. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/broken_links/__init__.py +0 -0
  39. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/broken_links/detector.py +0 -0
  40. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/config.py +0 -0
  41. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/content/__init__.py +0 -0
  42. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/__init__.py +0 -0
  43. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/detector.py +0 -0
  44. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/extractor.py +0 -0
  45. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/__init__.py +0 -0
  46. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_base.py +0 -0
  47. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_custom_detector.py +0 -0
  48. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_decision.py +0 -0
  49. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_factory.py +0 -0
  50. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_gliner2.py +0 -0
  51. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_image_classification.py +0 -0
  52. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_llm.py +0 -0
  53. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_object_detection.py +0 -0
  54. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_provider_call.py +0 -0
  55. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_regex.py +0 -0
  56. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_tag.py +0 -0
  57. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_text_classification.py +0 -0
  58. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/trainer.py +0 -0
  59. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom_detector/__init__.py +0 -0
  60. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom_detector/contract.py +0 -0
  61. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom_detector/files.py +0 -0
  62. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom_detector/preview.py +0 -0
  63. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom_detector/results.py +0 -0
  64. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom_detector/runner.py +0 -0
  65. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom_detector/sdk.py +0 -0
  66. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom_detector/session.py +0 -0
  67. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/dependencies.py +0 -0
  68. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/engine_version.py +0 -0
  69. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/errors.py +0 -0
  70. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/pii/__init__.py +0 -0
  71. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/pii/detector.py +0 -0
  72. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/secrets/__init__.py +0 -0
  73. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/secrets/detector.py +0 -0
  74. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/threat/__init__.py +0 -0
  75. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/threat/code_security_detector.py +0 -0
  76. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/threat/yara_detector.py +0 -0
  77. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/file_evaluation/__init__.py +0 -0
  78. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/file_evaluation/asset_fixture.py +0 -0
  79. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/file_evaluation/runner.py +0 -0
  80. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/graph/__init__.py +0 -0
  81. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/graph/edges.py +0 -0
  82. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/models/generated_detectors.py +0 -0
  83. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/models/generated_input.py +0 -0
  84. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/models/generated_single_asset_scan_results.py +0 -0
  85. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/__init__.py +0 -0
  86. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/child.py +0 -0
  87. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/cli.py +0 -0
  88. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/cohort.py +0 -0
  89. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/contract.py +0 -0
  90. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/execute.py +0 -0
  91. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/files.py +0 -0
  92. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/groups.py +0 -0
  93. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/outputs.py +0 -0
  94. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/packages.py +0 -0
  95. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/protocol.py +0 -0
  96. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/redact.py +0 -0
  97. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/scaffold.py +0 -0
  98. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/sdk.py +0 -0
  99. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/serialize.py +0 -0
  100. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/outputs/__init__.py +0 -0
  101. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/outputs/base.py +0 -0
  102. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/outputs/console.py +0 -0
  103. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/outputs/factory.py +0 -0
  104. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/outputs/file.py +0 -0
  105. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/__init__.py +0 -0
  106. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/asset_payload_server.py +0 -0
  107. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/content_provider.py +0 -0
  108. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/detector_breaker.py +0 -0
  109. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/detector_pipeline.py +0 -0
  110. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/finding_context.py +0 -0
  111. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/media_artifact.py +0 -0
  112. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/parsed_content_provider.py +0 -0
  113. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/payload_window.py +0 -0
  114. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/scan_cache.py +0 -0
  115. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/text_artifact.py +0 -0
  116. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/worker_pool.py +0 -0
  117. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/__init__.py +0 -0
  118. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/asset_metadata.py +0 -0
  119. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/atlassian_common.py +0 -0
  120. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/azure_blob_storage/__init__.py +0 -0
  121. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/azure_blob_storage/source.py +0 -0
  122. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/confluence/__init__.py +0 -0
  123. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/confluence/source.py +0 -0
  124. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/custom/__init__.py +0 -0
  125. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/custom/env.py +0 -0
  126. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/custom/runner.py +0 -0
  127. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/custom/source.py +0 -0
  128. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/databricks/__init__.py +0 -0
  129. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/delta_lake/__init__.py +0 -0
  130. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/delta_lake/source.py +0 -0
  131. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/dependencies.py +0 -0
  132. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/dremio/__init__.py +0 -0
  133. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/dremio/client.py +0 -0
  134. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/dremio/source.py +0 -0
  135. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/dropbox/__init__.py +0 -0
  136. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/dropbox/auth.py +0 -0
  137. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/dropbox/source.py +0 -0
  138. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/elasticsearch/__init__.py +0 -0
  139. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/elasticsearch/source.py +0 -0
  140. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/email/__init__.py +0 -0
  141. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/email/source.py +0 -0
  142. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/git/__init__.py +0 -0
  143. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/git/source.py +0 -0
  144. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/google_cloud_storage/__init__.py +0 -0
  145. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/google_cloud_storage/source.py +0 -0
  146. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/google_workspace/__init__.py +0 -0
  147. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/google_workspace/source.py +0 -0
  148. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/hive/__init__.py +0 -0
  149. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/hugging_face/__init__.py +0 -0
  150. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/hugging_face/source.py +0 -0
  151. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/iceberg/__init__.py +0 -0
  152. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/iceberg/source.py +0 -0
  153. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/jira/__init__.py +0 -0
  154. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/jira/source.py +0 -0
  155. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/kafka/__init__.py +0 -0
  156. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/kafka/rest.py +0 -0
  157. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/kafka/source.py +0 -0
  158. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/lakehouse_base.py +0 -0
  159. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/local_folder/__init__.py +0 -0
  160. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/local_folder/source.py +0 -0
  161. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/meilisearch/__init__.py +0 -0
  162. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/meilisearch/source.py +0 -0
  163. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/microsoft_365/__init__.py +0 -0
  164. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/microsoft_365/source.py +0 -0
  165. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/mongodb/__init__.py +0 -0
  166. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/mongodb/source.py +0 -0
  167. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/mssql/__init__.py +0 -0
  168. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/mssql/source.py +0 -0
  169. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/mysql/__init__.py +0 -0
  170. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/neo4j/__init__.py +0 -0
  171. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/notion/__init__.py +0 -0
  172. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/notion/client.py +0 -0
  173. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/notion/source.py +0 -0
  174. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/object_storage/base.py +0 -0
  175. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/opensearch/__init__.py +0 -0
  176. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/opensearch/source.py +0 -0
  177. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/oracle/__init__.py +0 -0
  178. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/oracle/source.py +0 -0
  179. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/postgresql/__init__.py +0 -0
  180. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/postgresql/source.py +0 -0
  181. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/powerbi/__init__.py +0 -0
  182. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/powerbi/source.py +0 -0
  183. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/recipe_normalizer.py +0 -0
  184. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/reddit/__init__.py +0 -0
  185. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/reddit/source.py +0 -0
  186. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/s3_client.py +0 -0
  187. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/s3_compatible_storage/README.md +0 -0
  188. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/s3_compatible_storage/__init__.py +0 -0
  189. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/s3_compatible_storage/source.py +0 -0
  190. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/sandbox/__init__.py +0 -0
  191. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/sandbox/source.py +0 -0
  192. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/search_engine_base.py +0 -0
  193. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/servicedesk/__init__.py +0 -0
  194. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/servicedesk/source.py +0 -0
  195. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/slack/__init__.py +0 -0
  196. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/slack/source.py +0 -0
  197. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/snowflake/__init__.py +0 -0
  198. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/snowflake/source.py +0 -0
  199. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/sqlite/__init__.py +0 -0
  200. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/sqlite/source.py +0 -0
  201. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/tableau/__init__.py +0 -0
  202. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/tableau/source.py +0 -0
  203. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/tabular_utils.py +0 -0
  204. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/wordpress/__init__.py +0 -0
  205. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/wordpress/source.py +0 -0
  206. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/youtube/__init__.py +0 -0
  207. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/youtube/source.py +0 -0
  208. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/telemetry.py +0 -0
  209. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/__init__.py +0 -0
  210. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/archive_extraction.py +0 -0
  211. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/content_extraction.py +0 -0
  212. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/dependency_groups.py +0 -0
  213. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/embedded_files.py +0 -0
  214. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/external_video.py +0 -0
  215. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/file_metadata.py +0 -0
  216. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/file_parser.py +0 -0
  217. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/file_to_images.py +0 -0
  218. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/hashing.py +0 -0
  219. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/legacy_office.py +0 -0
  220. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/payload.py +0 -0
  221. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/range_reader.py +0 -0
  222. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/redaction.py +0 -0
  223. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/resources.py +0 -0
  224. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/sampling_cursor.py +0 -0
  225. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/sampling_frontier.py +0 -0
  226. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/source_files.py +0 -0
  227. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/torch_runtime.py +0 -0
  228. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/transcription.py +0 -0
  229. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/urn.py +0 -0
  230. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/uv_sync.py +0 -0
  231. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/validation.py +0 -0
  232. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/video_processing.py +0 -0
  233. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/__init__.py +0 -0
  234. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/_lakehouse_fakes.py +0 -0
  235. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/conftest.py +0 -0
  236. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/__init__.py +0 -0
  237. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/broken_links/test_broken_links_detector.py +0 -0
  238. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/conftest.py +0 -0
  239. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/content/__init__.py +0 -0
  240. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/custom/__init__.py +0 -0
  241. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/custom/conftest.py +0 -0
  242. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/custom/test_decision_runner.py +0 -0
  243. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/custom/test_gliner25_runner.py +0 -0
  244. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/custom/test_gliner2_classification.py +0 -0
  245. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/custom/test_invoice_extraction.py +0 -0
  246. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/custom/test_llm_runner.py +0 -0
  247. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/custom/test_pipeline_integration.py +0 -0
  248. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/custom/test_regex_runner.py +0 -0
  249. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/custom/test_runner_hardening.py +0 -0
  250. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/custom/test_transformer_runners.py +0 -0
  251. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/pii/__init__.py +0 -0
  252. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/pii/conftest.py +0 -0
  253. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/pii/sample_invoice.pdf +0 -0
  254. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/pii/test_pii_chunking.py +0 -0
  255. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/pii/test_pii_detector.py +0 -0
  256. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/pii/test_pii_detector_extended.py +0 -0
  257. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/pii/test_pii_regional_entities.py +0 -0
  258. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/pii/test_pii_severity.py +0 -0
  259. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/secrets/__init__.py +0 -0
  260. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/secrets/test_secrets_detector.py +0 -0
  261. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/secrets/test_secrets_detector_extended.py +0 -0
  262. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/test_base_detector.py +0 -0
  263. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/test_custom_detector_examples_runtime.py +0 -0
  264. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/test_custom_detector_session.py +0 -0
  265. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/test_detector_catalog_commercial.py +0 -0
  266. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/test_detector_pipeline_types.py +0 -0
  267. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/test_detector_schema_examples.py +0 -0
  268. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/test_detector_types.py +0 -0
  269. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/test_phase2_detectors.py +0 -0
  270. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/test_registry.py +0 -0
  271. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/test_tag_runner.py +0 -0
  272. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/threat/__init__.py +0 -0
  273. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/threat/test_code_security_detector.py +0 -0
  274. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/threat/test_yara_detector.py +0 -0
  275. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/integration/test_wordpress_broken_links_detector.py +0 -0
  276. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/integration/test_wordpress_links_assets.py +0 -0
  277. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/pipeline/test_detector_breaker.py +0 -0
  278. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/pipeline/test_detector_outcomes.py +0 -0
  279. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/pipeline/test_detector_pipeline.py +0 -0
  280. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/pipeline/test_worker_pool.py +0 -0
  281. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_arrow_ipc_payloads.py +0 -0
  282. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_assets_metadata_catalog.py +0 -0
  283. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_augmentation_contract.py +0 -0
  284. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_augmentation_edges.py +0 -0
  285. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_augmentation_patch.py +0 -0
  286. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_augmentation_scan_cache.py +0 -0
  287. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_augmentation_sdk.py +0 -0
  288. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_augmentation_session.py +0 -0
  289. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_augmentation_tags_end_to_end.py +0 -0
  290. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_automatic_new_first.py +0 -0
  291. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_azure_blob_storage_source.py +0 -0
  292. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_base_source_attachment.py +0 -0
  293. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_base_source_sampling.py +0 -0
  294. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_byte_valued_fields.py +0 -0
  295. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_config.py +0 -0
  296. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_confluence_source.py +0 -0
  297. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_custom_detector_end_to_end.py +0 -0
  298. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_custom_detector_evaluation.py +0 -0
  299. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_custom_detector_notebook.py +0 -0
  300. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_custom_detector_templates.py +0 -0
  301. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_custom_extractor.py +0 -0
  302. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_custom_source.py +0 -0
  303. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_custom_source_relationships.py +0 -0
  304. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_custom_source_tags_end_to_end.py +0 -0
  305. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_delta_lake_source.py +0 -0
  306. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_dependency_groups.py +0 -0
  307. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_detector_scope.py +0 -0
  308. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_dremio_source.py +0 -0
  309. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_dropbox_auth_command.py +0 -0
  310. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_dropbox_source.py +0 -0
  311. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_elasticsearch_source.py +0 -0
  312. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_email_source.py +0 -0
  313. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_file_evaluation_runner.py +0 -0
  314. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_finding_context.py +0 -0
  315. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_git_source.py +0 -0
  316. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_google_cloud_storage_source.py +0 -0
  317. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_google_workspace_source.py +0 -0
  318. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_graph_edges.py +0 -0
  319. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_hashing.py +0 -0
  320. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_hugging_face_source.py +0 -0
  321. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_iceberg_source.py +0 -0
  322. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_jira_source.py +0 -0
  323. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_kafka_source.py +0 -0
  324. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_lineage_loss_reporting.py +0 -0
  325. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_local_folder_source.py +0 -0
  326. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_main.py +0 -0
  327. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_media_embedding.py +0 -0
  328. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_meilisearch_source.py +0 -0
  329. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_microsoft_365_source.py +0 -0
  330. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_mongodb_source.py +0 -0
  331. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_mssql_source.py +0 -0
  332. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_asset_entity.py +0 -0
  333. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_asset_extract.py +0 -0
  334. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_asset_tags.py +0 -0
  335. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_cohort.py +0 -0
  336. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_execute.py +0 -0
  337. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_files.py +0 -0
  338. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_groups.py +0 -0
  339. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_log_levels.py +0 -0
  340. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_packages.py +0 -0
  341. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_query_assets.py +0 -0
  342. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_sdk_exports.py +0 -0
  343. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_serialize.py +0 -0
  344. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_templates.py +0 -0
  345. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notion_source.py +0 -0
  346. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_object_storage_download_efficiency.py +0 -0
  347. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_opensearch_source.py +0 -0
  348. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_oracle_source.py +0 -0
  349. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_outputs.py +0 -0
  350. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_payload_window.py +0 -0
  351. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_payload_window_parquet.py +0 -0
  352. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_postgresql_source.py +0 -0
  353. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_powerbi_source.py +0 -0
  354. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_range_reading.py +0 -0
  355. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_recipe_normalizer.py +0 -0
  356. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_reddit_source.py +0 -0
  357. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_rest_backpressure.py +0 -0
  358. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_runtime_packages.py +0 -0
  359. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_s3_compatible_storage_source.py +0 -0
  360. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_sampling_automatic.py +0 -0
  361. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_sampling_cursor.py +0 -0
  362. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_sampling_frontier.py +0 -0
  363. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_sandbox_source.py +0 -0
  364. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_scan_cache.py +0 -0
  365. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_sdk_completions.py +0 -0
  366. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_secret_redaction.py +0 -0
  367. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_semantic_declarations.py +0 -0
  368. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_servicedesk_source.py +0 -0
  369. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_slack_source.py +0 -0
  370. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_snowflake_source.py +0 -0
  371. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_source_dependency_groups.py +0 -0
  372. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_source_strategy_conformance.py +0 -0
  373. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_sqlite_source.py +0 -0
  374. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_tableau_source.py +0 -0
  375. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_tabular_automatic_sampling.py +0 -0
  376. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_tabular_lineage.py +0 -0
  377. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_tabular_utils.py +0 -0
  378. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_torch_runtime.py +0 -0
  379. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_urn.py +0 -0
  380. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_uv_sync.py +0 -0
  381. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_view_dependencies_are_not_foreign_keys.py +0 -0
  382. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_wordpress_source.py +0 -0
  383. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_youtube_source.py +0 -0
  384. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_youtube_source_integration.py +0 -0
  385. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/utils/test_archive_extraction.py +0 -0
  386. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/utils/test_content_extraction.py +0 -0
  387. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/utils/test_embedded_files.py +0 -0
  388. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/utils/test_file_metadata.py +0 -0
  389. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/utils/test_file_parser.py +0 -0
  390. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/utils/test_file_to_images.py +0 -0
  391. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/utils/test_legacy_office.py +0 -0
  392. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/utils/test_payload.py +0 -0
  393. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/utils/test_transcription.py +0 -0
  394. {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/utils/test_video_processing.py +0 -0
@@ -0,0 +1,3 @@
1
+ $ uv sync
2
+ Resolved 325 packages in 329ms
3
+ Checked 52 packages in 4ms
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: classifyre-cli
3
- Version: 0.7.0
3
+ Version: 0.7.1
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.7.0",
3
+ "version": "0.7.1",
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.7.0"
3
+ version = "0.7.1"
4
4
  description = "Classifyre CLI — scan and classify unstructured data sources"
5
5
  readme = "README.md"
6
6
  # Upper bound is load-bearing: onnxruntime publishes no cp315 wheels (notably
@@ -971,6 +971,11 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
971
971
  source.partial_coverage_reason or "no reason given",
972
972
  )
973
973
 
974
+ if getattr(source, "listing_complete", False) and hasattr(
975
+ sink, "set_listing_complete"
976
+ ):
977
+ sink.set_listing_complete()
978
+
974
979
  # Relationships go *before* finish(): finish() finalizes the
975
980
  # run and marks it COMPLETED, and edges that land after that
976
981
  # belong to a run that already claimed to be done.
@@ -203,6 +203,7 @@ class FinalizeIngestRunRequest(BaseModel):
203
203
  # "This run covered a slice of the source." Suppresses retirement: absence
204
204
  # from a cohort run is not evidence that an asset is gone.
205
205
  partial_coverage: bool | None = Field(None, serialization_alias="partialCoverage")
206
+ discovery_complete: bool | None = Field(None, serialization_alias="discoveryComplete")
206
207
  # Per ctx.cohort(): keys each band visited, and the weights it ran with.
207
208
  cohort_stats: dict[str, Any] | None = Field(None, serialization_alias="cohortStats")
208
209
  # Detectors the circuit breaker disabled mid-run (scan-cache identity
@@ -261,6 +262,7 @@ class RestOutputSink:
261
262
  self._scan_cache_savings: tuple[int, int] | None = None
262
263
  self._partial_coverage: bool = False
263
264
  self._partial_coverage_reason: str = ""
265
+ self._listing_complete: bool = False
264
266
  self._detectors_degraded: list[str] = []
265
267
  self._cohort_stats: dict[str, Any] | None = None
266
268
  self.relationship_report = RelationshipReport()
@@ -279,6 +281,10 @@ class RestOutputSink:
279
281
  if reason:
280
282
  self._partial_coverage_reason = str(reason)
281
283
 
284
+ def set_listing_complete(self) -> None:
285
+ """Mark that discovery named every object of the configured scope."""
286
+ self._listing_complete = True
287
+
282
288
  def set_detectors_degraded(self, keys: list[str]) -> None:
283
289
  """Record detectors the breaker disabled mid-run, for the next run's sweep."""
284
290
  seen: set[str] = set(self._detectors_degraded)
@@ -415,6 +421,7 @@ class RestOutputSink:
415
421
  relationships_dropped=lineage.dropped,
416
422
  relationship_errors=lineage.errors or None,
417
423
  partial_coverage=True if self._partial_coverage else None,
424
+ discovery_complete=True if self._listing_complete else None,
418
425
  cohort_stats=self._cohort_stats,
419
426
  detectors_degraded=self._detectors_degraded or None,
420
427
  )
@@ -89,6 +89,8 @@ class BaseSource(ABC):
89
89
  self._next_sampling_cursor: dict[str, Any] | None = None
90
90
  self._partial_coverage: bool = False
91
91
  self._partial_coverage_reason: str = ""
92
+ self._listing_complete: bool = False
93
+ self._listing_errors: int = 0
92
94
  self._sampling_cursor_lock = threading.Lock()
93
95
  self._apply_initial_sampling_override(normalized_recipe)
94
96
  recipe.clear()
@@ -191,6 +193,34 @@ class BaseSource(ABC):
191
193
  if reason and not self._partial_coverage_reason:
192
194
  self._partial_coverage_reason = str(reason)
193
195
 
196
+ def note_listing_error(self) -> None:
197
+ """Record that part of the catalog could not be listed this run.
198
+
199
+ A listing that skipped a schema, a folder or a label cannot say that
200
+ what it did not list is gone, so it forfeits :meth:`declare_listing_complete`.
201
+ """
202
+ self._listing_errors = getattr(self, "_listing_errors", 0) + 1
203
+
204
+ def declare_listing_complete(self) -> None:
205
+ """Say that discovery named every object of the configured scope.
206
+
207
+ Sampling decides how much of each asset is read, not which assets
208
+ exist. A catalog connector (tables, node labels) lists its whole scope
209
+ on every run whatever the strategy, so an asset missing from that list
210
+ is gone and can be retired -- without this, only strategy=ALL ever
211
+ retired anything and every other source kept deleted tables, moved
212
+ databases and renamed labels as "unchanged" forever.
213
+
214
+ Ignored when any part of the listing failed or the run declared partial
215
+ coverage.
216
+ """
217
+ if getattr(self, "_listing_errors", 0) == 0:
218
+ self._listing_complete = True
219
+
220
+ @property
221
+ def listing_complete(self) -> bool:
222
+ return bool(getattr(self, "_listing_complete", False)) and not self.partial_coverage
223
+
194
224
  @property
195
225
  def partial_coverage(self) -> bool:
196
226
  return bool(getattr(self, "_partial_coverage", False))
@@ -109,6 +109,9 @@ class DatabricksSource(BaseTabularSource):
109
109
  self._validate_auth_configuration()
110
110
 
111
111
  self.session = requests.Session()
112
+ # table_key -> upstream table -> ids of the notebooks Unity Catalog saw
113
+ # move data between the two. Filled by _lineage_refs_for_table.
114
+ self._lineage_notebook_ids: dict[tuple[str, ...], dict[tuple[str, str, str], set[str]]] = {}
112
115
  self._access_token: str | None = None
113
116
  self._access_token_expiry: datetime | None = None
114
117
 
@@ -409,9 +412,13 @@ class DatabricksSource(BaseTabularSource):
409
412
  without_scheme = raw.split("://", 1)[-1]
410
413
  return without_scheme.strip("/").split("/", 1)[0]
411
414
 
412
- def _available_columns(self, table_ref: TableRef) -> list[str]:
415
+ def _available_column_types(self, table_ref: TableRef) -> dict[str, str]:
416
+ # ``system.information_schema`` sees every catalog from one connection;
417
+ # the default query reads only the connection's current catalog and a
418
+ # parameter style Databricks does not take, so types used to come back
419
+ # empty for every column.
413
420
  query = (
414
- "SELECT column_name "
421
+ "SELECT column_name, COALESCE(full_data_type, data_type) "
415
422
  "FROM system.information_schema.columns "
416
423
  f"WHERE table_catalog = {_quote_literal(table_ref.database)} "
417
424
  f"AND table_schema = {_quote_literal(table_ref.schema or '')} "
@@ -421,17 +428,17 @@ class DatabricksSource(BaseTabularSource):
421
428
  conn = self._get_cached_connection(table_ref.database)
422
429
  with conn.cursor() as cursor:
423
430
  cursor.execute(query)
424
- columns: list[str] = []
431
+ columns: dict[str, str] = {}
425
432
  for row in cursor.fetchall():
426
- candidate: Any | None = None
427
- try:
428
- candidate = row[0] # type: ignore[index]
429
- except Exception:
430
- candidate = None
431
- if isinstance(candidate, str):
432
- columns.append(candidate)
433
+ name = row[0] if len(row) > 0 else None
434
+ if isinstance(name, str):
435
+ data_type = row[1] if len(row) > 1 else None
436
+ columns[name] = str(data_type) if data_type is not None else ""
433
437
  return columns
434
438
 
439
+ def _available_columns(self, table_ref: TableRef) -> list[str]:
440
+ return list(self._available_column_types(table_ref))
441
+
435
442
  # ── Databricks-specific cell serialization ───────────────────────────
436
443
 
437
444
  def _serialize_cell(self, value: Any) -> str:
@@ -598,6 +605,7 @@ class DatabricksSource(BaseTabularSource):
598
605
  try:
599
606
  schemas = self._list_schemas_for_catalog(database)
600
607
  except Exception as exc:
608
+ self.note_listing_error()
601
609
  logger.warning("Skipping catalog %s due to schema listing error: %s", database, exc)
602
610
  return tables
603
611
  for schema in schemas:
@@ -606,6 +614,7 @@ class DatabricksSource(BaseTabularSource):
606
614
  try:
607
615
  tables.extend(self._list_tables_for_schema(database, schema))
608
616
  except Exception as exc:
617
+ self.note_listing_error()
609
618
  logger.warning(
610
619
  "Skipping schema %s.%s due to table listing error: %s",
611
620
  database,
@@ -653,6 +662,7 @@ class DatabricksSource(BaseTabularSource):
653
662
  },
654
663
  )
655
664
  refs: set[tuple[str, str, str]] = set()
665
+ notebooks: dict[tuple[str, str, str], set[str]] = {}
656
666
  for key in ("upstreams", "upstream_tables"):
657
667
  entries = response.get(key)
658
668
  if isinstance(entries, list):
@@ -661,6 +671,12 @@ class DatabricksSource(BaseTabularSource):
661
671
  parsed = self._lineage_table_ref_from_payload(entry)
662
672
  if parsed:
663
673
  refs.add(parsed)
674
+ for info in entry.get("notebookInfos") or []:
675
+ if isinstance(info, dict) and info.get("notebook_id") is not None:
676
+ notebooks.setdefault(parsed, set()).add(
677
+ str(info["notebook_id"])
678
+ )
679
+ self._lineage_notebook_ids[table_ref.table_key] = notebooks
664
680
  return refs
665
681
 
666
682
  def _emit_table_lineage(
@@ -668,6 +684,7 @@ class DatabricksSource(BaseTabularSource):
668
684
  table_ref: TableRef,
669
685
  upstream_refs: set[tuple[str, str, str]],
670
686
  table_hash_by_key: dict[tuple[str, ...], str],
687
+ notebook_hash_by_id: dict[str, str] | None = None,
671
688
  ) -> None:
672
689
  """Turn Unity Catalog's lineage into typed edges.
673
690
 
@@ -675,6 +692,9 @@ class DatabricksSource(BaseTabularSource):
675
692
  question directly, so nothing here is parsed out of SQL — these edges
676
693
  are as trustworthy as lineage gets, and are marked SYSTEM_CATALOG.
677
694
 
695
+ When Unity Catalog saw a notebook run the statement, the edge carries it
696
+ as ``via`` -- if that notebook is one of this scan's assets.
697
+
678
698
  The upstreams that are *not* part of this scan are the important half.
679
699
  They used to be dropped on the floor (``if target in table_hash_by_key``),
680
700
  which meant a table fed by a catalog outside the configured scope looked
@@ -686,6 +706,7 @@ class DatabricksSource(BaseTabularSource):
686
706
  return
687
707
 
688
708
  column_mappings = self._column_mappings_for_table(table_ref)
709
+ notebook_ids = self._lineage_notebook_ids.get(table_ref.table_key, {})
689
710
  for upstream in sorted(upstream_refs):
690
711
  upstream_hash = table_hash_by_key.get(upstream)
691
712
  upstream_ref = (
@@ -693,12 +714,21 @@ class DatabricksSource(BaseTabularSource):
693
714
  )
694
715
  if upstream_ref is None:
695
716
  continue
717
+ via_hash = next(
718
+ (
719
+ notebook_hash_by_id[notebook_id]
720
+ for notebook_id in sorted(notebook_ids.get(upstream, ()))
721
+ if notebook_id in (notebook_hash_by_id or {})
722
+ ),
723
+ None,
724
+ )
696
725
  self.add_edge(
697
726
  flow(
698
727
  upstream=upstream_ref,
699
728
  downstream=Ref.asset(downstream_hash),
700
729
  type=FlowType.TRANSFORM,
701
730
  fields=column_mappings.get(upstream, []),
731
+ via=Ref.asset(via_hash) if via_hash else None,
702
732
  method=Method.SYSTEM_CATALOG,
703
733
  )
704
734
  )
@@ -786,6 +816,7 @@ class DatabricksSource(BaseTabularSource):
786
816
  "get", "/api/2.0/workspace/list", params={"path": path}
787
817
  )
788
818
  except Exception as exc:
819
+ self.note_listing_error()
789
820
  logger.warning("Skipping workspace path %s due to listing error: %s", path, exc)
790
821
  continue
791
822
  objects = payload.get("objects")
@@ -865,6 +896,7 @@ class DatabricksSource(BaseTabularSource):
865
896
  try:
866
897
  payload = self._request_json("get", "/api/2.0/pipelines", params=params)
867
898
  except Exception as exc:
899
+ self.note_listing_error()
868
900
  logger.warning("Could not list Databricks pipelines: %s", exc)
869
901
  break
870
902
  values: list[dict[str, Any]] = []
@@ -935,6 +967,15 @@ class DatabricksSource(BaseTabularSource):
935
967
  t.table_key: self.generate_hash_id(t.raw_id) for t in tables
936
968
  }
937
969
 
970
+ # Listed before the tables so a lineage edge can name the notebook that
971
+ # produced it; the notebook assets are still emitted after the tables.
972
+ notebooks = list(self._iter_notebooks())
973
+ notebook_hash_by_id = {
974
+ nb.object_id: self.generate_hash_id(f"notebook_#_{nb.path}")
975
+ for nb in notebooks
976
+ if nb.object_id
977
+ }
978
+
938
979
  include_lineage = self._extraction_options().include_table_lineage
939
980
  if include_lineage and tables:
940
981
  logger.info("Fetching table lineage for %d table(s)...", len(tables))
@@ -957,7 +998,9 @@ class DatabricksSource(BaseTabularSource):
957
998
  for target in sorted(upstream_refs)
958
999
  if target in table_hash_by_key
959
1000
  ]
960
- self._emit_table_lineage(table_ref, upstream_refs, table_hash_by_key)
1001
+ self._emit_table_lineage(
1002
+ table_ref, upstream_refs, table_hash_by_key, notebook_hash_by_id
1003
+ )
961
1004
  except Exception as exc:
962
1005
  logger.warning(
963
1006
  "Could not resolve Databricks lineage for %s: %s", table_label, exc
@@ -974,7 +1017,7 @@ class DatabricksSource(BaseTabularSource):
974
1017
 
975
1018
  # Notebooks
976
1019
  notebook_count = 0
977
- for notebook in self._iter_notebooks():
1020
+ for notebook in notebooks:
978
1021
  if self._aborted:
979
1022
  break
980
1023
  batch.append(self._notebook_to_asset(notebook))
@@ -997,6 +1040,7 @@ class DatabricksSource(BaseTabularSource):
997
1040
  batch = []
998
1041
  if pipeline_count:
999
1042
  logger.info("Discovered %d pipeline(s)", pipeline_count)
1043
+ self._declare_listing_if_whole()
1000
1044
 
1001
1045
  if batch:
1002
1046
  yield batch
@@ -293,7 +293,10 @@ class HiveSource(BaseTabularSource):
293
293
  upstreams = [
294
294
  key
295
295
  for key in upstream_tables_from_sql(
296
- definition, dialect="hive", default_database=view.database
296
+ definition,
297
+ dialect="hive",
298
+ default_database=view.database,
299
+ two_level=True,
297
300
  )
298
301
  if key != view.table_key and key in scoped
299
302
  ]
@@ -322,7 +322,7 @@ class MySQLSource(BaseTabularSource):
322
322
  upstreams = [
323
323
  key
324
324
  for key in upstream_tables_from_sql(
325
- definition, dialect="mysql", default_database=database
325
+ definition, dialect="mysql", default_database=database, two_level=True
326
326
  )
327
327
  if key != view_key
328
328
  ]
@@ -325,6 +325,9 @@ class Neo4jSource(BaseSource):
325
325
  yield batch
326
326
  batch = []
327
327
 
328
+ if not self._aborted and not self._scope_options().node_limit_per_label:
329
+ self.declare_listing_complete()
330
+
328
331
  if batch:
329
332
  logger.info("Emitting final batch of %d asset(s)", len(batch))
330
333
  yield batch
@@ -319,9 +319,23 @@ class BaseTabularSource(BaseSource):
319
319
  try:
320
320
  tables.extend(self._list_tables_for_database(database))
321
321
  except Exception as exc:
322
+ self.note_listing_error()
322
323
  logger.warning("Skipping database %s due to listing error: %s", database, exc)
323
324
  return tables
324
325
 
326
+ def _listing_is_limited(self) -> bool:
327
+ """Whether a configured cap can have cut the table list short."""
328
+ scope_options = getattr(self, "_scope_options", None)
329
+ scope = scope_options() if callable(scope_options) else None
330
+ return bool(
331
+ getattr(scope, "table_limit", None) or getattr(scope, "table_limit_per_schema", None)
332
+ )
333
+
334
+ def _declare_listing_if_whole(self) -> None:
335
+ """Offer this run's table list as the whole scope, when it plausibly is."""
336
+ if not self._aborted and not self._listing_is_limited():
337
+ self.declare_listing_complete()
338
+
325
339
  # ── Primary keys ─────────────────────────────────────────────────────
326
340
 
327
341
  def _get_primary_key_columns(self, table_ref: TableRef) -> list[str]:
@@ -1097,6 +1111,7 @@ class BaseTabularSource(BaseSource):
1097
1111
  table_hash_by_key: dict[tuple[str, ...], str] = {
1098
1112
  t.table_key: self.generate_hash_id(t.raw_id) for t in tables
1099
1113
  }
1114
+ self._declare_listing_if_whole()
1100
1115
  fk_links = self._collect_foreign_key_links(tables)
1101
1116
  self._emit_relationship_edges(tables, table_hash_by_key, fk_links)
1102
1117
 
@@ -444,6 +444,7 @@ def upstream_tables_from_sql(
444
444
  dialect: str | None = None,
445
445
  default_database: str | None = None,
446
446
  default_schema: str | None = None,
447
+ two_level: bool = False,
447
448
  ) -> list[tuple[str, ...]]:
448
449
  """Table keys a statement reads from, for catalogs with no dependency view.
449
450
 
@@ -453,6 +454,11 @@ def upstream_tables_from_sql(
453
454
  the wrong default schema points at nothing — so callers should prefer the
454
455
  catalog wherever it exists and treat these edges as SQL_PARSED.
455
456
 
457
+ ``two_level`` is for engines whose tables are named ``database.table`` (MySQL,
458
+ Hive). sqlglot files the first part under ``db``, so without it a qualified
459
+ name came back as ``(default_database, database, table)`` -- a key no asset
460
+ has, which turned every such view's edges into unresolved ones.
461
+
456
462
  CTE names are excluded: a ``WITH`` alias is not a table, and emitting it
457
463
  would create lineage to an object that does not exist.
458
464
  """
@@ -481,6 +487,11 @@ def upstream_tables_from_sql(
481
487
  name = table.name
482
488
  if not name or name.lower() in cte_names:
483
489
  continue
490
+ if two_level:
491
+ namespace = table.text("db") or default_database
492
+ if namespace:
493
+ keys.add((namespace, name))
494
+ continue
484
495
  schema = table.text("db") or default_schema
485
496
  database = table.text("catalog") or default_database
486
497
  if database and schema:
@@ -415,3 +415,93 @@ async def test_databricks_fetch_content_pages_batches_for_all_strategy(
415
415
  assert len(pages) == 12
416
416
  assert "item1" in pages[0]
417
417
  assert "item12" in pages[11]
418
+
419
+
420
+ def test_databricks_column_types_come_from_the_system_catalog(
421
+ monkeypatch: pytest.MonkeyPatch,
422
+ ) -> None:
423
+ source = DatabricksSource(_pat_recipe())
424
+ issued: list[str] = []
425
+
426
+ class _TypesCursor(_DummyCursor):
427
+ def execute(self, query: str, _params: Any = None) -> None:
428
+ issued.append(query)
429
+ self._rows = [("order_id", "int"), ("line_total", "decimal(21,2)"), ("note", None)]
430
+
431
+ class _TypesConnection(_DummyConnection):
432
+ def cursor(self) -> _TypesCursor:
433
+ return _TypesCursor()
434
+
435
+ monkeypatch.setattr(source, "_get_cached_connection", lambda _db: _TypesConnection())
436
+ ref = TableRef(database="main", schema="sales", table="order_lines", object_type="TABLE")
437
+
438
+ assert source._available_column_types(ref) == {
439
+ "order_id": "int",
440
+ "line_total": "decimal(21,2)",
441
+ "note": "",
442
+ }
443
+ assert source._available_columns(ref) == ["order_id", "line_total", "note"]
444
+ assert "system.information_schema.columns" in issued[0]
445
+ assert "table_catalog = 'main'" in issued[0]
446
+
447
+
448
+ def test_databricks_lineage_edge_names_the_notebook_that_wrote_it(
449
+ monkeypatch: pytest.MonkeyPatch,
450
+ ) -> None:
451
+ source = DatabricksSource(_pat_recipe())
452
+ downstream = TableRef(database="main", schema="mart", table="daily", object_type="TABLE")
453
+ monkeypatch.setattr(
454
+ source,
455
+ "_request_json",
456
+ lambda *_a, **_k: {
457
+ "upstreams": [
458
+ {
459
+ "tableInfo": {
460
+ "catalog_name": "main",
461
+ "schema_name": "raw",
462
+ "name": "orders",
463
+ },
464
+ "notebookInfos": [{"notebook_id": 1001}],
465
+ },
466
+ {
467
+ "tableInfo": {
468
+ "catalog_name": "main",
469
+ "schema_name": "raw",
470
+ "name": "payments",
471
+ },
472
+ },
473
+ ]
474
+ },
475
+ )
476
+
477
+ refs = source._lineage_refs_for_table(downstream)
478
+ table_hashes = {
479
+ ("main", "raw", "orders"): "h-orders",
480
+ ("main", "raw", "payments"): "h-payments",
481
+ downstream.table_key: "h-daily",
482
+ }
483
+ source._emit_table_lineage(downstream, refs, table_hashes, {"1001": "h-notebook"})
484
+
485
+ edges = {edge.from_hash: edge for edge in source.drain_edges()}
486
+ assert edges["h-orders"].via_id == "h-notebook"
487
+ assert edges["h-payments"].via_id is None
488
+
489
+
490
+ def test_databricks_unlistable_schema_forfeits_the_complete_declaration(
491
+ monkeypatch: pytest.MonkeyPatch,
492
+ ) -> None:
493
+ # A schema whose tables could not be listed must not read as "now empty".
494
+ source = DatabricksSource(_pat_recipe())
495
+ monkeypatch.setattr(source, "_list_schemas_for_catalog", lambda _c: ["good", "bad"])
496
+
497
+ def _tables(catalog: str, schema: str) -> list[TableRef]:
498
+ if schema == "bad":
499
+ raise RuntimeError("permission denied")
500
+ return [TableRef(database=catalog, schema=schema, table="t", object_type="TABLE")]
501
+
502
+ monkeypatch.setattr(source, "_list_tables_for_schema", _tables)
503
+
504
+ assert len(source._list_tables_for_database("main")) == 1
505
+ source._declare_listing_if_whole()
506
+
507
+ assert source.listing_complete is False
@@ -314,3 +314,26 @@ async def test_hive_fetch_content_pages_batches_for_all_strategy(
314
314
  assert len(pages) == 12
315
315
  assert "item1" in pages[0]
316
316
  assert "item12" in pages[11]
317
+
318
+
319
+ def test_hive_view_upstreams_are_database_and_table_keys(
320
+ monkeypatch: pytest.MonkeyPatch,
321
+ ) -> None:
322
+ # Hive stores view text with database-qualified names; the key must stay
323
+ # ``(database, table)`` to match an asset, not grow a third part.
324
+ source = HiveSource(_recipe())
325
+ monkeypatch.setattr(
326
+ source,
327
+ "_view_definition",
328
+ lambda _ref: "SELECT `o`.`id` FROM `sales`.`orders` `o` JOIN `crm`.`accounts` `a` ON 1=1",
329
+ )
330
+ tables = [
331
+ TableRef(database="reporting", schema=None, table="v_orders", object_type="VIEW"),
332
+ TableRef(database="sales", schema=None, table="orders", object_type="TABLE"),
333
+ TableRef(database="crm", schema=None, table="accounts", object_type="TABLE"),
334
+ ]
335
+
336
+ lineage = source._collect_view_lineage(tables)
337
+
338
+ assert len(lineage) == 1
339
+ assert set(lineage[0].upstreams) == {("sales", "orders"), ("crm", "accounts")}
@@ -411,3 +411,107 @@ def test_mysql_include_views_false_lists_only_base_tables(
411
411
  tables = source._list_tables_for_database("app_db")
412
412
 
413
413
  assert [(t.table, t.object_type) for t in tables] == [("orders", "TABLE")]
414
+
415
+
416
+ class _ViewsCursor:
417
+ def __init__(self) -> None:
418
+ self._rows: list[tuple[Any, ...]] = []
419
+
420
+ def execute(self, query: str, _params: Any = None) -> None:
421
+ assert "information_schema.VIEWS" in query
422
+ self._rows = [
423
+ (
424
+ "v_orders_with_manager",
425
+ "select `app_db`.`o`.`order_id` AS `order_id`,`e`.`full_name` AS `manager` "
426
+ "from (`app_db`.`orders` `o` left join `hr_db`.`employees` `e` "
427
+ "on((`e`.`employee_id` = `o`.`account_manager_id`)))",
428
+ )
429
+ ]
430
+
431
+ def fetchall(self) -> list[tuple[Any, ...]]:
432
+ return list(self._rows)
433
+
434
+ def __enter__(self) -> _ViewsCursor:
435
+ return self
436
+
437
+ def __exit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
438
+ return None
439
+
440
+
441
+ class _ViewsConnection:
442
+ def cursor(self) -> _ViewsCursor:
443
+ return _ViewsCursor()
444
+
445
+
446
+ def test_mysql_view_upstreams_are_database_and_table_keys(
447
+ monkeypatch: pytest.MonkeyPatch,
448
+ ) -> None:
449
+ # Keys of any other shape name no asset, so every edge of the view would be
450
+ # left unresolved instead of pointing at the table that was scanned.
451
+ source = MySQLSource(_recipe())
452
+ monkeypatch.setattr(source, "_get_cached_connection", lambda _db=None: _ViewsConnection())
453
+ tables = [
454
+ TableRef(database="app_db", schema=None, table="orders", object_type="TABLE"),
455
+ TableRef(database="app_db", schema=None, table="v_orders_with_manager", object_type="VIEW"),
456
+ TableRef(database="hr_db", schema=None, table="employees", object_type="TABLE"),
457
+ ]
458
+
459
+ lineage = source._collect_view_lineage(tables)
460
+
461
+ assert len(lineage) == 1
462
+ assert lineage[0].view == ("app_db", "v_orders_with_manager")
463
+ assert set(lineage[0].upstreams) == {("app_db", "orders"), ("hr_db", "employees")}
464
+
465
+
466
+ def test_a_failed_database_listing_forfeits_the_complete_declaration(
467
+ monkeypatch: pytest.MonkeyPatch,
468
+ ) -> None:
469
+ # A schema that could not be listed says nothing about the tables in it.
470
+ source = MySQLSource(_recipe())
471
+ monkeypatch.setattr(source, "_resolve_databases", lambda: ["app_db", "broken_db"])
472
+
473
+ def _list(database: str) -> list[TableRef]:
474
+ if database == "broken_db":
475
+ raise RuntimeError("access denied")
476
+ return [TableRef(database=database, schema=None, table="orders")]
477
+
478
+ monkeypatch.setattr(source, "_list_tables_for_database", _list)
479
+
480
+ tables = source._iter_tables()
481
+ source._declare_listing_if_whole()
482
+
483
+ assert [t.table for t in tables] == ["orders"]
484
+ assert source.listing_complete is False
485
+
486
+
487
+ def test_a_clean_unlimited_listing_is_declared_complete(
488
+ monkeypatch: pytest.MonkeyPatch,
489
+ ) -> None:
490
+ source = MySQLSource(_recipe())
491
+ monkeypatch.setattr(source, "_resolve_databases", lambda: ["app_db"])
492
+ monkeypatch.setattr(
493
+ source,
494
+ "_list_tables_for_database",
495
+ lambda db: [TableRef(database=db, schema=None, table="orders")],
496
+ )
497
+
498
+ source._iter_tables()
499
+ source._declare_listing_if_whole()
500
+
501
+ assert source.listing_complete is True
502
+
503
+
504
+ def test_a_table_limit_means_the_listing_may_be_short() -> None:
505
+ source = MySQLSource(_recipe(optional={"scope": {"database": "app_db", "table_limit": 5}}))
506
+
507
+ source._declare_listing_if_whole()
508
+
509
+ assert source.listing_complete is False
510
+
511
+
512
+ def test_declared_partial_coverage_overrides_a_complete_listing() -> None:
513
+ source = MySQLSource(_recipe())
514
+ source.declare_listing_complete()
515
+ source.declare_partial_coverage("timed out")
516
+
517
+ assert source.listing_complete is False