classifyre-cli 0.4.82__tar.gz → 0.4.84__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.84/.turbo/turbo-build.log +3 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/PKG-INFO +1 -1
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/package.json +1 -1
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/pyproject.toml +1 -1
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/models/generated_input.py +140 -10
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/pipeline/payload_window.py +58 -12
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/azure_blob_storage/source.py +32 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/base.py +6 -1
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/dropbox/source.py +29 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/google_cloud_storage/source.py +30 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/hugging_face/source.py +64 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/local_folder/source.py +22 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/object_storage/base.py +360 -93
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/s3_compatible_storage/source.py +47 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/utils/embedded_files.py +56 -27
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/utils/file_parser.py +240 -101
- classifyre_cli-0.4.84/src/utils/payload.py +165 -0
- classifyre_cli-0.4.84/src/utils/range_reader.py +227 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_object_storage_download_efficiency.py +81 -1
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_payload_window.py +120 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_payload_window_parquet.py +71 -4
- classifyre_cli-0.4.84/tests/test_range_reading.py +390 -0
- classifyre_cli-0.4.84/tests/utils/test_payload.py +123 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/uv.lock +2 -2
- classifyre_cli-0.4.82/.turbo/turbo-build.log +0 -3
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/.gitignore +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/.python-version +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/README.md +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/main.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/scripts/generate_models.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/config.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/base.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/broken_links/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/broken_links/detector.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/config.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/content/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/custom/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/custom/detector.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/custom/extractor.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/custom/runners/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/custom/runners/_base.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/custom/runners/_factory.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/custom/runners/_gliner2.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/custom/runners/_image_classification.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/custom/runners/_llm.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/custom/runners/_object_detection.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/custom/runners/_regex.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/custom/runners/_text_classification.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/custom/trainer.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/dependencies.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/engine_version.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/pii/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/pii/detector.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/secrets/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/secrets/detector.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/threat/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/threat/code_security_detector.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/detectors/threat/yara_detector.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/file_evaluation/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/file_evaluation/runner.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/main.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/models/generated_detectors.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/models/generated_single_asset_scan_results.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/outputs/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/outputs/base.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/outputs/console.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/outputs/factory.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/outputs/file.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/outputs/rest.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/pipeline/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/pipeline/content_provider.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/pipeline/detector_pipeline.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/pipeline/parsed_content_provider.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/pipeline/scan_cache.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/pipeline/text_artifact.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/pipeline/worker_pool.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/asset_metadata.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/atlassian_common.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/azure_blob_storage/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/confluence/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/confluence/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/databricks/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/databricks/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/delta_lake/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/delta_lake/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/dependencies.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/dropbox/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/dropbox/auth.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/elasticsearch/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/elasticsearch/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/email/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/email/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/google_cloud_storage/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/google_workspace/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/google_workspace/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/hive/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/hive/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/hugging_face/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/iceberg/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/iceberg/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/jira/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/jira/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/kafka/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/kafka/rest.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/kafka/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/lakehouse_base.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/local_folder/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/meilisearch/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/meilisearch/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/microsoft_365/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/microsoft_365/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/mongodb/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/mongodb/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/mssql/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/mssql/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/mysql/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/mysql/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/neo4j/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/neo4j/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/notion/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/notion/client.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/notion/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/opensearch/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/opensearch/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/oracle/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/oracle/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/postgresql/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/postgresql/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/powerbi/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/powerbi/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/recipe_normalizer.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/reddit/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/reddit/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/s3_client.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/s3_compatible_storage/README.md +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/s3_compatible_storage/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/sandbox/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/sandbox/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/search_engine_base.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/servicedesk/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/servicedesk/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/slack/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/slack/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/snowflake/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/snowflake/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/sqlite/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/sqlite/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/tableau/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/tableau/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/tabular_base.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/tabular_utils.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/wordpress/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/wordpress/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/youtube/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/sources/youtube/source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/telemetry.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/utils/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/utils/archive_extraction.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/utils/content_extraction.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/utils/dependency_groups.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/utils/external_video.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/utils/file_metadata.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/utils/file_to_images.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/utils/hashing.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/utils/legacy_office.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/utils/resources.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/utils/transcription.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/utils/uv_sync.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/utils/validation.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/src/utils/video_processing.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/_lakehouse_fakes.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/conftest.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/broken_links/test_broken_links_detector.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/conftest.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/content/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/custom/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/custom/conftest.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/custom/test_gliner2_classification.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/custom/test_invoice_extraction.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/custom/test_llm_runner.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/custom/test_pipeline_integration.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/custom/test_regex_runner.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/custom/test_runner_hardening.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/custom/test_transformer_runners.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/pii/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/pii/conftest.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/pii/sample_invoice.pdf +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/pii/test_pii_chunking.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/pii/test_pii_detector.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/pii/test_pii_detector_extended.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/pii/test_pii_regional_entities.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/pii/test_pii_severity.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/secrets/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/secrets/test_secrets_detector.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/secrets/test_secrets_detector_extended.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/test_base_detector.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/test_custom_detector_examples_runtime.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/test_detector_catalog_commercial.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/test_detector_pipeline_types.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/test_detector_schema_examples.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/test_detector_types.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/test_phase2_detectors.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/test_registry.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/threat/__init__.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/threat/test_code_security_detector.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/detectors/threat/test_yara_detector.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/integration/test_wordpress_broken_links_detector.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/integration/test_wordpress_links_assets.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/pipeline/test_detector_outcomes.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/pipeline/test_detector_pipeline.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/pipeline/test_worker_pool.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_assets_metadata_catalog.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_azure_blob_storage_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_base_source_attachment.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_base_source_sampling.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_byte_valued_fields.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_config.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_confluence_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_custom_extractor.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_databricks_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_delta_lake_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_dependency_groups.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_dropbox_auth_command.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_dropbox_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_elasticsearch_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_email_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_file_evaluation_runner.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_google_cloud_storage_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_google_workspace_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_hashing.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_hive_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_hugging_face_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_iceberg_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_jira_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_kafka_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_local_folder_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_main.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_meilisearch_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_microsoft_365_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_mongodb_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_mssql_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_mysql_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_neo4j_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_notion_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_opensearch_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_oracle_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_outputs.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_postgresql_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_powerbi_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_recipe_normalizer.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_reddit_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_s3_compatible_storage_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_sampling_automatic.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_sandbox_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_scan_cache.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_servicedesk_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_slack_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_snowflake_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_source_dependency_groups.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_source_strategy_conformance.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_sqlite_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_tableau_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_tabular_automatic_sampling.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_tabular_utils.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_uv_sync.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_wordpress_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_youtube_source.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/test_youtube_source_integration.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/utils/test_archive_extraction.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/utils/test_content_extraction.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/utils/test_embedded_files.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/utils/test_file_metadata.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/utils/test_file_parser.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/utils/test_file_to_images.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/utils/test_legacy_office.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/utils/test_transcription.py +0 -0
- {classifyre_cli-0.4.82 → classifyre_cli-0.4.84}/tests/utils/test_video_processing.py +0 -0
|
@@ -977,13 +977,39 @@ class S3CompatibleStorageOptionalConnection(BaseModel):
|
|
|
977
977
|
)
|
|
978
978
|
max_object_bytes: int | None = Field(
|
|
979
979
|
5242880,
|
|
980
|
-
description='Maximum bytes
|
|
980
|
+
description='Maximum bytes of one object held in memory. Larger objects are streamed to a temporary file (or read by byte range where the provider supports it), so this bounds memory rather than the size of file that can be scanned. See max_file_bytes to refuse large objects outright.',
|
|
981
981
|
ge=1024,
|
|
982
|
-
le=52428800,
|
|
983
982
|
)
|
|
984
983
|
verify_ssl: bool | None = Field(
|
|
985
984
|
True, description='TLS certificate verification toggle'
|
|
986
985
|
)
|
|
986
|
+
max_file_bytes: int | None = Field(
|
|
987
|
+
None,
|
|
988
|
+
description='Refuse any object larger than this many bytes. 0 or unset means no limit: an object above max_object_bytes is spooled to disk rather than held in memory, so file size is bounded by free disk, not by RAM.',
|
|
989
|
+
ge=0,
|
|
990
|
+
)
|
|
991
|
+
max_archive_members: int | None = Field(
|
|
992
|
+
200,
|
|
993
|
+
description='Maximum member files expanded from one archive into child assets. Bounds fan-out, and is the guard against a zip bomb.',
|
|
994
|
+
ge=1,
|
|
995
|
+
le=10000,
|
|
996
|
+
)
|
|
997
|
+
max_archive_member_bytes: int | None = Field(
|
|
998
|
+
10485760,
|
|
999
|
+
description='Maximum uncompressed bytes read from a single archive member',
|
|
1000
|
+
ge=1024,
|
|
1001
|
+
)
|
|
1002
|
+
max_archive_total_bytes: int | None = Field(
|
|
1003
|
+
104857600,
|
|
1004
|
+
description='Maximum uncompressed bytes read across all members of one archive. The decompression-ratio ceiling: a zip bomb hits this before it hits memory.',
|
|
1005
|
+
ge=1024,
|
|
1006
|
+
)
|
|
1007
|
+
max_embedded_files: int | None = Field(
|
|
1008
|
+
200,
|
|
1009
|
+
description='Maximum embedded files (parquet image/audio columns, office media) expanded from one container into child assets per run',
|
|
1010
|
+
ge=1,
|
|
1011
|
+
le=10000,
|
|
1012
|
+
)
|
|
987
1013
|
|
|
988
1014
|
|
|
989
1015
|
class S3CompatibleStorageOptional(BaseModel):
|
|
@@ -1129,9 +1155,35 @@ class AzureBlobStorageOptionalConnection(BaseModel):
|
|
|
1129
1155
|
)
|
|
1130
1156
|
max_object_bytes: int | None = Field(
|
|
1131
1157
|
5242880,
|
|
1132
|
-
description='Maximum bytes
|
|
1158
|
+
description='Maximum bytes of one object held in memory. Larger objects are streamed to a temporary file (or read by byte range where the provider supports it), so this bounds memory rather than the size of file that can be scanned. See max_file_bytes to refuse large objects outright.',
|
|
1159
|
+
ge=1024,
|
|
1160
|
+
)
|
|
1161
|
+
max_file_bytes: int | None = Field(
|
|
1162
|
+
None,
|
|
1163
|
+
description='Refuse any object larger than this many bytes. 0 or unset means no limit: an object above max_object_bytes is spooled to disk rather than held in memory, so file size is bounded by free disk, not by RAM.',
|
|
1164
|
+
ge=0,
|
|
1165
|
+
)
|
|
1166
|
+
max_archive_members: int | None = Field(
|
|
1167
|
+
200,
|
|
1168
|
+
description='Maximum member files expanded from one archive into child assets. Bounds fan-out, and is the guard against a zip bomb.',
|
|
1169
|
+
ge=1,
|
|
1170
|
+
le=10000,
|
|
1171
|
+
)
|
|
1172
|
+
max_archive_member_bytes: int | None = Field(
|
|
1173
|
+
10485760,
|
|
1174
|
+
description='Maximum uncompressed bytes read from a single archive member',
|
|
1133
1175
|
ge=1024,
|
|
1134
|
-
|
|
1176
|
+
)
|
|
1177
|
+
max_archive_total_bytes: int | None = Field(
|
|
1178
|
+
104857600,
|
|
1179
|
+
description='Maximum uncompressed bytes read across all members of one archive. The decompression-ratio ceiling: a zip bomb hits this before it hits memory.',
|
|
1180
|
+
ge=1024,
|
|
1181
|
+
)
|
|
1182
|
+
max_embedded_files: int | None = Field(
|
|
1183
|
+
200,
|
|
1184
|
+
description='Maximum embedded files (parquet image/audio columns, office media) expanded from one container into child assets per run',
|
|
1185
|
+
ge=1,
|
|
1186
|
+
le=10000,
|
|
1135
1187
|
)
|
|
1136
1188
|
|
|
1137
1189
|
|
|
@@ -1185,9 +1237,35 @@ class GoogleCloudStorageOptionalConnection(BaseModel):
|
|
|
1185
1237
|
)
|
|
1186
1238
|
max_object_bytes: int | None = Field(
|
|
1187
1239
|
5242880,
|
|
1188
|
-
description='Maximum bytes
|
|
1240
|
+
description='Maximum bytes of one object held in memory. Larger objects are streamed to a temporary file (or read by byte range where the provider supports it), so this bounds memory rather than the size of file that can be scanned. See max_file_bytes to refuse large objects outright.',
|
|
1189
1241
|
ge=1024,
|
|
1190
|
-
|
|
1242
|
+
)
|
|
1243
|
+
max_file_bytes: int | None = Field(
|
|
1244
|
+
None,
|
|
1245
|
+
description='Refuse any object larger than this many bytes. 0 or unset means no limit: an object above max_object_bytes is spooled to disk rather than held in memory, so file size is bounded by free disk, not by RAM.',
|
|
1246
|
+
ge=0,
|
|
1247
|
+
)
|
|
1248
|
+
max_archive_members: int | None = Field(
|
|
1249
|
+
200,
|
|
1250
|
+
description='Maximum member files expanded from one archive into child assets. Bounds fan-out, and is the guard against a zip bomb.',
|
|
1251
|
+
ge=1,
|
|
1252
|
+
le=10000,
|
|
1253
|
+
)
|
|
1254
|
+
max_archive_member_bytes: int | None = Field(
|
|
1255
|
+
10485760,
|
|
1256
|
+
description='Maximum uncompressed bytes read from a single archive member',
|
|
1257
|
+
ge=1024,
|
|
1258
|
+
)
|
|
1259
|
+
max_archive_total_bytes: int | None = Field(
|
|
1260
|
+
104857600,
|
|
1261
|
+
description='Maximum uncompressed bytes read across all members of one archive. The decompression-ratio ceiling: a zip bomb hits this before it hits memory.',
|
|
1262
|
+
ge=1024,
|
|
1263
|
+
)
|
|
1264
|
+
max_embedded_files: int | None = Field(
|
|
1265
|
+
200,
|
|
1266
|
+
description='Maximum embedded files (parquet image/audio columns, office media) expanded from one container into child assets per run',
|
|
1267
|
+
ge=1,
|
|
1268
|
+
le=10000,
|
|
1191
1269
|
)
|
|
1192
1270
|
|
|
1193
1271
|
|
|
@@ -4712,9 +4790,8 @@ class DropboxOptionalConnection(BaseModel):
|
|
|
4712
4790
|
)
|
|
4713
4791
|
max_object_bytes: int | None = Field(
|
|
4714
4792
|
5242880,
|
|
4715
|
-
description='Maximum bytes
|
|
4793
|
+
description='Maximum bytes of one object held in memory. Larger objects are streamed to a temporary file (or read by byte range where the provider supports it), so this bounds memory rather than the size of file that can be scanned. See max_file_bytes to refuse large objects outright.',
|
|
4716
4794
|
ge=1024,
|
|
4717
|
-
le=52428800,
|
|
4718
4795
|
)
|
|
4719
4796
|
request_timeout_seconds: float | None = Field(
|
|
4720
4797
|
60,
|
|
@@ -4728,6 +4805,33 @@ class DropboxOptionalConnection(BaseModel):
|
|
|
4728
4805
|
ge=0,
|
|
4729
4806
|
le=10,
|
|
4730
4807
|
)
|
|
4808
|
+
max_file_bytes: int | None = Field(
|
|
4809
|
+
None,
|
|
4810
|
+
description='Refuse any object larger than this many bytes. 0 or unset means no limit: an object above max_object_bytes is spooled to disk rather than held in memory, so file size is bounded by free disk, not by RAM.',
|
|
4811
|
+
ge=0,
|
|
4812
|
+
)
|
|
4813
|
+
max_archive_members: int | None = Field(
|
|
4814
|
+
200,
|
|
4815
|
+
description='Maximum member files expanded from one archive into child assets. Bounds fan-out, and is the guard against a zip bomb.',
|
|
4816
|
+
ge=1,
|
|
4817
|
+
le=10000,
|
|
4818
|
+
)
|
|
4819
|
+
max_archive_member_bytes: int | None = Field(
|
|
4820
|
+
10485760,
|
|
4821
|
+
description='Maximum uncompressed bytes read from a single archive member',
|
|
4822
|
+
ge=1024,
|
|
4823
|
+
)
|
|
4824
|
+
max_archive_total_bytes: int | None = Field(
|
|
4825
|
+
104857600,
|
|
4826
|
+
description='Maximum uncompressed bytes read across all members of one archive. The decompression-ratio ceiling: a zip bomb hits this before it hits memory.',
|
|
4827
|
+
ge=1024,
|
|
4828
|
+
)
|
|
4829
|
+
max_embedded_files: int | None = Field(
|
|
4830
|
+
200,
|
|
4831
|
+
description='Maximum embedded files (parquet image/audio columns, office media) expanded from one container into child assets per run',
|
|
4832
|
+
ge=1,
|
|
4833
|
+
le=10000,
|
|
4834
|
+
)
|
|
4731
4835
|
|
|
4732
4836
|
|
|
4733
4837
|
class DropboxOptionalTeam(BaseModel):
|
|
@@ -4892,9 +4996,8 @@ class HuggingFaceOptionalConnection(BaseModel):
|
|
|
4892
4996
|
)
|
|
4893
4997
|
max_object_bytes: int | None = Field(
|
|
4894
4998
|
26214400,
|
|
4895
|
-
description='Maximum bytes
|
|
4999
|
+
description='Maximum bytes of one object held in memory. Larger objects are streamed to a temporary file (or read by byte range where the provider supports it), so this bounds memory rather than the size of file that can be scanned. See max_file_bytes to refuse large objects outright.',
|
|
4896
5000
|
ge=1024,
|
|
4897
|
-
le=104857600,
|
|
4898
5001
|
)
|
|
4899
5002
|
request_timeout_seconds: float | None = Field(
|
|
4900
5003
|
60,
|
|
@@ -4908,6 +5011,33 @@ class HuggingFaceOptionalConnection(BaseModel):
|
|
|
4908
5011
|
ge=0,
|
|
4909
5012
|
le=10,
|
|
4910
5013
|
)
|
|
5014
|
+
max_file_bytes: int | None = Field(
|
|
5015
|
+
None,
|
|
5016
|
+
description='Refuse any object larger than this many bytes. 0 or unset means no limit: an object above max_object_bytes is spooled to disk rather than held in memory, so file size is bounded by free disk, not by RAM.',
|
|
5017
|
+
ge=0,
|
|
5018
|
+
)
|
|
5019
|
+
max_archive_members: int | None = Field(
|
|
5020
|
+
200,
|
|
5021
|
+
description='Maximum member files expanded from one archive into child assets. Bounds fan-out, and is the guard against a zip bomb.',
|
|
5022
|
+
ge=1,
|
|
5023
|
+
le=10000,
|
|
5024
|
+
)
|
|
5025
|
+
max_archive_member_bytes: int | None = Field(
|
|
5026
|
+
10485760,
|
|
5027
|
+
description='Maximum uncompressed bytes read from a single archive member',
|
|
5028
|
+
ge=1024,
|
|
5029
|
+
)
|
|
5030
|
+
max_archive_total_bytes: int | None = Field(
|
|
5031
|
+
104857600,
|
|
5032
|
+
description='Maximum uncompressed bytes read across all members of one archive. The decompression-ratio ceiling: a zip bomb hits this before it hits memory.',
|
|
5033
|
+
ge=1024,
|
|
5034
|
+
)
|
|
5035
|
+
max_embedded_files: int | None = Field(
|
|
5036
|
+
200,
|
|
5037
|
+
description='Maximum embedded files (parquet image/audio columns, office media) expanded from one container into child assets per run',
|
|
5038
|
+
ge=1,
|
|
5039
|
+
le=10000,
|
|
5040
|
+
)
|
|
4911
5041
|
|
|
4912
5042
|
|
|
4913
5043
|
class HuggingFaceOptional(BaseModel):
|
|
@@ -114,8 +114,16 @@ class PayloadCursor:
|
|
|
114
114
|
|
|
115
115
|
Returning None is the restart signal. It fires on an unreadable cursor,
|
|
116
116
|
an unknown format version, a checksum that no longer matches the file,
|
|
117
|
-
|
|
118
|
-
can no longer vouch for.
|
|
117
|
+
a strategy switch, and a completed sweep that covered no rows — in each
|
|
118
|
+
case the stored offset points at rows we can no longer vouch for.
|
|
119
|
+
|
|
120
|
+
That last one is a repair. A cursor reading ``exhausted`` with an explicit
|
|
121
|
+
``rows_seen: 0`` was written by a run that read nothing and concluded the
|
|
122
|
+
file was finished — the signature of a payload that failed to open, not
|
|
123
|
+
one that was scanned. Such a cursor retires the asset from every future
|
|
124
|
+
scan (the scan cache skips on ``exhausted``), so it is discarded on sight
|
|
125
|
+
and the sweep starts over. A genuinely empty payload is re-read instead
|
|
126
|
+
of skipped, which costs nothing.
|
|
119
127
|
"""
|
|
120
128
|
if not isinstance(raw, dict):
|
|
121
129
|
return None
|
|
@@ -137,6 +145,15 @@ class PayloadCursor:
|
|
|
137
145
|
|
|
138
146
|
rows_seen = raw.get("rows_seen")
|
|
139
147
|
offset = _coerce_int(raw.get("offset"), 0)
|
|
148
|
+
swept_nothing = (
|
|
149
|
+
isinstance(rows_seen, int) and not isinstance(rows_seen, bool) and rows_seen <= 0
|
|
150
|
+
)
|
|
151
|
+
if bool(raw.get("exhausted", False)) and swept_nothing:
|
|
152
|
+
logger.info(
|
|
153
|
+
"Discarding a payload cursor that claims a completed sweep of 0 rows; "
|
|
154
|
+
"the payload was never actually read, so this asset restarts its sweep"
|
|
155
|
+
)
|
|
156
|
+
return None
|
|
140
157
|
return cls(
|
|
141
158
|
offset=max(0, offset),
|
|
142
159
|
rows_seen=(max(0, int(rows_seen)) if isinstance(rows_seen, int) else None),
|
|
@@ -227,6 +244,8 @@ class PayloadWindow:
|
|
|
227
244
|
it for free (Parquet keeps it in the footer). RANDOM uses it to seek
|
|
228
245
|
straight to a random window; without it, RANDOM falls back to reservoir
|
|
229
246
|
sampling, which still visits every row but holds only one window.
|
|
247
|
+
AUTOMATIC uses it to decide when a sweep is genuinely complete instead of
|
|
248
|
+
inferring it from the size of the last window (see ``_advanced``).
|
|
230
249
|
"""
|
|
231
250
|
take = max(1, self.rows_per_page)
|
|
232
251
|
unit = max(1, rows_per_unit)
|
|
@@ -244,7 +263,7 @@ class PayloadWindow:
|
|
|
244
263
|
yield from pages(0, None)
|
|
245
264
|
return
|
|
246
265
|
|
|
247
|
-
yield from self._iterate_automatic(pages, take, unit, on_cursor)
|
|
266
|
+
yield from self._iterate_automatic(pages, take, unit, on_cursor, row_count)
|
|
248
267
|
|
|
249
268
|
# ── AUTOMATIC ────────────────────────────────────────────────────────
|
|
250
269
|
|
|
@@ -254,6 +273,7 @@ class PayloadWindow:
|
|
|
254
273
|
take: int,
|
|
255
274
|
unit: int,
|
|
256
275
|
on_cursor: Callable[[PayloadCursor], None] | None,
|
|
276
|
+
row_count: int | None = None,
|
|
257
277
|
) -> Iterator[str]:
|
|
258
278
|
start = self.start_row
|
|
259
279
|
emitted = 0
|
|
@@ -276,15 +296,33 @@ class PayloadWindow:
|
|
|
276
296
|
yield page
|
|
277
297
|
|
|
278
298
|
if on_cursor is not None:
|
|
279
|
-
on_cursor(self._advanced(start, emitted, take, unit))
|
|
299
|
+
on_cursor(self._advanced(start, emitted, take, unit, row_count))
|
|
280
300
|
|
|
281
|
-
def _advanced(
|
|
301
|
+
def _advanced(
|
|
302
|
+
self,
|
|
303
|
+
start: int,
|
|
304
|
+
emitted: int,
|
|
305
|
+
take: int,
|
|
306
|
+
unit: int,
|
|
307
|
+
row_count: int | None = None,
|
|
308
|
+
) -> PayloadCursor:
|
|
282
309
|
"""The cursor to persist after emitting ``emitted`` pages from ``start``.
|
|
283
310
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
311
|
+
``exhausted`` is the flag that lets the scan cache skip this asset for
|
|
312
|
+
good, so it is only ever set on positive proof that every row has been
|
|
313
|
+
read:
|
|
314
|
+
|
|
315
|
+
* when the reader knows the payload's row count (Parquet keeps it in the
|
|
316
|
+
footer), the sweep is complete exactly when it has reached that count.
|
|
317
|
+
Nothing is inferred, which is what stops a file whose length is a
|
|
318
|
+
multiple of ``rows_per_page`` from wrapping forever;
|
|
319
|
+
* otherwise the only available signal is an underfilled window, and it is
|
|
320
|
+
trusted *except* when the window came back completely empty from row 0.
|
|
321
|
+
A reader that produced nothing at all has not proved the payload is
|
|
322
|
+
empty — it may have failed — and treating that as a finished sweep is
|
|
323
|
+
how an unreadable file gets banked as fully scanned and skipped for
|
|
324
|
+
ever after. Re-reading a genuinely empty payload costs one cheap pass;
|
|
325
|
+
the other mistake costs the whole file.
|
|
288
326
|
"""
|
|
289
327
|
prior = self.prior
|
|
290
328
|
passes = prior.passes if prior is not None else 0
|
|
@@ -292,10 +330,18 @@ class PayloadWindow:
|
|
|
292
330
|
# rows each, so a full window is that many pages.
|
|
293
331
|
expected_units = -(-take // unit)
|
|
294
332
|
reached = start + emitted * unit
|
|
295
|
-
|
|
333
|
+
|
|
334
|
+
if row_count is not None and row_count >= 0:
|
|
335
|
+
complete = reached >= row_count
|
|
336
|
+
rows_seen = min(reached, row_count)
|
|
337
|
+
else:
|
|
338
|
+
complete = emitted < expected_units and not (emitted == 0 and start == 0)
|
|
339
|
+
rows_seen = reached
|
|
340
|
+
|
|
341
|
+
if complete:
|
|
296
342
|
return PayloadCursor(
|
|
297
343
|
offset=0,
|
|
298
|
-
rows_seen=
|
|
344
|
+
rows_seen=rows_seen,
|
|
299
345
|
passes=passes + 1,
|
|
300
346
|
exhausted=True,
|
|
301
347
|
checksum=self.checksum,
|
|
@@ -303,7 +349,7 @@ class PayloadWindow:
|
|
|
303
349
|
)
|
|
304
350
|
return PayloadCursor(
|
|
305
351
|
offset=reached,
|
|
306
|
-
rows_seen=
|
|
352
|
+
rows_seen=(rows_seen if row_count is not None else prior.rows_seen if prior else None),
|
|
307
353
|
passes=passes,
|
|
308
354
|
exhausted=False,
|
|
309
355
|
checksum=self.checksum,
|
|
@@ -6,17 +6,22 @@ from typing import Any
|
|
|
6
6
|
from urllib.parse import quote
|
|
7
7
|
|
|
8
8
|
from ...models.generated_input import AzureBlobStorageInput
|
|
9
|
+
from ...utils.range_reader import CallableRangeReader, open_buffered
|
|
9
10
|
from ..dependencies import require_module
|
|
10
11
|
from ..object_storage.base import ObjectRef, ObjectStorageSourceBase
|
|
11
12
|
|
|
12
13
|
logger = logging.getLogger(__name__)
|
|
13
14
|
|
|
15
|
+
_STREAM_CHUNK_BYTES = 1024 * 1024
|
|
16
|
+
|
|
14
17
|
|
|
15
18
|
class AzureBlobStorageSource(ObjectStorageSourceBase):
|
|
16
19
|
source_type = "azure_blob_storage"
|
|
17
20
|
provider_label = "AZURE_BLOB_STORAGE"
|
|
18
21
|
input_model = AzureBlobStorageInput
|
|
19
22
|
|
|
23
|
+
SUPPORTS_RANGE_READS = True
|
|
24
|
+
|
|
20
25
|
def _required_container(self) -> str:
|
|
21
26
|
container = str(self.config.required.container).strip()
|
|
22
27
|
if not container:
|
|
@@ -109,6 +114,33 @@ class AzureBlobStorageSource(ObjectStorageSourceBase):
|
|
|
109
114
|
content_type_hint=str(content_type_hint) if content_type_hint else None,
|
|
110
115
|
)
|
|
111
116
|
|
|
117
|
+
def _blob_client(self, key: str) -> Any:
|
|
118
|
+
container_client = self._client().get_container_client(self._required_container())
|
|
119
|
+
return container_client.get_blob_client(key)
|
|
120
|
+
|
|
121
|
+
def _stream_object(self, ref: ObjectRef) -> Iterator[bytes]:
|
|
122
|
+
"""Stream the whole blob in chunks — no cap, nothing held whole."""
|
|
123
|
+
downloader = self._blob_client(ref.key).download_blob(
|
|
124
|
+
timeout=self._request_timeout_seconds()
|
|
125
|
+
)
|
|
126
|
+
yield from downloader.chunks()
|
|
127
|
+
|
|
128
|
+
def _open_object_range_reader(self, ref: ObjectRef) -> Any | None:
|
|
129
|
+
"""Azure takes offset/length, so a columnar blob needs no download."""
|
|
130
|
+
if not ref.size:
|
|
131
|
+
return None
|
|
132
|
+
blob_client = self._blob_client(ref.key)
|
|
133
|
+
timeout = self._request_timeout_seconds()
|
|
134
|
+
label = f"{self._required_container()}/{ref.key}"
|
|
135
|
+
|
|
136
|
+
def _fetch(start: int, end_inclusive: int) -> bytes:
|
|
137
|
+
downloader = blob_client.download_blob(
|
|
138
|
+
offset=start, length=end_inclusive - start + 1, timeout=timeout
|
|
139
|
+
)
|
|
140
|
+
return bytes(downloader.readall())
|
|
141
|
+
|
|
142
|
+
return open_buffered(CallableRangeReader(_fetch, size=int(ref.size), label=label))
|
|
143
|
+
|
|
112
144
|
def _download_object(self, ref: ObjectRef) -> tuple[bytes, str | None]:
|
|
113
145
|
blob_service_client = self._client()
|
|
114
146
|
container = self._required_container()
|
|
@@ -378,7 +378,7 @@ class BaseSource(ABC):
|
|
|
378
378
|
|
|
379
379
|
def iter_asset_pages(
|
|
380
380
|
self,
|
|
381
|
-
file_bytes: bytes,
|
|
381
|
+
file_bytes: bytes | Any,
|
|
382
382
|
mime_type: str,
|
|
383
383
|
batch_size: int = 100,
|
|
384
384
|
include_column_names: bool = True,
|
|
@@ -397,6 +397,11 @@ class BaseSource(ABC):
|
|
|
397
397
|
|
|
398
398
|
Everything else — PDFs, images, audio — has no row axis and streams
|
|
399
399
|
unchanged.
|
|
400
|
+
|
|
401
|
+
``file_bytes`` may be a seekable binary handle instead of bytes (Parquet
|
|
402
|
+
only). The window then bounds what is *transferred*, not just what is
|
|
403
|
+
decoded: a source that can serve byte ranges reads the footer and the one
|
|
404
|
+
row group this run needs out of an object it never downloads whole.
|
|
400
405
|
"""
|
|
401
406
|
from ..utils.file_parser import count_tabular_rows, iter_file_pages
|
|
402
407
|
|
|
@@ -28,6 +28,8 @@ from ..object_storage.base import ObjectRef, ObjectStorageSourceBase
|
|
|
28
28
|
|
|
29
29
|
logger = logging.getLogger(__name__)
|
|
30
30
|
|
|
31
|
+
_DOWNLOAD_CHUNK_BYTES = 1024 * 1024
|
|
32
|
+
|
|
31
33
|
# Dropbox Paper docs (and a few other cloud-native items) carry no downloadable
|
|
32
34
|
# bytes. They are exported instead; markdown keeps the text intact for detectors.
|
|
33
35
|
_EXPORT_MIME_TYPES = {
|
|
@@ -587,6 +589,33 @@ class DropboxSource(ObjectStorageSourceBase):
|
|
|
587
589
|
|
|
588
590
|
# ── download ─────────────────────────────────────────────────────────
|
|
589
591
|
|
|
592
|
+
def _stream_object(self, ref: ObjectRef) -> Iterator[bytes]:
|
|
593
|
+
"""Stream a Dropbox file in chunks — no cap, nothing held whole.
|
|
594
|
+
|
|
595
|
+
Exported formats (Paper docs and friends) go through the same path: the
|
|
596
|
+
export is generated server-side and streamed back like any other file.
|
|
597
|
+
"""
|
|
598
|
+
dropbox_ref = ref if isinstance(ref, DropboxObjectRef) else None
|
|
599
|
+
client = self._client_for(dropbox_ref.target if dropbox_ref else _ACCOUNT_TARGET)
|
|
600
|
+
path = dropbox_ref.file_id if dropbox_ref and dropbox_ref.file_id else ref.key
|
|
601
|
+
|
|
602
|
+
if dropbox_ref is not None and not dropbox_ref.is_downloadable:
|
|
603
|
+
export_format = dropbox_ref.export_format or "markdown"
|
|
604
|
+
_export_result, response = client.files_export(path, export_format)
|
|
605
|
+
self._exported_as_by_key[ref.key] = _EXPORT_MIME_TYPES.get(export_format, "text/plain")
|
|
606
|
+
else:
|
|
607
|
+
_metadata, response = client.files_download(path)
|
|
608
|
+
|
|
609
|
+
try:
|
|
610
|
+
yield from response.iter_content(chunk_size=_DOWNLOAD_CHUNK_BYTES)
|
|
611
|
+
finally:
|
|
612
|
+
close = getattr(response, "close", None)
|
|
613
|
+
if callable(close):
|
|
614
|
+
try:
|
|
615
|
+
close()
|
|
616
|
+
except Exception:
|
|
617
|
+
logger.debug("Failed to close Dropbox response body")
|
|
618
|
+
|
|
590
619
|
def _download_object(self, ref: ObjectRef) -> tuple[bytes, str | None]:
|
|
591
620
|
max_bytes = self._max_object_bytes()
|
|
592
621
|
dropbox_ref = ref if isinstance(ref, DropboxObjectRef) else None
|
|
@@ -6,17 +6,22 @@ from collections.abc import Iterator
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
8
|
from ...models.generated_input import GoogleCloudStorageInput
|
|
9
|
+
from ...utils.range_reader import CallableRangeReader, open_buffered
|
|
9
10
|
from ..dependencies import require_module
|
|
10
11
|
from ..object_storage.base import ObjectRef, ObjectStorageSourceBase
|
|
11
12
|
|
|
12
13
|
logger = logging.getLogger(__name__)
|
|
13
14
|
|
|
15
|
+
_STREAM_CHUNK_BYTES = 1024 * 1024
|
|
16
|
+
|
|
14
17
|
|
|
15
18
|
class GoogleCloudStorageSource(ObjectStorageSourceBase):
|
|
16
19
|
source_type = "google_cloud_storage"
|
|
17
20
|
provider_label = "GOOGLE_CLOUD_STORAGE"
|
|
18
21
|
input_model = GoogleCloudStorageInput
|
|
19
22
|
|
|
23
|
+
SUPPORTS_RANGE_READS = True
|
|
24
|
+
|
|
20
25
|
def _required_bucket(self) -> str:
|
|
21
26
|
bucket = str(self.config.required.bucket).strip()
|
|
22
27
|
if not bucket:
|
|
@@ -98,6 +103,31 @@ class GoogleCloudStorageSource(ObjectStorageSourceBase):
|
|
|
98
103
|
content_type_hint=str(getattr(blob, "content_type", "") or "") or None,
|
|
99
104
|
)
|
|
100
105
|
|
|
106
|
+
def _stream_object(self, ref: ObjectRef) -> Iterator[bytes]:
|
|
107
|
+
"""Stream the whole blob in chunks — no cap, nothing held whole."""
|
|
108
|
+
blob = self._client().bucket(self._required_bucket()).blob(ref.key)
|
|
109
|
+
with blob.open("rb") as handle:
|
|
110
|
+
while True:
|
|
111
|
+
chunk = handle.read(_STREAM_CHUNK_BYTES)
|
|
112
|
+
if not chunk:
|
|
113
|
+
return
|
|
114
|
+
yield chunk
|
|
115
|
+
|
|
116
|
+
def _open_object_range_reader(self, ref: ObjectRef) -> Any | None:
|
|
117
|
+
"""GCS takes an inclusive byte range, so a columnar blob needs no download."""
|
|
118
|
+
if not ref.size:
|
|
119
|
+
return None
|
|
120
|
+
bucket_name = self._required_bucket()
|
|
121
|
+
blob = self._client().bucket(bucket_name).blob(ref.key)
|
|
122
|
+
timeout = self._request_timeout_seconds()
|
|
123
|
+
|
|
124
|
+
def _fetch(start: int, end_inclusive: int) -> bytes:
|
|
125
|
+
return bytes(blob.download_as_bytes(start=start, end=end_inclusive, timeout=timeout))
|
|
126
|
+
|
|
127
|
+
return open_buffered(
|
|
128
|
+
CallableRangeReader(_fetch, size=int(ref.size), label=f"gs://{bucket_name}/{ref.key}")
|
|
129
|
+
)
|
|
130
|
+
|
|
101
131
|
def _download_object(self, ref: ObjectRef) -> tuple[bytes, str | None]:
|
|
102
132
|
client = self._client()
|
|
103
133
|
bucket_name = self._required_bucket()
|
|
@@ -12,6 +12,7 @@ import requests
|
|
|
12
12
|
|
|
13
13
|
from ...models.generated_input import HuggingFaceInput
|
|
14
14
|
from ...models.generated_single_asset_scan_results import SingleAssetScanResults
|
|
15
|
+
from ...utils.range_reader import HttpRangeReader, open_buffered
|
|
15
16
|
from ..dependencies import require_module
|
|
16
17
|
from ..object_storage.base import ObjectRef, ObjectStorageSourceBase
|
|
17
18
|
|
|
@@ -59,6 +60,9 @@ class HuggingFaceSource(ObjectStorageSourceBase):
|
|
|
59
60
|
provider_label = "HUGGING_FACE"
|
|
60
61
|
input_model = HuggingFaceInput
|
|
61
62
|
|
|
63
|
+
# The Hub's resolve endpoint and its CDN both serve byte ranges.
|
|
64
|
+
SUPPORTS_RANGE_READS = True
|
|
65
|
+
|
|
62
66
|
def __init__(
|
|
63
67
|
self,
|
|
64
68
|
recipe: dict[str, Any],
|
|
@@ -409,6 +413,66 @@ class HuggingFaceSource(ObjectStorageSourceBase):
|
|
|
409
413
|
)
|
|
410
414
|
return file_bytes, content_type
|
|
411
415
|
|
|
416
|
+
def _stream_object(self, ref: ObjectRef) -> Iterator[bytes]:
|
|
417
|
+
"""Stream a Hub file in chunks — no cap, nothing held whole.
|
|
418
|
+
|
|
419
|
+
``_open_object`` spools this, so the peak cost of a file the range reader
|
|
420
|
+
cannot help with (a PDF, a zip) is a temp file rather than its full size
|
|
421
|
+
in memory.
|
|
422
|
+
"""
|
|
423
|
+
response = self._session().get(
|
|
424
|
+
self._resolve_url(ref.key),
|
|
425
|
+
headers={
|
|
426
|
+
"Authorization": f"Bearer {self._token()}",
|
|
427
|
+
"User-Agent": "classifyre",
|
|
428
|
+
},
|
|
429
|
+
stream=True,
|
|
430
|
+
timeout=self._request_timeout_seconds(),
|
|
431
|
+
allow_redirects=True,
|
|
432
|
+
)
|
|
433
|
+
response.raise_for_status()
|
|
434
|
+
try:
|
|
435
|
+
yield from response.iter_content(chunk_size=_DOWNLOAD_CHUNK_BYTES)
|
|
436
|
+
finally:
|
|
437
|
+
close = getattr(response, "close", None)
|
|
438
|
+
if callable(close):
|
|
439
|
+
try:
|
|
440
|
+
close()
|
|
441
|
+
except Exception:
|
|
442
|
+
logger.debug("Failed to close Hugging Face response body")
|
|
443
|
+
|
|
444
|
+
def _open_object_range_reader(self, ref: ObjectRef) -> Any | None:
|
|
445
|
+
"""A seekable handle over a Hub file, served by HTTP range requests.
|
|
446
|
+
|
|
447
|
+
This is what makes a repository of multi-hundred-megabyte Parquet shards
|
|
448
|
+
scannable at all. ``max_object_bytes`` bounds a whole-file download, and
|
|
449
|
+
a Parquet file cut off at that bound has lost its footer — the index of
|
|
450
|
+
where the row groups are — so it yields not fewer rows but none. Reading
|
|
451
|
+
by range inverts the cost: the sampling window picks the rows, and only
|
|
452
|
+
the footer plus the row groups holding those rows ever cross the wire.
|
|
453
|
+
|
|
454
|
+
The Hub's ``resolve`` endpoint and its CDN both honour ``Range``. If a
|
|
455
|
+
deployment ever does not, ``HttpRangeReader`` still returns correct bytes
|
|
456
|
+
(it slices a full response) and says so in the log.
|
|
457
|
+
"""
|
|
458
|
+
if not ref.size:
|
|
459
|
+
return None
|
|
460
|
+
|
|
461
|
+
reader = HttpRangeReader(
|
|
462
|
+
self._session(),
|
|
463
|
+
self._resolve_url(ref.key),
|
|
464
|
+
size=int(ref.size),
|
|
465
|
+
# As in _download_object: requests drops this on the redirect to the
|
|
466
|
+
# pre-signed CDN host, which is exactly what the Hub expects.
|
|
467
|
+
headers={
|
|
468
|
+
"Authorization": f"Bearer {self._token()}",
|
|
469
|
+
"User-Agent": "classifyre",
|
|
470
|
+
},
|
|
471
|
+
timeout=self._request_timeout_seconds(),
|
|
472
|
+
label=f"hugging_face:{ref.key}",
|
|
473
|
+
)
|
|
474
|
+
return open_buffered(reader)
|
|
475
|
+
|
|
412
476
|
@staticmethod
|
|
413
477
|
def _read_capped(response: Any, max_bytes: int) -> bytes:
|
|
414
478
|
"""Read at most ``max_bytes`` + 1 bytes, then drop the connection.
|
|
@@ -9,6 +9,7 @@ from typing import Any
|
|
|
9
9
|
from urllib.parse import quote
|
|
10
10
|
|
|
11
11
|
from ...models.generated_input import LocalFolderInput
|
|
12
|
+
from ...utils.file_parser import resolve_mime_type
|
|
12
13
|
from ..object_storage.base import ObjectRef, ObjectStorageSourceBase
|
|
13
14
|
|
|
14
15
|
logger = logging.getLogger(__name__)
|
|
@@ -133,6 +134,27 @@ class LocalFolderSource(ObjectStorageSourceBase):
|
|
|
133
134
|
def _list_objects(self) -> Iterator[ObjectRef]:
|
|
134
135
|
yield from self._walk(self._root(), depth=0)
|
|
135
136
|
|
|
137
|
+
def _open_object(self, ref: ObjectRef) -> tuple[Any, str]:
|
|
138
|
+
"""Open the file where it already is.
|
|
139
|
+
|
|
140
|
+
The one source that needs neither a download nor a spool: the payload is
|
|
141
|
+
a local file, so the parser reads it in place at any size. ``max_file_bytes``
|
|
142
|
+
keeps governing how much is pulled into memory by the *bytes* path
|
|
143
|
+
(binary detectors), not how large a file can be read.
|
|
144
|
+
"""
|
|
145
|
+
file_path = self._root() / ref.key
|
|
146
|
+
handle = open(file_path, "rb")
|
|
147
|
+
try:
|
|
148
|
+
mime_type = resolve_mime_type(
|
|
149
|
+
handle,
|
|
150
|
+
declared_mime_type=ref.content_type_hint or "",
|
|
151
|
+
file_name=self._object_file_name(ref),
|
|
152
|
+
)
|
|
153
|
+
except Exception:
|
|
154
|
+
handle.close()
|
|
155
|
+
raise
|
|
156
|
+
return handle, mime_type
|
|
157
|
+
|
|
136
158
|
def _download_object(self, ref: ObjectRef) -> tuple[bytes, str | None]:
|
|
137
159
|
file_path = self._root() / ref.key
|
|
138
160
|
max_bytes = self._max_file_bytes()
|