classifyre-cli 0.4.129__tar.gz → 0.4.131__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- classifyre_cli-0.4.131/.turbo/turbo-build.log +3 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/PKG-INFO +1 -1
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/package.json +1 -1
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/pyproject.toml +8 -1
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/scripts/generate_runtime_packages.py +3 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/scripts/generate_sdk_completions.py +64 -2
- classifyre_cli-0.4.131/src/graph/__init__.py +37 -0
- classifyre_cli-0.4.131/src/graph/edges.py +409 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/main.py +60 -14
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/models/generated_input.py +4 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/models/generated_single_asset_scan_results.py +5 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/notebook/contract.py +1 -1
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/notebook/sdk.py +91 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/outputs/rest.py +44 -6
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/base.py +62 -4
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/custom/runner.py +16 -1
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/custom/source.py +67 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/databricks/source.py +124 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/hive/source.py +50 -1
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/lakehouse_base.py +15 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/mssql/source.py +64 -1
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/mysql/source.py +53 -1
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/oracle/source.py +83 -1
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/postgresql/source.py +73 -1
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/powerbi/source.py +97 -1
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/snowflake/source.py +100 -1
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/tableau/source.py +140 -1
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/tabular_base.py +142 -1
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/tabular_utils.py +16 -0
- classifyre_cli-0.4.131/src/utils/sql_lineage.py +233 -0
- classifyre_cli-0.4.131/src/utils/urn.py +296 -0
- classifyre_cli-0.4.131/tests/test_custom_source_relationships.py +138 -0
- classifyre_cli-0.4.131/tests/test_graph_edges.py +149 -0
- classifyre_cli-0.4.131/tests/test_tabular_lineage.py +252 -0
- classifyre_cli-0.4.131/tests/test_urn.py +94 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/uv.lock +73 -76
- classifyre_cli-0.4.129/.turbo/turbo-build.log +0 -3
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/.gitignore +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/.python-version +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/README.md +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/main.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/scripts/generate_models.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/config.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/base.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/broken_links/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/broken_links/detector.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/config.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/content/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/custom/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/custom/detector.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/custom/extractor.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/custom/runners/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/custom/runners/_base.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/custom/runners/_factory.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/custom/runners/_gliner2.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/custom/runners/_image_classification.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/custom/runners/_llm.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/custom/runners/_object_detection.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/custom/runners/_regex.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/custom/runners/_text_classification.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/custom/trainer.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/dependencies.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/engine_version.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/pii/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/pii/detector.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/secrets/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/secrets/detector.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/threat/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/threat/code_security_detector.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/detectors/threat/yara_detector.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/file_evaluation/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/file_evaluation/runner.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/models/generated_detectors.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/notebook/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/notebook/cli.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/notebook/execute.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/notebook/files.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/notebook/groups.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/notebook/outputs.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/notebook/packages.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/notebook/protocol.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/notebook/redact.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/notebook/scaffold.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/notebook/serialize.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/outputs/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/outputs/base.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/outputs/console.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/outputs/factory.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/outputs/file.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/pipeline/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/pipeline/content_provider.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/pipeline/detector_pipeline.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/pipeline/parsed_content_provider.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/pipeline/payload_window.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/pipeline/scan_cache.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/pipeline/text_artifact.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/pipeline/worker_pool.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/asset_metadata.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/atlassian_common.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/azure_blob_storage/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/azure_blob_storage/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/confluence/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/confluence/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/custom/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/custom/env.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/databricks/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/delta_lake/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/delta_lake/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/dependencies.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/dropbox/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/dropbox/auth.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/dropbox/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/elasticsearch/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/elasticsearch/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/email/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/email/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/git/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/git/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/google_cloud_storage/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/google_cloud_storage/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/google_workspace/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/google_workspace/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/hive/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/hugging_face/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/hugging_face/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/iceberg/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/iceberg/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/jira/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/jira/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/kafka/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/kafka/rest.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/kafka/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/local_folder/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/local_folder/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/meilisearch/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/meilisearch/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/microsoft_365/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/microsoft_365/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/mongodb/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/mongodb/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/mssql/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/mysql/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/neo4j/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/neo4j/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/notion/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/notion/client.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/notion/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/object_storage/base.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/opensearch/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/opensearch/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/oracle/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/postgresql/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/powerbi/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/recipe_normalizer.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/reddit/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/reddit/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/s3_client.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/s3_compatible_storage/README.md +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/s3_compatible_storage/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/s3_compatible_storage/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/sandbox/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/sandbox/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/search_engine_base.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/servicedesk/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/servicedesk/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/slack/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/slack/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/snowflake/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/sqlite/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/sqlite/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/tableau/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/wordpress/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/wordpress/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/youtube/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/sources/youtube/source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/telemetry.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/utils/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/utils/archive_extraction.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/utils/content_extraction.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/utils/dependency_groups.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/utils/embedded_files.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/utils/external_video.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/utils/file_metadata.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/utils/file_parser.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/utils/file_to_images.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/utils/hashing.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/utils/legacy_office.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/utils/payload.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/utils/range_reader.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/utils/resources.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/utils/source_files.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/utils/torch_runtime.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/utils/transcription.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/utils/uv_sync.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/utils/validation.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/src/utils/video_processing.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/_lakehouse_fakes.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/conftest.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/broken_links/test_broken_links_detector.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/conftest.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/content/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/custom/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/custom/conftest.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/custom/test_gliner2_classification.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/custom/test_invoice_extraction.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/custom/test_llm_runner.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/custom/test_pipeline_integration.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/custom/test_regex_runner.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/custom/test_runner_hardening.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/custom/test_transformer_runners.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/pii/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/pii/conftest.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/pii/sample_invoice.pdf +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/pii/test_pii_chunking.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/pii/test_pii_detector.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/pii/test_pii_detector_extended.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/pii/test_pii_regional_entities.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/pii/test_pii_severity.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/secrets/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/secrets/test_secrets_detector.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/secrets/test_secrets_detector_extended.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/test_base_detector.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/test_custom_detector_examples_runtime.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/test_detector_catalog_commercial.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/test_detector_pipeline_types.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/test_detector_schema_examples.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/test_detector_types.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/test_phase2_detectors.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/test_registry.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/threat/__init__.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/threat/test_code_security_detector.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/detectors/threat/test_yara_detector.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/integration/test_wordpress_broken_links_detector.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/integration/test_wordpress_links_assets.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/pipeline/test_detector_outcomes.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/pipeline/test_detector_pipeline.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/pipeline/test_worker_pool.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_arrow_ipc_payloads.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_assets_metadata_catalog.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_azure_blob_storage_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_base_source_attachment.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_base_source_sampling.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_byte_valued_fields.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_config.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_confluence_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_custom_extractor.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_custom_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_databricks_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_delta_lake_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_dependency_groups.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_dropbox_auth_command.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_dropbox_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_elasticsearch_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_email_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_file_evaluation_runner.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_git_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_google_cloud_storage_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_google_workspace_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_hashing.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_hive_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_hugging_face_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_iceberg_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_jira_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_kafka_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_local_folder_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_main.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_meilisearch_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_microsoft_365_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_mongodb_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_mssql_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_mysql_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_neo4j_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_notebook_execute.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_notebook_files.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_notebook_groups.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_notebook_packages.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_notebook_serialize.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_notebook_templates.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_notion_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_object_storage_download_efficiency.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_opensearch_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_oracle_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_outputs.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_payload_window.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_payload_window_parquet.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_postgresql_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_powerbi_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_range_reading.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_recipe_normalizer.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_reddit_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_rest_backpressure.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_runtime_packages.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_s3_compatible_storage_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_sampling_automatic.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_sandbox_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_scan_cache.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_sdk_completions.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_servicedesk_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_slack_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_snowflake_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_source_dependency_groups.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_source_strategy_conformance.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_sqlite_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_tableau_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_tabular_automatic_sampling.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_tabular_utils.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_torch_runtime.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_uv_sync.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_wordpress_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_youtube_source.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/test_youtube_source_integration.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/utils/test_archive_extraction.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/utils/test_content_extraction.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/utils/test_embedded_files.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/utils/test_file_metadata.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/utils/test_file_parser.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/utils/test_file_to_images.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/utils/test_legacy_office.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/utils/test_payload.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/utils/test_transcription.py +0 -0
- {classifyre_cli-0.4.129 → classifyre_cli-0.4.131}/tests/utils/test_video_processing.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "classifyre-cli"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.131"
|
|
4
4
|
description = "Classifyre CLI — scan and classify unstructured data sources"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -114,6 +114,13 @@ custom = [
|
|
|
114
114
|
regex = [
|
|
115
115
|
"google-re2>=1.1",
|
|
116
116
|
]
|
|
117
|
+
# Column-level lineage for dialects whose catalog only hands back the view SQL.
|
|
118
|
+
# Kept out of the base image: a warehouse connector needs it, a folder scan
|
|
119
|
+
# never does, and it is loaded through require_module like every other optional
|
|
120
|
+
# source dependency.
|
|
121
|
+
lineage = [
|
|
122
|
+
"sqlglot>=25.0.0",
|
|
123
|
+
]
|
|
117
124
|
llm = [
|
|
118
125
|
"litellm>=1.86.2",
|
|
119
126
|
# Pure-wheel PDF renderer (permissive license, no system binaries) used to
|
|
@@ -80,6 +80,9 @@ INCLUDED_GROUPS = (
|
|
|
80
80
|
"kafka",
|
|
81
81
|
"delta-lake",
|
|
82
82
|
"iceberg",
|
|
83
|
+
# sqlglot: pure Python and small, and the thing a connector author reaches
|
|
84
|
+
# for when they want to derive column lineage from a query they already have.
|
|
85
|
+
"lineage",
|
|
83
86
|
)
|
|
84
87
|
|
|
85
88
|
#: Everything else in `[dependency-groups]`, listed rather than inferred so that
|
|
@@ -33,10 +33,19 @@ from src.notebook.contract import ( # noqa: E402
|
|
|
33
33
|
from src.notebook.sdk import ( # noqa: E402
|
|
34
34
|
Asset,
|
|
35
35
|
Context,
|
|
36
|
+
FieldMapping,
|
|
37
|
+
FlowType,
|
|
36
38
|
NotebookFile,
|
|
37
39
|
ParsedContent,
|
|
40
|
+
Ref,
|
|
41
|
+
contains,
|
|
42
|
+
flow,
|
|
38
43
|
pages,
|
|
39
44
|
parse,
|
|
45
|
+
references,
|
|
46
|
+
same_as,
|
|
47
|
+
urn_for,
|
|
48
|
+
uses,
|
|
40
49
|
)
|
|
41
50
|
|
|
42
51
|
OUTPUT_PATH = (
|
|
@@ -94,8 +103,22 @@ def _snippet(name: str, func: Any) -> str:
|
|
|
94
103
|
]
|
|
95
104
|
if not required:
|
|
96
105
|
return f"{name}()"
|
|
106
|
+
# Keyword-only parameters are spelled `name=` in the snippet. `flow` makes
|
|
107
|
+
# both its ends keyword-only precisely so a reversed lineage edge cannot be
|
|
108
|
+
# written by accident; a snippet that filled them positionally would hand
|
|
109
|
+
# the author code that does not run.
|
|
110
|
+
keyword_only = {
|
|
111
|
+
parameter_name
|
|
112
|
+
for parameter_name, parameter in signature.parameters.items()
|
|
113
|
+
if parameter.kind is inspect.Parameter.KEYWORD_ONLY
|
|
114
|
+
}
|
|
97
115
|
placeholders = ", ".join(
|
|
98
|
-
|
|
116
|
+
(
|
|
117
|
+
f"{parameter}=${{{index}:{parameter}}}"
|
|
118
|
+
if parameter in keyword_only
|
|
119
|
+
else f"${{{index}:{parameter}}}"
|
|
120
|
+
)
|
|
121
|
+
for index, parameter in enumerate(required, start=1)
|
|
99
122
|
)
|
|
100
123
|
return f"{name}({placeholders})"
|
|
101
124
|
|
|
@@ -166,7 +189,21 @@ def _fields(cls: type) -> list[dict[str, Any]]:
|
|
|
166
189
|
#: Module-level helpers a notebook can call directly. Offered alongside `ctx`
|
|
167
190
|
#: and `Asset` because `namespace()` pre-binds them, so they are as available as
|
|
168
191
|
#: the import line says they are.
|
|
169
|
-
|
|
192
|
+
#:
|
|
193
|
+
#: The relationship builders are here rather than left to be typed from memory
|
|
194
|
+
#: for the same reason they exist at all: an author who cannot see that `flow`
|
|
195
|
+
#: and `contains` are different functions will reach for `links` and flatten the
|
|
196
|
+
#: distinction back out.
|
|
197
|
+
MODULE_FUNCTIONS = (
|
|
198
|
+
("parse", parse),
|
|
199
|
+
("pages", pages),
|
|
200
|
+
("flow", flow),
|
|
201
|
+
("contains", contains),
|
|
202
|
+
("references", references),
|
|
203
|
+
("same_as", same_as),
|
|
204
|
+
("uses", uses),
|
|
205
|
+
("urn_for", urn_for),
|
|
206
|
+
)
|
|
170
207
|
|
|
171
208
|
|
|
172
209
|
def _function_entry(name: str, func: Any) -> dict[str, Any]:
|
|
@@ -201,6 +238,27 @@ def build() -> dict[str, Any]:
|
|
|
201
238
|
"documentation": _summary(Asset),
|
|
202
239
|
"insertText": "Asset",
|
|
203
240
|
},
|
|
241
|
+
{
|
|
242
|
+
"label": "Ref",
|
|
243
|
+
"kind": "class",
|
|
244
|
+
"detail": "Ref.asset(id) | Ref.urn(urn)",
|
|
245
|
+
"documentation": _summary(Ref),
|
|
246
|
+
"insertText": "Ref",
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"label": "FieldMapping",
|
|
250
|
+
"kind": "class",
|
|
251
|
+
"detail": "FieldMapping(downstream, upstreams, transform)",
|
|
252
|
+
"documentation": _summary(FieldMapping),
|
|
253
|
+
"insertText": "FieldMapping",
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"label": "FlowType",
|
|
257
|
+
"kind": "class",
|
|
258
|
+
"detail": "TRANSFORM | VIEW | COPY | WRITE | EXPORT | SEND",
|
|
259
|
+
"documentation": _summary(FlowType),
|
|
260
|
+
"insertText": "FlowType",
|
|
261
|
+
},
|
|
204
262
|
*(_function_entry(name, func) for name, func in MODULE_FUNCTIONS),
|
|
205
263
|
],
|
|
206
264
|
"objects": {
|
|
@@ -222,6 +280,10 @@ def build() -> dict[str, Any]:
|
|
|
222
280
|
"fields": _fields(NotebookFile),
|
|
223
281
|
"members": _members(NotebookFile),
|
|
224
282
|
},
|
|
283
|
+
"FieldMapping": {
|
|
284
|
+
"documentation": _summary(FieldMapping),
|
|
285
|
+
"fields": _fields(FieldMapping),
|
|
286
|
+
},
|
|
225
287
|
},
|
|
226
288
|
"contract": {
|
|
227
289
|
"required": list(REQUIRED_FUNCTIONS),
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Typed relationship edges, and the platform names that let them cross systems."""
|
|
2
|
+
|
|
3
|
+
from .edges import (
|
|
4
|
+
ContainmentType,
|
|
5
|
+
Edge,
|
|
6
|
+
EdgeClass,
|
|
7
|
+
FieldMapping,
|
|
8
|
+
FieldTransform,
|
|
9
|
+
FlowType,
|
|
10
|
+
Method,
|
|
11
|
+
Ref,
|
|
12
|
+
ReferenceType,
|
|
13
|
+
UsageType,
|
|
14
|
+
contains,
|
|
15
|
+
flow,
|
|
16
|
+
references,
|
|
17
|
+
same_as,
|
|
18
|
+
uses,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"ContainmentType",
|
|
23
|
+
"Edge",
|
|
24
|
+
"EdgeClass",
|
|
25
|
+
"FieldMapping",
|
|
26
|
+
"FieldTransform",
|
|
27
|
+
"FlowType",
|
|
28
|
+
"Method",
|
|
29
|
+
"Ref",
|
|
30
|
+
"ReferenceType",
|
|
31
|
+
"UsageType",
|
|
32
|
+
"contains",
|
|
33
|
+
"flow",
|
|
34
|
+
"references",
|
|
35
|
+
"same_as",
|
|
36
|
+
"uses",
|
|
37
|
+
]
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
"""The typed relationship vocabulary a connector emits.
|
|
2
|
+
|
|
3
|
+
Everything a connector knows about how two assets relate arrives here, and the
|
|
4
|
+
only thing this module really enforces is the distinction that a flat list of
|
|
5
|
+
links cannot make: **what does traversing this edge mean?**
|
|
6
|
+
|
|
7
|
+
Four classes, and only one of them is lineage:
|
|
8
|
+
|
|
9
|
+
``FLOW`` the values in B come from A. The only class where "what breaks
|
|
10
|
+
if I change this" is a meaningful question.
|
|
11
|
+
``CONTAINMENT`` A is a part of B; dropping B drops A. Column in table, chart in
|
|
12
|
+
dashboard, member in archive. Used to *collapse* a graph, never
|
|
13
|
+
to add a hop to it.
|
|
14
|
+
``IDENTITY`` A and B are the same real thing seen from two systems. Used to
|
|
15
|
+
*merge* nodes.
|
|
16
|
+
``REFERENCE`` everything about meaning or navigation -- foreign keys, "see
|
|
17
|
+
also", glossary terms. Propagates nothing.
|
|
18
|
+
``USAGE`` who touched it. An edge weight, not a path.
|
|
19
|
+
|
|
20
|
+
Flattening these into one "links" list is what produces lineage graphs that are
|
|
21
|
+
technically correct and unreadable: PII tags travelling downstream through a
|
|
22
|
+
join, or a dbt model showing up as an extra hop in every path.
|
|
23
|
+
|
|
24
|
+
**Direction.** A ``FLOW`` edge points *the way the data moves*: upstream to
|
|
25
|
+
downstream, so the arrow on screen follows the data and a downstream traversal
|
|
26
|
+
is an outward one. :func:`flow` takes both ends as keyword-only arguments
|
|
27
|
+
because reversing them is the easiest mistake in the whole subject and the
|
|
28
|
+
hardest to notice afterwards.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
from collections.abc import Iterable, Sequence
|
|
34
|
+
from dataclasses import dataclass, field
|
|
35
|
+
from enum import StrEnum
|
|
36
|
+
from typing import Any
|
|
37
|
+
|
|
38
|
+
from ..utils.urn import Urn
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"ContainmentType",
|
|
42
|
+
"Edge",
|
|
43
|
+
"EdgeClass",
|
|
44
|
+
"FieldMapping",
|
|
45
|
+
"FieldTransform",
|
|
46
|
+
"FlowType",
|
|
47
|
+
"Method",
|
|
48
|
+
"Ref",
|
|
49
|
+
"ReferenceType",
|
|
50
|
+
"UsageType",
|
|
51
|
+
"contains",
|
|
52
|
+
"edge_from_payload",
|
|
53
|
+
"edge_to_payload",
|
|
54
|
+
"flow",
|
|
55
|
+
"references",
|
|
56
|
+
"same_as",
|
|
57
|
+
"uses",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class EdgeClass(StrEnum):
|
|
62
|
+
FLOW = "FLOW"
|
|
63
|
+
CONTAINMENT = "CONTAINMENT"
|
|
64
|
+
IDENTITY = "IDENTITY"
|
|
65
|
+
REFERENCE = "REFERENCE"
|
|
66
|
+
USAGE = "USAGE"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class FlowType(StrEnum):
|
|
70
|
+
"""Subtypes of lineage, named so they do not imply a direction.
|
|
71
|
+
|
|
72
|
+
A verb would: "DERIVED_FROM" reads backwards on an edge that points
|
|
73
|
+
downstream. These are the *nature* of the derivation instead, and the
|
|
74
|
+
direction is carried by the edge itself.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
TRANSFORM = "TRANSFORM" # computed from, with real logic in between
|
|
78
|
+
VIEW = "VIEW" # a view over its base tables
|
|
79
|
+
COPY = "COPY" # replica, CDC mirror, CLONE -- identity-preserving
|
|
80
|
+
WRITE = "WRITE" # a process produced this
|
|
81
|
+
EXPORT = "EXPORT" # left the system
|
|
82
|
+
SEND = "SEND" # delivered to a recipient
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class ContainmentType(StrEnum):
|
|
86
|
+
CONTAINS = "CONTAINS"
|
|
87
|
+
ATTACHED_TO = "ATTACHED_TO"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class ReferenceType(StrEnum):
|
|
91
|
+
REFERENCES = "REFERENCES"
|
|
92
|
+
MENTIONS = "MENTIONS"
|
|
93
|
+
FOREIGN_KEY = "FOREIGN_KEY"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class UsageType(StrEnum):
|
|
97
|
+
OWNS = "OWNS"
|
|
98
|
+
ACCESSED = "ACCESSED"
|
|
99
|
+
READS = "READS"
|
|
100
|
+
EXECUTED = "EXECUTED"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class Method(StrEnum):
|
|
104
|
+
"""How the edge was derived -- which is how much it should be trusted.
|
|
105
|
+
|
|
106
|
+
Ordered strongest first. A runtime observation saw the data move; a SQL
|
|
107
|
+
parse only read the intent; a heuristic guessed. Keeping this per-edge is
|
|
108
|
+
what lets two connectors disagree about the same pair without either of
|
|
109
|
+
them having to win at write time.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
RUNTIME_OBSERVED = "RUNTIME_OBSERVED"
|
|
113
|
+
SYSTEM_CATALOG = "SYSTEM_CATALOG"
|
|
114
|
+
SQL_PARSED = "SQL_PARSED"
|
|
115
|
+
HEURISTIC = "HEURISTIC"
|
|
116
|
+
MANUAL = "MANUAL"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
#: Default confidence per method. A connector may override, but should not have
|
|
120
|
+
#: to think about it for the ordinary case.
|
|
121
|
+
_METHOD_CONFIDENCE: dict[Method, float] = {
|
|
122
|
+
Method.RUNTIME_OBSERVED: 1.0,
|
|
123
|
+
Method.SYSTEM_CATALOG: 0.95,
|
|
124
|
+
Method.SQL_PARSED: 0.8,
|
|
125
|
+
Method.HEURISTIC: 0.5,
|
|
126
|
+
Method.MANUAL: 1.0,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class FieldTransform(StrEnum):
|
|
131
|
+
IDENTITY = "IDENTITY" # copied through unchanged
|
|
132
|
+
TRANSFORMED = "TRANSFORMED" # an expression combined the upstreams
|
|
133
|
+
AGGREGATED = "AGGREGATED" # SUM/COUNT/... over a group
|
|
134
|
+
INDIRECT = "INDIRECT" # influenced the rows, not the values
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@dataclass(frozen=True)
|
|
138
|
+
class FieldMapping:
|
|
139
|
+
"""One column-level dependency inside a dataset-level flow edge.
|
|
140
|
+
|
|
141
|
+
``downstream=None`` records an *indirect* dependency -- a column that shaped
|
|
142
|
+
which rows came out (an ``ORDER BY``, a ``WHERE``, a join key) without
|
|
143
|
+
feeding any particular output column. Keeping it against the dataset instead
|
|
144
|
+
of against every output column is what stops indirect dependencies from
|
|
145
|
+
fanning out across the whole mapping, and it is the one thing warehouse
|
|
146
|
+
lineage tables have no way to say at all.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
downstream: str | None
|
|
150
|
+
upstreams: Sequence[str] = ()
|
|
151
|
+
transform: str | None = None
|
|
152
|
+
type: FieldTransform = FieldTransform.TRANSFORMED
|
|
153
|
+
|
|
154
|
+
def __post_init__(self) -> None:
|
|
155
|
+
cleaned = tuple(str(name).strip() for name in self.upstreams if str(name).strip())
|
|
156
|
+
object.__setattr__(self, "upstreams", cleaned)
|
|
157
|
+
if self.downstream is not None:
|
|
158
|
+
object.__setattr__(self, "downstream", str(self.downstream).strip() or None)
|
|
159
|
+
if self.downstream is None and self.type is not FieldTransform.INDIRECT:
|
|
160
|
+
# A mapping with no output column is only meaningful as an indirect
|
|
161
|
+
# dependency; silently keeping it as TRANSFORMED would render as a
|
|
162
|
+
# column-to-nothing arrow in the UI.
|
|
163
|
+
object.__setattr__(self, "type", FieldTransform.INDIRECT)
|
|
164
|
+
|
|
165
|
+
def to_payload(self) -> dict[str, Any]:
|
|
166
|
+
return {
|
|
167
|
+
"downstream": self.downstream,
|
|
168
|
+
"upstreams": list(self.upstreams),
|
|
169
|
+
"transform": self.transform,
|
|
170
|
+
"type": str(self.type),
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@dataclass(frozen=True)
|
|
175
|
+
class Ref:
|
|
176
|
+
"""One end of an edge.
|
|
177
|
+
|
|
178
|
+
``asset`` names something this run produced, by its hash. ``urn`` names an
|
|
179
|
+
object by what its *platform* calls it -- which is how a connector points at
|
|
180
|
+
a table in a system it does not scan. An unresolved URN is kept and stitched
|
|
181
|
+
when the other system is eventually ingested, rather than dropped.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
kind: str
|
|
185
|
+
value: str
|
|
186
|
+
|
|
187
|
+
@staticmethod
|
|
188
|
+
def asset(asset_hash: str) -> Ref:
|
|
189
|
+
value = str(asset_hash or "").strip()
|
|
190
|
+
if not value:
|
|
191
|
+
raise ValueError("Ref.asset() needs an asset hash")
|
|
192
|
+
return Ref("asset", value)
|
|
193
|
+
|
|
194
|
+
@staticmethod
|
|
195
|
+
def urn(urn: str | Urn) -> Ref:
|
|
196
|
+
return Ref("urn", str(Urn.parse(urn)))
|
|
197
|
+
|
|
198
|
+
@staticmethod
|
|
199
|
+
def finding(finding_id: str) -> Ref:
|
|
200
|
+
value = str(finding_id or "").strip()
|
|
201
|
+
if not value:
|
|
202
|
+
raise ValueError("Ref.finding() needs a finding id")
|
|
203
|
+
return Ref("finding", value)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@dataclass(frozen=True)
|
|
207
|
+
class Edge:
|
|
208
|
+
"""A directed, classified relationship between two graph entities."""
|
|
209
|
+
|
|
210
|
+
frm: Ref
|
|
211
|
+
to: Ref
|
|
212
|
+
edge_class: EdgeClass
|
|
213
|
+
relation_type: str
|
|
214
|
+
confidence: float = 1.0
|
|
215
|
+
method: Method = Method.SYSTEM_CATALOG
|
|
216
|
+
fields: tuple[FieldMapping, ...] = ()
|
|
217
|
+
via: Ref | None = None
|
|
218
|
+
evidence: dict[str, Any] = field(default_factory=dict)
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def granularity(self) -> str:
|
|
222
|
+
return "FIELD" if self.fields else "DATASET"
|
|
223
|
+
|
|
224
|
+
def to_ingest(self) -> Any:
|
|
225
|
+
"""Convert to the wire model the REST sink posts.
|
|
226
|
+
|
|
227
|
+
Imported lazily: this module is part of the connector-facing SDK and
|
|
228
|
+
must not drag the HTTP sink into a notebook's import graph.
|
|
229
|
+
"""
|
|
230
|
+
from ..outputs.rest import IngestEdge
|
|
231
|
+
|
|
232
|
+
payload: dict[str, Any] = {
|
|
233
|
+
"from_type": "asset" if self.frm.kind != "finding" else "finding",
|
|
234
|
+
"to_type": "asset" if self.to.kind != "finding" else "finding",
|
|
235
|
+
"relation_type": str(self.relation_type),
|
|
236
|
+
"relation_class": str(self.edge_class),
|
|
237
|
+
"granularity": self.granularity,
|
|
238
|
+
"method": str(self.method),
|
|
239
|
+
"confidence": round(float(self.confidence), 2),
|
|
240
|
+
}
|
|
241
|
+
_apply_endpoint(payload, self.frm, "from")
|
|
242
|
+
_apply_endpoint(payload, self.to, "to")
|
|
243
|
+
if self.fields:
|
|
244
|
+
payload["field_mappings"] = [mapping.to_payload() for mapping in self.fields]
|
|
245
|
+
if self.evidence:
|
|
246
|
+
payload["evidence"] = dict(self.evidence)
|
|
247
|
+
if self.via is not None:
|
|
248
|
+
payload["via_id"] = self.via.value if self.via.kind == "asset" else None
|
|
249
|
+
payload["via_urn"] = self.via.value if self.via.kind == "urn" else None
|
|
250
|
+
return IngestEdge(**payload)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def edge_to_payload(edge: Edge) -> dict[str, Any]:
|
|
254
|
+
"""Plain-JSON form, for crossing the notebook subprocess boundary."""
|
|
255
|
+
return {
|
|
256
|
+
"from": {"kind": edge.frm.kind, "value": edge.frm.value},
|
|
257
|
+
"to": {"kind": edge.to.kind, "value": edge.to.value},
|
|
258
|
+
"class": str(edge.edge_class),
|
|
259
|
+
"relationType": str(edge.relation_type),
|
|
260
|
+
"confidence": edge.confidence,
|
|
261
|
+
"method": str(edge.method),
|
|
262
|
+
"fields": [mapping.to_payload() for mapping in edge.fields],
|
|
263
|
+
"evidence": dict(edge.evidence),
|
|
264
|
+
"via": None if edge.via is None else {"kind": edge.via.kind, "value": edge.via.value},
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def edge_from_payload(payload: dict[str, Any]) -> Edge:
|
|
269
|
+
"""Rebuild an Edge on the parent side of the notebook boundary."""
|
|
270
|
+
|
|
271
|
+
def ref(value: Any) -> Ref | None:
|
|
272
|
+
if not isinstance(value, dict):
|
|
273
|
+
return None
|
|
274
|
+
kind = str(value.get("kind") or "asset")
|
|
275
|
+
text = str(value.get("value") or "")
|
|
276
|
+
return Ref(kind, text) if text else None
|
|
277
|
+
|
|
278
|
+
frm = ref(payload.get("from"))
|
|
279
|
+
to = ref(payload.get("to"))
|
|
280
|
+
if frm is None or to is None:
|
|
281
|
+
raise ValueError("A relationship needs both ends")
|
|
282
|
+
|
|
283
|
+
return Edge(
|
|
284
|
+
frm=frm,
|
|
285
|
+
to=to,
|
|
286
|
+
edge_class=EdgeClass(str(payload.get("class") or EdgeClass.REFERENCE)),
|
|
287
|
+
relation_type=str(payload.get("relationType") or "REFERENCES"),
|
|
288
|
+
confidence=float(payload.get("confidence") or 1.0),
|
|
289
|
+
method=Method(str(payload.get("method") or Method.SYSTEM_CATALOG)),
|
|
290
|
+
fields=tuple(
|
|
291
|
+
FieldMapping(
|
|
292
|
+
downstream=item.get("downstream"),
|
|
293
|
+
upstreams=item.get("upstreams") or [],
|
|
294
|
+
transform=item.get("transform"),
|
|
295
|
+
type=FieldTransform(str(item.get("type") or FieldTransform.TRANSFORMED)),
|
|
296
|
+
)
|
|
297
|
+
for item in payload.get("fields") or []
|
|
298
|
+
if isinstance(item, dict)
|
|
299
|
+
),
|
|
300
|
+
via=ref(payload.get("via")),
|
|
301
|
+
evidence=dict(payload.get("evidence") or {}),
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _apply_endpoint(payload: dict[str, Any], ref: Ref, side: str) -> None:
|
|
306
|
+
if ref.kind == "urn":
|
|
307
|
+
payload[f"{side}_urn"] = ref.value
|
|
308
|
+
else:
|
|
309
|
+
payload[f"{side}_hash"] = ref.value
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _confidence(method: Method, override: float | None) -> float:
|
|
313
|
+
if override is not None:
|
|
314
|
+
return max(0.0, min(1.0, float(override)))
|
|
315
|
+
return _METHOD_CONFIDENCE.get(method, 1.0)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
# ── Builders ─────────────────────────────────────────────────────────────
|
|
319
|
+
#
|
|
320
|
+
# One per class. The class is never a free-text argument, so a connector cannot
|
|
321
|
+
# accidentally file a containment relation as lineage -- it would have to call a
|
|
322
|
+
# different function to do it.
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def flow(
|
|
326
|
+
*,
|
|
327
|
+
upstream: Ref,
|
|
328
|
+
downstream: Ref,
|
|
329
|
+
type: FlowType = FlowType.TRANSFORM,
|
|
330
|
+
fields: Iterable[FieldMapping] | None = None,
|
|
331
|
+
via: Ref | None = None,
|
|
332
|
+
sql: str | None = None,
|
|
333
|
+
method: Method = Method.SYSTEM_CATALOG,
|
|
334
|
+
confidence: float | None = None,
|
|
335
|
+
evidence: dict[str, Any] | None = None,
|
|
336
|
+
) -> Edge:
|
|
337
|
+
"""Data moves from ``upstream`` into ``downstream``.
|
|
338
|
+
|
|
339
|
+
Both ends are keyword-only on purpose: a reversed lineage edge is silently
|
|
340
|
+
wrong rather than loudly broken, and it is the mistake everyone makes once.
|
|
341
|
+
"""
|
|
342
|
+
detail = dict(evidence or {})
|
|
343
|
+
if sql:
|
|
344
|
+
detail["sql"] = sql
|
|
345
|
+
return Edge(
|
|
346
|
+
frm=upstream,
|
|
347
|
+
to=downstream,
|
|
348
|
+
edge_class=EdgeClass.FLOW,
|
|
349
|
+
relation_type=str(type),
|
|
350
|
+
confidence=_confidence(method, confidence),
|
|
351
|
+
method=method,
|
|
352
|
+
fields=tuple(fields or ()),
|
|
353
|
+
via=via,
|
|
354
|
+
evidence=detail,
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def contains(parent: Ref, child: Ref, *, type: ContainmentType = ContainmentType.CONTAINS) -> Edge:
|
|
359
|
+
"""``child`` is a part of ``parent`` -- structural, not temporal."""
|
|
360
|
+
return Edge(
|
|
361
|
+
frm=parent,
|
|
362
|
+
to=child,
|
|
363
|
+
edge_class=EdgeClass.CONTAINMENT,
|
|
364
|
+
relation_type=str(type),
|
|
365
|
+
method=Method.SYSTEM_CATALOG,
|
|
366
|
+
confidence=1.0,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def same_as(a: Ref, b: Ref, *, method: Method = Method.SYSTEM_CATALOG) -> Edge:
|
|
371
|
+
"""The same real-world object, seen from two systems."""
|
|
372
|
+
return Edge(
|
|
373
|
+
frm=a,
|
|
374
|
+
to=b,
|
|
375
|
+
edge_class=EdgeClass.IDENTITY,
|
|
376
|
+
relation_type="SAME_AS",
|
|
377
|
+
method=method,
|
|
378
|
+
confidence=_confidence(method, None),
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def references(
|
|
383
|
+
frm: Ref,
|
|
384
|
+
to: Ref,
|
|
385
|
+
*,
|
|
386
|
+
type: ReferenceType = ReferenceType.REFERENCES,
|
|
387
|
+
method: Method = Method.SYSTEM_CATALOG,
|
|
388
|
+
) -> Edge:
|
|
389
|
+
"""``frm`` points at ``to``. No data moves -- a foreign key is not lineage."""
|
|
390
|
+
return Edge(
|
|
391
|
+
frm=frm,
|
|
392
|
+
to=to,
|
|
393
|
+
edge_class=EdgeClass.REFERENCE,
|
|
394
|
+
relation_type=str(type),
|
|
395
|
+
method=method,
|
|
396
|
+
confidence=_confidence(method, None),
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def uses(actor: Ref, target: Ref, *, type: UsageType = UsageType.ACCESSED) -> Edge:
|
|
401
|
+
"""``actor`` touched ``target``. Answers "who", not "where did this come from"."""
|
|
402
|
+
return Edge(
|
|
403
|
+
frm=actor,
|
|
404
|
+
to=target,
|
|
405
|
+
edge_class=EdgeClass.USAGE,
|
|
406
|
+
relation_type=str(type),
|
|
407
|
+
method=Method.RUNTIME_OBSERVED,
|
|
408
|
+
confidence=1.0,
|
|
409
|
+
)
|