classifyre-cli 0.4.124__tar.gz → 0.4.126__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 (317) hide show
  1. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/.turbo/turbo-build.log +1 -1
  2. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/PKG-INFO +1 -1
  3. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/package.json +1 -1
  4. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/pyproject.toml +1 -1
  5. classifyre_cli-0.4.126/scripts/generate_runtime_packages.py +244 -0
  6. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/scripts/generate_sdk_completions.py +36 -1
  7. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/models/generated_input.py +27 -0
  8. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/notebook/cli.py +20 -1
  9. classifyre_cli-0.4.126/src/notebook/files.py +180 -0
  10. classifyre_cli-0.4.126/src/notebook/groups.py +118 -0
  11. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/notebook/sdk.py +117 -5
  12. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/custom/runner.py +7 -0
  13. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/custom/source.py +77 -7
  14. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/sandbox/source.py +6 -39
  15. classifyre_cli-0.4.126/src/utils/source_files.py +136 -0
  16. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_custom_source.py +155 -0
  17. classifyre_cli-0.4.126/tests/test_notebook_files.py +121 -0
  18. classifyre_cli-0.4.126/tests/test_notebook_groups.py +76 -0
  19. classifyre_cli-0.4.126/tests/test_notebook_templates.py +144 -0
  20. classifyre_cli-0.4.126/tests/test_runtime_packages.py +77 -0
  21. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/uv.lock +47 -47
  22. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/.gitignore +0 -0
  23. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/.python-version +0 -0
  24. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/README.md +0 -0
  25. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/main.py +0 -0
  26. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/scripts/generate_models.py +0 -0
  27. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/__init__.py +0 -0
  28. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/config.py +0 -0
  29. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/__init__.py +0 -0
  30. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/base.py +0 -0
  31. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/broken_links/__init__.py +0 -0
  32. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/broken_links/detector.py +0 -0
  33. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/config.py +0 -0
  34. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/content/__init__.py +0 -0
  35. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/custom/__init__.py +0 -0
  36. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/custom/detector.py +0 -0
  37. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/custom/extractor.py +0 -0
  38. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/custom/runners/__init__.py +0 -0
  39. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/custom/runners/_base.py +0 -0
  40. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/custom/runners/_factory.py +0 -0
  41. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/custom/runners/_gliner2.py +0 -0
  42. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/custom/runners/_image_classification.py +0 -0
  43. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/custom/runners/_llm.py +0 -0
  44. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/custom/runners/_object_detection.py +0 -0
  45. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/custom/runners/_regex.py +0 -0
  46. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/custom/runners/_text_classification.py +0 -0
  47. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/custom/trainer.py +0 -0
  48. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/dependencies.py +0 -0
  49. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/engine_version.py +0 -0
  50. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/pii/__init__.py +0 -0
  51. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/pii/detector.py +0 -0
  52. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/secrets/__init__.py +0 -0
  53. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/secrets/detector.py +0 -0
  54. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/threat/__init__.py +0 -0
  55. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/threat/code_security_detector.py +0 -0
  56. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/detectors/threat/yara_detector.py +0 -0
  57. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/file_evaluation/__init__.py +0 -0
  58. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/file_evaluation/runner.py +0 -0
  59. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/main.py +0 -0
  60. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/models/generated_detectors.py +0 -0
  61. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/models/generated_single_asset_scan_results.py +0 -0
  62. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/notebook/__init__.py +0 -0
  63. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/notebook/contract.py +0 -0
  64. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/notebook/execute.py +0 -0
  65. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/notebook/outputs.py +0 -0
  66. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/notebook/packages.py +0 -0
  67. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/notebook/protocol.py +0 -0
  68. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/notebook/redact.py +0 -0
  69. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/notebook/scaffold.py +0 -0
  70. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/notebook/serialize.py +0 -0
  71. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/outputs/__init__.py +0 -0
  72. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/outputs/base.py +0 -0
  73. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/outputs/console.py +0 -0
  74. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/outputs/factory.py +0 -0
  75. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/outputs/file.py +0 -0
  76. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/outputs/rest.py +0 -0
  77. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/pipeline/__init__.py +0 -0
  78. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/pipeline/content_provider.py +0 -0
  79. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/pipeline/detector_pipeline.py +0 -0
  80. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/pipeline/parsed_content_provider.py +0 -0
  81. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/pipeline/payload_window.py +0 -0
  82. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/pipeline/scan_cache.py +0 -0
  83. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/pipeline/text_artifact.py +0 -0
  84. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/pipeline/worker_pool.py +0 -0
  85. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/__init__.py +0 -0
  86. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/asset_metadata.py +0 -0
  87. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/atlassian_common.py +0 -0
  88. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/azure_blob_storage/__init__.py +0 -0
  89. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/azure_blob_storage/source.py +0 -0
  90. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/base.py +0 -0
  91. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/confluence/__init__.py +0 -0
  92. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/confluence/source.py +0 -0
  93. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/custom/__init__.py +0 -0
  94. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/custom/env.py +0 -0
  95. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/databricks/__init__.py +0 -0
  96. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/databricks/source.py +0 -0
  97. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/delta_lake/__init__.py +0 -0
  98. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/delta_lake/source.py +0 -0
  99. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/dependencies.py +0 -0
  100. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/dropbox/__init__.py +0 -0
  101. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/dropbox/auth.py +0 -0
  102. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/dropbox/source.py +0 -0
  103. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/elasticsearch/__init__.py +0 -0
  104. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/elasticsearch/source.py +0 -0
  105. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/email/__init__.py +0 -0
  106. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/email/source.py +0 -0
  107. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/git/__init__.py +0 -0
  108. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/git/source.py +0 -0
  109. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/google_cloud_storage/__init__.py +0 -0
  110. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/google_cloud_storage/source.py +0 -0
  111. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/google_workspace/__init__.py +0 -0
  112. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/google_workspace/source.py +0 -0
  113. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/hive/__init__.py +0 -0
  114. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/hive/source.py +0 -0
  115. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/hugging_face/__init__.py +0 -0
  116. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/hugging_face/source.py +0 -0
  117. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/iceberg/__init__.py +0 -0
  118. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/iceberg/source.py +0 -0
  119. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/jira/__init__.py +0 -0
  120. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/jira/source.py +0 -0
  121. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/kafka/__init__.py +0 -0
  122. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/kafka/rest.py +0 -0
  123. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/kafka/source.py +0 -0
  124. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/lakehouse_base.py +0 -0
  125. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/local_folder/__init__.py +0 -0
  126. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/local_folder/source.py +0 -0
  127. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/meilisearch/__init__.py +0 -0
  128. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/meilisearch/source.py +0 -0
  129. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/microsoft_365/__init__.py +0 -0
  130. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/microsoft_365/source.py +0 -0
  131. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/mongodb/__init__.py +0 -0
  132. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/mongodb/source.py +0 -0
  133. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/mssql/__init__.py +0 -0
  134. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/mssql/source.py +0 -0
  135. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/mysql/__init__.py +0 -0
  136. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/mysql/source.py +0 -0
  137. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/neo4j/__init__.py +0 -0
  138. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/neo4j/source.py +0 -0
  139. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/notion/__init__.py +0 -0
  140. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/notion/client.py +0 -0
  141. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/notion/source.py +0 -0
  142. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/object_storage/base.py +0 -0
  143. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/opensearch/__init__.py +0 -0
  144. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/opensearch/source.py +0 -0
  145. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/oracle/__init__.py +0 -0
  146. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/oracle/source.py +0 -0
  147. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/postgresql/__init__.py +0 -0
  148. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/postgresql/source.py +0 -0
  149. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/powerbi/__init__.py +0 -0
  150. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/powerbi/source.py +0 -0
  151. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/recipe_normalizer.py +0 -0
  152. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/reddit/__init__.py +0 -0
  153. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/reddit/source.py +0 -0
  154. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/s3_client.py +0 -0
  155. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/s3_compatible_storage/README.md +0 -0
  156. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/s3_compatible_storage/__init__.py +0 -0
  157. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/s3_compatible_storage/source.py +0 -0
  158. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/sandbox/__init__.py +0 -0
  159. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/search_engine_base.py +0 -0
  160. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/servicedesk/__init__.py +0 -0
  161. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/servicedesk/source.py +0 -0
  162. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/slack/__init__.py +0 -0
  163. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/slack/source.py +0 -0
  164. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/snowflake/__init__.py +0 -0
  165. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/snowflake/source.py +0 -0
  166. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/sqlite/__init__.py +0 -0
  167. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/sqlite/source.py +0 -0
  168. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/tableau/__init__.py +0 -0
  169. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/tableau/source.py +0 -0
  170. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/tabular_base.py +0 -0
  171. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/tabular_utils.py +0 -0
  172. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/wordpress/__init__.py +0 -0
  173. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/wordpress/source.py +0 -0
  174. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/youtube/__init__.py +0 -0
  175. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/sources/youtube/source.py +0 -0
  176. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/telemetry.py +0 -0
  177. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/utils/__init__.py +0 -0
  178. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/utils/archive_extraction.py +0 -0
  179. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/utils/content_extraction.py +0 -0
  180. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/utils/dependency_groups.py +0 -0
  181. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/utils/embedded_files.py +0 -0
  182. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/utils/external_video.py +0 -0
  183. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/utils/file_metadata.py +0 -0
  184. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/utils/file_parser.py +0 -0
  185. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/utils/file_to_images.py +0 -0
  186. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/utils/hashing.py +0 -0
  187. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/utils/legacy_office.py +0 -0
  188. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/utils/payload.py +0 -0
  189. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/utils/range_reader.py +0 -0
  190. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/utils/resources.py +0 -0
  191. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/utils/torch_runtime.py +0 -0
  192. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/utils/transcription.py +0 -0
  193. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/utils/uv_sync.py +0 -0
  194. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/utils/validation.py +0 -0
  195. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/src/utils/video_processing.py +0 -0
  196. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/__init__.py +0 -0
  197. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/_lakehouse_fakes.py +0 -0
  198. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/conftest.py +0 -0
  199. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/__init__.py +0 -0
  200. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/broken_links/test_broken_links_detector.py +0 -0
  201. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/conftest.py +0 -0
  202. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/content/__init__.py +0 -0
  203. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/custom/__init__.py +0 -0
  204. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/custom/conftest.py +0 -0
  205. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/custom/test_gliner2_classification.py +0 -0
  206. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/custom/test_invoice_extraction.py +0 -0
  207. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/custom/test_llm_runner.py +0 -0
  208. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/custom/test_pipeline_integration.py +0 -0
  209. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/custom/test_regex_runner.py +0 -0
  210. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/custom/test_runner_hardening.py +0 -0
  211. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/custom/test_transformer_runners.py +0 -0
  212. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/pii/__init__.py +0 -0
  213. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/pii/conftest.py +0 -0
  214. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/pii/sample_invoice.pdf +0 -0
  215. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/pii/test_pii_chunking.py +0 -0
  216. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/pii/test_pii_detector.py +0 -0
  217. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/pii/test_pii_detector_extended.py +0 -0
  218. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/pii/test_pii_regional_entities.py +0 -0
  219. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/pii/test_pii_severity.py +0 -0
  220. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/secrets/__init__.py +0 -0
  221. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/secrets/test_secrets_detector.py +0 -0
  222. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/secrets/test_secrets_detector_extended.py +0 -0
  223. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/test_base_detector.py +0 -0
  224. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/test_custom_detector_examples_runtime.py +0 -0
  225. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/test_detector_catalog_commercial.py +0 -0
  226. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/test_detector_pipeline_types.py +0 -0
  227. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/test_detector_schema_examples.py +0 -0
  228. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/test_detector_types.py +0 -0
  229. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/test_phase2_detectors.py +0 -0
  230. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/test_registry.py +0 -0
  231. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/threat/__init__.py +0 -0
  232. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/threat/test_code_security_detector.py +0 -0
  233. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/detectors/threat/test_yara_detector.py +0 -0
  234. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/integration/test_wordpress_broken_links_detector.py +0 -0
  235. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/integration/test_wordpress_links_assets.py +0 -0
  236. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/pipeline/test_detector_outcomes.py +0 -0
  237. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/pipeline/test_detector_pipeline.py +0 -0
  238. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/pipeline/test_worker_pool.py +0 -0
  239. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_arrow_ipc_payloads.py +0 -0
  240. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_assets_metadata_catalog.py +0 -0
  241. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_azure_blob_storage_source.py +0 -0
  242. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_base_source_attachment.py +0 -0
  243. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_base_source_sampling.py +0 -0
  244. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_byte_valued_fields.py +0 -0
  245. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_config.py +0 -0
  246. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_confluence_source.py +0 -0
  247. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_custom_extractor.py +0 -0
  248. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_databricks_source.py +0 -0
  249. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_delta_lake_source.py +0 -0
  250. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_dependency_groups.py +0 -0
  251. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_dropbox_auth_command.py +0 -0
  252. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_dropbox_source.py +0 -0
  253. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_elasticsearch_source.py +0 -0
  254. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_email_source.py +0 -0
  255. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_file_evaluation_runner.py +0 -0
  256. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_git_source.py +0 -0
  257. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_google_cloud_storage_source.py +0 -0
  258. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_google_workspace_source.py +0 -0
  259. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_hashing.py +0 -0
  260. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_hive_source.py +0 -0
  261. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_hugging_face_source.py +0 -0
  262. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_iceberg_source.py +0 -0
  263. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_jira_source.py +0 -0
  264. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_kafka_source.py +0 -0
  265. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_local_folder_source.py +0 -0
  266. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_main.py +0 -0
  267. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_meilisearch_source.py +0 -0
  268. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_microsoft_365_source.py +0 -0
  269. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_mongodb_source.py +0 -0
  270. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_mssql_source.py +0 -0
  271. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_mysql_source.py +0 -0
  272. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_neo4j_source.py +0 -0
  273. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_notebook_execute.py +0 -0
  274. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_notebook_packages.py +0 -0
  275. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_notebook_serialize.py +0 -0
  276. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_notion_source.py +0 -0
  277. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_object_storage_download_efficiency.py +0 -0
  278. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_opensearch_source.py +0 -0
  279. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_oracle_source.py +0 -0
  280. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_outputs.py +0 -0
  281. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_payload_window.py +0 -0
  282. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_payload_window_parquet.py +0 -0
  283. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_postgresql_source.py +0 -0
  284. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_powerbi_source.py +0 -0
  285. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_range_reading.py +0 -0
  286. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_recipe_normalizer.py +0 -0
  287. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_reddit_source.py +0 -0
  288. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_rest_backpressure.py +0 -0
  289. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_s3_compatible_storage_source.py +0 -0
  290. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_sampling_automatic.py +0 -0
  291. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_sandbox_source.py +0 -0
  292. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_scan_cache.py +0 -0
  293. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_sdk_completions.py +0 -0
  294. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_servicedesk_source.py +0 -0
  295. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_slack_source.py +0 -0
  296. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_snowflake_source.py +0 -0
  297. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_source_dependency_groups.py +0 -0
  298. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_source_strategy_conformance.py +0 -0
  299. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_sqlite_source.py +0 -0
  300. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_tableau_source.py +0 -0
  301. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_tabular_automatic_sampling.py +0 -0
  302. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_tabular_utils.py +0 -0
  303. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_torch_runtime.py +0 -0
  304. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_uv_sync.py +0 -0
  305. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_wordpress_source.py +0 -0
  306. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_youtube_source.py +0 -0
  307. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/test_youtube_source_integration.py +0 -0
  308. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/utils/test_archive_extraction.py +0 -0
  309. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/utils/test_content_extraction.py +0 -0
  310. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/utils/test_embedded_files.py +0 -0
  311. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/utils/test_file_metadata.py +0 -0
  312. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/utils/test_file_parser.py +0 -0
  313. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/utils/test_file_to_images.py +0 -0
  314. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/utils/test_legacy_office.py +0 -0
  315. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/utils/test_payload.py +0 -0
  316. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/utils/test_transcription.py +0 -0
  317. {classifyre_cli-0.4.124 → classifyre_cli-0.4.126}/tests/utils/test_video_processing.py +0 -0
