classifyre-cli 0.6.18__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.6.18 → classifyre_cli-0.7.1}/PKG-INFO +1 -1
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/package.json +1 -1
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/pyproject.toml +1 -1
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/main.py +5 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/outputs/rest.py +7 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/base.py +30 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/databricks/source.py +56 -12
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/hive/source.py +4 -1
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/mysql/source.py +1 -1
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/neo4j/source.py +3 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/tabular_base.py +22 -2
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/sql_lineage.py +134 -8
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_databricks_source.py +90 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_hive_source.py +23 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_mysql_source.py +104 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_neo4j_source.py +26 -0
- classifyre_cli-0.7.1/tests/test_sql_lineage.py +319 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/uv.lock +175 -187
- classifyre_cli-0.6.18/.turbo/turbo-build.log +0 -3
- classifyre_cli-0.6.18/tests/test_sql_lineage.py +0 -153
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/.gitignore +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/.python-version +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/README.md +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/main.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/scripts/generate_models.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/scripts/generate_runtime_packages.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/scripts/generate_sdk_completions.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/augmentation/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/augmentation/contract.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/augmentation/preview.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/augmentation/runner.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/augmentation/sdk.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/augmentation/session.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/config.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/base.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/broken_links/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/broken_links/detector.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/config.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/content/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom/detector.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom/extractor.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom/runners/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_base.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_custom_detector.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_decision.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_factory.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_gliner2.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_image_classification.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_llm.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_object_detection.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_provider_call.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_regex.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_tag.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom/runners/_text_classification.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom/trainer.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom_detector/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom_detector/contract.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom_detector/files.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom_detector/preview.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom_detector/results.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom_detector/runner.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom_detector/sdk.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/custom_detector/session.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/dependencies.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/engine_version.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/errors.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/pii/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/pii/detector.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/secrets/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/secrets/detector.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/threat/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/threat/code_security_detector.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/detectors/threat/yara_detector.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/file_evaluation/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/file_evaluation/asset_fixture.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/file_evaluation/runner.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/graph/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/graph/edges.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/models/generated_detectors.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/models/generated_input.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/models/generated_single_asset_scan_results.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/notebook/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/notebook/child.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/notebook/cli.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/notebook/cohort.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/notebook/contract.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/notebook/execute.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/notebook/files.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/notebook/groups.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/notebook/outputs.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/notebook/packages.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/notebook/protocol.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/notebook/redact.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/notebook/scaffold.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/notebook/sdk.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/notebook/serialize.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/outputs/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/outputs/base.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/outputs/console.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/outputs/factory.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/outputs/file.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/pipeline/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/pipeline/asset_payload_server.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/pipeline/content_provider.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/pipeline/detector_breaker.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/pipeline/detector_pipeline.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/pipeline/finding_context.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/pipeline/media_artifact.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/pipeline/parsed_content_provider.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/pipeline/payload_window.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/pipeline/scan_cache.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/pipeline/text_artifact.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/pipeline/worker_pool.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/asset_metadata.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/atlassian_common.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/azure_blob_storage/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/azure_blob_storage/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/confluence/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/confluence/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/custom/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/custom/env.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/custom/runner.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/custom/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/databricks/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/delta_lake/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/delta_lake/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/dependencies.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/dremio/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/dremio/client.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/dremio/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/dropbox/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/dropbox/auth.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/dropbox/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/elasticsearch/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/elasticsearch/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/email/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/email/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/git/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/git/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/google_cloud_storage/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/google_cloud_storage/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/google_workspace/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/google_workspace/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/hive/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/hugging_face/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/hugging_face/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/iceberg/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/iceberg/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/jira/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/jira/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/kafka/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/kafka/rest.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/kafka/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/lakehouse_base.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/local_folder/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/local_folder/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/meilisearch/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/meilisearch/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/microsoft_365/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/microsoft_365/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/mongodb/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/mongodb/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/mssql/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/mssql/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/mysql/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/neo4j/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/notion/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/notion/client.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/notion/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/object_storage/base.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/opensearch/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/opensearch/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/oracle/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/oracle/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/postgresql/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/postgresql/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/powerbi/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/powerbi/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/recipe_normalizer.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/reddit/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/reddit/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/s3_client.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/s3_compatible_storage/README.md +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/s3_compatible_storage/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/s3_compatible_storage/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/sandbox/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/sandbox/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/search_engine_base.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/servicedesk/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/servicedesk/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/slack/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/slack/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/snowflake/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/snowflake/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/sqlite/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/sqlite/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/tableau/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/tableau/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/tabular_utils.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/wordpress/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/wordpress/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/youtube/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/sources/youtube/source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/telemetry.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/archive_extraction.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/content_extraction.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/dependency_groups.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/embedded_files.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/external_video.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/file_metadata.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/file_parser.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/file_to_images.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/hashing.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/legacy_office.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/payload.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/range_reader.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/redaction.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/resources.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/sampling_cursor.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/sampling_frontier.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/source_files.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/torch_runtime.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/transcription.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/urn.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/uv_sync.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/validation.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/src/utils/video_processing.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/_lakehouse_fakes.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/conftest.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/broken_links/test_broken_links_detector.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/conftest.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/content/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/custom/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/custom/conftest.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/custom/test_decision_runner.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/custom/test_gliner25_runner.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/custom/test_gliner2_classification.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/custom/test_invoice_extraction.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/custom/test_llm_runner.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/custom/test_pipeline_integration.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/custom/test_regex_runner.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/custom/test_runner_hardening.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/custom/test_transformer_runners.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/pii/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/pii/conftest.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/pii/sample_invoice.pdf +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/pii/test_pii_chunking.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/pii/test_pii_detector.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/pii/test_pii_detector_extended.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/pii/test_pii_regional_entities.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/pii/test_pii_severity.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/secrets/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/secrets/test_secrets_detector.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/secrets/test_secrets_detector_extended.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/test_base_detector.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/test_custom_detector_examples_runtime.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/test_custom_detector_session.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/test_detector_catalog_commercial.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/test_detector_pipeline_types.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/test_detector_schema_examples.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/test_detector_types.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/test_phase2_detectors.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/test_registry.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/test_tag_runner.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/threat/__init__.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/threat/test_code_security_detector.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/detectors/threat/test_yara_detector.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/integration/test_wordpress_broken_links_detector.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/integration/test_wordpress_links_assets.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/pipeline/test_detector_breaker.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/pipeline/test_detector_outcomes.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/pipeline/test_detector_pipeline.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/pipeline/test_worker_pool.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_arrow_ipc_payloads.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_assets_metadata_catalog.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_augmentation_contract.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_augmentation_edges.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_augmentation_patch.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_augmentation_scan_cache.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_augmentation_sdk.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_augmentation_session.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_augmentation_tags_end_to_end.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_automatic_new_first.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_azure_blob_storage_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_base_source_attachment.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_base_source_sampling.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_byte_valued_fields.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_config.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_confluence_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_custom_detector_end_to_end.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_custom_detector_evaluation.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_custom_detector_notebook.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_custom_detector_templates.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_custom_extractor.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_custom_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_custom_source_relationships.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_custom_source_tags_end_to_end.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_delta_lake_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_dependency_groups.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_detector_scope.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_dremio_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_dropbox_auth_command.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_dropbox_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_elasticsearch_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_email_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_file_evaluation_runner.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_finding_context.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_git_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_google_cloud_storage_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_google_workspace_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_graph_edges.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_hashing.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_hugging_face_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_iceberg_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_jira_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_kafka_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_lineage_loss_reporting.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_local_folder_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_main.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_media_embedding.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_meilisearch_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_microsoft_365_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_mongodb_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_mssql_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_notebook_asset_entity.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_notebook_asset_extract.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_notebook_asset_tags.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_notebook_cohort.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_notebook_execute.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_notebook_files.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_notebook_groups.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_notebook_log_levels.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_notebook_packages.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_notebook_query_assets.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_notebook_sdk_exports.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_notebook_serialize.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_notebook_templates.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_notion_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_object_storage_download_efficiency.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_opensearch_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_oracle_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_outputs.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_payload_window.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_payload_window_parquet.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_postgresql_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_powerbi_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_range_reading.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_recipe_normalizer.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_reddit_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_rest_backpressure.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_runtime_packages.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_s3_compatible_storage_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_sampling_automatic.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_sampling_cursor.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_sampling_frontier.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_sandbox_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_scan_cache.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_sdk_completions.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_secret_redaction.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_semantic_declarations.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_servicedesk_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_slack_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_snowflake_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_source_dependency_groups.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_source_strategy_conformance.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_sqlite_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_tableau_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_tabular_automatic_sampling.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_tabular_lineage.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_tabular_utils.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_torch_runtime.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_urn.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_uv_sync.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_view_dependencies_are_not_foreign_keys.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_wordpress_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_youtube_source.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/test_youtube_source_integration.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/utils/test_archive_extraction.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/utils/test_content_extraction.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/utils/test_embedded_files.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/utils/test_file_metadata.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/utils/test_file_parser.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/utils/test_file_to_images.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/utils/test_legacy_office.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/utils/test_payload.py +0 -0
- {classifyre_cli-0.6.18 → classifyre_cli-0.7.1}/tests/utils/test_transcription.py +0 -0
- {classifyre_cli-0.6.18 → 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
|
|
@@ -32,7 +32,12 @@ from ..utils.file_metadata import build_columns
|
|
|
32
32
|
from ..utils.file_parser import render_bytes_cell
|
|
33
33
|
from ..utils.hashing import hash_id, unhash_id
|
|
34
34
|
from ..utils.sampling_frontier import RangeFrontier
|
|
35
|
-
from ..utils.sql_lineage import
|
|
35
|
+
from ..utils.sql_lineage import (
|
|
36
|
+
NO_UPSTREAM,
|
|
37
|
+
column_mappings_by_table,
|
|
38
|
+
fields_for_table,
|
|
39
|
+
sqlglot_dialect,
|
|
40
|
+
)
|
|
36
41
|
from ..utils.urn import Urn, UrnError
|
|
37
42
|
from .base import BaseSource
|
|
38
43
|
from .tabular_utils import (
|
|
@@ -314,9 +319,23 @@ class BaseTabularSource(BaseSource):
|
|
|
314
319
|
try:
|
|
315
320
|
tables.extend(self._list_tables_for_database(database))
|
|
316
321
|
except Exception as exc:
|
|
322
|
+
self.note_listing_error()
|
|
317
323
|
logger.warning("Skipping database %s due to listing error: %s", database, exc)
|
|
318
324
|
return tables
|
|
319
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
|
+
|
|
320
339
|
# ── Primary keys ─────────────────────────────────────────────────────
|
|
321
340
|
|
|
322
341
|
def _get_primary_key_columns(self, table_ref: TableRef) -> list[str]:
|
|
@@ -1039,7 +1058,7 @@ class BaseTabularSource(BaseSource):
|
|
|
1039
1058
|
# spelled the name (a synonym, a quoted alias).
|
|
1040
1059
|
fields = [mapping for group in by_table.values() for mapping in group]
|
|
1041
1060
|
else:
|
|
1042
|
-
fields =
|
|
1061
|
+
fields = fields_for_table(by_table, upstream_key)
|
|
1043
1062
|
upstream_hash = table_hash_by_key.get(upstream_key)
|
|
1044
1063
|
upstream_ref = (
|
|
1045
1064
|
Ref.asset(upstream_hash)
|
|
@@ -1092,6 +1111,7 @@ class BaseTabularSource(BaseSource):
|
|
|
1092
1111
|
table_hash_by_key: dict[tuple[str, ...], str] = {
|
|
1093
1112
|
t.table_key: self.generate_hash_id(t.raw_id) for t in tables
|
|
1094
1113
|
}
|
|
1114
|
+
self._declare_listing_if_whole()
|
|
1095
1115
|
fk_links = self._collect_foreign_key_links(tables)
|
|
1096
1116
|
self._emit_relationship_edges(tables, table_hash_by_key, fk_links)
|
|
1097
1117
|
|
|
@@ -32,6 +32,7 @@ logger = logging.getLogger(__name__)
|
|
|
32
32
|
__all__ = [
|
|
33
33
|
"NO_UPSTREAM",
|
|
34
34
|
"column_mappings_by_table",
|
|
35
|
+
"fields_for_table",
|
|
35
36
|
"sqlglot_dialect",
|
|
36
37
|
"table_paths_from_sql",
|
|
37
38
|
"upstream_tables_from_sql",
|
|
@@ -171,6 +172,114 @@ def _paths(node: Any, trail: tuple[Any, ...] = ()) -> Iterator[tuple[Any, ...]]:
|
|
|
171
172
|
yield from _paths(child, trail)
|
|
172
173
|
|
|
173
174
|
|
|
175
|
+
def _table_key(table: Any) -> str:
|
|
176
|
+
"""A table as the SQL named it: ``schema.table``, or ``table`` when written bare.
|
|
177
|
+
|
|
178
|
+
The schema stays in the key because two tables can share a name across
|
|
179
|
+
schemas (``core.customers`` and ``edge.customers``) and a view can read both.
|
|
180
|
+
"""
|
|
181
|
+
name = str(table.name).lower()
|
|
182
|
+
schema = str(table.db).lower()
|
|
183
|
+
return f"{schema}.{name}" if schema else name
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def fields_for_table(
|
|
187
|
+
by_table: dict[str, list[FieldMapping]], table_key: tuple[str, ...]
|
|
188
|
+
) -> list[FieldMapping]:
|
|
189
|
+
"""The mappings that belong to one upstream, given its catalog key.
|
|
190
|
+
|
|
191
|
+
``table_key`` ends ``(…, schema, table)``. The schema-qualified spelling wins,
|
|
192
|
+
so same-named tables in two schemas are never confused; a table written bare
|
|
193
|
+
(it was on the search path) matches by name; and a name that only one
|
|
194
|
+
qualified entry carries is that entry, however the SQL spelled the schema.
|
|
195
|
+
"""
|
|
196
|
+
name = str(table_key[-1]).lower()
|
|
197
|
+
if len(table_key) >= 2:
|
|
198
|
+
qualified = f"{str(table_key[-2]).lower()}.{name}"
|
|
199
|
+
if qualified in by_table:
|
|
200
|
+
return list(by_table[qualified])
|
|
201
|
+
if name in by_table:
|
|
202
|
+
return list(by_table[name])
|
|
203
|
+
candidates = [key for key in by_table if key.rsplit(".", 1)[-1] == name]
|
|
204
|
+
return list(by_table[candidates[0]]) if len(candidates) == 1 else []
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _inline_lateral_subqueries(statement: Any, exp: Any) -> None:
|
|
208
|
+
"""Make ``LATERAL (SELECT …) t`` an ordinary derived table.
|
|
209
|
+
|
|
210
|
+
sqlglot's lineage stops at a lateral and reports every column its subquery
|
|
211
|
+
mentions, including the outer table's correlated one, as feeding the output.
|
|
212
|
+
As a plain derived table the output columns follow their own projection to
|
|
213
|
+
the table they come from; the correlation is still found among the
|
|
214
|
+
indirect dependencies because scopes resolve names through their parents.
|
|
215
|
+
A lateral over a function (``jsonb_each(…)``) is left alone.
|
|
216
|
+
"""
|
|
217
|
+
for lateral in list(statement.find_all(exp.Lateral)):
|
|
218
|
+
inner = lateral.this
|
|
219
|
+
if not isinstance(inner, exp.Subquery):
|
|
220
|
+
continue
|
|
221
|
+
alias = lateral.args.get("alias")
|
|
222
|
+
if alias is not None:
|
|
223
|
+
inner.set("alias", alias.copy())
|
|
224
|
+
lateral.replace(inner.copy())
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _qualify_lone_sources(statement: Any, exp: Any) -> None:
|
|
228
|
+
"""Name the table on columns written without one, where only one table is in play.
|
|
229
|
+
|
|
230
|
+
Postgres stores a single-table view with no qualifiers (``WHERE status <> …``),
|
|
231
|
+
and sqlglot will not place such a column without the table's schema. With one
|
|
232
|
+
table in the ``FROM`` there is only one place it can come from, so say so.
|
|
233
|
+
That also stops an output name that re-uses a column name (``qty::int AS qty``)
|
|
234
|
+
from being ambiguous the next time ``qty`` is read.
|
|
235
|
+
|
|
236
|
+
A name in ``ORDER BY`` / ``GROUP BY`` / ``HAVING`` that matches an output alias
|
|
237
|
+
is left for sqlglot to expand: it means the alias, not the column.
|
|
238
|
+
"""
|
|
239
|
+
for select in list(statement.find_all(exp.Select)):
|
|
240
|
+
source = select.args.get("from_") or select.args.get("from")
|
|
241
|
+
if source is None or select.args.get("joins"):
|
|
242
|
+
continue
|
|
243
|
+
node = source.this
|
|
244
|
+
if isinstance(node, exp.Table) and isinstance(node.this, exp.Identifier):
|
|
245
|
+
qualifier = (node.args.get("alias") and node.args["alias"].this) or node.this
|
|
246
|
+
elif isinstance(node, exp.Subquery) and node.args.get("alias"):
|
|
247
|
+
qualifier = node.args["alias"].this
|
|
248
|
+
else:
|
|
249
|
+
continue
|
|
250
|
+
if not isinstance(qualifier, exp.Identifier):
|
|
251
|
+
continue
|
|
252
|
+
|
|
253
|
+
derived = {
|
|
254
|
+
projection.alias.lower()
|
|
255
|
+
for projection in select.expressions
|
|
256
|
+
if isinstance(projection, exp.Alias)
|
|
257
|
+
and not (
|
|
258
|
+
isinstance(projection.this, exp.Column)
|
|
259
|
+
and projection.this.name.lower() == projection.alias.lower()
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
for column in list(select.find_all(exp.Column)):
|
|
263
|
+
if column.table or not isinstance(column.this, exp.Identifier):
|
|
264
|
+
continue
|
|
265
|
+
if column.find_ancestor(exp.Select) is not select:
|
|
266
|
+
continue
|
|
267
|
+
tail = column.find_ancestor(exp.Order, exp.Group, exp.Having)
|
|
268
|
+
if tail is not None and tail.parent is select and column.name.lower() in derived:
|
|
269
|
+
continue
|
|
270
|
+
column.set("table", qualifier.copy())
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _source_in_scope(scope: Any, name: str) -> Any:
|
|
274
|
+
"""Look a table alias up in this scope, then in the scopes around it."""
|
|
275
|
+
while scope is not None:
|
|
276
|
+
found = scope.sources.get(name)
|
|
277
|
+
if found is not None:
|
|
278
|
+
return found
|
|
279
|
+
scope = scope.parent
|
|
280
|
+
return None
|
|
281
|
+
|
|
282
|
+
|
|
174
283
|
def column_mappings_by_table(
|
|
175
284
|
sql: str,
|
|
176
285
|
*,
|
|
@@ -178,9 +287,10 @@ def column_mappings_by_table(
|
|
|
178
287
|
) -> dict[str, list[FieldMapping]]:
|
|
179
288
|
"""Per-column dependencies of a view, split by the table they come from.
|
|
180
289
|
|
|
181
|
-
Keys are lower-cased table names
|
|
182
|
-
|
|
183
|
-
the columns that table really
|
|
290
|
+
Keys are lower-cased table names as the SQL wrote them (``schema.table``, or
|
|
291
|
+
``table`` when bare); :func:`fields_for_table` picks the entry for a catalog
|
|
292
|
+
key, so a caller can attach to each edge only the columns that table really
|
|
293
|
+
feeds. :data:`NO_UPSTREAM` holds output
|
|
184
294
|
columns no table feeds.
|
|
185
295
|
|
|
186
296
|
Columns are resolved through CTEs, derived tables and set operations, so a
|
|
@@ -214,6 +324,8 @@ def column_mappings_by_table(
|
|
|
214
324
|
for projection in statement.copy().selects
|
|
215
325
|
if projection.alias_or_name
|
|
216
326
|
}
|
|
327
|
+
_inline_lateral_subqueries(statement, exp)
|
|
328
|
+
_qualify_lone_sources(statement, exp)
|
|
217
329
|
statement = qualify(
|
|
218
330
|
statement, dialect=dialect, validate_qualify_columns=False, identify=False
|
|
219
331
|
)
|
|
@@ -234,7 +346,10 @@ def column_mappings_by_table(
|
|
|
234
346
|
# which we deliberately do not resolve. Dataset-level it is.
|
|
235
347
|
continue
|
|
236
348
|
try:
|
|
237
|
-
|
|
349
|
+
# Quoted, so an alias like "Id" is matched as written instead of folded.
|
|
350
|
+
node = lineage(
|
|
351
|
+
exp.column(name, quoted=True), statement, {}, dialect=dialect, scope=root
|
|
352
|
+
)
|
|
238
353
|
except Exception as exc:
|
|
239
354
|
logger.debug("Could not trace column %s: %s", name, exc)
|
|
240
355
|
continue
|
|
@@ -258,8 +373,8 @@ def column_mappings_by_table(
|
|
|
258
373
|
constant = _Trace()
|
|
259
374
|
constant.add(projections, None, exp)
|
|
260
375
|
continue
|
|
261
|
-
table =
|
|
262
|
-
column = leaf.name.rsplit(".", 1)[-1]
|
|
376
|
+
table = _table_key(leaf.source)
|
|
377
|
+
column = leaf.name.rsplit(".", 1)[-1].strip('"`')
|
|
263
378
|
if table and column and column != "*":
|
|
264
379
|
per_table.setdefault(table, _Trace()).add(projections, column, exp)
|
|
265
380
|
|
|
@@ -312,9 +427,9 @@ def column_mappings_by_table(
|
|
|
312
427
|
if clause is None:
|
|
313
428
|
continue
|
|
314
429
|
for column in clause.find_all(exp.Column):
|
|
315
|
-
source = scope
|
|
430
|
+
source = _source_in_scope(scope, column.table)
|
|
316
431
|
if isinstance(source, exp.Table) and column.name and column.name != "*":
|
|
317
|
-
indirect.setdefault(
|
|
432
|
+
indirect.setdefault(_table_key(source), set()).add(column.name)
|
|
318
433
|
|
|
319
434
|
for table, columns in indirect.items():
|
|
320
435
|
by_table.setdefault(table, []).append(
|
|
@@ -329,6 +444,7 @@ def upstream_tables_from_sql(
|
|
|
329
444
|
dialect: str | None = None,
|
|
330
445
|
default_database: str | None = None,
|
|
331
446
|
default_schema: str | None = None,
|
|
447
|
+
two_level: bool = False,
|
|
332
448
|
) -> list[tuple[str, ...]]:
|
|
333
449
|
"""Table keys a statement reads from, for catalogs with no dependency view.
|
|
334
450
|
|
|
@@ -338,6 +454,11 @@ def upstream_tables_from_sql(
|
|
|
338
454
|
the wrong default schema points at nothing — so callers should prefer the
|
|
339
455
|
catalog wherever it exists and treat these edges as SQL_PARSED.
|
|
340
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
|
+
|
|
341
462
|
CTE names are excluded: a ``WITH`` alias is not a table, and emitting it
|
|
342
463
|
would create lineage to an object that does not exist.
|
|
343
464
|
"""
|
|
@@ -366,6 +487,11 @@ def upstream_tables_from_sql(
|
|
|
366
487
|
name = table.name
|
|
367
488
|
if not name or name.lower() in cte_names:
|
|
368
489
|
continue
|
|
490
|
+
if two_level:
|
|
491
|
+
namespace = table.text("db") or default_database
|
|
492
|
+
if namespace:
|
|
493
|
+
keys.add((namespace, name))
|
|
494
|
+
continue
|
|
369
495
|
schema = table.text("db") or default_schema
|
|
370
496
|
database = table.text("catalog") or default_database
|
|
371
497
|
if database and schema:
|