classifyre-cli 0.7.0__tar.gz → 0.7.1__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.7.1/.turbo/turbo-build.log +3 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/PKG-INFO +1 -1
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/package.json +1 -1
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/pyproject.toml +1 -1
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/main.py +5 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/outputs/rest.py +7 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/base.py +30 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/databricks/source.py +56 -12
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/hive/source.py +4 -1
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/mysql/source.py +1 -1
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/neo4j/source.py +3 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/tabular_base.py +15 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/sql_lineage.py +11 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_databricks_source.py +90 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_hive_source.py +23 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_mysql_source.py +104 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_neo4j_source.py +26 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_sql_lineage.py +42 -1
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/uv.lock +64 -64
- classifyre_cli-0.7.0/.turbo/turbo-build.log +0 -3
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/.gitignore +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/.python-version +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/README.md +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/main.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/scripts/generate_models.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/scripts/generate_runtime_packages.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/scripts/generate_sdk_completions.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/augmentation/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/augmentation/contract.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/augmentation/preview.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/augmentation/runner.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/augmentation/sdk.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/augmentation/session.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/config.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/base.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/broken_links/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/broken_links/detector.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/config.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/content/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/detector.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/extractor.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_base.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_custom_detector.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_decision.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_factory.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_gliner2.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_image_classification.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_llm.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_object_detection.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_provider_call.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_regex.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_tag.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_text_classification.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom/trainer.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom_detector/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom_detector/contract.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom_detector/files.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom_detector/preview.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom_detector/results.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom_detector/runner.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom_detector/sdk.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/custom_detector/session.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/dependencies.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/engine_version.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/errors.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/pii/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/pii/detector.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/secrets/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/secrets/detector.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/threat/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/threat/code_security_detector.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/detectors/threat/yara_detector.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/file_evaluation/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/file_evaluation/asset_fixture.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/file_evaluation/runner.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/graph/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/graph/edges.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/models/generated_detectors.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/models/generated_input.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/models/generated_single_asset_scan_results.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/child.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/cli.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/cohort.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/contract.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/execute.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/files.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/groups.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/outputs.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/packages.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/protocol.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/redact.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/scaffold.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/sdk.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/notebook/serialize.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/outputs/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/outputs/base.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/outputs/console.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/outputs/factory.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/outputs/file.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/asset_payload_server.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/content_provider.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/detector_breaker.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/detector_pipeline.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/finding_context.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/media_artifact.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/parsed_content_provider.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/payload_window.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/scan_cache.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/text_artifact.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/pipeline/worker_pool.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/asset_metadata.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/atlassian_common.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/azure_blob_storage/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/azure_blob_storage/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/confluence/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/confluence/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/custom/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/custom/env.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/custom/runner.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/custom/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/databricks/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/delta_lake/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/delta_lake/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/dependencies.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/dremio/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/dremio/client.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/dremio/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/dropbox/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/dropbox/auth.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/dropbox/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/elasticsearch/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/elasticsearch/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/email/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/email/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/git/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/git/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/google_cloud_storage/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/google_cloud_storage/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/google_workspace/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/google_workspace/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/hive/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/hugging_face/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/hugging_face/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/iceberg/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/iceberg/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/jira/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/jira/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/kafka/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/kafka/rest.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/kafka/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/lakehouse_base.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/local_folder/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/local_folder/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/meilisearch/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/meilisearch/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/microsoft_365/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/microsoft_365/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/mongodb/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/mongodb/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/mssql/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/mssql/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/mysql/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/neo4j/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/notion/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/notion/client.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/notion/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/object_storage/base.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/opensearch/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/opensearch/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/oracle/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/oracle/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/postgresql/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/postgresql/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/powerbi/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/powerbi/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/recipe_normalizer.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/reddit/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/reddit/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/s3_client.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/s3_compatible_storage/README.md +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/s3_compatible_storage/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/s3_compatible_storage/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/sandbox/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/sandbox/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/search_engine_base.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/servicedesk/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/servicedesk/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/slack/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/slack/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/snowflake/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/snowflake/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/sqlite/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/sqlite/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/tableau/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/tableau/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/tabular_utils.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/wordpress/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/wordpress/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/youtube/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/sources/youtube/source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/telemetry.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/archive_extraction.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/content_extraction.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/dependency_groups.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/embedded_files.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/external_video.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/file_metadata.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/file_parser.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/file_to_images.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/hashing.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/legacy_office.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/payload.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/range_reader.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/redaction.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/resources.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/sampling_cursor.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/sampling_frontier.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/source_files.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/torch_runtime.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/transcription.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/urn.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/uv_sync.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/validation.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/src/utils/video_processing.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/_lakehouse_fakes.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/conftest.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/broken_links/test_broken_links_detector.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/conftest.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/content/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/custom/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/custom/conftest.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/custom/test_decision_runner.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/custom/test_gliner25_runner.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/custom/test_gliner2_classification.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/custom/test_invoice_extraction.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/custom/test_llm_runner.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/custom/test_pipeline_integration.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/custom/test_regex_runner.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/custom/test_runner_hardening.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/custom/test_transformer_runners.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/pii/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/pii/conftest.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/pii/sample_invoice.pdf +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/pii/test_pii_chunking.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/pii/test_pii_detector.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/pii/test_pii_detector_extended.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/pii/test_pii_regional_entities.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/pii/test_pii_severity.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/secrets/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/secrets/test_secrets_detector.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/secrets/test_secrets_detector_extended.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/test_base_detector.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/test_custom_detector_examples_runtime.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/test_custom_detector_session.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/test_detector_catalog_commercial.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/test_detector_pipeline_types.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/test_detector_schema_examples.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/test_detector_types.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/test_phase2_detectors.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/test_registry.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/test_tag_runner.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/threat/__init__.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/threat/test_code_security_detector.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/detectors/threat/test_yara_detector.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/integration/test_wordpress_broken_links_detector.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/integration/test_wordpress_links_assets.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/pipeline/test_detector_breaker.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/pipeline/test_detector_outcomes.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/pipeline/test_detector_pipeline.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/pipeline/test_worker_pool.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_arrow_ipc_payloads.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_assets_metadata_catalog.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_augmentation_contract.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_augmentation_edges.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_augmentation_patch.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_augmentation_scan_cache.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_augmentation_sdk.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_augmentation_session.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_augmentation_tags_end_to_end.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_automatic_new_first.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_azure_blob_storage_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_base_source_attachment.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_base_source_sampling.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_byte_valued_fields.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_config.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_confluence_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_custom_detector_end_to_end.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_custom_detector_evaluation.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_custom_detector_notebook.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_custom_detector_templates.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_custom_extractor.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_custom_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_custom_source_relationships.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_custom_source_tags_end_to_end.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_delta_lake_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_dependency_groups.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_detector_scope.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_dremio_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_dropbox_auth_command.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_dropbox_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_elasticsearch_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_email_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_file_evaluation_runner.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_finding_context.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_git_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_google_cloud_storage_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_google_workspace_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_graph_edges.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_hashing.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_hugging_face_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_iceberg_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_jira_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_kafka_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_lineage_loss_reporting.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_local_folder_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_main.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_media_embedding.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_meilisearch_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_microsoft_365_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_mongodb_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_mssql_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_asset_entity.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_asset_extract.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_asset_tags.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_cohort.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_execute.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_files.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_groups.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_log_levels.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_packages.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_query_assets.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_sdk_exports.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_serialize.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notebook_templates.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_notion_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_object_storage_download_efficiency.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_opensearch_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_oracle_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_outputs.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_payload_window.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_payload_window_parquet.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_postgresql_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_powerbi_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_range_reading.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_recipe_normalizer.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_reddit_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_rest_backpressure.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_runtime_packages.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_s3_compatible_storage_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_sampling_automatic.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_sampling_cursor.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_sampling_frontier.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_sandbox_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_scan_cache.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_sdk_completions.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_secret_redaction.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_semantic_declarations.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_servicedesk_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_slack_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_snowflake_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_source_dependency_groups.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_source_strategy_conformance.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_sqlite_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_tableau_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_tabular_automatic_sampling.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_tabular_lineage.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_tabular_utils.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_torch_runtime.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_urn.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_uv_sync.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_view_dependencies_are_not_foreign_keys.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_wordpress_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_youtube_source.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/test_youtube_source_integration.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/utils/test_archive_extraction.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/utils/test_content_extraction.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/utils/test_embedded_files.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/utils/test_file_metadata.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/utils/test_file_parser.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/utils/test_file_to_images.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/utils/test_legacy_office.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/utils/test_payload.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/utils/test_transcription.py +0 -0
- {classifyre_cli-0.7.0 → classifyre_cli-0.7.1}/tests/utils/test_video_processing.py +0 -0
|
@@ -971,6 +971,11 @@ async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) ->
|
|
|
971
971
|
source.partial_coverage_reason or "no reason given",
|
|
972
972
|
)
|
|
973
973
|
|
|
974
|
+
if getattr(source, "listing_complete", False) and hasattr(
|
|
975
|
+
sink, "set_listing_complete"
|
|
976
|
+
):
|
|
977
|
+
sink.set_listing_complete()
|
|
978
|
+
|
|
974
979
|
# Relationships go *before* finish(): finish() finalizes the
|
|
975
980
|
# run and marks it COMPLETED, and edges that land after that
|
|
976
981
|
# belong to a run that already claimed to be done.
|
|
@@ -203,6 +203,7 @@ class FinalizeIngestRunRequest(BaseModel):
|
|
|
203
203
|
# "This run covered a slice of the source." Suppresses retirement: absence
|
|
204
204
|
# from a cohort run is not evidence that an asset is gone.
|
|
205
205
|
partial_coverage: bool | None = Field(None, serialization_alias="partialCoverage")
|
|
206
|
+
discovery_complete: bool | None = Field(None, serialization_alias="discoveryComplete")
|
|
206
207
|
# Per ctx.cohort(): keys each band visited, and the weights it ran with.
|
|
207
208
|
cohort_stats: dict[str, Any] | None = Field(None, serialization_alias="cohortStats")
|
|
208
209
|
# Detectors the circuit breaker disabled mid-run (scan-cache identity
|
|
@@ -261,6 +262,7 @@ class RestOutputSink:
|
|
|
261
262
|
self._scan_cache_savings: tuple[int, int] | None = None
|
|
262
263
|
self._partial_coverage: bool = False
|
|
263
264
|
self._partial_coverage_reason: str = ""
|
|
265
|
+
self._listing_complete: bool = False
|
|
264
266
|
self._detectors_degraded: list[str] = []
|
|
265
267
|
self._cohort_stats: dict[str, Any] | None = None
|
|
266
268
|
self.relationship_report = RelationshipReport()
|
|
@@ -279,6 +281,10 @@ class RestOutputSink:
|
|
|
279
281
|
if reason:
|
|
280
282
|
self._partial_coverage_reason = str(reason)
|
|
281
283
|
|
|
284
|
+
def set_listing_complete(self) -> None:
|
|
285
|
+
"""Mark that discovery named every object of the configured scope."""
|
|
286
|
+
self._listing_complete = True
|
|
287
|
+
|
|
282
288
|
def set_detectors_degraded(self, keys: list[str]) -> None:
|
|
283
289
|
"""Record detectors the breaker disabled mid-run, for the next run's sweep."""
|
|
284
290
|
seen: set[str] = set(self._detectors_degraded)
|
|
@@ -415,6 +421,7 @@ class RestOutputSink:
|
|
|
415
421
|
relationships_dropped=lineage.dropped,
|
|
416
422
|
relationship_errors=lineage.errors or None,
|
|
417
423
|
partial_coverage=True if self._partial_coverage else None,
|
|
424
|
+
discovery_complete=True if self._listing_complete else None,
|
|
418
425
|
cohort_stats=self._cohort_stats,
|
|
419
426
|
detectors_degraded=self._detectors_degraded or None,
|
|
420
427
|
)
|
|
@@ -89,6 +89,8 @@ class BaseSource(ABC):
|
|
|
89
89
|
self._next_sampling_cursor: dict[str, Any] | None = None
|
|
90
90
|
self._partial_coverage: bool = False
|
|
91
91
|
self._partial_coverage_reason: str = ""
|
|
92
|
+
self._listing_complete: bool = False
|
|
93
|
+
self._listing_errors: int = 0
|
|
92
94
|
self._sampling_cursor_lock = threading.Lock()
|
|
93
95
|
self._apply_initial_sampling_override(normalized_recipe)
|
|
94
96
|
recipe.clear()
|
|
@@ -191,6 +193,34 @@ class BaseSource(ABC):
|
|
|
191
193
|
if reason and not self._partial_coverage_reason:
|
|
192
194
|
self._partial_coverage_reason = str(reason)
|
|
193
195
|
|
|
196
|
+
def note_listing_error(self) -> None:
|
|
197
|
+
"""Record that part of the catalog could not be listed this run.
|
|
198
|
+
|
|
199
|
+
A listing that skipped a schema, a folder or a label cannot say that
|
|
200
|
+
what it did not list is gone, so it forfeits :meth:`declare_listing_complete`.
|
|
201
|
+
"""
|
|
202
|
+
self._listing_errors = getattr(self, "_listing_errors", 0) + 1
|
|
203
|
+
|
|
204
|
+
def declare_listing_complete(self) -> None:
|
|
205
|
+
"""Say that discovery named every object of the configured scope.
|
|
206
|
+
|
|
207
|
+
Sampling decides how much of each asset is read, not which assets
|
|
208
|
+
exist. A catalog connector (tables, node labels) lists its whole scope
|
|
209
|
+
on every run whatever the strategy, so an asset missing from that list
|
|
210
|
+
is gone and can be retired -- without this, only strategy=ALL ever
|
|
211
|
+
retired anything and every other source kept deleted tables, moved
|
|
212
|
+
databases and renamed labels as "unchanged" forever.
|
|
213
|
+
|
|
214
|
+
Ignored when any part of the listing failed or the run declared partial
|
|
215
|
+
coverage.
|
|
216
|
+
"""
|
|
217
|
+
if getattr(self, "_listing_errors", 0) == 0:
|
|
218
|
+
self._listing_complete = True
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def listing_complete(self) -> bool:
|
|
222
|
+
return bool(getattr(self, "_listing_complete", False)) and not self.partial_coverage
|
|
223
|
+
|
|
194
224
|
@property
|
|
195
225
|
def partial_coverage(self) -> bool:
|
|
196
226
|
return bool(getattr(self, "_partial_coverage", False))
|
|
@@ -109,6 +109,9 @@ class DatabricksSource(BaseTabularSource):
|
|
|
109
109
|
self._validate_auth_configuration()
|
|
110
110
|
|
|
111
111
|
self.session = requests.Session()
|
|
112
|
+
# table_key -> upstream table -> ids of the notebooks Unity Catalog saw
|
|
113
|
+
# move data between the two. Filled by _lineage_refs_for_table.
|
|
114
|
+
self._lineage_notebook_ids: dict[tuple[str, ...], dict[tuple[str, str, str], set[str]]] = {}
|
|
112
115
|
self._access_token: str | None = None
|
|
113
116
|
self._access_token_expiry: datetime | None = None
|
|
114
117
|
|
|
@@ -409,9 +412,13 @@ class DatabricksSource(BaseTabularSource):
|
|
|
409
412
|
without_scheme = raw.split("://", 1)[-1]
|
|
410
413
|
return without_scheme.strip("/").split("/", 1)[0]
|
|
411
414
|
|
|
412
|
-
def
|
|
415
|
+
def _available_column_types(self, table_ref: TableRef) -> dict[str, str]:
|
|
416
|
+
# ``system.information_schema`` sees every catalog from one connection;
|
|
417
|
+
# the default query reads only the connection's current catalog and a
|
|
418
|
+
# parameter style Databricks does not take, so types used to come back
|
|
419
|
+
# empty for every column.
|
|
413
420
|
query = (
|
|
414
|
-
"SELECT column_name "
|
|
421
|
+
"SELECT column_name, COALESCE(full_data_type, data_type) "
|
|
415
422
|
"FROM system.information_schema.columns "
|
|
416
423
|
f"WHERE table_catalog = {_quote_literal(table_ref.database)} "
|
|
417
424
|
f"AND table_schema = {_quote_literal(table_ref.schema or '')} "
|
|
@@ -421,17 +428,17 @@ class DatabricksSource(BaseTabularSource):
|
|
|
421
428
|
conn = self._get_cached_connection(table_ref.database)
|
|
422
429
|
with conn.cursor() as cursor:
|
|
423
430
|
cursor.execute(query)
|
|
424
|
-
columns:
|
|
431
|
+
columns: dict[str, str] = {}
|
|
425
432
|
for row in cursor.fetchall():
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
candidate = None
|
|
431
|
-
if isinstance(candidate, str):
|
|
432
|
-
columns.append(candidate)
|
|
433
|
+
name = row[0] if len(row) > 0 else None
|
|
434
|
+
if isinstance(name, str):
|
|
435
|
+
data_type = row[1] if len(row) > 1 else None
|
|
436
|
+
columns[name] = str(data_type) if data_type is not None else ""
|
|
433
437
|
return columns
|
|
434
438
|
|
|
439
|
+
def _available_columns(self, table_ref: TableRef) -> list[str]:
|
|
440
|
+
return list(self._available_column_types(table_ref))
|
|
441
|
+
|
|
435
442
|
# ── Databricks-specific cell serialization ───────────────────────────
|
|
436
443
|
|
|
437
444
|
def _serialize_cell(self, value: Any) -> str:
|
|
@@ -598,6 +605,7 @@ class DatabricksSource(BaseTabularSource):
|
|
|
598
605
|
try:
|
|
599
606
|
schemas = self._list_schemas_for_catalog(database)
|
|
600
607
|
except Exception as exc:
|
|
608
|
+
self.note_listing_error()
|
|
601
609
|
logger.warning("Skipping catalog %s due to schema listing error: %s", database, exc)
|
|
602
610
|
return tables
|
|
603
611
|
for schema in schemas:
|
|
@@ -606,6 +614,7 @@ class DatabricksSource(BaseTabularSource):
|
|
|
606
614
|
try:
|
|
607
615
|
tables.extend(self._list_tables_for_schema(database, schema))
|
|
608
616
|
except Exception as exc:
|
|
617
|
+
self.note_listing_error()
|
|
609
618
|
logger.warning(
|
|
610
619
|
"Skipping schema %s.%s due to table listing error: %s",
|
|
611
620
|
database,
|
|
@@ -653,6 +662,7 @@ class DatabricksSource(BaseTabularSource):
|
|
|
653
662
|
},
|
|
654
663
|
)
|
|
655
664
|
refs: set[tuple[str, str, str]] = set()
|
|
665
|
+
notebooks: dict[tuple[str, str, str], set[str]] = {}
|
|
656
666
|
for key in ("upstreams", "upstream_tables"):
|
|
657
667
|
entries = response.get(key)
|
|
658
668
|
if isinstance(entries, list):
|
|
@@ -661,6 +671,12 @@ class DatabricksSource(BaseTabularSource):
|
|
|
661
671
|
parsed = self._lineage_table_ref_from_payload(entry)
|
|
662
672
|
if parsed:
|
|
663
673
|
refs.add(parsed)
|
|
674
|
+
for info in entry.get("notebookInfos") or []:
|
|
675
|
+
if isinstance(info, dict) and info.get("notebook_id") is not None:
|
|
676
|
+
notebooks.setdefault(parsed, set()).add(
|
|
677
|
+
str(info["notebook_id"])
|
|
678
|
+
)
|
|
679
|
+
self._lineage_notebook_ids[table_ref.table_key] = notebooks
|
|
664
680
|
return refs
|
|
665
681
|
|
|
666
682
|
def _emit_table_lineage(
|
|
@@ -668,6 +684,7 @@ class DatabricksSource(BaseTabularSource):
|
|
|
668
684
|
table_ref: TableRef,
|
|
669
685
|
upstream_refs: set[tuple[str, str, str]],
|
|
670
686
|
table_hash_by_key: dict[tuple[str, ...], str],
|
|
687
|
+
notebook_hash_by_id: dict[str, str] | None = None,
|
|
671
688
|
) -> None:
|
|
672
689
|
"""Turn Unity Catalog's lineage into typed edges.
|
|
673
690
|
|
|
@@ -675,6 +692,9 @@ class DatabricksSource(BaseTabularSource):
|
|
|
675
692
|
question directly, so nothing here is parsed out of SQL — these edges
|
|
676
693
|
are as trustworthy as lineage gets, and are marked SYSTEM_CATALOG.
|
|
677
694
|
|
|
695
|
+
When Unity Catalog saw a notebook run the statement, the edge carries it
|
|
696
|
+
as ``via`` -- if that notebook is one of this scan's assets.
|
|
697
|
+
|
|
678
698
|
The upstreams that are *not* part of this scan are the important half.
|
|
679
699
|
They used to be dropped on the floor (``if target in table_hash_by_key``),
|
|
680
700
|
which meant a table fed by a catalog outside the configured scope looked
|
|
@@ -686,6 +706,7 @@ class DatabricksSource(BaseTabularSource):
|
|
|
686
706
|
return
|
|
687
707
|
|
|
688
708
|
column_mappings = self._column_mappings_for_table(table_ref)
|
|
709
|
+
notebook_ids = self._lineage_notebook_ids.get(table_ref.table_key, {})
|
|
689
710
|
for upstream in sorted(upstream_refs):
|
|
690
711
|
upstream_hash = table_hash_by_key.get(upstream)
|
|
691
712
|
upstream_ref = (
|
|
@@ -693,12 +714,21 @@ class DatabricksSource(BaseTabularSource):
|
|
|
693
714
|
)
|
|
694
715
|
if upstream_ref is None:
|
|
695
716
|
continue
|
|
717
|
+
via_hash = next(
|
|
718
|
+
(
|
|
719
|
+
notebook_hash_by_id[notebook_id]
|
|
720
|
+
for notebook_id in sorted(notebook_ids.get(upstream, ()))
|
|
721
|
+
if notebook_id in (notebook_hash_by_id or {})
|
|
722
|
+
),
|
|
723
|
+
None,
|
|
724
|
+
)
|
|
696
725
|
self.add_edge(
|
|
697
726
|
flow(
|
|
698
727
|
upstream=upstream_ref,
|
|
699
728
|
downstream=Ref.asset(downstream_hash),
|
|
700
729
|
type=FlowType.TRANSFORM,
|
|
701
730
|
fields=column_mappings.get(upstream, []),
|
|
731
|
+
via=Ref.asset(via_hash) if via_hash else None,
|
|
702
732
|
method=Method.SYSTEM_CATALOG,
|
|
703
733
|
)
|
|
704
734
|
)
|
|
@@ -786,6 +816,7 @@ class DatabricksSource(BaseTabularSource):
|
|
|
786
816
|
"get", "/api/2.0/workspace/list", params={"path": path}
|
|
787
817
|
)
|
|
788
818
|
except Exception as exc:
|
|
819
|
+
self.note_listing_error()
|
|
789
820
|
logger.warning("Skipping workspace path %s due to listing error: %s", path, exc)
|
|
790
821
|
continue
|
|
791
822
|
objects = payload.get("objects")
|
|
@@ -865,6 +896,7 @@ class DatabricksSource(BaseTabularSource):
|
|
|
865
896
|
try:
|
|
866
897
|
payload = self._request_json("get", "/api/2.0/pipelines", params=params)
|
|
867
898
|
except Exception as exc:
|
|
899
|
+
self.note_listing_error()
|
|
868
900
|
logger.warning("Could not list Databricks pipelines: %s", exc)
|
|
869
901
|
break
|
|
870
902
|
values: list[dict[str, Any]] = []
|
|
@@ -935,6 +967,15 @@ class DatabricksSource(BaseTabularSource):
|
|
|
935
967
|
t.table_key: self.generate_hash_id(t.raw_id) for t in tables
|
|
936
968
|
}
|
|
937
969
|
|
|
970
|
+
# Listed before the tables so a lineage edge can name the notebook that
|
|
971
|
+
# produced it; the notebook assets are still emitted after the tables.
|
|
972
|
+
notebooks = list(self._iter_notebooks())
|
|
973
|
+
notebook_hash_by_id = {
|
|
974
|
+
nb.object_id: self.generate_hash_id(f"notebook_#_{nb.path}")
|
|
975
|
+
for nb in notebooks
|
|
976
|
+
if nb.object_id
|
|
977
|
+
}
|
|
978
|
+
|
|
938
979
|
include_lineage = self._extraction_options().include_table_lineage
|
|
939
980
|
if include_lineage and tables:
|
|
940
981
|
logger.info("Fetching table lineage for %d table(s)...", len(tables))
|
|
@@ -957,7 +998,9 @@ class DatabricksSource(BaseTabularSource):
|
|
|
957
998
|
for target in sorted(upstream_refs)
|
|
958
999
|
if target in table_hash_by_key
|
|
959
1000
|
]
|
|
960
|
-
self._emit_table_lineage(
|
|
1001
|
+
self._emit_table_lineage(
|
|
1002
|
+
table_ref, upstream_refs, table_hash_by_key, notebook_hash_by_id
|
|
1003
|
+
)
|
|
961
1004
|
except Exception as exc:
|
|
962
1005
|
logger.warning(
|
|
963
1006
|
"Could not resolve Databricks lineage for %s: %s", table_label, exc
|
|
@@ -974,7 +1017,7 @@ class DatabricksSource(BaseTabularSource):
|
|
|
974
1017
|
|
|
975
1018
|
# Notebooks
|
|
976
1019
|
notebook_count = 0
|
|
977
|
-
for notebook in
|
|
1020
|
+
for notebook in notebooks:
|
|
978
1021
|
if self._aborted:
|
|
979
1022
|
break
|
|
980
1023
|
batch.append(self._notebook_to_asset(notebook))
|
|
@@ -997,6 +1040,7 @@ class DatabricksSource(BaseTabularSource):
|
|
|
997
1040
|
batch = []
|
|
998
1041
|
if pipeline_count:
|
|
999
1042
|
logger.info("Discovered %d pipeline(s)", pipeline_count)
|
|
1043
|
+
self._declare_listing_if_whole()
|
|
1000
1044
|
|
|
1001
1045
|
if batch:
|
|
1002
1046
|
yield batch
|
|
@@ -293,7 +293,10 @@ class HiveSource(BaseTabularSource):
|
|
|
293
293
|
upstreams = [
|
|
294
294
|
key
|
|
295
295
|
for key in upstream_tables_from_sql(
|
|
296
|
-
definition,
|
|
296
|
+
definition,
|
|
297
|
+
dialect="hive",
|
|
298
|
+
default_database=view.database,
|
|
299
|
+
two_level=True,
|
|
297
300
|
)
|
|
298
301
|
if key != view.table_key and key in scoped
|
|
299
302
|
]
|
|
@@ -322,7 +322,7 @@ class MySQLSource(BaseTabularSource):
|
|
|
322
322
|
upstreams = [
|
|
323
323
|
key
|
|
324
324
|
for key in upstream_tables_from_sql(
|
|
325
|
-
definition, dialect="mysql", default_database=database
|
|
325
|
+
definition, dialect="mysql", default_database=database, two_level=True
|
|
326
326
|
)
|
|
327
327
|
if key != view_key
|
|
328
328
|
]
|
|
@@ -325,6 +325,9 @@ class Neo4jSource(BaseSource):
|
|
|
325
325
|
yield batch
|
|
326
326
|
batch = []
|
|
327
327
|
|
|
328
|
+
if not self._aborted and not self._scope_options().node_limit_per_label:
|
|
329
|
+
self.declare_listing_complete()
|
|
330
|
+
|
|
328
331
|
if batch:
|
|
329
332
|
logger.info("Emitting final batch of %d asset(s)", len(batch))
|
|
330
333
|
yield batch
|
|
@@ -319,9 +319,23 @@ class BaseTabularSource(BaseSource):
|
|
|
319
319
|
try:
|
|
320
320
|
tables.extend(self._list_tables_for_database(database))
|
|
321
321
|
except Exception as exc:
|
|
322
|
+
self.note_listing_error()
|
|
322
323
|
logger.warning("Skipping database %s due to listing error: %s", database, exc)
|
|
323
324
|
return tables
|
|
324
325
|
|
|
326
|
+
def _listing_is_limited(self) -> bool:
|
|
327
|
+
"""Whether a configured cap can have cut the table list short."""
|
|
328
|
+
scope_options = getattr(self, "_scope_options", None)
|
|
329
|
+
scope = scope_options() if callable(scope_options) else None
|
|
330
|
+
return bool(
|
|
331
|
+
getattr(scope, "table_limit", None) or getattr(scope, "table_limit_per_schema", None)
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
def _declare_listing_if_whole(self) -> None:
|
|
335
|
+
"""Offer this run's table list as the whole scope, when it plausibly is."""
|
|
336
|
+
if not self._aborted and not self._listing_is_limited():
|
|
337
|
+
self.declare_listing_complete()
|
|
338
|
+
|
|
325
339
|
# ── Primary keys ─────────────────────────────────────────────────────
|
|
326
340
|
|
|
327
341
|
def _get_primary_key_columns(self, table_ref: TableRef) -> list[str]:
|
|
@@ -1097,6 +1111,7 @@ class BaseTabularSource(BaseSource):
|
|
|
1097
1111
|
table_hash_by_key: dict[tuple[str, ...], str] = {
|
|
1098
1112
|
t.table_key: self.generate_hash_id(t.raw_id) for t in tables
|
|
1099
1113
|
}
|
|
1114
|
+
self._declare_listing_if_whole()
|
|
1100
1115
|
fk_links = self._collect_foreign_key_links(tables)
|
|
1101
1116
|
self._emit_relationship_edges(tables, table_hash_by_key, fk_links)
|
|
1102
1117
|
|
|
@@ -444,6 +444,7 @@ def upstream_tables_from_sql(
|
|
|
444
444
|
dialect: str | None = None,
|
|
445
445
|
default_database: str | None = None,
|
|
446
446
|
default_schema: str | None = None,
|
|
447
|
+
two_level: bool = False,
|
|
447
448
|
) -> list[tuple[str, ...]]:
|
|
448
449
|
"""Table keys a statement reads from, for catalogs with no dependency view.
|
|
449
450
|
|
|
@@ -453,6 +454,11 @@ def upstream_tables_from_sql(
|
|
|
453
454
|
the wrong default schema points at nothing — so callers should prefer the
|
|
454
455
|
catalog wherever it exists and treat these edges as SQL_PARSED.
|
|
455
456
|
|
|
457
|
+
``two_level`` is for engines whose tables are named ``database.table`` (MySQL,
|
|
458
|
+
Hive). sqlglot files the first part under ``db``, so without it a qualified
|
|
459
|
+
name came back as ``(default_database, database, table)`` -- a key no asset
|
|
460
|
+
has, which turned every such view's edges into unresolved ones.
|
|
461
|
+
|
|
456
462
|
CTE names are excluded: a ``WITH`` alias is not a table, and emitting it
|
|
457
463
|
would create lineage to an object that does not exist.
|
|
458
464
|
"""
|
|
@@ -481,6 +487,11 @@ def upstream_tables_from_sql(
|
|
|
481
487
|
name = table.name
|
|
482
488
|
if not name or name.lower() in cte_names:
|
|
483
489
|
continue
|
|
490
|
+
if two_level:
|
|
491
|
+
namespace = table.text("db") or default_database
|
|
492
|
+
if namespace:
|
|
493
|
+
keys.add((namespace, name))
|
|
494
|
+
continue
|
|
484
495
|
schema = table.text("db") or default_schema
|
|
485
496
|
database = table.text("catalog") or default_database
|
|
486
497
|
if database and schema:
|
|
@@ -415,3 +415,93 @@ async def test_databricks_fetch_content_pages_batches_for_all_strategy(
|
|
|
415
415
|
assert len(pages) == 12
|
|
416
416
|
assert "item1" in pages[0]
|
|
417
417
|
assert "item12" in pages[11]
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def test_databricks_column_types_come_from_the_system_catalog(
|
|
421
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
422
|
+
) -> None:
|
|
423
|
+
source = DatabricksSource(_pat_recipe())
|
|
424
|
+
issued: list[str] = []
|
|
425
|
+
|
|
426
|
+
class _TypesCursor(_DummyCursor):
|
|
427
|
+
def execute(self, query: str, _params: Any = None) -> None:
|
|
428
|
+
issued.append(query)
|
|
429
|
+
self._rows = [("order_id", "int"), ("line_total", "decimal(21,2)"), ("note", None)]
|
|
430
|
+
|
|
431
|
+
class _TypesConnection(_DummyConnection):
|
|
432
|
+
def cursor(self) -> _TypesCursor:
|
|
433
|
+
return _TypesCursor()
|
|
434
|
+
|
|
435
|
+
monkeypatch.setattr(source, "_get_cached_connection", lambda _db: _TypesConnection())
|
|
436
|
+
ref = TableRef(database="main", schema="sales", table="order_lines", object_type="TABLE")
|
|
437
|
+
|
|
438
|
+
assert source._available_column_types(ref) == {
|
|
439
|
+
"order_id": "int",
|
|
440
|
+
"line_total": "decimal(21,2)",
|
|
441
|
+
"note": "",
|
|
442
|
+
}
|
|
443
|
+
assert source._available_columns(ref) == ["order_id", "line_total", "note"]
|
|
444
|
+
assert "system.information_schema.columns" in issued[0]
|
|
445
|
+
assert "table_catalog = 'main'" in issued[0]
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def test_databricks_lineage_edge_names_the_notebook_that_wrote_it(
|
|
449
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
450
|
+
) -> None:
|
|
451
|
+
source = DatabricksSource(_pat_recipe())
|
|
452
|
+
downstream = TableRef(database="main", schema="mart", table="daily", object_type="TABLE")
|
|
453
|
+
monkeypatch.setattr(
|
|
454
|
+
source,
|
|
455
|
+
"_request_json",
|
|
456
|
+
lambda *_a, **_k: {
|
|
457
|
+
"upstreams": [
|
|
458
|
+
{
|
|
459
|
+
"tableInfo": {
|
|
460
|
+
"catalog_name": "main",
|
|
461
|
+
"schema_name": "raw",
|
|
462
|
+
"name": "orders",
|
|
463
|
+
},
|
|
464
|
+
"notebookInfos": [{"notebook_id": 1001}],
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
"tableInfo": {
|
|
468
|
+
"catalog_name": "main",
|
|
469
|
+
"schema_name": "raw",
|
|
470
|
+
"name": "payments",
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
]
|
|
474
|
+
},
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
refs = source._lineage_refs_for_table(downstream)
|
|
478
|
+
table_hashes = {
|
|
479
|
+
("main", "raw", "orders"): "h-orders",
|
|
480
|
+
("main", "raw", "payments"): "h-payments",
|
|
481
|
+
downstream.table_key: "h-daily",
|
|
482
|
+
}
|
|
483
|
+
source._emit_table_lineage(downstream, refs, table_hashes, {"1001": "h-notebook"})
|
|
484
|
+
|
|
485
|
+
edges = {edge.from_hash: edge for edge in source.drain_edges()}
|
|
486
|
+
assert edges["h-orders"].via_id == "h-notebook"
|
|
487
|
+
assert edges["h-payments"].via_id is None
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def test_databricks_unlistable_schema_forfeits_the_complete_declaration(
|
|
491
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
492
|
+
) -> None:
|
|
493
|
+
# A schema whose tables could not be listed must not read as "now empty".
|
|
494
|
+
source = DatabricksSource(_pat_recipe())
|
|
495
|
+
monkeypatch.setattr(source, "_list_schemas_for_catalog", lambda _c: ["good", "bad"])
|
|
496
|
+
|
|
497
|
+
def _tables(catalog: str, schema: str) -> list[TableRef]:
|
|
498
|
+
if schema == "bad":
|
|
499
|
+
raise RuntimeError("permission denied")
|
|
500
|
+
return [TableRef(database=catalog, schema=schema, table="t", object_type="TABLE")]
|
|
501
|
+
|
|
502
|
+
monkeypatch.setattr(source, "_list_tables_for_schema", _tables)
|
|
503
|
+
|
|
504
|
+
assert len(source._list_tables_for_database("main")) == 1
|
|
505
|
+
source._declare_listing_if_whole()
|
|
506
|
+
|
|
507
|
+
assert source.listing_complete is False
|
|
@@ -314,3 +314,26 @@ async def test_hive_fetch_content_pages_batches_for_all_strategy(
|
|
|
314
314
|
assert len(pages) == 12
|
|
315
315
|
assert "item1" in pages[0]
|
|
316
316
|
assert "item12" in pages[11]
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def test_hive_view_upstreams_are_database_and_table_keys(
|
|
320
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
321
|
+
) -> None:
|
|
322
|
+
# Hive stores view text with database-qualified names; the key must stay
|
|
323
|
+
# ``(database, table)`` to match an asset, not grow a third part.
|
|
324
|
+
source = HiveSource(_recipe())
|
|
325
|
+
monkeypatch.setattr(
|
|
326
|
+
source,
|
|
327
|
+
"_view_definition",
|
|
328
|
+
lambda _ref: "SELECT `o`.`id` FROM `sales`.`orders` `o` JOIN `crm`.`accounts` `a` ON 1=1",
|
|
329
|
+
)
|
|
330
|
+
tables = [
|
|
331
|
+
TableRef(database="reporting", schema=None, table="v_orders", object_type="VIEW"),
|
|
332
|
+
TableRef(database="sales", schema=None, table="orders", object_type="TABLE"),
|
|
333
|
+
TableRef(database="crm", schema=None, table="accounts", object_type="TABLE"),
|
|
334
|
+
]
|
|
335
|
+
|
|
336
|
+
lineage = source._collect_view_lineage(tables)
|
|
337
|
+
|
|
338
|
+
assert len(lineage) == 1
|
|
339
|
+
assert set(lineage[0].upstreams) == {("sales", "orders"), ("crm", "accounts")}
|
|
@@ -411,3 +411,107 @@ def test_mysql_include_views_false_lists_only_base_tables(
|
|
|
411
411
|
tables = source._list_tables_for_database("app_db")
|
|
412
412
|
|
|
413
413
|
assert [(t.table, t.object_type) for t in tables] == [("orders", "TABLE")]
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
class _ViewsCursor:
|
|
417
|
+
def __init__(self) -> None:
|
|
418
|
+
self._rows: list[tuple[Any, ...]] = []
|
|
419
|
+
|
|
420
|
+
def execute(self, query: str, _params: Any = None) -> None:
|
|
421
|
+
assert "information_schema.VIEWS" in query
|
|
422
|
+
self._rows = [
|
|
423
|
+
(
|
|
424
|
+
"v_orders_with_manager",
|
|
425
|
+
"select `app_db`.`o`.`order_id` AS `order_id`,`e`.`full_name` AS `manager` "
|
|
426
|
+
"from (`app_db`.`orders` `o` left join `hr_db`.`employees` `e` "
|
|
427
|
+
"on((`e`.`employee_id` = `o`.`account_manager_id`)))",
|
|
428
|
+
)
|
|
429
|
+
]
|
|
430
|
+
|
|
431
|
+
def fetchall(self) -> list[tuple[Any, ...]]:
|
|
432
|
+
return list(self._rows)
|
|
433
|
+
|
|
434
|
+
def __enter__(self) -> _ViewsCursor:
|
|
435
|
+
return self
|
|
436
|
+
|
|
437
|
+
def __exit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
|
|
438
|
+
return None
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
class _ViewsConnection:
|
|
442
|
+
def cursor(self) -> _ViewsCursor:
|
|
443
|
+
return _ViewsCursor()
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def test_mysql_view_upstreams_are_database_and_table_keys(
|
|
447
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
448
|
+
) -> None:
|
|
449
|
+
# Keys of any other shape name no asset, so every edge of the view would be
|
|
450
|
+
# left unresolved instead of pointing at the table that was scanned.
|
|
451
|
+
source = MySQLSource(_recipe())
|
|
452
|
+
monkeypatch.setattr(source, "_get_cached_connection", lambda _db=None: _ViewsConnection())
|
|
453
|
+
tables = [
|
|
454
|
+
TableRef(database="app_db", schema=None, table="orders", object_type="TABLE"),
|
|
455
|
+
TableRef(database="app_db", schema=None, table="v_orders_with_manager", object_type="VIEW"),
|
|
456
|
+
TableRef(database="hr_db", schema=None, table="employees", object_type="TABLE"),
|
|
457
|
+
]
|
|
458
|
+
|
|
459
|
+
lineage = source._collect_view_lineage(tables)
|
|
460
|
+
|
|
461
|
+
assert len(lineage) == 1
|
|
462
|
+
assert lineage[0].view == ("app_db", "v_orders_with_manager")
|
|
463
|
+
assert set(lineage[0].upstreams) == {("app_db", "orders"), ("hr_db", "employees")}
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def test_a_failed_database_listing_forfeits_the_complete_declaration(
|
|
467
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
468
|
+
) -> None:
|
|
469
|
+
# A schema that could not be listed says nothing about the tables in it.
|
|
470
|
+
source = MySQLSource(_recipe())
|
|
471
|
+
monkeypatch.setattr(source, "_resolve_databases", lambda: ["app_db", "broken_db"])
|
|
472
|
+
|
|
473
|
+
def _list(database: str) -> list[TableRef]:
|
|
474
|
+
if database == "broken_db":
|
|
475
|
+
raise RuntimeError("access denied")
|
|
476
|
+
return [TableRef(database=database, schema=None, table="orders")]
|
|
477
|
+
|
|
478
|
+
monkeypatch.setattr(source, "_list_tables_for_database", _list)
|
|
479
|
+
|
|
480
|
+
tables = source._iter_tables()
|
|
481
|
+
source._declare_listing_if_whole()
|
|
482
|
+
|
|
483
|
+
assert [t.table for t in tables] == ["orders"]
|
|
484
|
+
assert source.listing_complete is False
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def test_a_clean_unlimited_listing_is_declared_complete(
|
|
488
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
489
|
+
) -> None:
|
|
490
|
+
source = MySQLSource(_recipe())
|
|
491
|
+
monkeypatch.setattr(source, "_resolve_databases", lambda: ["app_db"])
|
|
492
|
+
monkeypatch.setattr(
|
|
493
|
+
source,
|
|
494
|
+
"_list_tables_for_database",
|
|
495
|
+
lambda db: [TableRef(database=db, schema=None, table="orders")],
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
source._iter_tables()
|
|
499
|
+
source._declare_listing_if_whole()
|
|
500
|
+
|
|
501
|
+
assert source.listing_complete is True
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def test_a_table_limit_means_the_listing_may_be_short() -> None:
|
|
505
|
+
source = MySQLSource(_recipe(optional={"scope": {"database": "app_db", "table_limit": 5}}))
|
|
506
|
+
|
|
507
|
+
source._declare_listing_if_whole()
|
|
508
|
+
|
|
509
|
+
assert source.listing_complete is False
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def test_declared_partial_coverage_overrides_a_complete_listing() -> None:
|
|
513
|
+
source = MySQLSource(_recipe())
|
|
514
|
+
source.declare_listing_complete()
|
|
515
|
+
source.declare_partial_coverage("timed out")
|
|
516
|
+
|
|
517
|
+
assert source.listing_complete is False
|