@@ -1,3 +1,3 @@
1
1
  $ uv sync
2
- Resolved 323 packages in 147ms
2
+ Resolved 323 packages in 199ms
3
3
  Checked 51 packages in 1ms
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: classifyre-cli
3
- Version: 0.4.124
3
+ Version: 0.4.126
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.124",
3
+ "version": "0.4.126",
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.124"
3
+ version = "0.4.126"
4
4
  description = "Classifyre CLI — scan and classify unstructured data sources"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -0,0 +1,244 @@
1
+ """Generate the list of packages a notebook can import without declaring them.
2
+
3
+ The CLI image already carries a lot: requests and lxml are always there, and
4
+ pdfplumber, duckdb, boto3, psycopg2 and the rest arrive the first time something
5
+ imports them. None of that was visible to a connector author, so people
6
+ re-declared packages that were already installed -- at a different version than
7
+ the lock had resolved.
8
+
9
+ So the list is derived rather than written: names and pins come from
10
+ ``pyproject.toml``, resolved versions come from ``uv.lock``. Add a dependency and
11
+ the notebook editor offers it; bump it and the editor shows the new version.
12
+
13
+ Two judgements are encoded here and nowhere else:
14
+
15
+ * ``INCLUDED_GROUPS`` -- which optional groups are worth advertising. Connector
16
+ and file-format groups are; the ML detector groups are not, because putting a
17
+ multi-gigabyte ``uv sync`` behind a one-line ``import`` is a trap rather than a
18
+ feature.
19
+ * ``MODULE_ALIASES`` -- the import name, where it differs from the distribution
20
+ name. This is what makes ``import bs4`` resolve back to ``beautifulsoup4`` for
21
+ the group-warming path in ``src/notebook/groups.py``.
22
+
23
+ Run from apps/cli:
24
+
25
+ uv run python scripts/generate_runtime_packages.py
26
+
27
+ ``tests/test_runtime_packages.py`` fails when the checked-in file drifts from
28
+ what this would produce, so the two cannot silently diverge.
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ import json
34
+ import re
35
+ import tomllib
36
+ from pathlib import Path
37
+ from typing import Any
38
+
39
+ CLI_ROOT = Path(__file__).resolve().parents[1]
40
+ PYPROJECT_PATH = CLI_ROOT / "pyproject.toml"
41
+ LOCK_PATH = CLI_ROOT / "uv.lock"
42
+ OUTPUT_PATH = (
43
+ CLI_ROOT.parents[1]
44
+ / "packages"
45
+ / "schemas"
46
+ / "src"
47
+ / "schemas"
48
+ / "notebook_runtime_packages.json"
49
+ )
50
+
51
+ #: Optional dependency groups worth telling a connector author about: database
52
+ #: drivers, object stores, SaaS clients and the file-format stack. Each is
53
+ #: installed on demand, so listing one is a promise that importing it works --
54
+ #: `src/notebook/groups.py` is what keeps that promise.
55
+ INCLUDED_GROUPS = (
56
+ "file-processing",
57
+ "regex",
58
+ "llm",
59
+ "postgresql",
60
+ "mysql",
61
+ "mssql",
62
+ "oracle",
63
+ "hive",
64
+ "databricks",
65
+ "snowflake",
66
+ "mongodb",
67
+ "neo4j",
68
+ "tableau",
69
+ "s3-compatible-storage",
70
+ "azure-blob-storage",
71
+ "microsoft-graph",
72
+ "google-workspace",
73
+ "google-cloud-storage",
74
+ "dropbox",
75
+ "hugging-face",
76
+ "slack",
77
+ "email",
78
+ "youtube",
79
+ "reddit",
80
+ "kafka",
81
+ "delta-lake",
82
+ "iceberg",
83
+ )
84
+
85
+ #: Everything else in `[dependency-groups]`, listed rather than inferred so that
86
+ #: adding a group is a deliberate decision in one place. The ML groups pull
87
+ #: torch, transformers or docling -- gigabytes that an author would trigger by
88
+ #: typing one import. `dev`, `interactive` and `otel` are tooling, not a
89
+ #: connector's vocabulary; `detectors` and `ocr` are aggregates of the rest.
90
+ EXCLUDED_GROUPS = (
91
+ "dev",
92
+ "interactive",
93
+ "detectors",
94
+ "ocr",
95
+ "otel",
96
+ "privacy",
97
+ "security",
98
+ "threat-ml",
99
+ "content",
100
+ "quality",
101
+ "classification",
102
+ "custom",
103
+ "transcription",
104
+ "video",
105
+ )
106
+
107
+ #: Import name, where it is not just the distribution name with '-' as '_'.
108
+ MODULE_ALIASES: dict[str, tuple[str, ...]] = {
109
+ "beautifulsoup4": ("bs4",),
110
+ "pillow": ("PIL",),
111
+ "python-docx": ("docx",),
112
+ "opencv-python-headless": ("cv2",),
113
+ "psycopg2-binary": ("psycopg2",),
114
+ "google-re2": ("re2",),
115
+ "databricks-sql-connector": ("databricks.sql",),
116
+ "snowflake-connector-python": ("snowflake.connector",),
117
+ # Namespace packages: the dotted path is the whole import name, so that
118
+ # `import azure` matches both of these and `import azure.identity` matches
119
+ # only the one that provides it.
120
+ "azure-storage-blob": ("azure.storage.blob",),
121
+ "azure-identity": ("azure.identity",),
122
+ "google-api-python-client": ("googleapiclient",),
123
+ "google-auth": ("google.auth",),
124
+ "google-cloud-storage": ("google.cloud.storage",),
125
+ "msgraph-sdk": ("msgraph",),
126
+ "en-core-web-sm": ("en_core_web_sm",),
127
+ "classifyre-schemas": ("schemas",),
128
+ }
129
+
130
+ #: Distributions that exist in the lock but are not something a notebook imports
131
+ #: directly: they are transports or model data pulled in by something else.
132
+ SKIPPED_DISTRIBUTIONS = frozenset({"classifyre-schemas", "en-core-web-sm"})
133
+
134
+ #: A requirement string is only ever read for its name here -- the version comes
135
+ #: from the lock, which is what actually gets installed.
136
+ _NAME_OF_REQUIREMENT = re.compile(r"^\s*([A-Za-z0-9][A-Za-z0-9._-]*)")
137
+
138
+
139
+ def _canonical(name: str) -> str:
140
+ """PEP 503 normalization, so pyproject and uv.lock spellings line up."""
141
+ return re.sub(r"[-_.]+", "-", name).lower()
142
+
143
+
144
+ def _requirement_name(requirement: Any) -> str | None:
145
+ """The distribution named by one `[dependency-groups]` entry.
146
+
147
+ Entries are usually strings; an `{include-group = ...}` table pulls in
148
+ another group, which the caller already resolves by listing groups
149
+ explicitly, so it is skipped rather than expanded.
150
+ """
151
+ if not isinstance(requirement, str):
152
+ return None
153
+ match = _NAME_OF_REQUIREMENT.match(requirement)
154
+ return _canonical(match.group(1)) if match else None
155
+
156
+
157
+ def _locked_versions() -> dict[str, str]:
158
+ """Resolved version per distribution, from uv.lock.
159
+
160
+ A name can appear twice when an index pin produces a local build (`+cpu`);
161
+ the plain version is the one to show, since it is what the wheel reports.
162
+ """
163
+ lock = tomllib.loads(LOCK_PATH.read_text(encoding="utf-8"))
164
+ versions: dict[str, list[str]] = {}
165
+ for package in lock.get("package", []):
166
+ name = _canonical(str(package.get("name", "")))
167
+ version = str(package.get("version", ""))
168
+ if name and version:
169
+ versions.setdefault(name, []).append(version)
170
+
171
+ resolved: dict[str, str] = {}
172
+ for name, candidates in versions.items():
173
+ plain = [version for version in candidates if "+" not in version]
174
+ resolved[name] = sorted(plain or candidates)[-1]
175
+ return resolved
176
+
177
+
178
+ def _modules(name: str) -> list[str]:
179
+ return list(MODULE_ALIASES.get(name, (name.replace("-", "_"),)))
180
+
181
+
182
+ def build() -> dict[str, Any]:
183
+ pyproject = tomllib.loads(PYPROJECT_PATH.read_text(encoding="utf-8"))
184
+ versions = _locked_versions()
185
+
186
+ groups = pyproject.get("dependency-groups", {})
187
+ unknown = sorted(set(groups) - set(INCLUDED_GROUPS) - set(EXCLUDED_GROUPS))
188
+ if unknown:
189
+ raise SystemExit(
190
+ f"pyproject.toml has dependency groups this script has no verdict on: {unknown}. "
191
+ "Add each to INCLUDED_GROUPS or EXCLUDED_GROUPS in "
192
+ "scripts/generate_runtime_packages.py."
193
+ )
194
+
195
+ # A distribution can appear in several groups (pillow, pyarrow, boto3). The
196
+ # first one wins, and `always` wins over any group, so the availability an
197
+ # author is shown is the strongest one that holds.
198
+ entries: dict[str, dict[str, Any]] = {}
199
+
200
+ for requirement in pyproject.get("project", {}).get("dependencies", []):
201
+ name = _requirement_name(requirement)
202
+ if not name or name in SKIPPED_DISTRIBUTIONS:
203
+ continue
204
+ entries[name] = {
205
+ "name": name,
206
+ "version": versions.get(name, ""),
207
+ "modules": _modules(name),
208
+ "availability": "always",
209
+ }
210
+
211
+ for group in INCLUDED_GROUPS:
212
+ for requirement in groups.get(group, []):
213
+ name = _requirement_name(requirement)
214
+ if not name or name in SKIPPED_DISTRIBUTIONS or name in entries:
215
+ continue
216
+ entries[name] = {
217
+ "name": name,
218
+ "version": versions.get(name, ""),
219
+ "modules": _modules(name),
220
+ "availability": "on-demand",
221
+ "group": group,
222
+ }
223
+
224
+ return {
225
+ "$comment": (
226
+ "Generated by apps/cli/scripts/generate_runtime_packages.py from "
227
+ "apps/cli/pyproject.toml and apps/cli/uv.lock. Do not edit by hand."
228
+ ),
229
+ "pythonVersion": str(pyproject.get("project", {}).get("requires-python", "")),
230
+ "packages": [entries[name] for name in sorted(entries)],
231
+ }
232
+
233
+
234
+ def main() -> int:
235
+ payload = build()
236
+ OUTPUT_PATH.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
237
+ print(
238
+ f"Wrote {OUTPUT_PATH.relative_to(CLI_ROOT.parents[1])} ({len(payload['packages'])} packages)"
239
+ )
240
+ return 0
241
+
242
+
243
+ if __name__ == "__main__":
244
+ raise SystemExit(main())
@@ -30,7 +30,14 @@ from src.notebook.contract import ( # noqa: E402
30
30
  OPTIONAL_FUNCTIONS,
31
31
  REQUIRED_FUNCTIONS,
32
32
  )
33
- from src.notebook.sdk import Asset, Context # noqa: E402
33
+ from src.notebook.sdk import ( # noqa: E402
34
+ Asset,
35
+ Context,
36
+ NotebookFile,
37
+ ParsedContent,
38
+ pages,
39
+ parse,
40
+ )
34
41
 
35
42
  OUTPUT_PATH = (
36
43
  CLI_ROOT.parents[1] / "packages" / "schemas" / "src" / "schemas" / "notebook_completions.json"
@@ -156,6 +163,22 @@ def _fields(cls: type) -> list[dict[str, Any]]:
156
163
  return fields
157
164
 
158
165
 
166
+ #: Module-level helpers a notebook can call directly. Offered alongside `ctx`
167
+ #: and `Asset` because `namespace()` pre-binds them, so they are as available as
168
+ #: the import line says they are.
169
+ MODULE_FUNCTIONS = (("parse", parse), ("pages", pages))
170
+
171
+
172
+ def _function_entry(name: str, func: Any) -> dict[str, Any]:
173
+ return {
174
+ "label": name,
175
+ "kind": "function",
176
+ "detail": _signature(func, drop_self=False),
177
+ "documentation": _summary(func),
178
+ "insertText": _snippet(name, func),
179
+ }
180
+
181
+
159
182
  def build() -> dict[str, Any]:
160
183
  return {
161
184
  "$comment": (
@@ -178,6 +201,7 @@ def build() -> dict[str, Any]:
178
201
  "documentation": _summary(Asset),
179
202
  "insertText": "Asset",
180
203
  },
204
+ *(_function_entry(name, func) for name, func in MODULE_FUNCTIONS),
181
205
  ],
182
206
  "objects": {
183
207
  "ctx": {"type": "Context", "members": _members(Context)},
@@ -187,6 +211,17 @@ def build() -> dict[str, Any]:
187
211
  "documentation": _summary(Asset),
188
212
  "fields": _fields(Asset),
189
213
  },
214
+ # Returned by `parse()` and reached as `ctx.file(...)`, so an author
215
+ # who never writes their name still needs their members offered.
216
+ "ParsedContent": {
217
+ "documentation": _summary(ParsedContent),
218
+ "fields": _fields(ParsedContent),
219
+ },
220
+ "NotebookFile": {
221
+ "documentation": _summary(NotebookFile),
222
+ "fields": _fields(NotebookFile),
223
+ "members": _members(NotebookFile),
224
+ },
190
225
  },
191
226
  "contract": {
192
227
  "required": list(REQUIRED_FUNCTIONS),
@@ -5427,6 +5427,27 @@ class NotebookPackage(BaseModel):
5427
5427
  )
5428
5428
 
5429
5429
 
5430
+ class NotebookLocalFolder(BaseModel):
5431
+ """
5432
+ A folder on the machine running the desktop application.
5433
+ """
5434
+
5435
+ model_config = ConfigDict(
5436
+ extra='forbid',
5437
+ )
5438
+ name: str = Field(
5439
+ ...,
5440
+ description='How the notebook refers to it: ctx.folder("name"). Must be a valid Python identifier.',
5441
+ pattern='^[A-Za-z_][A-Za-z0-9_]{0,62}$',
5442
+ )
5443
+ path: str = Field(
5444
+ ...,
5445
+ description='Absolute path to the folder, for example /Users/me/dumps.',
5446
+ max_length=4096,
5447
+ min_length=1,
5448
+ )
5449
+
5450
+
5430
5451
  class YouTubeInput(CoreInput):
5431
5452
  type: Literal['YOUTUBE'] = Field(..., description='Type of the asset or source')
5432
5453
  required: YouTubeRequired
@@ -5581,6 +5602,12 @@ class CustomOptional(BaseModel):
5581
5602
  max_length=50,
5582
5603
  validate_default=True,
5583
5604
  )
5605
+ local_folders: list[NotebookLocalFolder] | None = Field(
5606
+ [],
5607
+ description='Desktop only. Folders on this machine the notebook reads with ctx.folder("name"). Not available in Kubernetes deployments, where files are uploaded to the source instead. This is a convenience, not a sandbox: the notebook process runs as you and can open any path you can.',
5608
+ max_length=10,
5609
+ validate_default=True,
5610
+ )
5584
5611
 
5585
5612
 
5586
5613
  class CustomInput(CoreInput):
@@ -11,11 +11,14 @@ from __future__ import annotations
11
11
 
12
12
  import json
13
13
  import logging
14
+ import os
14
15
  import signal
15
16
  import sys
16
17
  from typing import Any
17
18
 
18
19
  from .execute import execute_notebook
20
+ from .files import local_folders
21
+ from .groups import warm_declared_groups
19
22
  from .packages import install as install_packages
20
23
  from .protocol import (
21
24
  RESULT_PREFIX,
@@ -46,12 +49,24 @@ def _string_map(value: Any) -> dict[str, str]:
46
49
  return {str(key): str(item) for key, item in value.items() if item is not None}
47
50
 
48
51
 
52
+ #: Where the API put this source's uploaded files before starting us. Set for an
53
+ #: execution that runs cells; absent for one that only validates.
54
+ NOTEBOOK_FILES_DIR_ENV = "CLASSIFYRE_NOTEBOOK_FILES_DIR"
55
+
56
+
49
57
  def build_context(recipe: dict[str, Any], *, should_abort: Any = None) -> Context:
50
- """The `ctx` a notebook sees, assembled from its recipe."""
58
+ """The `ctx` a notebook sees, assembled from its recipe.
59
+
60
+ Authoring and scanning have to agree here: a cell that reads ``ctx.files``
61
+ while someone is writing it must see the same files the scan will, or the
62
+ notebook is tested against a different source than it runs against.
63
+ """
51
64
  return Context(
52
65
  variables=_string_map(_section(recipe, "optional").get("variables")),
53
66
  secrets=_string_map(_section(recipe, "masked").get("secrets")),
54
67
  sampling=_section(recipe, "sampling"),
68
+ files_dir=os.environ.get(NOTEBOOK_FILES_DIR_ENV) or None,
69
+ folders=local_folders(recipe),
55
70
  should_abort=should_abort,
56
71
  )
57
72
 
@@ -129,6 +144,10 @@ def run_request(raw_request: dict[str, Any]) -> ExecutionResponse:
129
144
  sys.stderr = RedactingStream(sys.stderr, redactor)
130
145
 
131
146
  warm_interactive_group()
147
+ # What the notebook imports from the runtime's own optional groups. Warmed
148
+ # before the declared packages below because `uv sync --frozen` prunes
149
+ # anything `uv pip install` added -- the reverse order uninstalls them.
150
+ warm_declared_groups(cells)
132
151
 
133
152
  # Before any cell: a connector's client library has to be importable by the
134
153
  # first line that imports it, and resolving on every Run would make the
@@ -0,0 +1,180 @@
1
+ """Reading and parsing files from a notebook.
2
+
3
+ A connector that fetches files should not have to know how to read a PDF, an
4
+ .eml, a .docx or a Parquet file -- every other source in this system delegates
5
+ that to ``src/utils/file_parser.py``, and a notebook has more reason to than
6
+ most. So the parser is exposed rather than reimplemented: ``parse()`` is a thin
7
+ adapter over ``file_parser.parse_file`` / ``parse_bytes``, and ``pages()`` over
8
+ its streaming path.
9
+
10
+ Two deliberate choices:
11
+
12
+ * The result is a small ``ParsedContent`` of our own rather than the parser's
13
+ ``ParsedFile`` / ``ParsedBytes``. Those two differ (``encoding`` on one,
14
+ ``raw_content`` on the other) and are internal shapes free to change; a
15
+ notebook that is someone's saved work should not break when they do.
16
+ * ``file_parser`` is imported inside the functions. Importing it pulls the
17
+ ``file-processing`` group in at first use, and a notebook that never touches a
18
+ file should never pay for that.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import logging
24
+ from collections.abc import Iterator
25
+ from dataclasses import dataclass
26
+ from pathlib import Path
27
+ from typing import TYPE_CHECKING, Any
28
+
29
+ if TYPE_CHECKING: # the parser and its heavy dependencies stay a runtime import
30
+ from ..utils.file_parser import ParsedBytes, ParsedFile
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+ #: Text pages are read in this many lines, tabular payloads in this many rows.
35
+ DEFAULT_PAGE_SIZE = 100
36
+
37
+
38
+ @dataclass
39
+ class ParsedContent:
40
+ """What one file turned out to be.
41
+
42
+ ``error`` is populated rather than raised: a corrupt attachment in the
43
+ middle of a long scan should cost that one asset, not the run.
44
+ """
45
+
46
+ #: Detected content type, e.g. ``application/pdf``.
47
+ mime_type: str
48
+ #: Extracted text, ready to hand to ``Asset(content=...)``. Empty when the
49
+ #: payload has no text in it, or when ``error`` is set.
50
+ text: str
51
+ #: Whether the payload is binary rather than plain text.
52
+ is_binary: bool
53
+ size_bytes: int
54
+ #: Why extraction produced nothing, when it did not. ``None`` on success.
55
+ error: str | None = None
56
+
57
+ def __bool__(self) -> bool:
58
+ return self.error is None
59
+
60
+
61
+ def local_folders(recipe: dict[str, Any]) -> dict[str, str]:
62
+ """The desktop-only folders a source declared, as ``{name: path}``.
63
+
64
+ Read straight from the recipe rather than handed over separately: unlike an
65
+ uploaded file there is nothing to fetch, only a path the notebook may open.
66
+ Both the scan runner and the authoring CLI read it here so that ``ctx.folder``
67
+ means the same thing in the editor and in a real run.
68
+ """
69
+ optional = recipe.get("optional") if isinstance(recipe, dict) else None
70
+ declared = optional.get("local_folders") if isinstance(optional, dict) else None
71
+ if not isinstance(declared, list):
72
+ return {}
73
+ folders: dict[str, str] = {}
74
+ for entry in declared:
75
+ if isinstance(entry, dict) and entry.get("name") and entry.get("path"):
76
+ folders[str(entry["name"])] = str(entry["path"])
77
+ return folders
78
+
79
+
80
+ def _as_source(source: Any) -> Any:
81
+ """Unwrap the things a notebook is likely to pass.
82
+
83
+ ``ctx.file(...)`` hands back a ``NotebookFile``; accepting it directly saves
84
+ every caller from writing ``.path``.
85
+ """
86
+ path = getattr(source, "path", None)
87
+ return path if isinstance(path, Path) else source
88
+
89
+
90
+ def _name_of(source: Any, name: str) -> str:
91
+ if name:
92
+ return name
93
+ if isinstance(source, str | Path):
94
+ return Path(source).name
95
+ return str(getattr(source, "name", "") or "")
96
+
97
+
98
+ def parse(source: Any, *, name: str = "", mime_type: str | None = None) -> ParsedContent:
99
+ """Detect a payload's type and extract its text.
100
+
101
+ Accepts a path, raw bytes, an open binary file, or a ``NotebookFile`` from
102
+ ``ctx.files``. ``mime_type`` is a hint: it is trusted over sniffing when the
103
+ caller already knows, which matters for a payload downloaded from an API
104
+ that declared one.
105
+
106
+ for file in ctx.files:
107
+ parsed = parse(file)
108
+ yield Asset(id=file.name, content=parsed.text, content_bytes=file.read_bytes())
109
+ """
110
+ from ..utils.file_parser import TextExtractionCoverageError, parse_bytes, parse_file
111
+
112
+ resolved = _as_source(source)
113
+ file_name = _name_of(resolved, name)
114
+
115
+ # `ParsedFile` and `ParsedBytes` are separate shapes that happen to share
116
+ # the four fields below; the union is what lets both land on ParsedContent.
117
+ result: ParsedFile | ParsedBytes
118
+ try:
119
+ if isinstance(resolved, str | Path):
120
+ result = parse_file(Path(resolved))
121
+ else:
122
+ result = parse_bytes(resolved, declared_mime_type=mime_type, file_name=file_name)
123
+ except (OSError, TextExtractionCoverageError, ValueError) as exc:
124
+ logger.warning("Could not parse %s: %s", file_name or "payload", exc)
125
+ return ParsedContent(
126
+ mime_type=mime_type or "application/octet-stream",
127
+ text="",
128
+ is_binary=True,
129
+ size_bytes=0,
130
+ error=str(exc),
131
+ )
132
+
133
+ return ParsedContent(
134
+ mime_type=result.mime_type,
135
+ text=result.text_content or "",
136
+ is_binary=bool(result.is_binary),
137
+ size_bytes=int(result.file_size_bytes or 0),
138
+ error=result.parse_error,
139
+ )
140
+
141
+
142
+ def pages(
143
+ source: Any,
144
+ *,
145
+ name: str = "",
146
+ mime_type: str | None = None,
147
+ page_size: int = DEFAULT_PAGE_SIZE,
148
+ ) -> Iterator[str]:
149
+ """Read a payload in pages instead of all at once.
150
+
151
+ For a Parquet file or a large CSV, ``parse()`` materialises the whole thing
152
+ as one string. This yields rows (tabular) or lines (everything else) a page
153
+ at a time, so a multi-gigabyte dump can become many assets without ever
154
+ being held whole.
155
+
156
+ for index, page in enumerate(pages(ctx.file("dump.parquet"))):
157
+ yield Asset(id=f"dump-{index}", content=page, kind="table")
158
+ """
159
+ from ..utils.file_parser import iter_file_pages, resolve_mime_type
160
+
161
+ resolved = _as_source(source)
162
+ file_name = _name_of(resolved, name)
163
+
164
+ handle = None
165
+ try:
166
+ if isinstance(resolved, str | Path):
167
+ handle = Path(resolved).open("rb")
168
+ payload: Any = handle
169
+ else:
170
+ payload = resolved
171
+
172
+ resolved_mime = resolve_mime_type(
173
+ payload, declared_mime_type=mime_type, file_name=file_name
174
+ )
175
+ yield from iter_file_pages(
176
+ payload, resolved_mime, batch_size=page_size, file_name=file_name
177
+ )
178
+ finally:
179
+ if handle is not None:
180
+ handle.close()