classifyre-cli 0.5.6__tar.gz → 0.5.8__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.5.8/.turbo/turbo-build.log +3 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/PKG-INFO +1 -1
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/package.json +1 -1
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/pyproject.toml +1 -1
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/main.py +149 -35
- classifyre_cli-0.5.8/src/notebook/redact.py +37 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/outputs/rest.py +16 -6
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/pipeline/detector_pipeline.py +21 -3
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/pipeline/parsed_content_provider.py +11 -3
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/custom/source.py +29 -10
- classifyre_cli-0.5.8/src/utils/redaction.py +300 -0
- classifyre_cli-0.5.8/src/utils/validation.py +111 -0
- classifyre_cli-0.5.8/tests/test_secret_redaction.py +223 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/uv.lock +104 -104
- classifyre_cli-0.5.6/.turbo/turbo-build.log +0 -3
- classifyre_cli-0.5.6/src/notebook/redact.py +0 -143
- classifyre_cli-0.5.6/src/utils/validation.py +0 -56
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/.gitignore +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/.python-version +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/README.md +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/main.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/scripts/generate_models.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/scripts/generate_runtime_packages.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/scripts/generate_sdk_completions.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/augmentation/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/augmentation/contract.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/augmentation/preview.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/augmentation/runner.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/augmentation/sdk.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/augmentation/session.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/config.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/base.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/broken_links/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/broken_links/detector.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/config.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/content/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/detector.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/extractor.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/runners/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/runners/_base.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/runners/_factory.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/runners/_gliner2.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/runners/_image_classification.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/runners/_llm.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/runners/_object_detection.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/runners/_regex.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/runners/_tag.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/runners/_text_classification.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/custom/trainer.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/dependencies.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/engine_version.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/errors.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/pii/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/pii/detector.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/secrets/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/secrets/detector.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/threat/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/threat/code_security_detector.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/detectors/threat/yara_detector.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/file_evaluation/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/file_evaluation/runner.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/graph/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/graph/edges.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/models/generated_detectors.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/models/generated_input.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/models/generated_single_asset_scan_results.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/child.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/cli.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/cohort.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/contract.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/execute.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/files.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/groups.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/outputs.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/packages.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/protocol.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/scaffold.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/sdk.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/notebook/serialize.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/outputs/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/outputs/base.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/outputs/console.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/outputs/factory.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/outputs/file.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/pipeline/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/pipeline/content_provider.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/pipeline/detector_breaker.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/pipeline/payload_window.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/pipeline/scan_cache.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/pipeline/text_artifact.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/pipeline/worker_pool.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/asset_metadata.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/atlassian_common.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/azure_blob_storage/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/azure_blob_storage/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/base.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/confluence/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/confluence/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/custom/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/custom/env.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/custom/runner.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/databricks/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/databricks/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/delta_lake/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/delta_lake/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/dependencies.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/dropbox/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/dropbox/auth.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/dropbox/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/elasticsearch/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/elasticsearch/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/email/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/email/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/git/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/git/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/google_cloud_storage/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/google_cloud_storage/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/google_workspace/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/google_workspace/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/hive/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/hive/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/hugging_face/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/hugging_face/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/iceberg/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/iceberg/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/jira/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/jira/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/kafka/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/kafka/rest.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/kafka/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/lakehouse_base.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/local_folder/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/local_folder/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/meilisearch/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/meilisearch/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/microsoft_365/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/microsoft_365/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/mongodb/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/mongodb/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/mssql/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/mssql/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/mysql/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/mysql/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/neo4j/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/neo4j/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/notion/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/notion/client.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/notion/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/object_storage/base.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/opensearch/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/opensearch/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/oracle/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/oracle/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/postgresql/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/postgresql/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/powerbi/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/powerbi/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/recipe_normalizer.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/reddit/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/reddit/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/s3_client.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/s3_compatible_storage/README.md +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/s3_compatible_storage/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/s3_compatible_storage/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/sandbox/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/sandbox/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/search_engine_base.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/servicedesk/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/servicedesk/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/slack/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/slack/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/snowflake/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/snowflake/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/sqlite/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/sqlite/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/tableau/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/tableau/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/tabular_base.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/tabular_utils.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/wordpress/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/wordpress/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/youtube/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/sources/youtube/source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/telemetry.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/archive_extraction.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/content_extraction.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/dependency_groups.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/embedded_files.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/external_video.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/file_metadata.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/file_parser.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/file_to_images.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/hashing.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/legacy_office.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/payload.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/range_reader.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/resources.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/source_files.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/sql_lineage.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/torch_runtime.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/transcription.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/urn.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/uv_sync.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/src/utils/video_processing.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/_lakehouse_fakes.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/conftest.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/broken_links/test_broken_links_detector.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/conftest.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/content/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/custom/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/custom/conftest.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/custom/test_gliner2_classification.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/custom/test_invoice_extraction.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/custom/test_llm_runner.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/custom/test_pipeline_integration.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/custom/test_regex_runner.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/custom/test_runner_hardening.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/custom/test_transformer_runners.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/pii/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/pii/conftest.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/pii/sample_invoice.pdf +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/pii/test_pii_chunking.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/pii/test_pii_detector.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/pii/test_pii_detector_extended.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/pii/test_pii_regional_entities.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/pii/test_pii_severity.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/secrets/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/secrets/test_secrets_detector.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/secrets/test_secrets_detector_extended.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/test_base_detector.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/test_custom_detector_examples_runtime.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/test_detector_catalog_commercial.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/test_detector_pipeline_types.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/test_detector_schema_examples.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/test_detector_types.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/test_phase2_detectors.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/test_registry.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/test_tag_runner.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/threat/__init__.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/threat/test_code_security_detector.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/detectors/threat/test_yara_detector.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/integration/test_wordpress_broken_links_detector.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/integration/test_wordpress_links_assets.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/pipeline/test_detector_breaker.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/pipeline/test_detector_outcomes.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/pipeline/test_detector_pipeline.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/pipeline/test_worker_pool.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_arrow_ipc_payloads.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_assets_metadata_catalog.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_augmentation_contract.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_augmentation_edges.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_augmentation_patch.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_augmentation_scan_cache.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_augmentation_sdk.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_augmentation_session.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_augmentation_tags_end_to_end.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_azure_blob_storage_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_base_source_attachment.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_base_source_sampling.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_byte_valued_fields.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_config.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_confluence_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_custom_extractor.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_custom_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_custom_source_relationships.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_custom_source_tags_end_to_end.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_databricks_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_delta_lake_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_dependency_groups.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_detector_scope.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_dropbox_auth_command.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_dropbox_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_elasticsearch_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_email_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_file_evaluation_runner.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_git_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_google_cloud_storage_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_google_workspace_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_graph_edges.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_hashing.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_hive_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_hugging_face_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_iceberg_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_jira_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_kafka_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_lineage_loss_reporting.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_local_folder_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_main.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_meilisearch_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_microsoft_365_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_mongodb_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_mssql_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_mysql_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_neo4j_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_notebook_asset_extract.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_notebook_asset_tags.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_notebook_cohort.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_notebook_execute.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_notebook_files.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_notebook_groups.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_notebook_packages.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_notebook_query_assets.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_notebook_serialize.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_notebook_templates.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_notion_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_object_storage_download_efficiency.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_opensearch_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_oracle_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_outputs.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_payload_window.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_payload_window_parquet.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_postgresql_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_powerbi_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_range_reading.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_recipe_normalizer.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_reddit_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_rest_backpressure.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_runtime_packages.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_s3_compatible_storage_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_sampling_automatic.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_sandbox_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_scan_cache.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_sdk_completions.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_servicedesk_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_slack_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_snowflake_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_source_dependency_groups.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_source_strategy_conformance.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_sqlite_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_tableau_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_tabular_automatic_sampling.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_tabular_lineage.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_tabular_utils.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_torch_runtime.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_urn.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_uv_sync.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_wordpress_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_youtube_source.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/test_youtube_source_integration.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/utils/test_archive_extraction.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/utils/test_content_extraction.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/utils/test_embedded_files.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/utils/test_file_metadata.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/utils/test_file_parser.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/utils/test_file_to_images.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/utils/test_legacy_office.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/utils/test_payload.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/utils/test_transcription.py +0 -0
- {classifyre_cli-0.5.6 → classifyre_cli-0.5.8}/tests/utils/test_video_processing.py +0 -0
|
@@ -21,7 +21,12 @@ from .sources.dropbox.auth import ( # noqa: E402
|
|
|
21
21
|
add_dropbox_auth_arguments,
|
|
22
22
|
run_dropbox_auth_command,
|
|
23
23
|
)
|
|
24
|
-
from .utils.
|
|
24
|
+
from .utils.redaction import Redactor, redact_generic, sanitize_for_logging # noqa: E402
|
|
25
|
+
from .utils.validation import ( # noqa: E402
|
|
26
|
+
RecipeValidationError,
|
|
27
|
+
validate_input,
|
|
28
|
+
validate_test_connection,
|
|
29
|
+
)
|
|
25
30
|
|
|
26
31
|
logger = logging.getLogger(__name__)
|
|
27
32
|
_SURROGATE_RE = re.compile(r"[\ud800-\udfff]")
|
|
@@ -56,6 +61,53 @@ def _sanitize_for_json(value: Any) -> Any:
|
|
|
56
61
|
return value
|
|
57
62
|
|
|
58
63
|
|
|
64
|
+
def _safe_text(text: Any, redactor: Redactor | None) -> str:
|
|
65
|
+
"""Render ``text`` for logs or stored run errors without secrets.
|
|
66
|
+
|
|
67
|
+
Value-based redaction (exact ``masked`` leaves from the recipe) runs
|
|
68
|
+
first, then credential-shaped patterns, so short secrets and
|
|
69
|
+
driver-echoed connection strings are hidden even when the exact value
|
|
70
|
+
is unknown.
|
|
71
|
+
"""
|
|
72
|
+
raw = text if isinstance(text, str) else str(text)
|
|
73
|
+
if redactor is not None:
|
|
74
|
+
raw = redactor.redact(raw)
|
|
75
|
+
return redact_generic(raw)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _safe_error_copy(exc: Exception, redactor: Redactor | None) -> Exception:
|
|
79
|
+
"""Copy ``exc`` with redacted args so ``str()`` of it is log-safe.
|
|
80
|
+
|
|
81
|
+
Preserves the exception type name used by ``sink.fail()``. Falls back
|
|
82
|
+
to a plain RuntimeError when the copy cannot be made.
|
|
83
|
+
"""
|
|
84
|
+
import copy as _copy
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
clone = _copy.copy(exc)
|
|
88
|
+
clone.args = tuple(
|
|
89
|
+
_safe_text(arg, redactor) if isinstance(arg, str) else arg for arg in exc.args
|
|
90
|
+
)
|
|
91
|
+
return clone
|
|
92
|
+
except Exception:
|
|
93
|
+
return RuntimeError(_safe_text(exc, redactor))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _log_debug_traceback(
|
|
97
|
+
log: Any, message: str, exc: BaseException, redactor: Redactor | None
|
|
98
|
+
) -> None:
|
|
99
|
+
"""Log a traceback at debug without leaking secrets through exc_info.
|
|
100
|
+
|
|
101
|
+
``logger.debug(..., exc_info=True)`` formats the original exception,
|
|
102
|
+
bypassing any redaction. Formatting first and redacting the text keeps
|
|
103
|
+
the traceback useful while hiding credential values.
|
|
104
|
+
"""
|
|
105
|
+
import traceback as _traceback
|
|
106
|
+
|
|
107
|
+
formatted = "".join(_traceback.format_exception(type(exc), exc, exc.__traceback__))
|
|
108
|
+
log.debug("%s\n%s", message, _safe_text(formatted, redactor))
|
|
109
|
+
|
|
110
|
+
|
|
59
111
|
async def _emit_text_chunks_with_retry(
|
|
60
112
|
sink: Any,
|
|
61
113
|
asset_hash: str,
|
|
@@ -178,7 +230,7 @@ def _asset_to_payload(asset: Any) -> dict[str, Any]:
|
|
|
178
230
|
raise TypeError(f"Unsupported asset payload type: {type(asset)}")
|
|
179
231
|
|
|
180
232
|
|
|
181
|
-
async def _flush_buffered_edges(source: Any, sink: Any) -> None:
|
|
233
|
+
async def _flush_buffered_edges(source: Any, sink: Any, redactor: Redactor | None = None) -> None:
|
|
182
234
|
"""Emit edges buffered so far, without asking for end-of-run relationships.
|
|
183
235
|
|
|
184
236
|
Augmentation can produce an edge per asset; holding a whole scan's lineage
|
|
@@ -192,7 +244,10 @@ async def _flush_buffered_edges(source: Any, sink: Any) -> None:
|
|
|
192
244
|
try:
|
|
193
245
|
edges = source.drain_edges()
|
|
194
246
|
except Exception as drain_error:
|
|
195
|
-
logger.warning(
|
|
247
|
+
logger.warning(
|
|
248
|
+
"Could not drain buffered edges (non-fatal): %s",
|
|
249
|
+
_safe_text(drain_error, redactor),
|
|
250
|
+
)
|
|
196
251
|
return
|
|
197
252
|
if not edges:
|
|
198
253
|
return
|
|
@@ -203,12 +258,12 @@ async def _flush_buffered_edges(source: Any, sink: Any) -> None:
|
|
|
203
258
|
logger.warning(
|
|
204
259
|
"Buffered relationship emission failed, %d edge(s) lost (non-fatal): %s",
|
|
205
260
|
len(edges),
|
|
206
|
-
emit_error,
|
|
261
|
+
_safe_text(emit_error, redactor),
|
|
207
262
|
)
|
|
208
263
|
if report is not None:
|
|
209
264
|
report.record_lost(
|
|
210
265
|
len(edges),
|
|
211
|
-
f"{type(emit_error).__name__}: {emit_error}",
|
|
266
|
+
_safe_text(f"{type(emit_error).__name__}: {emit_error}", redactor),
|
|
212
267
|
)
|
|
213
268
|
return
|
|
214
269
|
tally = result if isinstance(result, dict) else {}
|
|
@@ -219,7 +274,13 @@ async def _flush_buffered_edges(source: Any, sink: Any) -> None:
|
|
|
219
274
|
)
|
|
220
275
|
|
|
221
276
|
|
|
222
|
-
async def _emit_relationships(
|
|
277
|
+
async def _emit_relationships(
|
|
278
|
+
source: Any,
|
|
279
|
+
sink: Any,
|
|
280
|
+
*,
|
|
281
|
+
partial: bool = False,
|
|
282
|
+
redactor: Redactor | None = None,
|
|
283
|
+
) -> None:
|
|
223
284
|
"""Send everything the connector learned about how its assets relate.
|
|
224
285
|
|
|
225
286
|
Two sources, one flush: edges buffered mid-scan via ``add_edge`` and edges a
|
|
@@ -252,10 +313,13 @@ async def _emit_relationships(source: Any, sink: Any, *, partial: bool = False)
|
|
|
252
313
|
# The connector's relationship code raised. How many edges that cost is
|
|
253
314
|
# unknowable — it never got to say — so this is counted as one failed
|
|
254
315
|
# relationship pass rather than as N lost edges.
|
|
255
|
-
logger.warning(
|
|
316
|
+
logger.warning(
|
|
317
|
+
"Could not collect relationships (non-fatal): %s",
|
|
318
|
+
_safe_text(collect_error, redactor),
|
|
319
|
+
)
|
|
256
320
|
if report is not None:
|
|
257
321
|
report.record_failure(
|
|
258
|
-
f"{type(collect_error).__name__}: {collect_error}",
|
|
322
|
+
_safe_text(f"{type(collect_error).__name__}: {collect_error}", redactor),
|
|
259
323
|
)
|
|
260
324
|
|
|
261
325
|
# A connector that declares relationships() but produced nothing is not the
|
|
@@ -274,12 +338,12 @@ async def _emit_relationships(source: Any, sink: Any, *, partial: bool = False)
|
|
|
274
338
|
logger.warning(
|
|
275
339
|
"Relationship emission failed, %d edge(s) lost (non-fatal): %s",
|
|
276
340
|
len(edges),
|
|
277
|
-
emit_error,
|
|
341
|
+
_safe_text(emit_error, redactor),
|
|
278
342
|
)
|
|
279
343
|
if report is not None:
|
|
280
344
|
report.record_lost(
|
|
281
345
|
len(edges),
|
|
282
|
-
f"{type(emit_error).__name__}: {emit_error}",
|
|
346
|
+
_safe_text(f"{type(emit_error).__name__}: {emit_error}", redactor),
|
|
283
347
|
)
|
|
284
348
|
return
|
|
285
349
|
|
|
@@ -333,11 +397,16 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
|
|
|
333
397
|
# before phase 2; every later use is None-guarded.
|
|
334
398
|
augmentation: Any = None
|
|
335
399
|
|
|
400
|
+
# Built before any logging or error formatting: every message below that
|
|
401
|
+
# touches the recipe, a driver error, or a notebook verdict goes through
|
|
402
|
+
# it, so secrets never reach runner logs or stored run errors.
|
|
403
|
+
redactor = Redactor.from_recipe(recipe)
|
|
404
|
+
|
|
336
405
|
try:
|
|
337
406
|
try:
|
|
338
407
|
source = get_source(recipe, source_id=source_id, runner_id=runner_id)
|
|
339
408
|
except ValueError as e:
|
|
340
|
-
logger.error("Failed to initialize source: %s", e)
|
|
409
|
+
logger.error("Failed to initialize source: %s", _safe_text(e, redactor))
|
|
341
410
|
sys.exit(1)
|
|
342
411
|
|
|
343
412
|
try:
|
|
@@ -348,27 +417,46 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
|
|
|
348
417
|
validate_test_connection(result)
|
|
349
418
|
logger.info("Test connection output is valid")
|
|
350
419
|
except Exception as validation_error:
|
|
351
|
-
logger.warning(
|
|
420
|
+
logger.warning(
|
|
421
|
+
"Test connection output validation failed: %s",
|
|
422
|
+
_safe_text(validation_error, redactor),
|
|
423
|
+
)
|
|
352
424
|
|
|
353
|
-
print(
|
|
425
|
+
print(
|
|
426
|
+
json.dumps(
|
|
427
|
+
sanitize_for_logging(result, redactor),
|
|
428
|
+
indent=2,
|
|
429
|
+
)
|
|
430
|
+
)
|
|
354
431
|
if result.get("status") == "FAILURE":
|
|
355
432
|
sys.exit(1)
|
|
356
433
|
|
|
357
434
|
elif args.command == "discover":
|
|
358
435
|
result = source.test_connection()
|
|
359
436
|
if result.get("status") == "FAILURE":
|
|
360
|
-
logger.error(
|
|
437
|
+
logger.error(
|
|
438
|
+
"Aborting: Connection test failed: %s",
|
|
439
|
+
_safe_text(result.get("message"), redactor),
|
|
440
|
+
)
|
|
361
441
|
sys.exit(1)
|
|
362
442
|
|
|
363
443
|
logger.info("Discovering resources...")
|
|
364
444
|
data = source.discover()
|
|
365
|
-
print(
|
|
445
|
+
print(
|
|
446
|
+
json.dumps(
|
|
447
|
+
sanitize_for_logging(data, redactor),
|
|
448
|
+
indent=2,
|
|
449
|
+
)
|
|
450
|
+
)
|
|
366
451
|
|
|
367
452
|
elif args.command == "extract":
|
|
368
453
|
result = source.test_connection()
|
|
369
454
|
if result.get("status") == "FAILURE":
|
|
370
455
|
msg = result.get("message", "")
|
|
371
|
-
logger.error(
|
|
456
|
+
logger.error(
|
|
457
|
+
"Aborting: Connection test failed: %s",
|
|
458
|
+
_safe_text(msg, redactor),
|
|
459
|
+
)
|
|
372
460
|
sys.exit(1)
|
|
373
461
|
|
|
374
462
|
logger.info("Starting extraction...")
|
|
@@ -420,7 +508,7 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
|
|
|
420
508
|
if not warm_ok:
|
|
421
509
|
logger.warning(
|
|
422
510
|
"Dependency warm-up incomplete (workers will retry): %s",
|
|
423
|
-
warm_detail,
|
|
511
|
+
_safe_text(warm_detail, redactor),
|
|
424
512
|
)
|
|
425
513
|
|
|
426
514
|
worker_pool = DetectorWorkerPool(max_workers=pool_workers)
|
|
@@ -598,7 +686,7 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
|
|
|
598
686
|
await augmentation.augment(asset)
|
|
599
687
|
if augmentation.flush_requested:
|
|
600
688
|
augmentation.clear_flush_request()
|
|
601
|
-
await _flush_buffered_edges(source, sink)
|
|
689
|
+
await _flush_buffered_edges(source, sink, redactor)
|
|
602
690
|
|
|
603
691
|
if hasattr(sink, "update_asset_status"):
|
|
604
692
|
await sink.update_asset_status(asset_hash, "PROCESSING")
|
|
@@ -653,13 +741,15 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
|
|
|
653
741
|
# cannot mistake missing embeddings for a
|
|
654
742
|
# healthy completed scan.
|
|
655
743
|
chunks_ok = False
|
|
656
|
-
detail =
|
|
744
|
+
detail = _safe_text(
|
|
745
|
+
f"{asset_hash}: {chunk_exc}", redactor
|
|
746
|
+
)
|
|
657
747
|
chunk_errors.append(detail)
|
|
658
748
|
logger.error(
|
|
659
749
|
"Text-chunk emission exhausted retries"
|
|
660
750
|
" for asset %s: %s",
|
|
661
751
|
asset_hash,
|
|
662
|
-
chunk_exc,
|
|
752
|
+
_safe_text(chunk_exc, redactor),
|
|
663
753
|
)
|
|
664
754
|
|
|
665
755
|
f_total, f_by_sev, f_by_det = _compute_findings_counts(
|
|
@@ -714,10 +804,16 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
|
|
|
714
804
|
processed_count += 1
|
|
715
805
|
except Exception as exc:
|
|
716
806
|
error_count += 1
|
|
717
|
-
logger.error(
|
|
807
|
+
logger.error(
|
|
808
|
+
"Asset %s failed: %s",
|
|
809
|
+
asset_hash,
|
|
810
|
+
_safe_text(exc, redactor),
|
|
811
|
+
)
|
|
718
812
|
if hasattr(sink, "update_asset_status"):
|
|
719
813
|
try:
|
|
720
|
-
error_msg =
|
|
814
|
+
error_msg = _safe_text(
|
|
815
|
+
str(exc) or type(exc).__name__, redactor
|
|
816
|
+
)
|
|
721
817
|
await sink.update_asset_status(
|
|
722
818
|
asset_hash, "ERROR", error_message=error_msg
|
|
723
819
|
)
|
|
@@ -779,7 +875,7 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
|
|
|
779
875
|
"findings and are retried on the next run.",
|
|
780
876
|
degraded["label"],
|
|
781
877
|
degraded["cause"],
|
|
782
|
-
degraded["reason"],
|
|
878
|
+
_safe_text(degraded["reason"], redactor),
|
|
783
879
|
degraded["attempted_payloads"],
|
|
784
880
|
degraded["skipped_payloads"],
|
|
785
881
|
)
|
|
@@ -810,7 +906,7 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
|
|
|
810
906
|
# Relationships go *before* finish(): finish() finalizes the
|
|
811
907
|
# run and marks it COMPLETED, and edges that land after that
|
|
812
908
|
# belong to a run that already claimed to be done.
|
|
813
|
-
await _emit_relationships(source, sink)
|
|
909
|
+
await _emit_relationships(source, sink, redactor=redactor)
|
|
814
910
|
|
|
815
911
|
await sink.finish()
|
|
816
912
|
logger.info(
|
|
@@ -822,12 +918,12 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
|
|
|
822
918
|
if _is_timeout_error(extraction_error):
|
|
823
919
|
logger.warning(
|
|
824
920
|
"Source timed out during extraction, partial results flushed: %s",
|
|
825
|
-
extraction_error,
|
|
921
|
+
_safe_text(extraction_error, redactor),
|
|
826
922
|
)
|
|
827
923
|
# The assets this run did ingest keep their
|
|
828
924
|
# relationships. Dropping them here is what made a slow
|
|
829
925
|
# warehouse scan produce a graph with no lineage at all.
|
|
830
|
-
await _emit_relationships(source, sink, partial=True)
|
|
926
|
+
await _emit_relationships(source, sink, partial=True, redactor=redactor)
|
|
831
927
|
# A run that timed out mid-extraction did not visit the
|
|
832
928
|
# rest of the source, so absence from it proves nothing.
|
|
833
929
|
# Without this the timeout retires every asset the run
|
|
@@ -842,10 +938,17 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
|
|
|
842
938
|
return
|
|
843
939
|
if sink_started:
|
|
844
940
|
try:
|
|
845
|
-
|
|
941
|
+
# Redacted copy: sink.fail stores the message on
|
|
942
|
+
# the run record, which is readable beyond the
|
|
943
|
+
# operator who holds the credential.
|
|
944
|
+
await sink.fail(_safe_error_copy(extraction_error, redactor))
|
|
846
945
|
except Exception as sink_error:
|
|
847
946
|
logger.error(
|
|
848
|
-
"Failed to mark sink failure: %s",
|
|
947
|
+
"Failed to mark sink failure: %s",
|
|
948
|
+
_safe_text(sink_error, redactor),
|
|
949
|
+
)
|
|
950
|
+
_log_debug_traceback(
|
|
951
|
+
logger, "Sink failure traceback:", sink_error, redactor
|
|
849
952
|
)
|
|
850
953
|
raise
|
|
851
954
|
finally:
|
|
@@ -855,17 +958,19 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
|
|
|
855
958
|
worker_pool.shutdown(wait=True)
|
|
856
959
|
|
|
857
960
|
except Exception as e:
|
|
858
|
-
logger
|
|
961
|
+
_log_debug_traceback(logger, f"Traceback for {args.command} failure:", e, redactor)
|
|
859
962
|
if _is_timeout_error(e):
|
|
860
|
-
logger.warning("SCAN TIMED OUT (source unreachable): %s", e)
|
|
963
|
+
logger.warning("SCAN TIMED OUT (source unreachable): %s", _safe_text(e, redactor))
|
|
861
964
|
return
|
|
862
|
-
logger.error("SCAN FAILED: %s", e)
|
|
965
|
+
logger.error("SCAN FAILED: %s", _safe_text(e, redactor))
|
|
863
966
|
sys.exit(1)
|
|
864
967
|
finally:
|
|
865
968
|
source.cleanup()
|
|
866
969
|
except Exception as e:
|
|
867
|
-
|
|
868
|
-
|
|
970
|
+
# No recipe redactor here (failure happened before the recipe was
|
|
971
|
+
# usable): generic patterns still hide credential-shaped values.
|
|
972
|
+
_log_debug_traceback(logger, "Traceback for fatal error:", e, None)
|
|
973
|
+
logger.error("FATAL: %s", _safe_text(e, None))
|
|
869
974
|
sys.exit(1)
|
|
870
975
|
|
|
871
976
|
|
|
@@ -971,7 +1076,8 @@ def run_evaluate_file_command(args: argparse.Namespace) -> None:
|
|
|
971
1076
|
output["detector_errors"] = runner.detector_errors
|
|
972
1077
|
print(json.dumps(_sanitize_for_json(output), ensure_ascii=False))
|
|
973
1078
|
except Exception as e:
|
|
974
|
-
logger.error("File evaluation failed: %s", e,
|
|
1079
|
+
logger.error("File evaluation failed: %s", _safe_text(e, None))
|
|
1080
|
+
_log_debug_traceback(logger, "File evaluation traceback:", e, None)
|
|
975
1081
|
sys.exit(1)
|
|
976
1082
|
|
|
977
1083
|
|
|
@@ -1153,8 +1259,16 @@ def main() -> None:
|
|
|
1153
1259
|
try:
|
|
1154
1260
|
validate_input(recipe, source_type)
|
|
1155
1261
|
logger.info("Recipe is valid")
|
|
1262
|
+
except RecipeValidationError as e:
|
|
1263
|
+
# Already log-safe by construction (masked values hidden); the extra
|
|
1264
|
+
# pass only guards credential-shaped patterns in driver text.
|
|
1265
|
+
logger.error("%s", _safe_text(e, Redactor.from_recipe(recipe)))
|
|
1266
|
+
sys.exit(1)
|
|
1156
1267
|
except Exception as e:
|
|
1157
|
-
logger.error(
|
|
1268
|
+
logger.error(
|
|
1269
|
+
"Recipe validation failed: %s",
|
|
1270
|
+
_safe_text(e, Redactor.from_recipe(recipe)),
|
|
1271
|
+
)
|
|
1158
1272
|
sys.exit(1)
|
|
1159
1273
|
|
|
1160
1274
|
run_command(args, recipe)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Keep secret values out of everything a human or a log file will see.
|
|
2
|
+
|
|
3
|
+
Re-exported from :mod:`src.utils.redaction` (the canonical home, kept in
|
|
4
|
+
``utils`` so recipe validation can use it without importing the notebook
|
|
5
|
+
package, which would be a circular import). Import from either location;
|
|
6
|
+
new code outside the notebook runtime should prefer ``utils.redaction``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from ..utils.redaction import (
|
|
12
|
+
ENCODED_SECRET_MIN_LENGTH,
|
|
13
|
+
ENCRYPTED_CONFIG_PATHS,
|
|
14
|
+
MAX_LOG_ID_CHARS,
|
|
15
|
+
MIN_REDACTABLE_LENGTH,
|
|
16
|
+
PLACEHOLDER,
|
|
17
|
+
RedactingStream,
|
|
18
|
+
Redactor,
|
|
19
|
+
redact_generic,
|
|
20
|
+
redact_with_recipe,
|
|
21
|
+
sanitize_for_logging,
|
|
22
|
+
short_id_for_log,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"ENCODED_SECRET_MIN_LENGTH",
|
|
27
|
+
"ENCRYPTED_CONFIG_PATHS",
|
|
28
|
+
"MAX_LOG_ID_CHARS",
|
|
29
|
+
"MIN_REDACTABLE_LENGTH",
|
|
30
|
+
"PLACEHOLDER",
|
|
31
|
+
"RedactingStream",
|
|
32
|
+
"Redactor",
|
|
33
|
+
"redact_generic",
|
|
34
|
+
"redact_with_recipe",
|
|
35
|
+
"sanitize_for_logging",
|
|
36
|
+
"short_id_for_log",
|
|
37
|
+
]
|
|
@@ -394,7 +394,12 @@ class RestOutputSink:
|
|
|
394
394
|
if not self._runner_id:
|
|
395
395
|
return
|
|
396
396
|
|
|
397
|
-
|
|
397
|
+
# Defense in depth: the caller already redacts known recipe values,
|
|
398
|
+
# but the sink is the last point before the message is stored on the
|
|
399
|
+
# run record, so credential-shaped patterns are hidden here as well.
|
|
400
|
+
from ..utils.redaction import redact_generic
|
|
401
|
+
|
|
402
|
+
error_message = redact_generic(f"{type(error).__name__}: {error}")
|
|
398
403
|
try:
|
|
399
404
|
payload = UpdateRunnerStatusRequest(status="ERROR", error_message=error_message)
|
|
400
405
|
self._request_json(
|
|
@@ -405,8 +410,8 @@ class RestOutputSink:
|
|
|
405
410
|
except Exception as update_error:
|
|
406
411
|
logger.warning(
|
|
407
412
|
"Failed to update runner status to ERROR after failure %s: %s",
|
|
408
|
-
error,
|
|
409
|
-
update_error,
|
|
413
|
+
redact_generic(str(error)),
|
|
414
|
+
redact_generic(str(update_error)),
|
|
410
415
|
)
|
|
411
416
|
|
|
412
417
|
async def emit_edges(self, edges: list[IngestEdge]) -> dict[str, Any]:
|
|
@@ -451,11 +456,14 @@ class RestOutputSink:
|
|
|
451
456
|
# Edge emission is best-effort: log and continue — but a chunk
|
|
452
457
|
# the API rejected is lost, not dropped, so the run is
|
|
453
458
|
# downgraded rather than reporting an unqualified success.
|
|
459
|
+
# Redacted: these messages land on the run record.
|
|
460
|
+
from ..utils.redaction import redact_generic as _redact_generic
|
|
461
|
+
|
|
454
462
|
totals["lost"] += len(chunk)
|
|
455
|
-
message = f"{type(exc).__name__}: {exc}"
|
|
463
|
+
message = _redact_generic(f"{type(exc).__name__}: {exc}")
|
|
456
464
|
if message not in errors:
|
|
457
465
|
errors.append(message)
|
|
458
|
-
logger.warning("Failed to emit edges to graph: %s", exc)
|
|
466
|
+
logger.warning("Failed to emit edges to graph: %s", _redact_generic(str(exc)))
|
|
459
467
|
if errors:
|
|
460
468
|
totals["errors"] = errors
|
|
461
469
|
return totals
|
|
@@ -562,7 +570,9 @@ class RestOutputSink:
|
|
|
562
570
|
)
|
|
563
571
|
|
|
564
572
|
if response.status_code >= 400:
|
|
565
|
-
|
|
573
|
+
from ..utils.redaction import redact_generic as _redact_generic2
|
|
574
|
+
|
|
575
|
+
body_preview = _redact_generic2(response.text.strip()[:400])
|
|
566
576
|
raise RuntimeError(
|
|
567
577
|
f"REST output request failed ({method} {url}): "
|
|
568
578
|
f"{response.status_code} {response.reason} {body_preview}"
|
|
@@ -25,6 +25,7 @@ from ..models.generated_single_asset_scan_results import (
|
|
|
25
25
|
)
|
|
26
26
|
from ..sources.base import BaseSource
|
|
27
27
|
from ..utils.file_parser import TextExtractionCoverageError, resolve_mime_type
|
|
28
|
+
from ..utils.redaction import Redactor, redact_generic, short_id_for_log
|
|
28
29
|
from .content_provider import ContentProvider
|
|
29
30
|
from .detector_breaker import DetectorBreaker
|
|
30
31
|
from .text_artifact import TextArtifact
|
|
@@ -132,6 +133,10 @@ class DetectorPipeline:
|
|
|
132
133
|
self.detectors = detectors
|
|
133
134
|
self.source = source
|
|
134
135
|
self.runner_id = runner_id
|
|
136
|
+
# Built once: candidate ids logged per asset below can embed
|
|
137
|
+
# credential material (a reversible base64 asset hash over a secret
|
|
138
|
+
# bearing notebook id), so every id rendered to logs goes through it.
|
|
139
|
+
self._redactor = Redactor.from_recipe(getattr(source, "recipe", None))
|
|
135
140
|
self._worker_pool = worker_pool
|
|
136
141
|
self._detector_info: dict[int, _DetectorInfo] = {}
|
|
137
142
|
if content_provider is not None:
|
|
@@ -696,10 +701,16 @@ class DetectorPipeline:
|
|
|
696
701
|
continue
|
|
697
702
|
candidate_ids.append(value)
|
|
698
703
|
|
|
704
|
+
# Candidate ids are logged redacted and shortened: the second
|
|
705
|
+
# candidate is the asset hash, which is reversible base64 over the
|
|
706
|
+
# connector's raw id -- and a notebook that builds ids from secrets
|
|
707
|
+
# turns this line into a credential leak. The head still identifies
|
|
708
|
+
# the document, which is all debugging needs.
|
|
699
709
|
logger.info(
|
|
700
|
-
"_iter_text_content_pages(%s): trying
|
|
710
|
+
"_iter_text_content_pages(%s): trying %d candidate(s) %s",
|
|
701
711
|
asset.name,
|
|
702
|
-
candidate_ids,
|
|
712
|
+
len(candidate_ids),
|
|
713
|
+
[short_id_for_log(item, self._redactor) for item in candidate_ids],
|
|
703
714
|
)
|
|
704
715
|
|
|
705
716
|
for candidate_id in candidate_ids:
|
|
@@ -714,7 +725,14 @@ class DetectorPipeline:
|
|
|
714
725
|
# Text extraction failure must not discard independent link or
|
|
715
726
|
# binary detector results. The structured empty-text coverage
|
|
716
727
|
# signal still records that this asset was not embedded.
|
|
717
|
-
|
|
728
|
+
# Redacted like the candidates above: this message is stored
|
|
729
|
+
# on the run's scan stats.
|
|
730
|
+
safe_cause = redact_generic(self._redactor.redact(str(exc)))
|
|
731
|
+
message = (
|
|
732
|
+
f"Text extraction failed for "
|
|
733
|
+
f"{short_id_for_log(candidate_id, self._redactor)}: "
|
|
734
|
+
f"{safe_cause}"
|
|
735
|
+
)
|
|
718
736
|
logger.warning(message)
|
|
719
737
|
self._text_extraction_errors.setdefault(str(asset.hash), []).append(message)
|
|
720
738
|
self._text_extraction_statuses[str(asset.hash)] = TextExtractionStatus(
|
|
@@ -8,6 +8,7 @@ from collections.abc import AsyncGenerator, Mapping
|
|
|
8
8
|
|
|
9
9
|
from ..models.generated_single_asset_scan_results import DetectionResult, SingleAssetScanResults
|
|
10
10
|
from ..sources.base import BaseSource
|
|
11
|
+
from ..utils.redaction import Redactor, short_id_for_log
|
|
11
12
|
|
|
12
13
|
logger = logging.getLogger(__name__)
|
|
13
14
|
|
|
@@ -24,6 +25,10 @@ class ParsedContentProvider:
|
|
|
24
25
|
|
|
25
26
|
def __init__(self, source: BaseSource) -> None:
|
|
26
27
|
self._source = source
|
|
28
|
+
# asset_id here is often the reversible asset hash (see
|
|
29
|
+
# detector_pipeline._iter_text_content_pages), so it is never
|
|
30
|
+
# logged raw.
|
|
31
|
+
self._redactor = Redactor.from_recipe(getattr(source, "recipe", None))
|
|
27
32
|
|
|
28
33
|
async def fetch_text_pages(self, asset_id: str) -> AsyncGenerator[str, None]:
|
|
29
34
|
saw_text = False
|
|
@@ -43,19 +48,22 @@ class ParsedContentProvider:
|
|
|
43
48
|
if isinstance(pages_processed, set) and asset_id in pages_processed:
|
|
44
49
|
logger.info(
|
|
45
50
|
"fetch_text_pages(%s): source already processed, skipping fallback",
|
|
46
|
-
asset_id,
|
|
51
|
+
short_id_for_log(asset_id, self._redactor),
|
|
47
52
|
)
|
|
48
53
|
return
|
|
49
54
|
|
|
50
55
|
result = await self._source.fetch_content_bytes(asset_id)
|
|
51
56
|
if result is None:
|
|
52
|
-
logger.info(
|
|
57
|
+
logger.info(
|
|
58
|
+
"fetch_text_pages(%s): fetch_content_bytes returned None",
|
|
59
|
+
short_id_for_log(asset_id, self._redactor),
|
|
60
|
+
)
|
|
53
61
|
return
|
|
54
62
|
|
|
55
63
|
raw_bytes, mime = result
|
|
56
64
|
logger.info(
|
|
57
65
|
"fetch_text_pages(%s): fallback iter_asset_pages path (%s, %d bytes)",
|
|
58
|
-
asset_id,
|
|
66
|
+
short_id_for_log(asset_id, self._redactor),
|
|
59
67
|
mime,
|
|
60
68
|
len(raw_bytes),
|
|
61
69
|
)
|