classifyre-cli 0.6.5__tar.gz → 0.6.7__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.6.7/.turbo/turbo-build.log +3 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/PKG-INFO +1 -1
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/package.json +1 -1
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/pyproject.toml +1 -1
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/notebook/sdk.py +7 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/base.py +4 -7
- classifyre_cli-0.6.7/src/utils/sampling_cursor.py +45 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_custom_source.py +5 -12
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_dropbox_source.py +2 -4
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_elasticsearch_source.py +3 -4
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_google_workspace_source.py +3 -6
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_hugging_face_source.py +2 -3
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_kafka_source.py +2 -2
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_meilisearch_source.py +2 -3
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_opensearch_source.py +2 -3
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_sampling_automatic.py +2 -3
- classifyre_cli-0.6.7/tests/test_sampling_cursor.py +60 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_sandbox_source.py +2 -3
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_tabular_automatic_sampling.py +2 -3
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/uv.lock +396 -378
- classifyre_cli-0.6.5/.turbo/turbo-build.log +0 -3
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/.gitignore +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/.python-version +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/README.md +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/main.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/scripts/generate_models.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/scripts/generate_runtime_packages.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/scripts/generate_sdk_completions.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/augmentation/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/augmentation/contract.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/augmentation/preview.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/augmentation/runner.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/augmentation/sdk.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/augmentation/session.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/config.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/base.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/broken_links/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/broken_links/detector.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/config.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/content/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/custom/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/custom/detector.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/custom/extractor.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/custom/runners/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/custom/runners/_base.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/custom/runners/_custom_detector.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/custom/runners/_factory.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/custom/runners/_gliner2.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/custom/runners/_image_classification.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/custom/runners/_llm.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/custom/runners/_object_detection.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/custom/runners/_regex.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/custom/runners/_tag.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/custom/runners/_text_classification.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/custom/trainer.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/custom_detector/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/custom_detector/contract.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/custom_detector/files.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/custom_detector/preview.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/custom_detector/results.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/custom_detector/runner.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/custom_detector/sdk.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/custom_detector/session.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/dependencies.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/engine_version.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/errors.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/pii/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/pii/detector.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/secrets/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/secrets/detector.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/threat/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/threat/code_security_detector.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/detectors/threat/yara_detector.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/file_evaluation/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/file_evaluation/asset_fixture.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/file_evaluation/runner.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/graph/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/graph/edges.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/main.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/models/generated_detectors.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/models/generated_input.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/models/generated_single_asset_scan_results.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/notebook/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/notebook/child.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/notebook/cli.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/notebook/cohort.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/notebook/contract.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/notebook/execute.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/notebook/files.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/notebook/groups.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/notebook/outputs.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/notebook/packages.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/notebook/protocol.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/notebook/redact.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/notebook/scaffold.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/notebook/serialize.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/outputs/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/outputs/base.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/outputs/console.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/outputs/factory.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/outputs/file.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/outputs/rest.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/pipeline/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/pipeline/asset_payload_server.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/pipeline/content_provider.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/pipeline/detector_breaker.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/pipeline/detector_pipeline.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/pipeline/parsed_content_provider.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/pipeline/payload_window.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/pipeline/scan_cache.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/pipeline/text_artifact.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/pipeline/worker_pool.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/asset_metadata.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/atlassian_common.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/azure_blob_storage/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/azure_blob_storage/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/confluence/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/confluence/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/custom/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/custom/env.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/custom/runner.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/custom/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/databricks/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/databricks/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/delta_lake/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/delta_lake/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/dependencies.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/dropbox/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/dropbox/auth.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/dropbox/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/elasticsearch/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/elasticsearch/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/email/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/email/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/git/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/git/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/google_cloud_storage/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/google_cloud_storage/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/google_workspace/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/google_workspace/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/hive/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/hive/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/hugging_face/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/hugging_face/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/iceberg/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/iceberg/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/jira/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/jira/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/kafka/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/kafka/rest.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/kafka/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/lakehouse_base.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/local_folder/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/local_folder/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/meilisearch/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/meilisearch/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/microsoft_365/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/microsoft_365/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/mongodb/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/mongodb/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/mssql/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/mssql/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/mysql/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/mysql/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/neo4j/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/neo4j/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/notion/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/notion/client.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/notion/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/object_storage/base.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/opensearch/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/opensearch/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/oracle/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/oracle/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/postgresql/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/postgresql/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/powerbi/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/powerbi/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/recipe_normalizer.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/reddit/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/reddit/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/s3_client.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/s3_compatible_storage/README.md +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/s3_compatible_storage/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/s3_compatible_storage/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/sandbox/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/sandbox/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/search_engine_base.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/servicedesk/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/servicedesk/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/slack/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/slack/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/snowflake/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/snowflake/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/sqlite/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/sqlite/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/tableau/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/tableau/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/tabular_base.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/tabular_utils.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/wordpress/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/wordpress/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/youtube/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/sources/youtube/source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/telemetry.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/utils/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/utils/archive_extraction.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/utils/content_extraction.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/utils/dependency_groups.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/utils/embedded_files.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/utils/external_video.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/utils/file_metadata.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/utils/file_parser.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/utils/file_to_images.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/utils/hashing.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/utils/legacy_office.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/utils/payload.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/utils/range_reader.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/utils/redaction.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/utils/resources.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/utils/source_files.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/utils/sql_lineage.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/utils/torch_runtime.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/utils/transcription.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/utils/urn.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/utils/uv_sync.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/utils/validation.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/src/utils/video_processing.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/_lakehouse_fakes.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/conftest.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/broken_links/test_broken_links_detector.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/conftest.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/content/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/custom/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/custom/conftest.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/custom/test_gliner2_classification.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/custom/test_invoice_extraction.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/custom/test_llm_runner.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/custom/test_pipeline_integration.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/custom/test_regex_runner.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/custom/test_runner_hardening.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/custom/test_transformer_runners.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/pii/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/pii/conftest.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/pii/sample_invoice.pdf +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/pii/test_pii_chunking.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/pii/test_pii_detector.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/pii/test_pii_detector_extended.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/pii/test_pii_regional_entities.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/pii/test_pii_severity.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/secrets/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/secrets/test_secrets_detector.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/secrets/test_secrets_detector_extended.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/test_base_detector.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/test_custom_detector_examples_runtime.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/test_custom_detector_session.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/test_detector_catalog_commercial.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/test_detector_pipeline_types.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/test_detector_schema_examples.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/test_detector_types.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/test_phase2_detectors.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/test_registry.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/test_tag_runner.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/threat/__init__.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/threat/test_code_security_detector.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/detectors/threat/test_yara_detector.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/integration/test_wordpress_broken_links_detector.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/integration/test_wordpress_links_assets.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/pipeline/test_detector_breaker.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/pipeline/test_detector_outcomes.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/pipeline/test_detector_pipeline.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/pipeline/test_worker_pool.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_arrow_ipc_payloads.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_assets_metadata_catalog.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_augmentation_contract.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_augmentation_edges.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_augmentation_patch.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_augmentation_scan_cache.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_augmentation_sdk.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_augmentation_session.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_augmentation_tags_end_to_end.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_azure_blob_storage_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_base_source_attachment.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_base_source_sampling.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_byte_valued_fields.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_config.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_confluence_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_custom_detector_end_to_end.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_custom_detector_evaluation.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_custom_detector_notebook.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_custom_detector_templates.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_custom_extractor.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_custom_source_relationships.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_custom_source_tags_end_to_end.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_databricks_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_delta_lake_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_dependency_groups.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_detector_scope.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_dropbox_auth_command.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_email_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_file_evaluation_runner.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_git_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_google_cloud_storage_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_graph_edges.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_hashing.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_hive_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_iceberg_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_jira_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_lineage_loss_reporting.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_local_folder_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_main.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_microsoft_365_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_mongodb_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_mssql_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_mysql_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_neo4j_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_notebook_asset_entity.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_notebook_asset_extract.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_notebook_asset_tags.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_notebook_cohort.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_notebook_execute.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_notebook_files.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_notebook_groups.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_notebook_log_levels.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_notebook_packages.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_notebook_query_assets.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_notebook_sdk_exports.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_notebook_serialize.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_notebook_templates.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_notion_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_object_storage_download_efficiency.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_oracle_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_outputs.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_payload_window.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_payload_window_parquet.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_postgresql_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_powerbi_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_range_reading.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_recipe_normalizer.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_reddit_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_rest_backpressure.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_runtime_packages.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_s3_compatible_storage_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_scan_cache.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_sdk_completions.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_secret_redaction.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_semantic_declarations.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_servicedesk_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_slack_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_snowflake_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_source_dependency_groups.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_source_strategy_conformance.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_sqlite_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_tableau_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_tabular_lineage.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_tabular_utils.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_torch_runtime.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_urn.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_uv_sync.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_wordpress_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_youtube_source.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/test_youtube_source_integration.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/utils/test_archive_extraction.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/utils/test_content_extraction.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/utils/test_embedded_files.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/utils/test_file_metadata.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/utils/test_file_parser.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/utils/test_file_to_images.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/utils/test_legacy_office.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/utils/test_payload.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/utils/test_transcription.py +0 -0
- {classifyre_cli-0.6.5 → classifyre_cli-0.6.7}/tests/utils/test_video_processing.py +0 -0
|
@@ -47,6 +47,7 @@ from ..graph.edges import (
|
|
|
47
47
|
same_as,
|
|
48
48
|
uses,
|
|
49
49
|
)
|
|
50
|
+
from ..utils.sampling_cursor import check_cursor_fits
|
|
50
51
|
from ..utils.urn import Urn
|
|
51
52
|
from .cohort import CohortItem, normalize_bands, select_cohort
|
|
52
53
|
from .files import DEFAULT_PAGE_SIZE, ParsedContent, pages, parse
|
|
@@ -700,7 +701,13 @@ class Context:
|
|
|
700
701
|
no longer implies a deleted one. Worse, the discard was silent: the call
|
|
701
702
|
succeeded, the notebook believed it had saved state, and the next run
|
|
702
703
|
started from nothing.
|
|
704
|
+
|
|
705
|
+
Raises ``ValueError`` for a cursor too large to start the next run with
|
|
706
|
+
(128 KiB compressed). Keep a cursor to positions and counters; state
|
|
707
|
+
that grows with the corpus belongs in assets, read back with
|
|
708
|
+
``ctx.query_assets()``.
|
|
703
709
|
"""
|
|
710
|
+
check_cursor_fits(cursor)
|
|
704
711
|
self._next_cursor = dict(cursor)
|
|
705
712
|
|
|
706
713
|
@property
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import base64
|
|
2
|
-
import json
|
|
3
1
|
import logging
|
|
4
2
|
import os
|
|
5
3
|
import threading
|
|
@@ -13,6 +11,7 @@ from ..outputs.rest import IngestEdge
|
|
|
13
11
|
if TYPE_CHECKING:
|
|
14
12
|
from ..utils.file_parser import ParsedBytes
|
|
15
13
|
from ..utils.hashing import calculate_checksum, normalize_http_url
|
|
14
|
+
from ..utils.sampling_cursor import decode_sampling_cursor
|
|
16
15
|
from ..utils.validation import validate_output
|
|
17
16
|
from .recipe_normalizer import normalize_source_recipe
|
|
18
17
|
|
|
@@ -34,8 +33,8 @@ class BaseSource(ABC):
|
|
|
34
33
|
# Default batch size for streaming asset results
|
|
35
34
|
BATCH_SIZE: int = 50
|
|
36
35
|
HAS_SUCCESSFUL_RUN_ENV = "CLASSIFYRE_SOURCE_HAS_SUCCESSFUL_RUN"
|
|
37
|
-
# The API injects the saved
|
|
38
|
-
#
|
|
36
|
+
# The API injects the saved cursor here (gzip + base64 JSON, see
|
|
37
|
+
# utils/sampling_cursor.py) before launching the CLI job. The recipe itself cannot carry it
|
|
39
38
|
# because every source schema sets ``additionalProperties: false``.
|
|
40
39
|
SAMPLING_CURSOR_ENV = "CLASSIFYRE_SAMPLING_CURSOR"
|
|
41
40
|
|
|
@@ -128,12 +127,10 @@ class BaseSource(ABC):
|
|
|
128
127
|
if not raw:
|
|
129
128
|
return {}
|
|
130
129
|
try:
|
|
131
|
-
|
|
132
|
-
data = json.loads(decoded)
|
|
130
|
+
return decode_sampling_cursor(raw)
|
|
133
131
|
except Exception as exc:
|
|
134
132
|
logger.warning("Ignoring malformed %s: %s", self.SAMPLING_CURSOR_ENV, exc)
|
|
135
133
|
return {}
|
|
136
|
-
return data if isinstance(data, dict) else {}
|
|
137
134
|
|
|
138
135
|
def sampling_cursor(self) -> dict[str, Any]:
|
|
139
136
|
"""Return the cursor saved by the previous run (empty on first run)."""
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""How a source's saved cursor travels from the API to a scan.
|
|
2
|
+
|
|
3
|
+
The API hands the cursor to the CLI in one environment variable, gzip then
|
|
4
|
+
base64. Linux caps a single environment variable at 128 KiB (MAX_ARG_STRLEN);
|
|
5
|
+
over it the process never starts and the kernel's "argument list too long"
|
|
6
|
+
names neither the variable nor its size. So the size is checked on both ends:
|
|
7
|
+
the API refuses to launch with a cursor that does not fit, and a notebook is
|
|
8
|
+
told in the run that tries to *save* one, while it can still do something
|
|
9
|
+
about it.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import base64
|
|
15
|
+
import gzip
|
|
16
|
+
import json
|
|
17
|
+
from collections.abc import Mapping
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
#: The limit for one environment variable, and so for an encoded cursor.
|
|
21
|
+
MAX_ENCODED_CURSOR_BYTES = 128 * 1024
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def encode_sampling_cursor(cursor: Mapping[str, Any]) -> str:
|
|
25
|
+
"""The cursor as it is put into the environment variable."""
|
|
26
|
+
raw = json.dumps(cursor, separators=(",", ":")).encode("utf-8")
|
|
27
|
+
return base64.b64encode(gzip.compress(raw)).decode("ascii")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def decode_sampling_cursor(encoded: str) -> dict[str, Any]:
|
|
31
|
+
"""The cursor an environment variable carries. Raises on malformed input."""
|
|
32
|
+
data = json.loads(gzip.decompress(base64.b64decode(encoded)).decode("utf-8"))
|
|
33
|
+
return data if isinstance(data, dict) else {}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def check_cursor_fits(cursor: Mapping[str, Any]) -> None:
|
|
37
|
+
"""Refuse a cursor the next run could not be started with."""
|
|
38
|
+
size = len(encode_sampling_cursor(cursor))
|
|
39
|
+
if size > MAX_ENCODED_CURSOR_BYTES:
|
|
40
|
+
raise ValueError(
|
|
41
|
+
f"This cursor is {size} bytes compressed, over the "
|
|
42
|
+
f"{MAX_ENCODED_CURSOR_BYTES}-byte limit: the next run could not be "
|
|
43
|
+
"started with it. A cursor records where a run stopped; keep bulk "
|
|
44
|
+
"state in assets and read it back with ctx.query_assets()."
|
|
45
|
+
)
|
|
@@ -20,6 +20,7 @@ from src.sources.asset_metadata import is_open_kind, validate_metadata
|
|
|
20
20
|
from src.sources.custom.env import ALLOWED_ENV_KEYS, scrubbed_environment
|
|
21
21
|
from src.sources.custom.source import CustomSource, CustomSourceError
|
|
22
22
|
from src.utils.hashing import hash_id
|
|
23
|
+
from src.utils.sampling_cursor import encode_sampling_cursor
|
|
23
24
|
|
|
24
25
|
SIMPLE_NOTEBOOK = """from classifyre import Asset, ctx
|
|
25
26
|
|
|
@@ -344,12 +345,10 @@ def test_automatic_strategy_pages_across_runs(monkeypatch: pytest.MonkeyPatch) -
|
|
|
344
345
|
|
|
345
346
|
# The API carries the cursor to the next run through the environment,
|
|
346
347
|
# because the recipe itself forbids extra keys.
|
|
347
|
-
import base64
|
|
348
|
-
import json
|
|
349
348
|
|
|
350
349
|
monkeypatch.setenv(
|
|
351
350
|
CustomSource.SAMPLING_CURSOR_ENV,
|
|
352
|
-
|
|
351
|
+
encode_sampling_cursor(cursor),
|
|
353
352
|
)
|
|
354
353
|
second = get_source(dict(recipe), source_id="s", runner_id="r")
|
|
355
354
|
try:
|
|
@@ -378,13 +377,11 @@ def test_an_automatic_window_keeps_the_notebooks_stored_cursor(
|
|
|
378
377
|
) -> None:
|
|
379
378
|
# GENESIS field report P15: a window stopped the notebook before it reached
|
|
380
379
|
# ctx.set_cursor(), and the positional offset replaced its whole state.
|
|
381
|
-
import base64
|
|
382
|
-
import json
|
|
383
380
|
|
|
384
381
|
stored = {"no_data": {"51000-0013": "2026-09-13"}, "seed_applied": True}
|
|
385
382
|
monkeypatch.setenv(
|
|
386
383
|
CustomSource.SAMPLING_CURSOR_ENV,
|
|
387
|
-
|
|
384
|
+
encode_sampling_cursor(stored),
|
|
388
385
|
)
|
|
389
386
|
recipe = build_recipe(counting_notebook(25))
|
|
390
387
|
recipe["sampling"] = {"strategy": "AUTOMATIC", "rows_per_page": 10}
|
|
@@ -1020,8 +1017,6 @@ def test_random_is_bounded_and_actually_random_without_cooperation(source) -> No
|
|
|
1020
1017
|
|
|
1021
1018
|
|
|
1022
1019
|
def test_automatic_pages_without_cooperation(monkeypatch) -> None:
|
|
1023
|
-
import base64
|
|
1024
|
-
import json
|
|
1025
1020
|
|
|
1026
1021
|
recipe = build_recipe(IGNORES_CTX)
|
|
1027
1022
|
recipe["sampling"] = {"strategy": "AUTOMATIC", "rows_per_page": 10}
|
|
@@ -1032,7 +1027,7 @@ def test_automatic_pages_without_cooperation(monkeypatch) -> None:
|
|
|
1032
1027
|
if cursor is not None:
|
|
1033
1028
|
monkeypatch.setenv(
|
|
1034
1029
|
CustomSource.SAMPLING_CURSOR_ENV,
|
|
1035
|
-
|
|
1030
|
+
encode_sampling_cursor(cursor),
|
|
1036
1031
|
)
|
|
1037
1032
|
instance = get_source(dict(recipe), source_id="s", runner_id="r")
|
|
1038
1033
|
try:
|
|
@@ -1050,8 +1045,6 @@ def test_a_notebook_can_take_over_paging_from_the_runtime(monkeypatch) -> None:
|
|
|
1050
1045
|
# Without this, run N makes extract() produce N*page items to deliver page
|
|
1051
1046
|
# of them -- fine for a list in memory, quadratic when each item is a
|
|
1052
1047
|
# request. Reading ctx.offset hands the skipping to the notebook.
|
|
1053
|
-
import base64
|
|
1054
|
-
import json
|
|
1055
1048
|
|
|
1056
1049
|
notebook = """from classifyre import Asset, ctx
|
|
1057
1050
|
|
|
@@ -1070,7 +1063,7 @@ def extract():
|
|
|
1070
1063
|
|
|
1071
1064
|
monkeypatch.setenv(
|
|
1072
1065
|
CustomSource.SAMPLING_CURSOR_ENV,
|
|
1073
|
-
|
|
1066
|
+
encode_sampling_cursor({"assets": 20}),
|
|
1074
1067
|
)
|
|
1075
1068
|
instance = get_source(dict(recipe), source_id="s", runner_id="r")
|
|
1076
1069
|
try:
|
|
@@ -11,6 +11,7 @@ from src.models.generated_single_asset_scan_results import AssetType as OutputAs
|
|
|
11
11
|
from src.sources.asset_metadata import resolve_fields
|
|
12
12
|
from src.sources.dropbox.source import DropboxObjectRef, DropboxScanTarget, DropboxSource
|
|
13
13
|
from src.sources.object_storage.base import ContentSnapshot
|
|
14
|
+
from src.utils.sampling_cursor import encode_sampling_cursor
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
def _recipe(
|
|
@@ -146,12 +147,9 @@ def test_dropbox_sampling_automatic_advances_window_across_runs(monkeypatch):
|
|
|
146
147
|
assert [item.key for item in first] == [f"/f{index:02d}.txt" for index in range(10)]
|
|
147
148
|
assert cursor == {"objects": 10}
|
|
148
149
|
|
|
149
|
-
import base64
|
|
150
|
-
import json
|
|
151
|
-
|
|
152
150
|
monkeypatch.setenv(
|
|
153
151
|
DropboxSource.SAMPLING_CURSOR_ENV,
|
|
154
|
-
|
|
152
|
+
encode_sampling_cursor(cursor),
|
|
155
153
|
)
|
|
156
154
|
resumed = DropboxSource(_recipe(strategy="AUTOMATIC", rows_per_page=10))
|
|
157
155
|
second = resumed._apply_sampling(refs)
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import base64
|
|
4
|
-
import json
|
|
5
3
|
from typing import Any
|
|
6
4
|
|
|
7
5
|
import pytest
|
|
8
6
|
import requests
|
|
9
7
|
|
|
10
8
|
from src.sources.elasticsearch.source import ElasticsearchSource
|
|
9
|
+
from src.utils.sampling_cursor import encode_sampling_cursor
|
|
11
10
|
|
|
12
11
|
_INDICES = [
|
|
13
12
|
{
|
|
@@ -237,7 +236,7 @@ def test_elasticsearch_automatic_strategy_starts_at_offset_zero(
|
|
|
237
236
|
def test_elasticsearch_automatic_strategy_resumes_and_wraps_on_underfill(
|
|
238
237
|
_patch_requests: dict[str, Any], monkeypatch: pytest.MonkeyPatch
|
|
239
238
|
) -> None:
|
|
240
|
-
saved_cursor =
|
|
239
|
+
saved_cursor = encode_sampling_cursor({"index:orders": 10})
|
|
241
240
|
monkeypatch.setenv("CLASSIFYRE_SAMPLING_CURSOR", saved_cursor)
|
|
242
241
|
|
|
243
242
|
src = ElasticsearchSource(_recipe(sampling={"strategy": "AUTOMATIC", "rows_per_page": 10}))
|
|
@@ -274,7 +273,7 @@ async def test_elasticsearch_all_strategy_scrolls_the_whole_index(
|
|
|
274
273
|
def test_elasticsearch_automatic_strategy_wraps_at_max_result_window(
|
|
275
274
|
_patch_requests: dict[str, Any], monkeypatch: pytest.MonkeyPatch
|
|
276
275
|
) -> None:
|
|
277
|
-
saved_cursor =
|
|
276
|
+
saved_cursor = encode_sampling_cursor({"index:orders": 10})
|
|
278
277
|
monkeypatch.setenv("CLASSIFYRE_SAMPLING_CURSOR", saved_cursor)
|
|
279
278
|
|
|
280
279
|
src = ElasticsearchSource(_recipe(sampling={"strategy": "AUTOMATIC", "rows_per_page": 10}))
|
|
@@ -13,6 +13,7 @@ from src.sources.google_workspace.source import (
|
|
|
13
13
|
FileRef,
|
|
14
14
|
GoogleWorkspaceSource,
|
|
15
15
|
)
|
|
16
|
+
from src.utils.sampling_cursor import encode_sampling_cursor
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
def _base_recipe(auth_mode: str = "service_account", **overrides: object) -> dict:
|
|
@@ -140,11 +141,9 @@ class TestSampling:
|
|
|
140
141
|
assert cursor_after_first["drive_items:drive-1"] == 10
|
|
141
142
|
|
|
142
143
|
# Simulate a second run reading the persisted cursor.
|
|
143
|
-
import base64
|
|
144
|
-
import json
|
|
145
144
|
import os
|
|
146
145
|
|
|
147
|
-
encoded =
|
|
146
|
+
encoded = encode_sampling_cursor(cursor_after_first)
|
|
148
147
|
os.environ["CLASSIFYRE_SAMPLING_CURSOR"] = encoded
|
|
149
148
|
try:
|
|
150
149
|
source2 = GoogleWorkspaceSource(recipe)
|
|
@@ -158,9 +157,7 @@ class TestSampling:
|
|
|
158
157
|
assert cursor_after_second["drive_items:drive-1"] == 20
|
|
159
158
|
|
|
160
159
|
# A third page underfills (only 4 items remain) and should wrap to 0.
|
|
161
|
-
os.environ["CLASSIFYRE_SAMPLING_CURSOR"] =
|
|
162
|
-
json.dumps(cursor_after_second).encode("utf-8")
|
|
163
|
-
).decode("utf-8")
|
|
160
|
+
os.environ["CLASSIFYRE_SAMPLING_CURSOR"] = encode_sampling_cursor(cursor_after_second)
|
|
164
161
|
try:
|
|
165
162
|
source3 = GoogleWorkspaceSource(recipe)
|
|
166
163
|
third = source3._apply_sampling(items, "drive-1")
|
|
@@ -11,6 +11,7 @@ from src.models.generated_single_asset_scan_results import AssetType as OutputAs
|
|
|
11
11
|
from src.sources.asset_metadata import resolve_fields
|
|
12
12
|
from src.sources.hugging_face.source import HuggingFaceObjectRef, HuggingFaceSource
|
|
13
13
|
from src.sources.object_storage.base import ContentSnapshot
|
|
14
|
+
from src.utils.sampling_cursor import encode_sampling_cursor
|
|
14
15
|
|
|
15
16
|
REPO_SHA = "0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c"
|
|
16
17
|
|
|
@@ -462,8 +463,6 @@ def test_hugging_face_sampling_random_is_deterministic():
|
|
|
462
463
|
|
|
463
464
|
|
|
464
465
|
def test_hugging_face_sampling_automatic_advances_window_across_runs(monkeypatch):
|
|
465
|
-
import base64
|
|
466
|
-
import json
|
|
467
466
|
|
|
468
467
|
refs = [_ref(f"data/f{index:02d}.parquet", days_ago=index) for index in range(25)]
|
|
469
468
|
|
|
@@ -476,7 +475,7 @@ def test_hugging_face_sampling_automatic_advances_window_across_runs(monkeypatch
|
|
|
476
475
|
|
|
477
476
|
monkeypatch.setenv(
|
|
478
477
|
HuggingFaceSource.SAMPLING_CURSOR_ENV,
|
|
479
|
-
|
|
478
|
+
encode_sampling_cursor(cursor),
|
|
480
479
|
)
|
|
481
480
|
resumed = _source(strategy="AUTOMATIC", rows_per_page=10)
|
|
482
481
|
second = resumed._apply_sampling(iter(refs))
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import base64
|
|
4
|
-
import json
|
|
5
4
|
from typing import Any
|
|
6
5
|
|
|
7
6
|
import pytest
|
|
8
7
|
|
|
9
8
|
from src.models.generated_input import SamplingStrategy
|
|
10
9
|
from src.sources.kafka.source import KafkaSource
|
|
10
|
+
from src.utils.sampling_cursor import encode_sampling_cursor
|
|
11
11
|
|
|
12
12
|
CURSOR_ENV = "CLASSIFYRE_SAMPLING_CURSOR"
|
|
13
13
|
|
|
@@ -19,7 +19,7 @@ _CA_PEM = "-----BEGIN CERTIFICATE-----\nZmFrZS1jYQ==\n-----END CERTIFICATE-----"
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def _encode_cursor(cursor: dict[str, Any]) -> str:
|
|
22
|
-
return
|
|
22
|
+
return encode_sampling_cursor(cursor)
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
# ── confluent-kafka fakes ────────────────────────────────────────────────
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import base64
|
|
4
|
-
import json
|
|
5
3
|
from typing import Any
|
|
6
4
|
|
|
7
5
|
import pytest
|
|
8
6
|
import requests
|
|
9
7
|
|
|
10
8
|
from src.sources.meilisearch.source import MeilisearchSource
|
|
9
|
+
from src.utils.sampling_cursor import encode_sampling_cursor
|
|
11
10
|
|
|
12
11
|
_INDEX_LIST_PAGE1 = {
|
|
13
12
|
"results": [
|
|
@@ -227,7 +226,7 @@ def test_meilisearch_automatic_strategy_starts_at_offset_zero(
|
|
|
227
226
|
def test_meilisearch_automatic_strategy_resumes_and_wraps_on_underfill(
|
|
228
227
|
_patch_requests: dict[str, Any], monkeypatch: pytest.MonkeyPatch
|
|
229
228
|
) -> None:
|
|
230
|
-
saved_cursor =
|
|
229
|
+
saved_cursor = encode_sampling_cursor({"index:products": 10})
|
|
231
230
|
monkeypatch.setenv("CLASSIFYRE_SAMPLING_CURSOR", saved_cursor)
|
|
232
231
|
|
|
233
232
|
src = MeilisearchSource(_recipe(sampling={"strategy": "AUTOMATIC", "rows_per_page": 10}))
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import base64
|
|
4
|
-
import json
|
|
5
3
|
from typing import Any
|
|
6
4
|
|
|
7
5
|
import pytest
|
|
8
6
|
import requests
|
|
9
7
|
|
|
10
8
|
from src.sources.opensearch.source import OpenSearchSource
|
|
9
|
+
from src.utils.sampling_cursor import encode_sampling_cursor
|
|
11
10
|
|
|
12
11
|
_INDICES = [
|
|
13
12
|
{
|
|
@@ -235,7 +234,7 @@ def test_opensearch_automatic_strategy_starts_at_offset_zero(
|
|
|
235
234
|
def test_opensearch_automatic_strategy_resumes_and_wraps_on_underfill(
|
|
236
235
|
_patch_requests: dict[str, Any], monkeypatch: pytest.MonkeyPatch
|
|
237
236
|
) -> None:
|
|
238
|
-
saved_cursor =
|
|
237
|
+
saved_cursor = encode_sampling_cursor({"index:logs-app": 10})
|
|
239
238
|
monkeypatch.setenv("CLASSIFYRE_SAMPLING_CURSOR", saved_cursor)
|
|
240
239
|
|
|
241
240
|
src = OpenSearchSource(_recipe(sampling={"strategy": "AUTOMATIC", "rows_per_page": 10}))
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import base64
|
|
6
|
-
import json
|
|
7
5
|
from collections.abc import AsyncGenerator
|
|
8
6
|
from types import SimpleNamespace
|
|
9
7
|
from typing import Any
|
|
@@ -12,6 +10,7 @@ import pytest
|
|
|
12
10
|
|
|
13
11
|
from src.models.generated_single_asset_scan_results import SingleAssetScanResults
|
|
14
12
|
from src.sources.base import BaseSource
|
|
13
|
+
from src.utils.sampling_cursor import encode_sampling_cursor
|
|
15
14
|
|
|
16
15
|
CURSOR_ENV = "CLASSIFYRE_SAMPLING_CURSOR"
|
|
17
16
|
|
|
@@ -47,7 +46,7 @@ def _src(rows: int = 3) -> _DummySource:
|
|
|
47
46
|
|
|
48
47
|
|
|
49
48
|
def _encode(cursor: dict[str, Any]) -> str:
|
|
50
|
-
return
|
|
49
|
+
return encode_sampling_cursor(cursor)
|
|
51
50
|
|
|
52
51
|
|
|
53
52
|
def test_default_strategy_is_automatic() -> None:
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from src.notebook.sdk import Context
|
|
9
|
+
from src.sources.base import BaseSource
|
|
10
|
+
from src.utils.sampling_cursor import (
|
|
11
|
+
MAX_ENCODED_CURSOR_BYTES,
|
|
12
|
+
check_cursor_fits,
|
|
13
|
+
decode_sampling_cursor,
|
|
14
|
+
encode_sampling_cursor,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _person_companies(persons: int) -> dict[str, list[str]]:
|
|
19
|
+
"""The shape that broke a production source: a person -> companies map."""
|
|
20
|
+
return {
|
|
21
|
+
f"person-{i:06d}--19{i % 100:02d}-01-01": [f"{i:06d}a", f"{i + 1:06d}b"]
|
|
22
|
+
for i in range(persons)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_cursor_round_trips() -> None:
|
|
27
|
+
cursor = {"cohort": {"register": {"newest": "606601k"}}, "companies": 1200}
|
|
28
|
+
assert decode_sampling_cursor(encode_sampling_cursor(cursor)) == cursor
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_a_cursor_too_large_as_plain_base64_fits_once_compressed() -> None:
|
|
32
|
+
cursor = {"person_companies": _person_companies(2400)}
|
|
33
|
+
plain = base64.b64encode(json.dumps(cursor).encode()).decode()
|
|
34
|
+
assert len(plain) > MAX_ENCODED_CURSOR_BYTES
|
|
35
|
+
assert len(encode_sampling_cursor(cursor)) < MAX_ENCODED_CURSOR_BYTES
|
|
36
|
+
check_cursor_fits(cursor)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_a_cursor_that_cannot_start_the_next_run_is_refused() -> None:
|
|
40
|
+
# Random-looking values, so compression cannot rescue it.
|
|
41
|
+
cursor = {str(i): base64.b64encode(i.to_bytes(4, "big") * 12).decode() for i in range(40000)}
|
|
42
|
+
with pytest.raises(ValueError, match="over the 131072-byte limit"):
|
|
43
|
+
check_cursor_fits(cursor)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_set_cursor_refuses_an_oversized_cursor_and_keeps_the_last_good_one() -> None:
|
|
47
|
+
ctx = Context()
|
|
48
|
+
ctx.set_cursor({"offset": 5})
|
|
49
|
+
huge = {str(i): base64.b64encode(i.to_bytes(4, "big") * 12).decode() for i in range(40000)}
|
|
50
|
+
with pytest.raises(ValueError, match=r"ctx\.query_assets\(\)"):
|
|
51
|
+
ctx.set_cursor(huge)
|
|
52
|
+
assert ctx.next_cursor == {"offset": 5}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_a_malformed_cursor_is_ignored(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
56
|
+
class _Source:
|
|
57
|
+
SAMPLING_CURSOR_ENV = BaseSource.SAMPLING_CURSOR_ENV
|
|
58
|
+
|
|
59
|
+
monkeypatch.setenv(BaseSource.SAMPLING_CURSOR_ENV, "not-a-cursor")
|
|
60
|
+
assert BaseSource._load_sampling_cursor(_Source()) == {} # type: ignore[arg-type]
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import base64
|
|
4
|
-
import json
|
|
5
3
|
from datetime import UTC, datetime, timedelta
|
|
6
4
|
|
|
7
5
|
import pytest
|
|
@@ -9,6 +7,7 @@ import pytest
|
|
|
9
7
|
from src.sources import get_source, list_available_sources
|
|
10
8
|
from src.sources.object_storage.base import ContentSnapshot, ObjectRef
|
|
11
9
|
from src.sources.sandbox.source import SandboxSource
|
|
10
|
+
from src.utils.sampling_cursor import encode_sampling_cursor
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
def _recipe(strategy: str = "ALL", rows_per_page: int = 10) -> dict:
|
|
@@ -76,7 +75,7 @@ def test_sandbox_automatic_sampling_uses_persisted_rolling_window(monkeypatch) -
|
|
|
76
75
|
|
|
77
76
|
monkeypatch.setenv(
|
|
78
77
|
"CLASSIFYRE_SAMPLING_CURSOR",
|
|
79
|
-
|
|
78
|
+
encode_sampling_cursor(cursor),
|
|
80
79
|
)
|
|
81
80
|
second = SandboxSource(_recipe("AUTOMATIC"), source_id="source-1")
|
|
82
81
|
try:
|
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import base64
|
|
6
|
-
import json
|
|
7
5
|
from typing import Any
|
|
8
6
|
|
|
9
7
|
import pytest
|
|
10
8
|
|
|
11
9
|
from src.sources.mysql.source import MySQLSource, TableRef
|
|
10
|
+
from src.utils.sampling_cursor import encode_sampling_cursor
|
|
12
11
|
|
|
13
12
|
CURSOR_ENV = "CLASSIFYRE_SAMPLING_CURSOR"
|
|
14
13
|
PAGE = 10 # rows_per_page (MySQL config enforces a minimum of 10)
|
|
@@ -94,7 +93,7 @@ def _make_source(
|
|
|
94
93
|
|
|
95
94
|
|
|
96
95
|
def _encode(cursor: dict[str, Any]) -> str:
|
|
97
|
-
return
|
|
96
|
+
return encode_sampling_cursor(cursor)
|
|
98
97
|
|
|
99
98
|
|
|
100
99
|
_TABLE = TableRef(database="app_db", schema=None, table="users")
|