classifyre-cli 0.4.89__tar.gz → 0.4.91__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.89 → classifyre_cli-0.4.91}/.turbo/turbo-build.log +1 -1
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/PKG-INFO +1 -1
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/package.json +1 -1
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/pyproject.toml +1 -1
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/file_evaluation/runner.py +1 -1
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/atlassian_common.py +6 -1
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/base.py +6 -5
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/confluence/source.py +4 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/email/source.py +6 -1
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/google_workspace/source.py +10 -1
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/hugging_face/source.py +3 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/microsoft_365/source.py +10 -1
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/notion/source.py +4 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/object_storage/base.py +13 -2
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/utils/embedded_files.py +175 -52
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/utils/file_metadata.py +25 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/utils/file_parser.py +293 -38
- classifyre_cli-0.4.91/tests/test_arrow_ipc_payloads.py +473 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/uv.lock +37 -37
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/.gitignore +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/.python-version +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/README.md +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/main.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/scripts/generate_models.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/config.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/base.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/broken_links/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/broken_links/detector.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/config.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/content/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/custom/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/custom/detector.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/custom/extractor.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/custom/runners/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/custom/runners/_base.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/custom/runners/_factory.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/custom/runners/_gliner2.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/custom/runners/_image_classification.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/custom/runners/_llm.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/custom/runners/_object_detection.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/custom/runners/_regex.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/custom/runners/_text_classification.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/custom/trainer.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/dependencies.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/engine_version.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/pii/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/pii/detector.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/secrets/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/secrets/detector.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/threat/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/threat/code_security_detector.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/detectors/threat/yara_detector.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/file_evaluation/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/main.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/models/generated_detectors.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/models/generated_input.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/models/generated_single_asset_scan_results.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/outputs/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/outputs/base.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/outputs/console.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/outputs/factory.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/outputs/file.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/outputs/rest.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/pipeline/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/pipeline/content_provider.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/pipeline/detector_pipeline.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/pipeline/parsed_content_provider.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/pipeline/payload_window.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/pipeline/scan_cache.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/pipeline/text_artifact.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/pipeline/worker_pool.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/asset_metadata.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/azure_blob_storage/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/azure_blob_storage/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/confluence/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/databricks/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/databricks/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/delta_lake/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/delta_lake/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/dependencies.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/dropbox/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/dropbox/auth.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/dropbox/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/elasticsearch/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/elasticsearch/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/email/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/google_cloud_storage/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/google_cloud_storage/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/google_workspace/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/hive/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/hive/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/hugging_face/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/iceberg/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/iceberg/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/jira/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/jira/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/kafka/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/kafka/rest.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/kafka/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/lakehouse_base.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/local_folder/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/local_folder/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/meilisearch/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/meilisearch/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/microsoft_365/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/mongodb/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/mongodb/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/mssql/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/mssql/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/mysql/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/mysql/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/neo4j/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/neo4j/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/notion/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/notion/client.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/opensearch/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/opensearch/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/oracle/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/oracle/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/postgresql/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/postgresql/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/powerbi/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/powerbi/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/recipe_normalizer.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/reddit/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/reddit/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/s3_client.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/s3_compatible_storage/README.md +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/s3_compatible_storage/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/s3_compatible_storage/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/sandbox/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/sandbox/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/search_engine_base.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/servicedesk/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/servicedesk/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/slack/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/slack/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/snowflake/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/snowflake/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/sqlite/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/sqlite/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/tableau/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/tableau/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/tabular_base.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/tabular_utils.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/wordpress/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/wordpress/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/youtube/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/sources/youtube/source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/telemetry.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/utils/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/utils/archive_extraction.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/utils/content_extraction.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/utils/dependency_groups.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/utils/external_video.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/utils/file_to_images.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/utils/hashing.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/utils/legacy_office.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/utils/payload.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/utils/range_reader.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/utils/resources.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/utils/transcription.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/utils/uv_sync.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/utils/validation.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/src/utils/video_processing.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/_lakehouse_fakes.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/conftest.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/broken_links/test_broken_links_detector.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/conftest.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/content/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/custom/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/custom/conftest.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/custom/test_gliner2_classification.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/custom/test_invoice_extraction.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/custom/test_llm_runner.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/custom/test_pipeline_integration.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/custom/test_regex_runner.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/custom/test_runner_hardening.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/custom/test_transformer_runners.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/pii/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/pii/conftest.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/pii/sample_invoice.pdf +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/pii/test_pii_chunking.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/pii/test_pii_detector.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/pii/test_pii_detector_extended.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/pii/test_pii_regional_entities.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/pii/test_pii_severity.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/secrets/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/secrets/test_secrets_detector.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/secrets/test_secrets_detector_extended.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/test_base_detector.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/test_custom_detector_examples_runtime.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/test_detector_catalog_commercial.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/test_detector_pipeline_types.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/test_detector_schema_examples.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/test_detector_types.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/test_phase2_detectors.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/test_registry.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/threat/__init__.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/threat/test_code_security_detector.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/detectors/threat/test_yara_detector.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/integration/test_wordpress_broken_links_detector.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/integration/test_wordpress_links_assets.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/pipeline/test_detector_outcomes.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/pipeline/test_detector_pipeline.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/pipeline/test_worker_pool.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_assets_metadata_catalog.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_azure_blob_storage_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_base_source_attachment.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_base_source_sampling.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_byte_valued_fields.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_config.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_confluence_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_custom_extractor.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_databricks_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_delta_lake_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_dependency_groups.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_dropbox_auth_command.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_dropbox_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_elasticsearch_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_email_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_file_evaluation_runner.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_google_cloud_storage_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_google_workspace_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_hashing.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_hive_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_hugging_face_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_iceberg_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_jira_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_kafka_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_local_folder_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_main.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_meilisearch_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_microsoft_365_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_mongodb_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_mssql_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_mysql_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_neo4j_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_notion_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_object_storage_download_efficiency.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_opensearch_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_oracle_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_outputs.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_payload_window.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_payload_window_parquet.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_postgresql_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_powerbi_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_range_reading.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_recipe_normalizer.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_reddit_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_s3_compatible_storage_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_sampling_automatic.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_sandbox_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_scan_cache.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_servicedesk_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_slack_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_snowflake_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_source_dependency_groups.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_source_strategy_conformance.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_sqlite_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_tableau_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_tabular_automatic_sampling.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_tabular_utils.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_uv_sync.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_wordpress_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_youtube_source.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/test_youtube_source_integration.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/utils/test_archive_extraction.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/utils/test_content_extraction.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/utils/test_embedded_files.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/utils/test_file_metadata.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/utils/test_file_parser.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/utils/test_file_to_images.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/utils/test_legacy_office.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/utils/test_payload.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/utils/test_transcription.py +0 -0
- {classifyre_cli-0.4.89 → classifyre_cli-0.4.91}/tests/utils/test_video_processing.py +0 -0
|
@@ -148,7 +148,7 @@ class FileEvaluationRunner:
|
|
|
148
148
|
file_mime,
|
|
149
149
|
)
|
|
150
150
|
|
|
151
|
-
# Files that embed whole other files (parquet file columns, office media)
|
|
151
|
+
# Files that embed whole other files (parquet/Arrow file columns, office media)
|
|
152
152
|
# get each embedded file run through the binary detectors directly, with
|
|
153
153
|
# findings tagged by the embedded location so the UI can group them.
|
|
154
154
|
embedded_files = has_embedded_files(mime_type)
|
|
@@ -11,7 +11,7 @@ from urllib.parse import urljoin, urlsplit, urlunsplit
|
|
|
11
11
|
|
|
12
12
|
import requests
|
|
13
13
|
|
|
14
|
-
from ..utils.file_parser import normalize_mime_type
|
|
14
|
+
from ..utils.file_parser import ARROW_MIME_TYPES, normalize_mime_type
|
|
15
15
|
from ..utils.hashing import normalize_http_url
|
|
16
16
|
|
|
17
17
|
logger = logging.getLogger(__name__)
|
|
@@ -25,6 +25,7 @@ TABULAR_MIME_TYPES = {
|
|
|
25
25
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
26
26
|
"application/parquet",
|
|
27
27
|
"application/vnd.apache.parquet",
|
|
28
|
+
*ARROW_MIME_TYPES,
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
TABULAR_FILE_EXTENSIONS = {
|
|
@@ -33,6 +34,10 @@ TABULAR_FILE_EXTENSIONS = {
|
|
|
33
34
|
".xls",
|
|
34
35
|
".xlsx",
|
|
35
36
|
".parquet",
|
|
37
|
+
".arrow",
|
|
38
|
+
".arrows",
|
|
39
|
+
".feather",
|
|
40
|
+
".ipc",
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
|
|
@@ -399,7 +399,7 @@ class BaseSource(ABC):
|
|
|
399
399
|
unchanged.
|
|
400
400
|
|
|
401
401
|
``file_bytes`` may be a seekable binary handle instead of bytes (Parquet
|
|
402
|
-
|
|
402
|
+
and Arrow). The window then bounds what is *transferred*, not just what is
|
|
403
403
|
decoded: a source that can serve byte ranges reads the footer and the one
|
|
404
404
|
row group this run needs out of an object it never downloads whole.
|
|
405
405
|
"""
|
|
@@ -432,11 +432,11 @@ class BaseSource(ABC):
|
|
|
432
432
|
def _payload_rows_per_page_unit(mime_type: str, batch_size: int) -> int:
|
|
433
433
|
"""How many payload rows one page from ``iter_file_pages`` carries.
|
|
434
434
|
|
|
435
|
-
Record-shaped readers (Parquet, CSV/TSV) emit a page per row.
|
|
436
|
-
reach the detectors as extracted text, which is paged in
|
|
437
|
-
``batch_size`` lines — one line per sheet row.
|
|
435
|
+
Record-shaped readers (Parquet, Arrow IPC, CSV/TSV) emit a page per row.
|
|
436
|
+
Spreadsheets reach the detectors as extracted text, which is paged in
|
|
437
|
+
blocks of ``batch_size`` lines — one line per sheet row.
|
|
438
438
|
"""
|
|
439
|
-
from ..utils.file_parser import normalize_mime_type
|
|
439
|
+
from ..utils.file_parser import ARROW_MIME_TYPES, normalize_mime_type
|
|
440
440
|
|
|
441
441
|
normalized = normalize_mime_type(mime_type)
|
|
442
442
|
record_shaped = {
|
|
@@ -444,6 +444,7 @@ class BaseSource(ABC):
|
|
|
444
444
|
"application/vnd.apache.parquet",
|
|
445
445
|
"text/csv",
|
|
446
446
|
"text/tab-separated-values",
|
|
447
|
+
*ARROW_MIME_TYPES,
|
|
447
448
|
}
|
|
448
449
|
return 1 if normalized in record_shaped else max(1, batch_size)
|
|
449
450
|
|
|
@@ -71,6 +71,10 @@ FILE_EXTENSION_HINTS: dict[str, OutputAssetType] = {
|
|
|
71
71
|
".tar": OutputAssetType.BINARY,
|
|
72
72
|
".gz": OutputAssetType.BINARY,
|
|
73
73
|
".parquet": OutputAssetType.TABLE,
|
|
74
|
+
".arrow": OutputAssetType.TABLE,
|
|
75
|
+
".arrows": OutputAssetType.TABLE,
|
|
76
|
+
".feather": OutputAssetType.TABLE,
|
|
77
|
+
".ipc": OutputAssetType.TABLE,
|
|
74
78
|
".json": OutputAssetType.TXT,
|
|
75
79
|
".xml": OutputAssetType.TXT,
|
|
76
80
|
".txt": OutputAssetType.TXT,
|
|
@@ -25,7 +25,11 @@ from ...models.generated_single_asset_scan_results import (
|
|
|
25
25
|
)
|
|
26
26
|
from ...utils.content_extraction import html_to_text
|
|
27
27
|
from ...utils.file_metadata import extract_file_metadata
|
|
28
|
-
from ...utils.file_parser import
|
|
28
|
+
from ...utils.file_parser import (
|
|
29
|
+
ARROW_MIME_TYPES,
|
|
30
|
+
normalize_mime_type,
|
|
31
|
+
resolve_mime_type,
|
|
32
|
+
)
|
|
29
33
|
from ...utils.hashing import hash_id
|
|
30
34
|
from ..base import BaseSource
|
|
31
35
|
from ..dependencies import require_module
|
|
@@ -39,6 +43,7 @@ _TABULAR_MIME_TYPES = {
|
|
|
39
43
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
40
44
|
"application/parquet",
|
|
41
45
|
"application/vnd.apache.parquet",
|
|
46
|
+
*ARROW_MIME_TYPES,
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
# MIME types whose text can be extracted (by file_parser) and so should route
|
|
@@ -28,7 +28,11 @@ from ...models.generated_single_asset_scan_results import (
|
|
|
28
28
|
SingleAssetScanResults,
|
|
29
29
|
)
|
|
30
30
|
from ...utils.file_metadata import extract_file_metadata
|
|
31
|
-
from ...utils.file_parser import
|
|
31
|
+
from ...utils.file_parser import (
|
|
32
|
+
ARROW_MIME_TYPES,
|
|
33
|
+
normalize_mime_type,
|
|
34
|
+
resolve_mime_type,
|
|
35
|
+
)
|
|
32
36
|
from ...utils.hashing import hash_id
|
|
33
37
|
from ..base import BaseSource
|
|
34
38
|
from ..dependencies import require_module
|
|
@@ -85,6 +89,7 @@ _TABULAR_MIME_TYPES = {
|
|
|
85
89
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
86
90
|
"application/parquet",
|
|
87
91
|
"application/vnd.apache.parquet",
|
|
92
|
+
*ARROW_MIME_TYPES,
|
|
88
93
|
}
|
|
89
94
|
|
|
90
95
|
_FILE_EXTENSION_HINTS: dict[str, OutputAssetType] = {
|
|
@@ -109,6 +114,10 @@ _FILE_EXTENSION_HINTS: dict[str, OutputAssetType] = {
|
|
|
109
114
|
".xlsx": OutputAssetType.TABLE,
|
|
110
115
|
".pptx": OutputAssetType.BINARY,
|
|
111
116
|
".parquet": OutputAssetType.TABLE,
|
|
117
|
+
".arrow": OutputAssetType.TABLE,
|
|
118
|
+
".arrows": OutputAssetType.TABLE,
|
|
119
|
+
".feather": OutputAssetType.TABLE,
|
|
120
|
+
".ipc": OutputAssetType.TABLE,
|
|
112
121
|
".json": OutputAssetType.TXT,
|
|
113
122
|
".xml": OutputAssetType.TXT,
|
|
114
123
|
".txt": OutputAssetType.TXT,
|
|
@@ -450,6 +450,9 @@ class HuggingFaceSource(ObjectStorageSourceBase):
|
|
|
450
450
|
where the row groups are — so it yields not fewer rows but none. Reading
|
|
451
451
|
by range inverts the cost: the sampling window picks the rows, and only
|
|
452
452
|
the footer plus the row groups holding those rows ever cross the wire.
|
|
453
|
+
Arrow IPC shards are read the same way — their footer indexes batches by
|
|
454
|
+
offset rather than by row, so a window costs the batches up to its end
|
|
455
|
+
instead of the whole shard.
|
|
453
456
|
|
|
454
457
|
The Hub's ``resolve`` endpoint and its CDN both honour ``Range``. If a
|
|
455
458
|
deployment ever does not, ``HttpRangeReader`` still returns correct bytes
|
|
@@ -32,7 +32,11 @@ from ...models.generated_single_asset_scan_results import (
|
|
|
32
32
|
SingleAssetScanResults,
|
|
33
33
|
)
|
|
34
34
|
from ...utils.file_metadata import extract_file_metadata
|
|
35
|
-
from ...utils.file_parser import
|
|
35
|
+
from ...utils.file_parser import (
|
|
36
|
+
ARROW_MIME_TYPES,
|
|
37
|
+
normalize_mime_type,
|
|
38
|
+
resolve_mime_type,
|
|
39
|
+
)
|
|
36
40
|
from ...utils.hashing import hash_id
|
|
37
41
|
from ..base import BaseSource
|
|
38
42
|
from ..dependencies import require_module
|
|
@@ -53,6 +57,7 @@ _TABULAR_MIME_TYPES = {
|
|
|
53
57
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
54
58
|
"application/parquet",
|
|
55
59
|
"application/vnd.apache.parquet",
|
|
60
|
+
*ARROW_MIME_TYPES,
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
_FILE_EXTENSION_HINTS: dict[str, OutputAssetType] = {
|
|
@@ -77,6 +82,10 @@ _FILE_EXTENSION_HINTS: dict[str, OutputAssetType] = {
|
|
|
77
82
|
".xlsx": OutputAssetType.TABLE,
|
|
78
83
|
".pptx": OutputAssetType.BINARY,
|
|
79
84
|
".parquet": OutputAssetType.TABLE,
|
|
85
|
+
".arrow": OutputAssetType.TABLE,
|
|
86
|
+
".arrows": OutputAssetType.TABLE,
|
|
87
|
+
".feather": OutputAssetType.TABLE,
|
|
88
|
+
".ipc": OutputAssetType.TABLE,
|
|
80
89
|
".json": OutputAssetType.TXT,
|
|
81
90
|
".xml": OutputAssetType.TXT,
|
|
82
91
|
".txt": OutputAssetType.TXT,
|
|
@@ -68,6 +68,10 @@ FILE_EXTENSION_HINTS: dict[str, OutputAssetType] = {
|
|
|
68
68
|
".xls": OutputAssetType.TABLE,
|
|
69
69
|
".xlsx": OutputAssetType.TABLE,
|
|
70
70
|
".parquet": OutputAssetType.TABLE,
|
|
71
|
+
".arrow": OutputAssetType.TABLE,
|
|
72
|
+
".arrows": OutputAssetType.TABLE,
|
|
73
|
+
".feather": OutputAssetType.TABLE,
|
|
74
|
+
".ipc": OutputAssetType.TABLE,
|
|
71
75
|
".csv": OutputAssetType.TABLE,
|
|
72
76
|
".tsv": OutputAssetType.TABLE,
|
|
73
77
|
".json": OutputAssetType.TXT,
|
|
@@ -38,6 +38,8 @@ from ...utils.embedded_files import (
|
|
|
38
38
|
)
|
|
39
39
|
from ...utils.file_metadata import extract_file_metadata
|
|
40
40
|
from ...utils.file_parser import (
|
|
41
|
+
ARROW_FILE_MIME_TYPE,
|
|
42
|
+
ARROW_MIME_TYPES,
|
|
41
43
|
count_tabular_rows,
|
|
42
44
|
infer_mime_type_from_file_name,
|
|
43
45
|
normalize_mime_type,
|
|
@@ -66,6 +68,7 @@ _TABULAR_MIME_TYPES = {
|
|
|
66
68
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
67
69
|
"application/parquet",
|
|
68
70
|
"application/vnd.apache.parquet",
|
|
71
|
+
*ARROW_MIME_TYPES,
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
|
|
@@ -109,6 +112,10 @@ _FILE_EXTENSION_HINTS: dict[str, OutputAssetType] = {
|
|
|
109
112
|
".gz": OutputAssetType.BINARY,
|
|
110
113
|
".tgz": OutputAssetType.BINARY,
|
|
111
114
|
".parquet": OutputAssetType.TABLE,
|
|
115
|
+
".arrow": OutputAssetType.TABLE,
|
|
116
|
+
".arrows": OutputAssetType.TABLE,
|
|
117
|
+
".feather": OutputAssetType.TABLE,
|
|
118
|
+
".ipc": OutputAssetType.TABLE,
|
|
112
119
|
".json": OutputAssetType.TXT,
|
|
113
120
|
".xml": OutputAssetType.TXT,
|
|
114
121
|
".txt": OutputAssetType.TXT,
|
|
@@ -149,8 +156,12 @@ _MAX_PENDING_CONTAINER_BYTES = 256 * 1024 * 1024
|
|
|
149
156
|
|
|
150
157
|
# Formats a seekable handle buys anything for: the reader can seek to an index
|
|
151
158
|
# and pull only the parts it needs. Everything else is extracted whole, so a
|
|
152
|
-
# handle would just be a slower way to download the object.
|
|
153
|
-
|
|
159
|
+
# handle would just be a slower way to download the object. The Arrow *streaming*
|
|
160
|
+
# layout is deliberately absent: it has no index, so reading it through ranges
|
|
161
|
+
# transfers the same bytes in more requests.
|
|
162
|
+
_RANGE_READABLE_MIME_TYPES = frozenset(
|
|
163
|
+
{"application/parquet", "application/vnd.apache.parquet", ARROW_FILE_MIME_TYPE}
|
|
164
|
+
)
|
|
154
165
|
|
|
155
166
|
|
|
156
167
|
@dataclass
|
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
"""Extract whole files embedded inside
|
|
1
|
+
"""Extract whole files embedded inside table rows and OOXML media folders.
|
|
2
2
|
|
|
3
3
|
Some files carry other *files* inside them rather than beside them: a HuggingFace
|
|
4
4
|
dataset stores an ``image`` or ``audio`` column as ``struct<bytes, path>``, a
|
|
5
|
-
parquet export can hold a column of PDF or archive blobs, and Office
|
|
6
|
-
embed media under their ``media/`` folders. This module surfaces those
|
|
7
|
-
bytes so the scan pipeline can turn each one into its own child asset —
|
|
8
|
-
``utils.file_parser`` and scanned like any standalone file — instead of
|
|
9
|
-
undecodable bytes into text detectors.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
``pipeline.payload_window``), so a run
|
|
14
|
-
|
|
15
|
-
numbers stay absolute, which is what keeps a
|
|
16
|
-
row 4,000,001 is ``row=4000001;col=image``
|
|
17
|
-
|
|
5
|
+
parquet or Arrow export can hold a column of PDF or archive blobs, and Office
|
|
6
|
+
documents embed media under their ``media/`` folders. This module surfaces those
|
|
7
|
+
as raw bytes so the scan pipeline can turn each one into its own child asset —
|
|
8
|
+
parsed by ``utils.file_parser`` and scanned like any standalone file — instead of
|
|
9
|
+
dumping undecodable bytes into text detectors.
|
|
10
|
+
|
|
11
|
+
Row-shaped containers (parquet, Arrow IPC / Feather) are extracted by row.
|
|
12
|
+
``start_row``/``max_rows`` bound the walk to the same window the sampling strategy
|
|
13
|
+
applies to that payload's rows (see ``pipeline.payload_window``), so a run
|
|
14
|
+
materializes the children of the rows it actually scanned rather than the first N
|
|
15
|
+
rows of the file on every run. Row numbers stay absolute, which is what keeps a
|
|
16
|
+
child's identity stable: the image in row 4,000,001 is ``row=4000001;col=image``
|
|
17
|
+
whichever window found it, and whichever format it was stored in. OOXML media has
|
|
18
|
+
no row axis, so the bounds are ignored there.
|
|
18
19
|
|
|
19
20
|
On any missing optional dependency or parse failure the iterators log a warning and
|
|
20
21
|
yield nothing, so callers degrade gracefully.
|
|
@@ -30,11 +31,14 @@ from dataclasses import dataclass
|
|
|
30
31
|
from typing import Any
|
|
31
32
|
|
|
32
33
|
from .file_parser import (
|
|
34
|
+
ARROW_MIME_TYPES,
|
|
33
35
|
OCTET_STREAM,
|
|
36
|
+
_arrow_truncation_error,
|
|
34
37
|
_normalize_mime_type,
|
|
35
38
|
_parquet_row_group_span,
|
|
36
39
|
_require_file_processing,
|
|
37
40
|
is_readable_text,
|
|
41
|
+
open_arrow_batches,
|
|
38
42
|
resolve_mime_type,
|
|
39
43
|
)
|
|
40
44
|
|
|
@@ -121,7 +125,11 @@ def classify_embedded_mime(mime_type: str) -> str:
|
|
|
121
125
|
def has_embedded_files(mime_type: str) -> bool:
|
|
122
126
|
"""Return True if this MIME type is a container we can pull whole files out of."""
|
|
123
127
|
normalized = _normalize_mime_type(mime_type)
|
|
124
|
-
return
|
|
128
|
+
return (
|
|
129
|
+
normalized in _PARQUET_MIME_TYPES
|
|
130
|
+
or normalized in ARROW_MIME_TYPES
|
|
131
|
+
or normalized in _OOXML_MIME_TYPES
|
|
132
|
+
)
|
|
125
133
|
|
|
126
134
|
|
|
127
135
|
def iter_embedded_files(
|
|
@@ -132,14 +140,14 @@ def iter_embedded_files(
|
|
|
132
140
|
max_rows: int | None = None,
|
|
133
141
|
max_files: int = DEFAULT_MAX_EMBEDDED_FILES,
|
|
134
142
|
) -> Iterator[EmbeddedFile]:
|
|
135
|
-
"""Yield the files embedded in a parquet or OOXML container.
|
|
143
|
+
"""Yield the files embedded in a parquet, Arrow IPC, or OOXML container.
|
|
136
144
|
|
|
137
|
-
``start_row``/``max_rows`` bound a
|
|
145
|
+
``start_row``/``max_rows`` bound a row-shaped walk to one row window and are
|
|
138
146
|
ignored for containers with no row axis.
|
|
139
147
|
|
|
140
|
-
``source`` may be a seekable handle rather than bytes (parquet
|
|
141
|
-
is how an object too large to download still yields its embedded
|
|
142
|
-
only the
|
|
148
|
+
``source`` may be a seekable handle rather than bytes (parquet and Arrow),
|
|
149
|
+
which is how an object too large to download still yields its embedded
|
|
150
|
+
children: only the batches the window covers are read. OOXML has no row axis
|
|
143
151
|
and is read whole, so it stays on bytes.
|
|
144
152
|
"""
|
|
145
153
|
is_bytes = isinstance(source, bytes | bytearray)
|
|
@@ -153,6 +161,13 @@ def iter_embedded_files(
|
|
|
153
161
|
max_rows=max_rows,
|
|
154
162
|
max_files=max_files,
|
|
155
163
|
)
|
|
164
|
+
elif normalized in ARROW_MIME_TYPES:
|
|
165
|
+
yield from _iter_arrow_files(
|
|
166
|
+
source,
|
|
167
|
+
start_row=start_row,
|
|
168
|
+
max_rows=max_rows,
|
|
169
|
+
max_files=max_files,
|
|
170
|
+
)
|
|
156
171
|
elif normalized in _OOXML_MIME_TYPES and is_bytes:
|
|
157
172
|
yield from _iter_ooxml_files(source, max_files)
|
|
158
173
|
|
|
@@ -188,33 +203,10 @@ def embedded_cell_name(cell: object, kind: str) -> str:
|
|
|
188
203
|
return ""
|
|
189
204
|
|
|
190
205
|
|
|
191
|
-
def
|
|
192
|
-
|
|
193
|
-
*,
|
|
194
|
-
sample_row_group: int | None = None,
|
|
195
|
-
) -> dict[str, EmbeddedColumn]:
|
|
196
|
-
"""Map column name → ``EmbeddedColumn`` for every column whose cells hold bytes.
|
|
197
|
-
|
|
198
|
-
All of them are returned, classified by what the bytes turned out to be, because
|
|
199
|
-
each class needs different handling and none of them may fall through to
|
|
200
|
-
``str(cell)`` — that renders a Python bytes repr, which is how a column of JSON
|
|
201
|
-
documents reached the detectors as ``b'{"email": ...}'`` with escaped quotes.
|
|
202
|
-
|
|
203
|
-
Classification is per column, sampled once: sniffing every cell of a
|
|
204
|
-
million-row file would cost more than the scan it feeds. A column of uniform
|
|
205
|
-
content — the normal case — is therefore classified correctly, and the
|
|
206
|
-
child-asset path re-checks each cell it emits anyway.
|
|
207
|
-
|
|
208
|
-
Returns ``{}`` when pyarrow is unavailable or no column carries bytes.
|
|
209
|
-
"""
|
|
210
|
-
try:
|
|
211
|
-
pa = _require_file_processing("pyarrow")
|
|
212
|
-
except Exception as exc: # pragma: no cover - dependency missing
|
|
213
|
-
logger.warning("Cannot inspect parquet file columns: %s", exc)
|
|
214
|
-
return {}
|
|
215
|
-
|
|
206
|
+
def _byte_carrying_columns(pa: Any, schema: Any) -> list[tuple[str, str]]:
|
|
207
|
+
"""``(column name, kind)`` for every field of an Arrow schema holding bytes."""
|
|
216
208
|
candidates: list[tuple[str, str]] = []
|
|
217
|
-
for field in
|
|
209
|
+
for field in schema:
|
|
218
210
|
field_type = field.type
|
|
219
211
|
if pa.types.is_struct(field_type): # type: ignore[attr-defined]
|
|
220
212
|
child_names = {
|
|
@@ -227,15 +219,31 @@ def detect_parquet_payload_columns(
|
|
|
227
219
|
field_type
|
|
228
220
|
):
|
|
229
221
|
candidates.append((field.name, "binary"))
|
|
222
|
+
return candidates
|
|
230
223
|
|
|
231
|
-
|
|
224
|
+
|
|
225
|
+
def detect_parquet_payload_columns(
|
|
226
|
+
parquet_file: Any,
|
|
227
|
+
*,
|
|
228
|
+
sample_row_group: int | None = None,
|
|
229
|
+
) -> dict[str, EmbeddedColumn]:
|
|
230
|
+
"""``detect_payload_columns`` for a Parquet file, sampled from one row group.
|
|
231
|
+
|
|
232
|
+
Naming the row group matters: handed an open-ended batch iterator, pyarrow
|
|
233
|
+
buffers every group in the file before yielding the first one — which over a
|
|
234
|
+
range-reading handle means downloading the whole object to sniff one batch.
|
|
235
|
+
Callers that already know which group they are about to read pass it, so the
|
|
236
|
+
sample costs nothing beyond the window itself.
|
|
237
|
+
"""
|
|
238
|
+
try:
|
|
239
|
+
pa = _require_file_processing("pyarrow")
|
|
240
|
+
except Exception as exc: # pragma: no cover - dependency missing
|
|
241
|
+
logger.warning("Cannot inspect parquet file columns: %s", exc)
|
|
242
|
+
return {}
|
|
243
|
+
|
|
244
|
+
if not _byte_carrying_columns(pa, parquet_file.schema_arrow):
|
|
232
245
|
return {}
|
|
233
246
|
|
|
234
|
-
# Naming the row group matters: handed an open-ended batch iterator, pyarrow
|
|
235
|
-
# buffers every group in the file before yielding the first one — which over a
|
|
236
|
-
# range-reading handle means downloading the whole object to sniff one batch.
|
|
237
|
-
# Callers that already know which group they are about to read pass it, so the
|
|
238
|
-
# sample costs nothing beyond the window itself.
|
|
239
247
|
try:
|
|
240
248
|
sample_kwargs: dict[str, Any] = {"batch_size": _COLUMN_SAMPLE_ROWS}
|
|
241
249
|
groups = int(parquet_file.metadata.num_row_groups)
|
|
@@ -247,6 +255,34 @@ def detect_parquet_payload_columns(
|
|
|
247
255
|
logger.warning("Cannot sample parquet columns: %s", exc)
|
|
248
256
|
return {}
|
|
249
257
|
|
|
258
|
+
return detect_payload_columns(parquet_file.schema_arrow, sample)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def detect_payload_columns(schema: Any, sample: Any) -> dict[str, EmbeddedColumn]:
|
|
262
|
+
"""Map column name → ``EmbeddedColumn`` for every column whose cells hold bytes.
|
|
263
|
+
|
|
264
|
+
All of them are returned, classified by what the bytes turned out to be, because
|
|
265
|
+
each class needs different handling and none of them may fall through to
|
|
266
|
+
``str(cell)`` — that renders a Python bytes repr, which is how a column of JSON
|
|
267
|
+
documents reached the detectors as ``b'{"email": ...}'`` with escaped quotes.
|
|
268
|
+
|
|
269
|
+
Classification is per column, sampled once from ``sample`` (one record batch):
|
|
270
|
+
sniffing every cell of a million-row file would cost more than the scan it
|
|
271
|
+
feeds. A column of uniform content — the normal case — is therefore classified
|
|
272
|
+
correctly, and the child-asset path re-checks each cell it emits anyway.
|
|
273
|
+
|
|
274
|
+
Returns ``{}`` when pyarrow is unavailable or no column carries bytes.
|
|
275
|
+
"""
|
|
276
|
+
try:
|
|
277
|
+
pa = _require_file_processing("pyarrow")
|
|
278
|
+
except Exception as exc: # pragma: no cover - dependency missing
|
|
279
|
+
logger.warning("Cannot inspect columns for embedded files: %s", exc)
|
|
280
|
+
return {}
|
|
281
|
+
|
|
282
|
+
candidates = _byte_carrying_columns(pa, schema)
|
|
283
|
+
if not candidates:
|
|
284
|
+
return {}
|
|
285
|
+
|
|
250
286
|
columns: dict[str, EmbeddedColumn] = {}
|
|
251
287
|
for name, kind in candidates:
|
|
252
288
|
mime, sample_bytes = _sample_column_payload(sample, name, kind)
|
|
@@ -385,6 +421,93 @@ def _iter_parquet_files(
|
|
|
385
421
|
logger.warning("Parquet embedded-file iteration failed: %s", exc)
|
|
386
422
|
|
|
387
423
|
|
|
424
|
+
# ---------------------------------------------------------------------------
|
|
425
|
+
# Arrow IPC / Feather
|
|
426
|
+
# ---------------------------------------------------------------------------
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _iter_arrow_files(
|
|
430
|
+
source: bytes | Any,
|
|
431
|
+
*,
|
|
432
|
+
start_row: int,
|
|
433
|
+
max_rows: int | None,
|
|
434
|
+
max_files: int,
|
|
435
|
+
) -> Iterator[EmbeddedFile]:
|
|
436
|
+
"""The parquet walk over an Arrow payload: same rows, same child identities.
|
|
437
|
+
|
|
438
|
+
A HuggingFace dataset is published as both formats, so an ``image`` column is
|
|
439
|
+
a ``struct<bytes, path>`` either way and has to become the same child asset
|
|
440
|
+
either way — ``row=N;col=image``, numbered absolutely, so a second run updates
|
|
441
|
+
the child rather than duplicating it.
|
|
442
|
+
"""
|
|
443
|
+
truncation = _arrow_truncation_error(source)
|
|
444
|
+
if truncation is not None:
|
|
445
|
+
logger.warning("%s Skipping embedded-file extraction.", truncation)
|
|
446
|
+
return
|
|
447
|
+
|
|
448
|
+
try:
|
|
449
|
+
schema, batches = open_arrow_batches(source, batch_rows=_ROW_BATCH_SIZE)
|
|
450
|
+
except Exception as exc:
|
|
451
|
+
logger.warning("Cannot open Arrow payload for embedded-file extraction: %s", exc)
|
|
452
|
+
return
|
|
453
|
+
|
|
454
|
+
begin = max(0, start_row)
|
|
455
|
+
files = 0
|
|
456
|
+
rows_read = 0
|
|
457
|
+
abs_row = 0
|
|
458
|
+
# Only file-bearing columns produce child assets. A text column is decoded into
|
|
459
|
+
# its own row's text by the page iterator, so walking it here would scan the
|
|
460
|
+
# same characters twice under two different asset identities.
|
|
461
|
+
columns: dict[str, EmbeddedColumn] | None = None
|
|
462
|
+
try:
|
|
463
|
+
for batch in batches:
|
|
464
|
+
rows = batch.num_rows
|
|
465
|
+
if abs_row + rows <= begin:
|
|
466
|
+
abs_row += rows
|
|
467
|
+
continue
|
|
468
|
+
if columns is None:
|
|
469
|
+
# Sampled from the first batch of the window, which is being read
|
|
470
|
+
# anyway — the same bargain the parquet walk strikes.
|
|
471
|
+
columns = {
|
|
472
|
+
name: column
|
|
473
|
+
for name, column in detect_payload_columns(schema, batch).items()
|
|
474
|
+
if column.content == CONTENT_FILE
|
|
475
|
+
}
|
|
476
|
+
if not columns:
|
|
477
|
+
return
|
|
478
|
+
for local_index in range(rows):
|
|
479
|
+
if abs_row < begin:
|
|
480
|
+
abs_row += 1
|
|
481
|
+
continue
|
|
482
|
+
if max_rows is not None and rows_read >= max_rows:
|
|
483
|
+
return
|
|
484
|
+
for column_name, column in columns.items():
|
|
485
|
+
cell = batch.column(column_name)[local_index].as_py()
|
|
486
|
+
raw = extract_embedded_bytes(cell, column.kind)
|
|
487
|
+
if not raw:
|
|
488
|
+
continue
|
|
489
|
+
mime = resolve_mime_type(raw, file_name=embedded_cell_name(cell, column.kind))
|
|
490
|
+
if not is_embeddable_file_mime(mime):
|
|
491
|
+
continue
|
|
492
|
+
yield EmbeddedFile(
|
|
493
|
+
location=f"row={abs_row + 1};col={column_name}",
|
|
494
|
+
file_bytes=raw,
|
|
495
|
+
mime_type=mime,
|
|
496
|
+
)
|
|
497
|
+
files += 1
|
|
498
|
+
if files >= max_files:
|
|
499
|
+
logger.info(
|
|
500
|
+
"Reached max embedded files (%d); stopping extraction", max_files
|
|
501
|
+
)
|
|
502
|
+
return
|
|
503
|
+
abs_row += 1
|
|
504
|
+
rows_read += 1
|
|
505
|
+
if max_rows is not None and rows_read >= max_rows:
|
|
506
|
+
return
|
|
507
|
+
except Exception as exc:
|
|
508
|
+
logger.warning("Arrow embedded-file iteration failed: %s", exc)
|
|
509
|
+
|
|
510
|
+
|
|
388
511
|
# ---------------------------------------------------------------------------
|
|
389
512
|
# OOXML (xlsx / docx / pptx)
|
|
390
513
|
# ---------------------------------------------------------------------------
|
|
@@ -82,6 +82,8 @@ def extract_file_metadata(
|
|
|
82
82
|
metadata.update(_docx_metadata(file_bytes))
|
|
83
83
|
elif _is_parquet(normalized, extension):
|
|
84
84
|
metadata.update(_parquet_metadata(file_bytes))
|
|
85
|
+
elif _is_arrow(normalized, extension):
|
|
86
|
+
metadata.update(_arrow_metadata(file_bytes))
|
|
85
87
|
elif _is_xlsx(normalized, extension):
|
|
86
88
|
metadata.update(_xlsx_metadata(file_bytes))
|
|
87
89
|
elif _is_delimited(normalized, extension):
|
|
@@ -114,10 +116,17 @@ _IMAGE_EXTENSIONS = {
|
|
|
114
116
|
_TEXT_EXTENSIONS = {".txt", ".md", ".xml", ".log", ".yaml", ".yml"}
|
|
115
117
|
|
|
116
118
|
|
|
119
|
+
_ARROW_EXTENSIONS = {".arrow", ".arrows", ".feather", ".ipc"}
|
|
120
|
+
|
|
121
|
+
|
|
117
122
|
def _is_parquet(mime: str, extension: str) -> bool:
|
|
118
123
|
return "parquet" in mime or extension == ".parquet"
|
|
119
124
|
|
|
120
125
|
|
|
126
|
+
def _is_arrow(mime: str, extension: str) -> bool:
|
|
127
|
+
return "arrow" in mime or "feather" in mime or extension in _ARROW_EXTENSIONS
|
|
128
|
+
|
|
129
|
+
|
|
121
130
|
def _is_xlsx(mime: str, extension: str) -> bool:
|
|
122
131
|
return (
|
|
123
132
|
mime == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
@@ -189,6 +198,22 @@ def _parquet_metadata(file_bytes: bytes) -> dict[str, Any]:
|
|
|
189
198
|
}
|
|
190
199
|
|
|
191
200
|
|
|
201
|
+
def _arrow_metadata(file_bytes: bytes) -> dict[str, Any]:
|
|
202
|
+
# Arrow's footer indexes record batches by offset and does not total their
|
|
203
|
+
# rows, so unlike Parquet the count has to be walked for. One batch is held at
|
|
204
|
+
# a time and the payload is already resident here, so the walk costs decoding
|
|
205
|
+
# rather than transfer.
|
|
206
|
+
from .file_parser import open_arrow_batches
|
|
207
|
+
|
|
208
|
+
schema, batches = open_arrow_batches(io.BytesIO(file_bytes))
|
|
209
|
+
row_count = sum(batch.num_rows for batch in batches)
|
|
210
|
+
column_types = {field.name: str(field.type) for field in schema}
|
|
211
|
+
return {
|
|
212
|
+
"row_count": row_count,
|
|
213
|
+
"columns": build_columns(list(schema.names), column_types),
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
|
|
192
217
|
def _xlsx_metadata(file_bytes: bytes) -> dict[str, Any]:
|
|
193
218
|
import openpyxl # type: ignore[import-untyped]
|
|
194
219
|
|