classifyre-cli 0.4.125__tar.gz → 0.4.127__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.
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/.turbo/turbo-build.log +1 -1
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/PKG-INFO +1 -1
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/package.json +1 -1
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/pyproject.toml +1 -1
- classifyre_cli-0.4.127/scripts/generate_runtime_packages.py +244 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/scripts/generate_sdk_completions.py +36 -1
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/models/generated_input.py +27 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/notebook/cli.py +20 -1
- classifyre_cli-0.4.127/src/notebook/files.py +180 -0
- classifyre_cli-0.4.127/src/notebook/groups.py +118 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/notebook/sdk.py +117 -5
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/custom/runner.py +7 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/custom/source.py +77 -7
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/sandbox/source.py +6 -39
- classifyre_cli-0.4.127/src/utils/source_files.py +136 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_custom_source.py +155 -0
- classifyre_cli-0.4.127/tests/test_notebook_files.py +121 -0
- classifyre_cli-0.4.127/tests/test_notebook_groups.py +76 -0
- classifyre_cli-0.4.127/tests/test_notebook_templates.py +144 -0
- classifyre_cli-0.4.127/tests/test_runtime_packages.py +77 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/uv.lock +101 -80
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/.gitignore +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/.python-version +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/README.md +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/main.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/scripts/generate_models.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/config.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/base.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/broken_links/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/broken_links/detector.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/config.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/content/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/custom/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/custom/detector.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/custom/extractor.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/custom/runners/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/custom/runners/_base.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/custom/runners/_factory.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/custom/runners/_gliner2.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/custom/runners/_image_classification.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/custom/runners/_llm.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/custom/runners/_object_detection.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/custom/runners/_regex.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/custom/runners/_text_classification.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/custom/trainer.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/dependencies.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/engine_version.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/pii/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/pii/detector.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/secrets/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/secrets/detector.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/threat/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/threat/code_security_detector.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/detectors/threat/yara_detector.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/file_evaluation/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/file_evaluation/runner.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/main.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/models/generated_detectors.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/models/generated_single_asset_scan_results.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/notebook/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/notebook/contract.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/notebook/execute.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/notebook/outputs.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/notebook/packages.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/notebook/protocol.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/notebook/redact.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/notebook/scaffold.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/notebook/serialize.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/outputs/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/outputs/base.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/outputs/console.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/outputs/factory.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/outputs/file.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/outputs/rest.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/pipeline/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/pipeline/content_provider.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/pipeline/detector_pipeline.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/pipeline/parsed_content_provider.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/pipeline/payload_window.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/pipeline/scan_cache.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/pipeline/text_artifact.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/pipeline/worker_pool.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/asset_metadata.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/atlassian_common.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/azure_blob_storage/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/azure_blob_storage/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/base.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/confluence/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/confluence/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/custom/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/custom/env.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/databricks/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/databricks/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/delta_lake/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/delta_lake/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/dependencies.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/dropbox/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/dropbox/auth.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/dropbox/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/elasticsearch/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/elasticsearch/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/email/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/email/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/git/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/git/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/google_cloud_storage/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/google_cloud_storage/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/google_workspace/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/google_workspace/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/hive/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/hive/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/hugging_face/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/hugging_face/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/iceberg/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/iceberg/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/jira/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/jira/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/kafka/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/kafka/rest.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/kafka/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/lakehouse_base.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/local_folder/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/local_folder/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/meilisearch/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/meilisearch/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/microsoft_365/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/microsoft_365/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/mongodb/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/mongodb/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/mssql/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/mssql/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/mysql/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/mysql/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/neo4j/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/neo4j/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/notion/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/notion/client.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/notion/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/object_storage/base.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/opensearch/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/opensearch/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/oracle/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/oracle/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/postgresql/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/postgresql/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/powerbi/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/powerbi/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/recipe_normalizer.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/reddit/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/reddit/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/s3_client.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/s3_compatible_storage/README.md +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/s3_compatible_storage/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/s3_compatible_storage/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/sandbox/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/search_engine_base.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/servicedesk/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/servicedesk/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/slack/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/slack/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/snowflake/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/snowflake/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/sqlite/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/sqlite/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/tableau/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/tableau/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/tabular_base.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/tabular_utils.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/wordpress/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/wordpress/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/youtube/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/sources/youtube/source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/telemetry.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/utils/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/utils/archive_extraction.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/utils/content_extraction.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/utils/dependency_groups.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/utils/embedded_files.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/utils/external_video.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/utils/file_metadata.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/utils/file_parser.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/utils/file_to_images.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/utils/hashing.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/utils/legacy_office.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/utils/payload.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/utils/range_reader.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/utils/resources.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/utils/torch_runtime.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/utils/transcription.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/utils/uv_sync.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/utils/validation.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/src/utils/video_processing.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/_lakehouse_fakes.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/conftest.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/broken_links/test_broken_links_detector.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/conftest.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/content/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/custom/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/custom/conftest.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/custom/test_gliner2_classification.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/custom/test_invoice_extraction.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/custom/test_llm_runner.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/custom/test_pipeline_integration.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/custom/test_regex_runner.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/custom/test_runner_hardening.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/custom/test_transformer_runners.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/pii/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/pii/conftest.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/pii/sample_invoice.pdf +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/pii/test_pii_chunking.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/pii/test_pii_detector.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/pii/test_pii_detector_extended.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/pii/test_pii_regional_entities.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/pii/test_pii_severity.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/secrets/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/secrets/test_secrets_detector.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/secrets/test_secrets_detector_extended.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/test_base_detector.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/test_custom_detector_examples_runtime.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/test_detector_catalog_commercial.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/test_detector_pipeline_types.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/test_detector_schema_examples.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/test_detector_types.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/test_phase2_detectors.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/test_registry.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/threat/__init__.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/threat/test_code_security_detector.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/detectors/threat/test_yara_detector.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/integration/test_wordpress_broken_links_detector.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/integration/test_wordpress_links_assets.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/pipeline/test_detector_outcomes.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/pipeline/test_detector_pipeline.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/pipeline/test_worker_pool.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_arrow_ipc_payloads.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_assets_metadata_catalog.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_azure_blob_storage_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_base_source_attachment.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_base_source_sampling.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_byte_valued_fields.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_config.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_confluence_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_custom_extractor.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_databricks_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_delta_lake_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_dependency_groups.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_dropbox_auth_command.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_dropbox_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_elasticsearch_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_email_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_file_evaluation_runner.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_git_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_google_cloud_storage_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_google_workspace_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_hashing.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_hive_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_hugging_face_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_iceberg_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_jira_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_kafka_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_local_folder_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_main.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_meilisearch_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_microsoft_365_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_mongodb_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_mssql_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_mysql_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_neo4j_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_notebook_execute.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_notebook_packages.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_notebook_serialize.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_notion_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_object_storage_download_efficiency.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_opensearch_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_oracle_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_outputs.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_payload_window.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_payload_window_parquet.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_postgresql_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_powerbi_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_range_reading.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_recipe_normalizer.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_reddit_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_rest_backpressure.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_s3_compatible_storage_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_sampling_automatic.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_sandbox_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_scan_cache.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_sdk_completions.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_servicedesk_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_slack_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_snowflake_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_source_dependency_groups.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_source_strategy_conformance.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_sqlite_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_tableau_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_tabular_automatic_sampling.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_tabular_utils.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_torch_runtime.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_uv_sync.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_wordpress_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_youtube_source.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/test_youtube_source_integration.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/utils/test_archive_extraction.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/utils/test_content_extraction.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/utils/test_embedded_files.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/utils/test_file_metadata.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/utils/test_file_parser.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/utils/test_file_to_images.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/utils/test_legacy_office.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/utils/test_payload.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/utils/test_transcription.py +0 -0
- {classifyre_cli-0.4.125 → classifyre_cli-0.4.127}/tests/utils/test_video_processing.py +0 -0
|
@@ -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
|
|
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()
|